From 62676e72a85cd23e7a87d94adff96d17859dbdc5 Mon Sep 17 00:00:00 2001 From: jdawg1290 Date: Wed, 15 Jul 2020 19:02:40 -0500 Subject: [PATCH] Force LF line endings with gitattributes and convert repo (#52266) Co-authored-by: Aleksej Komarov --- .dockerignore | 60 +- .editorconfig | 1 + .gitattributes | 47 +- .vscode/extensions.json | 16 +- Dockerfile | 124 +- README.md | 106 +- SQL/tgstation_schema.sql | 1166 +- TGS3.json | 44 +- _maps/multiz_debug.json | 12 +- _maps/runtimestation.json | 16 +- _maps/templates/shelter_3.dmm | 850 +- code/__DEFINES/_helpers.dm | 18 +- code/__DEFINES/_protect.dm | 22 +- code/__DEFINES/_readme.dm | 28 +- code/__DEFINES/admin.dm | 286 +- code/__DEFINES/atmospherics.dm | 832 +- code/__DEFINES/combat.dm | 488 +- code/__DEFINES/configuration.dm | 22 +- code/__DEFINES/cooldowns.dm | 142 +- code/__DEFINES/forensics.dm | 2 +- code/__DEFINES/hud.dm | 30 +- code/__DEFINES/interaction_flags.dm | 76 +- code/__DEFINES/jobs.dm | 188 +- code/__DEFINES/machines.dm | 256 +- code/__DEFINES/move_force.dm | 40 +- code/__DEFINES/networks.dm | 6 +- code/__DEFINES/preferences.dm | 240 +- code/__DEFINES/radio.dm | 232 +- code/__DEFINES/reagents_specific_heat.dm | 6 +- code/__DEFINES/research.dm | 180 +- code/__DEFINES/sight.dm | 70 +- code/__DEFINES/sound.dm | 166 +- code/__DEFINES/stat.dm | 42 +- code/__DEFINES/tgs.config.dm | 24 +- code/__DEFINES/tools.dm | 42 +- code/__DEFINES/wires.dm | 108 +- code/__HELPERS/_lists.dm | 1122 +- code/__HELPERS/_string_lists.dm | 84 +- code/__HELPERS/files.dm | 160 +- code/__HELPERS/game.dm | 1162 +- code/__HELPERS/global_lists.dm | 142 +- code/__HELPERS/icons.dm | 2428 +- code/__HELPERS/matrices.dm | 356 +- code/__HELPERS/mobs.dm | 1212 +- code/__HELPERS/mouse_control.dm | 104 +- code/__HELPERS/names.dm | 452 +- code/__HELPERS/qdel.dm | 20 +- code/__HELPERS/radio.dm | 38 +- code/__HELPERS/sanitize_values.dm | 176 +- code/__HELPERS/text.dm | 1762 +- code/__HELPERS/time.dm | 176 +- code/__HELPERS/type2type.dm | 978 +- code/_compile_options.dm | 128 +- code/_globalvars/bitfields.dm | 434 +- code/_globalvars/configuration.dm | 72 +- code/_globalvars/game_modes.dm | 30 +- code/_globalvars/genetics.dm | 18 +- code/_globalvars/lists/flavor_misc.dm | 500 +- code/_globalvars/lists/mapping.dm | 94 +- code/_globalvars/lists/mobs.dm | 166 +- code/_globalvars/lists/names.dm | 104 +- code/_globalvars/lists/objects.dm | 86 +- code/_globalvars/logging.dm | 162 +- code/_globalvars/misc.dm | 56 +- code/_js/byjax.dm | 96 +- code/_js/menus.dm | 74 +- code/_onclick/adjacent.dm | 210 +- code/_onclick/click.dm | 966 +- code/_onclick/cyborg.dm | 352 +- code/_onclick/drag_drop.dm | 230 +- code/_onclick/hud/_defines.dm | 348 +- code/_onclick/hud/alien.dm | 264 +- code/_onclick/hud/alien_larva.dm | 74 +- code/_onclick/hud/credits.dm | 138 +- code/_onclick/hud/fullscreen.dm | 344 +- code/_onclick/hud/ghost.dm | 232 +- code/_onclick/hud/hud.dm | 592 +- code/_onclick/hud/human.dm | 974 +- code/_onclick/hud/monkey.dm | 338 +- code/_onclick/hud/robot.dm | 574 +- code/_onclick/hud/screen_objects.dm | 1446 +- code/_onclick/item_attack.dm | 384 +- code/_onclick/observer.dm | 162 +- code/_onclick/other_mobs.dm | 520 +- code/_onclick/overmind.dm | 72 +- .../controllers/configuration/config_entry.dm | 394 +- .../configuration/configuration.dm | 848 +- .../configuration/entries/comms.dm | 46 +- .../configuration/entries/dbconfig.dm | 100 +- .../configuration/entries/game_options.dm | 808 +- .../configuration/entries/general.dm | 1010 +- code/controllers/failsafe.dm | 204 +- code/controllers/globals.dm | 112 +- code/controllers/subsystem/atoms.dm | 312 +- code/controllers/subsystem/blackbox.dm | 698 +- code/controllers/subsystem/dbcore.dm | 792 +- code/controllers/subsystem/discord.dm | 370 +- code/controllers/subsystem/moods.dm | 8 +- code/controllers/subsystem/nightshift.dm | 110 +- code/controllers/subsystem/npcpool.dm | 68 +- code/controllers/subsystem/pathfinder.dm | 96 +- .../subsystem/processing/networks.dm | 102 +- .../subsystem/processing/projectiles.dm | 46 +- .../subsystem/processing/wet_floors.dm | 14 +- code/controllers/subsystem/research.dm | 582 +- code/controllers/subsystem/spacedrift.dm | 118 +- code/controllers/subsystem/vote.dm | 762 +- code/datums/actions/beam_rifle.dm | 24 +- code/datums/actions/ninja.dm | 102 +- code/datums/ai_laws.dm | 938 +- code/datums/browser.dm | 946 +- code/datums/components/README.md | 18 +- code/datums/components/_component.dm | 1074 +- code/datums/components/creamed.dm | 124 +- code/datums/components/decals/blood.dm | 78 +- code/datums/components/edible.dm | 494 +- code/datums/components/forensics.dm | 368 +- code/datums/components/jousting.dm | 148 +- code/datums/components/lockon_aiming.dm | 428 +- code/datums/components/nanites.dm | 762 +- code/datums/components/ntnet_interface.dm | 132 +- code/datums/components/riding.dm | 790 +- code/datums/components/slippery.dm | 72 +- code/datums/components/spill.dm | 114 +- .../components/storage/concrete/_concrete.dm | 402 +- .../storage/concrete/bag_of_holding.dm | 46 +- .../components/storage/concrete/bluespace.dm | 44 +- .../components/storage/concrete/implant.dm | 36 +- .../components/storage/concrete/pockets.dm | 186 +- .../components/storage/concrete/rped.dm | 66 +- .../components/storage/concrete/stack.dm | 134 +- code/datums/components/storage/storage.dm | 1636 +- code/datums/components/wet_floor.dm | 412 +- code/datums/datacore.dm | 626 +- code/datums/datum.dm | 524 +- code/datums/datumvars.dm | 104 +- code/datums/diseases/advance/advance.dm | 1018 +- code/datums/diseases/advance/presets.dm | 84 +- .../datums/diseases/advance/symptoms/beard.dm | 86 +- .../diseases/advance/symptoms/choking.dm | 304 +- .../diseases/advance/symptoms/confusion.dm | 128 +- .../datums/diseases/advance/symptoms/cough.dm | 156 +- .../diseases/advance/symptoms/deafness.dm | 126 +- .../datums/diseases/advance/symptoms/dizzy.dm | 112 +- .../datums/diseases/advance/symptoms/fever.dm | 152 +- .../diseases/advance/symptoms/flesh_eating.dm | 270 +- .../diseases/advance/symptoms/genetics.dm | 140 +- .../diseases/advance/symptoms/hallucigen.dm | 140 +- .../diseases/advance/symptoms/headache.dm | 124 +- code/datums/diseases/advance/symptoms/heal.dm | 1000 +- .../diseases/advance/symptoms/itching.dm | 112 +- .../diseases/advance/symptoms/oxygen.dm | 138 +- .../diseases/advance/symptoms/shedding.dm | 110 +- .../diseases/advance/symptoms/shivering.dm | 150 +- .../diseases/advance/symptoms/sneeze.dm | 130 +- .../diseases/advance/symptoms/symptoms.dm | 162 +- .../diseases/advance/symptoms/voice_change.dm | 150 +- .../datums/diseases/advance/symptoms/vomit.dm | 130 +- .../diseases/advance/symptoms/weight.dm | 106 +- .../datums/diseases/advance/symptoms/youth.dm | 116 +- code/datums/diseases/appendicitis.dm | 72 +- code/datums/diseases/beesease.dm | 78 +- code/datums/diseases/brainrot.dm | 120 +- code/datums/diseases/cold.dm | 106 +- code/datums/diseases/cold9.dm | 78 +- code/datums/diseases/dna_spread.dm | 148 +- code/datums/diseases/fake_gbs.dm | 64 +- code/datums/diseases/flu.dm | 108 +- code/datums/diseases/fluspanish.dm | 72 +- code/datums/diseases/gbs.dm | 62 +- code/datums/diseases/magnitis.dm | 136 +- code/datums/diseases/pierrot_throat.dm | 114 +- code/datums/diseases/retrovirus.dm | 168 +- code/datums/diseases/rhumba_beat.dm | 90 +- code/datums/diseases/transformation.dm | 658 +- code/datums/diseases/wizarditis.dm | 234 +- code/datums/dna.dm | 1340 +- code/datums/forced_movement.dm | 186 +- code/datums/helper_datums/events.dm | 110 +- code/datums/helper_datums/getrev.dm | 256 +- code/datums/helper_datums/teleport.dm | 332 +- code/datums/holocall.dm | 932 +- code/datums/http.dm | 148 +- code/datums/hud.dm | 296 +- code/datums/map_config.dm | 286 +- code/datums/mind.dm | 1650 +- code/datums/mood_events/_mood_event.dm | 50 +- code/datums/mood_events/drug_events.dm | 160 +- code/datums/mood_events/needs_events.dm | 186 +- code/datums/numbered_display.dm | 20 +- code/datums/position_point_vector.dm | 452 +- code/datums/recipe.dm | 240 +- code/datums/spawners_menu.dm | 124 +- code/datums/verbs.dm | 204 +- code/datums/wires/_wires.dm | 594 +- code/datums/wires/airalarm.dm | 148 +- code/datums/wires/airlock.dm | 280 +- code/datums/wires/apc.dm | 110 +- code/datums/wires/autolathe.dm | 96 +- code/datums/wires/explosive.dm | 240 +- code/datums/wires/mulebot.dm | 68 +- code/datums/wires/particle_accelerator.dm | 96 +- code/datums/wires/radio.dm | 50 +- code/datums/wires/robot.dm | 172 +- code/datums/wires/suit_storage_unit.dm | 90 +- code/datums/wires/syndicatebomb.dm | 182 +- code/datums/wires/vending.dm | 124 +- code/datums/world_topic.dm | 370 +- code/game/area/Space_Station_13_areas.dm | 2524 +- code/game/area/ai_monitored.dm | 60 +- code/game/atoms.dm | 2866 +- code/game/atoms_movable.dm | 1958 +- code/game/communications.dm | 398 +- code/game/gamemodes/brother/traitor_bro.dm | 134 +- code/game/gamemodes/changeling/changeling.dm | 286 +- .../game/gamemodes/changeling/traitor_chan.dm | 176 +- code/game/gamemodes/events.dm | 132 +- code/game/gamemodes/extended/extended.dm | 58 +- code/game/gamemodes/game_mode.dm | 1204 +- code/game/gamemodes/gang/gang_handler.dm | 1232 +- code/game/gamemodes/meteor/meteor.dm | 120 +- code/game/gamemodes/objective_items.dm | 542 +- code/game/gamemodes/sandbox/airlock_maker.dm | 282 +- code/game/gamemodes/sandbox/h_sandbox.dm | 604 +- code/game/gamemodes/sandbox/sandbox.dm | 44 +- code/game/gamemodes/traitor/double_agents.dm | 166 +- code/game/gamemodes/traitor/traitor.dm | 206 +- code/game/gamemodes/wizard/wizard.dm | 164 +- code/game/machinery/Beacon.dm | 60 +- code/game/machinery/PDApainter.dm | 290 +- code/game/machinery/Sleeper.dm | 646 +- code/game/machinery/ai_slipper.dm | 86 +- code/game/machinery/airlock_control.dm | 328 +- code/game/machinery/autolathe.dm | 866 +- code/game/machinery/buttons.dm | 580 +- code/game/machinery/camera/camera_assembly.dm | 574 +- code/game/machinery/camera/motion.dm | 224 +- code/game/machinery/camera/presets.dm | 286 +- code/game/machinery/camera/tracking.dm | 308 +- code/game/machinery/cell_charger.dm | 262 +- code/game/machinery/computer/Operating.dm | 310 +- code/game/machinery/computer/atmos_alert.dm | 180 +- code/game/machinery/computer/atmos_control.dm | 670 +- .../game/machinery/computer/buildandrepair.dm | 286 +- code/game/machinery/computer/camera.dm | 680 +- code/game/machinery/computer/card.dm | 1268 +- .../game/machinery/computer/communications.dm | 1558 +- code/game/machinery/computer/crew.dm | 472 +- code/game/machinery/computer/dna_console.dm | 4022 +- code/game/machinery/computer/law.dm | 154 +- code/game/machinery/computer/medical.dm | 1156 +- code/game/machinery/computer/pod.dm | 266 +- code/game/machinery/computer/robot.dm | 254 +- code/game/machinery/computer/station_alert.dm | 182 +- code/game/machinery/constructable_frame.dm | 562 +- code/game/machinery/doors/alarmlock.dm | 86 +- code/game/machinery/doors/brigdoors.dm | 510 +- code/game/machinery/doors/door.dm | 860 +- code/game/machinery/doors/firedoor.dm | 966 +- code/game/machinery/doors/poddoor.dm | 232 +- code/game/machinery/doors/shutters.dm | 34 +- code/game/machinery/doors/unpowered.dm | 50 +- code/game/machinery/doors/windowdoor.dm | 962 +- code/game/machinery/doppler_array.dm | 486 +- .../embedded_controller/access_controller.dm | 618 +- .../embedded_controller/airlock_controller.dm | 630 +- .../embedded_controller_base.dm | 170 +- .../simple_vent_controller.dm | 144 +- code/game/machinery/firealarm.dm | 694 +- code/game/machinery/flasher.dm | 410 +- code/game/machinery/hologram.dm | 1402 +- code/game/machinery/igniter.dm | 276 +- code/game/machinery/lightswitch.dm | 128 +- code/game/machinery/magnet.dm | 756 +- code/game/machinery/mass_driver.dm | 84 +- code/game/machinery/navbeacon.dm | 452 +- code/game/machinery/pipe/construction.dm | 468 +- code/game/machinery/pipe/pipe_dispenser.dm | 428 +- .../machinery/porta_turret/portable_turret.dm | 2244 +- code/game/machinery/recharger.dm | 364 +- code/game/machinery/rechargestation.dm | 206 +- code/game/machinery/recycler.dm | 404 +- code/game/machinery/requests_console.dm | 922 +- code/game/machinery/roulette_machine.dm | 850 +- code/game/machinery/status_display.dm | 742 +- code/game/machinery/suit_storage_unit.dm | 1026 +- code/game/machinery/syndicatebeacon.dm | 278 +- .../machinery/telecomms/computers/message.dm | 960 +- .../telecomms/machines/message_server.dm | 536 +- .../machinery/telecomms/telecomunications.dm | 308 +- code/game/machinery/washing_machine.dm | 718 +- code/game/machinery/wishgranter.dm | 86 +- code/game/mecha/combat/combat.dm | 36 +- code/game/mecha/combat/durand.dm | 420 +- code/game/mecha/combat/gygax.dm | 138 +- code/game/mecha/combat/honker.dm | 336 +- code/game/mecha/combat/marauder.dm | 206 +- code/game/mecha/combat/phazon.dm | 60 +- code/game/mecha/combat/reticence.dm | 56 +- code/game/mecha/equipment/mecha_equipment.dm | 352 +- .../mecha/equipment/tools/medical_tools.dm | 1098 +- code/game/mecha/equipment/weapons/weapons.dm | 1050 +- code/game/mecha/mech_fabricator.dm | 886 +- code/game/mecha/mecha.dm | 2392 +- code/game/mecha/mecha_construction_paths.dm | 2668 +- code/game/mecha/mecha_control_console.dm | 326 +- code/game/mecha/mecha_parts.dm | 762 +- code/game/mecha/mecha_wreckage.dm | 442 +- code/game/mecha/medical/medical.dm | 14 +- code/game/mecha/medical/odysseus.dm | 62 +- code/game/mecha/working/ripley.dm | 408 +- code/game/mecha/working/working.dm | 40 +- code/game/objects/effects/bump_teleporter.dm | 74 +- code/game/objects/effects/decals/cleanable.dm | 222 +- .../effects/decals/cleanable/aliens.dm | 160 +- .../effects/decals/cleanable/humans.dm | 478 +- .../objects/effects/decals/cleanable/misc.dm | 478 +- .../effects/decals/cleanable/robots.dm | 166 +- code/game/objects/effects/decals/crayon.dm | 98 +- code/game/objects/effects/decals/decal.dm | 96 +- code/game/objects/effects/decals/misc.dm | 62 +- code/game/objects/effects/decals/remains.dm | 66 +- code/game/objects/effects/forcefields.dm | 76 +- code/game/objects/effects/landmarks.dm | 870 +- code/game/objects/effects/mines.dm | 398 +- code/game/objects/effects/misc.dm | 188 +- code/game/objects/effects/proximity.dm | 232 +- .../objects/effects/spawners/bombspawner.dm | 134 +- .../objects/effects/spawners/gibspawner.dm | 306 +- .../game/objects/effects/spawners/lootdrop.dm | 974 +- code/game/objects/effects/spiders.dm | 528 +- code/game/objects/effects/step_triggers.dm | 400 +- .../temporary_visuals/projectiles/impact.dm | 76 +- .../temporary_visuals/projectiles/muzzle.dm | 60 +- .../projectiles/projectile_effects.dm | 120 +- .../temporary_visuals/projectiles/tracer.dm | 136 +- code/game/objects/empulse.dm | 64 +- code/game/objects/items.dm | 2026 +- code/game/objects/items/AI_modules.dm | 1190 +- code/game/objects/items/airlock_painter.dm | 494 +- code/game/objects/items/bodybag.dm | 168 +- code/game/objects/items/candle.dm | 160 +- code/game/objects/items/cards_ids.dm | 1476 +- code/game/objects/items/cigs_lighters.dm | 1984 +- code/game/objects/items/clown_items.dm | 466 +- code/game/objects/items/cosmetics.dm | 462 +- code/game/objects/items/crab17.dm | 468 +- code/game/objects/items/devices/PDA/PDA.dm | 2302 +- code/game/objects/items/devices/PDA/cart.dm | 1484 +- code/game/objects/items/devices/PDA/radio.dm | 76 +- code/game/objects/items/devices/aicard.dm | 210 +- code/game/objects/items/devices/camera_bug.dm | 622 +- code/game/objects/items/devices/flashlight.dm | 1150 +- code/game/objects/items/devices/gps.dm | 156 +- .../game/objects/items/devices/instruments.dm | 640 +- .../objects/items/devices/laserpointer.dm | 400 +- .../objects/items/devices/lightreplacer.dm | 532 +- code/game/objects/items/devices/multitool.dm | 352 +- code/game/objects/items/devices/ocd.dm | 22 +- code/game/objects/items/devices/paicard.dm | 350 +- code/game/objects/items/devices/powersink.dm | 328 +- .../items/devices/radio/electropack.dm | 262 +- .../items/devices/radio/encryptionkey.dm | 274 +- .../objects/items/devices/radio/headset.dm | 704 +- .../objects/items/devices/radio/intercom.dm | 270 +- .../game/objects/items/devices/radio/radio.dm | 852 +- code/game/objects/items/devices/scanners.dm | 1796 +- .../objects/items/devices/taperecorder.dm | 660 +- .../objects/items/devices/transfer_valve.dm | 498 +- code/game/objects/items/dice.dm | 450 +- code/game/objects/items/dna_injector.dm | 1056 +- code/game/objects/items/emags.dm | 228 +- code/game/objects/items/extinguisher.dm | 498 +- .../objects/items/grenades/chem_grenade.dm | 1168 +- code/game/objects/items/grenades/emgrenade.dm | 22 +- code/game/objects/items/grenades/festive.dm | 236 +- code/game/objects/items/grenades/flashbang.dm | 280 +- .../game/objects/items/grenades/ghettobomb.dm | 154 +- code/game/objects/items/grenades/grenade.dm | 344 +- code/game/objects/items/grenades/smokebomb.dm | 66 +- .../objects/items/grenades/spawnergrenade.dm | 128 +- .../objects/items/grenades/syndieminibomb.dm | 136 +- code/game/objects/items/handcuffs.dm | 790 +- code/game/objects/items/hot_potato.dm | 348 +- code/game/objects/items/implants/implant.dm | 228 +- .../objects/items/implants/implantcase.dm | 168 +- .../objects/items/implants/implantchair.dm | 400 +- code/game/objects/items/implants/implanter.dm | 130 +- .../game/objects/items/implants/implantpad.dm | 156 +- .../objects/items/implants/implantuplink.dm | 46 +- code/game/objects/items/kitchen.dm | 558 +- code/game/objects/items/latexballoon.dm | 116 +- code/game/objects/items/manuals.dm | 902 +- code/game/objects/items/melee/energy.dm | 476 +- code/game/objects/items/paiwire.dm | 26 +- code/game/objects/items/robot/robot_items.dm | 1872 +- code/game/objects/items/robot/robot_parts.dm | 802 +- .../objects/items/robot/robot_upgrades.dm | 1414 +- code/game/objects/items/scrolls.dm | 146 +- code/game/objects/items/shields.dm | 558 +- code/game/objects/items/shooting_range.dm | 194 +- code/game/objects/items/singularityhammer.dm | 272 +- code/game/objects/items/stacks/bscrystal.dm | 182 +- code/game/objects/items/stacks/medical.dm | 838 +- code/game/objects/items/stacks/rods.dm | 218 +- .../game/objects/items/stacks/sheets/glass.dm | 720 +- .../objects/items/stacks/sheets/leather.dm | 514 +- .../game/objects/items/stacks/sheets/light.dm | 72 +- .../objects/items/stacks/sheets/sheets.dm | 42 +- code/game/objects/items/storage/backpack.dm | 1248 +- code/game/objects/items/storage/bags.dm | 898 +- code/game/objects/items/storage/belt.dm | 1470 +- code/game/objects/items/storage/boxes.dm | 2724 +- code/game/objects/items/storage/briefcase.dm | 98 +- code/game/objects/items/storage/fancy.dm | 898 +- code/game/objects/items/storage/firstaid.dm | 1270 +- code/game/objects/items/storage/lockbox.dm | 452 +- code/game/objects/items/storage/secure.dm | 376 +- code/game/objects/items/storage/storage.dm | 100 +- code/game/objects/items/storage/toolbox.dm | 602 +- code/game/objects/items/storage/wallets.dm | 250 +- code/game/objects/items/stunbaton.dm | 706 +- code/game/objects/items/tanks/tank_types.dm | 376 +- code/game/objects/items/teleportation.dm | 444 +- code/game/objects/items/tools/crowbar.dm | 270 +- code/game/objects/items/tools/screwdriver.dm | 282 +- code/game/objects/items/tools/wirecutters.dm | 170 +- code/game/objects/items/tools/wrench.dm | 238 +- code/game/objects/items/trash.dm | 220 +- code/game/objects/items/vending_items.dm | 102 +- code/game/objects/items/weaponry.dm | 1796 +- code/game/objects/objs.dm | 704 +- code/game/objects/structures.dm | 268 +- code/game/objects/structures/ai_core.dm | 662 +- code/game/objects/structures/bedsheet_bin.dm | 834 +- .../structures/crates_lockers/closets.dm | 1076 +- .../crates_lockers/closets/fitness.dm | 130 +- .../crates_lockers/closets/gimmick.dm | 222 +- .../crates_lockers/closets/job_closets.dm | 626 +- .../crates_lockers/closets/l3closet.dm | 108 +- .../crates_lockers/closets/secure/bar.dm | 34 +- .../crates_lockers/closets/secure/cargo.dm | 50 +- .../closets/secure/engineering.dm | 184 +- .../crates_lockers/closets/secure/freezer.dm | 232 +- .../closets/secure/hydroponics.dm | 26 +- .../crates_lockers/closets/secure/medical.dm | 262 +- .../crates_lockers/closets/secure/personal.dm | 114 +- .../closets/secure/scientist.dm | 56 +- .../closets/secure/secure_closets.dm | 18 +- .../crates_lockers/closets/secure/security.dm | 644 +- .../crates_lockers/closets/syndicate.dm | 244 +- .../crates_lockers/closets/utility_closets.dm | 350 +- .../crates_lockers/closets/wardrobe.dm | 408 +- .../structures/crates_lockers/crates.dm | 460 +- .../structures/crates_lockers/crates/bins.dm | 86 +- .../crates_lockers/crates/critter.dm | 102 +- .../structures/crates_lockers/crates/large.dm | 94 +- .../crates_lockers/crates/secure.dm | 208 +- code/game/objects/structures/displaycase.dm | 1146 +- code/game/objects/structures/dresser.dm | 116 +- code/game/objects/structures/electricchair.dm | 104 +- code/game/objects/structures/extinguisher.dm | 318 +- code/game/objects/structures/flora.dm | 950 +- code/game/objects/structures/hivebot.dm | 72 +- code/game/objects/structures/kitchen_spike.dm | 298 +- code/game/objects/structures/ladders.dm | 382 +- code/game/objects/structures/manned_turret.dm | 432 +- code/game/objects/structures/mineral_doors.dm | 700 +- code/game/objects/structures/mirror.dm | 498 +- code/game/objects/structures/mop_bucket.dm | 60 +- code/game/objects/structures/morgue.dm | 806 +- code/game/objects/structures/musician.dm | 786 +- code/game/objects/structures/noticeboard.dm | 264 +- code/game/objects/structures/safe.dm | 410 +- code/game/objects/structures/tables_racks.dm | 1370 +- .../game/objects/structures/tank_dispenser.dm | 222 +- code/game/objects/structures/target_stake.dm | 152 +- .../transit_tube_construction.dm | 328 +- .../objects/structures/windoor_assembly.dm | 714 +- code/game/shuttle_engines.dm | 316 +- code/game/turfs/open/openspace.dm | 336 +- code/game/world.dm | 746 +- code/modules/NTNet/services/_service.dm | 76 +- code/modules/admin/IsBanned.dm | 486 +- code/modules/admin/admin.dm | 1984 +- code/modules/admin/admin_investigate.dm | 84 +- code/modules/admin/admin_ranks.dm | 584 +- code/modules/admin/chat_commands.dm | 218 +- code/modules/admin/create_mob.dm | 82 +- code/modules/admin/create_object.dm | 64 +- code/modules/admin/create_turf.dm | 20 +- code/modules/admin/holder2.dm | 416 +- code/modules/admin/player_panel.dm | 656 +- code/modules/admin/skill_panel.dm | 110 +- code/modules/admin/topic.dm | 4582 +- code/modules/admin/verbs/BrokenInhands.dm | 70 +- code/modules/admin/verbs/adminhelp.dm | 1440 +- code/modules/admin/verbs/adminjump.dm | 320 +- code/modules/admin/verbs/adminpm.dm | 662 +- code/modules/admin/verbs/adminsay.dm | 44 +- code/modules/admin/verbs/atmosdebug.dm | 112 +- code/modules/admin/verbs/cinematic.dm | 22 +- code/modules/admin/verbs/deadsay.dm | 86 +- code/modules/admin/verbs/fps.dm | 52 +- code/modules/admin/verbs/getlogs.dm | 70 +- code/modules/admin/verbs/mapping.dm | 754 +- code/modules/admin/verbs/onlyone.dm | 62 +- code/modules/admin/verbs/playsound.dm | 354 +- code/modules/admin/verbs/possess.dm | 106 +- code/modules/admin/verbs/pray.dm | 150 +- code/modules/admin/verbs/randomverbs.dm | 2628 +- code/modules/admin/verbs/shuttlepanel.dm | 152 +- code/modules/admin/whitelist.dm | 46 +- code/modules/antagonists/_common/antag_hud.dm | 116 +- .../antagonists/_common/antag_spawner.dm | 554 +- .../abductor/equipment/orderable_gear.dm | 158 +- .../changeling/changeling_power.dm | 196 +- .../antagonists/changeling/powers/absorb.dm | 294 +- .../changeling/powers/digitalcamo.dm | 46 +- .../changeling/powers/fakedeath.dm | 140 +- .../changeling/powers/fleshmend.dm | 42 +- .../antagonists/changeling/powers/hivemind.dm | 234 +- .../changeling/powers/humanform.dm | 68 +- .../changeling/powers/lesserform.dm | 34 +- .../changeling/powers/mimic_voice.dm | 54 +- .../antagonists/changeling/powers/panacea.dm | 90 +- .../antagonists/changeling/powers/shriek.dm | 94 +- .../antagonists/changeling/powers/spiders.dm | 28 +- .../changeling/powers/tiny_prick.dm | 486 +- .../changeling/powers/transform.dm | 340 +- .../nukeop/equipment/pinpointer.dm | 180 +- .../antagonists/wizard/equipment/artefact.dm | 954 +- .../antagonists/wizard/equipment/spellbook.dm | 1538 +- code/modules/assembly/assembly.dm | 254 +- code/modules/assembly/flash.dm | 598 +- code/modules/assembly/helpers.dm | 32 +- code/modules/assembly/holder.dm | 290 +- code/modules/assembly/igniter.dm | 144 +- code/modules/assembly/infrared.dm | 476 +- code/modules/assembly/mousetrap.dm | 288 +- code/modules/assembly/proximity.dm | 312 +- code/modules/assembly/shock_kit.dm | 80 +- code/modules/assembly/signaler.dm | 378 +- code/modules/assembly/timer.dm | 256 +- code/modules/assembly/voice.dm | 208 +- code/modules/asset_cache/asset_cache.dm | 206 +- .../modules/asset_cache/asset_cache_client.dm | 102 +- code/modules/asset_cache/asset_cache_item.dm | 42 +- code/modules/asset_cache/asset_list.dm | 460 +- code/modules/asset_cache/asset_list_items.dm | 782 +- code/modules/asset_cache/validate_assets.html | 56 +- .../atmospherics/gasmixtures/gas_mixture.dm | 1062 +- .../atmospherics/machinery/atmosmachinery.dm | 676 +- .../binary_devices/binary_devices.dm | 34 +- .../components/binary_devices/circulator.dm | 380 +- .../components/binary_devices/dp_vent_pump.dm | 484 +- .../trinary_devices/trinary_devices.dm | 96 +- .../components/unary_devices/cryo.dm | 1008 +- .../atmospherics/machinery/other/meter.dm | 292 +- .../machinery/pipes/layermanifold.dm | 266 +- .../portable/portable_atmospherics.dm | 332 +- .../atmospherics/machinery/portable/pump.dm | 324 +- .../awaymissions/bluespaceartillery.dm | 110 +- code/modules/awaymissions/exile.dm | 18 +- code/modules/awaymissions/gateway.dm | 654 +- .../awaymissions/mission_code/Academy.dm | 802 +- .../awaymissions/mission_code/centcomAway.dm | 126 +- .../awaymissions/mission_code/wildwest.dm | 332 +- code/modules/awaymissions/pamphlet.dm | 84 +- code/modules/awaymissions/zlevel.dm | 124 +- code/modules/cargo/bounty_console.dm | 130 +- code/modules/cargo/console.dm | 572 +- code/modules/cargo/expressconsole.dm | 432 +- code/modules/cargo/gondolapod.dm | 152 +- code/modules/cargo/order.dm | 250 +- code/modules/cargo/supplypod.dm | 1170 +- code/modules/client/client_defines.dm | 358 +- code/modules/client/preferences.dm | 3946 +- code/modules/client/preferences_toggles.dm | 964 +- code/modules/client/verbs/etips.dm | 40 +- code/modules/clothing/clothing.dm | 920 +- code/modules/clothing/glasses/_glasses.dm | 1058 +- code/modules/clothing/glasses/hud.dm | 488 +- code/modules/clothing/gloves/_gloves.dm | 86 +- code/modules/clothing/gloves/boxing.dm | 38 +- code/modules/clothing/gloves/color.dm | 500 +- code/modules/clothing/gloves/miscellaneous.dm | 294 +- code/modules/clothing/head/_head.dm | 152 +- code/modules/clothing/head/collectable.dm | 306 +- code/modules/clothing/head/hardhat.dm | 328 +- code/modules/clothing/head/helmet.dm | 1038 +- code/modules/clothing/head/jobs.dm | 540 +- code/modules/clothing/head/misc.dm | 950 +- code/modules/clothing/head/misc_special.dm | 746 +- code/modules/clothing/head/soft_caps.dm | 262 +- code/modules/clothing/masks/_masks.dm | 144 +- code/modules/clothing/masks/boxing.dm | 196 +- code/modules/clothing/masks/breath.dm | 82 +- code/modules/clothing/masks/gasmask.dm | 528 +- code/modules/clothing/masks/miscellaneous.dm | 672 +- code/modules/clothing/shoes/_shoes.dm | 554 +- code/modules/clothing/shoes/colour.dm | 178 +- code/modules/clothing/shoes/magboots.dm | 118 +- .../clothing/spacesuits/_spacesuits.dm | 502 +- .../clothing/spacesuits/miscellaneous.dm | 922 +- code/modules/clothing/spacesuits/syndi.dm | 324 +- code/modules/clothing/suits/_suits.dm | 70 +- code/modules/clothing/suits/bio.dm | 200 +- code/modules/clothing/suits/jobs.dm | 370 +- code/modules/clothing/suits/labcoat.dm | 98 +- code/modules/clothing/suits/miscellaneous.dm | 1596 +- code/modules/clothing/suits/utility.dm | 296 +- code/modules/clothing/suits/wiz_robe.dm | 462 +- code/modules/clothing/under/_under.dm | 584 +- code/modules/clothing/under/color.dm | 462 +- .../clothing/under/jobs/engineering.dm | 128 +- code/modules/clothing/under/jobs/security.dm | 476 +- code/modules/clothing/under/miscellaneous.dm | 316 +- code/modules/clothing/under/pants.dm | 138 +- code/modules/clothing/under/shorts.dm | 68 +- code/modules/clothing/under/syndicate.dm | 182 +- code/modules/detectivework/detective_work.dm | 204 +- code/modules/detectivework/evidence.dm | 194 +- .../detectivework/footprints_and_rag.dm | 90 +- code/modules/detectivework/scanner.dm | 430 +- code/modules/discord/accountlink.dm | 186 +- code/modules/discord/manipulation.dm | 72 +- code/modules/discord/tgs_commands.dm | 92 +- code/modules/discord/toggle_notify.dm | 68 +- code/modules/events/anomaly.dm | 110 +- code/modules/events/anomaly_bluespace.dm | 28 +- code/modules/events/anomaly_flux.dm | 30 +- code/modules/events/anomaly_grav.dm | 52 +- code/modules/events/anomaly_vortex.dm | 30 +- code/modules/events/blob.dm | 60 +- code/modules/events/brand_intelligence.dm | 156 +- code/modules/events/carp_migration.dm | 68 +- .../modules/events/communications_blackout.dm | 52 +- code/modules/events/disease_outbreak.dm | 150 +- code/modules/events/dust.dm | 62 +- code/modules/events/electrical_storm.dm | 66 +- code/modules/events/false_alarm.dm | 124 +- code/modules/events/high_priority_bounty.dm | 40 +- code/modules/events/holiday/halloween.dm | 114 +- code/modules/events/immovable_rod.dm | 334 +- code/modules/events/ion_storm.dm | 1122 +- code/modules/events/mass_hallucination.dm | 76 +- code/modules/events/meteor_wave.dm | 152 +- code/modules/events/prison_break.dm | 118 +- code/modules/events/processor_overload.dm | 78 +- code/modules/events/radiation_storm.dm | 38 +- code/modules/events/spider_infestation.dm | 78 +- .../events/spontaneous_appendicitis.dm | 64 +- code/modules/events/vent_clog.dm | 220 +- code/modules/fields/timestop.dm | 412 +- code/modules/flufftext/Dreaming.dm | 138 +- code/modules/food_and_drinks/pizzabox.dm | 698 +- .../recipes/tablecraft/recipes_drink.dm | 310 +- code/modules/games/kotahi.dm | 40 +- code/modules/holodeck/holo_effect.dm | 222 +- code/modules/holodeck/items.dm | 458 +- code/modules/hydroponics/biogenerator.dm | 686 +- code/modules/hydroponics/grown.dm | 378 +- code/modules/hydroponics/growninedible.dm | 122 +- code/modules/hydroponics/hydroitemdefines.dm | 552 +- code/modules/hydroponics/hydroponics.dm | 1980 +- code/modules/hydroponics/seed_extractor.dm | 398 +- code/modules/hydroponics/seeds.dm | 1248 +- code/modules/jobs/access.dm | 762 +- code/modules/jobs/job_types/assistant.dm | 86 +- code/modules/jobs/job_types/captain.dm | 130 +- code/modules/jobs/jobs.dm | 278 +- code/modules/jobs/map_changes/map_changes.dm | 14 +- code/modules/language/buzzwords.dm | 20 +- code/modules/language/calcic.dm | 32 +- code/modules/language/common.dm | 140 +- code/modules/language/language.dm | 214 +- code/modules/language/machine.dm | 32 +- code/modules/language/moffic.dm | 32 +- code/modules/language/monkey.dm | 18 +- code/modules/language/shadowtongue.dm | 36 +- code/modules/language/sylvan.dm | 30 +- code/modules/language/terrum.dm | 28 +- code/modules/language/voltaic.dm | 28 +- code/modules/language/xenocommon.dm | 16 +- code/modules/library/lib_items.dm | 706 +- code/modules/library/lib_machines.dm | 1224 +- code/modules/library/random_books.dm | 184 +- code/modules/mapping/ruins.dm | 358 +- .../space_management/multiz_helpers.dm | 68 +- .../space_management/space_reservation.dm | 148 +- code/modules/mining/equipment/mining_tools.dm | 344 +- code/modules/mining/laborcamp/laborshuttle.dm | 54 +- code/modules/mining/laborcamp/laborstacker.dm | 322 +- code/modules/mining/machine_processing.dm | 526 +- code/modules/mining/machine_stacking.dm | 266 +- code/modules/mining/machine_unloading.dm | 46 +- code/modules/mining/mint.dm | 296 +- code/modules/mining/money_bag.dm | 58 +- code/modules/mining/ores_coins.dm | 970 +- code/modules/mining/satchel_ore_boxdm.dm | 210 +- code/modules/mob/camera/camera.dm | 54 +- code/modules/mob/dead/dead.dm | 274 +- code/modules/mob/dead/new_player/login.dm | 76 +- code/modules/mob/dead/new_player/logout.dm | 14 +- .../modules/mob/dead/new_player/new_player.dm | 1038 +- code/modules/mob/dead/new_player/poll.dm | 1138 +- .../mob/dead/new_player/preferences_setup.dm | 272 +- .../mob/dead/new_player/sprite_accessories.dm | 3936 +- code/modules/mob/dead/observer/login.dm | 44 +- code/modules/mob/dead/observer/logout.dm | 32 +- .../mob/dead/observer/observer_movement.dm | 4 +- .../modules/mob/dead/observer/observer_say.dm | 90 +- code/modules/mob/dead/observer/orbit.dm | 158 +- code/modules/mob/death.dm | 28 +- code/modules/mob/inventory.dm | 1014 +- code/modules/mob/living/carbon/alien/alien.dm | 282 +- .../mob/living/carbon/alien/alien_defense.dm | 272 +- .../mob/living/carbon/alien/alien_say.dm | 50 +- code/modules/mob/living/carbon/alien/death.dm | 28 +- .../carbon/alien/humanoid/caste/drone.dm | 86 +- .../carbon/alien/humanoid/caste/hunter.dm | 190 +- .../carbon/alien/humanoid/caste/sentinel.dm | 32 +- .../mob/living/carbon/alien/humanoid/death.dm | 44 +- .../living/carbon/alien/humanoid/humanoid.dm | 242 +- .../alien/humanoid/humanoid_update_icons.dm | 184 +- .../living/carbon/alien/humanoid/inventory.dm | 10 +- .../mob/living/carbon/alien/humanoid/life.dm | 36 +- .../mob/living/carbon/alien/humanoid/queen.dm | 276 +- .../mob/living/carbon/alien/larva/death.dm | 44 +- .../living/carbon/alien/larva/inventory.dm | 6 +- .../mob/living/carbon/alien/larva/larva.dm | 152 +- .../carbon/alien/larva/larva_update_icons.dm | 62 +- .../mob/living/carbon/alien/larva/life.dm | 58 +- .../mob/living/carbon/alien/larva/powers.dm | 126 +- code/modules/mob/living/carbon/alien/life.dm | 108 +- code/modules/mob/living/carbon/alien/login.dm | 12 +- .../modules/mob/living/carbon/alien/logout.dm | 8 +- .../modules/mob/living/carbon/alien/screen.dm | 66 +- .../carbon/alien/special/alien_embryo.dm | 260 +- .../living/carbon/alien/special/facehugger.dm | 542 +- code/modules/mob/living/carbon/carbon.dm | 2386 +- .../mob/living/carbon/carbon_defense.dm | 944 +- .../mob/living/carbon/carbon_defines.dm | 158 +- .../mob/living/carbon/carbon_update_icons.dm | 578 +- code/modules/mob/living/carbon/death.dm | 144 +- code/modules/mob/living/carbon/emote.dm | 280 +- code/modules/mob/living/carbon/human/death.dm | 132 +- code/modules/mob/living/carbon/human/dummy.dm | 98 +- code/modules/mob/living/carbon/human/emote.dm | 348 +- .../mob/living/carbon/human/examine.dm | 852 +- code/modules/mob/living/carbon/human/human.dm | 2628 +- .../mob/living/carbon/human/human_defense.dm | 1886 +- .../mob/living/carbon/human/human_defines.dm | 132 +- .../mob/living/carbon/human/human_helpers.dm | 392 +- .../mob/living/carbon/human/human_movement.dm | 156 +- .../mob/living/carbon/human/human_say.dm | 218 +- .../living/carbon/human/human_update_icons.dm | 1302 +- .../mob/living/carbon/human/inventory.dm | 668 +- .../carbon/human/species_types/abductors.dm | 38 +- .../carbon/human/species_types/android.dm | 50 +- .../carbon/human/species_types/corporate.dm | 40 +- .../carbon/human/species_types/felinid.dm | 250 +- .../carbon/human/species_types/flypeople.dm | 76 +- .../carbon/human/species_types/golems.dm | 2324 +- .../carbon/human/species_types/humans.dm | 30 +- .../carbon/human/species_types/jellypeople.dm | 1470 +- .../human/species_types/lizardpeople.dm | 178 +- .../carbon/human/species_types/plasmamen.dm | 370 +- .../carbon/human/species_types/podpeople.dm | 134 +- .../human/species_types/shadowpeople.dm | 450 +- .../carbon/human/species_types/skeletons.dm | 118 +- .../carbon/human/species_types/synths.dm | 262 +- .../carbon/human/species_types/zombies.dm | 206 +- code/modules/mob/living/carbon/inventory.dm | 406 +- code/modules/mob/living/carbon/life.dm | 1676 +- .../modules/mob/living/carbon/monkey/death.dm | 18 +- .../mob/living/carbon/monkey/inventory.dm | 68 +- code/modules/mob/living/carbon/monkey/life.dm | 322 +- .../mob/living/carbon/monkey/monkey.dm | 344 +- .../carbon/monkey/monkey_update_icons.dm | 154 +- code/modules/mob/living/damage_procs.dm | 604 +- code/modules/mob/living/death.dm | 180 +- code/modules/mob/living/emote.dm | 1010 +- code/modules/mob/living/inhand_holder.dm | 198 +- code/modules/mob/living/living_defense.dm | 906 +- code/modules/mob/living/living_defines.dm | 280 +- code/modules/mob/living/living_movement.dm | 118 +- code/modules/mob/living/living_say.dm | 848 +- code/modules/mob/living/login.dm | 68 +- code/modules/mob/living/logout.dm | 12 +- code/modules/mob/living/silicon/ai/ai.dm | 2038 +- code/modules/mob/living/silicon/ai/ai_say.dm | 328 +- code/modules/mob/living/silicon/ai/death.dm | 114 +- code/modules/mob/living/silicon/ai/examine.dm | 44 +- .../living/silicon/ai/freelook/cameranet.dm | 408 +- .../mob/living/silicon/ai/freelook/chunk.dm | 284 +- .../mob/living/silicon/ai/freelook/eye.dm | 452 +- .../mob/living/silicon/ai/freelook/read_me.dm | 102 +- code/modules/mob/living/silicon/ai/laws.dm | 52 +- code/modules/mob/living/silicon/ai/login.dm | 28 +- code/modules/mob/living/silicon/ai/logout.dm | 16 +- .../mob/living/silicon/ai/vox_sounds.dm | 1956 +- code/modules/mob/living/silicon/death.dm | 26 +- code/modules/mob/living/silicon/laws.dm | 200 +- code/modules/mob/living/silicon/login.dm | 30 +- code/modules/mob/living/silicon/pai/death.dm | 24 +- .../modules/mob/living/silicon/pai/pai_say.dm | 16 +- .../mob/living/silicon/pai/personality.dm | 122 +- .../mob/living/silicon/pai/software.dm | 1322 +- .../modules/mob/living/silicon/robot/death.dm | 68 +- .../modules/mob/living/silicon/robot/emote.dm | 124 +- .../mob/living/silicon/robot/examine.dm | 96 +- .../mob/living/silicon/robot/inventory.dm | 430 +- code/modules/mob/living/silicon/robot/laws.dm | 174 +- code/modules/mob/living/silicon/robot/life.dm | 206 +- .../modules/mob/living/silicon/robot/login.dm | 14 +- .../mob/living/silicon/robot/robot_modules.dm | 1422 +- .../living/silicon/robot/robot_movement.dm | 34 +- .../mob/living/silicon/robot/robot_say.dm | 4 +- code/modules/mob/living/silicon/silicon.dm | 870 +- .../modules/mob/living/silicon/silicon_say.dm | 108 +- .../mob/living/simple_animal/bot/bot.dm | 2164 +- .../mob/living/simple_animal/bot/ed209bot.dm | 246 +- .../mob/living/simple_animal/bot/medbot.dm | 1232 +- .../mob/living/simple_animal/bot/secbot.dm | 1066 +- .../mob/living/simple_animal/constructs.dm | 988 +- .../mob/living/simple_animal/corpse.dm | 482 +- .../mob/living/simple_animal/friendly/cat.dm | 622 +- .../mob/living/simple_animal/friendly/crab.dm | 146 +- .../simple_animal/friendly/farm_animals.dm | 874 +- .../living/simple_animal/friendly/lizard.dm | 108 +- .../mob/living/simple_animal/hostile/carp.dm | 472 +- .../living/simple_animal/hostile/faithless.dm | 96 +- .../simple_animal/hostile/giant_spider.dm | 1134 +- .../living/simple_animal/hostile/hivebot.dm | 324 +- .../mob/living/simple_animal/hostile/mimic.dm | 742 +- .../living/simple_animal/hostile/mushroom.dm | 390 +- .../living/simple_animal/hostile/pirate.dm | 196 +- .../simple_animal/hostile/retaliate/bat.dm | 88 +- .../simple_animal/hostile/retaliate/clown.dm | 616 +- .../hostile/retaliate/retaliate.dm | 94 +- .../living/simple_animal/hostile/russian.dm | 146 +- .../living/simple_animal/hostile/syndicate.dm | 638 +- .../mob/living/simple_animal/parrot.dm | 1992 +- .../modules/mob/living/simple_animal/shade.dm | 140 +- code/modules/mob/login.dm | 228 +- code/modules/mob/logout.dm | 36 +- code/modules/mob/mob_helpers.dm | 1116 +- code/modules/mob/mob_movement.dm | 998 +- code/modules/mob/mob_say.dm | 242 +- code/modules/mob/mob_transformation_simple.dm | 120 +- code/modules/mob/mob_update_icons.dm | 142 +- code/modules/mob/transform_procs.dm | 1304 +- .../modular_computers/hardware/recharger.dm | 182 +- code/modules/paperwork/clipboard.dm | 250 +- code/modules/paperwork/filingcabinet.dm | 446 +- code/modules/paperwork/folders.dm | 264 +- code/modules/paperwork/handlabeler.dm | 244 +- code/modules/paperwork/paperbin.dm | 362 +- code/modules/paperwork/pen.dm | 536 +- code/modules/paperwork/photocopier.dm | 690 +- code/modules/paperwork/stamps.dm | 180 +- code/modules/photography/_pictures.dm | 354 +- code/modules/photography/camera/camera.dm | 482 +- .../camera/camera_image_capturing.dm | 210 +- code/modules/photography/camera/film.dm | 28 +- code/modules/photography/camera/other.dm | 28 +- .../photography/camera/silicon_camera.dm | 198 +- code/modules/photography/photos/album.dm | 210 +- code/modules/photography/photos/frame.dm | 334 +- code/modules/photography/photos/photo.dm | 196 +- code/modules/power/cable.dm | 1504 +- code/modules/power/cell.dm | 800 +- code/modules/power/generator.dm | 458 +- code/modules/power/gravitygenerator.dm | 818 +- code/modules/power/monitor.dm | 248 +- code/modules/power/port_gen.dm | 588 +- code/modules/power/singularity/collector.dm | 414 +- .../power/singularity/containment_field.dm | 286 +- code/modules/power/singularity/emitter.dm | 1040 +- .../power/singularity/field_generator.dm | 828 +- code/modules/power/singularity/generator.dm | 70 +- code/modules/power/singularity/investigate.dm | 8 +- .../particle_accelerator/particle.dm | 138 +- .../particle_accelerator.dm | 346 +- .../particle_accelerator/particle_control.dm | 646 +- .../particle_accelerator/particle_emitter.dm | 86 +- code/modules/power/tracker.dm | 174 +- .../mapGenerators/lavaland.dm | 66 +- .../mapGenerators/repair.dm | 222 +- .../projectiles/ammunition/_ammunition.dm | 176 +- .../modules/projectiles/ammunition/_firing.dm | 134 +- .../projectiles/ammunition/ballistic/lmg.dm | 56 +- .../ammunition/ballistic/pistol.dm | 108 +- .../ammunition/ballistic/revolver.dm | 122 +- .../projectiles/ammunition/ballistic/rifle.dm | 66 +- .../ammunition/ballistic/shotgun.dm | 304 +- .../projectiles/ammunition/ballistic/smg.dm | 90 +- .../ammunition/ballistic/sniper.dm | 40 +- .../ammunition/caseless/_caseless.dm | 32 +- .../projectiles/ammunition/caseless/foam.dm | 130 +- .../projectiles/ammunition/caseless/misc.dm | 26 +- .../projectiles/ammunition/caseless/rocket.dm | 38 +- .../projectiles/ammunition/energy/_energy.dm | 20 +- .../projectiles/ammunition/energy/ebow.dm | 24 +- .../projectiles/ammunition/energy/gravity.dm | 58 +- .../projectiles/ammunition/energy/laser.dm | 150 +- .../projectiles/ammunition/energy/lmg.dm | 12 +- .../projectiles/ammunition/energy/plasma.dm | 22 +- .../projectiles/ammunition/energy/portal.dm | 42 +- .../projectiles/ammunition/energy/special.dm | 162 +- .../projectiles/ammunition/energy/stun.dm | 52 +- .../projectiles/ammunition/special/magic.dm | 142 +- .../projectiles/ammunition/special/syringe.dm | 122 +- .../boxes_magazines/_box_magazine.dm | 330 +- .../boxes_magazines/external/grenade.dm | 16 +- .../boxes_magazines/external/lmg.dm | 52 +- .../boxes_magazines/external/pistol.dm | 170 +- .../boxes_magazines/external/rechargable.dm | 28 +- .../boxes_magazines/external/rifle.dm | 52 +- .../boxes_magazines/external/shotgun.dm | 72 +- .../boxes_magazines/external/smg.dm | 196 +- .../boxes_magazines/external/sniper.dm | 54 +- .../boxes_magazines/external/toy.dm | 118 +- .../boxes_magazines/internal/_cylinder.dm | 98 +- .../boxes_magazines/internal/_internal.dm | 16 +- .../boxes_magazines/internal/grenade.dm | 34 +- .../boxes_magazines/internal/misc.dm | 22 +- .../boxes_magazines/internal/revolver.dm | 44 +- .../boxes_magazines/internal/rifle.dm | 30 +- .../boxes_magazines/internal/shotgun.dm | 76 +- .../boxes_magazines/internal/toy.dm | 14 +- code/modules/projectiles/guns/energy.dm | 490 +- code/modules/projectiles/guns/energy/laser.dm | 298 +- .../projectiles/guns/energy/mounted.dm | 52 +- code/modules/projectiles/guns/energy/pulse.dm | 156 +- .../projectiles/guns/energy/special.dm | 732 +- code/modules/projectiles/guns/energy/stun.dm | 96 +- code/modules/projectiles/guns/magic.dm | 182 +- code/modules/projectiles/guns/magic/wand.dm | 480 +- .../projectiles/guns/misc/beam_rifle.dm | 1126 +- .../modules/projectiles/guns/misc/chem_gun.dm | 92 +- .../projectiles/guns/misc/grenade_launcher.dm | 92 +- code/modules/projectiles/guns/misc/medbeam.dm | 270 +- .../projectiles/guns/misc/syringe_gun.dm | 246 +- code/modules/projectiles/projectile.dm | 1490 +- code/modules/projectiles/projectile/beams.dm | 452 +- .../modules/projectiles/projectile/bullets.dm | 20 +- .../projectile/bullets/_incendiary.dm | 34 +- .../projectile/bullets/dart_syringe.dm | 76 +- .../projectile/bullets/dnainjector.dm | 48 +- .../projectiles/projectile/bullets/grenade.dm | 24 +- .../projectiles/projectile/bullets/lmg.dm | 106 +- .../projectiles/projectile/bullets/pistol.dm | 82 +- .../projectile/bullets/revolver.dm | 220 +- .../projectiles/projectile/bullets/rifle.dm | 48 +- .../projectiles/projectile/bullets/shotgun.dm | 226 +- .../projectiles/projectile/bullets/smg.dm | 74 +- .../projectiles/projectile/bullets/sniper.dm | 90 +- .../projectiles/projectile/bullets/special.dm | 64 +- .../projectiles/projectile/energy/_energy.dm | 14 +- .../projectiles/projectile/energy/ebow.dm | 34 +- .../projectiles/projectile/energy/misc.dm | 38 +- .../projectiles/projectile/energy/stun.dm | 60 +- .../projectiles/projectile/energy/tesla.dm | 52 +- .../projectiles/projectile/magic/spellcard.dm | 12 +- .../projectile/reusable/_reusable.dm | 40 +- .../projectile/reusable/foam_dart.dm | 84 +- .../projectiles/projectile/special/curse.dm | 112 +- .../projectiles/projectile/special/floral.dm | 46 +- .../projectiles/projectile/special/gravity.dm | 204 +- .../projectile/special/hallucination.dm | 460 +- .../projectiles/projectile/special/ion.dm | 34 +- .../projectiles/projectile/special/meteor.dm | 44 +- .../projectile/special/mindflayer.dm | 18 +- .../projectile/special/neurotoxin.dm | 24 +- .../projectiles/projectile/special/plasma.dm | 80 +- .../projectiles/projectile/special/rocket.dm | 150 +- .../projectile/special/temperature.dm | 88 +- .../projectile/special/wormhole.dm | 58 +- code/modules/reagents/reagent_containers.dm | 292 +- .../reagents/reagent_containers/chem_pack.dm | 78 +- .../reagents/reagent_containers/dropper.dm | 180 +- .../reagents/reagent_containers/glass.dm | 702 +- .../reagents/reagent_containers/hypospray.dm | 566 +- .../reagents/reagent_containers/pill.dm | 564 +- .../reagents/reagent_containers/spray.dm | 826 +- .../reagents/reagent_containers/syringes.dm | 614 +- code/modules/reagents/reagent_dispenser.dm | 428 +- code/modules/recycling/sortingmachinery.dm | 936 +- code/modules/research/designs.dm | 180 +- .../research/designs/bluespace_designs.dm | 150 +- .../research/designs/electronics_designs.dm | 222 +- .../research/designs/mining_designs.dm | 266 +- code/modules/research/destructive_analyzer.dm | 332 +- code/modules/research/experimentor.dm | 1400 +- .../modules/research/machinery/_production.dm | 748 +- .../research/machinery/circuit_imprinter.dm | 64 +- .../departmental_circuit_imprinter.dm | 26 +- .../machinery/departmental_protolathe.dm | 86 +- .../machinery/departmental_techfab.dm | 82 +- code/modules/research/machinery/protolathe.dm | 50 +- code/modules/research/machinery/techfab.dm | 70 +- .../nanites/nanite_chamber_computer.dm | 200 +- .../research/nanites/public_chamber.dm | 408 +- code/modules/research/rdconsole.dm | 2310 +- code/modules/research/rdmachines.dm | 212 +- code/modules/research/research_disk.dm | 190 +- .../research/techweb/__techweb_helpers.dm | 74 +- code/modules/research/techweb/_techweb.dm | 784 +- .../modules/research/techweb/_techweb_node.dm | 202 +- .../security_levels/keycard_authentication.dm | 292 +- .../security_levels/security_levels.dm | 236 +- code/modules/shuttle/arrivals.dm | 414 +- code/modules/shuttle/computer.dm | 164 +- code/modules/shuttle/ferry.dm | 80 +- code/modules/shuttle/monastery.dm | 14 +- code/modules/shuttle/syndicate.dm | 134 +- code/modules/shuttle/white_ship.dm | 112 +- .../spells/spell_types/area_teleport.dm | 178 +- code/modules/spells/spell_types/conjure.dm | 198 +- code/modules/spells/spell_types/emplosion.dm | 36 +- .../spells/spell_types/ethereal_jaunt.dm | 222 +- code/modules/spells/spell_types/explosion.dm | 32 +- code/modules/spells/spell_types/genetic.dm | 90 +- .../spells/spell_types/inflict_handler.dm | 120 +- code/modules/spells/spell_types/knock.dm | 62 +- code/modules/spells/spell_types/mime.dm | 508 +- .../spell_types/pointed/mind_transfer.dm | 216 +- code/modules/spells/spell_types/projectile.dm | 260 +- .../spells/spell_types/rightandwrong.dm | 388 +- code/modules/spells/spell_types/trigger.dm | 52 +- code/modules/spells/spell_types/wizard.dm | 728 +- code/modules/surgery/core_removal.dm | 84 +- code/modules/surgery/eye_surgery.dm | 98 +- code/modules/surgery/implant_removal.dm | 128 +- code/modules/surgery/limb_augmentation.dm | 118 +- code/modules/surgery/lipoplasty.dm | 124 +- code/modules/surgery/organs/helpers.dm | 94 +- code/modules/surgery/organs/stomach.dm | 328 +- code/modules/surgery/plastic_surgery.dm | 104 +- code/modules/surgery/surgery_helpers.dm | 386 +- code/modules/surgery/surgery_step.dm | 356 +- code/modules/surgery/tools.dm | 838 +- code/modules/tgui/tgui.dm | 726 +- code/modules/unit_tests/_unit_tests.dm | 64 +- code/modules/unit_tests/anchored_mobs.dm | 18 +- .../unit_tests/chain_pull_through_space.dm | 124 +- code/modules/unit_tests/component_tests.dm | 24 +- code/modules/unit_tests/outfit_sanity.dm | 100 +- code/modules/unit_tests/spawn_humans.dm | 14 +- code/modules/unit_tests/surgeries.dm | 158 +- code/modules/unit_tests/unit_test.dm | 184 +- code/modules/uplink/uplink_devices.dm | 156 +- code/modules/vehicles/lavaboat.dm | 144 +- code/modules/vehicles/ridden.dm | 212 +- code/modules/vehicles/vehicle_actions.dm | 460 +- code/modules/vehicles/vehicle_key.dm | 146 +- code/modules/vending/wardrobes.dm | 934 +- config/admins.txt | 288 +- config/antag_rep.txt | 10 +- config/comms.txt | 18 +- config/title_screens/README.txt | 92 +- html/archivedchangelog.html | 18102 ++-- html/browser/common.css | 822 +- html/browser/scannernew.css | 90 +- html/changelog.css | 82 +- html/changelog.html | 3846 +- html/changelogs/.all_changelog.yml | 81670 ++++++++-------- html/create_object.html | 198 +- html/panels.css | 20 +- html/search.js | 64 +- interface/interface.dm | 174 +- interface/skin.dmf | 618 +- strings/names/adjectives.txt | 748 +- strings/names/ai.txt | 290 +- strings/names/carp.txt | 60 +- strings/names/clown.txt | 246 +- strings/names/death_commando.txt | 182 +- strings/names/first.txt | 2790 +- strings/names/first_female.txt | 1540 +- strings/names/first_male.txt | 1332 +- strings/names/golem.txt | 314 +- strings/names/last.txt | 1138 +- strings/names/lizard_female.txt | 324 +- strings/names/lizard_male.txt | 654 +- strings/names/mime.txt | 46 +- strings/names/ninjaname.txt | 86 +- strings/names/ninjatitle.txt | 90 +- strings/names/plasmaman.txt | 236 +- strings/names/posibrain.txt | 92 +- strings/names/verbs.txt | 1264 +- strings/names/wizardfirst.txt | 72 +- strings/names/wizardsecond.txt | 78 +- tgstation.dme | 6280 +- tools/CreditsTool/CreditsTool.csproj | 92 +- tools/CreditsTool/Program.cs | 300 +- tools/CreditsTool/UpdateCreditsDMI.sh | 26 +- tools/CreditsTool/remappings.txt | 58 +- tools/HitboxExpander/README | 20 +- tools/HitboxExpander/hitbox_expander.py | 214 +- tools/Redirector/Configurations.dm | 106 +- tools/Redirector/Redirect_Tgstation.dme | 42 +- tools/Redirector/Redirector.dm | 174 +- tools/Redirector/config.txt | 24 +- tools/Redirector/skin.dmf | 298 +- tools/Redirector/textprocs.dm | 304 +- tools/Runtime Condenser/Main.cpp | 836 +- .../UnstandardnessTestForDM.sln | 40 +- .../UnstandardnessTestForDM/Form1.Designer.cs | 320 +- .../UnstandardnessTestForDM/Form1.cs | 968 +- .../UnstandardnessTestForDM/Form1.resx | 238 +- .../UnstandardnessTestForDM/Program.cs | 42 +- .../Properties/AssemblyInfo.cs | 72 +- .../Properties/Resources.Designer.cs | 142 +- .../Properties/Resources.resx | 232 +- .../Properties/Settings.Designer.cs | 60 +- .../Properties/Settings.settings | 14 +- .../UnstandardnessTestForDM.csproj | 172 +- ...nstandardnessTestForDM.vshost.exe.manifest | 22 +- ...dnessTestForDM.csproj.FileListAbsolute.txt | 36 +- tools/UnstandardnessTestForDM/readme.txt | 8 +- tools/WebhookProcessor/secret.php | 266 +- tools/discordRoleScript/README.MD | 22 +- tools/discordRoleScript/Script.py | 118 +- tools/expand_filedir_paths.py | 186 +- tools/json_verifier.py | 40 +- tools/minibot/nudge.py | 48 +- tools/tgs4_scripts/PreSynchronize.bat | 6 +- tools/tgs4_scripts/PreSynchronize.ps1 | 62 +- tools/tgs4_scripts/PreSynchronize.sh | 74 +- tools/travis/travis_config.txt | 18 +- 1133 files changed, 266492 insertions(+), 266454 deletions(-) diff --git a/.dockerignore b/.dockerignore index 8e675af8cba..2e6259d23d6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,30 +1,30 @@ -.dockerignore -.editorconfig -.travis.yml -GPLv3.txt -LICENSE -README.md -TGS3.json -.github -.gitignore -.gitattributes -.git/hooks -.git/info -.git/modules -.git/objects -.git/refs -.vs* -cfg -data -SQL -node_modules -tgstation.dmb -tgstation.int -tgstation.rsc -tgstation.lk -tgstation.dyn.rsc -libmariadb.dll -rust_g.dll -BSQL.dll -appveyor.yml -Dockerfile +.dockerignore +.editorconfig +.travis.yml +GPLv3.txt +LICENSE +README.md +TGS3.json +.github +.gitignore +.gitattributes +.git/hooks +.git/info +.git/modules +.git/objects +.git/refs +.vs* +cfg +data +SQL +node_modules +tgstation.dmb +tgstation.int +tgstation.rsc +tgstation.lk +tgstation.dyn.rsc +libmariadb.dll +rust_g.dll +BSQL.dll +appveyor.yml +Dockerfile diff --git a/.editorconfig b/.editorconfig index 79c4b77c52b..e050ceea120 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,7 @@ indent_size = 4 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +end_of_line = lf [*.yml] indent_style = space diff --git a/.gitattributes b/.gitattributes index 2f9e769c262..115b676a959 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,43 @@ -# merger hooks, run tools/hooks/install.bat or install.sh to set up -*.dmm merge=dmm -*.dmi merge=dmi +* text=auto -# force changelog merging to use union -html/changelog.html merge=union +## Enforce text mode and LF line breaks +*.bat text eol=lf +*.css text eol=lf +*.css text eol=lf +*.dm text eol=lf +*.dme text eol=lf +*.dmf text eol=lf +*.htm text eol=lf +*.html text eol=lf +*.html text eol=lf +*.js text eol=lf +*.json text eol=lf +*.jsx text eol=lf +*.md text eol=lf +*.py text eol=lf +*.scss text eol=lf +*.sh text eol=lf +*.sql text eol=lf +*.svg text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.txt text eol=lf +*.yaml text eol=lf +*.yml text eol=lf + +## Enforce binary mode +*.bmp binary +*.dll binary +*.dmb binary +*.exe binary +*.gif binary +*.jpg binary +*.png binary +*.so binary + +## Merger hooks, run tools/hooks/install.bat or install.sh to set up +*.dmm text eol=lf merge=dmm +*.dmi binary merge=dmi + +## Force changelog merging to use union +html/changelog.html text eol=lf merge=union diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0abac7a5338..f794162129c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ -{ - "recommendations": [ - "gbasood.byond-dm-language-support", - "platymuus.dm-langclient", - "EditorConfig.EditorConfig", - "dbaeumer.vscode-eslint" - ] -} +{ + "recommendations": [ + "gbasood.byond-dm-language-support", + "platymuus.dm-langclient", + "EditorConfig.EditorConfig", + "dbaeumer.vscode-eslint" + ] +} diff --git a/Dockerfile b/Dockerfile index f7e32182910..4ea0b38db7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,62 +1,62 @@ -FROM tgstation/byond:513.1526 as base - -FROM base as rust_g - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - git \ - ca-certificates - -WORKDIR /rust_g - -RUN apt-get install -y --no-install-recommends \ - libssl-dev \ - pkg-config \ - curl \ - gcc-multilib \ - && curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host i686-unknown-linux-gnu \ - && git init \ - && git remote add origin https://github.com/tgstation/rust-g - -COPY dependencies.sh . - -RUN /bin/bash -c "source dependencies.sh \ - && git fetch --depth 1 origin \$RUST_G_VERSION" \ - && git checkout FETCH_HEAD \ - && ~/.cargo/bin/cargo build --release - -FROM base as dm_base - -WORKDIR /tgstation - -FROM dm_base as build - -COPY . . - -RUN DreamMaker -max_errors 0 tgstation.dme \ - && tools/deploy.sh /deploy \ - && rm /deploy/*.dll - -FROM dm_base - -EXPOSE 1337 - -RUN apt-get update \ - && apt-get install -y --no-install-recommends software-properties-common \ - && add-apt-repository ppa:ubuntu-toolchain-r/test \ - && apt-get update \ - && apt-get upgrade -y \ - && apt-get dist-upgrade -y \ - && apt-get install -y --no-install-recommends \ - libmariadb2 \ - mariadb-client \ - libssl1.0.0 \ - && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /root/.byond/bin - -COPY --from=rust_g /rust_g/target/release/librust_g.so /root/.byond/bin/rust_g -COPY --from=build /deploy ./ - -VOLUME [ "/tgstation/config", "/tgstation/data" ] - -ENTRYPOINT [ "DreamDaemon", "tgstation.dmb", "-port", "1337", "-trusted", "-close", "-verbose" ] +FROM tgstation/byond:513.1526 as base + +FROM base as rust_g + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git \ + ca-certificates + +WORKDIR /rust_g + +RUN apt-get install -y --no-install-recommends \ + libssl-dev \ + pkg-config \ + curl \ + gcc-multilib \ + && curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host i686-unknown-linux-gnu \ + && git init \ + && git remote add origin https://github.com/tgstation/rust-g + +COPY dependencies.sh . + +RUN /bin/bash -c "source dependencies.sh \ + && git fetch --depth 1 origin \$RUST_G_VERSION" \ + && git checkout FETCH_HEAD \ + && ~/.cargo/bin/cargo build --release + +FROM base as dm_base + +WORKDIR /tgstation + +FROM dm_base as build + +COPY . . + +RUN DreamMaker -max_errors 0 tgstation.dme \ + && tools/deploy.sh /deploy \ + && rm /deploy/*.dll + +FROM dm_base + +EXPOSE 1337 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends software-properties-common \ + && add-apt-repository ppa:ubuntu-toolchain-r/test \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get dist-upgrade -y \ + && apt-get install -y --no-install-recommends \ + libmariadb2 \ + mariadb-client \ + libssl1.0.0 \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /root/.byond/bin + +COPY --from=rust_g /rust_g/target/release/librust_g.so /root/.byond/bin/rust_g +COPY --from=build /deploy ./ + +VOLUME [ "/tgstation/config", "/tgstation/data" ] + +ENTRYPOINT [ "DreamDaemon", "tgstation.dmb", "-port", "1337", "-trusted", "-close", "-verbose" ] diff --git a/README.md b/README.md index ac66647cab1..00f8d4fab13 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,53 @@ -## /tg/station codebase - -[![Build Status](https://travis-ci.org/tgstation/tgstation.png)](https://travis-ci.org/tgstation/tgstation) -[![Percentage of issues still open](https://isitmaintained.com/badge/open/tgstation/tgstation.svg)](https://isitmaintained.com/project/tgstation/tgstation "Percentage of issues still open") -[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/tgstation/tgstation.svg)](https://isitmaintained.com/project/tgstation/tgstation "Average time to resolve an issue") -![Coverage](https://img.shields.io/badge/coverage---3%25-red.svg) - -[![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://user-images.githubusercontent.com/8171642/50290880-ffef5500-043a-11e9-8270-a2e5b697c86c.png) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) - -* **Website:** https://www.tgstation13.org -* **Code:** https://github.com/tgstation/tgstation -* **Wiki:** https://tgstation13.org/wiki/Main_Page -* **Codedocs:** https://codedocs.tgstation13.org/ -* **/tg/station Discord:** https://tgstation13.org/phpBB/viewforum.php?f=60 -* **Coderbus Discord:** https://discord.gg/Vh8TJp9 -* ~~**IRC:** irc://irc.rizon.net/coderbus~~ (dead) - -This is the codebase for the /tg/station flavoured fork of SpaceStation 13. - -Space Station 13 is a paranoia-laden round-based roleplaying game set against the backdrop of a nonsensical, metal death trap masquerading as a space station, with charming spritework designed to represent the sci-fi setting and its dangerous undertones. Have fun, and survive! - -## DOWNLOADING -[Downloading](.github/DOWNLOADING.md) - -[Running on the server](.github/RUNNING_A_SERVER.md) - -[Maps and Away Missions](.github/MAPS_AND_AWAY_MISSIONS.md) - -## Requirements for contributors -[Guidelines for Contributors](.github/CONTRIBUTING.md) - -[/tg/station HACKMD account](https://hackmd.io/@tgstation) - Design documentation here - -[Documenting your code](.github/AUTODOC_GUIDE.md) - -[Policy configuration system](.github/POLICYCONFIG.md) - -[Interested in some starting lore?](https://github.com/tgstation/common_core) - -## LICENSE - -All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.html). - -All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). -(Including tools unless their readme specifies otherwise.) - -See LICENSE and GPLv3.txt for more details. - -The TGS DMAPI API is licensed as a subproject under the MIT license. - -See the footer of [code/__DEFINES/tgs.dm](./code/__DEFINES/tgs.dm) and [code/modules/tgs/LICENSE](./code/modules/tgs/LICENSE) for the MIT license. - -All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated. +## /tg/station codebase + +[![Build Status](https://travis-ci.org/tgstation/tgstation.png)](https://travis-ci.org/tgstation/tgstation) +[![Percentage of issues still open](https://isitmaintained.com/badge/open/tgstation/tgstation.svg)](https://isitmaintained.com/project/tgstation/tgstation "Percentage of issues still open") +[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/tgstation/tgstation.svg)](https://isitmaintained.com/project/tgstation/tgstation "Average time to resolve an issue") +![Coverage](https://img.shields.io/badge/coverage---3%25-red.svg) + +[![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://user-images.githubusercontent.com/8171642/50290880-ffef5500-043a-11e9-8270-a2e5b697c86c.png) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) + +* **Website:** https://www.tgstation13.org +* **Code:** https://github.com/tgstation/tgstation +* **Wiki:** https://tgstation13.org/wiki/Main_Page +* **Codedocs:** https://codedocs.tgstation13.org/ +* **/tg/station Discord:** https://tgstation13.org/phpBB/viewforum.php?f=60 +* **Coderbus Discord:** https://discord.gg/Vh8TJp9 +* ~~**IRC:** irc://irc.rizon.net/coderbus~~ (dead) + +This is the codebase for the /tg/station flavoured fork of SpaceStation 13. + +Space Station 13 is a paranoia-laden round-based roleplaying game set against the backdrop of a nonsensical, metal death trap masquerading as a space station, with charming spritework designed to represent the sci-fi setting and its dangerous undertones. Have fun, and survive! + +## DOWNLOADING +[Downloading](.github/DOWNLOADING.md) + +[Running on the server](.github/RUNNING_A_SERVER.md) + +[Maps and Away Missions](.github/MAPS_AND_AWAY_MISSIONS.md) + +## Requirements for contributors +[Guidelines for Contributors](.github/CONTRIBUTING.md) + +[/tg/station HACKMD account](https://hackmd.io/@tgstation) - Design documentation here + +[Documenting your code](.github/AUTODOC_GUIDE.md) + +[Policy configuration system](.github/POLICYCONFIG.md) + +[Interested in some starting lore?](https://github.com/tgstation/common_core) + +## LICENSE + +All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.html). + +All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). +(Including tools unless their readme specifies otherwise.) + +See LICENSE and GPLv3.txt for more details. + +The TGS DMAPI API is licensed as a subproject under the MIT license. + +See the footer of [code/__DEFINES/tgs.dm](./code/__DEFINES/tgs.dm) and [code/modules/tgs/LICENSE](./code/modules/tgs/LICENSE) for the MIT license. + +All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated. diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 1f5edef8c57..176475a40c9 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -1,583 +1,583 @@ -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `admin` --- - -DROP TABLE IF EXISTS `admin`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `admin` ( - `ckey` varchar(32) NOT NULL, - `rank` varchar(32) NOT NULL, - `feedback` varchar(255) DEFAULT NULL, - PRIMARY KEY (`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `admin_log` --- - -DROP TABLE IF EXISTS `admin_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `admin_log` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `adminckey` varchar(32) NOT NULL, - `adminip` int(10) unsigned NOT NULL, - `operation` enum('add admin','remove admin','change admin rank','add rank','remove rank','change rank flags') NOT NULL, - `target` varchar(32) NOT NULL, - `log` varchar(1000) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `admin_ranks` --- - -DROP TABLE IF EXISTS `admin_ranks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `admin_ranks` ( - `rank` varchar(32) NOT NULL, - `flags` smallint(5) unsigned NOT NULL, - `exclude_flags` smallint(5) unsigned NOT NULL, - `can_edit_flags` smallint(5) unsigned NOT NULL, - PRIMARY KEY (`rank`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `ban` --- - -DROP TABLE IF EXISTS `ban`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `ban` ( - `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `bantime` DATETIME NOT NULL, - `server_ip` INT(10) UNSIGNED NOT NULL, - `server_port` SMALLINT(5) UNSIGNED NOT NULL, - `round_id` INT(11) UNSIGNED NOT NULL, - `role` VARCHAR(32) NULL DEFAULT NULL, - `expiration_time` DATETIME NULL DEFAULT NULL, - `applies_to_admins` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0', - `reason` VARCHAR(2048) NOT NULL, - `ckey` VARCHAR(32) NULL DEFAULT NULL, - `ip` INT(10) UNSIGNED NULL DEFAULT NULL, - `computerid` VARCHAR(32) NULL DEFAULT NULL, - `a_ckey` VARCHAR(32) NOT NULL, - `a_ip` INT(10) UNSIGNED NOT NULL, - `a_computerid` VARCHAR(32) NOT NULL, - `who` VARCHAR(2048) NOT NULL, - `adminwho` VARCHAR(2048) NOT NULL, - `edits` TEXT NULL DEFAULT NULL, - `unbanned_datetime` DATETIME NULL DEFAULT NULL, - `unbanned_ckey` VARCHAR(32) NULL DEFAULT NULL, - `unbanned_ip` INT(10) UNSIGNED NULL DEFAULT NULL, - `unbanned_computerid` VARCHAR(32) NULL DEFAULT NULL, - `unbanned_round_id` INT(11) UNSIGNED NULL DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `idx_ban_isbanned` (`ckey`,`role`,`unbanned_datetime`,`expiration_time`), - KEY `idx_ban_isbanned_details` (`ckey`,`ip`,`computerid`,`role`,`unbanned_datetime`,`expiration_time`), - KEY `idx_ban_count` (`bantime`,`a_ckey`,`applies_to_admins`,`unbanned_datetime`,`expiration_time`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `connection_log` --- - -DROP TABLE IF EXISTS `connection_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `connection_log` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime DEFAULT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `ckey` varchar(45) DEFAULT NULL, - `ip` int(10) unsigned NOT NULL, - `computerid` varchar(45) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `death` --- - -DROP TABLE IF EXISTS `death`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `death` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `pod` varchar(50) NOT NULL, - `x_coord` smallint(5) unsigned NOT NULL, - `y_coord` smallint(5) unsigned NOT NULL, - `z_coord` smallint(5) unsigned NOT NULL, - `mapname` varchar(32) NOT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) NOT NULL, - `tod` datetime NOT NULL COMMENT 'Time of death', - `job` varchar(32) NOT NULL, - `special` varchar(32) DEFAULT NULL, - `name` varchar(96) NOT NULL, - `byondkey` varchar(32) NOT NULL, - `laname` varchar(96) DEFAULT NULL, - `lakey` varchar(32) DEFAULT NULL, - `bruteloss` smallint(5) unsigned NOT NULL, - `brainloss` smallint(5) unsigned NOT NULL, - `fireloss` smallint(5) unsigned NOT NULL, - `oxyloss` smallint(5) unsigned NOT NULL, - `toxloss` smallint(5) unsigned NOT NULL, - `cloneloss` smallint(5) unsigned NOT NULL, - `staminaloss` smallint(5) unsigned NOT NULL, - `last_words` varchar(255) DEFAULT NULL, - `suicide` tinyint(1) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `feedback` --- - -DROP TABLE IF EXISTS `feedback`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `feedback` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `key_name` varchar(32) NOT NULL, - `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL, - `version` tinyint(3) unsigned NOT NULL, - `json` json NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `ipintel` --- - -DROP TABLE IF EXISTS `ipintel`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `ipintel` ( - `ip` int(10) unsigned NOT NULL, - `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `intel` double NOT NULL DEFAULT '0', - PRIMARY KEY (`ip`), - KEY `idx_ipintel` (`ip`,`intel`,`date`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `legacy_population` --- - -DROP TABLE IF EXISTS `legacy_population`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `legacy_population` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `playercount` int(11) DEFAULT NULL, - `admincount` int(11) DEFAULT NULL, - `time` datetime NOT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) unsigned NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `library` --- - -DROP TABLE IF EXISTS `library`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `library` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `author` varchar(45) NOT NULL, - `title` varchar(45) NOT NULL, - `content` text NOT NULL, - `category` enum('Any','Fiction','Non-Fiction','Adult','Reference','Religion') NOT NULL, - `ckey` varchar(32) NOT NULL DEFAULT 'LEGACY', - `datetime` datetime NOT NULL, - `deleted` tinyint(1) unsigned DEFAULT NULL, - `round_id_created` int(11) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `deleted_idx` (`deleted`), - KEY `idx_lib_id_del` (`id`,`deleted`), - KEY `idx_lib_del_title` (`deleted`,`title`), - KEY `idx_lib_search` (`deleted`,`author`,`title`,`category`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `messages` --- - -DROP TABLE IF EXISTS `messages`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `messages` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `type` enum('memo','message','message sent','note','watchlist entry') NOT NULL, - `targetckey` varchar(32) NOT NULL, - `adminckey` varchar(32) NOT NULL, - `text` varchar(2048) NOT NULL, - `timestamp` datetime NOT NULL, - `server` varchar(32) DEFAULT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `secret` tinyint(1) unsigned NOT NULL, - `expire_timestamp` datetime DEFAULT NULL, - `severity` enum('high','medium','minor','none') DEFAULT NULL, - `lasteditor` varchar(32) DEFAULT NULL, - `edits` text, - `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', - `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `idx_msg_ckey_time` (`targetckey`,`timestamp`, `deleted`), - KEY `idx_msg_type_ckeys_time` (`type`,`targetckey`,`adminckey`,`timestamp`, `deleted`), - KEY `idx_msg_type_ckey_time_odr` (`type`,`targetckey`,`timestamp`, `deleted`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `role_time` --- - -DROP TABLE IF EXISTS `role_time`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; - -CREATE TABLE `role_time` -( `ckey` VARCHAR(32) NOT NULL , - `job` VARCHAR(32) NOT NULL , - `minutes` INT UNSIGNED NOT NULL, - PRIMARY KEY (`ckey`, `job`) - ) ENGINE = InnoDB; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `role_time` --- - -DROP TABLE IF EXISTS `role_time_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; - -CREATE TABLE IF NOT EXISTS `role_time_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `ckey` varchar(32) NOT NULL, - `job` varchar(128) NOT NULL, - `delta` int(11) NOT NULL, - `datetime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), - PRIMARY KEY (`id`), - KEY `ckey` (`ckey`), - KEY `job` (`job`), - KEY `datetime` (`datetime`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `player` --- - -DROP TABLE IF EXISTS `player`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `player` ( - `ckey` varchar(32) NOT NULL, - `byond_key` varchar(32) DEFAULT NULL, - `firstseen` datetime NOT NULL, - `firstseen_round_id` int(11) unsigned NOT NULL, - `lastseen` datetime NOT NULL, - `lastseen_round_id` int(11) unsigned NOT NULL, - `ip` int(10) unsigned NOT NULL, - `computerid` varchar(32) NOT NULL, - `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', - `accountjoindate` DATE DEFAULT NULL, - `flags` smallint(5) unsigned DEFAULT '0' NOT NULL, - `discord_id` BIGINT(20) NULL DEFAULT NULL, - PRIMARY KEY (`ckey`), - KEY `idx_player_cid_ckey` (`computerid`,`ckey`), - KEY `idx_player_ip_ckey` (`ip`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_option` --- - -DROP TABLE IF EXISTS `poll_option`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `poll_option` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `pollid` int(11) NOT NULL, - `text` varchar(255) NOT NULL, - `minval` int(3) DEFAULT NULL, - `maxval` int(3) DEFAULT NULL, - `descmin` varchar(32) DEFAULT NULL, - `descmid` varchar(32) DEFAULT NULL, - `descmax` varchar(32) DEFAULT NULL, - `default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1', - `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - KEY `idx_pop_pollid` (`pollid`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_question` --- - -DROP TABLE IF EXISTS `poll_question`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `poll_question` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `polltype` enum('OPTION','TEXT','NUMVAL','MULTICHOICE','IRV') NOT NULL, - `created_datetime` datetime NOT NULL, - `starttime` datetime NOT NULL, - `endtime` datetime NOT NULL, - `question` varchar(255) NOT NULL, - `subtitle` varchar(255) DEFAULT NULL, - `adminonly` tinyint(1) unsigned NOT NULL, - `multiplechoiceoptions` int(2) DEFAULT NULL, - `createdby_ckey` varchar(32) NOT NULL, - `createdby_ip` int(10) unsigned NOT NULL, - `dontshow` tinyint(1) unsigned NOT NULL, - `allow_revoting` tinyint(1) unsigned NOT NULL, - `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`,`createdby_ip`), - KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`), - KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_textreply` --- - -DROP TABLE IF EXISTS `poll_textreply`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `poll_textreply` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `pollid` int(11) NOT NULL, - `ckey` varchar(32) NOT NULL, - `ip` int(10) unsigned NOT NULL, - `replytext` varchar(2048) NOT NULL, - `adminrank` varchar(32) NOT NULL, - `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_vote` --- - -DROP TABLE IF EXISTS `poll_vote`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `poll_vote` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `pollid` int(11) NOT NULL, - `optionid` int(11) NOT NULL, - `ckey` varchar(32) NOT NULL, - `ip` int(10) unsigned NOT NULL, - `adminrank` varchar(32) NOT NULL, - `rating` int(2) DEFAULT NULL, - `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`), - KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `round` --- -DROP TABLE IF EXISTS `round`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `round` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `initialize_datetime` DATETIME NOT NULL, - `start_datetime` DATETIME NULL, - `shutdown_datetime` DATETIME NULL, - `end_datetime` DATETIME NULL, - `server_ip` INT(10) UNSIGNED NOT NULL, - `server_port` SMALLINT(5) UNSIGNED NOT NULL, - `commit_hash` CHAR(40) NULL, - `game_mode` VARCHAR(32) NULL, - `game_mode_result` VARCHAR(64) NULL, - `end_state` VARCHAR(64) NULL, - `shuttle_name` VARCHAR(64) NULL, - `map_name` VARCHAR(32) NULL, - `station_name` VARCHAR(80) NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - --- --- Table structure for table `schema_revision` --- -DROP TABLE IF EXISTS `schema_revision`; -CREATE TABLE `schema_revision` ( - `major` TINYINT(3) unsigned NOT NULL, - `minor` TINYINT(3) unsigned NOT NULL, - `date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`major`, `minor`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - --- --- Table structure for table `stickyban` --- -DROP TABLE IF EXISTS `stickyban`; -CREATE TABLE `stickyban` ( - `ckey` VARCHAR(32) NOT NULL, - `reason` VARCHAR(2048) NOT NULL, - `banning_admin` VARCHAR(32) NOT NULL, - `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`ckey`) -) ENGINE=InnoDB; - --- --- Table structure for table `stickyban_matched_ckey` --- -DROP TABLE IF EXISTS `stickyban_matched_ckey`; -CREATE TABLE `stickyban_matched_ckey` ( - `stickyban` VARCHAR(32) NOT NULL, - `matched_ckey` VARCHAR(32) NOT NULL, - `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `exempt` TINYINT(1) NOT NULL DEFAULT '0', - PRIMARY KEY (`stickyban`, `matched_ckey`) -) ENGINE=InnoDB; - --- --- Table structure for table `stickyban_matched_ip` --- -DROP TABLE IF EXISTS `stickyban_matched_ip`; -CREATE TABLE `stickyban_matched_ip` ( - `stickyban` VARCHAR(32) NOT NULL, - `matched_ip` INT UNSIGNED NOT NULL, - `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`stickyban`, `matched_ip`) -) ENGINE=InnoDB; - --- --- Table structure for table `stickyban_matched_cid` --- -DROP TABLE IF EXISTS `stickyban_matched_cid`; -CREATE TABLE `stickyban_matched_cid` ( - `stickyban` VARCHAR(32) NOT NULL, - `matched_cid` VARCHAR(32) NOT NULL, - `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`stickyban`, `matched_cid`) -) ENGINE=InnoDB; - --- --- Table structure for table `achievements` --- -DROP TABLE IF EXISTS `achievements`; -CREATE TABLE `achievements` ( - `ckey` VARCHAR(32) NOT NULL, - `achievement_key` VARCHAR(32) NOT NULL, - `value` INT NULL, - `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`ckey`,`achievement_key`) -) ENGINE=InnoDB; - -DROP TABLE IF EXISTS `achievement_metadata`; -CREATE TABLE `achievement_metadata` ( - `achievement_key` VARCHAR(32) NOT NULL, - `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0, - `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL, - `achievement_name` VARCHAR(64) NULL DEFAULT NULL, - `achievement_description` VARCHAR(512) NULL DEFAULT NULL, - PRIMARY KEY (`achievement_key`) -) ENGINE=InnoDB; - --- --- Table structure for table `ticket` --- -DROP TABLE IF EXISTS `ticket`; -CREATE TABLE `ticket` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `ticket` smallint(11) unsigned NOT NULL, - `action` varchar(20) NOT NULL DEFAULT 'Message', - `message` text NOT NULL, - `timestamp` datetime NOT NULL, - `recipient` varchar(32) DEFAULT NULL, - `sender` varchar(32) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - -DELIMITER $$ -CREATE PROCEDURE `set_poll_deleted`( - IN `poll_id` INT -) -SQL SECURITY INVOKER -BEGIN -UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id; -UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id; -UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id; -UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id; -END -$$ -CREATE TRIGGER `role_timeTlogupdate` AFTER UPDATE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes); -END -$$ -CREATE TRIGGER `role_timeTloginsert` AFTER INSERT ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.ckey, NEW.job, NEW.minutes); -END -$$ -CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); -END -$$ -DELIMITER ; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `admin` +-- + +DROP TABLE IF EXISTS `admin`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `admin` ( + `ckey` varchar(32) NOT NULL, + `rank` varchar(32) NOT NULL, + `feedback` varchar(255) DEFAULT NULL, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `admin_log` +-- + +DROP TABLE IF EXISTS `admin_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `admin_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `adminckey` varchar(32) NOT NULL, + `adminip` int(10) unsigned NOT NULL, + `operation` enum('add admin','remove admin','change admin rank','add rank','remove rank','change rank flags') NOT NULL, + `target` varchar(32) NOT NULL, + `log` varchar(1000) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `admin_ranks` +-- + +DROP TABLE IF EXISTS `admin_ranks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `admin_ranks` ( + `rank` varchar(32) NOT NULL, + `flags` smallint(5) unsigned NOT NULL, + `exclude_flags` smallint(5) unsigned NOT NULL, + `can_edit_flags` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`rank`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ban` +-- + +DROP TABLE IF EXISTS `ban`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ban` ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `bantime` DATETIME NOT NULL, + `server_ip` INT(10) UNSIGNED NOT NULL, + `server_port` SMALLINT(5) UNSIGNED NOT NULL, + `round_id` INT(11) UNSIGNED NOT NULL, + `role` VARCHAR(32) NULL DEFAULT NULL, + `expiration_time` DATETIME NULL DEFAULT NULL, + `applies_to_admins` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0', + `reason` VARCHAR(2048) NOT NULL, + `ckey` VARCHAR(32) NULL DEFAULT NULL, + `ip` INT(10) UNSIGNED NULL DEFAULT NULL, + `computerid` VARCHAR(32) NULL DEFAULT NULL, + `a_ckey` VARCHAR(32) NOT NULL, + `a_ip` INT(10) UNSIGNED NOT NULL, + `a_computerid` VARCHAR(32) NOT NULL, + `who` VARCHAR(2048) NOT NULL, + `adminwho` VARCHAR(2048) NOT NULL, + `edits` TEXT NULL DEFAULT NULL, + `unbanned_datetime` DATETIME NULL DEFAULT NULL, + `unbanned_ckey` VARCHAR(32) NULL DEFAULT NULL, + `unbanned_ip` INT(10) UNSIGNED NULL DEFAULT NULL, + `unbanned_computerid` VARCHAR(32) NULL DEFAULT NULL, + `unbanned_round_id` INT(11) UNSIGNED NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_ban_isbanned` (`ckey`,`role`,`unbanned_datetime`,`expiration_time`), + KEY `idx_ban_isbanned_details` (`ckey`,`ip`,`computerid`,`role`,`unbanned_datetime`,`expiration_time`), + KEY `idx_ban_count` (`bantime`,`a_ckey`,`applies_to_admins`,`unbanned_datetime`,`expiration_time`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `connection_log` +-- + +DROP TABLE IF EXISTS `connection_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `connection_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime DEFAULT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `ckey` varchar(45) DEFAULT NULL, + `ip` int(10) unsigned NOT NULL, + `computerid` varchar(45) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `death` +-- + +DROP TABLE IF EXISTS `death`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `death` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `pod` varchar(50) NOT NULL, + `x_coord` smallint(5) unsigned NOT NULL, + `y_coord` smallint(5) unsigned NOT NULL, + `z_coord` smallint(5) unsigned NOT NULL, + `mapname` varchar(32) NOT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) NOT NULL, + `tod` datetime NOT NULL COMMENT 'Time of death', + `job` varchar(32) NOT NULL, + `special` varchar(32) DEFAULT NULL, + `name` varchar(96) NOT NULL, + `byondkey` varchar(32) NOT NULL, + `laname` varchar(96) DEFAULT NULL, + `lakey` varchar(32) DEFAULT NULL, + `bruteloss` smallint(5) unsigned NOT NULL, + `brainloss` smallint(5) unsigned NOT NULL, + `fireloss` smallint(5) unsigned NOT NULL, + `oxyloss` smallint(5) unsigned NOT NULL, + `toxloss` smallint(5) unsigned NOT NULL, + `cloneloss` smallint(5) unsigned NOT NULL, + `staminaloss` smallint(5) unsigned NOT NULL, + `last_words` varchar(255) DEFAULT NULL, + `suicide` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `feedback` +-- + +DROP TABLE IF EXISTS `feedback`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `feedback` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `key_name` varchar(32) NOT NULL, + `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL, + `version` tinyint(3) unsigned NOT NULL, + `json` json NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ipintel` +-- + +DROP TABLE IF EXISTS `ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel` ( + `ip` int(10) unsigned NOT NULL, + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `intel` double NOT NULL DEFAULT '0', + PRIMARY KEY (`ip`), + KEY `idx_ipintel` (`ip`,`intel`,`date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `legacy_population` +-- + +DROP TABLE IF EXISTS `legacy_population`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `legacy_population` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `playercount` int(11) DEFAULT NULL, + `admincount` int(11) DEFAULT NULL, + `time` datetime NOT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `library` +-- + +DROP TABLE IF EXISTS `library`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `library` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `author` varchar(45) NOT NULL, + `title` varchar(45) NOT NULL, + `content` text NOT NULL, + `category` enum('Any','Fiction','Non-Fiction','Adult','Reference','Religion') NOT NULL, + `ckey` varchar(32) NOT NULL DEFAULT 'LEGACY', + `datetime` datetime NOT NULL, + `deleted` tinyint(1) unsigned DEFAULT NULL, + `round_id_created` int(11) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `deleted_idx` (`deleted`), + KEY `idx_lib_id_del` (`id`,`deleted`), + KEY `idx_lib_del_title` (`deleted`,`title`), + KEY `idx_lib_search` (`deleted`,`author`,`title`,`category`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `messages` +-- + +DROP TABLE IF EXISTS `messages`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `messages` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` enum('memo','message','message sent','note','watchlist entry') NOT NULL, + `targetckey` varchar(32) NOT NULL, + `adminckey` varchar(32) NOT NULL, + `text` varchar(2048) NOT NULL, + `timestamp` datetime NOT NULL, + `server` varchar(32) DEFAULT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `secret` tinyint(1) unsigned NOT NULL, + `expire_timestamp` datetime DEFAULT NULL, + `severity` enum('high','medium','minor','none') DEFAULT NULL, + `lasteditor` varchar(32) DEFAULT NULL, + `edits` text, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_msg_ckey_time` (`targetckey`,`timestamp`, `deleted`), + KEY `idx_msg_type_ckeys_time` (`type`,`targetckey`,`adminckey`,`timestamp`, `deleted`), + KEY `idx_msg_type_ckey_time_odr` (`type`,`targetckey`,`timestamp`, `deleted`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `role_time` +-- + +DROP TABLE IF EXISTS `role_time`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; + +CREATE TABLE `role_time` +( `ckey` VARCHAR(32) NOT NULL , + `job` VARCHAR(32) NOT NULL , + `minutes` INT UNSIGNED NOT NULL, + PRIMARY KEY (`ckey`, `job`) + ) ENGINE = InnoDB; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `role_time` +-- + +DROP TABLE IF EXISTS `role_time_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; + +CREATE TABLE IF NOT EXISTS `role_time_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `ckey` varchar(32) NOT NULL, + `job` varchar(128) NOT NULL, + `delta` int(11) NOT NULL, + `datetime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (`id`), + KEY `ckey` (`ckey`), + KEY `job` (`job`), + KEY `datetime` (`datetime`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `player` +-- + +DROP TABLE IF EXISTS `player`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `player` ( + `ckey` varchar(32) NOT NULL, + `byond_key` varchar(32) DEFAULT NULL, + `firstseen` datetime NOT NULL, + `firstseen_round_id` int(11) unsigned NOT NULL, + `lastseen` datetime NOT NULL, + `lastseen_round_id` int(11) unsigned NOT NULL, + `ip` int(10) unsigned NOT NULL, + `computerid` varchar(32) NOT NULL, + `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', + `accountjoindate` DATE DEFAULT NULL, + `flags` smallint(5) unsigned DEFAULT '0' NOT NULL, + `discord_id` BIGINT(20) NULL DEFAULT NULL, + PRIMARY KEY (`ckey`), + KEY `idx_player_cid_ckey` (`computerid`,`ckey`), + KEY `idx_player_ip_ckey` (`ip`,`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_option` +-- + +DROP TABLE IF EXISTS `poll_option`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `poll_option` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `pollid` int(11) NOT NULL, + `text` varchar(255) NOT NULL, + `minval` int(3) DEFAULT NULL, + `maxval` int(3) DEFAULT NULL, + `descmin` varchar(32) DEFAULT NULL, + `descmid` varchar(32) DEFAULT NULL, + `descmax` varchar(32) DEFAULT NULL, + `default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1', + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `idx_pop_pollid` (`pollid`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_question` +-- + +DROP TABLE IF EXISTS `poll_question`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `poll_question` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `polltype` enum('OPTION','TEXT','NUMVAL','MULTICHOICE','IRV') NOT NULL, + `created_datetime` datetime NOT NULL, + `starttime` datetime NOT NULL, + `endtime` datetime NOT NULL, + `question` varchar(255) NOT NULL, + `subtitle` varchar(255) DEFAULT NULL, + `adminonly` tinyint(1) unsigned NOT NULL, + `multiplechoiceoptions` int(2) DEFAULT NULL, + `createdby_ckey` varchar(32) NOT NULL, + `createdby_ip` int(10) unsigned NOT NULL, + `dontshow` tinyint(1) unsigned NOT NULL, + `allow_revoting` tinyint(1) unsigned NOT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`,`createdby_ip`), + KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`), + KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_textreply` +-- + +DROP TABLE IF EXISTS `poll_textreply`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `poll_textreply` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `pollid` int(11) NOT NULL, + `ckey` varchar(32) NOT NULL, + `ip` int(10) unsigned NOT NULL, + `replytext` varchar(2048) NOT NULL, + `adminrank` varchar(32) NOT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_vote` +-- + +DROP TABLE IF EXISTS `poll_vote`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `poll_vote` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `pollid` int(11) NOT NULL, + `optionid` int(11) NOT NULL, + `ckey` varchar(32) NOT NULL, + `ip` int(10) unsigned NOT NULL, + `adminrank` varchar(32) NOT NULL, + `rating` int(2) DEFAULT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`), + KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `round` +-- +DROP TABLE IF EXISTS `round`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `round` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `initialize_datetime` DATETIME NOT NULL, + `start_datetime` DATETIME NULL, + `shutdown_datetime` DATETIME NULL, + `end_datetime` DATETIME NULL, + `server_ip` INT(10) UNSIGNED NOT NULL, + `server_port` SMALLINT(5) UNSIGNED NOT NULL, + `commit_hash` CHAR(40) NULL, + `game_mode` VARCHAR(32) NULL, + `game_mode_result` VARCHAR(64) NULL, + `end_state` VARCHAR(64) NULL, + `shuttle_name` VARCHAR(64) NULL, + `map_name` VARCHAR(32) NULL, + `station_name` VARCHAR(80) NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +-- +-- Table structure for table `schema_revision` +-- +DROP TABLE IF EXISTS `schema_revision`; +CREATE TABLE `schema_revision` ( + `major` TINYINT(3) unsigned NOT NULL, + `minor` TINYINT(3) unsigned NOT NULL, + `date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`major`, `minor`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Table structure for table `stickyban` +-- +DROP TABLE IF EXISTS `stickyban`; +CREATE TABLE `stickyban` ( + `ckey` VARCHAR(32) NOT NULL, + `reason` VARCHAR(2048) NOT NULL, + `banning_admin` VARCHAR(32) NOT NULL, + `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `stickyban_matched_ckey` +-- +DROP TABLE IF EXISTS `stickyban_matched_ckey`; +CREATE TABLE `stickyban_matched_ckey` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ckey` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `exempt` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`stickyban`, `matched_ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `stickyban_matched_ip` +-- +DROP TABLE IF EXISTS `stickyban_matched_ip`; +CREATE TABLE `stickyban_matched_ip` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ip` INT UNSIGNED NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_ip`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `stickyban_matched_cid` +-- +DROP TABLE IF EXISTS `stickyban_matched_cid`; +CREATE TABLE `stickyban_matched_cid` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_cid` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_cid`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `achievements` +-- +DROP TABLE IF EXISTS `achievements`; +CREATE TABLE `achievements` ( + `ckey` VARCHAR(32) NOT NULL, + `achievement_key` VARCHAR(32) NOT NULL, + `value` INT NULL, + `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`,`achievement_key`) +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `achievement_metadata`; +CREATE TABLE `achievement_metadata` ( + `achievement_key` VARCHAR(32) NOT NULL, + `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0, + `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL, + `achievement_name` VARCHAR(64) NULL DEFAULT NULL, + `achievement_description` VARCHAR(512) NULL DEFAULT NULL, + PRIMARY KEY (`achievement_key`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ticket` +-- +DROP TABLE IF EXISTS `ticket`; +CREATE TABLE `ticket` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `ticket` smallint(11) unsigned NOT NULL, + `action` varchar(20) NOT NULL DEFAULT 'Message', + `message` text NOT NULL, + `timestamp` datetime NOT NULL, + `recipient` varchar(32) DEFAULT NULL, + `sender` varchar(32) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +DELIMITER $$ +CREATE PROCEDURE `set_poll_deleted`( + IN `poll_id` INT +) +SQL SECURITY INVOKER +BEGIN +UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id; +UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id; +END +$$ +CREATE TRIGGER `role_timeTlogupdate` AFTER UPDATE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes); +END +$$ +CREATE TRIGGER `role_timeTloginsert` AFTER INSERT ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.ckey, NEW.job, NEW.minutes); +END +$$ +CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); +END +$$ +DELIMITER ; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/TGS3.json b/TGS3.json index 2a2b80cf958..39b75bd913c 100644 --- a/TGS3.json +++ b/TGS3.json @@ -1,22 +1,22 @@ -{ - "documentation": "/tg/station server 3 configuration file", - "changelog": { - "script": "tools/ss13_genchangelog.py", - "arguments": "html/changelog.html html/changelogs", - "pip_dependancies": [ - "PyYaml", - "beautifulsoup4" - ] - }, - "synchronize_paths": [ - "html/changelog.html", - "html/changelogs/*" - ], - "static_directories": [ - "config", - "data" - ], - "dlls": [ - "libmariadb.dll" - ] - } +{ + "documentation": "/tg/station server 3 configuration file", + "changelog": { + "script": "tools/ss13_genchangelog.py", + "arguments": "html/changelog.html html/changelogs", + "pip_dependancies": [ + "PyYaml", + "beautifulsoup4" + ] + }, + "synchronize_paths": [ + "html/changelog.html", + "html/changelogs/*" + ], + "static_directories": [ + "config", + "data" + ], + "dlls": [ + "libmariadb.dll" + ] + } diff --git a/_maps/multiz_debug.json b/_maps/multiz_debug.json index a1c35541087..e916a77d1dc 100644 --- a/_maps/multiz_debug.json +++ b/_maps/multiz_debug.json @@ -1,6 +1,6 @@ -{ - "map_name": "MultiZ Debug", - "map_path": "map_files/debug", - "map_file": "multiz.dmm", - "traits": [{"Up" : 1, "Linkage" : "Cross"}, {"Up" : 1, "Down" : -1, "Baseturf" : "/turf/open/transparent/openspace", "Linkage" : "Cross"}, {"Down" : -1, "Baseturf" : "/turf/open/transparent/openspace", "Linkage" : "Cross"}] - } +{ + "map_name": "MultiZ Debug", + "map_path": "map_files/debug", + "map_file": "multiz.dmm", + "traits": [{"Up" : 1, "Linkage" : "Cross"}, {"Up" : 1, "Down" : -1, "Baseturf" : "/turf/open/transparent/openspace", "Linkage" : "Cross"}, {"Down" : -1, "Baseturf" : "/turf/open/transparent/openspace", "Linkage" : "Cross"}] + } diff --git a/_maps/runtimestation.json b/_maps/runtimestation.json index f9333c65a23..ca2cc3d3790 100644 --- a/_maps/runtimestation.json +++ b/_maps/runtimestation.json @@ -1,8 +1,8 @@ -{ - "map_name": "Runtime Station", - "map_path": "map_files/debug", - "map_file": "runtimestation.dmm", - "shuttles": { - "cargo": "cargo_delta" - } -} +{ + "map_name": "Runtime Station", + "map_path": "map_files/debug", + "map_file": "runtimestation.dmm", + "shuttles": { + "cargo": "cargo_delta" + } +} diff --git a/_maps/templates/shelter_3.dmm b/_maps/templates/shelter_3.dmm index 290d38ab24d..30c427ce3b2 100644 --- a/_maps/templates/shelter_3.dmm +++ b/_maps/templates/shelter_3.dmm @@ -1,425 +1,425 @@ -//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE -"a" = ( -/turf/closed/wall/mineral/titanium/survival/pod, -/area/survivalpod) -"b" = ( -/obj/structure/sign/mining/survival{ - dir = 1 - }, -/turf/closed/wall/mineral/titanium/survival/pod, -/area/survivalpod) -"c" = ( -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"d" = ( -/obj/structure/sign/mining/survival{ - dir = 1 - }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"e" = ( -/obj/structure/sign/mining/survival{ - dir = 8 - }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"f" = ( -/obj/structure/table/wood/fancy/black, -/obj/machinery/chem_dispenser/drinks, -/turf/open/floor/pod/dark, -/area/survivalpod) -"g" = ( -/obj/structure/table/wood/fancy/black, -/obj/machinery/chem_dispenser/drinks/beer, -/obj/machinery/light{ - dir = 1 - }, -/turf/open/floor/pod/dark, -/area/survivalpod) -"h" = ( -/obj/machinery/vending/boozeomat, -/turf/open/floor/pod/dark, -/area/survivalpod) -"i" = ( -/obj/item/book/manual/wiki/barman_recipes, -/obj/item/reagent_containers/food/drinks/shaker, -/obj/item/reagent_containers/glass/rag, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/pod/dark, -/area/survivalpod) -"j" = ( -/obj/structure/table/wood/fancy/black, -/obj/item/clipboard, -/obj/item/toy/figure/bartender, -/turf/open/floor/pod/dark, -/area/survivalpod) -"k" = ( -/obj/structure/table/wood/fancy/black, -/obj/item/storage/fancy/cigarettes/cigars, -/obj/item/storage/fancy/cigarettes/cigars/cohiba{ - pixel_y = 4 - }, -/obj/item/storage/fancy/cigarettes/cigars/havana{ - pixel_y = 8 - }, -/turf/open/floor/pod/dark, -/area/survivalpod) -"l" = ( -/obj/structure/table/wood/fancy/black, -/obj/structure/reagent_dispensers/beerkeg, -/turf/open/floor/pod/dark, -/area/survivalpod) -"m" = ( -/obj/structure/closet/secure_closet/bar{ - req_access = 0; - req_access_txt = 0; - req_one_access = 0; - req_one_access_txt = "25;48" - }, -/obj/machinery/light{ - dir = 1 - }, -/turf/open/floor/pod/dark, -/area/survivalpod) -"n" = ( -/obj/structure/disposalpipe/trunk{ - dir = 4 - }, -/obj/machinery/disposal/bin, -/turf/open/floor/pod/dark, -/area/survivalpod) -"o" = ( -/obj/structure/sign/mining/survival{ - dir = 4 - }, -/obj/structure/disposalpipe/junction{ - dir = 4 - }, -/turf/closed/wall/mineral/titanium/survival/pod, -/area/survivalpod) -"p" = ( -/obj/machinery/door/airlock/survival_pod/glass{ - req_access = 0; - req_access_txt = 0; - req_one_access = 0; - req_one_access_txt = "25;48" - }, -/obj/structure/fans/tiny, -/turf/open/floor/pod/dark, -/area/survivalpod) -"q" = ( -/turf/open/floor/pod/dark, -/area/survivalpod) -"r" = ( -/obj/structure/disposalpipe/segment, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"s" = ( -/obj/structure/table/reinforced, -/obj/item/lighter{ - pixel_x = -4; - pixel_y = 4 - }, -/obj/item/lighter, -/turf/open/floor/pod/dark, -/area/survivalpod) -"t" = ( -/obj/structure/table/reinforced, -/turf/open/floor/pod/dark, -/area/survivalpod) -"u" = ( -/obj/structure/table/reinforced, -/obj/item/storage/box/matches{ - pixel_x = -4; - pixel_y = 8 - }, -/turf/open/floor/pod/dark, -/area/survivalpod) -"v" = ( -/obj/machinery/door/window/survival_pod{ - req_access = 0; - req_access_txt = 0; - req_one_access = 0; - req_one_access_txt = "25;48" - }, -/turf/open/floor/pod/dark, -/area/survivalpod) -"w" = ( -/obj/structure/sign/mining/survival{ - dir = 4 - }, -/obj/structure/disposalpipe/segment, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"x" = ( -/obj/structure/chair/stool/bar, -/turf/open/floor/carpet/black, -/area/survivalpod) -"y" = ( -/turf/open/floor/carpet/black, -/area/survivalpod) -"z" = ( -/obj/machinery/vending/cigarette/beach, -/turf/open/floor/carpet/black, -/area/survivalpod) -"A" = ( -/obj/structure/disposalpipe/trunk{ - dir = 4 - }, -/obj/machinery/disposal/bin, -/turf/open/floor/carpet/black, -/area/survivalpod) -"B" = ( -/obj/structure/sign/mining/survival{ - dir = 4 - }, -/obj/structure/disposalpipe/segment{ - dir = 9 - }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"C" = ( -/obj/structure/window/reinforced/survival_pod{ - dir = 8 - }, -/obj/structure/window/reinforced/survival_pod{ - dir = 4 - }, -/obj/structure/window/reinforced/survival_pod{ - dir = 1 - }, -/obj/structure/window/reinforced/survival_pod, -/obj/structure/grille, -/turf/open/floor/pod/dark, -/area/survivalpod) -"D" = ( -/obj/structure/chair/comfy/black, -/turf/open/floor/carpet/black, -/area/survivalpod) -"E" = ( -/obj/machinery/door/airlock/survival_pod, -/turf/open/floor/pod/light, -/area/survivalpod) -"F" = ( -/obj/structure/table/wood/fancy, -/obj/item/reagent_containers/food/condiment/peppermill{ - pixel_x = -4; - pixel_y = 12 - }, -/obj/item/reagent_containers/food/condiment/saltshaker{ - pixel_x = 4; - pixel_y = 4 - }, -/turf/open/floor/carpet/black, -/area/survivalpod) -"G" = ( -/obj/structure/urinal{ - pixel_y = 24 - }, -/turf/open/floor/pod/light, -/area/survivalpod) -"H" = ( -/turf/open/floor/pod/light, -/area/survivalpod) -"I" = ( -/obj/structure/sink{ - dir = 8; - pixel_x = 11 - }, -/obj/machinery/light/small{ - dir = 4 - }, -/turf/open/floor/pod/light, -/area/survivalpod) -"J" = ( -/obj/structure/sign/mining/survival{ - dir = 4 - }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"K" = ( -/obj/structure/chair/comfy/black{ - dir = 1 - }, -/turf/open/floor/carpet/black, -/area/survivalpod) -"L" = ( -/obj/machinery/vending/snack/random, -/turf/open/floor/carpet/black, -/area/survivalpod) -"M" = ( -/obj/machinery/light, -/turf/open/floor/carpet/black, -/area/survivalpod) -"N" = ( -/obj/structure/toilet{ - dir = 8 - }, -/obj/machinery/light/small{ - dir = 4 - }, -/turf/open/floor/pod/light, -/area/survivalpod) -"O" = ( -/obj/structure/sign/mining/survival{ - dir = 4 - }, -/turf/closed/wall/mineral/titanium/survival/pod, -/area/survivalpod) -"P" = ( -/obj/structure/sign/mining/survival, -/turf/closed/wall/mineral/titanium/survival/pod, -/area/survivalpod) -"Q" = ( -/obj/structure/sign/mining/survival, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, -/area/survivalpod) -"R" = ( -/obj/machinery/door/airlock/survival_pod/glass, -/obj/structure/fans/tiny, -/turf/open/floor/carpet/black, -/area/survivalpod) - -(1,1,1) = {" -a -e -p -e -c -e -C -e -C -e -a -"} -(2,1,1) = {" -b -f -q -s -x -y -D -F -K -L -P -"} -(3,1,1) = {" -c -g -q -t -x -y -y -y -y -M -c -"} -(4,1,1) = {" -d -h -q -t -x -y -D -F -K -y -Q -"} -(5,1,1) = {" -c -i -q -u -x -y -D -F -K -y -c -"} -(6,1,1) = {" -d -j -q -t -x -y -y -y -y -y -R -"} -(7,1,1) = {" -c -k -q -t -x -y -c -c -c -c -c -"} -(8,1,1) = {" -d -l -q -t -x -y -c -G -H -H -Q -"} -(9,1,1) = {" -c -m -q -v -y -y -E -H -c -E -c -"} -(10,1,1) = {" -b -n -q -t -z -A -c -I -c -N -P -"} -(11,1,1) = {" -a -o -r -w -r -B -c -J -c -O -a -"} +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/wall/mineral/titanium/survival/pod, +/area/survivalpod) +"b" = ( +/obj/structure/sign/mining/survival{ + dir = 1 + }, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/survivalpod) +"c" = ( +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"d" = ( +/obj/structure/sign/mining/survival{ + dir = 1 + }, +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"e" = ( +/obj/structure/sign/mining/survival{ + dir = 8 + }, +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"f" = ( +/obj/structure/table/wood/fancy/black, +/obj/machinery/chem_dispenser/drinks, +/turf/open/floor/pod/dark, +/area/survivalpod) +"g" = ( +/obj/structure/table/wood/fancy/black, +/obj/machinery/chem_dispenser/drinks/beer, +/obj/machinery/light{ + dir = 1 + }, +/turf/open/floor/pod/dark, +/area/survivalpod) +"h" = ( +/obj/machinery/vending/boozeomat, +/turf/open/floor/pod/dark, +/area/survivalpod) +"i" = ( +/obj/item/book/manual/wiki/barman_recipes, +/obj/item/reagent_containers/food/drinks/shaker, +/obj/item/reagent_containers/glass/rag, +/obj/structure/table/wood/fancy/black, +/turf/open/floor/pod/dark, +/area/survivalpod) +"j" = ( +/obj/structure/table/wood/fancy/black, +/obj/item/clipboard, +/obj/item/toy/figure/bartender, +/turf/open/floor/pod/dark, +/area/survivalpod) +"k" = ( +/obj/structure/table/wood/fancy/black, +/obj/item/storage/fancy/cigarettes/cigars, +/obj/item/storage/fancy/cigarettes/cigars/cohiba{ + pixel_y = 4 + }, +/obj/item/storage/fancy/cigarettes/cigars/havana{ + pixel_y = 8 + }, +/turf/open/floor/pod/dark, +/area/survivalpod) +"l" = ( +/obj/structure/table/wood/fancy/black, +/obj/structure/reagent_dispensers/beerkeg, +/turf/open/floor/pod/dark, +/area/survivalpod) +"m" = ( +/obj/structure/closet/secure_closet/bar{ + req_access = 0; + req_access_txt = 0; + req_one_access = 0; + req_one_access_txt = "25;48" + }, +/obj/machinery/light{ + dir = 1 + }, +/turf/open/floor/pod/dark, +/area/survivalpod) +"n" = ( +/obj/structure/disposalpipe/trunk{ + dir = 4 + }, +/obj/machinery/disposal/bin, +/turf/open/floor/pod/dark, +/area/survivalpod) +"o" = ( +/obj/structure/sign/mining/survival{ + dir = 4 + }, +/obj/structure/disposalpipe/junction{ + dir = 4 + }, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/survivalpod) +"p" = ( +/obj/machinery/door/airlock/survival_pod/glass{ + req_access = 0; + req_access_txt = 0; + req_one_access = 0; + req_one_access_txt = "25;48" + }, +/obj/structure/fans/tiny, +/turf/open/floor/pod/dark, +/area/survivalpod) +"q" = ( +/turf/open/floor/pod/dark, +/area/survivalpod) +"r" = ( +/obj/structure/disposalpipe/segment, +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"s" = ( +/obj/structure/table/reinforced, +/obj/item/lighter{ + pixel_x = -4; + pixel_y = 4 + }, +/obj/item/lighter, +/turf/open/floor/pod/dark, +/area/survivalpod) +"t" = ( +/obj/structure/table/reinforced, +/turf/open/floor/pod/dark, +/area/survivalpod) +"u" = ( +/obj/structure/table/reinforced, +/obj/item/storage/box/matches{ + pixel_x = -4; + pixel_y = 8 + }, +/turf/open/floor/pod/dark, +/area/survivalpod) +"v" = ( +/obj/machinery/door/window/survival_pod{ + req_access = 0; + req_access_txt = 0; + req_one_access = 0; + req_one_access_txt = "25;48" + }, +/turf/open/floor/pod/dark, +/area/survivalpod) +"w" = ( +/obj/structure/sign/mining/survival{ + dir = 4 + }, +/obj/structure/disposalpipe/segment, +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"x" = ( +/obj/structure/chair/stool/bar, +/turf/open/floor/carpet/black, +/area/survivalpod) +"y" = ( +/turf/open/floor/carpet/black, +/area/survivalpod) +"z" = ( +/obj/machinery/vending/cigarette/beach, +/turf/open/floor/carpet/black, +/area/survivalpod) +"A" = ( +/obj/structure/disposalpipe/trunk{ + dir = 4 + }, +/obj/machinery/disposal/bin, +/turf/open/floor/carpet/black, +/area/survivalpod) +"B" = ( +/obj/structure/sign/mining/survival{ + dir = 4 + }, +/obj/structure/disposalpipe/segment{ + dir = 9 + }, +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"C" = ( +/obj/structure/window/reinforced/survival_pod{ + dir = 8 + }, +/obj/structure/window/reinforced/survival_pod{ + dir = 4 + }, +/obj/structure/window/reinforced/survival_pod{ + dir = 1 + }, +/obj/structure/window/reinforced/survival_pod, +/obj/structure/grille, +/turf/open/floor/pod/dark, +/area/survivalpod) +"D" = ( +/obj/structure/chair/comfy/black, +/turf/open/floor/carpet/black, +/area/survivalpod) +"E" = ( +/obj/machinery/door/airlock/survival_pod, +/turf/open/floor/pod/light, +/area/survivalpod) +"F" = ( +/obj/structure/table/wood/fancy, +/obj/item/reagent_containers/food/condiment/peppermill{ + pixel_x = -4; + pixel_y = 12 + }, +/obj/item/reagent_containers/food/condiment/saltshaker{ + pixel_x = 4; + pixel_y = 4 + }, +/turf/open/floor/carpet/black, +/area/survivalpod) +"G" = ( +/obj/structure/urinal{ + pixel_y = 24 + }, +/turf/open/floor/pod/light, +/area/survivalpod) +"H" = ( +/turf/open/floor/pod/light, +/area/survivalpod) +"I" = ( +/obj/structure/sink{ + dir = 8; + pixel_x = 11 + }, +/obj/machinery/light/small{ + dir = 4 + }, +/turf/open/floor/pod/light, +/area/survivalpod) +"J" = ( +/obj/structure/sign/mining/survival{ + dir = 4 + }, +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"K" = ( +/obj/structure/chair/comfy/black{ + dir = 1 + }, +/turf/open/floor/carpet/black, +/area/survivalpod) +"L" = ( +/obj/machinery/vending/snack/random, +/turf/open/floor/carpet/black, +/area/survivalpod) +"M" = ( +/obj/machinery/light, +/turf/open/floor/carpet/black, +/area/survivalpod) +"N" = ( +/obj/structure/toilet{ + dir = 8 + }, +/obj/machinery/light/small{ + dir = 4 + }, +/turf/open/floor/pod/light, +/area/survivalpod) +"O" = ( +/obj/structure/sign/mining/survival{ + dir = 4 + }, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/survivalpod) +"P" = ( +/obj/structure/sign/mining/survival, +/turf/closed/wall/mineral/titanium/survival/pod, +/area/survivalpod) +"Q" = ( +/obj/structure/sign/mining/survival, +/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/area/survivalpod) +"R" = ( +/obj/machinery/door/airlock/survival_pod/glass, +/obj/structure/fans/tiny, +/turf/open/floor/carpet/black, +/area/survivalpod) + +(1,1,1) = {" +a +e +p +e +c +e +C +e +C +e +a +"} +(2,1,1) = {" +b +f +q +s +x +y +D +F +K +L +P +"} +(3,1,1) = {" +c +g +q +t +x +y +y +y +y +M +c +"} +(4,1,1) = {" +d +h +q +t +x +y +D +F +K +y +Q +"} +(5,1,1) = {" +c +i +q +u +x +y +D +F +K +y +c +"} +(6,1,1) = {" +d +j +q +t +x +y +y +y +y +y +R +"} +(7,1,1) = {" +c +k +q +t +x +y +c +c +c +c +c +"} +(8,1,1) = {" +d +l +q +t +x +y +c +G +H +H +Q +"} +(9,1,1) = {" +c +m +q +v +y +y +E +H +c +E +c +"} +(10,1,1) = {" +b +n +q +t +z +A +c +I +c +N +P +"} +(11,1,1) = {" +a +o +r +w +r +B +c +J +c +O +a +"} diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm index fba061701dc..22ca001ae4e 100644 --- a/code/__DEFINES/_helpers.dm +++ b/code/__DEFINES/_helpers.dm @@ -1,9 +1,9 @@ -// Stuff that is relatively "core" and is used in other defines/helpers - -//Returns the hex value of a decimal number -//len == length of returned string -#define num2hex(X, len) num2text(X, len, 16) - -//Returns an integer given a hex input, supports negative values "-ff" -//skips preceding invalid characters -#define hex2num(X) text2num(X, 16) +// Stuff that is relatively "core" and is used in other defines/helpers + +//Returns the hex value of a decimal number +//len == length of returned string +#define num2hex(X, len) num2text(X, len, 16) + +//Returns an integer given a hex input, supports negative values "-ff" +//skips preceding invalid characters +#define hex2num(X) text2num(X, 16) diff --git a/code/__DEFINES/_protect.dm b/code/__DEFINES/_protect.dm index 27baee2f882..8e162a8b336 100644 --- a/code/__DEFINES/_protect.dm +++ b/code/__DEFINES/_protect.dm @@ -1,11 +1,11 @@ -///Protects a datum from being VV'd -#define GENERAL_PROTECT_DATUM(Path)\ -##Path/can_vv_get(var_name){\ - return FALSE;\ -}\ -##Path/vv_edit_var(var_name, var_value){\ - return FALSE;\ -}\ -##Path/CanProcCall(procname){\ - return FALSE;\ -} +///Protects a datum from being VV'd +#define GENERAL_PROTECT_DATUM(Path)\ +##Path/can_vv_get(var_name){\ + return FALSE;\ +}\ +##Path/vv_edit_var(var_name, var_value){\ + return FALSE;\ +}\ +##Path/CanProcCall(procname){\ + return FALSE;\ +} diff --git a/code/__DEFINES/_readme.dm b/code/__DEFINES/_readme.dm index 1b78d44a10d..8bf6ada6477 100644 --- a/code/__DEFINES/_readme.dm +++ b/code/__DEFINES/_readme.dm @@ -1,14 +1,14 @@ -/* - This folder is full of #define statements. They are similar to constants, - but must come before any code that references them, and they do not take up - memory the way constants do. - - The values in this folder are NOT options. They are not for hosts to play with. - Some of the values are arbitrary and only need to be different from similar constants; - for example, the genetic mutation numbers in genetics.dm mean nothing, but MUST be distinct. - - It is wise not to touch them unless you understand what they do, where they're used, - and most importantly, - how to undo your changes if you screw it up. - - Sayu -*/ +/* + This folder is full of #define statements. They are similar to constants, + but must come before any code that references them, and they do not take up + memory the way constants do. + + The values in this folder are NOT options. They are not for hosts to play with. + Some of the values are arbitrary and only need to be different from similar constants; + for example, the genetic mutation numbers in genetics.dm mean nothing, but MUST be distinct. + + It is wise not to touch them unless you understand what they do, where they're used, + and most importantly, + how to undo your changes if you screw it up. + - Sayu +*/ diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index 552001c60ca..a0e8a1f9420 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -1,143 +1,143 @@ -//A set of constants used to determine which type of mute an admin wishes to apply: -//Please read and understand the muting/automuting stuff before changing these. MUTE_IC_AUTO etc = (MUTE_IC << 1) -//Therefore there needs to be a gap between the flags for the automute flags -#define MUTE_IC (1<<0) -#define MUTE_OOC (1<<1) -#define MUTE_PRAY (1<<2) -#define MUTE_ADMINHELP (1<<3) -#define MUTE_DEADCHAT (1<<4) -#define MUTE_ALL (~0) - -//Some constants for DB_Ban -#define BANTYPE_PERMA 1 -#define BANTYPE_TEMP 2 -#define BANTYPE_JOB_PERMA 3 -#define BANTYPE_JOB_TEMP 4 -/// used to locate stuff to unban. -#define BANTYPE_ANY_FULLBAN 5 - -#define BANTYPE_ADMIN_PERMA 7 -#define BANTYPE_ADMIN_TEMP 8 -/// used to remove jobbans -#define BANTYPE_ANY_JOB 9 - -//Admin Permissions -#define R_BUILD (1<<0) -#define R_ADMIN (1<<1) -#define R_BAN (1<<2) -#define R_FUN (1<<3) -#define R_SERVER (1<<4) -#define R_DEBUG (1<<5) -#define R_POSSESS (1<<6) -#define R_PERMISSIONS (1<<7) -#define R_STEALTH (1<<8) -#define R_POLL (1<<9) -#define R_VAREDIT (1<<10) -#define R_SOUND (1<<11) -#define R_SPAWN (1<<12) -#define R_AUTOADMIN (1<<13) -#define R_DBRANKS (1<<14) - -#define R_DEFAULT R_AUTOADMIN - -#define R_EVERYTHING (1<<15)-1 //the sum of all other rank permissions, used for +EVERYTHING - -#define ADMIN_QUE(user) "(?)" -#define ADMIN_FLW(user) "(FLW)" -#define ADMIN_PP(user) "(PP)" -#define ADMIN_VV(atom) "(VV)" -#define ADMIN_SM(user) "(SM)" -#define ADMIN_TP(user) "(TP)" -#define ADMIN_SP(user) "(SP)" -#define ADMIN_KICK(user) "(KICK)" -#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" -#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" -#define ADMIN_SC(user) "(SC)" -#define ADMIN_SMITE(user) "(SMITE)" -#define ADMIN_LOOKUP(user) "[key_name_admin(user)][ADMIN_QUE(user)]" -#define ADMIN_LOOKUPFLW(user) "[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]" -#define ADMIN_SET_SD_CODE "(SETCODE)" -#define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)] [ADMIN_SMITE(user)]" -#define ADMIN_FULLMONTY(user) "[key_name_admin(user)] [ADMIN_FULLMONTY_NONAME(user)]" -#define ADMIN_JMP(src) "(JMP)" -#define COORD(src) "[src ? src.Admin_Coordinates_Readable() : "nonexistent location"]" -#define AREACOORD(src) "[src ? src.Admin_Coordinates_Readable(TRUE) : "nonexistent location"]" -#define ADMIN_COORDJMP(src) "[src ? src.Admin_Coordinates_Readable(FALSE, TRUE) : "nonexistent location"]" -#define ADMIN_VERBOSEJMP(src) "[src ? src.Admin_Coordinates_Readable(TRUE, TRUE) : "nonexistent location"]" -#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" - -/atom/proc/Admin_Coordinates_Readable(area_name, admin_jump_ref) - var/turf/T = Safe_COORD_Location() - return T ? "[area_name ? "[get_area_name(T, TRUE)] " : " "]([T.x],[T.y],[T.z])[admin_jump_ref ? " [ADMIN_JMP(T)]" : ""]" : "nonexistent location" - -/atom/proc/Safe_COORD_Location() - var/atom/A = drop_location() - if(!A) - return //not a valid atom. - var/turf/T = get_step(A, 0) //resolve where the thing is. - if(!T) //incase it's inside a valid drop container, inside another container. ie if a mech picked up a closet and has it inside it's internal storage. - var/atom/last_try = A.loc?.drop_location() //one last try, otherwise fuck it. - if(last_try) - T = get_step(last_try, 0) - return T - -/turf/Safe_COORD_Location() - return src - - -#define ADMIN_PUNISHMENT_LIGHTNING "Lightning bolt" -#define ADMIN_PUNISHMENT_BRAINDAMAGE "Brain damage" -#define ADMIN_PUNISHMENT_GIB "Gib" -#define ADMIN_PUNISHMENT_BSA "Bluespace Artillery Device" -#define ADMIN_PUNISHMENT_FIREBALL "Fireball" -#define ADMIN_PUNISHMENT_ROD "Immovable Rod" -#define ADMIN_PUNISHMENT_SUPPLYPOD_QUICK "Supply Pod (Quick)" -#define ADMIN_PUNISHMENT_SUPPLYPOD "Supply Pod" -#define ADMIN_PUNISHMENT_MAZING "Puzzle" -#define ADMIN_PUNISHMENT_IMMERSE "Fully Immerse" -#define ADMIN_PUNISHMENT_FAT "Fatten up" -#define ADMIN_PUNISHMENT_FAKEBWOINK "Fake bwoink" -#define ADMIN_PUNISHMENT_NUGGET "Nugget" -#define ADMIN_PUNISHMENT_CRACK ":B:oneless" -#define ADMIN_PUNISHMENT_BLEED ":B:loodless" -#define ADMIN_PUNISHMENT_SCARIFY "Scarify" -#define ADMIN_PUNISHMENT_SHOES "Knot Shoes" - -#define AHELP_ACTIVE 1 -#define AHELP_CLOSED 2 -#define AHELP_RESOLVED 3 - -/// Amount of time (in deciseconds) after the rounds starts, that the player disconnect report is issued. -#define ROUNDSTART_LOGOUT_REPORT_TIME 6000 - -/// Number of identical messages required before the spam-prevention will warn you to stfu -#define SPAM_TRIGGER_WARNING 5 -/// Number of identical messages required before the spam-prevention will automute you -#define SPAM_TRIGGER_AUTOMUTE 10 - -///Max length of a keypress command before it's considered to be a forged packet/bogus command -#define MAX_KEYPRESS_COMMANDLENGTH 16 -///Maximum keys that can be bound to one button -#define MAX_COMMANDS_PER_KEY 5 -///Maximum keys per keybind -#define MAX_KEYS_PER_KEYBIND 3 -///Max amount of keypress messages per second over two seconds before client is autokicked -#define MAX_KEYPRESS_AUTOKICK 50 -///Length of held key rolling buffer -#define HELD_KEY_BUFFER_LENGTH 15 - -#define STICKYBAN_DB_CACHE_TIME 10 SECONDS -#define STICKYBAN_ROGUE_CHECK_TIME 5 - - -/// Shown to vicitm of staff of change and related effects. -#define POLICY_POLYMORPH "polymorph" -/// Shown on top of policy verb window -#define POLICY_VERB_HEADER "policy_verb_header" - -//How many things you can spawn at once with spawn verb/create panel -#define ADMIN_SPAWN_CAP 100 - -// LOG BROWSE TYPES -#define BROWSE_ROOT_ALL_LOGS 1 -#define BROWSE_ROOT_CURRENT_LOGS 2 +//A set of constants used to determine which type of mute an admin wishes to apply: +//Please read and understand the muting/automuting stuff before changing these. MUTE_IC_AUTO etc = (MUTE_IC << 1) +//Therefore there needs to be a gap between the flags for the automute flags +#define MUTE_IC (1<<0) +#define MUTE_OOC (1<<1) +#define MUTE_PRAY (1<<2) +#define MUTE_ADMINHELP (1<<3) +#define MUTE_DEADCHAT (1<<4) +#define MUTE_ALL (~0) + +//Some constants for DB_Ban +#define BANTYPE_PERMA 1 +#define BANTYPE_TEMP 2 +#define BANTYPE_JOB_PERMA 3 +#define BANTYPE_JOB_TEMP 4 +/// used to locate stuff to unban. +#define BANTYPE_ANY_FULLBAN 5 + +#define BANTYPE_ADMIN_PERMA 7 +#define BANTYPE_ADMIN_TEMP 8 +/// used to remove jobbans +#define BANTYPE_ANY_JOB 9 + +//Admin Permissions +#define R_BUILD (1<<0) +#define R_ADMIN (1<<1) +#define R_BAN (1<<2) +#define R_FUN (1<<3) +#define R_SERVER (1<<4) +#define R_DEBUG (1<<5) +#define R_POSSESS (1<<6) +#define R_PERMISSIONS (1<<7) +#define R_STEALTH (1<<8) +#define R_POLL (1<<9) +#define R_VAREDIT (1<<10) +#define R_SOUND (1<<11) +#define R_SPAWN (1<<12) +#define R_AUTOADMIN (1<<13) +#define R_DBRANKS (1<<14) + +#define R_DEFAULT R_AUTOADMIN + +#define R_EVERYTHING (1<<15)-1 //the sum of all other rank permissions, used for +EVERYTHING + +#define ADMIN_QUE(user) "(?)" +#define ADMIN_FLW(user) "(FLW)" +#define ADMIN_PP(user) "(PP)" +#define ADMIN_VV(atom) "(VV)" +#define ADMIN_SM(user) "(SM)" +#define ADMIN_TP(user) "(TP)" +#define ADMIN_SP(user) "(SP)" +#define ADMIN_KICK(user) "(KICK)" +#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" +#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" +#define ADMIN_SC(user) "(SC)" +#define ADMIN_SMITE(user) "(SMITE)" +#define ADMIN_LOOKUP(user) "[key_name_admin(user)][ADMIN_QUE(user)]" +#define ADMIN_LOOKUPFLW(user) "[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]" +#define ADMIN_SET_SD_CODE "(SETCODE)" +#define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)] [ADMIN_SMITE(user)]" +#define ADMIN_FULLMONTY(user) "[key_name_admin(user)] [ADMIN_FULLMONTY_NONAME(user)]" +#define ADMIN_JMP(src) "(JMP)" +#define COORD(src) "[src ? src.Admin_Coordinates_Readable() : "nonexistent location"]" +#define AREACOORD(src) "[src ? src.Admin_Coordinates_Readable(TRUE) : "nonexistent location"]" +#define ADMIN_COORDJMP(src) "[src ? src.Admin_Coordinates_Readable(FALSE, TRUE) : "nonexistent location"]" +#define ADMIN_VERBOSEJMP(src) "[src ? src.Admin_Coordinates_Readable(TRUE, TRUE) : "nonexistent location"]" +#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" + +/atom/proc/Admin_Coordinates_Readable(area_name, admin_jump_ref) + var/turf/T = Safe_COORD_Location() + return T ? "[area_name ? "[get_area_name(T, TRUE)] " : " "]([T.x],[T.y],[T.z])[admin_jump_ref ? " [ADMIN_JMP(T)]" : ""]" : "nonexistent location" + +/atom/proc/Safe_COORD_Location() + var/atom/A = drop_location() + if(!A) + return //not a valid atom. + var/turf/T = get_step(A, 0) //resolve where the thing is. + if(!T) //incase it's inside a valid drop container, inside another container. ie if a mech picked up a closet and has it inside it's internal storage. + var/atom/last_try = A.loc?.drop_location() //one last try, otherwise fuck it. + if(last_try) + T = get_step(last_try, 0) + return T + +/turf/Safe_COORD_Location() + return src + + +#define ADMIN_PUNISHMENT_LIGHTNING "Lightning bolt" +#define ADMIN_PUNISHMENT_BRAINDAMAGE "Brain damage" +#define ADMIN_PUNISHMENT_GIB "Gib" +#define ADMIN_PUNISHMENT_BSA "Bluespace Artillery Device" +#define ADMIN_PUNISHMENT_FIREBALL "Fireball" +#define ADMIN_PUNISHMENT_ROD "Immovable Rod" +#define ADMIN_PUNISHMENT_SUPPLYPOD_QUICK "Supply Pod (Quick)" +#define ADMIN_PUNISHMENT_SUPPLYPOD "Supply Pod" +#define ADMIN_PUNISHMENT_MAZING "Puzzle" +#define ADMIN_PUNISHMENT_IMMERSE "Fully Immerse" +#define ADMIN_PUNISHMENT_FAT "Fatten up" +#define ADMIN_PUNISHMENT_FAKEBWOINK "Fake bwoink" +#define ADMIN_PUNISHMENT_NUGGET "Nugget" +#define ADMIN_PUNISHMENT_CRACK ":B:oneless" +#define ADMIN_PUNISHMENT_BLEED ":B:loodless" +#define ADMIN_PUNISHMENT_SCARIFY "Scarify" +#define ADMIN_PUNISHMENT_SHOES "Knot Shoes" + +#define AHELP_ACTIVE 1 +#define AHELP_CLOSED 2 +#define AHELP_RESOLVED 3 + +/// Amount of time (in deciseconds) after the rounds starts, that the player disconnect report is issued. +#define ROUNDSTART_LOGOUT_REPORT_TIME 6000 + +/// Number of identical messages required before the spam-prevention will warn you to stfu +#define SPAM_TRIGGER_WARNING 5 +/// Number of identical messages required before the spam-prevention will automute you +#define SPAM_TRIGGER_AUTOMUTE 10 + +///Max length of a keypress command before it's considered to be a forged packet/bogus command +#define MAX_KEYPRESS_COMMANDLENGTH 16 +///Maximum keys that can be bound to one button +#define MAX_COMMANDS_PER_KEY 5 +///Maximum keys per keybind +#define MAX_KEYS_PER_KEYBIND 3 +///Max amount of keypress messages per second over two seconds before client is autokicked +#define MAX_KEYPRESS_AUTOKICK 50 +///Length of held key rolling buffer +#define HELD_KEY_BUFFER_LENGTH 15 + +#define STICKYBAN_DB_CACHE_TIME 10 SECONDS +#define STICKYBAN_ROGUE_CHECK_TIME 5 + + +/// Shown to vicitm of staff of change and related effects. +#define POLICY_POLYMORPH "polymorph" +/// Shown on top of policy verb window +#define POLICY_VERB_HEADER "policy_verb_header" + +//How many things you can spawn at once with spawn verb/create panel +#define ADMIN_SPAWN_CAP 100 + +// LOG BROWSE TYPES +#define BROWSE_ROOT_ALL_LOGS 1 +#define BROWSE_ROOT_CURRENT_LOGS 2 diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 697cdf1bfe7..d5c776954ac 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -1,416 +1,416 @@ -//LISTMOS -//indices of values in gas lists. -#define MOLES 1 -#define ARCHIVE 2 -#define GAS_META 3 -#define META_GAS_SPECIFIC_HEAT 1 -#define META_GAS_NAME 2 -#define META_GAS_MOLES_VISIBLE 3 -#define META_GAS_OVERLAY 4 -#define META_GAS_DANGER 5 -#define META_GAS_ID 6 -#define META_GAS_FUSION_POWER 7 -//ATMOS -//stuff you should probably leave well alone! -/// kPa*L/(K*mol) -#define R_IDEAL_GAS_EQUATION 8.31 -/// kPa -#define ONE_ATMOSPHERE 101.325 -/// -270.3degC -#define TCMB 2.7 -/// -48.15degC -#define TCRYO 225 -/// 0degC -#define T0C 273.15 -/// 20degC -#define T20C 293.15 - -///moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC -#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) -///compared against for superconductivity -#define M_CELL_WITH_RATIO (MOLES_CELLSTANDARD * 0.005) -/// percentage of oxygen in a normal mixture of air -#define O2STANDARD 0.21 -/// same but for nitrogen -#define N2STANDARD 0.79 -/// O2 standard value (21%) -#define MOLES_O2STANDARD (MOLES_CELLSTANDARD*O2STANDARD) -/// N2 standard value (79%) -#define MOLES_N2STANDARD (MOLES_CELLSTANDARD*N2STANDARD) -/// liters in a cell -#define CELL_VOLUME 2500 - -/// liters in a normal breath -#define BREATH_VOLUME 0.5 -/// Amount of air to take a from a tile -#define BREATH_PERCENTAGE (BREATH_VOLUME/CELL_VOLUME) - - -//EXCITED GROUPS -/// number of FULL air controller ticks before an excited group breaks down (averages gas contents across turfs) -#define EXCITED_GROUP_BREAKDOWN_CYCLES 4 -/// number of FULL air controller ticks before an excited group dismantles and removes its turfs from active -#define EXCITED_GROUP_DISMANTLE_CYCLES 16 -/// Ratio of air that must move to/from a tile to reset group processing -#define MINIMUM_AIR_RATIO_TO_SUSPEND 0.1 -/// Minimum ratio of air that must move to/from a tile -#define MINIMUM_AIR_RATIO_TO_MOVE 0.001 -/// Minimum amount of air that has to move before a group processing can be suspended -#define MINIMUM_AIR_TO_SUSPEND (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_SUSPEND) -/// Either this must be active -#define MINIMUM_MOLES_DELTA_TO_MOVE (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_MOVE) -/// or this (or both, obviously) -#define MINIMUM_TEMPERATURE_TO_MOVE (T20C+100) -/// Minimum temperature difference before group processing is suspended -#define MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND 4 -/// Minimum temperature difference before the gas temperatures are just set to be equal -#define MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER 0.5 -#define MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION (T20C+10) -#define MINIMUM_TEMPERATURE_START_SUPERCONDUCTION (T20C+200) - -//HEAT TRANSFER COEFFICIENTS -//Must be between 0 and 1. Values closer to 1 equalize temperature faster -//Should not exceed 0.4 else strange heat flow occur -#define WALL_HEAT_TRANSFER_COEFFICIENT 0.0 -#define OPEN_HEAT_TRANSFER_COEFFICIENT 0.4 -/// a hack for now -#define WINDOW_HEAT_TRANSFER_COEFFICIENT 0.1 -/// a hack to help make vacuums "cold", sacrificing realism for gameplay -#define HEAT_CAPACITY_VACUUM 7000 - -//FIRE -#define FIRE_MINIMUM_TEMPERATURE_TO_SPREAD (150+T0C) -#define FIRE_MINIMUM_TEMPERATURE_TO_EXIST (100+T0C) -#define FIRE_SPREAD_RADIOSITY_SCALE 0.85 -#define FIRE_GROWTH_RATE 40000 //For small fires -#define PLASMA_MINIMUM_BURN_TEMPERATURE (100+T0C) -#define PLASMA_UPPER_TEMPERATURE (1370+T0C) -#define PLASMA_OXYGEN_FULLBURN 10 -#define HYDROGEN_MINIMUM_BURN_TEMPERATURE (100+T0C) -#define HYDROGEN_UPPER_TEMPERATURE (1370+T0C) -#define HYDROGEN_OXYGEN_FULLBURN 10 - -//COLD FIRE (this is used only for the freon-o2 reaction, there is no fire still) -#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_SPREAD 263 //fire will spread if the temperature is -10 °C -#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_EXIST 273 //fire will start if the temperature is 0 °C -#define COLD_FIRE_SPREAD_RADIOSITY_SCALE 0.95 -#define COLD_FIRE_GROWTH_RATE 40000 -#define FREON_MAXIMUM_BURN_TEMPERATURE 293 -#define FREON_LOWER_TEMPERATURE 60 //minimum temperature allowed for the burn to go, we would have negative pressure otherwise -#define FREON_OXYGEN_FULLBURN 10 - -//GASES -#define MIN_TOXIC_GAS_DAMAGE 1 -#define MAX_TOXIC_GAS_DAMAGE 10 -/// Moles in a standard cell after which gases are visible -#define MOLES_GAS_VISIBLE 0.25 - -/// moles_visible * FACTOR_GAS_VISIBLE_MAX = Moles after which gas is at maximum visibility -#define FACTOR_GAS_VISIBLE_MAX 20 -/// Mole step for alpha updates. This means alpha can update at 0.25, 0.5, 0.75 and so on -#define MOLES_GAS_VISIBLE_STEP 0.25 - -//REACTIONS -//return values for reactions (bitflags) -#define NO_REACTION 0 -#define REACTING 1 -#define STOP_REACTIONS 2 - -// Pressure limits. -/// This determins at what pressure the ultra-high pressure red icon is displayed. (This one is set as a constant) -#define HAZARD_HIGH_PRESSURE 550 -/// This determins when the orange pressure icon is displayed (it is 0.7 * HAZARD_HIGH_PRESSURE) -#define WARNING_HIGH_PRESSURE 325 -/// This is when the gray low pressure icon is displayed. (it is 2.5 * HAZARD_LOW_PRESSURE) -#define WARNING_LOW_PRESSURE 50 -/// This is when the black ultra-low pressure icon is displayed. (This one is set as a constant) -#define HAZARD_LOW_PRESSURE 20 - -/// This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount. -#define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 - -/// The natural temperature for a body -#define BODYTEMP_NORMAL 310.15 -/// This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive. -#define BODYTEMP_AUTORECOVERY_DIVISOR 11 -/// Minimum amount of kelvin moved toward 310K per tick. So long as abs(310.15 - bodytemp) is more than 50. -#define BODYTEMP_AUTORECOVERY_MINIMUM 12 -///Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is lower than their body temperature. Make it lower to lose bodytemp faster. -#define BODYTEMP_COLD_DIVISOR 15 -/// Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is higher than their body temperature. Make it lower to gain bodytemp faster. -#define BODYTEMP_HEAT_DIVISOR 15 -/// The maximum number of degrees that your body can cool in 1 tick, due to the environment, when in a cold area. -#define BODYTEMP_COOLING_MAX -100 -/// The maximum number of degrees that your body can heat up in 1 tick, due to the environment, when in a hot area. -#define BODYTEMP_HEATING_MAX 30 -/// The body temperature limit the human body can take before it starts taking damage from heat. -/// This also affects how fast the body normalises it's temperature when hot. -/// 340k is about 66c, and rather high for a human. -#define BODYTEMP_HEAT_DAMAGE_LIMIT (BODYTEMP_NORMAL + 30) -/// The body temperature limit the human body can take before it starts taking damage from cold. -/// This also affects how fast the body normalises it's temperature when cold. -/// 270k is about -3c, that is below freezing and would hurt over time. -#define BODYTEMP_COLD_DAMAGE_LIMIT (BODYTEMP_NORMAL - 40) - -/// what min_cold_protection_temperature is set to for space-helmet quality headwear. MUST NOT BE 0. -#define SPACE_HELM_MIN_TEMP_PROTECT 2.0 -/// Thermal insulation works both ways /Malkevin -#define SPACE_HELM_MAX_TEMP_PROTECT 1500 -/// what min_cold_protection_temperature is set to for space-suit quality jumpsuits or suits. MUST NOT BE 0. -#define SPACE_SUIT_MIN_TEMP_PROTECT 2.0 -/// The min cold protection of a space suit without the heater active -#define SPACE_SUIT_MIN_TEMP_PROTECT_OFF 72 -#define SPACE_SUIT_MAX_TEMP_PROTECT 1500 - -/// Cold protection for firesuits -#define FIRE_SUIT_MIN_TEMP_PROTECT 60 -/// what max_heat_protection_temperature is set to for firesuit quality suits. MUST NOT BE 0. -#define FIRE_SUIT_MAX_TEMP_PROTECT 30000 -/// Cold protection for fire helmets -#define FIRE_HELM_MIN_TEMP_PROTECT 60 -/// for fire helmet quality items (red and white hardhats) -#define FIRE_HELM_MAX_TEMP_PROTECT 30000 - -/// what max_heat_protection_temperature is set to for firesuit quality suits and helmets. MUST NOT BE 0. -#define FIRE_IMMUNITY_MAX_TEMP_PROTECT 35000 - -/// For normal helmets -#define HELMET_MIN_TEMP_PROTECT 160 -/// For normal helmets -#define HELMET_MAX_TEMP_PROTECT 600 -/// For armor -#define ARMOR_MIN_TEMP_PROTECT 160 -/// For armor -#define ARMOR_MAX_TEMP_PROTECT 600 - -/// For some gloves (black and) -#define GLOVES_MIN_TEMP_PROTECT 2.0 -/// For some gloves -#define GLOVES_MAX_TEMP_PROTECT 1500 -/// For gloves -#define SHOES_MIN_TEMP_PROTECT 2.0 -/// For gloves -#define SHOES_MAX_TEMP_PROTECT 1500 - -/// The amount of pressure damage someone takes is equal to (pressure / HAZARD_HIGH_PRESSURE)*PRESSURE_DAMAGE_COEFFICIENT, with the maximum of MAX_PRESSURE_DAMAGE -#define PRESSURE_DAMAGE_COEFFICIENT 4 -#define MAX_HIGH_PRESSURE_DAMAGE 4 -/// The amount of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value). -#define LOW_PRESSURE_DAMAGE 4 - -/// Humans are slowed by the difference between bodytemp and BODYTEMP_COLD_DAMAGE_LIMIT divided by this -#define COLD_SLOWDOWN_FACTOR 20 - -//PIPES -//Atmos pipe limits -/// (kPa) What pressure pumps and powered equipment max out at. -#define MAX_OUTPUT_PRESSURE 4500 -/// (L/s) Maximum speed powered equipment can work at. -#define MAX_TRANSFER_RATE 200 -/// 10% of an overclocked volume pump leaks into the air -#define VOLUME_PUMP_LEAK_AMOUNT 0.1 -//used for device_type vars -#define UNARY 1 -#define BINARY 2 -#define TRINARY 3 -#define QUATERNARY 4 - -//TANKS -/// temperature in kelvins at which a tank will start to melt -#define TANK_MELT_TEMPERATURE 1000000 -/// Tank starts leaking -#define TANK_LEAK_PRESSURE (30.*ONE_ATMOSPHERE) -/// Tank spills all contents into atmosphere -#define TANK_RUPTURE_PRESSURE (35.*ONE_ATMOSPHERE) -/// Boom 3x3 base explosion -#define TANK_FRAGMENT_PRESSURE (40.*ONE_ATMOSPHERE) -/// +1 for each SCALE kPa aboe threshold -#define TANK_FRAGMENT_SCALE (6.*ONE_ATMOSPHERE) -#define TANK_MAX_RELEASE_PRESSURE (ONE_ATMOSPHERE*3) -#define TANK_MIN_RELEASE_PRESSURE 0 -#define TANK_DEFAULT_RELEASE_PRESSURE 16 - -//CANATMOSPASS -#define ATMOS_PASS_YES 1 -#define ATMOS_PASS_NO 0 -/// ask CanAtmosPass() -#define ATMOS_PASS_PROC -1 -/// just check density -#define ATMOS_PASS_DENSITY -2 - -#define CANATMOSPASS(A, O) ( A.CanAtmosPass == ATMOS_PASS_PROC ? A.CanAtmosPass(O) : ( A.CanAtmosPass == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPass ) ) -#define CANVERTICALATMOSPASS(A, O) ( A.CanAtmosPassVertical == ATMOS_PASS_PROC ? A.CanAtmosPass(O, TRUE) : ( A.CanAtmosPassVertical == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPassVertical ) ) - -//OPEN TURF ATMOS -/// the default air mix that open turfs spawn -#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" -#define OPENTURF_LOW_PRESSURE "o2=14;n2=30;TEMP=293.15" -/// -193,15°C telecommunications. also used for xenobiology slime killrooms -#define TCOMMS_ATMOS "n2=100;TEMP=80" -/// space -#define AIRLESS_ATMOS "TEMP=2.7" -/// -93.15°C snow and ice turfs -#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" -/// -14°C kitchen coldroom, just might loss your tail; higher amount of mol to reach about 101.3 kpA -#define KITCHEN_COLDROOM_ATMOS "o2=26;n2=97;TEMP=259.15" -/// used in the holodeck burn test program -#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" - -//ATMOSPHERICS DEPARTMENT GAS TANK TURFS -#define ATMOS_TANK_N2O "n2o=6000;TEMP=293.15" -#define ATMOS_TANK_CO2 "co2=50000;TEMP=293.15" -#define ATMOS_TANK_PLASMA "plasma=70000;TEMP=293.15" -#define ATMOS_TANK_O2 "o2=100000;TEMP=293.15" -#define ATMOS_TANK_N2 "n2=100000;TEMP=293.15" -#define ATMOS_TANK_AIRMIX "o2=2644;n2=10580;TEMP=293.15" - -//LAVALAND -/// what pressure you have to be under to increase the effect of equipment meant for lavaland -#define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 50 - -//ATMOS MIX IDS -#define LAVALAND_DEFAULT_ATMOS "LAVALAND_ATMOS" -#define ICEMOON_DEFAULT_ATMOS "ICEMOON_ATMOS" - -//ATMOSIA GAS MONITOR TAGS -#define ATMOS_GAS_MONITOR_INPUT_O2 "o2_in" -#define ATMOS_GAS_MONITOR_OUTPUT_O2 "o2_out" -#define ATMOS_GAS_MONITOR_SENSOR_O2 "o2_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_TOX "tox_in" -#define ATMOS_GAS_MONITOR_OUTPUT_TOX "tox_out" -#define ATMOS_GAS_MONITOR_SENSOR_TOX "tox_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_AIR "air_in" -#define ATMOS_GAS_MONITOR_OUTPUT_AIR "air_out" -#define ATMOS_GAS_MONITOR_SENSOR_AIR "air_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_MIX "mix_in" -#define ATMOS_GAS_MONITOR_OUTPUT_MIX "mix_out" -#define ATMOS_GAS_MONITOR_SENSOR_MIX "mix_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_N2O "n2o_in" -#define ATMOS_GAS_MONITOR_OUTPUT_N2O "n2o_out" -#define ATMOS_GAS_MONITOR_SENSOR_N2O "n2o_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_N2 "n2_in" -#define ATMOS_GAS_MONITOR_OUTPUT_N2 "n2_out" -#define ATMOS_GAS_MONITOR_SENSOR_N2 "n2_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_CO2 "co2_in" -#define ATMOS_GAS_MONITOR_OUTPUT_CO2 "co2_out" -#define ATMOS_GAS_MONITOR_SENSOR_CO2 "co2_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_INCINERATOR "incinerator_in" -#define ATMOS_GAS_MONITOR_OUTPUT_INCINERATOR "incinerator_out" -#define ATMOS_GAS_MONITOR_SENSOR_INCINERATOR "incinerator_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_TOXINS_LAB "toxinslab_in" -#define ATMOS_GAS_MONITOR_OUTPUT_TOXINS_LAB "toxinslab_out" -#define ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB "toxinslab_sensor" - -#define ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION "distro-loop_meter" -#define ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE "atmos-waste_loop_meter" - -#define ATMOS_GAS_MONITOR_WASTE_ENGINE "engine-waste_out" -#define ATMOS_GAS_MONITOR_WASTE_ATMOS "atmos-waste_out" - -//AIRLOCK CONTROLLER TAGS - -//RnD toxins burn chamber -#define INCINERATOR_TOXMIX_IGNITER "toxmix_igniter" -#define INCINERATOR_TOXMIX_VENT "toxmix_vent" -#define INCINERATOR_TOXMIX_DP_VENTPUMP "toxmix_airlock_pump" -#define INCINERATOR_TOXMIX_AIRLOCK_SENSOR "toxmix_airlock_sensor" -#define INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER "toxmix_airlock_controller" -#define INCINERATOR_TOXMIX_AIRLOCK_INTERIOR "toxmix_airlock_interior" -#define INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR "toxmix_airlock_exterior" - -//Atmospherics/maintenance incinerator -#define INCINERATOR_ATMOS_IGNITER "atmos_incinerator_igniter" -#define INCINERATOR_ATMOS_MAINVENT "atmos_incinerator_mainvent" -#define INCINERATOR_ATMOS_AUXVENT "atmos_incinerator_auxvent" -#define INCINERATOR_ATMOS_DP_VENTPUMP "atmos_incinerator_airlock_pump" -#define INCINERATOR_ATMOS_AIRLOCK_SENSOR "atmos_incinerator_airlock_sensor" -#define INCINERATOR_ATMOS_AIRLOCK_CONTROLLER "atmos_incinerator_airlock_controller" -#define INCINERATOR_ATMOS_AIRLOCK_INTERIOR "atmos_incinerator_airlock_interior" -#define INCINERATOR_ATMOS_AIRLOCK_EXTERIOR "atmos_incinerator_airlock_exterior" - -//Syndicate lavaland base incinerator (lavaland_surface_syndicate_base1.dmm) -#define INCINERATOR_SYNDICATELAVA_IGNITER "syndicatelava_igniter" -#define INCINERATOR_SYNDICATELAVA_MAINVENT "syndicatelava_mainvent" -#define INCINERATOR_SYNDICATELAVA_AUXVENT "syndicatelava_auxvent" -#define INCINERATOR_SYNDICATELAVA_DP_VENTPUMP "syndicatelava_airlock_pump" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR "syndicatelava_airlock_sensor" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER "syndicatelava_airlock_controller" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR "syndicatelava_airlock_interior" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR "syndicatelava_airlock_exterior" - -//MULTIPIPES -//IF YOU EVER CHANGE THESE CHANGE SPRITES TO MATCH. -#define PIPING_LAYER_MIN 1 -#define PIPING_LAYER_MAX 3 -#define PIPING_LAYER_DEFAULT 2 -#define PIPING_LAYER_P_X 5 -#define PIPING_LAYER_P_Y 5 -#define PIPING_LAYER_LCHANGE 0.05 - -/// intended to connect with all layers, check for all instead of just one. -#define PIPING_ALL_LAYER (1<<0) -/// can only be built if nothing else with this flag is on the tile already. -#define PIPING_ONE_PER_TURF (1<<1) -/// can only exist at PIPING_LAYER_DEFAULT -#define PIPING_DEFAULT_LAYER_ONLY (1<<2) -/// north/south east/west doesn't matter, auto normalize on build. -#define PIPING_CARDINAL_AUTONORMALIZE (1<<3) - -//HELPERS -#define PIPING_LAYER_SHIFT(T, PipingLayer) \ - if(T.dir & (NORTH|SOUTH)) { \ - T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ - } \ - if(T.dir & (EAST|WEST)) { \ - T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y;\ - } - -#define PIPING_LAYER_DOUBLE_SHIFT(T, PipingLayer) \ - T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ - T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y; - -#define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) - -#define ADD_GAS(gas_id, out_list)\ - var/list/tmp_gaslist = GLOB.gaslist_cache[gas_id]; out_list[gas_id] = tmp_gaslist.Copy(); - -#define ASSERT_GAS(gas_id, gas_mixture) if (!gas_mixture.gases[gas_id]) { ADD_GAS(gas_id, gas_mixture.gases) }; - -//prefer this to gas_mixture/total_moles in performance critical areas -#define TOTAL_MOLES(cached_gases, out_var)\ - out_var = 0;\ - for(var/total_moles_id in cached_gases){\ - out_var += cached_gases[total_moles_id][MOLES];\ - } -#ifdef TESTING -GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) -#define CALCULATE_ADJACENT_TURFS(T) if (SSadjacent_air.queue[T]) { GLOB.atmos_adjacent_savings[1] += 1 } else { GLOB.atmos_adjacent_savings[2] += 1; SSadjacent_air.queue[T] = 1 } -#else -#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1 -#endif - -GLOBAL_LIST_INIT(pipe_paint_colors, sortList(list( - "amethyst" = rgb(130,43,255), //supplymain - "blue" = rgb(0,0,255), - "brown" = rgb(178,100,56), - "cyan" = rgb(0,255,249), - "dark" = rgb(69,69,69), - "green" = rgb(30,255,0), - "grey" = rgb(255,255,255), - "orange" = rgb(255,129,25), - "purple" = rgb(128,0,182), - "red" = rgb(255,0,0), - "violet" = rgb(64,0,128), - "yellow" = rgb(255,198,0) -))) - -#define MIASMA_CORPSE_MOLES 0.02 -#define MIASMA_GIBS_MOLES 0.005 +//LISTMOS +//indices of values in gas lists. +#define MOLES 1 +#define ARCHIVE 2 +#define GAS_META 3 +#define META_GAS_SPECIFIC_HEAT 1 +#define META_GAS_NAME 2 +#define META_GAS_MOLES_VISIBLE 3 +#define META_GAS_OVERLAY 4 +#define META_GAS_DANGER 5 +#define META_GAS_ID 6 +#define META_GAS_FUSION_POWER 7 +//ATMOS +//stuff you should probably leave well alone! +/// kPa*L/(K*mol) +#define R_IDEAL_GAS_EQUATION 8.31 +/// kPa +#define ONE_ATMOSPHERE 101.325 +/// -270.3degC +#define TCMB 2.7 +/// -48.15degC +#define TCRYO 225 +/// 0degC +#define T0C 273.15 +/// 20degC +#define T20C 293.15 + +///moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC +#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) +///compared against for superconductivity +#define M_CELL_WITH_RATIO (MOLES_CELLSTANDARD * 0.005) +/// percentage of oxygen in a normal mixture of air +#define O2STANDARD 0.21 +/// same but for nitrogen +#define N2STANDARD 0.79 +/// O2 standard value (21%) +#define MOLES_O2STANDARD (MOLES_CELLSTANDARD*O2STANDARD) +/// N2 standard value (79%) +#define MOLES_N2STANDARD (MOLES_CELLSTANDARD*N2STANDARD) +/// liters in a cell +#define CELL_VOLUME 2500 + +/// liters in a normal breath +#define BREATH_VOLUME 0.5 +/// Amount of air to take a from a tile +#define BREATH_PERCENTAGE (BREATH_VOLUME/CELL_VOLUME) + + +//EXCITED GROUPS +/// number of FULL air controller ticks before an excited group breaks down (averages gas contents across turfs) +#define EXCITED_GROUP_BREAKDOWN_CYCLES 4 +/// number of FULL air controller ticks before an excited group dismantles and removes its turfs from active +#define EXCITED_GROUP_DISMANTLE_CYCLES 16 +/// Ratio of air that must move to/from a tile to reset group processing +#define MINIMUM_AIR_RATIO_TO_SUSPEND 0.1 +/// Minimum ratio of air that must move to/from a tile +#define MINIMUM_AIR_RATIO_TO_MOVE 0.001 +/// Minimum amount of air that has to move before a group processing can be suspended +#define MINIMUM_AIR_TO_SUSPEND (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_SUSPEND) +/// Either this must be active +#define MINIMUM_MOLES_DELTA_TO_MOVE (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_MOVE) +/// or this (or both, obviously) +#define MINIMUM_TEMPERATURE_TO_MOVE (T20C+100) +/// Minimum temperature difference before group processing is suspended +#define MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND 4 +/// Minimum temperature difference before the gas temperatures are just set to be equal +#define MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER 0.5 +#define MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION (T20C+10) +#define MINIMUM_TEMPERATURE_START_SUPERCONDUCTION (T20C+200) + +//HEAT TRANSFER COEFFICIENTS +//Must be between 0 and 1. Values closer to 1 equalize temperature faster +//Should not exceed 0.4 else strange heat flow occur +#define WALL_HEAT_TRANSFER_COEFFICIENT 0.0 +#define OPEN_HEAT_TRANSFER_COEFFICIENT 0.4 +/// a hack for now +#define WINDOW_HEAT_TRANSFER_COEFFICIENT 0.1 +/// a hack to help make vacuums "cold", sacrificing realism for gameplay +#define HEAT_CAPACITY_VACUUM 7000 + +//FIRE +#define FIRE_MINIMUM_TEMPERATURE_TO_SPREAD (150+T0C) +#define FIRE_MINIMUM_TEMPERATURE_TO_EXIST (100+T0C) +#define FIRE_SPREAD_RADIOSITY_SCALE 0.85 +#define FIRE_GROWTH_RATE 40000 //For small fires +#define PLASMA_MINIMUM_BURN_TEMPERATURE (100+T0C) +#define PLASMA_UPPER_TEMPERATURE (1370+T0C) +#define PLASMA_OXYGEN_FULLBURN 10 +#define HYDROGEN_MINIMUM_BURN_TEMPERATURE (100+T0C) +#define HYDROGEN_UPPER_TEMPERATURE (1370+T0C) +#define HYDROGEN_OXYGEN_FULLBURN 10 + +//COLD FIRE (this is used only for the freon-o2 reaction, there is no fire still) +#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_SPREAD 263 //fire will spread if the temperature is -10 °C +#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_EXIST 273 //fire will start if the temperature is 0 °C +#define COLD_FIRE_SPREAD_RADIOSITY_SCALE 0.95 +#define COLD_FIRE_GROWTH_RATE 40000 +#define FREON_MAXIMUM_BURN_TEMPERATURE 293 +#define FREON_LOWER_TEMPERATURE 60 //minimum temperature allowed for the burn to go, we would have negative pressure otherwise +#define FREON_OXYGEN_FULLBURN 10 + +//GASES +#define MIN_TOXIC_GAS_DAMAGE 1 +#define MAX_TOXIC_GAS_DAMAGE 10 +/// Moles in a standard cell after which gases are visible +#define MOLES_GAS_VISIBLE 0.25 + +/// moles_visible * FACTOR_GAS_VISIBLE_MAX = Moles after which gas is at maximum visibility +#define FACTOR_GAS_VISIBLE_MAX 20 +/// Mole step for alpha updates. This means alpha can update at 0.25, 0.5, 0.75 and so on +#define MOLES_GAS_VISIBLE_STEP 0.25 + +//REACTIONS +//return values for reactions (bitflags) +#define NO_REACTION 0 +#define REACTING 1 +#define STOP_REACTIONS 2 + +// Pressure limits. +/// This determins at what pressure the ultra-high pressure red icon is displayed. (This one is set as a constant) +#define HAZARD_HIGH_PRESSURE 550 +/// This determins when the orange pressure icon is displayed (it is 0.7 * HAZARD_HIGH_PRESSURE) +#define WARNING_HIGH_PRESSURE 325 +/// This is when the gray low pressure icon is displayed. (it is 2.5 * HAZARD_LOW_PRESSURE) +#define WARNING_LOW_PRESSURE 50 +/// This is when the black ultra-low pressure icon is displayed. (This one is set as a constant) +#define HAZARD_LOW_PRESSURE 20 + +/// This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount. +#define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 + +/// The natural temperature for a body +#define BODYTEMP_NORMAL 310.15 +/// This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive. +#define BODYTEMP_AUTORECOVERY_DIVISOR 11 +/// Minimum amount of kelvin moved toward 310K per tick. So long as abs(310.15 - bodytemp) is more than 50. +#define BODYTEMP_AUTORECOVERY_MINIMUM 12 +///Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is lower than their body temperature. Make it lower to lose bodytemp faster. +#define BODYTEMP_COLD_DIVISOR 15 +/// Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is higher than their body temperature. Make it lower to gain bodytemp faster. +#define BODYTEMP_HEAT_DIVISOR 15 +/// The maximum number of degrees that your body can cool in 1 tick, due to the environment, when in a cold area. +#define BODYTEMP_COOLING_MAX -100 +/// The maximum number of degrees that your body can heat up in 1 tick, due to the environment, when in a hot area. +#define BODYTEMP_HEATING_MAX 30 +/// The body temperature limit the human body can take before it starts taking damage from heat. +/// This also affects how fast the body normalises it's temperature when hot. +/// 340k is about 66c, and rather high for a human. +#define BODYTEMP_HEAT_DAMAGE_LIMIT (BODYTEMP_NORMAL + 30) +/// The body temperature limit the human body can take before it starts taking damage from cold. +/// This also affects how fast the body normalises it's temperature when cold. +/// 270k is about -3c, that is below freezing and would hurt over time. +#define BODYTEMP_COLD_DAMAGE_LIMIT (BODYTEMP_NORMAL - 40) + +/// what min_cold_protection_temperature is set to for space-helmet quality headwear. MUST NOT BE 0. +#define SPACE_HELM_MIN_TEMP_PROTECT 2.0 +/// Thermal insulation works both ways /Malkevin +#define SPACE_HELM_MAX_TEMP_PROTECT 1500 +/// what min_cold_protection_temperature is set to for space-suit quality jumpsuits or suits. MUST NOT BE 0. +#define SPACE_SUIT_MIN_TEMP_PROTECT 2.0 +/// The min cold protection of a space suit without the heater active +#define SPACE_SUIT_MIN_TEMP_PROTECT_OFF 72 +#define SPACE_SUIT_MAX_TEMP_PROTECT 1500 + +/// Cold protection for firesuits +#define FIRE_SUIT_MIN_TEMP_PROTECT 60 +/// what max_heat_protection_temperature is set to for firesuit quality suits. MUST NOT BE 0. +#define FIRE_SUIT_MAX_TEMP_PROTECT 30000 +/// Cold protection for fire helmets +#define FIRE_HELM_MIN_TEMP_PROTECT 60 +/// for fire helmet quality items (red and white hardhats) +#define FIRE_HELM_MAX_TEMP_PROTECT 30000 + +/// what max_heat_protection_temperature is set to for firesuit quality suits and helmets. MUST NOT BE 0. +#define FIRE_IMMUNITY_MAX_TEMP_PROTECT 35000 + +/// For normal helmets +#define HELMET_MIN_TEMP_PROTECT 160 +/// For normal helmets +#define HELMET_MAX_TEMP_PROTECT 600 +/// For armor +#define ARMOR_MIN_TEMP_PROTECT 160 +/// For armor +#define ARMOR_MAX_TEMP_PROTECT 600 + +/// For some gloves (black and) +#define GLOVES_MIN_TEMP_PROTECT 2.0 +/// For some gloves +#define GLOVES_MAX_TEMP_PROTECT 1500 +/// For gloves +#define SHOES_MIN_TEMP_PROTECT 2.0 +/// For gloves +#define SHOES_MAX_TEMP_PROTECT 1500 + +/// The amount of pressure damage someone takes is equal to (pressure / HAZARD_HIGH_PRESSURE)*PRESSURE_DAMAGE_COEFFICIENT, with the maximum of MAX_PRESSURE_DAMAGE +#define PRESSURE_DAMAGE_COEFFICIENT 4 +#define MAX_HIGH_PRESSURE_DAMAGE 4 +/// The amount of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value). +#define LOW_PRESSURE_DAMAGE 4 + +/// Humans are slowed by the difference between bodytemp and BODYTEMP_COLD_DAMAGE_LIMIT divided by this +#define COLD_SLOWDOWN_FACTOR 20 + +//PIPES +//Atmos pipe limits +/// (kPa) What pressure pumps and powered equipment max out at. +#define MAX_OUTPUT_PRESSURE 4500 +/// (L/s) Maximum speed powered equipment can work at. +#define MAX_TRANSFER_RATE 200 +/// 10% of an overclocked volume pump leaks into the air +#define VOLUME_PUMP_LEAK_AMOUNT 0.1 +//used for device_type vars +#define UNARY 1 +#define BINARY 2 +#define TRINARY 3 +#define QUATERNARY 4 + +//TANKS +/// temperature in kelvins at which a tank will start to melt +#define TANK_MELT_TEMPERATURE 1000000 +/// Tank starts leaking +#define TANK_LEAK_PRESSURE (30.*ONE_ATMOSPHERE) +/// Tank spills all contents into atmosphere +#define TANK_RUPTURE_PRESSURE (35.*ONE_ATMOSPHERE) +/// Boom 3x3 base explosion +#define TANK_FRAGMENT_PRESSURE (40.*ONE_ATMOSPHERE) +/// +1 for each SCALE kPa aboe threshold +#define TANK_FRAGMENT_SCALE (6.*ONE_ATMOSPHERE) +#define TANK_MAX_RELEASE_PRESSURE (ONE_ATMOSPHERE*3) +#define TANK_MIN_RELEASE_PRESSURE 0 +#define TANK_DEFAULT_RELEASE_PRESSURE 16 + +//CANATMOSPASS +#define ATMOS_PASS_YES 1 +#define ATMOS_PASS_NO 0 +/// ask CanAtmosPass() +#define ATMOS_PASS_PROC -1 +/// just check density +#define ATMOS_PASS_DENSITY -2 + +#define CANATMOSPASS(A, O) ( A.CanAtmosPass == ATMOS_PASS_PROC ? A.CanAtmosPass(O) : ( A.CanAtmosPass == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPass ) ) +#define CANVERTICALATMOSPASS(A, O) ( A.CanAtmosPassVertical == ATMOS_PASS_PROC ? A.CanAtmosPass(O, TRUE) : ( A.CanAtmosPassVertical == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPassVertical ) ) + +//OPEN TURF ATMOS +/// the default air mix that open turfs spawn +#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" +#define OPENTURF_LOW_PRESSURE "o2=14;n2=30;TEMP=293.15" +/// -193,15°C telecommunications. also used for xenobiology slime killrooms +#define TCOMMS_ATMOS "n2=100;TEMP=80" +/// space +#define AIRLESS_ATMOS "TEMP=2.7" +/// -93.15°C snow and ice turfs +#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" +/// -14°C kitchen coldroom, just might loss your tail; higher amount of mol to reach about 101.3 kpA +#define KITCHEN_COLDROOM_ATMOS "o2=26;n2=97;TEMP=259.15" +/// used in the holodeck burn test program +#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" + +//ATMOSPHERICS DEPARTMENT GAS TANK TURFS +#define ATMOS_TANK_N2O "n2o=6000;TEMP=293.15" +#define ATMOS_TANK_CO2 "co2=50000;TEMP=293.15" +#define ATMOS_TANK_PLASMA "plasma=70000;TEMP=293.15" +#define ATMOS_TANK_O2 "o2=100000;TEMP=293.15" +#define ATMOS_TANK_N2 "n2=100000;TEMP=293.15" +#define ATMOS_TANK_AIRMIX "o2=2644;n2=10580;TEMP=293.15" + +//LAVALAND +/// what pressure you have to be under to increase the effect of equipment meant for lavaland +#define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 50 + +//ATMOS MIX IDS +#define LAVALAND_DEFAULT_ATMOS "LAVALAND_ATMOS" +#define ICEMOON_DEFAULT_ATMOS "ICEMOON_ATMOS" + +//ATMOSIA GAS MONITOR TAGS +#define ATMOS_GAS_MONITOR_INPUT_O2 "o2_in" +#define ATMOS_GAS_MONITOR_OUTPUT_O2 "o2_out" +#define ATMOS_GAS_MONITOR_SENSOR_O2 "o2_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_TOX "tox_in" +#define ATMOS_GAS_MONITOR_OUTPUT_TOX "tox_out" +#define ATMOS_GAS_MONITOR_SENSOR_TOX "tox_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_AIR "air_in" +#define ATMOS_GAS_MONITOR_OUTPUT_AIR "air_out" +#define ATMOS_GAS_MONITOR_SENSOR_AIR "air_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_MIX "mix_in" +#define ATMOS_GAS_MONITOR_OUTPUT_MIX "mix_out" +#define ATMOS_GAS_MONITOR_SENSOR_MIX "mix_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_N2O "n2o_in" +#define ATMOS_GAS_MONITOR_OUTPUT_N2O "n2o_out" +#define ATMOS_GAS_MONITOR_SENSOR_N2O "n2o_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_N2 "n2_in" +#define ATMOS_GAS_MONITOR_OUTPUT_N2 "n2_out" +#define ATMOS_GAS_MONITOR_SENSOR_N2 "n2_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_CO2 "co2_in" +#define ATMOS_GAS_MONITOR_OUTPUT_CO2 "co2_out" +#define ATMOS_GAS_MONITOR_SENSOR_CO2 "co2_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_INCINERATOR "incinerator_in" +#define ATMOS_GAS_MONITOR_OUTPUT_INCINERATOR "incinerator_out" +#define ATMOS_GAS_MONITOR_SENSOR_INCINERATOR "incinerator_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_TOXINS_LAB "toxinslab_in" +#define ATMOS_GAS_MONITOR_OUTPUT_TOXINS_LAB "toxinslab_out" +#define ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB "toxinslab_sensor" + +#define ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION "distro-loop_meter" +#define ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE "atmos-waste_loop_meter" + +#define ATMOS_GAS_MONITOR_WASTE_ENGINE "engine-waste_out" +#define ATMOS_GAS_MONITOR_WASTE_ATMOS "atmos-waste_out" + +//AIRLOCK CONTROLLER TAGS + +//RnD toxins burn chamber +#define INCINERATOR_TOXMIX_IGNITER "toxmix_igniter" +#define INCINERATOR_TOXMIX_VENT "toxmix_vent" +#define INCINERATOR_TOXMIX_DP_VENTPUMP "toxmix_airlock_pump" +#define INCINERATOR_TOXMIX_AIRLOCK_SENSOR "toxmix_airlock_sensor" +#define INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER "toxmix_airlock_controller" +#define INCINERATOR_TOXMIX_AIRLOCK_INTERIOR "toxmix_airlock_interior" +#define INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR "toxmix_airlock_exterior" + +//Atmospherics/maintenance incinerator +#define INCINERATOR_ATMOS_IGNITER "atmos_incinerator_igniter" +#define INCINERATOR_ATMOS_MAINVENT "atmos_incinerator_mainvent" +#define INCINERATOR_ATMOS_AUXVENT "atmos_incinerator_auxvent" +#define INCINERATOR_ATMOS_DP_VENTPUMP "atmos_incinerator_airlock_pump" +#define INCINERATOR_ATMOS_AIRLOCK_SENSOR "atmos_incinerator_airlock_sensor" +#define INCINERATOR_ATMOS_AIRLOCK_CONTROLLER "atmos_incinerator_airlock_controller" +#define INCINERATOR_ATMOS_AIRLOCK_INTERIOR "atmos_incinerator_airlock_interior" +#define INCINERATOR_ATMOS_AIRLOCK_EXTERIOR "atmos_incinerator_airlock_exterior" + +//Syndicate lavaland base incinerator (lavaland_surface_syndicate_base1.dmm) +#define INCINERATOR_SYNDICATELAVA_IGNITER "syndicatelava_igniter" +#define INCINERATOR_SYNDICATELAVA_MAINVENT "syndicatelava_mainvent" +#define INCINERATOR_SYNDICATELAVA_AUXVENT "syndicatelava_auxvent" +#define INCINERATOR_SYNDICATELAVA_DP_VENTPUMP "syndicatelava_airlock_pump" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR "syndicatelava_airlock_sensor" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER "syndicatelava_airlock_controller" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR "syndicatelava_airlock_interior" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR "syndicatelava_airlock_exterior" + +//MULTIPIPES +//IF YOU EVER CHANGE THESE CHANGE SPRITES TO MATCH. +#define PIPING_LAYER_MIN 1 +#define PIPING_LAYER_MAX 3 +#define PIPING_LAYER_DEFAULT 2 +#define PIPING_LAYER_P_X 5 +#define PIPING_LAYER_P_Y 5 +#define PIPING_LAYER_LCHANGE 0.05 + +/// intended to connect with all layers, check for all instead of just one. +#define PIPING_ALL_LAYER (1<<0) +/// can only be built if nothing else with this flag is on the tile already. +#define PIPING_ONE_PER_TURF (1<<1) +/// can only exist at PIPING_LAYER_DEFAULT +#define PIPING_DEFAULT_LAYER_ONLY (1<<2) +/// north/south east/west doesn't matter, auto normalize on build. +#define PIPING_CARDINAL_AUTONORMALIZE (1<<3) + +//HELPERS +#define PIPING_LAYER_SHIFT(T, PipingLayer) \ + if(T.dir & (NORTH|SOUTH)) { \ + T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ + } \ + if(T.dir & (EAST|WEST)) { \ + T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y;\ + } + +#define PIPING_LAYER_DOUBLE_SHIFT(T, PipingLayer) \ + T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ + T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y; + +#define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) + +#define ADD_GAS(gas_id, out_list)\ + var/list/tmp_gaslist = GLOB.gaslist_cache[gas_id]; out_list[gas_id] = tmp_gaslist.Copy(); + +#define ASSERT_GAS(gas_id, gas_mixture) if (!gas_mixture.gases[gas_id]) { ADD_GAS(gas_id, gas_mixture.gases) }; + +//prefer this to gas_mixture/total_moles in performance critical areas +#define TOTAL_MOLES(cached_gases, out_var)\ + out_var = 0;\ + for(var/total_moles_id in cached_gases){\ + out_var += cached_gases[total_moles_id][MOLES];\ + } +#ifdef TESTING +GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) +#define CALCULATE_ADJACENT_TURFS(T) if (SSadjacent_air.queue[T]) { GLOB.atmos_adjacent_savings[1] += 1 } else { GLOB.atmos_adjacent_savings[2] += 1; SSadjacent_air.queue[T] = 1 } +#else +#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1 +#endif + +GLOBAL_LIST_INIT(pipe_paint_colors, sortList(list( + "amethyst" = rgb(130,43,255), //supplymain + "blue" = rgb(0,0,255), + "brown" = rgb(178,100,56), + "cyan" = rgb(0,255,249), + "dark" = rgb(69,69,69), + "green" = rgb(30,255,0), + "grey" = rgb(255,255,255), + "orange" = rgb(255,129,25), + "purple" = rgb(128,0,182), + "red" = rgb(255,0,0), + "violet" = rgb(64,0,128), + "yellow" = rgb(255,198,0) +))) + +#define MIASMA_CORPSE_MOLES 0.02 +#define MIASMA_GIBS_MOLES 0.005 diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 1bb5578122f..8965715cbb7 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -1,244 +1,244 @@ -/*ALL DEFINES RELATED TO COMBAT GO HERE*/ - -//Damage and status effect defines - -//Damage defines //TODO: merge these down to reduce on defines -#define BRUTE "brute" -#define BURN "fire" -#define TOX "toxin" -#define OXY "oxygen" -#define CLONE "clone" -#define STAMINA "stamina" -#define BRAIN "brain" - -//bitflag damage defines used for suicide_act -#define BRUTELOSS (1<<0) -#define FIRELOSS (1<<1) -#define TOXLOSS (1<<2) -#define OXYLOSS (1<<3) -#define SHAME (1<<4) -#define MANUAL_SUICIDE (1<<5) //suicide_act will do the actual killing. -#define MANUAL_SUICIDE_NONLETHAL (1<<6) //when the suicide is conditionally lethal - -#define EFFECT_STUN "stun" -#define EFFECT_KNOCKDOWN "knockdown" -#define EFFECT_UNCONSCIOUS "unconscious" -#define EFFECT_PARALYZE "paralyze" -#define EFFECT_IMMOBILIZE "immobilize" -#define EFFECT_IRRADIATE "irradiate" -#define EFFECT_STUTTER "stutter" -#define EFFECT_SLUR "slur" -#define EFFECT_EYE_BLUR "eye_blur" -#define EFFECT_DROWSY "drowsy" -#define EFFECT_JITTER "jitter" - -//Bitflags defining which status effects could be or are inflicted on a mob -#define CANSTUN (1<<0) -#define CANKNOCKDOWN (1<<1) -#define CANUNCONSCIOUS (1<<2) -#define CANPUSH (1<<3) -#define GODMODE (1<<4) - -//Health Defines -#define HEALTH_THRESHOLD_CRIT 0 -#define HEALTH_THRESHOLD_FULLCRIT -30 -#define HEALTH_THRESHOLD_DEAD -100 - -#define HEALTH_THRESHOLD_NEARDEATH -90 //Not used mechanically, but to determine if someone is so close to death they hear the other side - -//Actual combat defines - -//click cooldowns, in tenths of a second, used for various combat actions -#define CLICK_CD_MELEE 8 -#define CLICK_CD_RANGE 4 -#define CLICK_CD_RAPID 2 -#define CLICK_CD_CLICK_ABILITY 6 -#define CLICK_CD_BREAKOUT 100 -#define CLICK_CD_HANDCUFFED 10 -#define CLICK_CD_RESIST 20 -#define CLICK_CD_GRABBING 10 -#define CLICK_CD_LOOK_UP 5 - -//Cuff resist speeds -#define FAST_CUFFBREAK 1 -#define INSTANT_CUFFBREAK 2 - -//Grab levels -#define GRAB_PASSIVE 0 -#define GRAB_AGGRESSIVE 1 -#define GRAB_NECK 2 -#define GRAB_KILL 3 - -//Grab breakout odds -#define BASE_GRAB_RESIST_CHANCE 60 //base chance for whether or not you can escape from a grab - -//slowdown when in softcrit. Note that crawling slowdown will also apply at the same time! -#define SOFTCRIT_ADD_SLOWDOWN 2 -//slowdown when crawling -#define CRAWLING_ADD_SLOWDOWN 4 - -//Attack types for checking shields/hit reactions -#define MELEE_ATTACK 1 -#define UNARMED_ATTACK 2 -#define PROJECTILE_ATTACK 3 -#define THROWN_PROJECTILE_ATTACK 4 -#define LEAP_ATTACK 5 - -//attack visual effects -#define ATTACK_EFFECT_PUNCH "punch" -#define ATTACK_EFFECT_KICK "kick" -#define ATTACK_EFFECT_SMASH "smash" -#define ATTACK_EFFECT_CLAW "claw" -#define ATTACK_EFFECT_SLASH "slash" -#define ATTACK_EFFECT_DISARM "disarm" -#define ATTACK_EFFECT_BITE "bite" -#define ATTACK_EFFECT_MECHFIRE "mech_fire" -#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" -#define ATTACK_EFFECT_BOOP "boop" //Honk - -//intent defines -#define INTENT_HELP "help" -#define INTENT_GRAB "grab" -#define INTENT_DISARM "disarm" -#define INTENT_HARM "harm" -//NOTE: INTENT_HOTKEY_* defines are not actual intents! -//they are here to support hotkeys -#define INTENT_HOTKEY_LEFT "left" -#define INTENT_HOTKEY_RIGHT "right" - -//the define for visible message range in combat -#define COMBAT_MESSAGE_RANGE 3 -#define DEFAULT_MESSAGE_RANGE 7 - -//Shove knockdown lengths (deciseconds) -#define SHOVE_KNOCKDOWN_SOLID 30 -#define SHOVE_KNOCKDOWN_HUMAN 30 -#define SHOVE_KNOCKDOWN_TABLE 30 -#define SHOVE_KNOCKDOWN_COLLATERAL 10 -#define SHOVE_CHAIN_PARALYZE 40 -//Shove slowdown -#define SHOVE_SLOWDOWN_LENGTH 30 -#define SHOVE_SLOWDOWN_STRENGTH 0.85 //multiplier -//Shove disarming item list -GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( - /obj/item/gun))) - - -//Combat object defines - -//Embedded objects -#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) -#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) -#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) -#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) -#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) -#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) -#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) -#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) -#define EMBEDDED_UNSAFE_REMOVAL_TIME 30 //A Time in ticks, total removal time = (this*item.w_class) -#define EMBEDDED_JOSTLE_CHANCE 5 //Chance for embedded objects to cause pain every time they move (jostle) -#define EMBEDDED_JOSTLE_PAIN_MULTIPLIER 1 //Coefficient of multiplication for the damage the item does while -#define EMBEDDED_PAIN_STAM_PCT 0.0 //This percentage of all pain will be dealt as stam damage rather than brute (0-1) - -#define EMBED_HARMLESS list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE) -#define EMBED_HARMLESS_SUPERIOR list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 100, "fall_chance" = 0.1) -#define EMBED_POINTY list("ignore_throwspeed_threshold" = TRUE) -#define EMBED_POINTY_SUPERIOR list("embed_chance" = 100, "ignore_throwspeed_threshold" = TRUE) - -//Gun weapon weight -#define WEAPON_LIGHT 1 -#define WEAPON_MEDIUM 2 -#define WEAPON_HEAVY 3 -//Gun trigger guards -#define TRIGGER_GUARD_ALLOW_ALL -1 -#define TRIGGER_GUARD_NONE 0 -#define TRIGGER_GUARD_NORMAL 1 -//Gun bolt types -///Gun has a bolt, it stays closed while not cycling. The gun must be racked to have a bullet chambered when a mag is inserted. -/// Example: c20, shotguns, m90 -#define BOLT_TYPE_STANDARD 1 -///Gun has a bolt, it is open when ready to fire. The gun can never have a chambered bullet with no magazine, but the bolt stays ready when a mag is removed. -/// Example: Some SMGs, the L6 -#define BOLT_TYPE_OPEN 2 -///Gun has no moving bolt mechanism, it cannot be racked. Also dumps the entire contents when emptied instead of a magazine. -/// Example: Break action shotguns, revolvers -#define BOLT_TYPE_NO_BOLT 3 -///Gun has a bolt, it locks back when empty. It can be released to chamber a round if a magazine is in. -/// Example: Pistols with a slide lock, some SMGs -#define BOLT_TYPE_LOCKING 4 -//Sawn off nerfs -///accuracy penalty of sawn off guns -#define SAWN_OFF_ACC_PENALTY 25 -///added recoil of sawn off guns -#define SAWN_OFF_RECOIL 1 - -//ammo box sprite defines -///ammo box will always use provided icon state -#define AMMO_BOX_ONE_SPRITE 0 -///ammo box will have a different state for each bullet; - -#define AMMO_BOX_PER_BULLET 1 -///ammo box will have a different state for full and empty; -max_ammo and -0 -#define AMMO_BOX_FULL_EMPTY 2 - -#define SUPPRESSED_NONE 0 -#define SUPPRESSED_QUIET 1 ///standard suppressed -#define SUPPRESSED_VERY 2 /// no message - -//Projectile Reflect -#define REFLECT_NORMAL (1<<0) -#define REFLECT_FAKEPROJECTILE (1<<1) - -//Object/Item sharpness -#define IS_BLUNT 0 -#define IS_SHARP 1 -#define IS_SHARP_ACCURATE 2 - -//His Grace. -#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. -#define HIS_GRACE_PECKISH 20 //Slightly hungry. -#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. -#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. -#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. -#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. -#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. - -#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. - -#define EXPLODE_NONE 0 //Don't even ask me why we need this. -#define EXPLODE_DEVASTATE 1 -#define EXPLODE_HEAVY 2 -#define EXPLODE_LIGHT 3 -#define EXPLODE_GIB_THRESHOLD 50 //ex_act() with EXPLODE_DEVASTATE severity will gib mobs with less than this much bomb armor - -#define EMP_HEAVY 1 -#define EMP_LIGHT 2 - -#define GRENADE_CLUMSY_FUMBLE 1 -#define GRENADE_NONCLUMSY_FUMBLE 2 -#define GRENADE_NO_FUMBLE 3 - -#define BODY_ZONE_HEAD "head" -#define BODY_ZONE_CHEST "chest" -#define BODY_ZONE_L_ARM "l_arm" -#define BODY_ZONE_R_ARM "r_arm" -#define BODY_ZONE_L_LEG "l_leg" -#define BODY_ZONE_R_LEG "r_leg" - -#define BODY_ZONE_PRECISE_EYES "eyes" -#define BODY_ZONE_PRECISE_MOUTH "mouth" -#define BODY_ZONE_PRECISE_GROIN "groin" -#define BODY_ZONE_PRECISE_L_HAND "l_hand" -#define BODY_ZONE_PRECISE_R_HAND "r_hand" -#define BODY_ZONE_PRECISE_L_FOOT "l_foot" -#define BODY_ZONE_PRECISE_R_FOOT "r_foot" - -//We will round to this value in damage calculations. -#define DAMAGE_PRECISION 0.1 - -//bullet_act() return values -#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. -#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. - -#define NICE_SHOT_RICOCHET_BONUS 10 //if the shooter has the NICE_SHOT trait and they fire a ricocheting projectile, add this to the ricochet chance and auto aim angle +/*ALL DEFINES RELATED TO COMBAT GO HERE*/ + +//Damage and status effect defines + +//Damage defines //TODO: merge these down to reduce on defines +#define BRUTE "brute" +#define BURN "fire" +#define TOX "toxin" +#define OXY "oxygen" +#define CLONE "clone" +#define STAMINA "stamina" +#define BRAIN "brain" + +//bitflag damage defines used for suicide_act +#define BRUTELOSS (1<<0) +#define FIRELOSS (1<<1) +#define TOXLOSS (1<<2) +#define OXYLOSS (1<<3) +#define SHAME (1<<4) +#define MANUAL_SUICIDE (1<<5) //suicide_act will do the actual killing. +#define MANUAL_SUICIDE_NONLETHAL (1<<6) //when the suicide is conditionally lethal + +#define EFFECT_STUN "stun" +#define EFFECT_KNOCKDOWN "knockdown" +#define EFFECT_UNCONSCIOUS "unconscious" +#define EFFECT_PARALYZE "paralyze" +#define EFFECT_IMMOBILIZE "immobilize" +#define EFFECT_IRRADIATE "irradiate" +#define EFFECT_STUTTER "stutter" +#define EFFECT_SLUR "slur" +#define EFFECT_EYE_BLUR "eye_blur" +#define EFFECT_DROWSY "drowsy" +#define EFFECT_JITTER "jitter" + +//Bitflags defining which status effects could be or are inflicted on a mob +#define CANSTUN (1<<0) +#define CANKNOCKDOWN (1<<1) +#define CANUNCONSCIOUS (1<<2) +#define CANPUSH (1<<3) +#define GODMODE (1<<4) + +//Health Defines +#define HEALTH_THRESHOLD_CRIT 0 +#define HEALTH_THRESHOLD_FULLCRIT -30 +#define HEALTH_THRESHOLD_DEAD -100 + +#define HEALTH_THRESHOLD_NEARDEATH -90 //Not used mechanically, but to determine if someone is so close to death they hear the other side + +//Actual combat defines + +//click cooldowns, in tenths of a second, used for various combat actions +#define CLICK_CD_MELEE 8 +#define CLICK_CD_RANGE 4 +#define CLICK_CD_RAPID 2 +#define CLICK_CD_CLICK_ABILITY 6 +#define CLICK_CD_BREAKOUT 100 +#define CLICK_CD_HANDCUFFED 10 +#define CLICK_CD_RESIST 20 +#define CLICK_CD_GRABBING 10 +#define CLICK_CD_LOOK_UP 5 + +//Cuff resist speeds +#define FAST_CUFFBREAK 1 +#define INSTANT_CUFFBREAK 2 + +//Grab levels +#define GRAB_PASSIVE 0 +#define GRAB_AGGRESSIVE 1 +#define GRAB_NECK 2 +#define GRAB_KILL 3 + +//Grab breakout odds +#define BASE_GRAB_RESIST_CHANCE 60 //base chance for whether or not you can escape from a grab + +//slowdown when in softcrit. Note that crawling slowdown will also apply at the same time! +#define SOFTCRIT_ADD_SLOWDOWN 2 +//slowdown when crawling +#define CRAWLING_ADD_SLOWDOWN 4 + +//Attack types for checking shields/hit reactions +#define MELEE_ATTACK 1 +#define UNARMED_ATTACK 2 +#define PROJECTILE_ATTACK 3 +#define THROWN_PROJECTILE_ATTACK 4 +#define LEAP_ATTACK 5 + +//attack visual effects +#define ATTACK_EFFECT_PUNCH "punch" +#define ATTACK_EFFECT_KICK "kick" +#define ATTACK_EFFECT_SMASH "smash" +#define ATTACK_EFFECT_CLAW "claw" +#define ATTACK_EFFECT_SLASH "slash" +#define ATTACK_EFFECT_DISARM "disarm" +#define ATTACK_EFFECT_BITE "bite" +#define ATTACK_EFFECT_MECHFIRE "mech_fire" +#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" +#define ATTACK_EFFECT_BOOP "boop" //Honk + +//intent defines +#define INTENT_HELP "help" +#define INTENT_GRAB "grab" +#define INTENT_DISARM "disarm" +#define INTENT_HARM "harm" +//NOTE: INTENT_HOTKEY_* defines are not actual intents! +//they are here to support hotkeys +#define INTENT_HOTKEY_LEFT "left" +#define INTENT_HOTKEY_RIGHT "right" + +//the define for visible message range in combat +#define COMBAT_MESSAGE_RANGE 3 +#define DEFAULT_MESSAGE_RANGE 7 + +//Shove knockdown lengths (deciseconds) +#define SHOVE_KNOCKDOWN_SOLID 30 +#define SHOVE_KNOCKDOWN_HUMAN 30 +#define SHOVE_KNOCKDOWN_TABLE 30 +#define SHOVE_KNOCKDOWN_COLLATERAL 10 +#define SHOVE_CHAIN_PARALYZE 40 +//Shove slowdown +#define SHOVE_SLOWDOWN_LENGTH 30 +#define SHOVE_SLOWDOWN_STRENGTH 0.85 //multiplier +//Shove disarming item list +GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( + /obj/item/gun))) + + +//Combat object defines + +//Embedded objects +#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) +#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) +#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) +#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) +#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) +#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) +#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) +#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) +#define EMBEDDED_UNSAFE_REMOVAL_TIME 30 //A Time in ticks, total removal time = (this*item.w_class) +#define EMBEDDED_JOSTLE_CHANCE 5 //Chance for embedded objects to cause pain every time they move (jostle) +#define EMBEDDED_JOSTLE_PAIN_MULTIPLIER 1 //Coefficient of multiplication for the damage the item does while +#define EMBEDDED_PAIN_STAM_PCT 0.0 //This percentage of all pain will be dealt as stam damage rather than brute (0-1) + +#define EMBED_HARMLESS list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE) +#define EMBED_HARMLESS_SUPERIOR list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 100, "fall_chance" = 0.1) +#define EMBED_POINTY list("ignore_throwspeed_threshold" = TRUE) +#define EMBED_POINTY_SUPERIOR list("embed_chance" = 100, "ignore_throwspeed_threshold" = TRUE) + +//Gun weapon weight +#define WEAPON_LIGHT 1 +#define WEAPON_MEDIUM 2 +#define WEAPON_HEAVY 3 +//Gun trigger guards +#define TRIGGER_GUARD_ALLOW_ALL -1 +#define TRIGGER_GUARD_NONE 0 +#define TRIGGER_GUARD_NORMAL 1 +//Gun bolt types +///Gun has a bolt, it stays closed while not cycling. The gun must be racked to have a bullet chambered when a mag is inserted. +/// Example: c20, shotguns, m90 +#define BOLT_TYPE_STANDARD 1 +///Gun has a bolt, it is open when ready to fire. The gun can never have a chambered bullet with no magazine, but the bolt stays ready when a mag is removed. +/// Example: Some SMGs, the L6 +#define BOLT_TYPE_OPEN 2 +///Gun has no moving bolt mechanism, it cannot be racked. Also dumps the entire contents when emptied instead of a magazine. +/// Example: Break action shotguns, revolvers +#define BOLT_TYPE_NO_BOLT 3 +///Gun has a bolt, it locks back when empty. It can be released to chamber a round if a magazine is in. +/// Example: Pistols with a slide lock, some SMGs +#define BOLT_TYPE_LOCKING 4 +//Sawn off nerfs +///accuracy penalty of sawn off guns +#define SAWN_OFF_ACC_PENALTY 25 +///added recoil of sawn off guns +#define SAWN_OFF_RECOIL 1 + +//ammo box sprite defines +///ammo box will always use provided icon state +#define AMMO_BOX_ONE_SPRITE 0 +///ammo box will have a different state for each bullet; - +#define AMMO_BOX_PER_BULLET 1 +///ammo box will have a different state for full and empty; -max_ammo and -0 +#define AMMO_BOX_FULL_EMPTY 2 + +#define SUPPRESSED_NONE 0 +#define SUPPRESSED_QUIET 1 ///standard suppressed +#define SUPPRESSED_VERY 2 /// no message + +//Projectile Reflect +#define REFLECT_NORMAL (1<<0) +#define REFLECT_FAKEPROJECTILE (1<<1) + +//Object/Item sharpness +#define IS_BLUNT 0 +#define IS_SHARP 1 +#define IS_SHARP_ACCURATE 2 + +//His Grace. +#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. +#define HIS_GRACE_PECKISH 20 //Slightly hungry. +#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. +#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. +#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. +#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. +#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. + +#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. + +#define EXPLODE_NONE 0 //Don't even ask me why we need this. +#define EXPLODE_DEVASTATE 1 +#define EXPLODE_HEAVY 2 +#define EXPLODE_LIGHT 3 +#define EXPLODE_GIB_THRESHOLD 50 //ex_act() with EXPLODE_DEVASTATE severity will gib mobs with less than this much bomb armor + +#define EMP_HEAVY 1 +#define EMP_LIGHT 2 + +#define GRENADE_CLUMSY_FUMBLE 1 +#define GRENADE_NONCLUMSY_FUMBLE 2 +#define GRENADE_NO_FUMBLE 3 + +#define BODY_ZONE_HEAD "head" +#define BODY_ZONE_CHEST "chest" +#define BODY_ZONE_L_ARM "l_arm" +#define BODY_ZONE_R_ARM "r_arm" +#define BODY_ZONE_L_LEG "l_leg" +#define BODY_ZONE_R_LEG "r_leg" + +#define BODY_ZONE_PRECISE_EYES "eyes" +#define BODY_ZONE_PRECISE_MOUTH "mouth" +#define BODY_ZONE_PRECISE_GROIN "groin" +#define BODY_ZONE_PRECISE_L_HAND "l_hand" +#define BODY_ZONE_PRECISE_R_HAND "r_hand" +#define BODY_ZONE_PRECISE_L_FOOT "l_foot" +#define BODY_ZONE_PRECISE_R_FOOT "r_foot" + +//We will round to this value in damage calculations. +#define DAMAGE_PRECISION 0.1 + +//bullet_act() return values +#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. +#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. + +#define NICE_SHOT_RICOCHET_BONUS 10 //if the shooter has the NICE_SHOT trait and they fire a ricocheting projectile, add this to the ricochet chance and auto aim angle diff --git a/code/__DEFINES/configuration.dm b/code/__DEFINES/configuration.dm index c724c358dbf..3fbc6d098f3 100644 --- a/code/__DEFINES/configuration.dm +++ b/code/__DEFINES/configuration.dm @@ -1,11 +1,11 @@ -//config files -#define CONFIG_GET(X) global.config.Get(/datum/config_entry/##X) -#define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y) - -#define CONFIG_MAPS_FILE "maps.txt" - -//flags -/// can't edit -#define CONFIG_ENTRY_LOCKED 1 -/// can't see value -#define CONFIG_ENTRY_HIDDEN 2 +//config files +#define CONFIG_GET(X) global.config.Get(/datum/config_entry/##X) +#define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y) + +#define CONFIG_MAPS_FILE "maps.txt" + +//flags +/// can't edit +#define CONFIG_ENTRY_LOCKED 1 +/// can't see value +#define CONFIG_ENTRY_HIDDEN 2 diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm index 7ee9f68d9da..2510db0fbfe 100644 --- a/code/__DEFINES/cooldowns.dm +++ b/code/__DEFINES/cooldowns.dm @@ -1,71 +1,71 @@ -//// COOLDOWN SYSTEMS -/* - * We have 2 cooldown systems: timer cooldowns (divided between stoppable and regular) and world.time cooldowns. - * - * When to use each? - * - * * Adding a commonly-checked cooldown, like on a subsystem to check for processing - * * * Use the world.time ones, as they are cheaper. - * - * * Adding a rarely-used one for special situations, such as giving an uncommon item a cooldown on a target. - * * * Timer cooldown, as adding a new variable on each mob to track the cooldown of said uncommon item is going too far. - * - * * Triggering events at the end of a cooldown. - * * * Timer cooldown, registering to its signal. - * - * * Being able to check how long left for the cooldown to end. - * * * Either world.time or stoppable timer cooldowns, depending on the other factors. Regular timer cooldowns do not support this. - * - * * Being able to stop the timer before it ends. - * * * Either world.time or stoppable timer cooldowns, depending on the other factors. Regular timer cooldowns do not support this. -*/ - - -/* - * Cooldown system based on an datum-level associative lazylist using timers. -*/ - -//INDEXES -#define COOLDOWN_BORG_SELF_REPAIR "borg_self_repair" - - -//TIMER COOLDOWN MACROS - -#define COMSIG_CD_STOP(cd_index) "cooldown_[cd_index]" -#define COMSIG_CD_RESET(cd_index) "cd_reset_[cd_index]" - -#define TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, /proc/end_cooldown, cd_source, cd_index), cd_time)) - -#define TIMER_COOLDOWN_CHECK(cd_source, cd_index) LAZYACCESS(cd_source.cooldowns, cd_index) - -#define TIMER_COOLDOWN_END(cd_source, cd_index) LAZYREMOVE(cd_source.cooldowns, cd_index) - -/* - * Stoppable timer cooldowns. - * Use indexes the same as the regular tiemr cooldowns. - * They make use of the TIMER_COOLDOWN_CHECK() and TIMER_COOLDOWN_END() macros the same, just not the TIMER_COOLDOWN_START() one. - * A bit more expensive than the regular timers, but can be reset before they end and the time left can be checked. -*/ - -#define S_TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, /proc/end_cooldown, cd_source, cd_index), cd_time, TIMER_STOPPABLE)) - -#define S_TIMER_COOLDOWN_RESET(cd_source, cd_index) reset_cooldown(cd_source, cd_index) - -#define S_TIMER_COOLDOWN_TIMELEFT(cd_source, cd_index) (timeleft(TIMER_COOLDOWN_CHECK(cd_source, cd_index))) - - -/* - * Cooldown system based on storing world.time on a variable, plus the cooldown time. - * Better performance over timer cooldowns, lower control. Same functionality. -*/ - -#define COOLDOWN_DECLARE(cd_index) var/##cd_index = 0 - -#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + cd_time) - -//Returns true if the cooldown has run its course, false otherwise -#define COOLDOWN_FINISHED(cd_source, cd_index) (cd_source.cd_index < world.time) - -#define COOLDOWN_RESET(cd_source, cd_index) cd_source.cd_index = 0 - -#define COOLDOWN_TIMELEFT(cd_source, cd_index) (max(0, cd_source.cd_index - world.time)) +//// COOLDOWN SYSTEMS +/* + * We have 2 cooldown systems: timer cooldowns (divided between stoppable and regular) and world.time cooldowns. + * + * When to use each? + * + * * Adding a commonly-checked cooldown, like on a subsystem to check for processing + * * * Use the world.time ones, as they are cheaper. + * + * * Adding a rarely-used one for special situations, such as giving an uncommon item a cooldown on a target. + * * * Timer cooldown, as adding a new variable on each mob to track the cooldown of said uncommon item is going too far. + * + * * Triggering events at the end of a cooldown. + * * * Timer cooldown, registering to its signal. + * + * * Being able to check how long left for the cooldown to end. + * * * Either world.time or stoppable timer cooldowns, depending on the other factors. Regular timer cooldowns do not support this. + * + * * Being able to stop the timer before it ends. + * * * Either world.time or stoppable timer cooldowns, depending on the other factors. Regular timer cooldowns do not support this. +*/ + + +/* + * Cooldown system based on an datum-level associative lazylist using timers. +*/ + +//INDEXES +#define COOLDOWN_BORG_SELF_REPAIR "borg_self_repair" + + +//TIMER COOLDOWN MACROS + +#define COMSIG_CD_STOP(cd_index) "cooldown_[cd_index]" +#define COMSIG_CD_RESET(cd_index) "cd_reset_[cd_index]" + +#define TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, /proc/end_cooldown, cd_source, cd_index), cd_time)) + +#define TIMER_COOLDOWN_CHECK(cd_source, cd_index) LAZYACCESS(cd_source.cooldowns, cd_index) + +#define TIMER_COOLDOWN_END(cd_source, cd_index) LAZYREMOVE(cd_source.cooldowns, cd_index) + +/* + * Stoppable timer cooldowns. + * Use indexes the same as the regular tiemr cooldowns. + * They make use of the TIMER_COOLDOWN_CHECK() and TIMER_COOLDOWN_END() macros the same, just not the TIMER_COOLDOWN_START() one. + * A bit more expensive than the regular timers, but can be reset before they end and the time left can be checked. +*/ + +#define S_TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, /proc/end_cooldown, cd_source, cd_index), cd_time, TIMER_STOPPABLE)) + +#define S_TIMER_COOLDOWN_RESET(cd_source, cd_index) reset_cooldown(cd_source, cd_index) + +#define S_TIMER_COOLDOWN_TIMELEFT(cd_source, cd_index) (timeleft(TIMER_COOLDOWN_CHECK(cd_source, cd_index))) + + +/* + * Cooldown system based on storing world.time on a variable, plus the cooldown time. + * Better performance over timer cooldowns, lower control. Same functionality. +*/ + +#define COOLDOWN_DECLARE(cd_index) var/##cd_index = 0 + +#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + cd_time) + +//Returns true if the cooldown has run its course, false otherwise +#define COOLDOWN_FINISHED(cd_source, cd_index) (cd_source.cd_index < world.time) + +#define COOLDOWN_RESET(cd_source, cd_index) cd_source.cd_index = 0 + +#define COOLDOWN_TIMELEFT(cd_source, cd_index) (max(0, cd_source.cd_index - world.time)) diff --git a/code/__DEFINES/forensics.dm b/code/__DEFINES/forensics.dm index f6121482dab..8d0647cb1ed 100644 --- a/code/__DEFINES/forensics.dm +++ b/code/__DEFINES/forensics.dm @@ -1 +1 @@ -#define HAS_BLOOD_DNA(thing) (length(thing.GetComponent(/datum/component/forensics)?.blood_DNA)) +#define HAS_BLOOD_DNA(thing) (length(thing.GetComponent(/datum/component/forensics)?.blood_DNA)) diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 0d6ec96021d..58d465d70aa 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -1,15 +1,15 @@ -//HUD styles. Index order defines how they are cycled in F12. -/// Standard hud -#define HUD_STYLE_STANDARD 1 -/// Reduced hud (just hands and intent switcher) -#define HUD_STYLE_REDUCED 2 -/// No hud (for screenshots) -#define HUD_STYLE_NOHUD 3 - -/// Used in show_hud(); Please ensure this is the same as the maximum index. -#define HUD_VERSIONS 3 - -//1:1 HUD layout stuff -#define UI_BOXCRAFT "EAST-4:22,SOUTH+1:6" -#define UI_BOXAREA "EAST-4:6,SOUTH+1:6" -#define UI_BOXLANG "EAST-5:22,SOUTH+1:6" +//HUD styles. Index order defines how they are cycled in F12. +/// Standard hud +#define HUD_STYLE_STANDARD 1 +/// Reduced hud (just hands and intent switcher) +#define HUD_STYLE_REDUCED 2 +/// No hud (for screenshots) +#define HUD_STYLE_NOHUD 3 + +/// Used in show_hud(); Please ensure this is the same as the maximum index. +#define HUD_VERSIONS 3 + +//1:1 HUD layout stuff +#define UI_BOXCRAFT "EAST-4:22,SOUTH+1:6" +#define UI_BOXAREA "EAST-4:6,SOUTH+1:6" +#define UI_BOXLANG "EAST-5:22,SOUTH+1:6" diff --git a/code/__DEFINES/interaction_flags.dm b/code/__DEFINES/interaction_flags.dm index b9aaa3e6391..a85ffb8d7a0 100644 --- a/code/__DEFINES/interaction_flags.dm +++ b/code/__DEFINES/interaction_flags.dm @@ -1,38 +1,38 @@ -/// whether can_interact() checks for anchored. only works on movables. -#define INTERACT_ATOM_REQUIRES_ANCHORED (1<<0) -/// calls try_interact() on attack_hand() and returns that. -#define INTERACT_ATOM_ATTACK_HAND (1<<1) -/// automatically calls and returns ui_interact() on interact(). -#define INTERACT_ATOM_UI_INTERACT (1<<2) -/// user must be dextrous -#define INTERACT_ATOM_REQUIRES_DEXTERITY (1<<3) -/// ignores incapacitated check -#define INTERACT_ATOM_IGNORE_INCAPACITATED (1<<4) -/// incapacitated check ignores restrained -#define INTERACT_ATOM_IGNORE_RESTRAINED (1<<5) -/// incapacitated check checks grab -#define INTERACT_ATOM_CHECK_GRAB (1<<6) -/// prevents leaving fingerprints automatically on attack_hand -#define INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND (1<<7) -/// adds hiddenprints instead of fingerprints on interact -#define INTERACT_ATOM_NO_FINGERPRINT_INTERACT (1<<8) - -/// attempt pickup on attack_hand for items -#define INTERACT_ITEM_ATTACK_HAND_PICKUP (1<<0) - -/// can_interact() while open -#define INTERACT_MACHINE_OPEN (1<<0) -/// can_interact() while offline -#define INTERACT_MACHINE_OFFLINE (1<<1) -/// try to interact with wires if open -#define INTERACT_MACHINE_WIRES_IF_OPEN (1<<2) -/// let silicons interact -#define INTERACT_MACHINE_ALLOW_SILICON (1<<3) -/// let silicons interact while open -#define INTERACT_MACHINE_OPEN_SILICON (1<<4) -/// must be silicon to interact -#define INTERACT_MACHINE_REQUIRES_SILICON (1<<5) -/// MACHINES HAVE THIS BY DEFAULT, SOMEONE SHOULD RUN THROUGH MACHINES AND REMOVE IT FROM THINGS LIKE LIGHT SWITCHES WHEN POSSIBLE!!-------------------------- -/// This flag determines if a machine set_machine's the user when the user uses it, making updateUsrDialog make the user re-call interact() on it. -/// THIS FLAG IS ON ALL MACHINES BY DEFAULT, NEEDS TO BE RE-EVALUATED LATER!! -#define INTERACT_MACHINE_SET_MACHINE (1<<6) +/// whether can_interact() checks for anchored. only works on movables. +#define INTERACT_ATOM_REQUIRES_ANCHORED (1<<0) +/// calls try_interact() on attack_hand() and returns that. +#define INTERACT_ATOM_ATTACK_HAND (1<<1) +/// automatically calls and returns ui_interact() on interact(). +#define INTERACT_ATOM_UI_INTERACT (1<<2) +/// user must be dextrous +#define INTERACT_ATOM_REQUIRES_DEXTERITY (1<<3) +/// ignores incapacitated check +#define INTERACT_ATOM_IGNORE_INCAPACITATED (1<<4) +/// incapacitated check ignores restrained +#define INTERACT_ATOM_IGNORE_RESTRAINED (1<<5) +/// incapacitated check checks grab +#define INTERACT_ATOM_CHECK_GRAB (1<<6) +/// prevents leaving fingerprints automatically on attack_hand +#define INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND (1<<7) +/// adds hiddenprints instead of fingerprints on interact +#define INTERACT_ATOM_NO_FINGERPRINT_INTERACT (1<<8) + +/// attempt pickup on attack_hand for items +#define INTERACT_ITEM_ATTACK_HAND_PICKUP (1<<0) + +/// can_interact() while open +#define INTERACT_MACHINE_OPEN (1<<0) +/// can_interact() while offline +#define INTERACT_MACHINE_OFFLINE (1<<1) +/// try to interact with wires if open +#define INTERACT_MACHINE_WIRES_IF_OPEN (1<<2) +/// let silicons interact +#define INTERACT_MACHINE_ALLOW_SILICON (1<<3) +/// let silicons interact while open +#define INTERACT_MACHINE_OPEN_SILICON (1<<4) +/// must be silicon to interact +#define INTERACT_MACHINE_REQUIRES_SILICON (1<<5) +/// MACHINES HAVE THIS BY DEFAULT, SOMEONE SHOULD RUN THROUGH MACHINES AND REMOVE IT FROM THINGS LIKE LIGHT SWITCHES WHEN POSSIBLE!!-------------------------- +/// This flag determines if a machine set_machine's the user when the user uses it, making updateUsrDialog make the user re-call interact() on it. +/// THIS FLAG IS ON ALL MACHINES BY DEFAULT, NEEDS TO BE RE-EVALUATED LATER!! +#define INTERACT_MACHINE_SET_MACHINE (1<<6) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 5bcfa54e907..d108e4eb337 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -1,94 +1,94 @@ - -#define ENGSEC (1<<0) - -#define CAPTAIN (1<<0) -#define HOS (1<<1) -#define WARDEN (1<<2) -#define DETECTIVE (1<<3) -#define OFFICER (1<<4) -#define CHIEF (1<<5) -#define ENGINEER (1<<6) -#define ATMOSTECH (1<<7) -#define ROBOTICIST (1<<8) -#define AI_JF (1<<9) -#define CYBORG (1<<10) - - -#define MEDSCI (1<<1) - -#define RD_JF (1<<0) -#define SCIENTIST (1<<1) -#define CHEMIST (1<<2) -#define CMO_JF (1<<3) -#define DOCTOR (1<<4) -#define GENETICIST (1<<5) -#define VIROLOGIST (1<<6) -#define PARAMEDIC (1<<7) - - -#define CIVILIAN (1<<2) - -#define HOP (1<<0) -#define BARTENDER (1<<1) -#define BOTANIST (1<<2) -#define COOK (1<<3) -#define JANITOR (1<<4) -#define CURATOR (1<<5) -#define QUARTERMASTER (1<<6) -#define CARGOTECH (1<<7) -#define MINER (1<<8) -#define LAWYER (1<<9) -#define CHAPLAIN (1<<10) -#define CLOWN (1<<11) -#define MIME (1<<12) -#define ASSISTANT (1<<13) -#define PRISONER (1<<14) -#define PSYCHOLOGIST (1<<15) - -#define JOB_AVAILABLE 0 -#define JOB_UNAVAILABLE_GENERIC 1 -#define JOB_UNAVAILABLE_BANNED 2 -#define JOB_UNAVAILABLE_PLAYTIME 3 -#define JOB_UNAVAILABLE_ACCOUNTAGE 4 -#define JOB_UNAVAILABLE_SLOTFULL 5 - -#define DEFAULT_RELIGION "Christianity" -#define DEFAULT_DEITY "Space Jesus" - -#define JOB_DISPLAY_ORDER_DEFAULT 0 - -#define JOB_DISPLAY_ORDER_ASSISTANT 1 -#define JOB_DISPLAY_ORDER_CAPTAIN 2 -#define JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL 3 -#define JOB_DISPLAY_ORDER_QUARTERMASTER 4 -#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 5 -#define JOB_DISPLAY_ORDER_SHAFT_MINER 6 -#define JOB_DISPLAY_ORDER_BARTENDER 7 -#define JOB_DISPLAY_ORDER_COOK 8 -#define JOB_DISPLAY_ORDER_BOTANIST 9 -#define JOB_DISPLAY_ORDER_JANITOR 10 -#define JOB_DISPLAY_ORDER_CLOWN 11 -#define JOB_DISPLAY_ORDER_MIME 12 -#define JOB_DISPLAY_ORDER_CURATOR 13 -#define JOB_DISPLAY_ORDER_LAWYER 14 -#define JOB_DISPLAY_ORDER_CHAPLAIN 15 -#define JOB_DISPLAY_ORDER_AI 16 -#define JOB_DISPLAY_ORDER_CYBORG 17 -#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 18 -#define JOB_DISPLAY_ORDER_STATION_ENGINEER 19 -#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 20 -#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 21 -#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 22 -#define JOB_DISPLAY_ORDER_PARAMEDIC 23 -#define JOB_DISPLAY_ORDER_CHEMIST 24 -#define JOB_DISPLAY_ORDER_VIROLOGIST 25 -#define JOB_DISPLAY_ORDER_PSYCHOLOGIST 26 -#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 27 -#define JOB_DISPLAY_ORDER_SCIENTIST 28 -#define JOB_DISPLAY_ORDER_ROBOTICIST 29 -#define JOB_DISPLAY_ORDER_GENETICIST 30 -#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 31 -#define JOB_DISPLAY_ORDER_WARDEN 32 -#define JOB_DISPLAY_ORDER_DETECTIVE 33 -#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 34 -#define JOB_DISPLAY_ORDER_PRISONER 35 + +#define ENGSEC (1<<0) + +#define CAPTAIN (1<<0) +#define HOS (1<<1) +#define WARDEN (1<<2) +#define DETECTIVE (1<<3) +#define OFFICER (1<<4) +#define CHIEF (1<<5) +#define ENGINEER (1<<6) +#define ATMOSTECH (1<<7) +#define ROBOTICIST (1<<8) +#define AI_JF (1<<9) +#define CYBORG (1<<10) + + +#define MEDSCI (1<<1) + +#define RD_JF (1<<0) +#define SCIENTIST (1<<1) +#define CHEMIST (1<<2) +#define CMO_JF (1<<3) +#define DOCTOR (1<<4) +#define GENETICIST (1<<5) +#define VIROLOGIST (1<<6) +#define PARAMEDIC (1<<7) + + +#define CIVILIAN (1<<2) + +#define HOP (1<<0) +#define BARTENDER (1<<1) +#define BOTANIST (1<<2) +#define COOK (1<<3) +#define JANITOR (1<<4) +#define CURATOR (1<<5) +#define QUARTERMASTER (1<<6) +#define CARGOTECH (1<<7) +#define MINER (1<<8) +#define LAWYER (1<<9) +#define CHAPLAIN (1<<10) +#define CLOWN (1<<11) +#define MIME (1<<12) +#define ASSISTANT (1<<13) +#define PRISONER (1<<14) +#define PSYCHOLOGIST (1<<15) + +#define JOB_AVAILABLE 0 +#define JOB_UNAVAILABLE_GENERIC 1 +#define JOB_UNAVAILABLE_BANNED 2 +#define JOB_UNAVAILABLE_PLAYTIME 3 +#define JOB_UNAVAILABLE_ACCOUNTAGE 4 +#define JOB_UNAVAILABLE_SLOTFULL 5 + +#define DEFAULT_RELIGION "Christianity" +#define DEFAULT_DEITY "Space Jesus" + +#define JOB_DISPLAY_ORDER_DEFAULT 0 + +#define JOB_DISPLAY_ORDER_ASSISTANT 1 +#define JOB_DISPLAY_ORDER_CAPTAIN 2 +#define JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL 3 +#define JOB_DISPLAY_ORDER_QUARTERMASTER 4 +#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 5 +#define JOB_DISPLAY_ORDER_SHAFT_MINER 6 +#define JOB_DISPLAY_ORDER_BARTENDER 7 +#define JOB_DISPLAY_ORDER_COOK 8 +#define JOB_DISPLAY_ORDER_BOTANIST 9 +#define JOB_DISPLAY_ORDER_JANITOR 10 +#define JOB_DISPLAY_ORDER_CLOWN 11 +#define JOB_DISPLAY_ORDER_MIME 12 +#define JOB_DISPLAY_ORDER_CURATOR 13 +#define JOB_DISPLAY_ORDER_LAWYER 14 +#define JOB_DISPLAY_ORDER_CHAPLAIN 15 +#define JOB_DISPLAY_ORDER_AI 16 +#define JOB_DISPLAY_ORDER_CYBORG 17 +#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 18 +#define JOB_DISPLAY_ORDER_STATION_ENGINEER 19 +#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 20 +#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 21 +#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 22 +#define JOB_DISPLAY_ORDER_PARAMEDIC 23 +#define JOB_DISPLAY_ORDER_CHEMIST 24 +#define JOB_DISPLAY_ORDER_VIROLOGIST 25 +#define JOB_DISPLAY_ORDER_PSYCHOLOGIST 26 +#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 27 +#define JOB_DISPLAY_ORDER_SCIENTIST 28 +#define JOB_DISPLAY_ORDER_ROBOTICIST 29 +#define JOB_DISPLAY_ORDER_GENETICIST 30 +#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 31 +#define JOB_DISPLAY_ORDER_WARDEN 32 +#define JOB_DISPLAY_ORDER_DETECTIVE 33 +#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 34 +#define JOB_DISPLAY_ORDER_PRISONER 35 diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index 14f7ba9a08b..1c331af1ef5 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -1,128 +1,128 @@ -// channel numbers for power -// These are indexes in a list, and indexes for "dynamic" and static channels should be kept contiguous -#define AREA_USAGE_EQUIP 1 -#define AREA_USAGE_LIGHT 2 -#define AREA_USAGE_ENVIRON 3 -#define AREA_USAGE_STATIC_EQUIP 4 -#define AREA_USAGE_STATIC_LIGHT 5 -#define AREA_USAGE_STATIC_ENVIRON 6 -#define AREA_USAGE_LEN AREA_USAGE_STATIC_ENVIRON // largest idx -/// Index of the first dynamic usage channel -#define AREA_USAGE_DYNAMIC_START AREA_USAGE_EQUIP -/// Index of the last dynamic usage channel -#define AREA_USAGE_DYNAMIC_END AREA_USAGE_ENVIRON -/// Index of the first static usage channel -#define AREA_USAGE_STATIC_START AREA_USAGE_STATIC_EQUIP -/// Index of the last static usage channel -#define AREA_USAGE_STATIC_END AREA_USAGE_STATIC_ENVIRON - - -//Power use -#define NO_POWER_USE 0 -#define IDLE_POWER_USE 1 -#define ACTIVE_POWER_USE 2 - -/// Bitflags for a machine's preferences on when it should start processing. For use with machinery's `processing_flags` var. -#define START_PROCESSING_ON_INIT (1<<0) /// Indicates the machine will automatically start processing right after it's `Initialize()` is ran. -#define START_PROCESSING_MANUALLY (1<<1) /// Machines with this flag will not start processing when it's spawned. Use this if you want to manually control when a machine starts processing. - -//bitflags for door switches. -#define OPEN (1<<0) -#define IDSCAN (1<<1) -#define BOLTS (1<<2) -#define SHOCK (1<<3) -#define SAFE (1<<4) - -//used in design to specify which machine can build it -#define IMPRINTER (1<<0) //For circuits. Uses glass/chemicals. -#define PROTOLATHE (1<<1) //New stuff. Uses glass/metal/chemicals -#define AUTOLATHE (1<<2) //Uses glass/metal only. -#define CRAFTLATHE (1<<3) //Uses fuck if I know. For use eventually. -#define MECHFAB (1<<4) //Remember, objects utilising this flag should have construction_time and construction_cost vars. -#define BIOGENERATOR (1<<5) //Uses biomass -#define LIMBGROWER (1<<6) //Uses synthetic flesh -#define SMELTER (1<<7) //uses various minerals -#define NANITE_COMPILER (1<<8) //Prints nanite disks -//Note: More than one of these can be added to a design but imprinter and lathe designs are incompatable. - -//Modular computer/NTNet defines - -//Modular computer part defines -#define MC_CPU "CPU" -#define MC_HDD "HDD" -#define MC_SDD "SDD" -#define MC_CARD "CARD" -#define MC_NET "NET" -#define MC_PRINT "PRINT" -#define MC_CELL "CELL" -#define MC_CHARGE "CHARGE" -#define MC_AI "AI" - -//NTNet stuff, for modular computers - // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) -#define NTNET_SOFTWAREDOWNLOAD 1 // Downloads of software from NTNet -#define NTNET_PEERTOPEER 2 // P2P transfers of files between devices -#define NTNET_COMMUNICATION 3 // Communication (messaging) -#define NTNET_SYSTEMCONTROL 4 // Control of various systems, RCon, air alarm control, etc. - -//NTNet transfer speeds, used when downloading/uploading a file/program. -#define NTNETSPEED_LOWSIGNAL 0.5 // GQ/s transfer speed when the device is wirelessly connected and on Low signal -#define NTNETSPEED_HIGHSIGNAL 1 // GQ/s transfer speed when the device is wirelessly connected and on High signal -#define NTNETSPEED_ETHERNET 2 // GQ/s transfer speed when the device is using wired connection - -//Caps for NTNet logging. Less than 10 would make logging useless anyway, more than 500 may make the log browser too laggy. Defaults to 100 unless user changes it. -#define MAX_NTNET_LOGS 300 -#define MIN_NTNET_LOGS 10 - -//Program bitflags -#define PROGRAM_ALL (~0) -#define PROGRAM_CONSOLE (1<<0) -#define PROGRAM_LAPTOP (1<<1) -#define PROGRAM_TABLET (1<<2) -//Program states -#define PROGRAM_STATE_KILLED 0 -#define PROGRAM_STATE_BACKGROUND 1 -#define PROGRAM_STATE_ACTIVE 2 - -#define FIREDOOR_OPEN 1 -#define FIREDOOR_CLOSED 2 - - - -// These are used by supermatter and supermatter monitor program, mostly for UI updating purposes. Higher should always be worse! -#define SUPERMATTER_ERROR -1 // Unknown status, shouldn't happen but just in case. -#define SUPERMATTER_INACTIVE 0 // No or minimal energy -#define SUPERMATTER_NORMAL 1 // Normal operation -#define SUPERMATTER_NOTIFY 2 // Ambient temp > 80% of CRITICAL_TEMPERATURE -#define SUPERMATTER_WARNING 3 // Ambient temp > CRITICAL_TEMPERATURE OR integrity damaged -#define SUPERMATTER_DANGER 4 // Integrity < 50% -#define SUPERMATTER_EMERGENCY 5 // Integrity < 25% -#define SUPERMATTER_DELAMINATING 6 // Pretty obvious. - -//Nuclear bomb stuff -#define NUKESTATE_INTACT 5 -#define NUKESTATE_UNSCREWED 4 -#define NUKESTATE_PANEL_REMOVED 3 -#define NUKESTATE_WELDED 2 -#define NUKESTATE_CORE_EXPOSED 1 -#define NUKESTATE_CORE_REMOVED 0 - -#define NUKEUI_AWAIT_DISK 0 -#define NUKEUI_AWAIT_CODE 1 -#define NUKEUI_AWAIT_TIMER 2 -#define NUKEUI_AWAIT_ARM 3 -#define NUKEUI_TIMING 4 -#define NUKEUI_EXPLODED 5 - -#define NUKE_OFF_LOCKED 0 -#define NUKE_OFF_UNLOCKED 1 -#define NUKE_ON_TIMING 2 -#define NUKE_ON_EXPLODING 3 - -#define MACHINE_NOT_ELECTRIFIED 0 -#define MACHINE_ELECTRIFIED_PERMANENT -1 -#define MACHINE_DEFAULT_ELECTRIFY_TIME 30 - -//these flags are used to tell the DNA modifier if a plant gene cannot be extracted or modified. -#define PLANT_GENE_REMOVABLE (1<<0) -#define PLANT_GENE_EXTRACTABLE (1<<1) +// channel numbers for power +// These are indexes in a list, and indexes for "dynamic" and static channels should be kept contiguous +#define AREA_USAGE_EQUIP 1 +#define AREA_USAGE_LIGHT 2 +#define AREA_USAGE_ENVIRON 3 +#define AREA_USAGE_STATIC_EQUIP 4 +#define AREA_USAGE_STATIC_LIGHT 5 +#define AREA_USAGE_STATIC_ENVIRON 6 +#define AREA_USAGE_LEN AREA_USAGE_STATIC_ENVIRON // largest idx +/// Index of the first dynamic usage channel +#define AREA_USAGE_DYNAMIC_START AREA_USAGE_EQUIP +/// Index of the last dynamic usage channel +#define AREA_USAGE_DYNAMIC_END AREA_USAGE_ENVIRON +/// Index of the first static usage channel +#define AREA_USAGE_STATIC_START AREA_USAGE_STATIC_EQUIP +/// Index of the last static usage channel +#define AREA_USAGE_STATIC_END AREA_USAGE_STATIC_ENVIRON + + +//Power use +#define NO_POWER_USE 0 +#define IDLE_POWER_USE 1 +#define ACTIVE_POWER_USE 2 + +/// Bitflags for a machine's preferences on when it should start processing. For use with machinery's `processing_flags` var. +#define START_PROCESSING_ON_INIT (1<<0) /// Indicates the machine will automatically start processing right after it's `Initialize()` is ran. +#define START_PROCESSING_MANUALLY (1<<1) /// Machines with this flag will not start processing when it's spawned. Use this if you want to manually control when a machine starts processing. + +//bitflags for door switches. +#define OPEN (1<<0) +#define IDSCAN (1<<1) +#define BOLTS (1<<2) +#define SHOCK (1<<3) +#define SAFE (1<<4) + +//used in design to specify which machine can build it +#define IMPRINTER (1<<0) //For circuits. Uses glass/chemicals. +#define PROTOLATHE (1<<1) //New stuff. Uses glass/metal/chemicals +#define AUTOLATHE (1<<2) //Uses glass/metal only. +#define CRAFTLATHE (1<<3) //Uses fuck if I know. For use eventually. +#define MECHFAB (1<<4) //Remember, objects utilising this flag should have construction_time and construction_cost vars. +#define BIOGENERATOR (1<<5) //Uses biomass +#define LIMBGROWER (1<<6) //Uses synthetic flesh +#define SMELTER (1<<7) //uses various minerals +#define NANITE_COMPILER (1<<8) //Prints nanite disks +//Note: More than one of these can be added to a design but imprinter and lathe designs are incompatable. + +//Modular computer/NTNet defines + +//Modular computer part defines +#define MC_CPU "CPU" +#define MC_HDD "HDD" +#define MC_SDD "SDD" +#define MC_CARD "CARD" +#define MC_NET "NET" +#define MC_PRINT "PRINT" +#define MC_CELL "CELL" +#define MC_CHARGE "CHARGE" +#define MC_AI "AI" + +//NTNet stuff, for modular computers + // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) +#define NTNET_SOFTWAREDOWNLOAD 1 // Downloads of software from NTNet +#define NTNET_PEERTOPEER 2 // P2P transfers of files between devices +#define NTNET_COMMUNICATION 3 // Communication (messaging) +#define NTNET_SYSTEMCONTROL 4 // Control of various systems, RCon, air alarm control, etc. + +//NTNet transfer speeds, used when downloading/uploading a file/program. +#define NTNETSPEED_LOWSIGNAL 0.5 // GQ/s transfer speed when the device is wirelessly connected and on Low signal +#define NTNETSPEED_HIGHSIGNAL 1 // GQ/s transfer speed when the device is wirelessly connected and on High signal +#define NTNETSPEED_ETHERNET 2 // GQ/s transfer speed when the device is using wired connection + +//Caps for NTNet logging. Less than 10 would make logging useless anyway, more than 500 may make the log browser too laggy. Defaults to 100 unless user changes it. +#define MAX_NTNET_LOGS 300 +#define MIN_NTNET_LOGS 10 + +//Program bitflags +#define PROGRAM_ALL (~0) +#define PROGRAM_CONSOLE (1<<0) +#define PROGRAM_LAPTOP (1<<1) +#define PROGRAM_TABLET (1<<2) +//Program states +#define PROGRAM_STATE_KILLED 0 +#define PROGRAM_STATE_BACKGROUND 1 +#define PROGRAM_STATE_ACTIVE 2 + +#define FIREDOOR_OPEN 1 +#define FIREDOOR_CLOSED 2 + + + +// These are used by supermatter and supermatter monitor program, mostly for UI updating purposes. Higher should always be worse! +#define SUPERMATTER_ERROR -1 // Unknown status, shouldn't happen but just in case. +#define SUPERMATTER_INACTIVE 0 // No or minimal energy +#define SUPERMATTER_NORMAL 1 // Normal operation +#define SUPERMATTER_NOTIFY 2 // Ambient temp > 80% of CRITICAL_TEMPERATURE +#define SUPERMATTER_WARNING 3 // Ambient temp > CRITICAL_TEMPERATURE OR integrity damaged +#define SUPERMATTER_DANGER 4 // Integrity < 50% +#define SUPERMATTER_EMERGENCY 5 // Integrity < 25% +#define SUPERMATTER_DELAMINATING 6 // Pretty obvious. + +//Nuclear bomb stuff +#define NUKESTATE_INTACT 5 +#define NUKESTATE_UNSCREWED 4 +#define NUKESTATE_PANEL_REMOVED 3 +#define NUKESTATE_WELDED 2 +#define NUKESTATE_CORE_EXPOSED 1 +#define NUKESTATE_CORE_REMOVED 0 + +#define NUKEUI_AWAIT_DISK 0 +#define NUKEUI_AWAIT_CODE 1 +#define NUKEUI_AWAIT_TIMER 2 +#define NUKEUI_AWAIT_ARM 3 +#define NUKEUI_TIMING 4 +#define NUKEUI_EXPLODED 5 + +#define NUKE_OFF_LOCKED 0 +#define NUKE_OFF_UNLOCKED 1 +#define NUKE_ON_TIMING 2 +#define NUKE_ON_EXPLODING 3 + +#define MACHINE_NOT_ELECTRIFIED 0 +#define MACHINE_ELECTRIFIED_PERMANENT -1 +#define MACHINE_DEFAULT_ELECTRIFY_TIME 30 + +//these flags are used to tell the DNA modifier if a plant gene cannot be extracted or modified. +#define PLANT_GENE_REMOVABLE (1<<0) +#define PLANT_GENE_EXTRACTABLE (1<<1) diff --git a/code/__DEFINES/move_force.dm b/code/__DEFINES/move_force.dm index 5ab615643af..1f8819b0c85 100644 --- a/code/__DEFINES/move_force.dm +++ b/code/__DEFINES/move_force.dm @@ -1,20 +1,20 @@ -//Defaults -#define MOVE_FORCE_DEFAULT 1000 -#define MOVE_RESIST_DEFAULT 1000 -#define PULL_FORCE_DEFAULT 1000 - -//Factors/modifiers -#define MOVE_FORCE_PULL_RATIO 1 //Same move force to pull objects -#define MOVE_FORCE_PUSH_RATIO 1 //Same move force to normally push -#define MOVE_FORCE_FORCEPUSH_RATIO 2 //2x move force to forcefully push -#define MOVE_FORCE_CRUSH_RATIO 3 //3x move force to do things like crush objects -#define MOVE_FORCE_THROW_RATIO 1 //Same force throw as resist to throw objects - -#define MOVE_FORCE_OVERPOWERING (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 10) -#define MOVE_FORCE_EXTREMELY_STRONG (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 3) -#define MOVE_FORCE_VERY_STRONG ((MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO) - 1) -#define MOVE_FORCE_STRONG (MOVE_FORCE_DEFAULT * 2) -#define MOVE_FORCE_NORMAL MOVE_FORCE_DEFAULT -#define MOVE_FORCE_WEAK (MOVE_FORCE_DEFAULT / 2) -#define MOVE_FORCE_VERY_WEAK ((MOVE_FORCE_DEFAULT / MOVE_FORCE_CRUSH_RATIO) + 1) -#define MOVE_FORCE_EXTREMELY_WEAK (MOVE_FORCE_DEFAULT / (MOVE_FORCE_CRUSH_RATIO * 3)) +//Defaults +#define MOVE_FORCE_DEFAULT 1000 +#define MOVE_RESIST_DEFAULT 1000 +#define PULL_FORCE_DEFAULT 1000 + +//Factors/modifiers +#define MOVE_FORCE_PULL_RATIO 1 //Same move force to pull objects +#define MOVE_FORCE_PUSH_RATIO 1 //Same move force to normally push +#define MOVE_FORCE_FORCEPUSH_RATIO 2 //2x move force to forcefully push +#define MOVE_FORCE_CRUSH_RATIO 3 //3x move force to do things like crush objects +#define MOVE_FORCE_THROW_RATIO 1 //Same force throw as resist to throw objects + +#define MOVE_FORCE_OVERPOWERING (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 10) +#define MOVE_FORCE_EXTREMELY_STRONG (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 3) +#define MOVE_FORCE_VERY_STRONG ((MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO) - 1) +#define MOVE_FORCE_STRONG (MOVE_FORCE_DEFAULT * 2) +#define MOVE_FORCE_NORMAL MOVE_FORCE_DEFAULT +#define MOVE_FORCE_WEAK (MOVE_FORCE_DEFAULT / 2) +#define MOVE_FORCE_VERY_WEAK ((MOVE_FORCE_DEFAULT / MOVE_FORCE_CRUSH_RATIO) + 1) +#define MOVE_FORCE_EXTREMELY_WEAK (MOVE_FORCE_DEFAULT / (MOVE_FORCE_CRUSH_RATIO * 3)) diff --git a/code/__DEFINES/networks.dm b/code/__DEFINES/networks.dm index 09b9e4cadfc..115c1653493 100644 --- a/code/__DEFINES/networks.dm +++ b/code/__DEFINES/networks.dm @@ -1,3 +1,3 @@ -#define HID_RESTRICTED_END 101 //the first nonrestricted ID, automatically assigned on connection creation. - -#define NETWORK_BROADCAST_ID "ALL" +#define HID_RESTRICTED_END 101 //the first nonrestricted ID, automatically assigned on connection creation. + +#define NETWORK_BROADCAST_ID "ALL" diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 3bf2f330cdd..842fc4c7e99 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -1,120 +1,120 @@ - -//Preference toggles -#define SOUND_ADMINHELP (1<<0) -#define SOUND_MIDI (1<<1) -#define SOUND_AMBIENCE (1<<2) -#define SOUND_LOBBY (1<<3) -#define MEMBER_PUBLIC (1<<4) -#define INTENT_STYLE (1<<5) -#define MIDROUND_ANTAG (1<<6) -#define SOUND_INSTRUMENTS (1<<7) -#define SOUND_SHIP_AMBIENCE (1<<8) -#define SOUND_PRAYERS (1<<9) -#define ANNOUNCE_LOGIN (1<<10) -#define SOUND_ANNOUNCEMENTS (1<<11) -#define DISABLE_DEATHRATTLE (1<<12) -#define DISABLE_ARRIVALRATTLE (1<<13) -#define COMBOHUD_LIGHTING (1<<14) -#define DEADMIN_ALWAYS (1<<15) -#define DEADMIN_ANTAGONIST (1<<16) -#define DEADMIN_POSITION_HEAD (1<<17) -#define DEADMIN_POSITION_SECURITY (1<<18) -#define DEADMIN_POSITION_SILICON (1<<19) -#define SOUND_ENDOFROUND (1<<20) -#define ADMIN_IGNORE_CULT_GHOST (1<<21) - -#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|SOUND_ENDOFROUND|MEMBER_PUBLIC|INTENT_STYLE|MIDROUND_ANTAG|SOUND_INSTRUMENTS|SOUND_SHIP_AMBIENCE|SOUND_PRAYERS|SOUND_ANNOUNCEMENTS) - -//Chat toggles -#define CHAT_OOC (1<<0) -#define CHAT_DEAD (1<<1) -#define CHAT_GHOSTEARS (1<<2) -#define CHAT_GHOSTSIGHT (1<<3) -#define CHAT_PRAYER (1<<4) -#define CHAT_RADIO (1<<5) -#define CHAT_PULLR (1<<6) -#define CHAT_GHOSTWHISPER (1<<7) -#define CHAT_GHOSTPDA (1<<8) -#define CHAT_GHOSTRADIO (1<<9) -#define CHAT_BANKCARD (1<<10) -#define CHAT_GHOSTLAWS (1<<11) - -#define TOGGLES_DEFAULT_CHAT (CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|CHAT_PULLR|CHAT_GHOSTWHISPER|CHAT_GHOSTPDA|CHAT_GHOSTRADIO|CHAT_BANKCARD|CHAT_GHOSTLAWS) - -#define PARALLAX_INSANE -1 //for show offs -#define PARALLAX_HIGH 0 //default. -#define PARALLAX_MED 1 -#define PARALLAX_LOW 2 -#define PARALLAX_DISABLE 3 //this option must be the highest number - -#define PIXEL_SCALING_AUTO 0 -#define PIXEL_SCALING_1X 1 -#define PIXEL_SCALING_1_2X 1.5 -#define PIXEL_SCALING_2X 2 -#define PIXEL_SCALING_3X 3 - -#define SCALING_METHOD_NORMAL "normal" -#define SCALING_METHOD_DISTORT "distort" -#define SCALING_METHOD_BLUR "blur" - -#define PARALLAX_DELAY_DEFAULT world.tick_lag -#define PARALLAX_DELAY_MED 1 -#define PARALLAX_DELAY_LOW 2 - -#define SEC_DEPT_NONE "None" -#define SEC_DEPT_RANDOM "Random" -#define SEC_DEPT_ENGINEERING "Engineering" -#define SEC_DEPT_MEDICAL "Medical" -#define SEC_DEPT_SCIENCE "Science" -#define SEC_DEPT_SUPPLY "Supply" - -// Playtime tracking system, see jobs_exp.dm -#define EXP_TYPE_LIVING "Living" -#define EXP_TYPE_CREW "Crew" -#define EXP_TYPE_COMMAND "Command" -#define EXP_TYPE_ENGINEERING "Engineering" -#define EXP_TYPE_MEDICAL "Medical" -#define EXP_TYPE_SCIENCE "Science" -#define EXP_TYPE_SUPPLY "Supply" -#define EXP_TYPE_SECURITY "Security" -#define EXP_TYPE_SILICON "Silicon" -#define EXP_TYPE_SERVICE "Service" -#define EXP_TYPE_ANTAG "Antag" -#define EXP_TYPE_SPECIAL "Special" -#define EXP_TYPE_GHOST "Ghost" -#define EXP_TYPE_ADMIN "Admin" - -//Flags in the players table in the db -#define DB_FLAG_EXEMPT 1 - -#define DEFAULT_CYBORG_NAME "Default Cyborg Name" - - -//Job preferences levels -#define JP_LOW 1 -#define JP_MEDIUM 2 -#define JP_HIGH 3 - -//randomised elements -#define RANDOM_HARDCORE "random_hardcore" -#define RANDOM_NAME "random_name" -#define RANDOM_NAME_ANTAG "random_name_antag" -#define RANDOM_BODY "random_body" -#define RANDOM_BODY_ANTAG "random_body_antag" -#define RANDOM_SPECIES "random_species" -#define RANDOM_GENDER "random_gender" -#define RANDOM_GENDER_ANTAG "random_gender_antag" -#define RANDOM_AGE "random_age" -#define RANDOM_AGE_ANTAG "random_age_antag" -#define RANDOM_UNDERWEAR "random_underwear" -#define RANDOM_UNDERWEAR_COLOR "random_underwear_color" -#define RANDOM_UNDERSHIRT "random_undershirt" -#define RANDOM_SOCKS "random_socks" -#define RANDOM_BACKPACK "random_backpack" -#define RANDOM_JUMPSUIT_STYLE "random_jumpsuit_style" -#define RANDOM_HAIRSTYLE "random_hairstyle" -#define RANDOM_HAIR_COLOR "random_hair_color" -#define RANDOM_FACIAL_HAIR_COLOR "random_facial_hair_color" -#define RANDOM_FACIAL_HAIRSTYLE "random_facial_hairstyle" -#define RANDOM_SKIN_TONE "random_skin_tone" -#define RANDOM_EYE_COLOR "random_eye_color" + +//Preference toggles +#define SOUND_ADMINHELP (1<<0) +#define SOUND_MIDI (1<<1) +#define SOUND_AMBIENCE (1<<2) +#define SOUND_LOBBY (1<<3) +#define MEMBER_PUBLIC (1<<4) +#define INTENT_STYLE (1<<5) +#define MIDROUND_ANTAG (1<<6) +#define SOUND_INSTRUMENTS (1<<7) +#define SOUND_SHIP_AMBIENCE (1<<8) +#define SOUND_PRAYERS (1<<9) +#define ANNOUNCE_LOGIN (1<<10) +#define SOUND_ANNOUNCEMENTS (1<<11) +#define DISABLE_DEATHRATTLE (1<<12) +#define DISABLE_ARRIVALRATTLE (1<<13) +#define COMBOHUD_LIGHTING (1<<14) +#define DEADMIN_ALWAYS (1<<15) +#define DEADMIN_ANTAGONIST (1<<16) +#define DEADMIN_POSITION_HEAD (1<<17) +#define DEADMIN_POSITION_SECURITY (1<<18) +#define DEADMIN_POSITION_SILICON (1<<19) +#define SOUND_ENDOFROUND (1<<20) +#define ADMIN_IGNORE_CULT_GHOST (1<<21) + +#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|SOUND_ENDOFROUND|MEMBER_PUBLIC|INTENT_STYLE|MIDROUND_ANTAG|SOUND_INSTRUMENTS|SOUND_SHIP_AMBIENCE|SOUND_PRAYERS|SOUND_ANNOUNCEMENTS) + +//Chat toggles +#define CHAT_OOC (1<<0) +#define CHAT_DEAD (1<<1) +#define CHAT_GHOSTEARS (1<<2) +#define CHAT_GHOSTSIGHT (1<<3) +#define CHAT_PRAYER (1<<4) +#define CHAT_RADIO (1<<5) +#define CHAT_PULLR (1<<6) +#define CHAT_GHOSTWHISPER (1<<7) +#define CHAT_GHOSTPDA (1<<8) +#define CHAT_GHOSTRADIO (1<<9) +#define CHAT_BANKCARD (1<<10) +#define CHAT_GHOSTLAWS (1<<11) + +#define TOGGLES_DEFAULT_CHAT (CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|CHAT_PULLR|CHAT_GHOSTWHISPER|CHAT_GHOSTPDA|CHAT_GHOSTRADIO|CHAT_BANKCARD|CHAT_GHOSTLAWS) + +#define PARALLAX_INSANE -1 //for show offs +#define PARALLAX_HIGH 0 //default. +#define PARALLAX_MED 1 +#define PARALLAX_LOW 2 +#define PARALLAX_DISABLE 3 //this option must be the highest number + +#define PIXEL_SCALING_AUTO 0 +#define PIXEL_SCALING_1X 1 +#define PIXEL_SCALING_1_2X 1.5 +#define PIXEL_SCALING_2X 2 +#define PIXEL_SCALING_3X 3 + +#define SCALING_METHOD_NORMAL "normal" +#define SCALING_METHOD_DISTORT "distort" +#define SCALING_METHOD_BLUR "blur" + +#define PARALLAX_DELAY_DEFAULT world.tick_lag +#define PARALLAX_DELAY_MED 1 +#define PARALLAX_DELAY_LOW 2 + +#define SEC_DEPT_NONE "None" +#define SEC_DEPT_RANDOM "Random" +#define SEC_DEPT_ENGINEERING "Engineering" +#define SEC_DEPT_MEDICAL "Medical" +#define SEC_DEPT_SCIENCE "Science" +#define SEC_DEPT_SUPPLY "Supply" + +// Playtime tracking system, see jobs_exp.dm +#define EXP_TYPE_LIVING "Living" +#define EXP_TYPE_CREW "Crew" +#define EXP_TYPE_COMMAND "Command" +#define EXP_TYPE_ENGINEERING "Engineering" +#define EXP_TYPE_MEDICAL "Medical" +#define EXP_TYPE_SCIENCE "Science" +#define EXP_TYPE_SUPPLY "Supply" +#define EXP_TYPE_SECURITY "Security" +#define EXP_TYPE_SILICON "Silicon" +#define EXP_TYPE_SERVICE "Service" +#define EXP_TYPE_ANTAG "Antag" +#define EXP_TYPE_SPECIAL "Special" +#define EXP_TYPE_GHOST "Ghost" +#define EXP_TYPE_ADMIN "Admin" + +//Flags in the players table in the db +#define DB_FLAG_EXEMPT 1 + +#define DEFAULT_CYBORG_NAME "Default Cyborg Name" + + +//Job preferences levels +#define JP_LOW 1 +#define JP_MEDIUM 2 +#define JP_HIGH 3 + +//randomised elements +#define RANDOM_HARDCORE "random_hardcore" +#define RANDOM_NAME "random_name" +#define RANDOM_NAME_ANTAG "random_name_antag" +#define RANDOM_BODY "random_body" +#define RANDOM_BODY_ANTAG "random_body_antag" +#define RANDOM_SPECIES "random_species" +#define RANDOM_GENDER "random_gender" +#define RANDOM_GENDER_ANTAG "random_gender_antag" +#define RANDOM_AGE "random_age" +#define RANDOM_AGE_ANTAG "random_age_antag" +#define RANDOM_UNDERWEAR "random_underwear" +#define RANDOM_UNDERWEAR_COLOR "random_underwear_color" +#define RANDOM_UNDERSHIRT "random_undershirt" +#define RANDOM_SOCKS "random_socks" +#define RANDOM_BACKPACK "random_backpack" +#define RANDOM_JUMPSUIT_STYLE "random_jumpsuit_style" +#define RANDOM_HAIRSTYLE "random_hairstyle" +#define RANDOM_HAIR_COLOR "random_hair_color" +#define RANDOM_FACIAL_HAIR_COLOR "random_facial_hair_color" +#define RANDOM_FACIAL_HAIRSTYLE "random_facial_hairstyle" +#define RANDOM_SKIN_TONE "random_skin_tone" +#define RANDOM_EYE_COLOR "random_eye_color" diff --git a/code/__DEFINES/radio.dm b/code/__DEFINES/radio.dm index af6a9ac51b1..d3190b8fa20 100644 --- a/code/__DEFINES/radio.dm +++ b/code/__DEFINES/radio.dm @@ -1,116 +1,116 @@ -// Radios use a large variety of predefined frequencies. - -//say based modes like binary are in living/say.dm - -#define RADIO_CHANNEL_COMMON "Common" -#define RADIO_KEY_COMMON ";" - -#define RADIO_CHANNEL_SECURITY "Security" -#define RADIO_KEY_SECURITY "s" -#define RADIO_TOKEN_SECURITY ":s" - -#define RADIO_CHANNEL_ENGINEERING "Engineering" -#define RADIO_KEY_ENGINEERING "e" -#define RADIO_TOKEN_ENGINEERING ":e" - -#define RADIO_CHANNEL_COMMAND "Command" -#define RADIO_KEY_COMMAND "c" -#define RADIO_TOKEN_COMMAND ":c" - -#define RADIO_CHANNEL_SCIENCE "Science" -#define RADIO_KEY_SCIENCE "n" -#define RADIO_TOKEN_SCIENCE ":n" - -#define RADIO_CHANNEL_MEDICAL "Medical" -#define RADIO_KEY_MEDICAL "m" -#define RADIO_TOKEN_MEDICAL ":m" - -#define RADIO_CHANNEL_SUPPLY "Supply" -#define RADIO_KEY_SUPPLY "u" -#define RADIO_TOKEN_SUPPLY ":u" - -#define RADIO_CHANNEL_SERVICE "Service" -#define RADIO_KEY_SERVICE "v" -#define RADIO_TOKEN_SERVICE ":v" - -#define RADIO_CHANNEL_AI_PRIVATE "AI Private" -#define RADIO_KEY_AI_PRIVATE "o" -#define RADIO_TOKEN_AI_PRIVATE ":o" - - -#define RADIO_CHANNEL_SYNDICATE "Syndicate" -#define RADIO_KEY_SYNDICATE "t" -#define RADIO_TOKEN_SYNDICATE ":t" - -#define RADIO_CHANNEL_CENTCOM "CentCom" -#define RADIO_KEY_CENTCOM "y" -#define RADIO_TOKEN_CENTCOM ":y" - -#define RADIO_CHANNEL_CTF_RED "Red Team" -#define RADIO_CHANNEL_CTF_BLUE "Blue Team" - - -#define MIN_FREE_FREQ 1201 // ------------------------------------------------- -// Frequencies are always odd numbers and range from 1201 to 1599. - -#define FREQ_SYNDICATE 1213 // Nuke op comms frequency, dark brown -#define FREQ_CTF_RED 1215 // CTF red team comms frequency, red -#define FREQ_CTF_BLUE 1217 // CTF blue team comms frequency, blue -#define FREQ_CENTCOM 1337 // CentCom comms frequency, gray -#define FREQ_SUPPLY 1347 // Supply comms frequency, light brown -#define FREQ_SERVICE 1349 // Service comms frequency, green -#define FREQ_SCIENCE 1351 // Science comms frequency, plum -#define FREQ_COMMAND 1353 // Command comms frequency, gold -#define FREQ_MEDICAL 1355 // Medical comms frequency, soft blue -#define FREQ_ENGINEERING 1357 // Engineering comms frequency, orange -#define FREQ_SECURITY 1359 // Security comms frequency, red - -#define FREQ_HOLOGRID_SOLUTION 1433 -#define FREQ_STATUS_DISPLAYS 1435 -#define FREQ_ATMOS_ALARMS 1437 // air alarms <-> alert computers -#define FREQ_ATMOS_CONTROL 1439 // air alarms <-> vents and scrubbers - -#define MIN_FREQ 1441 // ------------------------------------------------------ -// Only the 1441 to 1489 range is freely available for general conversation. -// This represents 1/8th of the available spectrum. - -#define FREQ_ATMOS_STORAGE 1441 -#define FREQ_NAV_BEACON 1445 -#define FREQ_AI_PRIVATE 1447 // AI private comms frequency, magenta -#define FREQ_PRESSURE_PLATE 1447 -#define FREQ_AIRLOCK_CONTROL 1449 -#define FREQ_ELECTROPACK 1449 -#define FREQ_MAGNETS 1449 -#define FREQ_LOCATOR_IMPLANT 1451 -#define FREQ_SIGNALER 1457 // the default for new signalers -#define FREQ_COMMON 1459 // Common comms frequency, dark green - -#define MAX_FREQ 1489 // ------------------------------------------------------ - -#define MAX_FREE_FREQ 1599 // ------------------------------------------------- - -// Transmission types. -#define TRANSMISSION_WIRE 0 // some sort of wired connection, not used -#define TRANSMISSION_RADIO 1 // electromagnetic radiation (default) -#define TRANSMISSION_SUBSPACE 2 // subspace transmission (headsets only) -#define TRANSMISSION_SUPERSPACE 3 // reaches independent (CentCom) radios only - -// Filter types, used as an optimization to avoid unnecessary proc calls. -#define RADIO_TO_AIRALARM "to_airalarm" -#define RADIO_FROM_AIRALARM "from_airalarm" -#define RADIO_SIGNALER "signaler" -#define RADIO_ATMOSIA "atmosia" -#define RADIO_AIRLOCK "airlock" -#define RADIO_MAGNETS "magnets" - -#define DEFAULT_SIGNALER_CODE 30 - -//Requests Console -#define REQ_NO_NEW_MESSAGE 0 -#define REQ_NORMAL_MESSAGE_PRIORITY 1 -#define REQ_HIGH_MESSAGE_PRIORITY 2 -#define REQ_EXTREME_MESSAGE_PRIORITY 3 - -#define REQ_DEP_TYPE_ASSISTANCE (1<<0) -#define REQ_DEP_TYPE_SUPPLIES (1<<1) -#define REQ_DEP_TYPE_INFORMATION (1<<2) +// Radios use a large variety of predefined frequencies. + +//say based modes like binary are in living/say.dm + +#define RADIO_CHANNEL_COMMON "Common" +#define RADIO_KEY_COMMON ";" + +#define RADIO_CHANNEL_SECURITY "Security" +#define RADIO_KEY_SECURITY "s" +#define RADIO_TOKEN_SECURITY ":s" + +#define RADIO_CHANNEL_ENGINEERING "Engineering" +#define RADIO_KEY_ENGINEERING "e" +#define RADIO_TOKEN_ENGINEERING ":e" + +#define RADIO_CHANNEL_COMMAND "Command" +#define RADIO_KEY_COMMAND "c" +#define RADIO_TOKEN_COMMAND ":c" + +#define RADIO_CHANNEL_SCIENCE "Science" +#define RADIO_KEY_SCIENCE "n" +#define RADIO_TOKEN_SCIENCE ":n" + +#define RADIO_CHANNEL_MEDICAL "Medical" +#define RADIO_KEY_MEDICAL "m" +#define RADIO_TOKEN_MEDICAL ":m" + +#define RADIO_CHANNEL_SUPPLY "Supply" +#define RADIO_KEY_SUPPLY "u" +#define RADIO_TOKEN_SUPPLY ":u" + +#define RADIO_CHANNEL_SERVICE "Service" +#define RADIO_KEY_SERVICE "v" +#define RADIO_TOKEN_SERVICE ":v" + +#define RADIO_CHANNEL_AI_PRIVATE "AI Private" +#define RADIO_KEY_AI_PRIVATE "o" +#define RADIO_TOKEN_AI_PRIVATE ":o" + + +#define RADIO_CHANNEL_SYNDICATE "Syndicate" +#define RADIO_KEY_SYNDICATE "t" +#define RADIO_TOKEN_SYNDICATE ":t" + +#define RADIO_CHANNEL_CENTCOM "CentCom" +#define RADIO_KEY_CENTCOM "y" +#define RADIO_TOKEN_CENTCOM ":y" + +#define RADIO_CHANNEL_CTF_RED "Red Team" +#define RADIO_CHANNEL_CTF_BLUE "Blue Team" + + +#define MIN_FREE_FREQ 1201 // ------------------------------------------------- +// Frequencies are always odd numbers and range from 1201 to 1599. + +#define FREQ_SYNDICATE 1213 // Nuke op comms frequency, dark brown +#define FREQ_CTF_RED 1215 // CTF red team comms frequency, red +#define FREQ_CTF_BLUE 1217 // CTF blue team comms frequency, blue +#define FREQ_CENTCOM 1337 // CentCom comms frequency, gray +#define FREQ_SUPPLY 1347 // Supply comms frequency, light brown +#define FREQ_SERVICE 1349 // Service comms frequency, green +#define FREQ_SCIENCE 1351 // Science comms frequency, plum +#define FREQ_COMMAND 1353 // Command comms frequency, gold +#define FREQ_MEDICAL 1355 // Medical comms frequency, soft blue +#define FREQ_ENGINEERING 1357 // Engineering comms frequency, orange +#define FREQ_SECURITY 1359 // Security comms frequency, red + +#define FREQ_HOLOGRID_SOLUTION 1433 +#define FREQ_STATUS_DISPLAYS 1435 +#define FREQ_ATMOS_ALARMS 1437 // air alarms <-> alert computers +#define FREQ_ATMOS_CONTROL 1439 // air alarms <-> vents and scrubbers + +#define MIN_FREQ 1441 // ------------------------------------------------------ +// Only the 1441 to 1489 range is freely available for general conversation. +// This represents 1/8th of the available spectrum. + +#define FREQ_ATMOS_STORAGE 1441 +#define FREQ_NAV_BEACON 1445 +#define FREQ_AI_PRIVATE 1447 // AI private comms frequency, magenta +#define FREQ_PRESSURE_PLATE 1447 +#define FREQ_AIRLOCK_CONTROL 1449 +#define FREQ_ELECTROPACK 1449 +#define FREQ_MAGNETS 1449 +#define FREQ_LOCATOR_IMPLANT 1451 +#define FREQ_SIGNALER 1457 // the default for new signalers +#define FREQ_COMMON 1459 // Common comms frequency, dark green + +#define MAX_FREQ 1489 // ------------------------------------------------------ + +#define MAX_FREE_FREQ 1599 // ------------------------------------------------- + +// Transmission types. +#define TRANSMISSION_WIRE 0 // some sort of wired connection, not used +#define TRANSMISSION_RADIO 1 // electromagnetic radiation (default) +#define TRANSMISSION_SUBSPACE 2 // subspace transmission (headsets only) +#define TRANSMISSION_SUPERSPACE 3 // reaches independent (CentCom) radios only + +// Filter types, used as an optimization to avoid unnecessary proc calls. +#define RADIO_TO_AIRALARM "to_airalarm" +#define RADIO_FROM_AIRALARM "from_airalarm" +#define RADIO_SIGNALER "signaler" +#define RADIO_ATMOSIA "atmosia" +#define RADIO_AIRLOCK "airlock" +#define RADIO_MAGNETS "magnets" + +#define DEFAULT_SIGNALER_CODE 30 + +//Requests Console +#define REQ_NO_NEW_MESSAGE 0 +#define REQ_NORMAL_MESSAGE_PRIORITY 1 +#define REQ_HIGH_MESSAGE_PRIORITY 2 +#define REQ_EXTREME_MESSAGE_PRIORITY 3 + +#define REQ_DEP_TYPE_ASSISTANCE (1<<0) +#define REQ_DEP_TYPE_SUPPLIES (1<<1) +#define REQ_DEP_TYPE_INFORMATION (1<<2) diff --git a/code/__DEFINES/reagents_specific_heat.dm b/code/__DEFINES/reagents_specific_heat.dm index a721b98cc64..90a379d7de5 100644 --- a/code/__DEFINES/reagents_specific_heat.dm +++ b/code/__DEFINES/reagents_specific_heat.dm @@ -1,3 +1,3 @@ -#define SPECIFIC_HEAT_DEFAULT 200 - -#define SPECIFIC_HEAT_PLASMA 500 +#define SPECIFIC_HEAT_DEFAULT 200 + +#define SPECIFIC_HEAT_PLASMA 500 diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index 47e95102c5d..86c4bf155c4 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -1,90 +1,90 @@ -///Defines for the R&D console, see: [/modules/research/rdconsole][rdconsole] -#define RDCONSOLE_UI_MODE_NORMAL 1 -#define RDCONSOLE_UI_MODE_EXPERT 2 -#define RDCONSOLE_UI_MODE_LIST 3 - -#define RDSCREEN_MENU 0 -#define RDSCREEN_TECHDISK 1 -#define RDSCREEN_DESIGNDISK 20 -#define RDSCREEN_DESIGNDISK_UPLOAD 21 -#define RDSCREEN_DECONSTRUCT 3 -#define RDSCREEN_PROTOLATHE 40 -#define RDSCREEN_PROTOLATHE_MATERIALS 41 -#define RDSCREEN_PROTOLATHE_CHEMICALS 42 -#define RDSCREEN_PROTOLATHE_CATEGORY_VIEW 43 -#define RDSCREEN_PROTOLATHE_SEARCH 44 -#define RDSCREEN_IMPRINTER 50 -#define RDSCREEN_IMPRINTER_MATERIALS 51 -#define RDSCREEN_IMPRINTER_CHEMICALS 52 -#define RDSCREEN_IMPRINTER_CATEGORY_VIEW 53 -#define RDSCREEN_IMPRINTER_SEARCH 54 -#define RDSCREEN_SETTINGS 61 -#define RDSCREEN_DEVICE_LINKING 62 -#define RDSCREEN_TECHWEB 70 -#define RDSCREEN_TECHWEB_NODEVIEW 71 -#define RDSCREEN_TECHWEB_DESIGNVIEW 72 - -#define RDSCREEN_NOBREAK "" - -///Sanity check defines for when these devices aren't connected or no disk is inserted -#define RDSCREEN_TEXT_NO_PROTOLATHE "

No Protolathe Linked!


" -#define RDSCREEN_TEXT_NO_IMPRINTER "

No Circuit Imprinter Linked!


" -#define RDSCREEN_TEXT_NO_DECONSTRUCT "

No Destructive Analyzer Linked!


" -#define RDSCREEN_TEXT_NO_TDISK "

No Technology Disk Inserted!


" -#define RDSCREEN_TEXT_NO_DDISK "

No Design Disk Inserted!


" -#define RDSCREEN_TEXT_NO_SNODE "

No Technology Node Selected!


" -#define RDSCREEN_TEXT_NO_SDESIGN "

No Design Selected!


" - -#define RDSCREEN_UI_LATHE_CHECK if(QDELETED(linked_lathe)) { return RDSCREEN_TEXT_NO_PROTOLATHE } -#define RDSCREEN_UI_IMPRINTER_CHECK if(QDELETED(linked_imprinter)) { return RDSCREEN_TEXT_NO_IMPRINTER } -#define RDSCREEN_UI_DECONSTRUCT_CHECK if(QDELETED(linked_destroy)) { return RDSCREEN_TEXT_NO_DECONSTRUCT } -#define RDSCREEN_UI_TDISK_CHECK if(QDELETED(t_disk)) { return RDSCREEN_TEXT_NO_TDISK } -#define RDSCREEN_UI_DDISK_CHECK if(QDELETED(d_disk)) { return RDSCREEN_TEXT_NO_DDISK } -#define RDSCREEN_UI_SNODE_CHECK if(!selected_node) { return RDSCREEN_TEXT_NO_SNODE } -#define RDSCREEN_UI_SDESIGN_CHECK if(!selected_design) { return RDSCREEN_TEXT_NO_SDESIGN } - -///Defines for the Protolathe screens, see: [/modules/research/machinery/protolathe][Protolathe] -#define RESEARCH_FABRICATOR_SCREEN_MAIN 1 -#define RESEARCH_FABRICATOR_SCREEN_CHEMICALS 2 -#define RESEARCH_FABRICATOR_SCREEN_MATERIALS 3 -#define RESEARCH_FABRICATOR_SCREEN_SEARCH 4 -#define RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW 5 - -///Department flags for techwebs. Defines which department can print what from each protolathe so Cargo can't print guns, etc. -#define DEPARTMENTAL_FLAG_SECURITY (1<<0) -#define DEPARTMENTAL_FLAG_MEDICAL (1<<1) -#define DEPARTMENTAL_FLAG_CARGO (1<<2) -#define DEPARTMENTAL_FLAG_SCIENCE (1<<3) -#define DEPARTMENTAL_FLAG_ENGINEERING (1<<4) -#define DEPARTMENTAL_FLAG_SERVICE (1<<5) - -#define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" ///For instances where we don't want a design showing up due to it being for debug/sanity purposes - -#define RESEARCH_MATERIAL_RECLAMATION_ID "__materials" - -///Techweb names for new point types. Can be used to define specific point values for specific types of research (science, security, engineering, etc.) -#define TECHWEB_POINT_TYPE_GENERIC "General Research" -#define TECHWEB_POINT_TYPE_NANITES "Nanite Research" - -#define TECHWEB_POINT_TYPE_DEFAULT TECHWEB_POINT_TYPE_GENERIC - -///Associative names for techweb point values, see: [/modules/research/techweb/all_nodes][all_nodes] -#define TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES list(\ - TECHWEB_POINT_TYPE_GENERIC = "General Research",\ - TECHWEB_POINT_TYPE_NANITES = "Nanite Research"\ - ) - -///R&D point value for a maxcap bomb. Can be adjusted if need be. Current Value Cap Radius: 100 -#define TECHWEB_BOMB_POINTCAP 50000 - -///Research point values for slime extracts, see: [/modules/research/xenobiology/xenobio_camera][xenobio_camera] -#define SLIME_RESEARCH_TIER_0 100 -#define SLIME_RESEARCH_TIER_1 500 -#define SLIME_RESEARCH_TIER_2 1000 -#define SLIME_RESEARCH_TIER_3 1500 -#define SLIME_RESEARCH_TIER_4 2000 -#define SLIME_RESEARCH_TIER_5 2500 -#define SLIME_RESEARCH_TIER_RAINBOW 5000 - -///Amount of points gained per second by a single R&D server, see: [/controllers/subsystem/research.dm][research] -#define TECHWEB_SINGLE_SERVER_INCOME 52.3 +///Defines for the R&D console, see: [/modules/research/rdconsole][rdconsole] +#define RDCONSOLE_UI_MODE_NORMAL 1 +#define RDCONSOLE_UI_MODE_EXPERT 2 +#define RDCONSOLE_UI_MODE_LIST 3 + +#define RDSCREEN_MENU 0 +#define RDSCREEN_TECHDISK 1 +#define RDSCREEN_DESIGNDISK 20 +#define RDSCREEN_DESIGNDISK_UPLOAD 21 +#define RDSCREEN_DECONSTRUCT 3 +#define RDSCREEN_PROTOLATHE 40 +#define RDSCREEN_PROTOLATHE_MATERIALS 41 +#define RDSCREEN_PROTOLATHE_CHEMICALS 42 +#define RDSCREEN_PROTOLATHE_CATEGORY_VIEW 43 +#define RDSCREEN_PROTOLATHE_SEARCH 44 +#define RDSCREEN_IMPRINTER 50 +#define RDSCREEN_IMPRINTER_MATERIALS 51 +#define RDSCREEN_IMPRINTER_CHEMICALS 52 +#define RDSCREEN_IMPRINTER_CATEGORY_VIEW 53 +#define RDSCREEN_IMPRINTER_SEARCH 54 +#define RDSCREEN_SETTINGS 61 +#define RDSCREEN_DEVICE_LINKING 62 +#define RDSCREEN_TECHWEB 70 +#define RDSCREEN_TECHWEB_NODEVIEW 71 +#define RDSCREEN_TECHWEB_DESIGNVIEW 72 + +#define RDSCREEN_NOBREAK "" + +///Sanity check defines for when these devices aren't connected or no disk is inserted +#define RDSCREEN_TEXT_NO_PROTOLATHE "

No Protolathe Linked!


" +#define RDSCREEN_TEXT_NO_IMPRINTER "

No Circuit Imprinter Linked!


" +#define RDSCREEN_TEXT_NO_DECONSTRUCT "

No Destructive Analyzer Linked!


" +#define RDSCREEN_TEXT_NO_TDISK "

No Technology Disk Inserted!


" +#define RDSCREEN_TEXT_NO_DDISK "

No Design Disk Inserted!


" +#define RDSCREEN_TEXT_NO_SNODE "

No Technology Node Selected!


" +#define RDSCREEN_TEXT_NO_SDESIGN "

No Design Selected!


" + +#define RDSCREEN_UI_LATHE_CHECK if(QDELETED(linked_lathe)) { return RDSCREEN_TEXT_NO_PROTOLATHE } +#define RDSCREEN_UI_IMPRINTER_CHECK if(QDELETED(linked_imprinter)) { return RDSCREEN_TEXT_NO_IMPRINTER } +#define RDSCREEN_UI_DECONSTRUCT_CHECK if(QDELETED(linked_destroy)) { return RDSCREEN_TEXT_NO_DECONSTRUCT } +#define RDSCREEN_UI_TDISK_CHECK if(QDELETED(t_disk)) { return RDSCREEN_TEXT_NO_TDISK } +#define RDSCREEN_UI_DDISK_CHECK if(QDELETED(d_disk)) { return RDSCREEN_TEXT_NO_DDISK } +#define RDSCREEN_UI_SNODE_CHECK if(!selected_node) { return RDSCREEN_TEXT_NO_SNODE } +#define RDSCREEN_UI_SDESIGN_CHECK if(!selected_design) { return RDSCREEN_TEXT_NO_SDESIGN } + +///Defines for the Protolathe screens, see: [/modules/research/machinery/protolathe][Protolathe] +#define RESEARCH_FABRICATOR_SCREEN_MAIN 1 +#define RESEARCH_FABRICATOR_SCREEN_CHEMICALS 2 +#define RESEARCH_FABRICATOR_SCREEN_MATERIALS 3 +#define RESEARCH_FABRICATOR_SCREEN_SEARCH 4 +#define RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW 5 + +///Department flags for techwebs. Defines which department can print what from each protolathe so Cargo can't print guns, etc. +#define DEPARTMENTAL_FLAG_SECURITY (1<<0) +#define DEPARTMENTAL_FLAG_MEDICAL (1<<1) +#define DEPARTMENTAL_FLAG_CARGO (1<<2) +#define DEPARTMENTAL_FLAG_SCIENCE (1<<3) +#define DEPARTMENTAL_FLAG_ENGINEERING (1<<4) +#define DEPARTMENTAL_FLAG_SERVICE (1<<5) + +#define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" ///For instances where we don't want a design showing up due to it being for debug/sanity purposes + +#define RESEARCH_MATERIAL_RECLAMATION_ID "__materials" + +///Techweb names for new point types. Can be used to define specific point values for specific types of research (science, security, engineering, etc.) +#define TECHWEB_POINT_TYPE_GENERIC "General Research" +#define TECHWEB_POINT_TYPE_NANITES "Nanite Research" + +#define TECHWEB_POINT_TYPE_DEFAULT TECHWEB_POINT_TYPE_GENERIC + +///Associative names for techweb point values, see: [/modules/research/techweb/all_nodes][all_nodes] +#define TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES list(\ + TECHWEB_POINT_TYPE_GENERIC = "General Research",\ + TECHWEB_POINT_TYPE_NANITES = "Nanite Research"\ + ) + +///R&D point value for a maxcap bomb. Can be adjusted if need be. Current Value Cap Radius: 100 +#define TECHWEB_BOMB_POINTCAP 50000 + +///Research point values for slime extracts, see: [/modules/research/xenobiology/xenobio_camera][xenobio_camera] +#define SLIME_RESEARCH_TIER_0 100 +#define SLIME_RESEARCH_TIER_1 500 +#define SLIME_RESEARCH_TIER_2 1000 +#define SLIME_RESEARCH_TIER_3 1500 +#define SLIME_RESEARCH_TIER_4 2000 +#define SLIME_RESEARCH_TIER_5 2500 +#define SLIME_RESEARCH_TIER_RAINBOW 5000 + +///Amount of points gained per second by a single R&D server, see: [/controllers/subsystem/research.dm][research] +#define TECHWEB_SINGLE_SERVER_INCOME 52.3 diff --git a/code/__DEFINES/sight.dm b/code/__DEFINES/sight.dm index 6ab3092d1d0..5cac2900823 100644 --- a/code/__DEFINES/sight.dm +++ b/code/__DEFINES/sight.dm @@ -1,35 +1,35 @@ -#define SEE_INVISIBLE_MINIMUM 5 - -#define INVISIBILITY_LIGHTING 20 - -#define SEE_INVISIBLE_LIVING 25 - -//#define SEE_INVISIBLE_LEVEL_ONE 35 //currently unused -//#define INVISIBILITY_LEVEL_ONE 35 //currently unused - -//#define SEE_INVISIBLE_LEVEL_TWO 45 //currently unused -//#define INVISIBILITY_LEVEL_TWO 45 //currently unused - -#define INVISIBILITY_OBSERVER 60 -#define SEE_INVISIBLE_OBSERVER 60 - -#define INVISIBILITY_MAXIMUM 100 //the maximum allowed for "real" objects - -#define INVISIBILITY_ABSTRACT 101 //only used for abstract objects (e.g. spacevine_controller), things that are not really there. - -#define BORGMESON (1<<0) -#define BORGTHERM (1<<1) -#define BORGXRAY (1<<2) -#define BORGMATERIAL (1<<3) - -//for clothing visor toggles, these determine which vars to toggle -#define VISOR_FLASHPROTECT (1<<0) -#define VISOR_TINT (1<<1) -#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses -#define VISOR_DARKNESSVIEW (1<<3) -#define VISOR_INVISVIEW (1<<4) - -//for whether AI eyes see static, and whether it is mouse-opaque or not -#define USE_STATIC_NONE 0 -#define USE_STATIC_TRANSPARENT 1 -#define USE_STATIC_OPAQUE 2 +#define SEE_INVISIBLE_MINIMUM 5 + +#define INVISIBILITY_LIGHTING 20 + +#define SEE_INVISIBLE_LIVING 25 + +//#define SEE_INVISIBLE_LEVEL_ONE 35 //currently unused +//#define INVISIBILITY_LEVEL_ONE 35 //currently unused + +//#define SEE_INVISIBLE_LEVEL_TWO 45 //currently unused +//#define INVISIBILITY_LEVEL_TWO 45 //currently unused + +#define INVISIBILITY_OBSERVER 60 +#define SEE_INVISIBLE_OBSERVER 60 + +#define INVISIBILITY_MAXIMUM 100 //the maximum allowed for "real" objects + +#define INVISIBILITY_ABSTRACT 101 //only used for abstract objects (e.g. spacevine_controller), things that are not really there. + +#define BORGMESON (1<<0) +#define BORGTHERM (1<<1) +#define BORGXRAY (1<<2) +#define BORGMATERIAL (1<<3) + +//for clothing visor toggles, these determine which vars to toggle +#define VISOR_FLASHPROTECT (1<<0) +#define VISOR_TINT (1<<1) +#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses +#define VISOR_DARKNESSVIEW (1<<3) +#define VISOR_INVISVIEW (1<<4) + +//for whether AI eyes see static, and whether it is mouse-opaque or not +#define USE_STATIC_NONE 0 +#define USE_STATIC_TRANSPARENT 1 +#define USE_STATIC_OPAQUE 2 diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index 5e975b29b86..8c48e9a916a 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -1,83 +1,83 @@ -//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on -#define CHANNEL_LOBBYMUSIC 1024 -#define CHANNEL_ADMIN 1023 -#define CHANNEL_VOX 1022 -#define CHANNEL_JUKEBOX 1021 -#define CHANNEL_JUSTICAR_ARK 1020 -#define CHANNEL_HEARTBEAT 1019 //sound channel for heartbeats -#define CHANNEL_AMBIENCE 1018 -#define CHANNEL_BUZZ 1017 -#define CHANNEL_BICYCLE 1016 - -//THIS SHOULD ALWAYS BE THE LOWEST ONE! -//KEEP IT UPDATED - -#define CHANNEL_HIGHEST_AVAILABLE 1015 - - -#define SOUND_MINIMUM_PRESSURE 10 -#define FALLOFF_SOUNDS 1 - - -//Ambience types - -#define GENERIC list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ - 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ - 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ - 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ - 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ - 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg','sound/ambience/ambigen15.ogg') - -#define HOLY list('sound/ambience/ambicha1.ogg','sound/ambience/ambicha2.ogg','sound/ambience/ambicha3.ogg',\ - 'sound/ambience/ambicha4.ogg', 'sound/ambience/ambiholy.ogg', 'sound/ambience/ambiholy2.ogg',\ - 'sound/ambience/ambiholy3.ogg') - -#define HIGHSEC list('sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg') - -#define RUINS list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambitech3.ogg',\ - 'sound/ambience/ambimystery.ogg', 'sound/ambience/ambimaint1.ogg') - -#define ENGINEERING list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg',\ - 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') - -#define MINING list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg',\ - 'sound/ambience/ambilava1.ogg', 'sound/ambience/ambilava2.ogg', 'sound/ambience/ambilava3.ogg') - -#define MEDICAL list('sound/ambience/ambinice.ogg') - -#define SPOOKY list('sound/ambience/ambimo1.ogg','sound/ambience/ambimo2.ogg','sound/ambience/ambiruin7.ogg','sound/ambience/ambiruin6.ogg',\ - 'sound/ambience/ambiodd.ogg', 'sound/ambience/ambimystery.ogg') - -#define SPACE list('sound/ambience/ambispace.ogg', 'sound/ambience/ambispace2.ogg', 'sound/ambience/title2.ogg', 'sound/ambience/ambiatmos.ogg') - -#define MAINTENANCE list('sound/ambience/ambimaint1.ogg', 'sound/ambience/ambimaint2.ogg', 'sound/ambience/ambimaint3.ogg', 'sound/ambience/ambimaint4.ogg',\ - 'sound/ambience/ambimaint5.ogg', 'sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg', 'sound/ambience/ambitech2.ogg' ) - -#define AWAY_MISSION list('sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint.ogg',\ - 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambiodd.ogg') - -#define REEBE list('sound/ambience/ambireebe1.ogg', 'sound/ambience/ambireebe2.ogg', 'sound/ambience/ambireebe3.ogg') - - - -#define CREEPY_SOUNDS list('sound/effects/ghost.ogg', 'sound/effects/ghost2.ogg', 'sound/effects/heart_beat.ogg', 'sound/effects/screech.ogg',\ - 'sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/behind_you2.ogg', 'sound/hallucinations/far_noise.ogg', 'sound/hallucinations/growl1.ogg', 'sound/hallucinations/growl2.ogg',\ - 'sound/hallucinations/growl3.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg',\ - 'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\ - 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg') - - -#define INTERACTION_SOUND_RANGE_MODIFIER -3 -#define EQUIP_SOUND_VOLUME 30 -#define PICKUP_SOUND_VOLUME 15 -#define DROP_SOUND_VOLUME 20 -#define YEET_SOUND_VOLUME 90 +//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on +#define CHANNEL_LOBBYMUSIC 1024 +#define CHANNEL_ADMIN 1023 +#define CHANNEL_VOX 1022 +#define CHANNEL_JUKEBOX 1021 +#define CHANNEL_JUSTICAR_ARK 1020 +#define CHANNEL_HEARTBEAT 1019 //sound channel for heartbeats +#define CHANNEL_AMBIENCE 1018 +#define CHANNEL_BUZZ 1017 +#define CHANNEL_BICYCLE 1016 + +//THIS SHOULD ALWAYS BE THE LOWEST ONE! +//KEEP IT UPDATED + +#define CHANNEL_HIGHEST_AVAILABLE 1015 + + +#define SOUND_MINIMUM_PRESSURE 10 +#define FALLOFF_SOUNDS 1 + + +//Ambience types + +#define GENERIC list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ + 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ + 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ + 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ + 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ + 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg','sound/ambience/ambigen15.ogg') + +#define HOLY list('sound/ambience/ambicha1.ogg','sound/ambience/ambicha2.ogg','sound/ambience/ambicha3.ogg',\ + 'sound/ambience/ambicha4.ogg', 'sound/ambience/ambiholy.ogg', 'sound/ambience/ambiholy2.ogg',\ + 'sound/ambience/ambiholy3.ogg') + +#define HIGHSEC list('sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg') + +#define RUINS list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambitech3.ogg',\ + 'sound/ambience/ambimystery.ogg', 'sound/ambience/ambimaint1.ogg') + +#define ENGINEERING list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg',\ + 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') + +#define MINING list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg',\ + 'sound/ambience/ambilava1.ogg', 'sound/ambience/ambilava2.ogg', 'sound/ambience/ambilava3.ogg') + +#define MEDICAL list('sound/ambience/ambinice.ogg') + +#define SPOOKY list('sound/ambience/ambimo1.ogg','sound/ambience/ambimo2.ogg','sound/ambience/ambiruin7.ogg','sound/ambience/ambiruin6.ogg',\ + 'sound/ambience/ambiodd.ogg', 'sound/ambience/ambimystery.ogg') + +#define SPACE list('sound/ambience/ambispace.ogg', 'sound/ambience/ambispace2.ogg', 'sound/ambience/title2.ogg', 'sound/ambience/ambiatmos.ogg') + +#define MAINTENANCE list('sound/ambience/ambimaint1.ogg', 'sound/ambience/ambimaint2.ogg', 'sound/ambience/ambimaint3.ogg', 'sound/ambience/ambimaint4.ogg',\ + 'sound/ambience/ambimaint5.ogg', 'sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg', 'sound/ambience/ambitech2.ogg' ) + +#define AWAY_MISSION list('sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint.ogg',\ + 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambiodd.ogg') + +#define REEBE list('sound/ambience/ambireebe1.ogg', 'sound/ambience/ambireebe2.ogg', 'sound/ambience/ambireebe3.ogg') + + + +#define CREEPY_SOUNDS list('sound/effects/ghost.ogg', 'sound/effects/ghost2.ogg', 'sound/effects/heart_beat.ogg', 'sound/effects/screech.ogg',\ + 'sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/behind_you2.ogg', 'sound/hallucinations/far_noise.ogg', 'sound/hallucinations/growl1.ogg', 'sound/hallucinations/growl2.ogg',\ + 'sound/hallucinations/growl3.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg',\ + 'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\ + 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg') + + +#define INTERACTION_SOUND_RANGE_MODIFIER -3 +#define EQUIP_SOUND_VOLUME 30 +#define PICKUP_SOUND_VOLUME 15 +#define DROP_SOUND_VOLUME 20 +#define YEET_SOUND_VOLUME 90 diff --git a/code/__DEFINES/stat.dm b/code/__DEFINES/stat.dm index 3321f871de2..a77b65acf81 100644 --- a/code/__DEFINES/stat.dm +++ b/code/__DEFINES/stat.dm @@ -1,21 +1,21 @@ -/* - Used with the various stat variables (mob, machines) -*/ - -//mob/var/stat things -#define CONSCIOUS 0 -#define SOFT_CRIT 1 -#define UNCONSCIOUS 2 -#define DEAD 3 - -//Maximum healthiness an individual can have -#define MAX_SATIETY 600 - -// bitflags for machine stat variable -#define BROKEN (1<<0) -#define NOPOWER (1<<1) -#define MAINT (1<<2) // under maintaince -#define EMPED (1<<3) // temporary broken by EMP pulse - -//ai power requirement defines -#define POWER_REQ_ALL 1 +/* + Used with the various stat variables (mob, machines) +*/ + +//mob/var/stat things +#define CONSCIOUS 0 +#define SOFT_CRIT 1 +#define UNCONSCIOUS 2 +#define DEAD 3 + +//Maximum healthiness an individual can have +#define MAX_SATIETY 600 + +// bitflags for machine stat variable +#define BROKEN (1<<0) +#define NOPOWER (1<<1) +#define MAINT (1<<2) // under maintaince +#define EMPED (1<<3) // temporary broken by EMP pulse + +//ai power requirement defines +#define POWER_REQ_ALL 1 diff --git a/code/__DEFINES/tgs.config.dm b/code/__DEFINES/tgs.config.dm index f06fcc96e80..a2fcc7855ab 100644 --- a/code/__DEFINES/tgs.config.dm +++ b/code/__DEFINES/tgs.config.dm @@ -1,12 +1,12 @@ -#define TGS_EXTERNAL_CONFIGURATION -#define TGS_V3_API -#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name) -#define TGS_READ_GLOBAL(Name) GLOB.##Name -#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value -#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]") -#define TGS_INFO_LOG(message) log_world("TGS Info: [##message]") -#define TGS_WARNING_LOG(message) log_world("TGS Warn: [##message]") -#define TGS_ERROR_LOG(message) log_world("TGS Error: [##message]") -#define TGS_NOTIFY_ADMINS(event) message_admins(##event) -#define TGS_CLIENT_COUNT GLOB.clients.len -#define TGS_PROTECT_DATUM(Path) GENERAL_PROTECT_DATUM(##Path) +#define TGS_EXTERNAL_CONFIGURATION +#define TGS_V3_API +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name) +#define TGS_READ_GLOBAL(Name) GLOB.##Name +#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value +#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]") +#define TGS_INFO_LOG(message) log_world("TGS Info: [##message]") +#define TGS_WARNING_LOG(message) log_world("TGS Warn: [##message]") +#define TGS_ERROR_LOG(message) log_world("TGS Error: [##message]") +#define TGS_NOTIFY_ADMINS(event) message_admins(##event) +#define TGS_CLIENT_COUNT GLOB.clients.len +#define TGS_PROTECT_DATUM(Path) GENERAL_PROTECT_DATUM(##Path) diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm index 4605975e78c..ad4c7c640c5 100644 --- a/code/__DEFINES/tools.dm +++ b/code/__DEFINES/tools.dm @@ -1,21 +1,21 @@ -// Tool types -#define TOOL_CROWBAR "crowbar" -#define TOOL_MULTITOOL "multitool" -#define TOOL_SCREWDRIVER "screwdriver" -#define TOOL_WIRECUTTER "wirecutter" -#define TOOL_WRENCH "wrench" -#define TOOL_WELDER "welder" -#define TOOL_ANALYZER "analyzer" -#define TOOL_MINING "mining" -#define TOOL_SHOVEL "shovel" -#define TOOL_RETRACTOR "retractor" -#define TOOL_HEMOSTAT "hemostat" -#define TOOL_CAUTERY "cautery" -#define TOOL_DRILL "drill" -#define TOOL_SCALPEL "scalpel" -#define TOOL_SAW "saw" -#define TOOL_BONESET "bonesetter" - -// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY, -// tool sound is only played when op is started. If not, it's played twice. -#define MIN_TOOL_SOUND_DELAY 20 +// Tool types +#define TOOL_CROWBAR "crowbar" +#define TOOL_MULTITOOL "multitool" +#define TOOL_SCREWDRIVER "screwdriver" +#define TOOL_WIRECUTTER "wirecutter" +#define TOOL_WRENCH "wrench" +#define TOOL_WELDER "welder" +#define TOOL_ANALYZER "analyzer" +#define TOOL_MINING "mining" +#define TOOL_SHOVEL "shovel" +#define TOOL_RETRACTOR "retractor" +#define TOOL_HEMOSTAT "hemostat" +#define TOOL_CAUTERY "cautery" +#define TOOL_DRILL "drill" +#define TOOL_SCALPEL "scalpel" +#define TOOL_SAW "saw" +#define TOOL_BONESET "bonesetter" + +// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY, +// tool sound is only played when op is started. If not, it's played twice. +#define MIN_TOOL_SOUND_DELAY 20 diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm index 202b1bfc1ad..3df50625185 100644 --- a/code/__DEFINES/wires.dm +++ b/code/__DEFINES/wires.dm @@ -1,54 +1,54 @@ -//retvals for attempt_wires_interaction -#define WIRE_INTERACTION_FAIL 0 -#define WIRE_INTERACTION_SUCCESSFUL 1 -#define WIRE_INTERACTION_BLOCK 2 //don't do anything else rather than open wires and whatever else. - -#define WIRE_DUD_PREFIX "__dud" -#define WIRE_ACTIVATE "Activate" -#define WIRE_AI "AI Connection" -#define WIRE_ALARM "Alarm" -#define WIRE_AVOIDANCE "Avoidance" -#define WIRE_BACKUP1 "Auxiliary Power 1" -#define WIRE_BACKUP2 "Auxiliary Power 2" -#define WIRE_BEACON "Beacon" -#define WIRE_BOLTS "Bolts" -#define WIRE_BOOM "Boom" -#define WIRE_CAMERA "Camera" -#define WIRE_CONTRABAND "Contraband" -#define WIRE_DELAY "Delay" -#define WIRE_DISABLE "Disable" -#define WIRE_DISARM "Disarm" -#define WIRE_HACK "Hack" -#define WIRE_IDSCAN "ID Scan" -#define WIRE_INTERFACE "Interface" -#define WIRE_LAWSYNC "AI Law Synchronization" -#define WIRE_LIGHT "Bolt Lights" -#define WIRE_LIMIT "Limiter" -#define WIRE_LOADCHECK "Load Check" -#define WIRE_LOCKDOWN "Lockdown" -#define WIRE_MOTOR1 "Motor 1" -#define WIRE_MOTOR2 "Motor 2" -#define WIRE_OPEN "Open" -#define WIRE_PANIC "Panic Siphon" -#define WIRE_POWER "Power" -#define WIRE_POWER1 "Main Power 1" -#define WIRE_POWER2 "Main Power 2" -#define WIRE_PROCEED "Proceed" -#define WIRE_RX "Receive" -#define WIRE_RESET_MODULE "Reset Module" -#define WIRE_SAFETY "Safety" -#define WIRE_SHOCK "High Voltage Ground" -#define WIRE_SIGNAL "Signal" -#define WIRE_SPEAKER "Speaker" -#define WIRE_STRENGTH "Strength" -#define WIRE_THROW "Throw" -#define WIRE_TIMING "Timing" -#define WIRE_TX "Transmit" -#define WIRE_UNBOLT "Unbolt" -#define WIRE_ZAP "High Voltage Circuit" -#define WIRE_ZAP1 "High Voltage Circuit 1" -#define WIRE_ZAP2 "High Voltage Circuit 2" -#define WIRE_PRIZEVEND "Emergency Prize Vend" -#define WIRE_RESETOWNER "Reset Owner" -#define WIRE_AGELIMIT "Age Limit" - +//retvals for attempt_wires_interaction +#define WIRE_INTERACTION_FAIL 0 +#define WIRE_INTERACTION_SUCCESSFUL 1 +#define WIRE_INTERACTION_BLOCK 2 //don't do anything else rather than open wires and whatever else. + +#define WIRE_DUD_PREFIX "__dud" +#define WIRE_ACTIVATE "Activate" +#define WIRE_AI "AI Connection" +#define WIRE_ALARM "Alarm" +#define WIRE_AVOIDANCE "Avoidance" +#define WIRE_BACKUP1 "Auxiliary Power 1" +#define WIRE_BACKUP2 "Auxiliary Power 2" +#define WIRE_BEACON "Beacon" +#define WIRE_BOLTS "Bolts" +#define WIRE_BOOM "Boom" +#define WIRE_CAMERA "Camera" +#define WIRE_CONTRABAND "Contraband" +#define WIRE_DELAY "Delay" +#define WIRE_DISABLE "Disable" +#define WIRE_DISARM "Disarm" +#define WIRE_HACK "Hack" +#define WIRE_IDSCAN "ID Scan" +#define WIRE_INTERFACE "Interface" +#define WIRE_LAWSYNC "AI Law Synchronization" +#define WIRE_LIGHT "Bolt Lights" +#define WIRE_LIMIT "Limiter" +#define WIRE_LOADCHECK "Load Check" +#define WIRE_LOCKDOWN "Lockdown" +#define WIRE_MOTOR1 "Motor 1" +#define WIRE_MOTOR2 "Motor 2" +#define WIRE_OPEN "Open" +#define WIRE_PANIC "Panic Siphon" +#define WIRE_POWER "Power" +#define WIRE_POWER1 "Main Power 1" +#define WIRE_POWER2 "Main Power 2" +#define WIRE_PROCEED "Proceed" +#define WIRE_RX "Receive" +#define WIRE_RESET_MODULE "Reset Module" +#define WIRE_SAFETY "Safety" +#define WIRE_SHOCK "High Voltage Ground" +#define WIRE_SIGNAL "Signal" +#define WIRE_SPEAKER "Speaker" +#define WIRE_STRENGTH "Strength" +#define WIRE_THROW "Throw" +#define WIRE_TIMING "Timing" +#define WIRE_TX "Transmit" +#define WIRE_UNBOLT "Unbolt" +#define WIRE_ZAP "High Voltage Circuit" +#define WIRE_ZAP1 "High Voltage Circuit 1" +#define WIRE_ZAP2 "High Voltage Circuit 2" +#define WIRE_PRIZEVEND "Emergency Prize Vend" +#define WIRE_RESETOWNER "Reset Owner" +#define WIRE_AGELIMIT "Age Limit" + diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index a448ca3d711..aaf004cc1a9 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -1,561 +1,561 @@ -/* - * Holds procs to help with list operations - * Contains groups: - * Misc - * Sorting - */ - -/* - * Misc - */ - -#define LAZYINITLIST(L) if (!L) L = list() -#define UNSETEMPTY(L) if (L && !length(L)) L = null -#define LAZYCOPY(L) (L ? L.Copy() : list() ) -#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } -#define LAZYADD(L, I) if(!L) { L = list(); } L += I; -#define LAZYOR(L, I) if(!L) { L = list(); } L |= I; -#define LAZYFIND(L, V) L ? L.Find(V) : 0 -#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) -#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; -#define LAZYLEN(L) length(L) -#define LAZYCLEARLIST(L) if(L) L.Cut() -#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) -#define reverseList(L) reverseRange(L.Copy()) -#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V); -#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } - -/// Passed into BINARY_INSERT to compare keys -#define COMPARE_KEY __BIN_LIST[__BIN_MID] -/// Passed into BINARY_INSERT to compare values -#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]] - -/**** - * Binary search sorted insert - * INPUT: Object to be inserted - * LIST: List to insert object into - * TYPECONT: The typepath of the contents of the list - * COMPARE: The object to compare against, usualy the same as INPUT - * COMPARISON: The variable on the objects to compare - */ -#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ - do {\ - var/list/__BIN_LIST = LIST;\ - var/__BIN_CTTL = length(__BIN_LIST);\ - if(!__BIN_CTTL) {\ - __BIN_LIST += INPUT;\ - } else {\ - var/__BIN_LEFT = 1;\ - var/__BIN_RIGHT = __BIN_CTTL;\ - var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ - var/##TYPECONT/__BIN_ITEM;\ - while(__BIN_LEFT < __BIN_RIGHT) {\ - __BIN_ITEM = COMPTYPE;\ - if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\ - __BIN_LEFT = __BIN_MID + 1;\ - } else {\ - __BIN_RIGHT = __BIN_MID;\ - };\ - __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ - };\ - __BIN_ITEM = COMPTYPE;\ - __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\ - __BIN_LIST.Insert(__BIN_MID, INPUT);\ - };\ - } while(FALSE) - -//Returns a list in plain english as a string -/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) - var/total = length(input) - switch(total) - if (0) - return "[nothing_text]" - if (1) - return "[input[1]]" - if (2) - return "[input[1]][and_text][input[2]]" - else - var/output = "" - var/index = 1 - while (index < total) - if (index == total - 1) - comma_text = final_comma_text - - output += "[input[index]][comma_text]" - index++ - - return "[output][and_text][input[index]]" - -//Checks for specific types in a list -/proc/is_type_in_list(atom/A, list/L) - if(!LAZYLEN(L) || !A) - return FALSE - for(var/type in L) - if(istype(A, type)) - return TRUE - return FALSE - -//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') -#define is_type_in_typecache(A, L) (A && length(L) && L[(ispath(A) ? A : A:type)]) - -//returns a new list with only atoms that are in typecache L -/proc/typecache_filter_list(list/atoms, list/typecache) - RETURN_TYPE(/list) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if (typecache[A.type]) - . += A - -/proc/typecache_filter_list_reverse(list/atoms, list/typecache) - RETURN_TYPE(/list) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if(!typecache[A.type]) - . += A - -/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if(typecache_include[A.type] && !typecache_exclude[A.type]) - . += A - -//Like typesof() or subtypesof(), but returns a typecache instead of a list -/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE) - if(ispath(path)) - var/list/types = list() - if(only_root_path) - types = list(path) - else - types = ignore_root_path ? subtypesof(path) : typesof(path) - var/list/L = list() - for(var/T in types) - L[T] = TRUE - return L - else if(islist(path)) - var/list/pathlist = path - var/list/L = list() - if(ignore_root_path) - for(var/P in pathlist) - for(var/T in subtypesof(P)) - L[T] = TRUE - else - for(var/P in pathlist) - if(only_root_path) - L[P] = TRUE - else - for(var/T in typesof(P)) - L[T] = TRUE - return L - -//Removes any null entries from the list -//Returns TRUE if the list had nulls, FALSE otherwise -/proc/listclearnulls(list/L) - var/start_len = L.len - var/list/N = new(start_len) - L -= N - return L.len < start_len - -/* - * Returns list containing all the entries from first list that are not present in second. - * If skiprep = 1, repeated elements are treated as one. - * If either of arguments is not a list, returns null - */ -/proc/difflist(list/first, list/second, skiprep=0) - if(!islist(first) || !islist(second)) - return - var/list/result = new - if(skiprep) - for(var/e in first) - if(!(e in result) && !(e in second)) - result += e - else - result = first - second - return result - -/* - * Returns list containing entries that are in either list but not both. - * If skipref = 1, repeated elements are treated as one. - * If either of arguments is not a list, returns null - */ -/proc/uniquemergelist(list/first, list/second, skiprep=0) - if(!islist(first) || !islist(second)) - return - var/list/result = new - if(skiprep) - result = difflist(first, second, skiprep)+difflist(second, first, skiprep) - else - result = first ^ second - return result - -//Picks a random element from a list based on a weighting system: -//1. Adds up the total of weights for each element -//2. Gets a number between 1 and that total -//3. For each element in the list, subtracts its weighting from that number -//4. If that makes the number 0 or less, return that element. -/proc/pickweight(list/L) - var/total = 0 - var/item - for (item in L) - if (!L[item]) - L[item] = 1 - total += L[item] - - total = rand(1, total) - for (item in L) - total -=L [item] - if (total <= 0) - return item - - return null - -/proc/pickweightAllowZero(list/L) //The original pickweight proc will sometimes pick entries with zero weight. I'm not sure if changing the original will break anything, so I left it be. - var/total = 0 - var/item - for (item in L) - if (!L[item]) - L[item] = 0 - total += L[item] - - total = rand(0, total) - for (item in L) - total -=L [item] - if (total <= 0 && L[item]) - return item - - return null - -//Pick a random element from the list and remove it from the list. -/proc/pick_n_take(list/L) - RETURN_TYPE(L[_].type) - if(L.len) - var/picked = rand(1,L.len) - . = L[picked] - L.Cut(picked,picked+1) //Cut is far more efficient that Remove() - -//Returns the top(last) element from the list and removes it from the list (typical stack function) -/proc/pop(list/L) - if(L.len) - . = L[L.len] - L.len-- - -/proc/popleft(list/L) - if(L.len) - . = L[1] - L.Cut(1,2) - -/proc/sorted_insert(list/L, thing, comparator) - var/pos = L.len - while(pos > 0 && call(comparator)(thing, L[pos]) > 0) - pos-- - L.Insert(pos+1, thing) - -// Returns the next item in a list -/proc/next_list_item(item, list/L) - var/i - i = L.Find(item) - if(i == L.len) - i = 1 - else - i++ - return L[i] - -// Returns the previous item in a list -/proc/previous_list_item(item, list/L) - var/i - i = L.Find(item) - if(i == 1) - i = L.len - else - i-- - return L[i] - -//Randomize: Return the list in a random order -/proc/shuffle(list/L) - if(!L) - return - L = L.Copy() - - for(var/i=1, i= 0 ? /proc/cmp_ckey_asc : /proc/cmp_ckey_dsc) - -//Specifically for record datums in a list. -/proc/sortRecord(list/L, field = "name", order = 1) - GLOB.cmp_field = field - return sortTim(L, order >= 0 ? /proc/cmp_records_asc : /proc/cmp_records_dsc) - -//any value in a list -/proc/sortList(list/L, cmp=/proc/cmp_text_asc) - return sortTim(L.Copy(), cmp) - -//uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead -/proc/sortNames(list/L, order=1) - return sortTim(L.Copy(), order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc) - - -//Converts a bitfield to a list of numbers (or words if a wordlist is provided) -/proc/bitfield2list(bitfield = 0, list/wordlist) - var/list/r = list() - if(islist(wordlist)) - var/max = min(wordlist.len,16) - var/bit = 1 - for(var/i=1, i<=max, i++) - if(bitfield & bit) - r += wordlist[i] - bit = bit << 1 - else - for(var/bit=1, bit<=65535, bit = bit << 1) - if(bitfield & bit) - r += bit - - return r - -// Returns the key based on the index -#define KEYBYINDEX(L, index) (((index <= length(L)) && (index > 0)) ? L[index] : null) - -/proc/count_by_type(list/L, type) - var/i = 0 - for(var/T in L) - if(istype(T, type)) - i++ - return i - -/// Returns datum/data/record -/proc/find_record(field, value, list/L) - for(var/datum/data/record/R in L) - if(R.fields[field] == value) - return R - return FALSE - - -//Move a single element from position fromIndex within a list, to position toIndex -//All elements in the range [1,toIndex) before the move will be before the pivot afterwards -//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards -//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. -//fromIndex and toIndex must be in the range [1,L.len+1] -//This will preserve associations ~Carnie -/proc/moveElement(list/L, fromIndex, toIndex) - if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move - return - if(fromIndex > toIndex) - ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one - - L.Insert(toIndex, null) - L.Swap(fromIndex, toIndex) - L.Cut(fromIndex, fromIndex+1) - - -//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) -//Same as moveElement but for ranges of elements -//This will preserve associations ~Carnie -/proc/moveRange(list/L, fromIndex, toIndex, len=1) - var/distance = abs(toIndex - fromIndex) - if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements - if(fromIndex <= toIndex) - return //no need to move - fromIndex += len //we want to shift left instead of right - - for(var/i=0, i toIndex) - fromIndex += len - - for(var/i=0, i distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements - if(fromIndex < toIndex) - toIndex += len - else - fromIndex += len - - for(var/i=0, i fromIndex) - var/a = toIndex - toIndex = fromIndex - fromIndex = a - - for(var/i=0, i 513 -#error Remie said that lummox was adding a way to get a lists -#error contents via list.values, if that is true remove this -#error otherwise, update the version and bug lummox -#endif -//Flattens a keyed list into a list of it's contents -/proc/flatten_list(list/key_list) - if(!islist(key_list)) - return null - . = list() - for(var/key in key_list) - . |= key_list[key] - -/proc/make_associative(list/flat_list) - . = list() - for(var/thing in flat_list) - .[thing] = TRUE - -//Picks from the list, with some safeties, and returns the "default" arg if it fails -#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default) - -/* Definining a counter as a series of key -> numeric value entries - - * All these procs modify in place. -*/ - -/proc/counterlist_scale(list/L, scalar) - var/list/out = list() - for(var/key in L) - out[key] = L[key] * scalar - . = out - -/proc/counterlist_sum(list/L) - . = 0 - for(var/key in L) - . += L[key] - -/proc/counterlist_normalise(list/L) - var/avg = counterlist_sum(L) - if(avg != 0) - . = counterlist_scale(L, 1 / avg) - else - . = L - -/proc/counterlist_combine(list/L1, list/L2) - for(var/key in L2) - var/other_value = L2[key] - if(key in L1) - L1[key] += other_value - else - L1[key] = other_value - -/proc/assoc_list_strip_value(list/input) - var/list/ret = list() - for(var/key in input) - ret += key - return ret - -/proc/compare_list(list/l,list/d) - if(!islist(l) || !islist(d)) - return FALSE - - if(l.len != d.len) - return FALSE - - for(var/i in 1 to l.len) - if(l[i] != d[i]) - return FALSE - - return TRUE +/* + * Holds procs to help with list operations + * Contains groups: + * Misc + * Sorting + */ + +/* + * Misc + */ + +#define LAZYINITLIST(L) if (!L) L = list() +#define UNSETEMPTY(L) if (L && !length(L)) L = null +#define LAZYCOPY(L) (L ? L.Copy() : list() ) +#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } +#define LAZYADD(L, I) if(!L) { L = list(); } L += I; +#define LAZYOR(L, I) if(!L) { L = list(); } L |= I; +#define LAZYFIND(L, V) L ? L.Find(V) : 0 +#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) +#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; +#define LAZYLEN(L) length(L) +#define LAZYCLEARLIST(L) if(L) L.Cut() +#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) +#define reverseList(L) reverseRange(L.Copy()) +#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V); +#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } + +/// Passed into BINARY_INSERT to compare keys +#define COMPARE_KEY __BIN_LIST[__BIN_MID] +/// Passed into BINARY_INSERT to compare values +#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]] + +/**** + * Binary search sorted insert + * INPUT: Object to be inserted + * LIST: List to insert object into + * TYPECONT: The typepath of the contents of the list + * COMPARE: The object to compare against, usualy the same as INPUT + * COMPARISON: The variable on the objects to compare + */ +#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ + do {\ + var/list/__BIN_LIST = LIST;\ + var/__BIN_CTTL = length(__BIN_LIST);\ + if(!__BIN_CTTL) {\ + __BIN_LIST += INPUT;\ + } else {\ + var/__BIN_LEFT = 1;\ + var/__BIN_RIGHT = __BIN_CTTL;\ + var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + var/##TYPECONT/__BIN_ITEM;\ + while(__BIN_LEFT < __BIN_RIGHT) {\ + __BIN_ITEM = COMPTYPE;\ + if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + };\ + __BIN_ITEM = COMPTYPE;\ + __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\ + __BIN_LIST.Insert(__BIN_MID, INPUT);\ + };\ + } while(FALSE) + +//Returns a list in plain english as a string +/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) + var/total = length(input) + switch(total) + if (0) + return "[nothing_text]" + if (1) + return "[input[1]]" + if (2) + return "[input[1]][and_text][input[2]]" + else + var/output = "" + var/index = 1 + while (index < total) + if (index == total - 1) + comma_text = final_comma_text + + output += "[input[index]][comma_text]" + index++ + + return "[output][and_text][input[index]]" + +//Checks for specific types in a list +/proc/is_type_in_list(atom/A, list/L) + if(!LAZYLEN(L) || !A) + return FALSE + for(var/type in L) + if(istype(A, type)) + return TRUE + return FALSE + +//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') +#define is_type_in_typecache(A, L) (A && length(L) && L[(ispath(A) ? A : A:type)]) + +//returns a new list with only atoms that are in typecache L +/proc/typecache_filter_list(list/atoms, list/typecache) + RETURN_TYPE(/list) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if (typecache[A.type]) + . += A + +/proc/typecache_filter_list_reverse(list/atoms, list/typecache) + RETURN_TYPE(/list) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(!typecache[A.type]) + . += A + +/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(typecache_include[A.type] && !typecache_exclude[A.type]) + . += A + +//Like typesof() or subtypesof(), but returns a typecache instead of a list +/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE) + if(ispath(path)) + var/list/types = list() + if(only_root_path) + types = list(path) + else + types = ignore_root_path ? subtypesof(path) : typesof(path) + var/list/L = list() + for(var/T in types) + L[T] = TRUE + return L + else if(islist(path)) + var/list/pathlist = path + var/list/L = list() + if(ignore_root_path) + for(var/P in pathlist) + for(var/T in subtypesof(P)) + L[T] = TRUE + else + for(var/P in pathlist) + if(only_root_path) + L[P] = TRUE + else + for(var/T in typesof(P)) + L[T] = TRUE + return L + +//Removes any null entries from the list +//Returns TRUE if the list had nulls, FALSE otherwise +/proc/listclearnulls(list/L) + var/start_len = L.len + var/list/N = new(start_len) + L -= N + return L.len < start_len + +/* + * Returns list containing all the entries from first list that are not present in second. + * If skiprep = 1, repeated elements are treated as one. + * If either of arguments is not a list, returns null + */ +/proc/difflist(list/first, list/second, skiprep=0) + if(!islist(first) || !islist(second)) + return + var/list/result = new + if(skiprep) + for(var/e in first) + if(!(e in result) && !(e in second)) + result += e + else + result = first - second + return result + +/* + * Returns list containing entries that are in either list but not both. + * If skipref = 1, repeated elements are treated as one. + * If either of arguments is not a list, returns null + */ +/proc/uniquemergelist(list/first, list/second, skiprep=0) + if(!islist(first) || !islist(second)) + return + var/list/result = new + if(skiprep) + result = difflist(first, second, skiprep)+difflist(second, first, skiprep) + else + result = first ^ second + return result + +//Picks a random element from a list based on a weighting system: +//1. Adds up the total of weights for each element +//2. Gets a number between 1 and that total +//3. For each element in the list, subtracts its weighting from that number +//4. If that makes the number 0 or less, return that element. +/proc/pickweight(list/L) + var/total = 0 + var/item + for (item in L) + if (!L[item]) + L[item] = 1 + total += L[item] + + total = rand(1, total) + for (item in L) + total -=L [item] + if (total <= 0) + return item + + return null + +/proc/pickweightAllowZero(list/L) //The original pickweight proc will sometimes pick entries with zero weight. I'm not sure if changing the original will break anything, so I left it be. + var/total = 0 + var/item + for (item in L) + if (!L[item]) + L[item] = 0 + total += L[item] + + total = rand(0, total) + for (item in L) + total -=L [item] + if (total <= 0 && L[item]) + return item + + return null + +//Pick a random element from the list and remove it from the list. +/proc/pick_n_take(list/L) + RETURN_TYPE(L[_].type) + if(L.len) + var/picked = rand(1,L.len) + . = L[picked] + L.Cut(picked,picked+1) //Cut is far more efficient that Remove() + +//Returns the top(last) element from the list and removes it from the list (typical stack function) +/proc/pop(list/L) + if(L.len) + . = L[L.len] + L.len-- + +/proc/popleft(list/L) + if(L.len) + . = L[1] + L.Cut(1,2) + +/proc/sorted_insert(list/L, thing, comparator) + var/pos = L.len + while(pos > 0 && call(comparator)(thing, L[pos]) > 0) + pos-- + L.Insert(pos+1, thing) + +// Returns the next item in a list +/proc/next_list_item(item, list/L) + var/i + i = L.Find(item) + if(i == L.len) + i = 1 + else + i++ + return L[i] + +// Returns the previous item in a list +/proc/previous_list_item(item, list/L) + var/i + i = L.Find(item) + if(i == 1) + i = L.len + else + i-- + return L[i] + +//Randomize: Return the list in a random order +/proc/shuffle(list/L) + if(!L) + return + L = L.Copy() + + for(var/i=1, i= 0 ? /proc/cmp_ckey_asc : /proc/cmp_ckey_dsc) + +//Specifically for record datums in a list. +/proc/sortRecord(list/L, field = "name", order = 1) + GLOB.cmp_field = field + return sortTim(L, order >= 0 ? /proc/cmp_records_asc : /proc/cmp_records_dsc) + +//any value in a list +/proc/sortList(list/L, cmp=/proc/cmp_text_asc) + return sortTim(L.Copy(), cmp) + +//uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead +/proc/sortNames(list/L, order=1) + return sortTim(L.Copy(), order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc) + + +//Converts a bitfield to a list of numbers (or words if a wordlist is provided) +/proc/bitfield2list(bitfield = 0, list/wordlist) + var/list/r = list() + if(islist(wordlist)) + var/max = min(wordlist.len,16) + var/bit = 1 + for(var/i=1, i<=max, i++) + if(bitfield & bit) + r += wordlist[i] + bit = bit << 1 + else + for(var/bit=1, bit<=65535, bit = bit << 1) + if(bitfield & bit) + r += bit + + return r + +// Returns the key based on the index +#define KEYBYINDEX(L, index) (((index <= length(L)) && (index > 0)) ? L[index] : null) + +/proc/count_by_type(list/L, type) + var/i = 0 + for(var/T in L) + if(istype(T, type)) + i++ + return i + +/// Returns datum/data/record +/proc/find_record(field, value, list/L) + for(var/datum/data/record/R in L) + if(R.fields[field] == value) + return R + return FALSE + + +//Move a single element from position fromIndex within a list, to position toIndex +//All elements in the range [1,toIndex) before the move will be before the pivot afterwards +//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards +//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. +//fromIndex and toIndex must be in the range [1,L.len+1] +//This will preserve associations ~Carnie +/proc/moveElement(list/L, fromIndex, toIndex) + if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move + return + if(fromIndex > toIndex) + ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one + + L.Insert(toIndex, null) + L.Swap(fromIndex, toIndex) + L.Cut(fromIndex, fromIndex+1) + + +//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) +//Same as moveElement but for ranges of elements +//This will preserve associations ~Carnie +/proc/moveRange(list/L, fromIndex, toIndex, len=1) + var/distance = abs(toIndex - fromIndex) + if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements + if(fromIndex <= toIndex) + return //no need to move + fromIndex += len //we want to shift left instead of right + + for(var/i=0, i toIndex) + fromIndex += len + + for(var/i=0, i distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements + if(fromIndex < toIndex) + toIndex += len + else + fromIndex += len + + for(var/i=0, i fromIndex) + var/a = toIndex + toIndex = fromIndex + fromIndex = a + + for(var/i=0, i 513 +#error Remie said that lummox was adding a way to get a lists +#error contents via list.values, if that is true remove this +#error otherwise, update the version and bug lummox +#endif +//Flattens a keyed list into a list of it's contents +/proc/flatten_list(list/key_list) + if(!islist(key_list)) + return null + . = list() + for(var/key in key_list) + . |= key_list[key] + +/proc/make_associative(list/flat_list) + . = list() + for(var/thing in flat_list) + .[thing] = TRUE + +//Picks from the list, with some safeties, and returns the "default" arg if it fails +#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default) + +/* Definining a counter as a series of key -> numeric value entries + + * All these procs modify in place. +*/ + +/proc/counterlist_scale(list/L, scalar) + var/list/out = list() + for(var/key in L) + out[key] = L[key] * scalar + . = out + +/proc/counterlist_sum(list/L) + . = 0 + for(var/key in L) + . += L[key] + +/proc/counterlist_normalise(list/L) + var/avg = counterlist_sum(L) + if(avg != 0) + . = counterlist_scale(L, 1 / avg) + else + . = L + +/proc/counterlist_combine(list/L1, list/L2) + for(var/key in L2) + var/other_value = L2[key] + if(key in L1) + L1[key] += other_value + else + L1[key] = other_value + +/proc/assoc_list_strip_value(list/input) + var/list/ret = list() + for(var/key in input) + ret += key + return ret + +/proc/compare_list(list/l,list/d) + if(!islist(l) || !islist(d)) + return FALSE + + if(l.len != d.len) + return FALSE + + for(var/i in 1 to l.len) + if(l[i] != d[i]) + return FALSE + + return TRUE diff --git a/code/__HELPERS/_string_lists.dm b/code/__HELPERS/_string_lists.dm index 32c2dc213ec..cdbee26f9ba 100644 --- a/code/__HELPERS/_string_lists.dm +++ b/code/__HELPERS/_string_lists.dm @@ -1,42 +1,42 @@ -#define pick_list(FILE, KEY) (pick(strings(FILE, KEY))) -#define pick_list_weighted(FILE, KEY) (pickweight(strings(FILE, KEY))) -#define pick_list_replacements(FILE, KEY) (strings_replacement(FILE, KEY)) -#define json_load(FILE) (json_decode(file2text(FILE))) - -GLOBAL_LIST(string_cache) -GLOBAL_VAR(string_filename_current_key) - - -/proc/strings_replacement(filename, key, directory = "strings") - load_strings_file(filename, directory) - - if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) - var/response = pick(GLOB.string_cache[filename][key]) - var/regex/r = regex("@pick\\((\\D+?)\\)", "g") - response = r.Replace(response, /proc/strings_subkey_lookup) - return response - else - CRASH("strings list not found: [directory]/[filename], index=[key]") - -/proc/strings(filename as text, key as text, directory = "strings") - load_strings_file(filename, directory) - if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) - return GLOB.string_cache[filename][key] - else - CRASH("strings list not found: [directory]/[filename], index=[key]") - -/proc/strings_subkey_lookup(match, group1) - return pick_list(GLOB.string_filename_current_key, group1) - -/proc/load_strings_file(filename, directory = "strings") - GLOB.string_filename_current_key = filename - if(filename in GLOB.string_cache) - return //no work to do - - if(!GLOB.string_cache) - GLOB.string_cache = new - - if(fexists("[directory]/[filename]")) - GLOB.string_cache[filename] = json_load("[directory]/[filename]") - else - CRASH("file not found: [directory]/[filename]") +#define pick_list(FILE, KEY) (pick(strings(FILE, KEY))) +#define pick_list_weighted(FILE, KEY) (pickweight(strings(FILE, KEY))) +#define pick_list_replacements(FILE, KEY) (strings_replacement(FILE, KEY)) +#define json_load(FILE) (json_decode(file2text(FILE))) + +GLOBAL_LIST(string_cache) +GLOBAL_VAR(string_filename_current_key) + + +/proc/strings_replacement(filename, key, directory = "strings") + load_strings_file(filename, directory) + + if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) + var/response = pick(GLOB.string_cache[filename][key]) + var/regex/r = regex("@pick\\((\\D+?)\\)", "g") + response = r.Replace(response, /proc/strings_subkey_lookup) + return response + else + CRASH("strings list not found: [directory]/[filename], index=[key]") + +/proc/strings(filename as text, key as text, directory = "strings") + load_strings_file(filename, directory) + if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) + return GLOB.string_cache[filename][key] + else + CRASH("strings list not found: [directory]/[filename], index=[key]") + +/proc/strings_subkey_lookup(match, group1) + return pick_list(GLOB.string_filename_current_key, group1) + +/proc/load_strings_file(filename, directory = "strings") + GLOB.string_filename_current_key = filename + if(filename in GLOB.string_cache) + return //no work to do + + if(!GLOB.string_cache) + GLOB.string_cache = new + + if(fexists("[directory]/[filename]")) + GLOB.string_cache[filename] = json_load("[directory]/[filename]") + else + CRASH("file not found: [directory]/[filename]") diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index 623ed18a25c..521ce0d08e2 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -1,80 +1,80 @@ -//Sends resource files to client cache -/client/proc/getFiles(...) - for(var/file in args) - src << browse_rsc(file) - -/client/proc/browse_files(root_type=BROWSE_ROOT_ALL_LOGS, max_iterations=10, list/valid_extensions=list("txt","log","htm", "html")) - // wow why was this ever a parameter - var/root = "data/logs/" - switch(root_type) - if(BROWSE_ROOT_ALL_LOGS) - root = "data/logs/" - if(BROWSE_ROOT_CURRENT_LOGS) - root = "[GLOB.log_directory]/" - var/path = root - - for(var/i=0, iError: browse_files(): File not found/Invalid file([path]).") - return - - return path - -#define FTPDELAY 200 //200 tick delay to discourage spam -#define ADMIN_FTPDELAY_MODIFIER 0.5 //Admins get to spam files faster since we ~trust~ them! -/* This proc is a failsafe to prevent spamming of file requests. - It is just a timer that only permits a download every [FTPDELAY] ticks. - This can be changed by modifying FTPDELAY's value above. - - PLEASE USE RESPONSIBLY, Some log files can reach sizes of 4MB! */ -/client/proc/file_spam_check() - var/time_to_wait = GLOB.fileaccess_timer - world.time - if(time_to_wait > 0) - to_chat(src, "Error: file_spam_check(): Spam. Please wait [DisplayTimeText(time_to_wait)].") - return 1 - var/delay = FTPDELAY - if(holder) - delay *= ADMIN_FTPDELAY_MODIFIER - GLOB.fileaccess_timer = world.time + delay - return 0 -#undef FTPDELAY -#undef ADMIN_FTPDELAY_MODIFIER - -/proc/pathwalk(path) - var/list/jobs = list(path) - var/list/filenames = list() - - while(jobs.len) - var/current_dir = pop(jobs) - var/list/new_filenames = flist(current_dir) - for(var/new_filename in new_filenames) - // if filename ends in / it is a directory, append to currdir - if(findtext(new_filename, "/", -1)) - jobs += current_dir + new_filename - else - filenames += current_dir + new_filename - return filenames - -/proc/pathflatten(path) - return replacetext(path, "/", "_") +//Sends resource files to client cache +/client/proc/getFiles(...) + for(var/file in args) + src << browse_rsc(file) + +/client/proc/browse_files(root_type=BROWSE_ROOT_ALL_LOGS, max_iterations=10, list/valid_extensions=list("txt","log","htm", "html")) + // wow why was this ever a parameter + var/root = "data/logs/" + switch(root_type) + if(BROWSE_ROOT_ALL_LOGS) + root = "data/logs/" + if(BROWSE_ROOT_CURRENT_LOGS) + root = "[GLOB.log_directory]/" + var/path = root + + for(var/i=0, iError: browse_files(): File not found/Invalid file([path]).") + return + + return path + +#define FTPDELAY 200 //200 tick delay to discourage spam +#define ADMIN_FTPDELAY_MODIFIER 0.5 //Admins get to spam files faster since we ~trust~ them! +/* This proc is a failsafe to prevent spamming of file requests. + It is just a timer that only permits a download every [FTPDELAY] ticks. + This can be changed by modifying FTPDELAY's value above. + + PLEASE USE RESPONSIBLY, Some log files can reach sizes of 4MB! */ +/client/proc/file_spam_check() + var/time_to_wait = GLOB.fileaccess_timer - world.time + if(time_to_wait > 0) + to_chat(src, "Error: file_spam_check(): Spam. Please wait [DisplayTimeText(time_to_wait)].") + return 1 + var/delay = FTPDELAY + if(holder) + delay *= ADMIN_FTPDELAY_MODIFIER + GLOB.fileaccess_timer = world.time + delay + return 0 +#undef FTPDELAY +#undef ADMIN_FTPDELAY_MODIFIER + +/proc/pathwalk(path) + var/list/jobs = list(path) + var/list/filenames = list() + + while(jobs.len) + var/current_dir = pop(jobs) + var/list/new_filenames = flist(current_dir) + for(var/new_filename in new_filenames) + // if filename ends in / it is a directory, append to currdir + if(findtext(new_filename, "/", -1)) + jobs += current_dir + new_filename + else + filenames += current_dir + new_filename + return filenames + +/proc/pathflatten(path) + return replacetext(path, "/", "_") diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index b5b1ba3f03a..6d57d58afa3 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -1,581 +1,581 @@ -//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a -#define RANGE_TURFS(RADIUS, CENTER) \ - block( \ - locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \ - locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ - ) - -#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL)) -#define CULT_POLL_WAIT 2400 - -/proc/get_area_name(atom/X, format_text = FALSE) - var/area/A = isarea(X) ? X : get_area(X) - if(!A) - return null - return format_text ? format_text(A.name) : A.name - -/proc/get_areas_in_range(dist=0, atom/center=usr) - if(!dist) - var/turf/T = get_turf(center) - return T ? list(T.loc) : list() - if(!center) - return list() - - var/list/turfs = RANGE_TURFS(dist, center) - var/list/areas = list() - for(var/V in turfs) - var/turf/T = V - areas |= T.loc - return areas - -/proc/get_adjacent_areas(atom/center) - . = list(get_area(get_ranged_target_turf(center, NORTH, 1)), - get_area(get_ranged_target_turf(center, SOUTH, 1)), - get_area(get_ranged_target_turf(center, EAST, 1)), - get_area(get_ranged_target_turf(center, WEST, 1))) - listclearnulls(.) - -/proc/get_open_turf_in_dir(atom/center, dir) - var/turf/open/T = get_ranged_target_turf(center, dir, 1) - if(istype(T)) - return T - -/proc/get_adjacent_open_turfs(atom/center) - . = list(get_open_turf_in_dir(center, NORTH), - get_open_turf_in_dir(center, SOUTH), - get_open_turf_in_dir(center, EAST), - get_open_turf_in_dir(center, WEST)) - listclearnulls(.) - -/proc/get_adjacent_open_areas(atom/center) - . = list() - var/list/adjacent_turfs = get_adjacent_open_turfs(center) - for(var/I in adjacent_turfs) - . |= get_area(I) - -/** - * Get a bounding box of a list of atoms. - * - * Arguments: - * - atoms - List of atoms. Can accept output of view() and range() procs. - * - * Returns: list(x1, y1, x2, y2) - */ -/proc/get_bbox_of_atoms(list/atoms) - var/list/list_x = list() - var/list/list_y = list() - for(var/_a in atoms) - var/atom/a = _a - list_x += a.x - list_y += a.y - return list( - min(list_x), - min(list_y), - max(list_x), - max(list_y)) - -// Like view but bypasses luminosity check - -/proc/get_hear(range, atom/source) - - var/lum = source.luminosity - source.luminosity = 6 - - var/list/heard = view(range, source) - source.luminosity = lum - - return heard - -/proc/alone_in_area(area/the_area, mob/must_be_alone, check_type = /mob/living/carbon) - var/area/our_area = get_area(the_area) - for(var/C in GLOB.alive_mob_list) - if(!istype(C, check_type)) - continue - if(C == must_be_alone) - continue - if(our_area == get_area(C)) - return 0 - return 1 - -//We used to use linear regression to approximate the answer, but Mloc realized this was actually faster. -//And lo and behold, it is, and it's more accurate to boot. -/proc/cheap_hypotenuse(Ax,Ay,Bx,By) - return sqrt(abs(Ax - Bx)**2 + abs(Ay - By)**2) //A squared + B squared = C squared - -/proc/circlerange(center=usr,radius=3) - - var/turf/centerturf = get_turf(center) - var/list/turfs = new/list() - var/rsq = radius * (radius+0.5) - - for(var/atom/T in range(radius, centerturf)) - var/dx = T.x - centerturf.x - var/dy = T.y - centerturf.y - if(dx*dx + dy*dy <= rsq) - turfs += T - - //turfs += centerturf - return turfs - -/proc/circleview(center=usr,radius=3) - - var/turf/centerturf = get_turf(center) - var/list/atoms = new/list() - var/rsq = radius * (radius+0.5) - - for(var/atom/A in view(radius, centerturf)) - var/dx = A.x - centerturf.x - var/dy = A.y - centerturf.y - if(dx*dx + dy*dy <= rsq) - atoms += A - - //turfs += centerturf - return atoms - -/proc/get_dist_euclidian(atom/Loc1 as turf|mob|obj,atom/Loc2 as turf|mob|obj) - var/dx = Loc1.x - Loc2.x - var/dy = Loc1.y - Loc2.y - - var/dist = sqrt(dx**2 + dy**2) - - return dist - -/proc/circlerangeturfs(center=usr,radius=3) - - var/turf/centerturf = get_turf(center) - var/list/turfs = new/list() - var/rsq = radius * (radius+0.5) - - for(var/turf/T in range(radius, centerturf)) - var/dx = T.x - centerturf.x - var/dy = T.y - centerturf.y - if(dx*dx + dy*dy <= rsq) - turfs += T - return turfs - -/proc/circleviewturfs(center=usr,radius=3) //Is there even a diffrence between this proc and circlerangeturfs()? - - var/turf/centerturf = get_turf(center) - var/list/turfs = new/list() - var/rsq = radius * (radius+0.5) - - for(var/turf/T in view(radius, centerturf)) - var/dx = T.x - centerturf.x - var/dy = T.y - centerturf.y - if(dx*dx + dy*dy <= rsq) - turfs += T - return turfs - - -//This is the new version of recursive_mob_check, used for say(). -//The other proc was left intact because morgue trays use it. -//Sped this up again for real this time -/proc/recursive_hear_check(O) - var/list/processing_list = list(O) - . = list() - var/i = 0 - while(i < length(processing_list)) - var/atom/A = processing_list[++i] - if(A.flags_1 & HEAR_1) - . += A - processing_list += A.contents - -/** recursive_organ_check - * inputs: O (object to start with) - * outputs: - * description: A pseudo-recursive loop based off of the recursive mob check, this check looks for any organs held - * within 'O', toggling their frozen flag. This check excludes items held within other safe organ - * storage units, so that only the lowest level of container dictates whether we do or don't decompose - */ -/proc/recursive_organ_check(atom/O) - - var/list/processing_list = list(O) - var/list/processed_list = list() - var/index = 1 - var/obj/item/organ/found_organ - - while(index <= length(processing_list)) - - var/atom/A = processing_list[index] - - if(istype(A, /obj/item/organ)) - found_organ = A - found_organ.organ_flags ^= ORGAN_FROZEN - - else if(istype(A, /mob/living/carbon)) - var/mob/living/carbon/Q = A - for(var/organ in Q.internal_organs) - found_organ = organ - found_organ.organ_flags ^= ORGAN_FROZEN - - for(var/atom/B in A) //objects held within other objects are added to the processing list, unless that object is something that can hold organs safely - if(!processed_list[B] && !istype(B, /obj/structure/closet/crate/freezer) && !istype(B, /obj/structure/closet/secure_closet/freezer)) - processing_list+= B - - index++ - processed_list[A] = A - - return - -/proc/get_hearers_in_view(R, atom/source) - // Returns a list of hearers in view(R) from source (ignoring luminosity). Used in saycode. - var/turf/T = get_turf(source) - . = list() - if(!T) - return - var/list/processing_list = list() - if (R == 0) // if the range is zero, we know exactly where to look for, we can skip view - processing_list += T.contents // We can shave off one iteration by assuming turfs cannot hear - else // A variation of get_hear inlined here to take advantage of the compiler's fastpath for obj/mob in view - var/lum = T.luminosity - T.luminosity = 6 // This is the maximum luminosity - for(var/mob/M in view(R, T)) - processing_list += M - for(var/obj/O in view(R, T)) - processing_list += O - T.luminosity = lum - - var/i = 0 - while(i < length(processing_list)) // recursive_hear_check inlined here - var/atom/A = processing_list[++i] - if(A.flags_1 & HEAR_1) - . += A - SEND_SIGNAL(A, COMSIG_ATOM_HEARER_IN_VIEW, processing_list, .) - processing_list += A.contents - -/proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios) - . = list() - // Returns a list of mobs who can hear any of the radios given in @radios - for(var/obj/item/radio/R in radios) - if(R) - . |= get_hearers_in_view(R.canhear_range, R) - -#define SIGNV(X) ((X<0)?-1:1) - -/proc/inLineOfSight(X1,Y1,X2,Y2,Z=1,PX1=16.5,PY1=16.5,PX2=16.5,PY2=16.5) - var/turf/T - if(X1==X2) - if(Y1==Y2) - return 1 //Light cannot be blocked on same tile - else - var/s = SIGN(Y2-Y1) - Y1+=s - while(Y1!=Y2) - T=locate(X1,Y1,Z) - if(T.opacity) - return 0 - Y1+=s - else - var/m=(32*(Y2-Y1)+(PY2-PY1))/(32*(X2-X1)+(PX2-PX1)) - var/b=(Y1+PY1/32-0.015625)-m*(X1+PX1/32-0.015625) //In tiles - var/signX = SIGN(X2-X1) - var/signY = SIGN(Y2-Y1) - if(X1Choice registered: Yes.") - if(time_passed + poll_time <= world.time) - to_chat(M, "Sorry, you answered too late to be considered!") - SEND_SOUND(M, 'sound/machines/buzz-sigh.ogg') - candidates -= M - else - candidates += M - if(2) - to_chat(M, "Choice registered: No.") - candidates -= M - if(3) - var/list/L = GLOB.poll_ignore[ignore_category] - if(!L) - GLOB.poll_ignore[ignore_category] = list() - GLOB.poll_ignore[ignore_category] += M.ckey - to_chat(M, "Choice registered: Never for this round.") - candidates -= M - else - candidates -= M - -/proc/pollGhostCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE) - var/list/candidates = list() - - for(var/mob/dead/observer/G in GLOB.player_list) - candidates += G - - return pollCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category, flashwindow, candidates) - -/proc/pollCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE, list/group = null) - var/time_passed = world.time - if (!Question) - Question = "Would you like to be a special role?" - var/list/result = list() - for(var/m in group) - var/mob/M = m - if(!M.key || !M.client || (ignore_category && GLOB.poll_ignore[ignore_category] && (M.ckey in GLOB.poll_ignore[ignore_category]))) - continue - if(be_special_flag) - if(!(M.client.prefs) || !(be_special_flag in M.client.prefs.be_special)) - continue - if(gametypeCheck) - if(!gametypeCheck.age_check(M.client)) - continue - if(jobbanType) - if(is_banned_from(M.ckey, list(jobbanType, ROLE_SYNDICATE)) || QDELETED(M)) - continue - - showCandidatePollWindow(M, poll_time, Question, result, ignore_category, time_passed, flashwindow) - sleep(poll_time) - - //Check all our candidates, to make sure they didn't log off or get deleted during the wait period. - for(var/mob/M in result) - if(!M.key || !M.client) - result -= M - - listclearnulls(result) - - return result - -/proc/pollCandidatesForMob(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, mob/M, ignore_category = null) - var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) - if(!M || QDELETED(M) || !M.loc) - return list() - return L - -/proc/pollCandidatesForMobs(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, list/mobs, ignore_category = null) - var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) - var/i=1 - for(var/v in mobs) - var/atom/A = v - if(!A || QDELETED(A) || !A.loc) - mobs.Cut(i,i+1) - else - ++i - return L - -/proc/makeBody(mob/dead/observer/G_found) // Uses stripped down and bastardized code from respawn character - if(!G_found || !G_found.key) - return - - //First we spawn a dude. - var/mob/living/carbon/human/new_character = new//The mob being spawned. - SSjob.SendToLateJoin(new_character) - - G_found.client.prefs.copy_to(new_character) - new_character.dna.update_dna_identity() - new_character.key = G_found.key - - return new_character - -/proc/send_to_playing_players(thing) //sends a whatever to all playing players; use instead of to_chat(world, where needed) - for(var/M in GLOB.player_list) - if(M && !isnewplayer(M)) - to_chat(M, thing) - -/proc/window_flash(client/C, ignorepref = FALSE) - if(ismob(C)) - var/mob/M = C - if(M.client) - C = M.client - if(!C || (!C.prefs.windowflashing && !ignorepref)) - return - winset(C, "mainwindow", "flash=5") - -//Recursively checks if an item is inside a given type, even through layers of storage. Returns the atom if it finds it. -/proc/recursive_loc_check(atom/movable/target, type) - var/atom/A = target - if(istype(A, type)) - return A - - while(!istype(A.loc, type)) - if(!A.loc) - return - A = A.loc - - return A.loc - -/proc/AnnounceArrival(mob/living/carbon/human/character, rank) - if(!SSticker.IsRoundInProgress() || QDELETED(character)) - return - var/area/A = get_area(character) - deadchat_broadcast(" has arrived at the station at [A.name].", "[character.real_name] ([rank])", follow_target = character, message_type=DEADCHAT_ARRIVALRATTLE) - if((!GLOB.announcement_systems.len) || (!character.mind)) - return - if((character.mind.assigned_role == "Cyborg") || (character.mind.assigned_role == character.mind.special_role)) - return - - var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) - announcer.announce("ARRIVAL", character.real_name, rank, list()) //make the list empty to make it announce it in common - -/proc/lavaland_equipment_pressure_check(turf/T) - . = FALSE - if(!istype(T)) - return - var/datum/gas_mixture/environment = T.return_air() - if(!istype(environment)) - return - var/pressure = environment.return_pressure() - if(pressure <= LAVALAND_EQUIPMENT_EFFECT_PRESSURE) - . = TRUE - -/proc/ispipewire(item) - var/static/list/pire_wire = list( - /obj/machinery/atmospherics, - /obj/structure/disposalpipe, - /obj/structure/cable - ) - return (is_type_in_list(item, pire_wire)) - -// Find an obstruction free turf that's within the range of the center. Can also condition on if it is of a certain area type. -/proc/find_obstruction_free_location(range, atom/center, area/specific_area) - var/list/turfs = RANGE_TURFS(range, center) - var/list/possible_loc = list() - - for(var/turf/found_turf in turfs) - var/area/turf_area = get_area(found_turf) - - // We check if both the turf is a floor, and that it's actually in the area. - // We also want a location that's clear of any obstructions. - if (specific_area) - if (!istype(turf_area, specific_area)) - continue - - if (!isspaceturf(found_turf)) - if (!is_blocked_turf(found_turf)) - possible_loc.Add(found_turf) - - // Need at least one free location. - if (possible_loc.len < 1) - return FALSE - - return pick(possible_loc) - -/proc/power_fail(duration_min, duration_max) - for(var/P in GLOB.apcs_list) - var/obj/machinery/power/apc/C = P - if(C.cell && SSmapping.level_trait(C.z, ZTRAIT_STATION)) - var/area/A = C.area - if(GLOB.typecache_powerfailure_safe_areas[A.type]) - continue - - C.energy_fail(rand(duration_min,duration_max)) +//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a +#define RANGE_TURFS(RADIUS, CENTER) \ + block( \ + locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \ + locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ + ) + +#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL)) +#define CULT_POLL_WAIT 2400 + +/proc/get_area_name(atom/X, format_text = FALSE) + var/area/A = isarea(X) ? X : get_area(X) + if(!A) + return null + return format_text ? format_text(A.name) : A.name + +/proc/get_areas_in_range(dist=0, atom/center=usr) + if(!dist) + var/turf/T = get_turf(center) + return T ? list(T.loc) : list() + if(!center) + return list() + + var/list/turfs = RANGE_TURFS(dist, center) + var/list/areas = list() + for(var/V in turfs) + var/turf/T = V + areas |= T.loc + return areas + +/proc/get_adjacent_areas(atom/center) + . = list(get_area(get_ranged_target_turf(center, NORTH, 1)), + get_area(get_ranged_target_turf(center, SOUTH, 1)), + get_area(get_ranged_target_turf(center, EAST, 1)), + get_area(get_ranged_target_turf(center, WEST, 1))) + listclearnulls(.) + +/proc/get_open_turf_in_dir(atom/center, dir) + var/turf/open/T = get_ranged_target_turf(center, dir, 1) + if(istype(T)) + return T + +/proc/get_adjacent_open_turfs(atom/center) + . = list(get_open_turf_in_dir(center, NORTH), + get_open_turf_in_dir(center, SOUTH), + get_open_turf_in_dir(center, EAST), + get_open_turf_in_dir(center, WEST)) + listclearnulls(.) + +/proc/get_adjacent_open_areas(atom/center) + . = list() + var/list/adjacent_turfs = get_adjacent_open_turfs(center) + for(var/I in adjacent_turfs) + . |= get_area(I) + +/** + * Get a bounding box of a list of atoms. + * + * Arguments: + * - atoms - List of atoms. Can accept output of view() and range() procs. + * + * Returns: list(x1, y1, x2, y2) + */ +/proc/get_bbox_of_atoms(list/atoms) + var/list/list_x = list() + var/list/list_y = list() + for(var/_a in atoms) + var/atom/a = _a + list_x += a.x + list_y += a.y + return list( + min(list_x), + min(list_y), + max(list_x), + max(list_y)) + +// Like view but bypasses luminosity check + +/proc/get_hear(range, atom/source) + + var/lum = source.luminosity + source.luminosity = 6 + + var/list/heard = view(range, source) + source.luminosity = lum + + return heard + +/proc/alone_in_area(area/the_area, mob/must_be_alone, check_type = /mob/living/carbon) + var/area/our_area = get_area(the_area) + for(var/C in GLOB.alive_mob_list) + if(!istype(C, check_type)) + continue + if(C == must_be_alone) + continue + if(our_area == get_area(C)) + return 0 + return 1 + +//We used to use linear regression to approximate the answer, but Mloc realized this was actually faster. +//And lo and behold, it is, and it's more accurate to boot. +/proc/cheap_hypotenuse(Ax,Ay,Bx,By) + return sqrt(abs(Ax - Bx)**2 + abs(Ay - By)**2) //A squared + B squared = C squared + +/proc/circlerange(center=usr,radius=3) + + var/turf/centerturf = get_turf(center) + var/list/turfs = new/list() + var/rsq = radius * (radius+0.5) + + for(var/atom/T in range(radius, centerturf)) + var/dx = T.x - centerturf.x + var/dy = T.y - centerturf.y + if(dx*dx + dy*dy <= rsq) + turfs += T + + //turfs += centerturf + return turfs + +/proc/circleview(center=usr,radius=3) + + var/turf/centerturf = get_turf(center) + var/list/atoms = new/list() + var/rsq = radius * (radius+0.5) + + for(var/atom/A in view(radius, centerturf)) + var/dx = A.x - centerturf.x + var/dy = A.y - centerturf.y + if(dx*dx + dy*dy <= rsq) + atoms += A + + //turfs += centerturf + return atoms + +/proc/get_dist_euclidian(atom/Loc1 as turf|mob|obj,atom/Loc2 as turf|mob|obj) + var/dx = Loc1.x - Loc2.x + var/dy = Loc1.y - Loc2.y + + var/dist = sqrt(dx**2 + dy**2) + + return dist + +/proc/circlerangeturfs(center=usr,radius=3) + + var/turf/centerturf = get_turf(center) + var/list/turfs = new/list() + var/rsq = radius * (radius+0.5) + + for(var/turf/T in range(radius, centerturf)) + var/dx = T.x - centerturf.x + var/dy = T.y - centerturf.y + if(dx*dx + dy*dy <= rsq) + turfs += T + return turfs + +/proc/circleviewturfs(center=usr,radius=3) //Is there even a diffrence between this proc and circlerangeturfs()? + + var/turf/centerturf = get_turf(center) + var/list/turfs = new/list() + var/rsq = radius * (radius+0.5) + + for(var/turf/T in view(radius, centerturf)) + var/dx = T.x - centerturf.x + var/dy = T.y - centerturf.y + if(dx*dx + dy*dy <= rsq) + turfs += T + return turfs + + +//This is the new version of recursive_mob_check, used for say(). +//The other proc was left intact because morgue trays use it. +//Sped this up again for real this time +/proc/recursive_hear_check(O) + var/list/processing_list = list(O) + . = list() + var/i = 0 + while(i < length(processing_list)) + var/atom/A = processing_list[++i] + if(A.flags_1 & HEAR_1) + . += A + processing_list += A.contents + +/** recursive_organ_check + * inputs: O (object to start with) + * outputs: + * description: A pseudo-recursive loop based off of the recursive mob check, this check looks for any organs held + * within 'O', toggling their frozen flag. This check excludes items held within other safe organ + * storage units, so that only the lowest level of container dictates whether we do or don't decompose + */ +/proc/recursive_organ_check(atom/O) + + var/list/processing_list = list(O) + var/list/processed_list = list() + var/index = 1 + var/obj/item/organ/found_organ + + while(index <= length(processing_list)) + + var/atom/A = processing_list[index] + + if(istype(A, /obj/item/organ)) + found_organ = A + found_organ.organ_flags ^= ORGAN_FROZEN + + else if(istype(A, /mob/living/carbon)) + var/mob/living/carbon/Q = A + for(var/organ in Q.internal_organs) + found_organ = organ + found_organ.organ_flags ^= ORGAN_FROZEN + + for(var/atom/B in A) //objects held within other objects are added to the processing list, unless that object is something that can hold organs safely + if(!processed_list[B] && !istype(B, /obj/structure/closet/crate/freezer) && !istype(B, /obj/structure/closet/secure_closet/freezer)) + processing_list+= B + + index++ + processed_list[A] = A + + return + +/proc/get_hearers_in_view(R, atom/source) + // Returns a list of hearers in view(R) from source (ignoring luminosity). Used in saycode. + var/turf/T = get_turf(source) + . = list() + if(!T) + return + var/list/processing_list = list() + if (R == 0) // if the range is zero, we know exactly where to look for, we can skip view + processing_list += T.contents // We can shave off one iteration by assuming turfs cannot hear + else // A variation of get_hear inlined here to take advantage of the compiler's fastpath for obj/mob in view + var/lum = T.luminosity + T.luminosity = 6 // This is the maximum luminosity + for(var/mob/M in view(R, T)) + processing_list += M + for(var/obj/O in view(R, T)) + processing_list += O + T.luminosity = lum + + var/i = 0 + while(i < length(processing_list)) // recursive_hear_check inlined here + var/atom/A = processing_list[++i] + if(A.flags_1 & HEAR_1) + . += A + SEND_SIGNAL(A, COMSIG_ATOM_HEARER_IN_VIEW, processing_list, .) + processing_list += A.contents + +/proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios) + . = list() + // Returns a list of mobs who can hear any of the radios given in @radios + for(var/obj/item/radio/R in radios) + if(R) + . |= get_hearers_in_view(R.canhear_range, R) + +#define SIGNV(X) ((X<0)?-1:1) + +/proc/inLineOfSight(X1,Y1,X2,Y2,Z=1,PX1=16.5,PY1=16.5,PX2=16.5,PY2=16.5) + var/turf/T + if(X1==X2) + if(Y1==Y2) + return 1 //Light cannot be blocked on same tile + else + var/s = SIGN(Y2-Y1) + Y1+=s + while(Y1!=Y2) + T=locate(X1,Y1,Z) + if(T.opacity) + return 0 + Y1+=s + else + var/m=(32*(Y2-Y1)+(PY2-PY1))/(32*(X2-X1)+(PX2-PX1)) + var/b=(Y1+PY1/32-0.015625)-m*(X1+PX1/32-0.015625) //In tiles + var/signX = SIGN(X2-X1) + var/signY = SIGN(Y2-Y1) + if(X1Choice registered: Yes.") + if(time_passed + poll_time <= world.time) + to_chat(M, "Sorry, you answered too late to be considered!") + SEND_SOUND(M, 'sound/machines/buzz-sigh.ogg') + candidates -= M + else + candidates += M + if(2) + to_chat(M, "Choice registered: No.") + candidates -= M + if(3) + var/list/L = GLOB.poll_ignore[ignore_category] + if(!L) + GLOB.poll_ignore[ignore_category] = list() + GLOB.poll_ignore[ignore_category] += M.ckey + to_chat(M, "Choice registered: Never for this round.") + candidates -= M + else + candidates -= M + +/proc/pollGhostCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE) + var/list/candidates = list() + + for(var/mob/dead/observer/G in GLOB.player_list) + candidates += G + + return pollCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category, flashwindow, candidates) + +/proc/pollCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE, list/group = null) + var/time_passed = world.time + if (!Question) + Question = "Would you like to be a special role?" + var/list/result = list() + for(var/m in group) + var/mob/M = m + if(!M.key || !M.client || (ignore_category && GLOB.poll_ignore[ignore_category] && (M.ckey in GLOB.poll_ignore[ignore_category]))) + continue + if(be_special_flag) + if(!(M.client.prefs) || !(be_special_flag in M.client.prefs.be_special)) + continue + if(gametypeCheck) + if(!gametypeCheck.age_check(M.client)) + continue + if(jobbanType) + if(is_banned_from(M.ckey, list(jobbanType, ROLE_SYNDICATE)) || QDELETED(M)) + continue + + showCandidatePollWindow(M, poll_time, Question, result, ignore_category, time_passed, flashwindow) + sleep(poll_time) + + //Check all our candidates, to make sure they didn't log off or get deleted during the wait period. + for(var/mob/M in result) + if(!M.key || !M.client) + result -= M + + listclearnulls(result) + + return result + +/proc/pollCandidatesForMob(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, mob/M, ignore_category = null) + var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) + if(!M || QDELETED(M) || !M.loc) + return list() + return L + +/proc/pollCandidatesForMobs(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, list/mobs, ignore_category = null) + var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) + var/i=1 + for(var/v in mobs) + var/atom/A = v + if(!A || QDELETED(A) || !A.loc) + mobs.Cut(i,i+1) + else + ++i + return L + +/proc/makeBody(mob/dead/observer/G_found) // Uses stripped down and bastardized code from respawn character + if(!G_found || !G_found.key) + return + + //First we spawn a dude. + var/mob/living/carbon/human/new_character = new//The mob being spawned. + SSjob.SendToLateJoin(new_character) + + G_found.client.prefs.copy_to(new_character) + new_character.dna.update_dna_identity() + new_character.key = G_found.key + + return new_character + +/proc/send_to_playing_players(thing) //sends a whatever to all playing players; use instead of to_chat(world, where needed) + for(var/M in GLOB.player_list) + if(M && !isnewplayer(M)) + to_chat(M, thing) + +/proc/window_flash(client/C, ignorepref = FALSE) + if(ismob(C)) + var/mob/M = C + if(M.client) + C = M.client + if(!C || (!C.prefs.windowflashing && !ignorepref)) + return + winset(C, "mainwindow", "flash=5") + +//Recursively checks if an item is inside a given type, even through layers of storage. Returns the atom if it finds it. +/proc/recursive_loc_check(atom/movable/target, type) + var/atom/A = target + if(istype(A, type)) + return A + + while(!istype(A.loc, type)) + if(!A.loc) + return + A = A.loc + + return A.loc + +/proc/AnnounceArrival(mob/living/carbon/human/character, rank) + if(!SSticker.IsRoundInProgress() || QDELETED(character)) + return + var/area/A = get_area(character) + deadchat_broadcast(" has arrived at the station at [A.name].", "[character.real_name] ([rank])", follow_target = character, message_type=DEADCHAT_ARRIVALRATTLE) + if((!GLOB.announcement_systems.len) || (!character.mind)) + return + if((character.mind.assigned_role == "Cyborg") || (character.mind.assigned_role == character.mind.special_role)) + return + + var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) + announcer.announce("ARRIVAL", character.real_name, rank, list()) //make the list empty to make it announce it in common + +/proc/lavaland_equipment_pressure_check(turf/T) + . = FALSE + if(!istype(T)) + return + var/datum/gas_mixture/environment = T.return_air() + if(!istype(environment)) + return + var/pressure = environment.return_pressure() + if(pressure <= LAVALAND_EQUIPMENT_EFFECT_PRESSURE) + . = TRUE + +/proc/ispipewire(item) + var/static/list/pire_wire = list( + /obj/machinery/atmospherics, + /obj/structure/disposalpipe, + /obj/structure/cable + ) + return (is_type_in_list(item, pire_wire)) + +// Find an obstruction free turf that's within the range of the center. Can also condition on if it is of a certain area type. +/proc/find_obstruction_free_location(range, atom/center, area/specific_area) + var/list/turfs = RANGE_TURFS(range, center) + var/list/possible_loc = list() + + for(var/turf/found_turf in turfs) + var/area/turf_area = get_area(found_turf) + + // We check if both the turf is a floor, and that it's actually in the area. + // We also want a location that's clear of any obstructions. + if (specific_area) + if (!istype(turf_area, specific_area)) + continue + + if (!isspaceturf(found_turf)) + if (!is_blocked_turf(found_turf)) + possible_loc.Add(found_turf) + + // Need at least one free location. + if (possible_loc.len < 1) + return FALSE + + return pick(possible_loc) + +/proc/power_fail(duration_min, duration_max) + for(var/P in GLOB.apcs_list) + var/obj/machinery/power/apc/C = P + if(C.cell && SSmapping.level_trait(C.z, ZTRAIT_STATION)) + var/area/A = C.area + if(GLOB.typecache_powerfailure_safe_areas[A.type]) + continue + + C.energy_fail(rand(duration_min,duration_max)) diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 62ba0139e14..21de1e8c37b 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -1,71 +1,71 @@ -////////////////////////// -/////Initial Building///// -////////////////////////// - -/proc/make_datum_references_lists() - //hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) - //facial hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) - //underwear - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - //undershirt - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - //socks - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - //bodypart accessories (blizzard intensifies) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/lizard, GLOB.animated_tails_list_lizard) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/human, GLOB.animated_tails_list_human) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns,GLOB.horns_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.ears_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines_animated, GLOB.animated_spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.r_wings_list,roundstart = TRUE) - init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) - - //Species - for(var/spath in subtypesof(/datum/species)) - var/datum/species/S = new spath() - GLOB.species_list[S.id] = spath - sortList(GLOB.species_list, /proc/cmp_typepaths_asc) - - //Surgeries - for(var/path in subtypesof(/datum/surgery)) - GLOB.surgeries_list += new path() - sortList(GLOB.surgeries_list, /proc/cmp_typepaths_asc) - - // Keybindings - init_keybindings() - - GLOB.emote_list = init_emote_list() - - init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes) - -//creates every subtype of prototype (excluding prototype) and adds it to list L. -//if no list/L is provided, one is created. -/proc/init_subtypes(prototype, list/L) - if(!istype(L)) - L = list() - for(var/path in subtypesof(prototype)) - L += new path() - return L - -//returns a list of paths to every subtype of prototype (excluding prototype) -//if no list/L is provided, one is created. -/proc/init_paths(prototype, list/L) - if(!istype(L)) - L = list() - for(var/path in subtypesof(prototype)) - L+= path - return L - +////////////////////////// +/////Initial Building///// +////////////////////////// + +/proc/make_datum_references_lists() + //hair + init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) + //facial hair + init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) + //underwear + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + //undershirt + init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + //socks + init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) + //bodypart accessories (blizzard intensifies) + init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/lizard, GLOB.animated_tails_list_lizard) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/human, GLOB.animated_tails_list_human) + init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/horns,GLOB.horns_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.ears_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines_animated, GLOB.animated_spines_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.r_wings_list,roundstart = TRUE) + init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) + + //Species + for(var/spath in subtypesof(/datum/species)) + var/datum/species/S = new spath() + GLOB.species_list[S.id] = spath + sortList(GLOB.species_list, /proc/cmp_typepaths_asc) + + //Surgeries + for(var/path in subtypesof(/datum/surgery)) + GLOB.surgeries_list += new path() + sortList(GLOB.surgeries_list, /proc/cmp_typepaths_asc) + + // Keybindings + init_keybindings() + + GLOB.emote_list = init_emote_list() + + init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes) + +//creates every subtype of prototype (excluding prototype) and adds it to list L. +//if no list/L is provided, one is created. +/proc/init_subtypes(prototype, list/L) + if(!istype(L)) + L = list() + for(var/path in subtypesof(prototype)) + L += new path() + return L + +//returns a list of paths to every subtype of prototype (excluding prototype) +//if no list/L is provided, one is created. +/proc/init_paths(prototype, list/L) + if(!istype(L)) + L = list() + for(var/path in subtypesof(prototype)) + L+= path + return L + diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index d0f1d3e7686..bb46a9b6542 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1,1214 +1,1214 @@ -/* -IconProcs README - -A BYOND library for manipulating icons and colors - -by Lummox JR - -version 1.0 - -The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation -routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. - -CHANGING ICONS - -Several new procs have been added to the /icon datum to simplify working with icons. To use them, -remember you first need to setup an /icon var like so: - -GLOBAL_DATUM_INIT(my_icon, /icon, new('iconfile.dmi')) - -icon/ChangeOpacity(amount = 1) - A very common operation in DM is to try to make an icon more or less transparent. Making an icon more - transparent is usually much easier than making it less so, however. This proc basically is a frontend - for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() - can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. - If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. -icon/GrayScale() - Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. -icon/ColorTone(tone) - Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an - RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. - See also the global ColorTone() proc. -icon/MinColors(icon) - The icon is blended with a second icon where the minimum of each RGB pixel is the result. - Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. -icon/MaxColors(icon) - The icon is blended with a second icon where the maximum of each RGB pixel is the result. - Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. -icon/Opaque(background = "#000000") - All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. -icon/BecomeAlphaMask() - You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. - The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. -icon/AddAlphaMask(mask) - The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, - the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. - Where the mask is translucent, the current icon becomes more transparent. -icon/UseAlphaMask(mask, mode) - Sometimes you may want to take the alpha values from one icon and use them on a different icon. - This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change - so it has the same colors as before but uses the mask for opacity. - -COLOR MANAGEMENT AND HSV - -RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. - - * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to - cyan to blue to magenta and back to red. - * The saturation of a color is how much color is in it. A color with low saturation will be more gray, - and with no saturation at all it is a shade of gray. - * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, - and no value at all is black. - -Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three -hex digits because it ranges from 0 to 0x5FF. - - * 0 to 0xFF - red to yellow - * 0x100 to 0x1FF - yellow to green - * 0x200 to 0x2FF - green to cyan - * 0x300 to 0x3FF - cyan to blue - * 0x400 to 0x4FF - blue to magenta - * 0x500 to 0x5FF - magenta to red - -Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), -value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". - -More than one HSV color can match the same RGB color. - -Here are some procs you can use for color management: - -ReadRGB(rgb) - Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used - that includes alpha, the list will have a fourth item for the alpha value. -hsv(hue, sat, val, apha) - Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" - format. Alpha is not included in the result if null. -ReadHSV(rgb) - Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that - includes alpha, the list will have a fourth item for the alpha value. -RGBtoHSV(rgb) - Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". -HSVtoRGB(hsv) - Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". -BlendRGB(rgb1, rgb2, amount) - Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; - if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. - The returned value is an RGB or RGBA color. -BlendHSV(hsv1, hsv2, amount) - Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB - blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, - the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. - The returned value is an HSV or HSVA color. -BlendRGBasHSV(rgb1, rgb2, amount) - Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. -HueToAngle(hue) - Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. -AngleToHue(hue) - Converts an angle to a hue in the valid range. -RotateHue(hsv, angle) - Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. - (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value - as the original, but a different hue. -GrayScale(rgb) - Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. -ColorTone(rgb, tone) - Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of - using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. -*/ - -/* -Get Flat Icon DEMO by DarkCampainger - -This is a test for the get flat icon proc, modified approprietly for icons and their states. -Probably not a good idea to run this unless you want to see how the proc works in detail. -mob - icon = 'old_or_unused.dmi' - icon_state = "green" - - Login() - // Testing image underlays - underlays += image(icon='old_or_unused.dmi',icon_state="red") - underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) - underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) - - // Testing image overlays - add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32)) - add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32)) - add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32)) - - // Testing icon file overlays (defaults to mob's state) - add_overlay('_flat_demoIcons2.dmi') - - // Testing icon_state overlays (defaults to mob's icon) - add_overlay("white") - - // Testing dynamic icon overlays - var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") - I.Shift(NORTH,16,1) - add_overlay(I) - - // Testing dynamic image overlays - I=image(icon=I,pixel_x = -32, pixel_y = 32) - add_overlay(I) - - // Testing object types (and layers) - add_overlay(/obj/effect/overlay_test) - - loc = locate (10,10,1) - verb - Browse_Icon() - set name = "1. Browse Icon" - // Give it a name for the cache - var/iconName = "[ckey(src.name)]_flattened.dmi" - // Send the icon to src's local cache - src<

") - - Output_Icon() - set name = "2. Output Icon" - to_chat(src, "Icon is: [icon2base64html(getFlatIcon(src))]") - - Label_Icon() - set name = "3. Label Icon" - // Give it a name for the cache - var/iconName = "[ckey(src.name)]_flattened.dmi" - // Copy the file to the rsc manually - var/icon/I = fcopy_rsc(getFlatIcon(src)) - // Send the icon to src's local cache - src< transparent, gray -> translucent white, white -> solid white -/icon/proc/BecomeAlphaMask() - SwapColor(null, "#000000ff") // don't let transparent become gray - MapColors(0,0,0,0.3, 0,0,0,0.59, 0,0,0,0.11, 0,0,0,0, 1,1,1,0) - -/icon/proc/UseAlphaMask(mask) - Opaque() - AddAlphaMask(mask) - -/icon/proc/AddAlphaMask(mask) - var/icon/M = new(mask) - M.Blend("#ffffff", ICON_SUBTRACT) - // apply mask - Blend(M, ICON_ADD) - -/* - HSV format is represented as "#hhhssvv" or "#hhhssvvaa" - - Hue ranges from 0 to 0x5ff (1535) - - 0x000 = red - 0x100 = yellow - 0x200 = green - 0x300 = cyan - 0x400 = blue - 0x500 = magenta - - Saturation is from 0 to 0xff (255) - - More saturation = more color - Less saturation = more gray - - Value ranges from 0 to 0xff (255) - - Higher value means brighter color - */ - -/proc/ReadRGB(rgb) - if(!rgb) - return - - // interpret the HSV or HSVA value - var/i=1,start=1 - if(text2ascii(rgb) == 35) ++start // skip opening # - var/ch,which=0,r=0,g=0,b=0,alpha=0,usealpha - var/digits=0 - for(i=start, i<=length(rgb), ++i) - ch = text2ascii(rgb, i) - if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) - break - ++digits - if(digits == 8) - break - - var/single = digits < 6 - if(digits != 3 && digits != 4 && digits != 6 && digits != 8) - return - if(digits == 4 || digits == 8) - usealpha = 1 - for(i=start, digits>0, ++i) - ch = text2ascii(rgb, i) - if(ch >= 48 && ch <= 57) - ch -= 48 - else if(ch >= 65 && ch <= 70) - ch -= 55 - else if(ch >= 97 && ch <= 102) - ch -= 87 - else - break - --digits - switch(which) - if(0) - r = (r << 4) | ch - if(single) - r |= r << 4 - ++which - else if(!(digits & 1)) - ++which - if(1) - g = (g << 4) | ch - if(single) - g |= g << 4 - ++which - else if(!(digits & 1)) - ++which - if(2) - b = (b << 4) | ch - if(single) - b |= b << 4 - ++which - else if(!(digits & 1)) - ++which - if(3) - alpha = (alpha << 4) | ch - if(single) - alpha |= alpha << 4 - - . = list(r, g, b) - if(usealpha) - . += alpha - -/proc/ReadHSV(hsv) - if(!hsv) - return - - // interpret the HSV or HSVA value - var/i=1,start=1 - if(text2ascii(hsv) == 35) - ++start // skip opening # - var/ch,which=0,hue=0,sat=0,val=0,alpha=0,usealpha - var/digits=0 - for(i=start, i<=length(hsv), ++i) - ch = text2ascii(hsv, i) - if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) - break - ++digits - if(digits == 9) - break - if(digits > 7) - usealpha = 1 - if(digits <= 4) - ++which - if(digits <= 2) - ++which - for(i=start, digits>0, ++i) - ch = text2ascii(hsv, i) - if(ch >= 48 && ch <= 57) - ch -= 48 - else if(ch >= 65 && ch <= 70) - ch -= 55 - else if(ch >= 97 && ch <= 102) - ch -= 87 - else - break - --digits - switch(which) - if(0) - hue = (hue << 4) | ch - if(digits == (usealpha ? 6 : 4)) - ++which - if(1) - sat = (sat << 4) | ch - if(digits == (usealpha ? 4 : 2)) - ++which - if(2) - val = (val << 4) | ch - if(digits == (usealpha ? 2 : 0)) - ++which - if(3) - alpha = (alpha << 4) | ch - - . = list(hue, sat, val) - if(usealpha) - . += alpha - -/proc/HSVtoRGB(hsv) - if(!hsv) - return "#000000" - var/list/HSV = ReadHSV(hsv) - if(!HSV) - return "#000000" - - var/hue = HSV[1] - var/sat = HSV[2] - var/val = HSV[3] - - // Compress hue into easier-to-manage range - hue -= hue >> 8 - if(hue >= 0x5fa) - hue -= 0x5fa - - var/hi,mid,lo,r,g,b - hi = val - lo = round((255 - sat) * val / 255, 1) - mid = lo + round(abs(round(hue, 510) - hue) * (hi - lo) / 255, 1) - if(hue >= 765) - if(hue >= 1275) {r=hi; g=lo; b=mid} - else if(hue >= 1020) {r=mid; g=lo; b=hi } - else {r=lo; g=mid; b=hi } - else - if(hue >= 510) {r=lo; g=hi; b=mid} - else if(hue >= 255) {r=mid; g=hi; b=lo } - else {r=hi; g=mid; b=lo } - - return (HSV.len > 3) ? rgb(r,g,b,HSV[4]) : rgb(r,g,b) - -/proc/RGBtoHSV(rgb) - if(!rgb) - return "#0000000" - var/list/RGB = ReadRGB(rgb) - if(!RGB) - return "#0000000" - - var/r = RGB[1] - var/g = RGB[2] - var/b = RGB[3] - var/hi = max(r,g,b) - var/lo = min(r,g,b) - - var/val = hi - var/sat = hi ? round((hi-lo) * 255 / hi, 1) : 0 - var/hue = 0 - - if(sat) - var/dir - var/mid - if(hi == r) - if(lo == b) {hue=0; dir=1; mid=g} - else {hue=1535; dir=-1; mid=b} - else if(hi == g) - if(lo == r) {hue=512; dir=1; mid=b} - else {hue=511; dir=-1; mid=r} - else if(hi == b) - if(lo == g) {hue=1024; dir=1; mid=r} - else {hue=1023; dir=-1; mid=g} - hue += dir * round((mid-lo) * 255 / (hi-lo), 1) - - return hsv(hue, sat, val, (RGB.len>3 ? RGB[4] : null)) - -/proc/hsv(hue, sat, val, alpha) - if(hue < 0 || hue >= 1536) - hue %= 1536 - if(hue < 0) - hue += 1536 - if((hue & 0xFF) == 0xFF) - ++hue - if(hue >= 1536) - hue = 0 - if(sat < 0) - sat = 0 - if(sat > 255) - sat = 255 - if(val < 0) - val = 0 - if(val > 255) - val = 255 - . = "#" - . += TO_HEX_DIGIT(hue >> 8) - . += TO_HEX_DIGIT(hue >> 4) - . += TO_HEX_DIGIT(hue) - . += TO_HEX_DIGIT(sat >> 4) - . += TO_HEX_DIGIT(sat) - . += TO_HEX_DIGIT(val >> 4) - . += TO_HEX_DIGIT(val) - if(!isnull(alpha)) - if(alpha < 0) - alpha = 0 - if(alpha > 255) - alpha = 255 - . += TO_HEX_DIGIT(alpha >> 4) - . += TO_HEX_DIGIT(alpha) - -/* - Smooth blend between HSV colors - - amount=0 is the first color - amount=1 is the second color - amount=0.5 is directly between the two colors - - amount<0 or amount>1 are allowed - */ -/proc/BlendHSV(hsv1, hsv2, amount) - var/list/HSV1 = ReadHSV(hsv1) - var/list/HSV2 = ReadHSV(hsv2) - - // add missing alpha if needed - if(HSV1.len < HSV2.len) - HSV1 += 255 - else if(HSV2.len < HSV1.len) - HSV2 += 255 - var/usealpha = HSV1.len > 3 - - // normalize hsv values in case anything is screwy - if(HSV1[1] > 1536) - HSV1[1] %= 1536 - if(HSV2[1] > 1536) - HSV2[1] %= 1536 - if(HSV1[1] < 0) - HSV1[1] += 1536 - if(HSV2[1] < 0) - HSV2[1] += 1536 - if(!HSV1[3]) {HSV1[1] = 0; HSV1[2] = 0} - if(!HSV2[3]) {HSV2[1] = 0; HSV2[2] = 0} - - // no value for one color means don't change saturation - if(!HSV1[3]) - HSV1[2] = HSV2[2] - if(!HSV2[3]) - HSV2[2] = HSV1[2] - // no saturation for one color means don't change hues - if(!HSV1[2]) - HSV1[1] = HSV2[1] - if(!HSV2[2]) - HSV2[1] = HSV1[1] - - // Compress hues into easier-to-manage range - HSV1[1] -= HSV1[1] >> 8 - HSV2[1] -= HSV2[1] >> 8 - - var/hue_diff = HSV2[1] - HSV1[1] - if(hue_diff > 765) - hue_diff -= 1530 - else if(hue_diff <= -765) - hue_diff += 1530 - - var/hue = round(HSV1[1] + hue_diff * amount, 1) - var/sat = round(HSV1[2] + (HSV2[2] - HSV1[2]) * amount, 1) - var/val = round(HSV1[3] + (HSV2[3] - HSV1[3]) * amount, 1) - var/alpha = usealpha ? round(HSV1[4] + (HSV2[4] - HSV1[4]) * amount, 1) : null - - // normalize hue - if(hue < 0 || hue >= 1530) - hue %= 1530 - if(hue < 0) - hue += 1530 - // decompress hue - hue += round(hue / 255) - - return hsv(hue, sat, val, alpha) - -/* - Smooth blend between RGB colors - - amount=0 is the first color - amount=1 is the second color - amount=0.5 is directly between the two colors - - amount<0 or amount>1 are allowed - */ -/proc/BlendRGB(rgb1, rgb2, amount) - var/list/RGB1 = ReadRGB(rgb1) - var/list/RGB2 = ReadRGB(rgb2) - - // add missing alpha if needed - if(RGB1.len < RGB2.len) - RGB1 += 255 - else if(RGB2.len < RGB1.len) - RGB2 += 255 - var/usealpha = RGB1.len > 3 - - var/r = round(RGB1[1] + (RGB2[1] - RGB1[1]) * amount, 1) - var/g = round(RGB1[2] + (RGB2[2] - RGB1[2]) * amount, 1) - var/b = round(RGB1[3] + (RGB2[3] - RGB1[3]) * amount, 1) - var/alpha = usealpha ? round(RGB1[4] + (RGB2[4] - RGB1[4]) * amount, 1) : null - - return isnull(alpha) ? rgb(r, g, b) : rgb(r, g, b, alpha) - -/proc/BlendRGBasHSV(rgb1, rgb2, amount) - return HSVtoRGB(RGBtoHSV(rgb1), RGBtoHSV(rgb2), amount) - -/proc/HueToAngle(hue) - // normalize hsv in case anything is screwy - if(hue < 0 || hue >= 1536) - hue %= 1536 - if(hue < 0) - hue += 1536 - // Compress hue into easier-to-manage range - hue -= hue >> 8 - return hue / (1530/360) - -/proc/AngleToHue(angle) - // normalize hsv in case anything is screwy - if(angle < 0 || angle >= 360) - angle -= 360 * round(angle / 360) - var/hue = angle * (1530/360) - // Decompress hue - hue += round(hue / 255) - return hue - - -// positive angle rotates forward through red->green->blue -/proc/RotateHue(hsv, angle) - var/list/HSV = ReadHSV(hsv) - - // normalize hsv in case anything is screwy - if(HSV[1] >= 1536) - HSV[1] %= 1536 - if(HSV[1] < 0) - HSV[1] += 1536 - - // Compress hue into easier-to-manage range - HSV[1] -= HSV[1] >> 8 - - if(angle < 0 || angle >= 360) - angle -= 360 * round(angle / 360) - HSV[1] = round(HSV[1] + angle * (1530/360), 1) - - // normalize hue - if(HSV[1] < 0 || HSV[1] >= 1530) - HSV[1] %= 1530 - if(HSV[1] < 0) - HSV[1] += 1530 - // decompress hue - HSV[1] += round(HSV[1] / 255) - - return hsv(HSV[1], HSV[2], HSV[3], (HSV.len > 3 ? HSV[4] : null)) - -// Convert an rgb color to grayscale -/proc/GrayScale(rgb) - var/list/RGB = ReadRGB(rgb) - var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 - return (RGB.len > 3) ? rgb(gray, gray, gray, RGB[4]) : rgb(gray, gray, gray) - -// Change grayscale color to black->tone->white range -/proc/ColorTone(rgb, tone) - var/list/RGB = ReadRGB(rgb) - var/list/TONE = ReadRGB(tone) - - var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 - var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 - - if(gray <= tone_gray) - return BlendRGB("#000000", tone, gray/(tone_gray || 1)) - else - return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) - - -//Used in the OLD chem colour mixing algorithm -/proc/GetColors(hex) - hex = uppertext(hex) - // No alpha set? Default to full alpha. - if(length(hex) == 7) - hex += "FF" - var/hi1 = text2ascii(hex, 2) // R - var/lo1 = text2ascii(hex, 3) // R - var/hi2 = text2ascii(hex, 4) // G - var/lo2 = text2ascii(hex, 5) // G - var/hi3 = text2ascii(hex, 6) // B - var/lo3 = text2ascii(hex, 7) // B - var/hi4 = text2ascii(hex, 8) // A - var/lo4 = text2ascii(hex, 9) // A - return list(((hi1>= 65 ? hi1-55 : hi1-48)<<4) | (lo1 >= 65 ? lo1-55 : lo1-48), - ((hi2 >= 65 ? hi2-55 : hi2-48)<<4) | (lo2 >= 65 ? lo2-55 : lo2-48), - ((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48), - ((hi4 >= 65 ? hi4-55 : hi4-48)<<4) | (lo4 >= 65 ? lo4-55 : lo4-48)) - -/// Create a single [/icon] from a given [/atom] or [/image]. -/// -/// Very low-performance. Should usually only be used for HTML, where BYOND's -/// appearance system (overlays/underlays, etc.) is not available. -/// -/// Only the first argument is required. -/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE) - //Define... defines. - var/static/icon/flat_template = icon('icons/blanks/32x32.dmi', "nothing") - - #define BLANK icon(flat_template) - #define SET_SELF(SETVAR) do { \ - var/icon/SELF_ICON=icon(icon(curicon, curstate, base_icon_dir),"",SOUTH,no_anim?1:null); \ - if(A.alpha<255) { \ - SELF_ICON.Blend(rgb(255,255,255,A.alpha),ICON_MULTIPLY);\ - } \ - if(A.color) { \ - if(islist(A.color)){ \ - SELF_ICON.MapColors(arglist(A.color))} \ - else{ \ - SELF_ICON.Blend(A.color,ICON_MULTIPLY)} \ - } \ - ##SETVAR=SELF_ICON;\ - } while (0) - #define INDEX_X_LOW 1 - #define INDEX_X_HIGH 2 - #define INDEX_Y_LOW 3 - #define INDEX_Y_HIGH 4 - - #define flatX1 flat_size[INDEX_X_LOW] - #define flatX2 flat_size[INDEX_X_HIGH] - #define flatY1 flat_size[INDEX_Y_LOW] - #define flatY2 flat_size[INDEX_Y_HIGH] - #define addX1 add_size[INDEX_X_LOW] - #define addX2 add_size[INDEX_X_HIGH] - #define addY1 add_size[INDEX_Y_LOW] - #define addY2 add_size[INDEX_Y_HIGH] - - if(!A || A.alpha <= 0) - return BLANK - - var/noIcon = FALSE - if(start) - if(!defdir) - defdir = A.dir - if(!deficon) - deficon = A.icon - if(!defstate) - defstate = A.icon_state - if(!defblend) - defblend = A.blend_mode - - var/curicon = A.icon || deficon - var/curstate = A.icon_state || defstate - - if(!((noIcon = (!curicon)))) - var/curstates = icon_states(curicon) - if(!(curstate in curstates)) - if("" in curstates) - curstate = "" - else - noIcon = TRUE // Do not render this object. - - var/curdir - var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have - - //These should use the parent's direction (most likely) - if(!A.dir || A.dir == SOUTH) - curdir = defdir - else - curdir = A.dir - - //Try to remove/optimize this section ASAP, CPU hog. - //Determines if there's directionals. - if(!noIcon && curdir != SOUTH) - var/exist = FALSE - var/static/list/checkdirs = list(NORTH, EAST, WEST) - for(var/i in checkdirs) //Not using GLOB for a reason. - if(length(icon_states(icon(curicon, curstate, i)))) - exist = TRUE - break - if(!exist) - base_icon_dir = SOUTH - // - - if(!base_icon_dir) - base_icon_dir = curdir - - ASSERT(!BLEND_DEFAULT) //I might just be stupid but lets make sure this define is 0. - - var/curblend = A.blend_mode || defblend - - if(A.overlays.len || A.underlays.len) - var/icon/flat = BLANK - // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed - var/list/layers = list() - var/image/copy - // Add the atom's icon itself, without pixel_x/y offsets. - if(!noIcon) - copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=base_icon_dir) - copy.color = A.color - copy.alpha = A.alpha - copy.blend_mode = curblend - layers[copy] = A.layer - - // Loop through the underlays, then overlays, sorting them into the layers list - for(var/process_set in 0 to 1) - var/list/process = process_set? A.overlays : A.underlays - for(var/i in 1 to process.len) - var/image/current = process[i] - if(!current) - continue - if(current.plane != FLOAT_PLANE && current.plane != A.plane) - continue - var/current_layer = current.layer - if(current_layer < 0) - if(current_layer <= -1000) - return flat - current_layer = process_set + A.layer + current_layer / 1000 - - for(var/p in 1 to layers.len) - var/image/cmp = layers[p] - if(current_layer < layers[cmp]) - layers.Insert(p, current) - break - layers[current] = current_layer - - //sortTim(layers, /proc/cmp_image_layer_asc) - - var/icon/add // Icon of overlay being added - - // Current dimensions of flattened icon - var/list/flat_size = list(1, flat.Width(), 1, flat.Height()) - // Dimensions of overlay being added - var/list/add_size[4] - - for(var/V in layers) - var/image/I = V - if(I.alpha == 0) - continue - - if(I == copy) // 'I' is an /image based on the object being flattened. - curblend = BLEND_OVERLAY - add = icon(I.icon, I.icon_state, base_icon_dir) - else // 'I' is an appearance object. - add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim) - if(!add) - continue - // Find the new dimensions of the flat icon to fit the added overlay - add_size = list( - min(flatX1, I.pixel_x+1), - max(flatX2, I.pixel_x+add.Width()), - min(flatY1, I.pixel_y+1), - max(flatY2, I.pixel_y+add.Height()) - ) - - if(flat_size ~! add_size) - // Resize the flattened icon so the new icon fits - flat.Crop( - addX1 - flatX1 + 1, - addY1 - flatY1 + 1, - addX2 - flatX1 + 1, - addY2 - flatY1 + 1 - ) - flat_size = add_size.Copy() - - // Blend the overlay into the flattened icon - flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) - - if(A.color) - if(islist(A.color)) - flat.MapColors(arglist(A.color)) - else - flat.Blend(A.color, ICON_MULTIPLY) - - if(A.alpha < 255) - flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) - - if(no_anim) - //Clean up repeated frames - var/icon/cleaned = new /icon() - cleaned.Insert(flat, "", SOUTH, 1, 0) - . = cleaned - else - . = icon(flat, "", SOUTH) - else //There's no overlays. - if(!noIcon) - SET_SELF(.) - - //Clear defines - #undef flatX1 - #undef flatX2 - #undef flatY1 - #undef flatY2 - #undef addX1 - #undef addX2 - #undef addY1 - #undef addY2 - - #undef INDEX_X_LOW - #undef INDEX_X_HIGH - #undef INDEX_Y_LOW - #undef INDEX_Y_HIGH - - #undef BLANK - #undef SET_SELF - -/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N - var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A. - for(var/V in A.overlays)//For every image in overlays. var/image/I will not work, don't try it. - var/image/I = V - if(I.layer>A.layer) - continue//If layer is greater than what we need, skip it. - var/icon/image_overlay = new(I.icon,I.icon_state)//Blend only works with icon objects. - //Also, icons cannot directly set icon_state. Slower than changing variables but whatever. - alpha_mask.Blend(image_overlay,ICON_OR)//OR so they are lumped together in a nice overlay. - return alpha_mask//And now return the mask. - -/mob/proc/AddCamoOverlay(atom/A)//A is the atom which we are using as the overlay. - var/icon/opacity_icon = new(A.icon, A.icon_state)//Don't really care for overlays/underlays. - //Now we need to culculate overlays+underlays and add them together to form an image for a mask. - var/icon/alpha_mask = getIconMask(src)//getFlatIcon(src) is accurate but SLOW. Not designed for running each tick. This is also a little slow since it's blending a bunch of icons together but good enough. - opacity_icon.AddAlphaMask(alpha_mask)//Likely the main source of lag for this proc. Probably not designed to run each tick. - opacity_icon.ChangeOpacity(0.4)//Front end for MapColors so it's fast. 0.5 means half opacity and looks the best in my opinion. - for(var/i=0,i<5,i++)//And now we add it as overlays. It's faster than creating an icon and then merging it. - var/image/I = image("icon" = opacity_icon, "icon_state" = A.icon_state, "layer" = layer+0.8)//So it's above other stuff but below weapons and the like. - switch(i)//Now to determine offset so the result is somewhat blurred. - if(1) - I.pixel_x-- - if(2) - I.pixel_x++ - if(3) - I.pixel_y-- - if(4) - I.pixel_y++ - add_overlay(I)//And finally add the overlay. - -/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created. - var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon. - flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish. - flat_icon.ChangeOpacity(0.5)//Make it half transparent. - var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect. - flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. - return flat_icon - -//What the mob looks like as animated static -//By vg's ComicIronic -/proc/getStaticIcon(icon/A, safety = TRUE) - var/icon/flat_icon = safety ? A : new(A) - flat_icon.Blend(rgb(255,255,255)) - flat_icon.BecomeAlphaMask() - var/icon/static_icon = icon('icons/effects/effects.dmi', "static_base") - static_icon.AddAlphaMask(flat_icon) - return static_icon - -//What the mob looks like as a pitch black outline -//By vg's ComicIronic -/proc/getBlankIcon(icon/A, safety=1) - var/icon/flat_icon = safety ? A : new(A) - flat_icon.Blend(rgb(255,255,255)) - flat_icon.BecomeAlphaMask() - var/icon/blank_icon = new/icon('icons/effects/effects.dmi', "blank_base") - blank_icon.AddAlphaMask(flat_icon) - return blank_icon - - -//Dwarf fortress style icons based on letters (defaults to the first letter of the Atom's name) -//By vg's ComicIronic -/proc/getLetterImage(atom/A, letter= "", uppercase = 0) - if(!A) - return - - var/icon/atom_icon = new(A.icon, A.icon_state) - - if(!letter) - letter = A.name[1] - if(uppercase == 1) - letter = uppertext(letter) - else if(uppercase == -1) - letter = lowertext(letter) - - var/image/text_image = new(loc = A) - text_image.maptext = "[letter]" - text_image.pixel_x = 7 - text_image.pixel_y = 5 - qdel(atom_icon) - return text_image - -GLOBAL_LIST_EMPTY(friendly_animal_types) - -// Pick a random animal instead of the icon, and use that instead -/proc/getRandomAnimalImage(atom/A) - if(!GLOB.friendly_animal_types.len) - for(var/T in typesof(/mob/living/simple_animal)) - var/mob/living/simple_animal/SA = T - if(initial(SA.gold_core_spawnable) == FRIENDLY_SPAWN) - GLOB.friendly_animal_types += SA - - - var/mob/living/simple_animal/SA = pick(GLOB.friendly_animal_types) - - var/icon = initial(SA.icon) - var/icon_state = initial(SA.icon_state) - - var/image/final_image = image(icon, icon_state=icon_state, loc = A) - - if(ispath(SA, /mob/living/simple_animal/butterfly)) - final_image.color = rgb(rand(0,255), rand(0,255), rand(0,255)) - - // For debugging - final_image.text = initial(SA.name) - return final_image - -//Interface for using DrawBox() to draw 1 pixel on a coordinate. -//Returns the same icon specifed in the argument, but with the pixel drawn -/proc/DrawPixel(icon/I,colour,drawX,drawY) - if(!I) - return 0 - - var/Iwidth = I.Width() - var/Iheight = I.Height() - - if(drawX > Iwidth || drawX <= 0) - return 0 - if(drawY > Iheight || drawY <= 0) - return 0 - - I.DrawBox(colour,drawX, drawY) - return I - - -//Interface for easy drawing of one pixel on an atom. -/atom/proc/DrawPixelOn(colour, drawX, drawY) - var/icon/I = new(icon) - var/icon/J = DrawPixel(I, colour, drawX, drawY) - if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square. - icon = J - return J - return 0 - -//For creating consistent icons for human looking simple animals -/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null) - var/static/list/humanoid_icon_cache = list() - if(!icon_id || !humanoid_icon_cache[icon_id]) - var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key) - - if(prefs) - prefs.copy_to(body,TRUE,FALSE) - if(J) - J.equip(body, TRUE, FALSE, outfit_override = outfit_override) - else if (outfit_override) - body.equipOutfit(outfit_override,visualsOnly = TRUE) - - - var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing") - for(var/D in showDirs) - body.setDir(D) - COMPILE_OVERLAYS(body) - var/icon/partial = getFlatIcon(body) - out_icon.Insert(partial,dir=D) - - humanoid_icon_cache[icon_id] = out_icon - dummy_key? unset_busy_human_dummy(dummy_key) : qdel(body) - return out_icon - else - return humanoid_icon_cache[icon_id] - -//Hook, override to run code on- wait this is images -//Images have dir without being an atom, so they get their own definition. -//Lame. -/image/proc/setDir(newdir) - dir = newdir - -GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0,0,0))) - -/obj/proc/make_frozen_visual() - // Used to make the frozen item visuals for Freon. - if(resistance_flags & FREEZE_PROOF) - return - if(!(obj_flags & FROZEN)) - name = "frozen [name]" - add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY) - alpha -= 25 - obj_flags |= FROZEN - -//Assumes already frozed -/obj/proc/make_unfrozen() - if(obj_flags & FROZEN) - name = replacetext(name, "frozen ", "") - remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix) - alpha += 25 - obj_flags &= ~FROZEN - - -/// Save file used in icon2base64. Used for converting icons to base64. -GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of icons for the browser output - -/** - * Converts an icon to base64. Operates by putting the icon in the iconCache savefile, - * exporting it as text, and then parsing the base64 from that. - * (This relies on byond automatically storing icons in savefiles as base64) - */ -/proc/icon2base64(icon/icon) - if (!isicon(icon)) - return FALSE - WRITE_FILE(GLOB.dummySave["dummy"], icon) - var/iconData = GLOB.dummySave.ExportText("dummy") - var/list/partial = splittext(iconData, "{") - return replacetext(copytext_char(partial[2], 3, -5), "\n", "") - -/proc/icon2html(thing, target, icon_state, dir, frame = 1, moving = FALSE) - if (!thing) - return - - var/key - var/icon/I = thing - if (!target) - return - if (target == world) - target = GLOB.clients - - var/list/targets - if (!islist(target)) - targets = list(target) - else - targets = target - if (!targets.len) - return - if (!isicon(I)) - if (isfile(thing)) //special snowflake - var/name = sanitize_filename("[generate_asset_name(thing)].png") - if (!SSassets.cache[name]) - register_asset(name, thing) - for (var/thing2 in targets) - send_asset(thing2, key) - return "" - var/atom/A = thing - if (isnull(dir)) - dir = A.dir - if (isnull(icon_state)) - icon_state = A.icon_state - I = A.icon - if (ishuman(thing)) // Shitty workaround for a BYOND issue. - var/icon/temp = I - I = icon() - I.Insert(temp, dir = SOUTH) - dir = SOUTH - else - if (isnull(dir)) - dir = SOUTH - if (isnull(icon_state)) - icon_state = "" - - I = icon(I, icon_state, dir, frame, moving) - - key = "[generate_asset_name(I)].png" - if(!SSassets.cache[key]) - register_asset(key, I) - for (var/thing2 in targets) - send_asset(thing2, key) - - return "" - -/proc/icon2base64html(thing) - if (!thing) - return - var/static/list/bicon_cache = list() - if (isicon(thing)) - var/icon/I = thing - var/icon_base64 = icon2base64(I) - - if (I.Height() > world.icon_size || I.Width() > world.icon_size) - var/icon_md5 = md5(icon_base64) - icon_base64 = bicon_cache[icon_md5] - if (!icon_base64) // Doesn't exist yet, make it. - bicon_cache[icon_md5] = icon_base64 = icon2base64(I) - - - return "" - - // Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with. - var/atom/A = thing - var/key = "[istype(A.icon, /icon) ? "[REF(A.icon)]" : A.icon]:[A.icon_state]" - - - if (!bicon_cache[key]) // Doesn't exist, make it. - var/icon/I = icon(A.icon, A.icon_state, SOUTH, 1) - if (ishuman(thing)) // Shitty workaround for a BYOND issue. - var/icon/temp = I - I = icon() - I.Insert(temp, dir = SOUTH) - - bicon_cache[key] = icon2base64(I) - - return "" - -//Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs. -/proc/costly_icon2html(thing, target) - if (!thing) - return - - if (isicon(thing)) - return icon2html(thing, target) - - var/icon/I = getFlatIcon(thing) - return icon2html(I, target) +/* +IconProcs README + +A BYOND library for manipulating icons and colors + +by Lummox JR + +version 1.0 + +The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation +routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. + +CHANGING ICONS + +Several new procs have been added to the /icon datum to simplify working with icons. To use them, +remember you first need to setup an /icon var like so: + +GLOBAL_DATUM_INIT(my_icon, /icon, new('iconfile.dmi')) + +icon/ChangeOpacity(amount = 1) + A very common operation in DM is to try to make an icon more or less transparent. Making an icon more + transparent is usually much easier than making it less so, however. This proc basically is a frontend + for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() + can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. + If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. +icon/GrayScale() + Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. +icon/ColorTone(tone) + Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an + RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. + See also the global ColorTone() proc. +icon/MinColors(icon) + The icon is blended with a second icon where the minimum of each RGB pixel is the result. + Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. +icon/MaxColors(icon) + The icon is blended with a second icon where the maximum of each RGB pixel is the result. + Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. +icon/Opaque(background = "#000000") + All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. +icon/BecomeAlphaMask() + You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. + The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. +icon/AddAlphaMask(mask) + The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, + the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. + Where the mask is translucent, the current icon becomes more transparent. +icon/UseAlphaMask(mask, mode) + Sometimes you may want to take the alpha values from one icon and use them on a different icon. + This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change + so it has the same colors as before but uses the mask for opacity. + +COLOR MANAGEMENT AND HSV + +RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. + + * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to + cyan to blue to magenta and back to red. + * The saturation of a color is how much color is in it. A color with low saturation will be more gray, + and with no saturation at all it is a shade of gray. + * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, + and no value at all is black. + +Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three +hex digits because it ranges from 0 to 0x5FF. + + * 0 to 0xFF - red to yellow + * 0x100 to 0x1FF - yellow to green + * 0x200 to 0x2FF - green to cyan + * 0x300 to 0x3FF - cyan to blue + * 0x400 to 0x4FF - blue to magenta + * 0x500 to 0x5FF - magenta to red + +Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), +value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". + +More than one HSV color can match the same RGB color. + +Here are some procs you can use for color management: + +ReadRGB(rgb) + Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used + that includes alpha, the list will have a fourth item for the alpha value. +hsv(hue, sat, val, apha) + Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" + format. Alpha is not included in the result if null. +ReadHSV(rgb) + Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that + includes alpha, the list will have a fourth item for the alpha value. +RGBtoHSV(rgb) + Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". +HSVtoRGB(hsv) + Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". +BlendRGB(rgb1, rgb2, amount) + Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; + if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an RGB or RGBA color. +BlendHSV(hsv1, hsv2, amount) + Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB + blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, + the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an HSV or HSVA color. +BlendRGBasHSV(rgb1, rgb2, amount) + Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. +HueToAngle(hue) + Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. +AngleToHue(hue) + Converts an angle to a hue in the valid range. +RotateHue(hsv, angle) + Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. + (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value + as the original, but a different hue. +GrayScale(rgb) + Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. +ColorTone(rgb, tone) + Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of + using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. +*/ + +/* +Get Flat Icon DEMO by DarkCampainger + +This is a test for the get flat icon proc, modified approprietly for icons and their states. +Probably not a good idea to run this unless you want to see how the proc works in detail. +mob + icon = 'old_or_unused.dmi' + icon_state = "green" + + Login() + // Testing image underlays + underlays += image(icon='old_or_unused.dmi',icon_state="red") + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) + + // Testing image overlays + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32)) + + // Testing icon file overlays (defaults to mob's state) + add_overlay('_flat_demoIcons2.dmi') + + // Testing icon_state overlays (defaults to mob's icon) + add_overlay("white") + + // Testing dynamic icon overlays + var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") + I.Shift(NORTH,16,1) + add_overlay(I) + + // Testing dynamic image overlays + I=image(icon=I,pixel_x = -32, pixel_y = 32) + add_overlay(I) + + // Testing object types (and layers) + add_overlay(/obj/effect/overlay_test) + + loc = locate (10,10,1) + verb + Browse_Icon() + set name = "1. Browse Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Send the icon to src's local cache + src<

") + + Output_Icon() + set name = "2. Output Icon" + to_chat(src, "Icon is: [icon2base64html(getFlatIcon(src))]") + + Label_Icon() + set name = "3. Label Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Copy the file to the rsc manually + var/icon/I = fcopy_rsc(getFlatIcon(src)) + // Send the icon to src's local cache + src< transparent, gray -> translucent white, white -> solid white +/icon/proc/BecomeAlphaMask() + SwapColor(null, "#000000ff") // don't let transparent become gray + MapColors(0,0,0,0.3, 0,0,0,0.59, 0,0,0,0.11, 0,0,0,0, 1,1,1,0) + +/icon/proc/UseAlphaMask(mask) + Opaque() + AddAlphaMask(mask) + +/icon/proc/AddAlphaMask(mask) + var/icon/M = new(mask) + M.Blend("#ffffff", ICON_SUBTRACT) + // apply mask + Blend(M, ICON_ADD) + +/* + HSV format is represented as "#hhhssvv" or "#hhhssvvaa" + + Hue ranges from 0 to 0x5ff (1535) + + 0x000 = red + 0x100 = yellow + 0x200 = green + 0x300 = cyan + 0x400 = blue + 0x500 = magenta + + Saturation is from 0 to 0xff (255) + + More saturation = more color + Less saturation = more gray + + Value ranges from 0 to 0xff (255) + + Higher value means brighter color + */ + +/proc/ReadRGB(rgb) + if(!rgb) + return + + // interpret the HSV or HSVA value + var/i=1,start=1 + if(text2ascii(rgb) == 35) ++start // skip opening # + var/ch,which=0,r=0,g=0,b=0,alpha=0,usealpha + var/digits=0 + for(i=start, i<=length(rgb), ++i) + ch = text2ascii(rgb, i) + if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 8) + break + + var/single = digits < 6 + if(digits != 3 && digits != 4 && digits != 6 && digits != 8) + return + if(digits == 4 || digits == 8) + usealpha = 1 + for(i=start, digits>0, ++i) + ch = text2ascii(rgb, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + r = (r << 4) | ch + if(single) + r |= r << 4 + ++which + else if(!(digits & 1)) + ++which + if(1) + g = (g << 4) | ch + if(single) + g |= g << 4 + ++which + else if(!(digits & 1)) + ++which + if(2) + b = (b << 4) | ch + if(single) + b |= b << 4 + ++which + else if(!(digits & 1)) + ++which + if(3) + alpha = (alpha << 4) | ch + if(single) + alpha |= alpha << 4 + + . = list(r, g, b) + if(usealpha) + . += alpha + +/proc/ReadHSV(hsv) + if(!hsv) + return + + // interpret the HSV or HSVA value + var/i=1,start=1 + if(text2ascii(hsv) == 35) + ++start // skip opening # + var/ch,which=0,hue=0,sat=0,val=0,alpha=0,usealpha + var/digits=0 + for(i=start, i<=length(hsv), ++i) + ch = text2ascii(hsv, i) + if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 9) + break + if(digits > 7) + usealpha = 1 + if(digits <= 4) + ++which + if(digits <= 2) + ++which + for(i=start, digits>0, ++i) + ch = text2ascii(hsv, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + hue = (hue << 4) | ch + if(digits == (usealpha ? 6 : 4)) + ++which + if(1) + sat = (sat << 4) | ch + if(digits == (usealpha ? 4 : 2)) + ++which + if(2) + val = (val << 4) | ch + if(digits == (usealpha ? 2 : 0)) + ++which + if(3) + alpha = (alpha << 4) | ch + + . = list(hue, sat, val) + if(usealpha) + . += alpha + +/proc/HSVtoRGB(hsv) + if(!hsv) + return "#000000" + var/list/HSV = ReadHSV(hsv) + if(!HSV) + return "#000000" + + var/hue = HSV[1] + var/sat = HSV[2] + var/val = HSV[3] + + // Compress hue into easier-to-manage range + hue -= hue >> 8 + if(hue >= 0x5fa) + hue -= 0x5fa + + var/hi,mid,lo,r,g,b + hi = val + lo = round((255 - sat) * val / 255, 1) + mid = lo + round(abs(round(hue, 510) - hue) * (hi - lo) / 255, 1) + if(hue >= 765) + if(hue >= 1275) {r=hi; g=lo; b=mid} + else if(hue >= 1020) {r=mid; g=lo; b=hi } + else {r=lo; g=mid; b=hi } + else + if(hue >= 510) {r=lo; g=hi; b=mid} + else if(hue >= 255) {r=mid; g=hi; b=lo } + else {r=hi; g=mid; b=lo } + + return (HSV.len > 3) ? rgb(r,g,b,HSV[4]) : rgb(r,g,b) + +/proc/RGBtoHSV(rgb) + if(!rgb) + return "#0000000" + var/list/RGB = ReadRGB(rgb) + if(!RGB) + return "#0000000" + + var/r = RGB[1] + var/g = RGB[2] + var/b = RGB[3] + var/hi = max(r,g,b) + var/lo = min(r,g,b) + + var/val = hi + var/sat = hi ? round((hi-lo) * 255 / hi, 1) : 0 + var/hue = 0 + + if(sat) + var/dir + var/mid + if(hi == r) + if(lo == b) {hue=0; dir=1; mid=g} + else {hue=1535; dir=-1; mid=b} + else if(hi == g) + if(lo == r) {hue=512; dir=1; mid=b} + else {hue=511; dir=-1; mid=r} + else if(hi == b) + if(lo == g) {hue=1024; dir=1; mid=r} + else {hue=1023; dir=-1; mid=g} + hue += dir * round((mid-lo) * 255 / (hi-lo), 1) + + return hsv(hue, sat, val, (RGB.len>3 ? RGB[4] : null)) + +/proc/hsv(hue, sat, val, alpha) + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + if((hue & 0xFF) == 0xFF) + ++hue + if(hue >= 1536) + hue = 0 + if(sat < 0) + sat = 0 + if(sat > 255) + sat = 255 + if(val < 0) + val = 0 + if(val > 255) + val = 255 + . = "#" + . += TO_HEX_DIGIT(hue >> 8) + . += TO_HEX_DIGIT(hue >> 4) + . += TO_HEX_DIGIT(hue) + . += TO_HEX_DIGIT(sat >> 4) + . += TO_HEX_DIGIT(sat) + . += TO_HEX_DIGIT(val >> 4) + . += TO_HEX_DIGIT(val) + if(!isnull(alpha)) + if(alpha < 0) + alpha = 0 + if(alpha > 255) + alpha = 255 + . += TO_HEX_DIGIT(alpha >> 4) + . += TO_HEX_DIGIT(alpha) + +/* + Smooth blend between HSV colors + + amount=0 is the first color + amount=1 is the second color + amount=0.5 is directly between the two colors + + amount<0 or amount>1 are allowed + */ +/proc/BlendHSV(hsv1, hsv2, amount) + var/list/HSV1 = ReadHSV(hsv1) + var/list/HSV2 = ReadHSV(hsv2) + + // add missing alpha if needed + if(HSV1.len < HSV2.len) + HSV1 += 255 + else if(HSV2.len < HSV1.len) + HSV2 += 255 + var/usealpha = HSV1.len > 3 + + // normalize hsv values in case anything is screwy + if(HSV1[1] > 1536) + HSV1[1] %= 1536 + if(HSV2[1] > 1536) + HSV2[1] %= 1536 + if(HSV1[1] < 0) + HSV1[1] += 1536 + if(HSV2[1] < 0) + HSV2[1] += 1536 + if(!HSV1[3]) {HSV1[1] = 0; HSV1[2] = 0} + if(!HSV2[3]) {HSV2[1] = 0; HSV2[2] = 0} + + // no value for one color means don't change saturation + if(!HSV1[3]) + HSV1[2] = HSV2[2] + if(!HSV2[3]) + HSV2[2] = HSV1[2] + // no saturation for one color means don't change hues + if(!HSV1[2]) + HSV1[1] = HSV2[1] + if(!HSV2[2]) + HSV2[1] = HSV1[1] + + // Compress hues into easier-to-manage range + HSV1[1] -= HSV1[1] >> 8 + HSV2[1] -= HSV2[1] >> 8 + + var/hue_diff = HSV2[1] - HSV1[1] + if(hue_diff > 765) + hue_diff -= 1530 + else if(hue_diff <= -765) + hue_diff += 1530 + + var/hue = round(HSV1[1] + hue_diff * amount, 1) + var/sat = round(HSV1[2] + (HSV2[2] - HSV1[2]) * amount, 1) + var/val = round(HSV1[3] + (HSV2[3] - HSV1[3]) * amount, 1) + var/alpha = usealpha ? round(HSV1[4] + (HSV2[4] - HSV1[4]) * amount, 1) : null + + // normalize hue + if(hue < 0 || hue >= 1530) + hue %= 1530 + if(hue < 0) + hue += 1530 + // decompress hue + hue += round(hue / 255) + + return hsv(hue, sat, val, alpha) + +/* + Smooth blend between RGB colors + + amount=0 is the first color + amount=1 is the second color + amount=0.5 is directly between the two colors + + amount<0 or amount>1 are allowed + */ +/proc/BlendRGB(rgb1, rgb2, amount) + var/list/RGB1 = ReadRGB(rgb1) + var/list/RGB2 = ReadRGB(rgb2) + + // add missing alpha if needed + if(RGB1.len < RGB2.len) + RGB1 += 255 + else if(RGB2.len < RGB1.len) + RGB2 += 255 + var/usealpha = RGB1.len > 3 + + var/r = round(RGB1[1] + (RGB2[1] - RGB1[1]) * amount, 1) + var/g = round(RGB1[2] + (RGB2[2] - RGB1[2]) * amount, 1) + var/b = round(RGB1[3] + (RGB2[3] - RGB1[3]) * amount, 1) + var/alpha = usealpha ? round(RGB1[4] + (RGB2[4] - RGB1[4]) * amount, 1) : null + + return isnull(alpha) ? rgb(r, g, b) : rgb(r, g, b, alpha) + +/proc/BlendRGBasHSV(rgb1, rgb2, amount) + return HSVtoRGB(RGBtoHSV(rgb1), RGBtoHSV(rgb2), amount) + +/proc/HueToAngle(hue) + // normalize hsv in case anything is screwy + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + // Compress hue into easier-to-manage range + hue -= hue >> 8 + return hue / (1530/360) + +/proc/AngleToHue(angle) + // normalize hsv in case anything is screwy + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + var/hue = angle * (1530/360) + // Decompress hue + hue += round(hue / 255) + return hue + + +// positive angle rotates forward through red->green->blue +/proc/RotateHue(hsv, angle) + var/list/HSV = ReadHSV(hsv) + + // normalize hsv in case anything is screwy + if(HSV[1] >= 1536) + HSV[1] %= 1536 + if(HSV[1] < 0) + HSV[1] += 1536 + + // Compress hue into easier-to-manage range + HSV[1] -= HSV[1] >> 8 + + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + HSV[1] = round(HSV[1] + angle * (1530/360), 1) + + // normalize hue + if(HSV[1] < 0 || HSV[1] >= 1530) + HSV[1] %= 1530 + if(HSV[1] < 0) + HSV[1] += 1530 + // decompress hue + HSV[1] += round(HSV[1] / 255) + + return hsv(HSV[1], HSV[2], HSV[3], (HSV.len > 3 ? HSV[4] : null)) + +// Convert an rgb color to grayscale +/proc/GrayScale(rgb) + var/list/RGB = ReadRGB(rgb) + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + return (RGB.len > 3) ? rgb(gray, gray, gray, RGB[4]) : rgb(gray, gray, gray) + +// Change grayscale color to black->tone->white range +/proc/ColorTone(rgb, tone) + var/list/RGB = ReadRGB(rgb) + var/list/TONE = ReadRGB(tone) + + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 + + if(gray <= tone_gray) + return BlendRGB("#000000", tone, gray/(tone_gray || 1)) + else + return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) + + +//Used in the OLD chem colour mixing algorithm +/proc/GetColors(hex) + hex = uppertext(hex) + // No alpha set? Default to full alpha. + if(length(hex) == 7) + hex += "FF" + var/hi1 = text2ascii(hex, 2) // R + var/lo1 = text2ascii(hex, 3) // R + var/hi2 = text2ascii(hex, 4) // G + var/lo2 = text2ascii(hex, 5) // G + var/hi3 = text2ascii(hex, 6) // B + var/lo3 = text2ascii(hex, 7) // B + var/hi4 = text2ascii(hex, 8) // A + var/lo4 = text2ascii(hex, 9) // A + return list(((hi1>= 65 ? hi1-55 : hi1-48)<<4) | (lo1 >= 65 ? lo1-55 : lo1-48), + ((hi2 >= 65 ? hi2-55 : hi2-48)<<4) | (lo2 >= 65 ? lo2-55 : lo2-48), + ((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48), + ((hi4 >= 65 ? hi4-55 : hi4-48)<<4) | (lo4 >= 65 ? lo4-55 : lo4-48)) + +/// Create a single [/icon] from a given [/atom] or [/image]. +/// +/// Very low-performance. Should usually only be used for HTML, where BYOND's +/// appearance system (overlays/underlays, etc.) is not available. +/// +/// Only the first argument is required. +/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE) + //Define... defines. + var/static/icon/flat_template = icon('icons/blanks/32x32.dmi', "nothing") + + #define BLANK icon(flat_template) + #define SET_SELF(SETVAR) do { \ + var/icon/SELF_ICON=icon(icon(curicon, curstate, base_icon_dir),"",SOUTH,no_anim?1:null); \ + if(A.alpha<255) { \ + SELF_ICON.Blend(rgb(255,255,255,A.alpha),ICON_MULTIPLY);\ + } \ + if(A.color) { \ + if(islist(A.color)){ \ + SELF_ICON.MapColors(arglist(A.color))} \ + else{ \ + SELF_ICON.Blend(A.color,ICON_MULTIPLY)} \ + } \ + ##SETVAR=SELF_ICON;\ + } while (0) + #define INDEX_X_LOW 1 + #define INDEX_X_HIGH 2 + #define INDEX_Y_LOW 3 + #define INDEX_Y_HIGH 4 + + #define flatX1 flat_size[INDEX_X_LOW] + #define flatX2 flat_size[INDEX_X_HIGH] + #define flatY1 flat_size[INDEX_Y_LOW] + #define flatY2 flat_size[INDEX_Y_HIGH] + #define addX1 add_size[INDEX_X_LOW] + #define addX2 add_size[INDEX_X_HIGH] + #define addY1 add_size[INDEX_Y_LOW] + #define addY2 add_size[INDEX_Y_HIGH] + + if(!A || A.alpha <= 0) + return BLANK + + var/noIcon = FALSE + if(start) + if(!defdir) + defdir = A.dir + if(!deficon) + deficon = A.icon + if(!defstate) + defstate = A.icon_state + if(!defblend) + defblend = A.blend_mode + + var/curicon = A.icon || deficon + var/curstate = A.icon_state || defstate + + if(!((noIcon = (!curicon)))) + var/curstates = icon_states(curicon) + if(!(curstate in curstates)) + if("" in curstates) + curstate = "" + else + noIcon = TRUE // Do not render this object. + + var/curdir + var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have + + //These should use the parent's direction (most likely) + if(!A.dir || A.dir == SOUTH) + curdir = defdir + else + curdir = A.dir + + //Try to remove/optimize this section ASAP, CPU hog. + //Determines if there's directionals. + if(!noIcon && curdir != SOUTH) + var/exist = FALSE + var/static/list/checkdirs = list(NORTH, EAST, WEST) + for(var/i in checkdirs) //Not using GLOB for a reason. + if(length(icon_states(icon(curicon, curstate, i)))) + exist = TRUE + break + if(!exist) + base_icon_dir = SOUTH + // + + if(!base_icon_dir) + base_icon_dir = curdir + + ASSERT(!BLEND_DEFAULT) //I might just be stupid but lets make sure this define is 0. + + var/curblend = A.blend_mode || defblend + + if(A.overlays.len || A.underlays.len) + var/icon/flat = BLANK + // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed + var/list/layers = list() + var/image/copy + // Add the atom's icon itself, without pixel_x/y offsets. + if(!noIcon) + copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=base_icon_dir) + copy.color = A.color + copy.alpha = A.alpha + copy.blend_mode = curblend + layers[copy] = A.layer + + // Loop through the underlays, then overlays, sorting them into the layers list + for(var/process_set in 0 to 1) + var/list/process = process_set? A.overlays : A.underlays + for(var/i in 1 to process.len) + var/image/current = process[i] + if(!current) + continue + if(current.plane != FLOAT_PLANE && current.plane != A.plane) + continue + var/current_layer = current.layer + if(current_layer < 0) + if(current_layer <= -1000) + return flat + current_layer = process_set + A.layer + current_layer / 1000 + + for(var/p in 1 to layers.len) + var/image/cmp = layers[p] + if(current_layer < layers[cmp]) + layers.Insert(p, current) + break + layers[current] = current_layer + + //sortTim(layers, /proc/cmp_image_layer_asc) + + var/icon/add // Icon of overlay being added + + // Current dimensions of flattened icon + var/list/flat_size = list(1, flat.Width(), 1, flat.Height()) + // Dimensions of overlay being added + var/list/add_size[4] + + for(var/V in layers) + var/image/I = V + if(I.alpha == 0) + continue + + if(I == copy) // 'I' is an /image based on the object being flattened. + curblend = BLEND_OVERLAY + add = icon(I.icon, I.icon_state, base_icon_dir) + else // 'I' is an appearance object. + add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim) + if(!add) + continue + // Find the new dimensions of the flat icon to fit the added overlay + add_size = list( + min(flatX1, I.pixel_x+1), + max(flatX2, I.pixel_x+add.Width()), + min(flatY1, I.pixel_y+1), + max(flatY2, I.pixel_y+add.Height()) + ) + + if(flat_size ~! add_size) + // Resize the flattened icon so the new icon fits + flat.Crop( + addX1 - flatX1 + 1, + addY1 - flatY1 + 1, + addX2 - flatX1 + 1, + addY2 - flatY1 + 1 + ) + flat_size = add_size.Copy() + + // Blend the overlay into the flattened icon + flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) + + if(A.color) + if(islist(A.color)) + flat.MapColors(arglist(A.color)) + else + flat.Blend(A.color, ICON_MULTIPLY) + + if(A.alpha < 255) + flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) + + if(no_anim) + //Clean up repeated frames + var/icon/cleaned = new /icon() + cleaned.Insert(flat, "", SOUTH, 1, 0) + . = cleaned + else + . = icon(flat, "", SOUTH) + else //There's no overlays. + if(!noIcon) + SET_SELF(.) + + //Clear defines + #undef flatX1 + #undef flatX2 + #undef flatY1 + #undef flatY2 + #undef addX1 + #undef addX2 + #undef addY1 + #undef addY2 + + #undef INDEX_X_LOW + #undef INDEX_X_HIGH + #undef INDEX_Y_LOW + #undef INDEX_Y_HIGH + + #undef BLANK + #undef SET_SELF + +/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N + var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A. + for(var/V in A.overlays)//For every image in overlays. var/image/I will not work, don't try it. + var/image/I = V + if(I.layer>A.layer) + continue//If layer is greater than what we need, skip it. + var/icon/image_overlay = new(I.icon,I.icon_state)//Blend only works with icon objects. + //Also, icons cannot directly set icon_state. Slower than changing variables but whatever. + alpha_mask.Blend(image_overlay,ICON_OR)//OR so they are lumped together in a nice overlay. + return alpha_mask//And now return the mask. + +/mob/proc/AddCamoOverlay(atom/A)//A is the atom which we are using as the overlay. + var/icon/opacity_icon = new(A.icon, A.icon_state)//Don't really care for overlays/underlays. + //Now we need to culculate overlays+underlays and add them together to form an image for a mask. + var/icon/alpha_mask = getIconMask(src)//getFlatIcon(src) is accurate but SLOW. Not designed for running each tick. This is also a little slow since it's blending a bunch of icons together but good enough. + opacity_icon.AddAlphaMask(alpha_mask)//Likely the main source of lag for this proc. Probably not designed to run each tick. + opacity_icon.ChangeOpacity(0.4)//Front end for MapColors so it's fast. 0.5 means half opacity and looks the best in my opinion. + for(var/i=0,i<5,i++)//And now we add it as overlays. It's faster than creating an icon and then merging it. + var/image/I = image("icon" = opacity_icon, "icon_state" = A.icon_state, "layer" = layer+0.8)//So it's above other stuff but below weapons and the like. + switch(i)//Now to determine offset so the result is somewhat blurred. + if(1) + I.pixel_x-- + if(2) + I.pixel_x++ + if(3) + I.pixel_y-- + if(4) + I.pixel_y++ + add_overlay(I)//And finally add the overlay. + +/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created. + var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon. + flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish. + flat_icon.ChangeOpacity(0.5)//Make it half transparent. + var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect. + flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. + return flat_icon + +//What the mob looks like as animated static +//By vg's ComicIronic +/proc/getStaticIcon(icon/A, safety = TRUE) + var/icon/flat_icon = safety ? A : new(A) + flat_icon.Blend(rgb(255,255,255)) + flat_icon.BecomeAlphaMask() + var/icon/static_icon = icon('icons/effects/effects.dmi', "static_base") + static_icon.AddAlphaMask(flat_icon) + return static_icon + +//What the mob looks like as a pitch black outline +//By vg's ComicIronic +/proc/getBlankIcon(icon/A, safety=1) + var/icon/flat_icon = safety ? A : new(A) + flat_icon.Blend(rgb(255,255,255)) + flat_icon.BecomeAlphaMask() + var/icon/blank_icon = new/icon('icons/effects/effects.dmi', "blank_base") + blank_icon.AddAlphaMask(flat_icon) + return blank_icon + + +//Dwarf fortress style icons based on letters (defaults to the first letter of the Atom's name) +//By vg's ComicIronic +/proc/getLetterImage(atom/A, letter= "", uppercase = 0) + if(!A) + return + + var/icon/atom_icon = new(A.icon, A.icon_state) + + if(!letter) + letter = A.name[1] + if(uppercase == 1) + letter = uppertext(letter) + else if(uppercase == -1) + letter = lowertext(letter) + + var/image/text_image = new(loc = A) + text_image.maptext = "[letter]" + text_image.pixel_x = 7 + text_image.pixel_y = 5 + qdel(atom_icon) + return text_image + +GLOBAL_LIST_EMPTY(friendly_animal_types) + +// Pick a random animal instead of the icon, and use that instead +/proc/getRandomAnimalImage(atom/A) + if(!GLOB.friendly_animal_types.len) + for(var/T in typesof(/mob/living/simple_animal)) + var/mob/living/simple_animal/SA = T + if(initial(SA.gold_core_spawnable) == FRIENDLY_SPAWN) + GLOB.friendly_animal_types += SA + + + var/mob/living/simple_animal/SA = pick(GLOB.friendly_animal_types) + + var/icon = initial(SA.icon) + var/icon_state = initial(SA.icon_state) + + var/image/final_image = image(icon, icon_state=icon_state, loc = A) + + if(ispath(SA, /mob/living/simple_animal/butterfly)) + final_image.color = rgb(rand(0,255), rand(0,255), rand(0,255)) + + // For debugging + final_image.text = initial(SA.name) + return final_image + +//Interface for using DrawBox() to draw 1 pixel on a coordinate. +//Returns the same icon specifed in the argument, but with the pixel drawn +/proc/DrawPixel(icon/I,colour,drawX,drawY) + if(!I) + return 0 + + var/Iwidth = I.Width() + var/Iheight = I.Height() + + if(drawX > Iwidth || drawX <= 0) + return 0 + if(drawY > Iheight || drawY <= 0) + return 0 + + I.DrawBox(colour,drawX, drawY) + return I + + +//Interface for easy drawing of one pixel on an atom. +/atom/proc/DrawPixelOn(colour, drawX, drawY) + var/icon/I = new(icon) + var/icon/J = DrawPixel(I, colour, drawX, drawY) + if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square. + icon = J + return J + return 0 + +//For creating consistent icons for human looking simple animals +/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null) + var/static/list/humanoid_icon_cache = list() + if(!icon_id || !humanoid_icon_cache[icon_id]) + var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key) + + if(prefs) + prefs.copy_to(body,TRUE,FALSE) + if(J) + J.equip(body, TRUE, FALSE, outfit_override = outfit_override) + else if (outfit_override) + body.equipOutfit(outfit_override,visualsOnly = TRUE) + + + var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing") + for(var/D in showDirs) + body.setDir(D) + COMPILE_OVERLAYS(body) + var/icon/partial = getFlatIcon(body) + out_icon.Insert(partial,dir=D) + + humanoid_icon_cache[icon_id] = out_icon + dummy_key? unset_busy_human_dummy(dummy_key) : qdel(body) + return out_icon + else + return humanoid_icon_cache[icon_id] + +//Hook, override to run code on- wait this is images +//Images have dir without being an atom, so they get their own definition. +//Lame. +/image/proc/setDir(newdir) + dir = newdir + +GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0,0,0))) + +/obj/proc/make_frozen_visual() + // Used to make the frozen item visuals for Freon. + if(resistance_flags & FREEZE_PROOF) + return + if(!(obj_flags & FROZEN)) + name = "frozen [name]" + add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY) + alpha -= 25 + obj_flags |= FROZEN + +//Assumes already frozed +/obj/proc/make_unfrozen() + if(obj_flags & FROZEN) + name = replacetext(name, "frozen ", "") + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix) + alpha += 25 + obj_flags &= ~FROZEN + + +/// Save file used in icon2base64. Used for converting icons to base64. +GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of icons for the browser output + +/** + * Converts an icon to base64. Operates by putting the icon in the iconCache savefile, + * exporting it as text, and then parsing the base64 from that. + * (This relies on byond automatically storing icons in savefiles as base64) + */ +/proc/icon2base64(icon/icon) + if (!isicon(icon)) + return FALSE + WRITE_FILE(GLOB.dummySave["dummy"], icon) + var/iconData = GLOB.dummySave.ExportText("dummy") + var/list/partial = splittext(iconData, "{") + return replacetext(copytext_char(partial[2], 3, -5), "\n", "") + +/proc/icon2html(thing, target, icon_state, dir, frame = 1, moving = FALSE) + if (!thing) + return + + var/key + var/icon/I = thing + if (!target) + return + if (target == world) + target = GLOB.clients + + var/list/targets + if (!islist(target)) + targets = list(target) + else + targets = target + if (!targets.len) + return + if (!isicon(I)) + if (isfile(thing)) //special snowflake + var/name = sanitize_filename("[generate_asset_name(thing)].png") + if (!SSassets.cache[name]) + register_asset(name, thing) + for (var/thing2 in targets) + send_asset(thing2, key) + return "" + var/atom/A = thing + if (isnull(dir)) + dir = A.dir + if (isnull(icon_state)) + icon_state = A.icon_state + I = A.icon + if (ishuman(thing)) // Shitty workaround for a BYOND issue. + var/icon/temp = I + I = icon() + I.Insert(temp, dir = SOUTH) + dir = SOUTH + else + if (isnull(dir)) + dir = SOUTH + if (isnull(icon_state)) + icon_state = "" + + I = icon(I, icon_state, dir, frame, moving) + + key = "[generate_asset_name(I)].png" + if(!SSassets.cache[key]) + register_asset(key, I) + for (var/thing2 in targets) + send_asset(thing2, key) + + return "" + +/proc/icon2base64html(thing) + if (!thing) + return + var/static/list/bicon_cache = list() + if (isicon(thing)) + var/icon/I = thing + var/icon_base64 = icon2base64(I) + + if (I.Height() > world.icon_size || I.Width() > world.icon_size) + var/icon_md5 = md5(icon_base64) + icon_base64 = bicon_cache[icon_md5] + if (!icon_base64) // Doesn't exist yet, make it. + bicon_cache[icon_md5] = icon_base64 = icon2base64(I) + + + return "" + + // Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with. + var/atom/A = thing + var/key = "[istype(A.icon, /icon) ? "[REF(A.icon)]" : A.icon]:[A.icon_state]" + + + if (!bicon_cache[key]) // Doesn't exist, make it. + var/icon/I = icon(A.icon, A.icon_state, SOUTH, 1) + if (ishuman(thing)) // Shitty workaround for a BYOND issue. + var/icon/temp = I + I = icon() + I.Insert(temp, dir = SOUTH) + + bicon_cache[key] = icon2base64(I) + + return "" + +//Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs. +/proc/costly_icon2html(thing, target) + if (!thing) + return + + if (isicon(thing)) + return icon2html(thing, target) + + var/icon/I = getFlatIcon(thing) + return icon2html(I, target) diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm index e2490c8e873..e12d753d20f 100644 --- a/code/__HELPERS/matrices.dm +++ b/code/__HELPERS/matrices.dm @@ -1,178 +1,178 @@ -/matrix/proc/TurnTo(old_angle, new_angle) - . = new_angle - old_angle - Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT - -/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE) - if(!segments) - return - var/segment = 360/segments - if(!clockwise) - segment = -segment - var/list/matrices = list() - for(var/i in 1 to segments-1) - var/matrix/M = matrix(transform) - M.Turn(segment*i) - matrices += M - var/matrix/last = matrix(transform) - matrices += last - - speed /= segments - - if(parallel) - animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL) - else - animate(src, transform = matrices[1], time = speed, loops) - for(var/i in 2 to segments) //2 because 1 is covered above - animate(transform = matrices[i], time = speed) - //doesn't have an object argument because this is "Stacking" with the animate call above - //3 billion% intentional - -//Dumps the matrix data in format a-f -/matrix/proc/tolist() - . = list() - . += a - . += b - . += c - . += d - . += e - . += f - -//Dumps the matrix data in a matrix-grid format -/* - a d 0 - b e 0 - c f 1 -*/ -/matrix/proc/togrid() - . = list() - . += a - . += d - . += 0 - . += b - . += e - . += 0 - . += c - . += f - . += 1 - -//The X pixel offset of this matrix -/matrix/proc/get_x_shift() - . = c - -//The Y pixel offset of this matrix -/matrix/proc/get_y_shift() - . = f - -/matrix/proc/get_x_skew() - . = b - -/matrix/proc/get_y_skew() - . = d - -//Skews a matrix in a particular direction -//Missing arguments are treated as no skew in that direction - -//As Rotation is defined as a scale+skew, these procs will break any existing rotation -//Unless the result is multiplied against the current matrix -/matrix/proc/set_skew(x = 0, y = 0) - b = x - d = y - - -///////////////////// -// COLOUR MATRICES // -///////////////////// - -/* Documenting a couple of potentially useful color matrices here to inspire ideas -// Greyscale - indentical to saturation @ 0 -list(LUMA_R,LUMA_R,LUMA_R,0, LUMA_G,LUMA_G,LUMA_G,0, LUMA_B,LUMA_B,LUMA_B,0, 0,0,0,1, 0,0,0,0) - -// Color inversion -list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0) - -// Sepiatone -list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0) -*/ - -//Does nothing -/proc/color_matrix_identity() - return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) - -//Adds/subtracts overall lightness -//0 is identity, 1 makes everything white, -1 makes everything black -/proc/color_matrix_lightness(power) - return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0) - -//Changes distance hues have from grey while maintaining the overall lightness. Greys are unaffected. -//1 is identity, 0 is greyscale, >1 oversaturates colors -/proc/color_matrix_saturation(value) - var/inv = 1 - value - var/R = round(LUMA_R * inv, 0.001) - var/G = round(LUMA_G * inv, 0.001) - var/B = round(LUMA_B * inv, 0.001) - - return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0) - -//Changes distance colors have from rgb(127,127,127) grey -//1 is identity. 0 makes everything grey >1 blows out colors and greys -/proc/color_matrix_contrast(value) - var/add = (1 - value) / 2 - return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0) - -//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys -//0 is identity, 120 moves reds to greens, 240 moves reds to blues -/proc/color_matrix_rotate_hue(angle) - var/sin = sin(angle) - var/cos = cos(angle) - var/cos_inv_third = 0.333*(1-cos) - var/sqrt3_sin = sqrt(3)*sin - return list( -round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), 0, -round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), 0, -round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), 0, -0,0,0,1, -0,0,0,0) - -//These next three rotate values about one axis only -//x is the red axis, y is the green axis, z is the blue axis. -/proc/color_matrix_rotate_x(angle) - var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) - return list(1,0,0,0, 0,cosval,sinval,0, 0,-sinval,cosval,0, 0,0,0,1, 0,0,0,0) - -/proc/color_matrix_rotate_y(angle) - var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) - return list(cosval,0,-sinval,0, 0,1,0,0, sinval,0,cosval,0, 0,0,0,1, 0,0,0,0) - -/proc/color_matrix_rotate_z(angle) - var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) - return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) - - -//Returns a matrix addition of A with B -/proc/color_matrix_add(list/A, list/B) - if(!istype(A) || !istype(B)) - return color_matrix_identity() - if(A.len != 20 || B.len != 20) - return color_matrix_identity() - var/list/output = list() - output.len = 20 - for(var/value in 1 to 20) - output[value] = A[value] + B[value] - return output - -//Returns a matrix multiplication of A with B -/proc/color_matrix_multiply(list/A, list/B) - if(!istype(A) || !istype(B)) - return color_matrix_identity() - if(A.len != 20 || B.len != 20) - return color_matrix_identity() - var/list/output = list() - output.len = 20 - var/x = 1 - var/y = 1 - var/offset = 0 - for(y in 1 to 5) - offset = (y-1)*4 - for(x in 1 to 4) - output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001) - return output +/matrix/proc/TurnTo(old_angle, new_angle) + . = new_angle - old_angle + Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT + +/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE) + if(!segments) + return + var/segment = 360/segments + if(!clockwise) + segment = -segment + var/list/matrices = list() + for(var/i in 1 to segments-1) + var/matrix/M = matrix(transform) + M.Turn(segment*i) + matrices += M + var/matrix/last = matrix(transform) + matrices += last + + speed /= segments + + if(parallel) + animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL) + else + animate(src, transform = matrices[1], time = speed, loops) + for(var/i in 2 to segments) //2 because 1 is covered above + animate(transform = matrices[i], time = speed) + //doesn't have an object argument because this is "Stacking" with the animate call above + //3 billion% intentional + +//Dumps the matrix data in format a-f +/matrix/proc/tolist() + . = list() + . += a + . += b + . += c + . += d + . += e + . += f + +//Dumps the matrix data in a matrix-grid format +/* + a d 0 + b e 0 + c f 1 +*/ +/matrix/proc/togrid() + . = list() + . += a + . += d + . += 0 + . += b + . += e + . += 0 + . += c + . += f + . += 1 + +//The X pixel offset of this matrix +/matrix/proc/get_x_shift() + . = c + +//The Y pixel offset of this matrix +/matrix/proc/get_y_shift() + . = f + +/matrix/proc/get_x_skew() + . = b + +/matrix/proc/get_y_skew() + . = d + +//Skews a matrix in a particular direction +//Missing arguments are treated as no skew in that direction + +//As Rotation is defined as a scale+skew, these procs will break any existing rotation +//Unless the result is multiplied against the current matrix +/matrix/proc/set_skew(x = 0, y = 0) + b = x + d = y + + +///////////////////// +// COLOUR MATRICES // +///////////////////// + +/* Documenting a couple of potentially useful color matrices here to inspire ideas +// Greyscale - indentical to saturation @ 0 +list(LUMA_R,LUMA_R,LUMA_R,0, LUMA_G,LUMA_G,LUMA_G,0, LUMA_B,LUMA_B,LUMA_B,0, 0,0,0,1, 0,0,0,0) + +// Color inversion +list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0) + +// Sepiatone +list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0) +*/ + +//Does nothing +/proc/color_matrix_identity() + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + +//Adds/subtracts overall lightness +//0 is identity, 1 makes everything white, -1 makes everything black +/proc/color_matrix_lightness(power) + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0) + +//Changes distance hues have from grey while maintaining the overall lightness. Greys are unaffected. +//1 is identity, 0 is greyscale, >1 oversaturates colors +/proc/color_matrix_saturation(value) + var/inv = 1 - value + var/R = round(LUMA_R * inv, 0.001) + var/G = round(LUMA_G * inv, 0.001) + var/B = round(LUMA_B * inv, 0.001) + + return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0) + +//Changes distance colors have from rgb(127,127,127) grey +//1 is identity. 0 makes everything grey >1 blows out colors and greys +/proc/color_matrix_contrast(value) + var/add = (1 - value) / 2 + return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0) + +//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys +//0 is identity, 120 moves reds to greens, 240 moves reds to blues +/proc/color_matrix_rotate_hue(angle) + var/sin = sin(angle) + var/cos = cos(angle) + var/cos_inv_third = 0.333*(1-cos) + var/sqrt3_sin = sqrt(3)*sin + return list( +round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), 0, +round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), 0, +round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), 0, +0,0,0,1, +0,0,0,0) + +//These next three rotate values about one axis only +//x is the red axis, y is the green axis, z is the blue axis. +/proc/color_matrix_rotate_x(angle) + var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) + return list(1,0,0,0, 0,cosval,sinval,0, 0,-sinval,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_y(angle) + var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) + return list(cosval,0,-sinval,0, 0,1,0,0, sinval,0,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_z(angle) + var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) + return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + + +//Returns a matrix addition of A with B +/proc/color_matrix_add(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + for(var/value in 1 to 20) + output[value] = A[value] + B[value] + return output + +//Returns a matrix multiplication of A with B +/proc/color_matrix_multiply(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + var/x = 1 + var/y = 1 + var/offset = 0 + for(y in 1 to 5) + offset = (y-1)*4 + for(x in 1 to 4) + output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001) + return output diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 5b76fe46a82..b719148b5a1 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -1,606 +1,606 @@ -/proc/random_blood_type() - return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") - -/proc/random_eye_color() - switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino")) - if("brown") - return "630" - if("hazel") - return "542" - if("grey") - return pick("666","777","888","999","aaa","bbb","ccc") - if("blue") - return "36c" - if("green") - return "060" - if("amber") - return "fc0" - if("albino") - return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9") - else - return "000" - -/proc/random_underwear(gender) - if(!GLOB.underwear_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - switch(gender) - if(MALE) - return pick(GLOB.underwear_m) - if(FEMALE) - return pick(GLOB.underwear_f) - else - return pick(GLOB.underwear_list) - -/proc/random_undershirt(gender) - if(!GLOB.undershirt_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - switch(gender) - if(MALE) - return pick(GLOB.undershirt_m) - if(FEMALE) - return pick(GLOB.undershirt_f) - else - return pick(GLOB.undershirt_list) - -/proc/random_socks() - if(!GLOB.socks_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - return pick(GLOB.socks_list) - -/proc/random_backpack() - return pick(GLOB.backpacklist) - -/proc/random_features() - if(!GLOB.tails_list_human.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) - if(!GLOB.tails_list_lizard.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) - if(!GLOB.snouts_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - if(!GLOB.horns_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) - if(!GLOB.ears_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) - if(!GLOB.frills_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - if(!GLOB.spines_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - if(!GLOB.legs_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - if(!GLOB.body_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - if(!GLOB.wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - if(!GLOB.moth_wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - if(!GLOB.moth_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) - //For now we will always return none for tail_human and ears. - return(list("mcolor" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),"ethcolor" = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)], "tail_lizard" = pick(GLOB.tails_list_lizard), "tail_human" = "None", "wings" = "None", "snout" = pick(GLOB.snouts_list), "horns" = pick(GLOB.horns_list), "ears" = "None", "frills" = pick(GLOB.frills_list), "spines" = pick(GLOB.spines_list), "body_markings" = pick(GLOB.body_markings_list), "legs" = "Normal Legs", "caps" = pick(GLOB.caps_list), "moth_wings" = pick(GLOB.moth_wings_list), "moth_markings" = pick(GLOB.moth_markings_list))) - -/proc/random_hairstyle(gender) - switch(gender) - if(MALE) - return pick(GLOB.hairstyles_male_list) - if(FEMALE) - return pick(GLOB.hairstyles_female_list) - else - return pick(GLOB.hairstyles_list) - -/proc/random_facial_hairstyle(gender) - switch(gender) - if(MALE) - return pick(GLOB.facial_hairstyles_male_list) - if(FEMALE) - return pick(GLOB.facial_hairstyles_female_list) - else - return pick(GLOB.facial_hairstyles_list) - -/proc/random_unique_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - if(gender==FEMALE) - . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) - else - . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) - - if(!findname(.)) - break - -/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(lizard_name(gender)) - - if(!findname(.)) - break - -/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(plasmaman_name()) - - if(!findname(.)) - break - -/proc/random_unique_ethereal_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(ethereal_name()) - - if(!findname(.)) - break - -/proc/random_unique_moth_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) - - if(!findname(.)) - break - -/proc/random_skin_tone() - return pick(GLOB.skin_tones) - -GLOBAL_LIST_INIT(skin_tones, sortList(list( - "albino", - "caucasian1", - "caucasian2", - "caucasian3", - "latino", - "mediterranean", - "asian1", - "asian2", - "arab", - "indian", - "african1", - "african2" - ))) - -GLOBAL_LIST_EMPTY(species_list) - -/proc/age2agedescription(age) - switch(age) - if(0 to 1) - return "infant" - if(1 to 3) - return "toddler" - if(3 to 13) - return "child" - if(13 to 19) - return "teenager" - if(19 to 30) - return "young adult" - if(30 to 45) - return "adult" - if(45 to 60) - return "middle-aged" - if(60 to 70) - return "aging" - if(70 to INFINITY) - return "elderly" - else - return "unknown" - -///Timed action involving two mobs, the user and the target. -/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null) - if(!user || !target) - return FALSE - var/user_loc = user.loc - - var/drifting = FALSE - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = TRUE - - var/target_loc = target.loc - - LAZYADD(user.do_afters, target) - LAZYADD(target.targeted_by, user) - var/holding = user.get_active_held_item() - var/datum/progressbar/progbar - if (progress) - progbar = new(user, time, target) - if(target.pixel_x != 0) //shifts the progress bar if target has an offset sprite - progbar.bar.pixel_x -= target.pixel_x - - var/endtime = world.time+time - var/starttime = world.time - . = TRUE - while (world.time < endtime) - stoplag(1) - if(!QDELETED(progbar)) - progbar.update(world.time - starttime) - if(QDELETED(user) || QDELETED(target)) - . = FALSE - break - if(uninterruptible) - continue - if(!(target in user.do_afters)) - . = FALSE - break - if(drifting && !user.inertia_dir) - drifting = FALSE - user_loc = user.loc - - if((!drifting && user.loc != user_loc) || target.loc != target_loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) - . = FALSE - break - if(!QDELETED(progbar)) - progbar.end_progress() - if(!QDELETED(target)) - LAZYREMOVE(user.do_afters, target) - LAZYREMOVE(target.targeted_by, user) - -//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action -/mob/proc/break_do_after_checks(list/checked_health, check_clicks) - if(check_clicks && next_move > world.time) - return FALSE - return TRUE - -//pass a list in the format list("health" = mob's health var) to check health during this -/mob/living/break_do_after_checks(list/checked_health, check_clicks) - if(islist(checked_health)) - if(health < checked_health["health"]) - return FALSE - checked_health["health"] = health - return ..() - -///Timed action involving one mob user. Target is optional. -/proc/do_after(mob/user, var/delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null) - if(!user) - return FALSE - var/atom/Tloc = null - if(target && !isturf(target)) - Tloc = target.loc - - if(target) - LAZYADD(user.do_afters, target) - LAZYADD(target.targeted_by, user) - - var/atom/Uloc = user.loc - - var/drifting = FALSE - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = TRUE - - var/holding = user.get_active_held_item() - - var/holdingnull = TRUE //User's hand started out empty, check for an empty hand - if(holding) - holdingnull = FALSE //Users hand started holding something, check to see if it's still holding that - - delay *= user.do_after_coefficent() - - var/datum/progressbar/progbar - if(progress) - progbar = new(user, delay, target || user) - - var/endtime = world.time + delay - var/starttime = world.time - . = TRUE - while (world.time < endtime) - stoplag(1) - if(!QDELETED(progbar)) - progbar.update(world.time - starttime) - - if(drifting && !user.inertia_dir) - drifting = FALSE - Uloc = user.loc - - if(QDELETED(user) || user.stat || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) - . = FALSE - break - - if(isliving(user)) - var/mob/living/L = user - if(L.IsStun() || L.IsParalyzed()) - . = FALSE - break - - if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) - if((Uloc != Tloc || Tloc != user) && !drifting) - . = FALSE - break - - if(target && !(target in user.do_afters)) - . = FALSE - break - - if(needhand) - //This might seem like an odd check, but you can still need a hand even when it's empty - //i.e the hand is used to pull some item/tool out of the construction - if(!holdingnull) - if(!holding) - . = FALSE - break - if(user.get_active_held_item() != holding) - . = FALSE - break - if(!QDELETED(progbar)) - progbar.end_progress() - - if(!QDELETED(target)) - LAZYREMOVE(user.do_afters, target) - LAZYREMOVE(target.targeted_by, user) - -/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 - . = 1 - return - -///Timed action involving at least one mob user and a list of targets. -/proc/do_after_mob(mob/user, list/targets, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND) - if(!user) - return FALSE - if(!islist(targets)) - targets = list(targets) - if(!length(targets)) - return FALSE - var/user_loc = user.loc - - var/drifting = FALSE - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = TRUE - - var/list/originalloc = list() - for(var/atom/target in targets) - originalloc[target] = target.loc - LAZYADD(user.do_afters, target) - LAZYADD(target.targeted_by, user) - - var/holding = user.get_active_held_item() - var/datum/progressbar/progbar - if(progress) - progbar = new(user, time, targets[1]) - - var/endtime = world.time + time - var/starttime = world.time - var/mob/living/L - if(isliving(user)) - L = user - . = TRUE - mainloop: - while(world.time < endtime) - stoplag(1) - if(!QDELETED(progbar)) - progbar.update(world.time - starttime) - if(QDELETED(user) || !targets) - . = FALSE - break - if(uninterruptible) - continue - - if(drifting && !user.inertia_dir) - drifting = FALSE - user_loc = user.loc - - if(L && !((L.mobility_flags & required_mobility_flags) == required_mobility_flags)) - . = FALSE - break - - for(var/atom/target in targets) - if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) - . = FALSE - break mainloop - if(!QDELETED(progbar)) - progbar.end_progress() - - for(var/thing in targets) - var/atom/target = thing - if(!QDELETED(target)) - LAZYREMOVE(user.do_afters, target) - LAZYREMOVE(target.targeted_by, user) - -/proc/is_species(A, species_datum) - . = FALSE - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.dna && istype(H.dna.species, species_datum)) - . = TRUE - -/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args) - var/turf/T = get_turf(target) - if(!T) - CRASH("attempt to spawn atom type: [spawn_type] in nullspace") - - var/list/new_args = list(T) - if(extra_args) - new_args += extra_args - var/atom/X - for(var/j in 1 to amount) - X = new spawn_type(arglist(new_args)) - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - return X //return the last mob spawned - -/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) - var/turf/T = get_turf(target) - var/step_count = 0 - if(!T) - CRASH("attempt to spawn atom type: [spawn_type] in nullspace") - - var/list/spawned_mobs = new(amount) - - for(var/j in 1 to amount) - var/atom/movable/X - - if (istype(spawn_type, /list)) - var/mob_type = pick(spawn_type) - X = new mob_type(T) - else - X = new spawn_type(T) - - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - - spawned_mobs[j] = X - - if(always_max_walk || prob(walk_chance)) - if(always_max_walk) - step_count = max_walk - else - step_count = rand(1, max_walk) - - for(var/i in 1 to step_count) - step(X, pick(NORTH, SOUTH, EAST, WEST)) - - return spawned_mobs - -// Displays a message in deadchat, sent by source. Source is not linkified, message is, to avoid stuff like character names to be linkified. -// Automatically gives the class deadsay to the whole message (message + source) -/proc/deadchat_broadcast(message, source=null, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) - message = "[source][message]" - for(var/mob/M in GLOB.player_list) - var/chat_toggles = TOGGLES_DEFAULT_CHAT - var/toggles = TOGGLES_DEFAULT - var/list/ignoring - if(M.client.prefs) - var/datum/preferences/prefs = M.client.prefs - chat_toggles = prefs.chat_toggles - toggles = prefs.toggles - ignoring = prefs.ignoring - - - var/override = FALSE - if(M.client.holder && (chat_toggles & CHAT_DEAD)) - override = TRUE - if(HAS_TRAIT(M, TRAIT_SIXTHSENSE) && message_type == DEADCHAT_REGULAR) - override = TRUE - if(SSticker.current_state == GAME_STATE_FINISHED) - override = TRUE - if(isnewplayer(M) && !override) - continue - if(M.stat != DEAD && !override) - continue - if(speaker_key && (speaker_key in ignoring)) - continue - - switch(message_type) - if(DEADCHAT_DEATHRATTLE) - if(toggles & DISABLE_DEATHRATTLE) - continue - if(DEADCHAT_ARRIVALRATTLE) - if(toggles & DISABLE_ARRIVALRATTLE) - continue - if(DEADCHAT_LAWCHANGE) - if(!(chat_toggles & CHAT_GHOSTLAWS)) - continue - - if(isobserver(M)) - var/rendered_message = message - - if(follow_target) - var/F - if(turf_target) - F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target) - else - F = FOLLOW_LINK(M, follow_target) - rendered_message = "[F] [message]" - else if(turf_target) - var/turf_link = TURF_LINK(M, turf_target) - rendered_message = "[turf_link] [message]" - - to_chat(M, rendered_message) - else - to_chat(M, message) - -//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. -/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) - var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs - var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs - - if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0) - for(var/T in typesof(/mob/living/simple_animal)) - var/mob/living/simple_animal/SA = T - switch(initial(SA.gold_core_spawnable)) - if(HOSTILE_SPAWN) - mob_spawn_meancritters += T - if(FRIENDLY_SPAWN) - mob_spawn_nicecritters += T - - var/chosen - if(mob_class == FRIENDLY_SPAWN) - chosen = pick(mob_spawn_nicecritters) - else - chosen = pick(mob_spawn_meancritters) - var/mob/living/simple_animal/C = new chosen(spawn_location) - return C - -/proc/passtable_on(target, source) - var/mob/living/L = target - if (!HAS_TRAIT(L, TRAIT_PASSTABLE) && L.pass_flags & PASSTABLE) - ADD_TRAIT(L, TRAIT_PASSTABLE, INNATE_TRAIT) - ADD_TRAIT(L, TRAIT_PASSTABLE, source) - L.pass_flags |= PASSTABLE - -/proc/passtable_off(target, source) - var/mob/living/L = target - REMOVE_TRAIT(L, TRAIT_PASSTABLE, source) - if(!HAS_TRAIT(L, TRAIT_PASSTABLE)) - L.pass_flags &= ~PASSTABLE - -/proc/dance_rotate(atom/movable/AM, datum/callback/callperrotate, set_original_dir=FALSE) - set waitfor = FALSE - var/originaldir = AM.dir - for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH)) - if(!AM) - return - AM.setDir(i) - callperrotate?.Invoke() - sleep(1) - if(set_original_dir) - AM.setDir(originaldir) - -/////////////////////// -///Silicon Mob Procs/// -/////////////////////// - -//Returns a list of unslaved cyborgs -/proc/active_free_borgs() - . = list() - for(var/mob/living/silicon/robot/R in GLOB.alive_mob_list) - if(R.connected_ai || R.shell) - continue - if(R.stat == DEAD) - continue - if(R.emagged || R.scrambledcodes) - continue - . += R - -//Returns a list of AI's -/proc/active_ais(check_mind=FALSE, var/z = null) - . = list() - for(var/mob/living/silicon/ai/A in GLOB.alive_mob_list) - if(A.stat == DEAD) - continue - if(A.control_disabled) - continue - if(check_mind) - if(!A.mind) - continue - if(z && !(z == A.z) && (!is_station_level(z) || !is_station_level(A.z))) //if a Z level was specified, AND the AI is not on the same level, AND either is off the station... - continue - . += A - return . - -//Find an active ai with the least borgs. VERBOSE PROCNAME HUH! -/proc/select_active_ai_with_fewest_borgs(var/z) - var/mob/living/silicon/ai/selected - var/list/active = active_ais(FALSE, z) - for(var/mob/living/silicon/ai/A in active) - if(!selected || (selected.connected_robots.len > A.connected_robots.len)) - selected = A - - return selected - -/proc/select_active_free_borg(mob/user) - var/list/borgs = active_free_borgs() - if(borgs.len) - if(user) - . = input(user,"Unshackled cyborg signals detected:", "Cyborg Selection", borgs[1]) in sortList(borgs) - else - . = pick(borgs) - return . - -/proc/select_active_ai(mob/user, var/z = null) - var/list/ais = active_ais(FALSE, z) - if(ais.len) - if(user) - . = input(user,"AI signals detected:", "AI Selection", ais[1]) in sortList(ais) - else - . = pick(ais) - return . +/proc/random_blood_type() + return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") + +/proc/random_eye_color() + switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino")) + if("brown") + return "630" + if("hazel") + return "542" + if("grey") + return pick("666","777","888","999","aaa","bbb","ccc") + if("blue") + return "36c" + if("green") + return "060" + if("amber") + return "fc0" + if("albino") + return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9") + else + return "000" + +/proc/random_underwear(gender) + if(!GLOB.underwear_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + switch(gender) + if(MALE) + return pick(GLOB.underwear_m) + if(FEMALE) + return pick(GLOB.underwear_f) + else + return pick(GLOB.underwear_list) + +/proc/random_undershirt(gender) + if(!GLOB.undershirt_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + switch(gender) + if(MALE) + return pick(GLOB.undershirt_m) + if(FEMALE) + return pick(GLOB.undershirt_f) + else + return pick(GLOB.undershirt_list) + +/proc/random_socks() + if(!GLOB.socks_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) + return pick(GLOB.socks_list) + +/proc/random_backpack() + return pick(GLOB.backpacklist) + +/proc/random_features() + if(!GLOB.tails_list_human.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) + if(!GLOB.tails_list_lizard.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) + if(!GLOB.snouts_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) + if(!GLOB.horns_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) + if(!GLOB.ears_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) + if(!GLOB.frills_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) + if(!GLOB.spines_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) + if(!GLOB.legs_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) + if(!GLOB.body_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) + if(!GLOB.wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) + if(!GLOB.moth_wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) + if(!GLOB.moth_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) + //For now we will always return none for tail_human and ears. + return(list("mcolor" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),"ethcolor" = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)], "tail_lizard" = pick(GLOB.tails_list_lizard), "tail_human" = "None", "wings" = "None", "snout" = pick(GLOB.snouts_list), "horns" = pick(GLOB.horns_list), "ears" = "None", "frills" = pick(GLOB.frills_list), "spines" = pick(GLOB.spines_list), "body_markings" = pick(GLOB.body_markings_list), "legs" = "Normal Legs", "caps" = pick(GLOB.caps_list), "moth_wings" = pick(GLOB.moth_wings_list), "moth_markings" = pick(GLOB.moth_markings_list))) + +/proc/random_hairstyle(gender) + switch(gender) + if(MALE) + return pick(GLOB.hairstyles_male_list) + if(FEMALE) + return pick(GLOB.hairstyles_female_list) + else + return pick(GLOB.hairstyles_list) + +/proc/random_facial_hairstyle(gender) + switch(gender) + if(MALE) + return pick(GLOB.facial_hairstyles_male_list) + if(FEMALE) + return pick(GLOB.facial_hairstyles_female_list) + else + return pick(GLOB.facial_hairstyles_list) + +/proc/random_unique_name(gender, attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + if(gender==FEMALE) + . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) + else + . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + + if(!findname(.)) + break + +/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(lizard_name(gender)) + + if(!findname(.)) + break + +/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(plasmaman_name()) + + if(!findname(.)) + break + +/proc/random_unique_ethereal_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(ethereal_name()) + + if(!findname(.)) + break + +/proc/random_unique_moth_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) + + if(!findname(.)) + break + +/proc/random_skin_tone() + return pick(GLOB.skin_tones) + +GLOBAL_LIST_INIT(skin_tones, sortList(list( + "albino", + "caucasian1", + "caucasian2", + "caucasian3", + "latino", + "mediterranean", + "asian1", + "asian2", + "arab", + "indian", + "african1", + "african2" + ))) + +GLOBAL_LIST_EMPTY(species_list) + +/proc/age2agedescription(age) + switch(age) + if(0 to 1) + return "infant" + if(1 to 3) + return "toddler" + if(3 to 13) + return "child" + if(13 to 19) + return "teenager" + if(19 to 30) + return "young adult" + if(30 to 45) + return "adult" + if(45 to 60) + return "middle-aged" + if(60 to 70) + return "aging" + if(70 to INFINITY) + return "elderly" + else + return "unknown" + +///Timed action involving two mobs, the user and the target. +/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null) + if(!user || !target) + return FALSE + var/user_loc = user.loc + + var/drifting = FALSE + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = TRUE + + var/target_loc = target.loc + + LAZYADD(user.do_afters, target) + LAZYADD(target.targeted_by, user) + var/holding = user.get_active_held_item() + var/datum/progressbar/progbar + if (progress) + progbar = new(user, time, target) + if(target.pixel_x != 0) //shifts the progress bar if target has an offset sprite + progbar.bar.pixel_x -= target.pixel_x + + var/endtime = world.time+time + var/starttime = world.time + . = TRUE + while (world.time < endtime) + stoplag(1) + if(!QDELETED(progbar)) + progbar.update(world.time - starttime) + if(QDELETED(user) || QDELETED(target)) + . = FALSE + break + if(uninterruptible) + continue + if(!(target in user.do_afters)) + . = FALSE + break + if(drifting && !user.inertia_dir) + drifting = FALSE + user_loc = user.loc + + if((!drifting && user.loc != user_loc) || target.loc != target_loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) + . = FALSE + break + if(!QDELETED(progbar)) + progbar.end_progress() + if(!QDELETED(target)) + LAZYREMOVE(user.do_afters, target) + LAZYREMOVE(target.targeted_by, user) + +//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action +/mob/proc/break_do_after_checks(list/checked_health, check_clicks) + if(check_clicks && next_move > world.time) + return FALSE + return TRUE + +//pass a list in the format list("health" = mob's health var) to check health during this +/mob/living/break_do_after_checks(list/checked_health, check_clicks) + if(islist(checked_health)) + if(health < checked_health["health"]) + return FALSE + checked_health["health"] = health + return ..() + +///Timed action involving one mob user. Target is optional. +/proc/do_after(mob/user, var/delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null) + if(!user) + return FALSE + var/atom/Tloc = null + if(target && !isturf(target)) + Tloc = target.loc + + if(target) + LAZYADD(user.do_afters, target) + LAZYADD(target.targeted_by, user) + + var/atom/Uloc = user.loc + + var/drifting = FALSE + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = TRUE + + var/holding = user.get_active_held_item() + + var/holdingnull = TRUE //User's hand started out empty, check for an empty hand + if(holding) + holdingnull = FALSE //Users hand started holding something, check to see if it's still holding that + + delay *= user.do_after_coefficent() + + var/datum/progressbar/progbar + if(progress) + progbar = new(user, delay, target || user) + + var/endtime = world.time + delay + var/starttime = world.time + . = TRUE + while (world.time < endtime) + stoplag(1) + if(!QDELETED(progbar)) + progbar.update(world.time - starttime) + + if(drifting && !user.inertia_dir) + drifting = FALSE + Uloc = user.loc + + if(QDELETED(user) || user.stat || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) + . = FALSE + break + + if(isliving(user)) + var/mob/living/L = user + if(L.IsStun() || L.IsParalyzed()) + . = FALSE + break + + if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) + if((Uloc != Tloc || Tloc != user) && !drifting) + . = FALSE + break + + if(target && !(target in user.do_afters)) + . = FALSE + break + + if(needhand) + //This might seem like an odd check, but you can still need a hand even when it's empty + //i.e the hand is used to pull some item/tool out of the construction + if(!holdingnull) + if(!holding) + . = FALSE + break + if(user.get_active_held_item() != holding) + . = FALSE + break + if(!QDELETED(progbar)) + progbar.end_progress() + + if(!QDELETED(target)) + LAZYREMOVE(user.do_afters, target) + LAZYREMOVE(target.targeted_by, user) + +/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 + . = 1 + return + +///Timed action involving at least one mob user and a list of targets. +/proc/do_after_mob(mob/user, list/targets, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND) + if(!user) + return FALSE + if(!islist(targets)) + targets = list(targets) + if(!length(targets)) + return FALSE + var/user_loc = user.loc + + var/drifting = FALSE + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = TRUE + + var/list/originalloc = list() + for(var/atom/target in targets) + originalloc[target] = target.loc + LAZYADD(user.do_afters, target) + LAZYADD(target.targeted_by, user) + + var/holding = user.get_active_held_item() + var/datum/progressbar/progbar + if(progress) + progbar = new(user, time, targets[1]) + + var/endtime = world.time + time + var/starttime = world.time + var/mob/living/L + if(isliving(user)) + L = user + . = TRUE + mainloop: + while(world.time < endtime) + stoplag(1) + if(!QDELETED(progbar)) + progbar.update(world.time - starttime) + if(QDELETED(user) || !targets) + . = FALSE + break + if(uninterruptible) + continue + + if(drifting && !user.inertia_dir) + drifting = FALSE + user_loc = user.loc + + if(L && !((L.mobility_flags & required_mobility_flags) == required_mobility_flags)) + . = FALSE + break + + for(var/atom/target in targets) + if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) + . = FALSE + break mainloop + if(!QDELETED(progbar)) + progbar.end_progress() + + for(var/thing in targets) + var/atom/target = thing + if(!QDELETED(target)) + LAZYREMOVE(user.do_afters, target) + LAZYREMOVE(target.targeted_by, user) + +/proc/is_species(A, species_datum) + . = FALSE + if(ishuman(A)) + var/mob/living/carbon/human/H = A + if(H.dna && istype(H.dna.species, species_datum)) + . = TRUE + +/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args) + var/turf/T = get_turf(target) + if(!T) + CRASH("attempt to spawn atom type: [spawn_type] in nullspace") + + var/list/new_args = list(T) + if(extra_args) + new_args += extra_args + var/atom/X + for(var/j in 1 to amount) + X = new spawn_type(arglist(new_args)) + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + return X //return the last mob spawned + +/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) + var/turf/T = get_turf(target) + var/step_count = 0 + if(!T) + CRASH("attempt to spawn atom type: [spawn_type] in nullspace") + + var/list/spawned_mobs = new(amount) + + for(var/j in 1 to amount) + var/atom/movable/X + + if (istype(spawn_type, /list)) + var/mob_type = pick(spawn_type) + X = new mob_type(T) + else + X = new spawn_type(T) + + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + + spawned_mobs[j] = X + + if(always_max_walk || prob(walk_chance)) + if(always_max_walk) + step_count = max_walk + else + step_count = rand(1, max_walk) + + for(var/i in 1 to step_count) + step(X, pick(NORTH, SOUTH, EAST, WEST)) + + return spawned_mobs + +// Displays a message in deadchat, sent by source. Source is not linkified, message is, to avoid stuff like character names to be linkified. +// Automatically gives the class deadsay to the whole message (message + source) +/proc/deadchat_broadcast(message, source=null, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) + message = "[source][message]" + for(var/mob/M in GLOB.player_list) + var/chat_toggles = TOGGLES_DEFAULT_CHAT + var/toggles = TOGGLES_DEFAULT + var/list/ignoring + if(M.client.prefs) + var/datum/preferences/prefs = M.client.prefs + chat_toggles = prefs.chat_toggles + toggles = prefs.toggles + ignoring = prefs.ignoring + + + var/override = FALSE + if(M.client.holder && (chat_toggles & CHAT_DEAD)) + override = TRUE + if(HAS_TRAIT(M, TRAIT_SIXTHSENSE) && message_type == DEADCHAT_REGULAR) + override = TRUE + if(SSticker.current_state == GAME_STATE_FINISHED) + override = TRUE + if(isnewplayer(M) && !override) + continue + if(M.stat != DEAD && !override) + continue + if(speaker_key && (speaker_key in ignoring)) + continue + + switch(message_type) + if(DEADCHAT_DEATHRATTLE) + if(toggles & DISABLE_DEATHRATTLE) + continue + if(DEADCHAT_ARRIVALRATTLE) + if(toggles & DISABLE_ARRIVALRATTLE) + continue + if(DEADCHAT_LAWCHANGE) + if(!(chat_toggles & CHAT_GHOSTLAWS)) + continue + + if(isobserver(M)) + var/rendered_message = message + + if(follow_target) + var/F + if(turf_target) + F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target) + else + F = FOLLOW_LINK(M, follow_target) + rendered_message = "[F] [message]" + else if(turf_target) + var/turf_link = TURF_LINK(M, turf_target) + rendered_message = "[turf_link] [message]" + + to_chat(M, rendered_message) + else + to_chat(M, message) + +//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. +/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) + var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs + var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs + + if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0) + for(var/T in typesof(/mob/living/simple_animal)) + var/mob/living/simple_animal/SA = T + switch(initial(SA.gold_core_spawnable)) + if(HOSTILE_SPAWN) + mob_spawn_meancritters += T + if(FRIENDLY_SPAWN) + mob_spawn_nicecritters += T + + var/chosen + if(mob_class == FRIENDLY_SPAWN) + chosen = pick(mob_spawn_nicecritters) + else + chosen = pick(mob_spawn_meancritters) + var/mob/living/simple_animal/C = new chosen(spawn_location) + return C + +/proc/passtable_on(target, source) + var/mob/living/L = target + if (!HAS_TRAIT(L, TRAIT_PASSTABLE) && L.pass_flags & PASSTABLE) + ADD_TRAIT(L, TRAIT_PASSTABLE, INNATE_TRAIT) + ADD_TRAIT(L, TRAIT_PASSTABLE, source) + L.pass_flags |= PASSTABLE + +/proc/passtable_off(target, source) + var/mob/living/L = target + REMOVE_TRAIT(L, TRAIT_PASSTABLE, source) + if(!HAS_TRAIT(L, TRAIT_PASSTABLE)) + L.pass_flags &= ~PASSTABLE + +/proc/dance_rotate(atom/movable/AM, datum/callback/callperrotate, set_original_dir=FALSE) + set waitfor = FALSE + var/originaldir = AM.dir + for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH)) + if(!AM) + return + AM.setDir(i) + callperrotate?.Invoke() + sleep(1) + if(set_original_dir) + AM.setDir(originaldir) + +/////////////////////// +///Silicon Mob Procs/// +/////////////////////// + +//Returns a list of unslaved cyborgs +/proc/active_free_borgs() + . = list() + for(var/mob/living/silicon/robot/R in GLOB.alive_mob_list) + if(R.connected_ai || R.shell) + continue + if(R.stat == DEAD) + continue + if(R.emagged || R.scrambledcodes) + continue + . += R + +//Returns a list of AI's +/proc/active_ais(check_mind=FALSE, var/z = null) + . = list() + for(var/mob/living/silicon/ai/A in GLOB.alive_mob_list) + if(A.stat == DEAD) + continue + if(A.control_disabled) + continue + if(check_mind) + if(!A.mind) + continue + if(z && !(z == A.z) && (!is_station_level(z) || !is_station_level(A.z))) //if a Z level was specified, AND the AI is not on the same level, AND either is off the station... + continue + . += A + return . + +//Find an active ai with the least borgs. VERBOSE PROCNAME HUH! +/proc/select_active_ai_with_fewest_borgs(var/z) + var/mob/living/silicon/ai/selected + var/list/active = active_ais(FALSE, z) + for(var/mob/living/silicon/ai/A in active) + if(!selected || (selected.connected_robots.len > A.connected_robots.len)) + selected = A + + return selected + +/proc/select_active_free_borg(mob/user) + var/list/borgs = active_free_borgs() + if(borgs.len) + if(user) + . = input(user,"Unshackled cyborg signals detected:", "Cyborg Selection", borgs[1]) in sortList(borgs) + else + . = pick(borgs) + return . + +/proc/select_active_ai(mob/user, var/z = null) + var/list/ais = active_ais(FALSE, z) + if(ais.len) + if(user) + . = input(user,"AI signals detected:", "AI Selection", ais[1]) in sortList(ais) + else + . = pick(ais) + return . diff --git a/code/__HELPERS/mouse_control.dm b/code/__HELPERS/mouse_control.dm index cdd839ce178..784496ed200 100644 --- a/code/__HELPERS/mouse_control.dm +++ b/code/__HELPERS/mouse_control.dm @@ -1,52 +1,52 @@ -/proc/mouse_angle_from_client(client/client) - var/list/mouse_control = params2list(client.mouseParams) - if(mouse_control["screen-loc"] && client) - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) - var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) - var/list/screenview = getviewsize(client.view) - var/screenviewX = screenview[1] * world.icon_size - var/screenviewY = screenview[2] * world.icon_size - var/ox = round(screenviewX/2) - client.pixel_x //"origin" x - var/oy = round(screenviewY/2) - client.pixel_y //"origin" y - var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox)) - return angle - -//Wow, specific name! -/proc/mouse_absolute_datum_map_position_from_client(client/client) - if(!isloc(client.mob.loc)) - return - var/list/mouse_control = params2list(client.mouseParams) - var/atom/A = client.eye - var/turf/T = get_turf(A) - var/cx = T.x - var/cy = T.y - var/cz = T.z - if(mouse_control["screen-loc"]) - var/x = 0 - var/y = 0 - var/z = 0 - var/p_x = 0 - var/p_y = 0 - //Split screen-loc up into X+Pixel_X and Y+Pixel_Y - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - //Split X+Pixel_X up into list(X, Pixel_X) - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - //Split Y+Pixel_Y up into list(Y, Pixel_Y) - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/sx = text2num(screen_loc_X[1]) - var/sy = text2num(screen_loc_Y[1]) - //Get the resolution of the client's current screen size. - var/list/screenview = getviewsize(client.view) - var/svx = screenview[1] - var/svy = screenview[2] - var/cox = round((svx - 1) / 2) - var/coy = round((svy - 1) / 2) - x = cx + (sx - 1 - cox) - y = cy + (sy - 1 - coy) - z = cz - p_x = text2num(screen_loc_X[2]) - p_y = text2num(screen_loc_Y[2]) - return new /datum/position(x, y, z, p_x, p_y) +/proc/mouse_angle_from_client(client/client) + var/list/mouse_control = params2list(client.mouseParams) + if(mouse_control["screen-loc"] && client) + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) + var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) + var/list/screenview = getviewsize(client.view) + var/screenviewX = screenview[1] * world.icon_size + var/screenviewY = screenview[2] * world.icon_size + var/ox = round(screenviewX/2) - client.pixel_x //"origin" x + var/oy = round(screenviewY/2) - client.pixel_y //"origin" y + var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox)) + return angle + +//Wow, specific name! +/proc/mouse_absolute_datum_map_position_from_client(client/client) + if(!isloc(client.mob.loc)) + return + var/list/mouse_control = params2list(client.mouseParams) + var/atom/A = client.eye + var/turf/T = get_turf(A) + var/cx = T.x + var/cy = T.y + var/cz = T.z + if(mouse_control["screen-loc"]) + var/x = 0 + var/y = 0 + var/z = 0 + var/p_x = 0 + var/p_y = 0 + //Split screen-loc up into X+Pixel_X and Y+Pixel_Y + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + //Split X+Pixel_X up into list(X, Pixel_X) + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + //Split Y+Pixel_Y up into list(Y, Pixel_Y) + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/sx = text2num(screen_loc_X[1]) + var/sy = text2num(screen_loc_Y[1]) + //Get the resolution of the client's current screen size. + var/list/screenview = getviewsize(client.view) + var/svx = screenview[1] + var/svy = screenview[2] + var/cox = round((svx - 1) / 2) + var/coy = round((svy - 1) / 2) + x = cx + (sx - 1 - cox) + y = cy + (sy - 1 - coy) + z = cz + p_x = text2num(screen_loc_X[2]) + p_y = text2num(screen_loc_Y[2]) + return new /datum/position(x, y, z, p_x, p_y) diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index b204803c652..41e8c19d949 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -1,226 +1,226 @@ -/proc/lizard_name(gender) - if(gender == MALE) - return "[pick(GLOB.lizard_names_male)]-[pick(GLOB.lizard_names_male)]" - else - return "[pick(GLOB.lizard_names_female)]-[pick(GLOB.lizard_names_female)]" - -/proc/ethereal_name() - var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" - if(prob(65)) - tempname += random_capital_letter() - return tempname - -/proc/plasmaman_name() - return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" - -/proc/moth_name() - return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" - -GLOBAL_VAR(command_name) -/proc/command_name() - if (GLOB.command_name) - return GLOB.command_name - - var/name = "Central Command" - - GLOB.command_name = name - return name - -/proc/change_command_name(name) - - GLOB.command_name = name - - return name - -/proc/station_name() - if(!GLOB.station_name) - var/newname - var/config_station_name = CONFIG_GET(string/stationname) - if(config_station_name) - newname = config_station_name - else - newname = new_station_name() - - set_station_name(newname) - - return GLOB.station_name - -/proc/set_station_name(newname) - GLOB.station_name = newname - - var/config_server_name = CONFIG_GET(string/servername) - if(config_server_name) - world.name = "[config_server_name][config_server_name == GLOB.station_name ? "" : ": [GLOB.station_name]"]" - else - world.name = GLOB.station_name - - -/proc/new_station_name() - var/random = rand(1,5) - var/name = "" - var/new_station_name = "" - - //Rare: Pre-Prefix - if (prob(10)) - name = pick(GLOB.station_prefixes) - new_station_name = name + " " - name = "" - - // Prefix - var/holiday_name = pick(SSevents.holidays) - if(holiday_name) - var/datum/holiday/holiday = SSevents.holidays[holiday_name] - if(istype(holiday, /datum/holiday/friday_thirteenth)) - random = 13 - name = holiday.getStationPrefix() - //get normal name - if(!name) - name = pick(GLOB.station_names) - if(name) - new_station_name += name + " " - - // Suffix - name = pick(GLOB.station_suffixes) - new_station_name += name + " " - - // ID Number - switch(random) - if(1) - new_station_name += "[rand(1, 99)]" - if(2) - new_station_name += pick(GLOB.greek_letters) - if(3) - new_station_name += "\Roman[rand(1,99)]" - if(4) - new_station_name += pick(GLOB.phonetic_alphabet) - if(5) - new_station_name += pick(GLOB.numbers_as_words) - if(13) - new_station_name += pick("13","XIII","Thirteen") - return new_station_name - -/proc/syndicate_name() - var/name = "" - - // Prefix - name += pick("Clandestine", "Prima", "Blue", "Zero-G", "Max", "Blasto", "Waffle", "North", "Omni", "Newton", "Cyber", "Bonk", "Gene", "Gib") - - // Suffix - if (prob(80)) - name += " " - - // Full - if (prob(60)) - name += pick("Syndicate", "Consortium", "Collective", "Corporation", "Group", "Holdings", "Biotech", "Industries", "Systems", "Products", "Chemicals", "Enterprises", "Family", "Creations", "International", "Intergalactic", "Interplanetary", "Foundation", "Positronics", "Hive") - // Broken - else - name += pick("Syndi", "Corp", "Bio", "System", "Prod", "Chem", "Inter", "Hive") - name += pick("", "-") - name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Code") - // Small - else - name += pick("-", "*", "") - name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Gen", "Star", "Dyne", "Code", "Hive") - - return name - - -//Traitors and traitor silicons will get these. Revs will not. -GLOBAL_VAR(syndicate_code_phrase) //Code phrase for traitors. -GLOBAL_VAR(syndicate_code_response) //Code response for traitors. - -//Cached regex search - for checking if codewords are used. -GLOBAL_DATUM(syndicate_code_phrase_regex, /regex) -GLOBAL_DATUM(syndicate_code_response_regex, /regex) - - /* - Should be expanded. - How this works: - Instead of "I'm looking for James Smith," the traitor would say "James Smith" as part of a conversation. - Another traitor may then respond with: "They enjoy running through the void-filled vacuum of the derelict." - The phrase should then have the words: James Smith. - The response should then have the words: run, void, and derelict. - This way assures that the code is suited to the conversation and is unpredicatable. - Obviously, some people will be better at this than others but in theory, everyone should be able to do it and it only enhances roleplay. - Can probably be done through "{ }" but I don't really see the practical benefit. - One example of an earlier system is commented below. - /N - */ - -/proc/generate_code_phrase(return_list=FALSE)//Proc is used for phrase and response in master_controller.dm - - if(!return_list) - . = "" - else - . = list() - - var/words = pick(//How many words there will be. Minimum of two. 2, 4 and 5 have a lesser chance of being selected. 3 is the most likely. - 50; 2, - 200; 3, - 50; 4, - 25; 5 - ) - - var/list/safety = list(1,2,3)//Tells the proc which options to remove later on. - var/nouns = strings(ION_FILE, "ionabstract") - var/objects = strings(ION_FILE, "ionobjects") - var/adjectives = strings(ION_FILE, "ionadjectives") - var/threats = strings(ION_FILE, "ionthreats") - var/foods = strings(ION_FILE, "ionfood") - var/drinks = strings(ION_FILE, "iondrinks") - var/locations = strings(LOCATIONS_FILE, "locations") - - var/list/names = list() - for(var/datum/data/record/t in GLOB.data_core.general)//Picks from crew manifest. - names += t.fields["name"] - - var/maxwords = words//Extra var to check for duplicates. - - for(words,words>0,words--)//Randomly picks from one of the choices below. - - if(words==1&&(1 in safety)&&(2 in safety))//If there is only one word remaining and choice 1 or 2 have not been selected. - safety = list(pick(1,2))//Select choice 1 or 2. - else if(words==1&&maxwords==2)//Else if there is only one word remaining (and there were two originally), and 1 or 2 were chosen, - safety = list(3)//Default to list 3 - - switch(pick(safety))//Chance based on the safety list. - if(1)//1 and 2 can only be selected once each to prevent more than two specific names/places/etc. - switch(rand(1,2))//Mainly to add more options later. - if(1) - if(names.len&&prob(70)) - . += pick(names) - else - if(prob(10)) - . += pick(lizard_name(MALE),lizard_name(FEMALE)) - else - var/new_name = pick(pick(GLOB.first_names_male,GLOB.first_names_female)) - new_name += " " - new_name += pick(GLOB.last_names) - . += new_name - if(2) - . += pick(get_all_jobs())//Returns a job. - safety -= 1 - if(2) - switch(rand(1,3))//Food, drinks, or places. Only selectable once. - if(1) - . += lowertext(pick(drinks)) - if(2) - . += lowertext(pick(foods)) - if(3) - . += lowertext(pick(locations)) - safety -= 2 - if(3) - switch(rand(1,4))//Abstract nouns, objects, adjectives, threats. Can be selected more than once. - if(1) - . += lowertext(pick(nouns)) - if(2) - . += lowertext(pick(objects)) - if(3) - . += lowertext(pick(adjectives)) - if(4) - . += lowertext(pick(threats)) - if(!return_list) - if(words==1) - . += "." - else - . += ", " +/proc/lizard_name(gender) + if(gender == MALE) + return "[pick(GLOB.lizard_names_male)]-[pick(GLOB.lizard_names_male)]" + else + return "[pick(GLOB.lizard_names_female)]-[pick(GLOB.lizard_names_female)]" + +/proc/ethereal_name() + var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" + if(prob(65)) + tempname += random_capital_letter() + return tempname + +/proc/plasmaman_name() + return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" + +/proc/moth_name() + return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" + +GLOBAL_VAR(command_name) +/proc/command_name() + if (GLOB.command_name) + return GLOB.command_name + + var/name = "Central Command" + + GLOB.command_name = name + return name + +/proc/change_command_name(name) + + GLOB.command_name = name + + return name + +/proc/station_name() + if(!GLOB.station_name) + var/newname + var/config_station_name = CONFIG_GET(string/stationname) + if(config_station_name) + newname = config_station_name + else + newname = new_station_name() + + set_station_name(newname) + + return GLOB.station_name + +/proc/set_station_name(newname) + GLOB.station_name = newname + + var/config_server_name = CONFIG_GET(string/servername) + if(config_server_name) + world.name = "[config_server_name][config_server_name == GLOB.station_name ? "" : ": [GLOB.station_name]"]" + else + world.name = GLOB.station_name + + +/proc/new_station_name() + var/random = rand(1,5) + var/name = "" + var/new_station_name = "" + + //Rare: Pre-Prefix + if (prob(10)) + name = pick(GLOB.station_prefixes) + new_station_name = name + " " + name = "" + + // Prefix + var/holiday_name = pick(SSevents.holidays) + if(holiday_name) + var/datum/holiday/holiday = SSevents.holidays[holiday_name] + if(istype(holiday, /datum/holiday/friday_thirteenth)) + random = 13 + name = holiday.getStationPrefix() + //get normal name + if(!name) + name = pick(GLOB.station_names) + if(name) + new_station_name += name + " " + + // Suffix + name = pick(GLOB.station_suffixes) + new_station_name += name + " " + + // ID Number + switch(random) + if(1) + new_station_name += "[rand(1, 99)]" + if(2) + new_station_name += pick(GLOB.greek_letters) + if(3) + new_station_name += "\Roman[rand(1,99)]" + if(4) + new_station_name += pick(GLOB.phonetic_alphabet) + if(5) + new_station_name += pick(GLOB.numbers_as_words) + if(13) + new_station_name += pick("13","XIII","Thirteen") + return new_station_name + +/proc/syndicate_name() + var/name = "" + + // Prefix + name += pick("Clandestine", "Prima", "Blue", "Zero-G", "Max", "Blasto", "Waffle", "North", "Omni", "Newton", "Cyber", "Bonk", "Gene", "Gib") + + // Suffix + if (prob(80)) + name += " " + + // Full + if (prob(60)) + name += pick("Syndicate", "Consortium", "Collective", "Corporation", "Group", "Holdings", "Biotech", "Industries", "Systems", "Products", "Chemicals", "Enterprises", "Family", "Creations", "International", "Intergalactic", "Interplanetary", "Foundation", "Positronics", "Hive") + // Broken + else + name += pick("Syndi", "Corp", "Bio", "System", "Prod", "Chem", "Inter", "Hive") + name += pick("", "-") + name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Code") + // Small + else + name += pick("-", "*", "") + name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Gen", "Star", "Dyne", "Code", "Hive") + + return name + + +//Traitors and traitor silicons will get these. Revs will not. +GLOBAL_VAR(syndicate_code_phrase) //Code phrase for traitors. +GLOBAL_VAR(syndicate_code_response) //Code response for traitors. + +//Cached regex search - for checking if codewords are used. +GLOBAL_DATUM(syndicate_code_phrase_regex, /regex) +GLOBAL_DATUM(syndicate_code_response_regex, /regex) + + /* + Should be expanded. + How this works: + Instead of "I'm looking for James Smith," the traitor would say "James Smith" as part of a conversation. + Another traitor may then respond with: "They enjoy running through the void-filled vacuum of the derelict." + The phrase should then have the words: James Smith. + The response should then have the words: run, void, and derelict. + This way assures that the code is suited to the conversation and is unpredicatable. + Obviously, some people will be better at this than others but in theory, everyone should be able to do it and it only enhances roleplay. + Can probably be done through "{ }" but I don't really see the practical benefit. + One example of an earlier system is commented below. + /N + */ + +/proc/generate_code_phrase(return_list=FALSE)//Proc is used for phrase and response in master_controller.dm + + if(!return_list) + . = "" + else + . = list() + + var/words = pick(//How many words there will be. Minimum of two. 2, 4 and 5 have a lesser chance of being selected. 3 is the most likely. + 50; 2, + 200; 3, + 50; 4, + 25; 5 + ) + + var/list/safety = list(1,2,3)//Tells the proc which options to remove later on. + var/nouns = strings(ION_FILE, "ionabstract") + var/objects = strings(ION_FILE, "ionobjects") + var/adjectives = strings(ION_FILE, "ionadjectives") + var/threats = strings(ION_FILE, "ionthreats") + var/foods = strings(ION_FILE, "ionfood") + var/drinks = strings(ION_FILE, "iondrinks") + var/locations = strings(LOCATIONS_FILE, "locations") + + var/list/names = list() + for(var/datum/data/record/t in GLOB.data_core.general)//Picks from crew manifest. + names += t.fields["name"] + + var/maxwords = words//Extra var to check for duplicates. + + for(words,words>0,words--)//Randomly picks from one of the choices below. + + if(words==1&&(1 in safety)&&(2 in safety))//If there is only one word remaining and choice 1 or 2 have not been selected. + safety = list(pick(1,2))//Select choice 1 or 2. + else if(words==1&&maxwords==2)//Else if there is only one word remaining (and there were two originally), and 1 or 2 were chosen, + safety = list(3)//Default to list 3 + + switch(pick(safety))//Chance based on the safety list. + if(1)//1 and 2 can only be selected once each to prevent more than two specific names/places/etc. + switch(rand(1,2))//Mainly to add more options later. + if(1) + if(names.len&&prob(70)) + . += pick(names) + else + if(prob(10)) + . += pick(lizard_name(MALE),lizard_name(FEMALE)) + else + var/new_name = pick(pick(GLOB.first_names_male,GLOB.first_names_female)) + new_name += " " + new_name += pick(GLOB.last_names) + . += new_name + if(2) + . += pick(get_all_jobs())//Returns a job. + safety -= 1 + if(2) + switch(rand(1,3))//Food, drinks, or places. Only selectable once. + if(1) + . += lowertext(pick(drinks)) + if(2) + . += lowertext(pick(foods)) + if(3) + . += lowertext(pick(locations)) + safety -= 2 + if(3) + switch(rand(1,4))//Abstract nouns, objects, adjectives, threats. Can be selected more than once. + if(1) + . += lowertext(pick(nouns)) + if(2) + . += lowertext(pick(objects)) + if(3) + . += lowertext(pick(adjectives)) + if(4) + . += lowertext(pick(threats)) + if(!return_list) + if(words==1) + . += "." + else + . += ", " diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm index 9bd0d4c95ff..0d2bf891529 100644 --- a/code/__HELPERS/qdel.dm +++ b/code/__HELPERS/qdel.dm @@ -1,10 +1,10 @@ -#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE) -#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME) -#define QDEL_NULL(item) qdel(item); item = null -#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } -#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE) -#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); } -#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); } - -/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly. - QDEL_LIST(L) +#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE) +#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME) +#define QDEL_NULL(item) qdel(item); item = null +#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } +#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE) +#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); } +#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); } + +/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly. + QDEL_LIST(L) diff --git a/code/__HELPERS/radio.dm b/code/__HELPERS/radio.dm index a8baf3a1d7d..dc52299025f 100644 --- a/code/__HELPERS/radio.dm +++ b/code/__HELPERS/radio.dm @@ -1,19 +1,19 @@ -// Ensure the frequency is within bounds of what it should be sending/receiving at -/proc/sanitize_frequency(frequency, free = FALSE) - frequency = round(frequency) - if(free) - . = clamp(frequency, MIN_FREE_FREQ, MAX_FREE_FREQ) - else - . = clamp(frequency, MIN_FREQ, MAX_FREQ) - if(!(. % 2)) // Ensure the last digit is an odd number - . += 1 - -// Format frequency by moving the decimal. -/proc/format_frequency(frequency) - frequency = text2num(frequency) - return "[round(frequency / 10)].[frequency % 10]" - -//Opposite of format, returns as a number -/proc/unformat_frequency(frequency) - frequency = text2num(frequency) - return frequency * 10 +// Ensure the frequency is within bounds of what it should be sending/receiving at +/proc/sanitize_frequency(frequency, free = FALSE) + frequency = round(frequency) + if(free) + . = clamp(frequency, MIN_FREE_FREQ, MAX_FREE_FREQ) + else + . = clamp(frequency, MIN_FREQ, MAX_FREQ) + if(!(. % 2)) // Ensure the last digit is an odd number + . += 1 + +// Format frequency by moving the decimal. +/proc/format_frequency(frequency) + frequency = text2num(frequency) + return "[round(frequency / 10)].[frequency % 10]" + +//Opposite of format, returns as a number +/proc/unformat_frequency(frequency) + frequency = text2num(frequency) + return frequency * 10 diff --git a/code/__HELPERS/sanitize_values.dm b/code/__HELPERS/sanitize_values.dm index 9a2e18f3156..b91f99c142b 100644 --- a/code/__HELPERS/sanitize_values.dm +++ b/code/__HELPERS/sanitize_values.dm @@ -1,88 +1,88 @@ -//general stuff -/proc/sanitize_integer(number, min=0, max=1, default=0) - if(isnum(number)) - number = round(number) - if(min <= number && number <= max) - return number - return default - -/proc/sanitize_text(text, default="") - if(istext(text)) - return text - return default - -/proc/sanitize_islist(value, default) - if(islist(value) && length(value)) - return value - if(default) - return default - -/proc/sanitize_inlist(value, list/List, default) - if(value in List) - return value - if(default) - return default - if(List && List.len) - return pick(List) - - - -//more specialised stuff -/proc/sanitize_gender(gender,neuter=0,plural=1, default="male") - switch(gender) - if(MALE, FEMALE) - return gender - if(NEUTER) - if(neuter) - return gender - else - return default - if(PLURAL) - if(plural) - return gender - else - return default - return default - -/proc/sanitize_hexcolor(color, desired_format=3, include_crunch=0, default) - var/crunch = include_crunch ? "#" : "" - if(!istext(color)) - color = "" - - var/start = 1 + (text2ascii(color, 1) == 35) - var/len = length(color) - var/char = "" - // RRGGBB -> RGB but awful - var/convert_to_shorthand = desired_format == 3 && length_char(color) > 3 - - . = "" - var/i = start - while(i <= len) - char = color[i] - switch(text2ascii(char)) - if(48 to 57) //numbers 0 to 9 - . += char - if(97 to 102) //letters a to f - . += char - if(65 to 70) //letters A to F - . += lowertext(char) - else - break - i += length(char) - if(convert_to_shorthand && i <= len) //skip next one - i += length(color[i]) - - if(length_char(.) != desired_format) - if(default) - return default - return crunch + repeat_string(desired_format, "0") - - return crunch + . - -/proc/sanitize_ooccolor(color) - if(length(color) != length_char(color)) - CRASH("Invalid characters in color '[color]'") - var/list/HSL = rgb2hsl(hex2num(copytext(color, 2, 4)), hex2num(copytext(color, 4, 6)), hex2num(copytext(color, 6, 8))) - HSL[3] = min(HSL[3],0.4) - var/list/RGB = hsl2rgb(arglist(HSL)) - return "#[num2hex(RGB[1],2)][num2hex(RGB[2],2)][num2hex(RGB[3],2)]" +//general stuff +/proc/sanitize_integer(number, min=0, max=1, default=0) + if(isnum(number)) + number = round(number) + if(min <= number && number <= max) + return number + return default + +/proc/sanitize_text(text, default="") + if(istext(text)) + return text + return default + +/proc/sanitize_islist(value, default) + if(islist(value) && length(value)) + return value + if(default) + return default + +/proc/sanitize_inlist(value, list/List, default) + if(value in List) + return value + if(default) + return default + if(List && List.len) + return pick(List) + + + +//more specialised stuff +/proc/sanitize_gender(gender,neuter=0,plural=1, default="male") + switch(gender) + if(MALE, FEMALE) + return gender + if(NEUTER) + if(neuter) + return gender + else + return default + if(PLURAL) + if(plural) + return gender + else + return default + return default + +/proc/sanitize_hexcolor(color, desired_format=3, include_crunch=0, default) + var/crunch = include_crunch ? "#" : "" + if(!istext(color)) + color = "" + + var/start = 1 + (text2ascii(color, 1) == 35) + var/len = length(color) + var/char = "" + // RRGGBB -> RGB but awful + var/convert_to_shorthand = desired_format == 3 && length_char(color) > 3 + + . = "" + var/i = start + while(i <= len) + char = color[i] + switch(text2ascii(char)) + if(48 to 57) //numbers 0 to 9 + . += char + if(97 to 102) //letters a to f + . += char + if(65 to 70) //letters A to F + . += lowertext(char) + else + break + i += length(char) + if(convert_to_shorthand && i <= len) //skip next one + i += length(color[i]) + + if(length_char(.) != desired_format) + if(default) + return default + return crunch + repeat_string(desired_format, "0") + + return crunch + . + +/proc/sanitize_ooccolor(color) + if(length(color) != length_char(color)) + CRASH("Invalid characters in color '[color]'") + var/list/HSL = rgb2hsl(hex2num(copytext(color, 2, 4)), hex2num(copytext(color, 4, 6)), hex2num(copytext(color, 6, 8))) + HSL[3] = min(HSL[3],0.4) + var/list/RGB = hsl2rgb(arglist(HSL)) + return "#[num2hex(RGB[1],2)][num2hex(RGB[2],2)][num2hex(RGB[3],2)]" diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index a77ed203c1c..d8fbb6b40d3 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1,881 +1,881 @@ -/* - * Holds procs designed to help with filtering text - * Contains groups: - * SQL sanitization/formating - * Text sanitization - * Text searches - * Text modification - * Misc - */ - - -/* - * SQL sanitization - */ - -/proc/format_table_name(table as text) - return CONFIG_GET(string/feedback_tableprefix) + table - -/* - * Text sanitization - */ - -//Simply removes < and > and limits the length of the message -/proc/strip_html_simple(t,limit=MAX_MESSAGE_LEN) - var/list/strip_chars = list("<",">") - t = copytext(t,1,limit) - for(var/char in strip_chars) - var/index = findtext(t, char) - while(index) - t = copytext(t, 1, index) + copytext(t, index+1) - index = findtext(t, char) - return t - -//Removes a few problematic characters -/proc/sanitize_simple(t,list/repl_chars = list("\n"="#","\t"="#")) - for(var/char in repl_chars) - var/index = findtext(t, char) - while(index) - t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index + length(char)) - index = findtext(t, char, index + length(char)) - return t - -/proc/sanitize_filename(t) - return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) - -///returns nothing with an alert instead of the message if it contains something in the ic filter, and sanitizes normally if the name is fine. It returns nothing so it backs out of the input the same way as if you had entered nothing. -/proc/sanitize_name(t,allow_numbers=FALSE) - if(CHAT_FILTER_CHECK(t)) - alert("You cannot set a name that contains a word prohibited in IC chat!") - return "" - var/r = reject_bad_name(t,allow_numbers=allow_numbers,strict=TRUE) - if(!r) - alert("Invalid name.") - return "" - return sanitize(r) - -//Runs byond's sanitization proc along-side sanitize_simple -/proc/sanitize(t,list/repl_chars = null) - return html_encode(sanitize_simple(t,repl_chars)) - -//Runs sanitize and strip_html_simple -//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode() -/proc/strip_html(t,limit=MAX_MESSAGE_LEN) - return copytext((sanitize(strip_html_simple(t))),1,limit) - -//Runs byond's sanitization proc along-side strip_html_simple -//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause -/proc/adminscrub(t,limit=MAX_MESSAGE_LEN) - return copytext((html_encode(strip_html_simple(t))),1,limit) - - -//Returns null if there is any bad text in the string -/proc/reject_bad_text(text, max_length = 512, ascii_only = TRUE) - var/char_count = 0 - var/non_whitespace = FALSE - var/lenbytes = length(text) - var/char = "" - for(var/i = 1, i <= lenbytes, i += length(char)) - char = text[i] - char_count++ - if(char_count > max_length) - return - switch(text2ascii(char)) - if(62, 60, 92, 47) // <, >, \, / - return - if(0 to 31) - return - if(32) - continue - if(127 to INFINITY) - if(ascii_only) - return - else - non_whitespace = TRUE - if(non_whitespace) - return text //only accepts the text if it has some non-spaces - -// Used to get a properly sanitized input, of max_length -// no_trim is self explanatory but it prevents the input from being trimed if you intend to parse newlines or whitespace. -/proc/stripped_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) - var/name = input(user, message, title, default) as text|null - if(no_trim) - return copytext(html_encode(name), 1, max_length) - else - return trim(html_encode(name), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <) - -// Used to get a properly sanitized multiline input, of max_length -/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) - var/name = input(user, message, title, default) as message|null - if(no_trim) - return copytext(html_encode(name), 1, max_length) - else - return trim(html_encode(name), max_length) - -#define NO_CHARS_DETECTED 0 -#define SPACES_DETECTED 1 -#define SYMBOLS_DETECTED 2 -#define NUMBERS_DETECTED 3 -#define LETTERS_DETECTED 4 - -/** - * Filters out undesirable characters from names. - * - * * strict - return null immidiately instead of filtering out - * * allow_numbers - allows numbers and common special characters - used for silicon/other weird things names - */ -/proc/reject_bad_name(t_in, allow_numbers = FALSE, max_length = MAX_NAME_LEN, ascii_only = TRUE, strict = FALSE) - if(!t_in) - return //Rejects the input if it is null - - var/number_of_alphanumeric = 0 - var/last_char_group = NO_CHARS_DETECTED - var/t_out = "" - var/t_len = length(t_in) - var/charcount = 0 - var/char = "" - - // This is a sanity short circuit, if the users name is three times the maximum allowable length of name - // We bail out on trying to process the name at all, as it could be a bug or malicious input and we dont - // Want to iterate all of it. - if(t_len > 3 * MAX_NAME_LEN) - return - for(var/i = 1, i <= t_len, i += length(char)) - char = t_in[i] - switch(text2ascii(char)) - - // A .. Z - if(65 to 90) //Uppercase Letters - number_of_alphanumeric++ - last_char_group = LETTERS_DETECTED - - // a .. z - if(97 to 122) //Lowercase Letters - if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || last_char_group == SYMBOLS_DETECTED) //start of a word - char = uppertext(char) - number_of_alphanumeric++ - last_char_group = LETTERS_DETECTED - - // 0 .. 9 - if(48 to 57) //Numbers - if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string - if(strict) - return - continue - number_of_alphanumeric++ - last_char_group = NUMBERS_DETECTED - - // ' - . - if(39,45,46) //Common name punctuation - if(last_char_group == NO_CHARS_DETECTED) - if(strict) - return - continue - last_char_group = SYMBOLS_DETECTED - - // ~ | @ : # $ % & * + - if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) - if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string - if(strict) - return - continue - last_char_group = SYMBOLS_DETECTED - - //Space - if(32) - if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED) //suppress double-spaces and spaces at start of string - if(strict) - return - continue - last_char_group = SPACES_DETECTED - - if(127 to INFINITY) - if(ascii_only) - if(strict) - return - continue - last_char_group = SYMBOLS_DETECTED //for now, we'll treat all non-ascii characters like symbols even though most are letters - - else - continue - t_out += char - charcount++ - if(charcount >= max_length) - break - - if(number_of_alphanumeric < 2) - return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" - - if(last_char_group == SPACES_DETECTED) - t_out = copytext_char(t_out, 1, -1) //removes the last character (in this case a space) - - for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai")) //prevents these common metagamey names - if(cmptext(t_out,bad_name)) - return //(not case sensitive) - - return t_out - -#undef NO_CHARS_DETECTED -#undef SPACES_DETECTED -#undef NUMBERS_DETECTED -#undef LETTERS_DETECTED - - - -//html_encode helper proc that returns the smallest non null of two numbers -//or 0 if they're both null (needed because of findtext returning 0 when a value is not present) -/proc/non_zero_min(a, b) - if(!a) - return b - if(!b) - return a - return (a < b ? a : b) - -//Checks if any of a given list of needles is in the haystack -/proc/text_in_list(haystack, list/needle_list, start=1, end=0) - for(var/needle in needle_list) - if(findtext(haystack, needle, start, end)) - return 1 - return 0 - -//Like above, but case sensitive -/proc/text_in_list_case(haystack, list/needle_list, start=1, end=0) - for(var/needle in needle_list) - if(findtextEx(haystack, needle, start, end)) - return 1 - return 0 - -//Adds 'char' ahead of 'text' until there are 'count' characters total -/proc/add_leading(text, count, char = " ") - text = "[text]" - var/charcount = count - length_char(text) - var/list/chars_to_add[max(charcount + 1, 0)] - return jointext(chars_to_add, char) + text - -//Adds 'char' behind 'text' until there are 'count' characters total -/proc/add_trailing(text, count, char = " ") - text = "[text]" - var/charcount = count - length_char(text) - var/list/chars_to_add[max(charcount + 1, 0)] - return text + jointext(chars_to_add, char) - -//Returns a string with reserved characters and spaces before the first letter removed -/proc/trim_left(text) - for (var/i = 1 to length(text)) - if (text2ascii(text, i) > 32) - return copytext(text, i) - return "" - -//Returns a string with reserved characters and spaces after the last letter removed -/proc/trim_right(text) - for (var/i = length(text), i > 0, i--) - if (text2ascii(text, i) > 32) - return copytext(text, 1, i + 1) - return "" - -//Returns a string with reserved characters and spaces before the first word and after the last word removed. -/proc/trim(text, max_length) - if(max_length) - text = copytext_char(text, 1, max_length) - return trim_left(trim_right(text)) - -//Returns a string with the first element of the string capitalized. -/proc/capitalize(t) - . = t - if(t) - . = t[1] - return uppertext(.) + copytext(t, 1 + length(.)) - -/proc/stringmerge(text,compare,replace = "*") -//This proc fills in all spaces with the "replace" var (* by default) with whatever -//is in the other string at the same spot (assuming it is not a replace char). -//This is used for fingerprints - var/newtext = text - var/text_it = 1 //iterators - var/comp_it = 1 - var/newtext_it = 1 - var/text_length = length(text) - var/comp_length = length(compare) - while(comp_it <= comp_length && text_it <= text_length) - var/a = text[text_it] - var/b = compare[comp_it] -//if it isn't both the same letter, or if they are both the replacement character -//(no way to know what it was supposed to be) - if(a != b) - if(a == replace) //if A is the replacement char - newtext = copytext(newtext, 1, newtext_it) + b + copytext(newtext, newtext_it + length(newtext[newtext_it])) - else if(b == replace) //if B is the replacement char - newtext = copytext(newtext, 1, newtext_it) + a + copytext(newtext, newtext_it + length(newtext[newtext_it])) - else //The lists disagree, Uh-oh! - return 0 - text_it += length(a) - comp_it += length(b) - newtext_it += length(newtext[newtext_it]) - - return newtext - -/proc/stringpercent(text,character = "*") -//This proc returns the number of chars of the string that is the character -//This is used for detective work to determine fingerprint completion. - if(!text || !character) - return 0 - var/count = 0 - var/lentext = length(text) - var/a = "" - for(var/i = 1, i <= lentext, i += length(a)) - a = text[i] - if(a == character) - count++ - return count - -/proc/reverse_text(text = "") - var/new_text = "" - var/lentext = length(text) - var/letter = "" - for(var/i = 1, i <= lentext, i += length(letter)) - letter = text[i] - new_text = letter + new_text - return new_text - -GLOBAL_LIST_INIT(zero_character_only, list("0")) -GLOBAL_LIST_INIT(hex_characters, list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f")) -GLOBAL_LIST_INIT(alphabet, list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")) -GLOBAL_LIST_INIT(binary, list("0","1")) -/proc/random_string(length, list/characters) - . = "" - for(var/i=1, i<=length, i++) - . += pick(characters) - -/proc/repeat_string(times, string="") - . = "" - for(var/i=1, i<=times, i++) - . += string - -/proc/random_short_color() - return random_string(3, GLOB.hex_characters) - -/proc/random_color() - return random_string(6, GLOB.hex_characters) - -//merges non-null characters (3rd argument) from "from" into "into". Returns result -//e.g. into = "Hello World" -// from = "Seeya______" -// returns"Seeya World" -//The returned text is always the same length as into -//This was coded to handle DNA gene-splicing. -/proc/merge_text(into, from, null_char="_") - . = "" - if(!istext(into)) - into = "" - if(!istext(from)) - from = "" - var/null_ascii = istext(null_char) ? text2ascii(null_char, 1) : null_char - var/copying_into = FALSE - var/char = "" - var/start = 1 - var/end_from = length(from) - var/end_into = length(into) - var/into_it = 1 - var/from_it = 1 - while(from_it <= end_from && into_it <= end_into) - char = from[from_it] - if(text2ascii(char) == null_ascii) - if(!copying_into) - . += copytext(from, start, from_it) - start = into_it - copying_into = TRUE - else - if(copying_into) - . += copytext(into, start, into_it) - start = from_it - copying_into = FALSE - into_it += length(into[into_it]) - from_it += length(char) - - if(copying_into) - . += copytext(into, start) - else - . += copytext(from, start, from_it) - if(into_it <= end_into) - . += copytext(into, into_it) - -//finds the first occurrence of one of the characters from needles argument inside haystack -//it may appear this can be optimised, but it really can't. findtext() is so much faster than anything you can do in byondcode. -//stupid byond :( -/proc/findchar(haystack, needles, start=1, end=0) - var/char = "" - var/len = length(needles) - for(var/i = 1, i <= len, i += length(char)) - char = needles[i] - . = findtextEx(haystack, char, start, end) - if(.) - return - return 0 - -/proc/parsemarkdown_basic_step1(t, limited=FALSE) - if(length(t) <= 0) - return - - // This parses markdown with no custom rules - - // Escape backslashed - - t = replacetext(t, "$", "$-") - t = replacetext(t, "\\\\", "$1") - t = replacetext(t, "\\**", "$2") - t = replacetext(t, "\\*", "$3") - t = replacetext(t, "\\__", "$4") - t = replacetext(t, "\\_", "$5") - t = replacetext(t, "\\^", "$6") - t = replacetext(t, "\\((", "$7") - t = replacetext(t, "\\))", "$8") - t = replacetext(t, "\\|", "$9") - t = replacetext(t, "\\%", "$0") - - // Escape single characters that will be used - - t = replacetext(t, "!", "$a") - - // Parse hr and small - - if(!limited) - t = replacetext(t, "((", "") - t = replacetext(t, "))", "") - t = replacetext(t, regex("(-){3,}", "gm"), "
") - t = replacetext(t, regex("^\\((-){3,}\\)$", "gm"), "$1") - - // Parse lists - - var/list/tlist = splittext(t, "\n") - var/tlistlen = tlist.len - var/listlevel = -1 - var/singlespace = -1 // if 0, double spaces are used before asterisks, if 1, single are - for(var/i = 1, i <= tlistlen, i++) - var/line = tlist[i] - var/count_asterisk = length(replacetext(line, regex("\[^\\*\]+", "g"), "")) - if(count_asterisk % 2 == 1 && findtext(line, regex("^\\s*\\*", "g"))) // there is an extra asterisk in the beggining - - var/count_w = length(replacetext(line, regex("^( *)\\*.*$", "g"), "$1")) // whitespace before asterisk - line = replacetext(line, regex("^ *(\\*.*)$", "g"), "$1") - - if(singlespace == -1 && count_w == 2) - if(listlevel == 0) - singlespace = 0 - else - singlespace = 1 - - if(singlespace == 0) - count_w = count_w % 2 ? round(count_w / 2 + 0.25) : count_w / 2 - - line = replacetext(line, regex("\\*", ""), "
  • ") - while(listlevel < count_w) - line = "
      " + line - listlevel++ - while(listlevel > count_w) - line = "
    " + line - listlevel-- - - else while(listlevel >= 0) - line = "" + line - listlevel-- - - tlist[i] = line - // end for - - t = tlist[1] - for(var/i = 2, i <= tlistlen, i++) - t += "\n" + tlist[i] - - while(listlevel >= 0) - t += "" - listlevel-- - - else - t = replacetext(t, "((", "") - t = replacetext(t, "))", "") - - // Parse headers - - t = replacetext(t, regex("^#(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^##(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^###(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^#### ?(.+)$", "gm"), "
    $1
    ") - - // Parse most rules - - t = replacetext(t, regex("\\*(\[^\\*\]*)\\*", "g"), "$1") - t = replacetext(t, regex("_(\[^_\]*)_", "g"), "$1") - t = replacetext(t, "", "!") - t = replacetext(t, "", "!") - t = replacetext(t, regex("\\!(\[^\\!\]+)\\!", "g"), "$1") - t = replacetext(t, regex("\\^(\[^\\^\]+)\\^", "g"), "$1") - t = replacetext(t, regex("\\|(\[^\\|\]+)\\|", "g"), "
    $1
    ") - t = replacetext(t, "!", "
    ") - - return t - -/proc/parsemarkdown_basic_step2(t) - if(length(t) <= 0) - return - - // Restore the single characters used - - t = replacetext(t, "$a", "!") - - // Redo the escaping - - t = replacetext(t, "$1", "\\") - t = replacetext(t, "$2", "**") - t = replacetext(t, "$3", "*") - t = replacetext(t, "$4", "__") - t = replacetext(t, "$5", "_") - t = replacetext(t, "$6", "^") - t = replacetext(t, "$7", "((") - t = replacetext(t, "$8", "))") - t = replacetext(t, "$9", "|") - t = replacetext(t, "$0", "%") - t = replacetext(t, "$-", "$") - - return t - -/proc/parsemarkdown_basic(t, limited=FALSE) - t = parsemarkdown_basic_step1(t, limited) - t = parsemarkdown_basic_step2(t) - return t - -/proc/parsemarkdown(t, mob/user=null, limited=FALSE) - if(length(t) <= 0) - return - - // Premanage whitespace - - t = replacetext(t, regex("\[^\\S\\r\\n \]", "g"), " ") - - t = parsemarkdown_basic_step1(t) - - t = replacetext(t, regex("%s(?:ign)?(?=\\s|$)", "igm"), user ? "[user.real_name]" : "") - t = replacetext(t, regex("%f(?:ield)?(?=\\s|$)", "igm"), "") - - t = parsemarkdown_basic_step2(t) - - // Manage whitespace - - t = replacetext(t, regex("(?:\\r\\n?|\\n)", "g"), "
    ") - - t = replacetext(t, " ", "  ") - - // Done - - return t - -/proc/text2charlist(text) - var/char = "" - var/lentext = length(text) - . = list() - for(var/i = 1, i <= lentext, i += length(char)) - char = text[i] - . += char - -/proc/rot13(text = "") - var/lentext = length(text) - var/char = "" - var/ascii = 0 - . = "" - for(var/i = 1, i <= lentext, i += length(char)) - char = text[i] - ascii = text2ascii(char) - switch(ascii) - if(65 to 77, 97 to 109) //A to M, a to m - ascii += 13 - if(78 to 90, 110 to 122) //N to Z, n to z - ascii -= 13 - . += ascii2text(ascii) - -//Takes a list of values, sanitizes it down for readability and character count, -//then exports it as a json file at data/npc_saves/[filename].json. -//As far as SS13 is concerned this is write only data. You can't change something -//in the json file and have it be reflected in the in game item/mob it came from. -//(That's what things like savefiles are for) Note that this list is not shuffled. -/proc/twitterize(list/proposed, filename, cullshort = 1, storemax = 1000) - if(!islist(proposed) || !filename || !CONFIG_GET(flag/log_twitter)) - return - - //Regular expressions are, as usual, absolute magic - //Any characters outside of 32 (space) to 126 (~) because treating things you don't understand as "magic" is really stupid - var/regex/all_invalid_symbols = new(@"[^ -~]{1}") - - var/list/accepted = list() - for(var/string in proposed) - if(findtext(string,GLOB.is_website) || findtext(string,GLOB.is_email) || findtext(string,all_invalid_symbols) || !findtext(string,GLOB.is_alphanumeric)) - continue - var/buffer = "" - var/early_culling = TRUE - var/lentext = length(string) - var/let = "" - - for(var/pos = 1, pos <= lentext, pos += length(let)) - let = string[pos] - if(!findtext(let, GLOB.is_alphanumeric)) - continue - early_culling = FALSE - buffer = copytext(string, pos) - break - if(early_culling) //Never found any letters! Bail! - continue - - var/punctbuffer = "" - var/cutoff = 0 - lentext = length_char(buffer) - for(var/pos = 1, pos <= lentext, pos++) - let = copytext_char(buffer, -pos, -pos + 1) - if(!findtext(let, GLOB.is_punctuation)) //This won't handle things like Nyaaaa!~ but that's fine - break - punctbuffer += let - cutoff += length(let) - if(punctbuffer) //We clip down excessive punctuation to get the letter count lower and reduce repeats. It's not perfect but it helps. - var/exclaim = FALSE - var/question = FALSE - var/periods = 0 - lentext = length(punctbuffer) - for(var/pos = 1, pos <= lentext, pos += length(let)) - let = punctbuffer[pos] - if(!exclaim && findtext(let, "!")) - exclaim = TRUE - if(question) - break - if(!question && findtext(let, "?")) - question = TRUE - if(exclaim) - break - if(!exclaim && !question && findtext(let, ".")) //? and ! take priority over periods - periods += 1 - if(exclaim) - if(question) - punctbuffer = "?!" - else - punctbuffer = "!" - else if(question) - punctbuffer = "?" - else if(periods > 1) - punctbuffer = "..." - else - punctbuffer = "" //Grammer nazis be damned - buffer = copytext(buffer, 1, -cutoff) + punctbuffer - lentext = length_char(buffer) - if(!buffer || lentext > 280 || lentext <= cullshort || (buffer in accepted)) - continue - - accepted += buffer - - var/log = file("data/npc_saves/[filename].json") //If this line ever shows up as changed in a PR be very careful you aren't being memed on - var/list/oldjson = list() - var/list/oldentries = list() - if(fexists(log)) - oldjson = json_decode(file2text(log)) - oldentries = oldjson["data"] - if(length(oldentries)) - for(var/string in accepted) - for(var/old in oldentries) - if(string == old) - oldentries.Remove(old) //Line's position in line is "refreshed" until it falls off the in game radar - break - - var/list/finalized = list() - finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling - listclearnulls(finalized) - if(length(finalized) > storemax) - finalized.Cut(storemax + 1) - fdel(log) - - var/list/tosend = list() - tosend["data"] = finalized - WRITE_FILE(log, json_encode(tosend)) - -//Used for applying byonds text macros to strings that are loaded at runtime -/proc/apply_text_macros(string) - var/next_backslash = findtext(string, "\\") - if(!next_backslash) - return string - - var/leng = length(string) - - var/next_space = findtext(string, " ", next_backslash + length(string[next_backslash])) - if(!next_space) - next_space = leng - next_backslash - - if(!next_space) //trailing bs - return string - - var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash) - var/macro = lowertext(copytext(string, next_backslash + length(string[next_backslash]), next_space)) - var/rest = next_backslash > leng ? "" : copytext(string, next_space + length(string[next_space])) - - //See https://secure.byond.com/docs/ref/info.html#/DM/text/macros - switch(macro) - //prefixes/agnostic - if("the") - rest = text("\the []", rest) - if("a") - rest = text("\a []", rest) - if("an") - rest = text("\an []", rest) - if("proper") - rest = text("\proper []", rest) - if("improper") - rest = text("\improper []", rest) - if("roman") - rest = text("\roman []", rest) - //postfixes - if("th") - base = text("[]\th", rest) - if("s") - base = text("[]\s", rest) - if("he") - base = text("[]\he", rest) - if("she") - base = text("[]\she", rest) - if("his") - base = text("[]\his", rest) - if("himself") - base = text("[]\himself", rest) - if("herself") - base = text("[]\herself", rest) - if("hers") - base = text("[]\hers", rest) - - . = base - if(rest) - . += .(rest) - -//Replacement for the \th macro when you want the whole word output as text (first instead of 1st) -/proc/thtotext(number) - if(!isnum(number)) - return - switch(number) - if(1) - return "first" - if(2) - return "second" - if(3) - return "third" - if(4) - return "fourth" - if(5) - return "fifth" - if(6) - return "sixth" - if(7) - return "seventh" - if(8) - return "eighth" - if(9) - return "ninth" - if(10) - return "tenth" - if(11) - return "eleventh" - if(12) - return "twelfth" - else - return "[number]\th" - - -/proc/random_capital_letter() - return uppertext(pick(GLOB.alphabet)) - -/proc/unintelligize(message) - var/regex/word_boundaries = regex(@"\b[\S]+\b", "g") - var/prefix = message[1] - if(prefix == ";") - message = copytext(message, 1 + length(prefix)) - else if(prefix in list(":", "#")) - prefix += message[1 + length(prefix)] - message = copytext(message, length(prefix)) - else - prefix = "" - - var/list/rearranged = list() - while(word_boundaries.Find(message)) - var/cword = word_boundaries.match - if(length(cword)) - rearranged += cword - shuffle_inplace(rearranged) - return "[prefix][jointext(rearranged, " ")]" - - -/proc/readable_corrupted_text(text) - var/list/corruption_options = list("..", "£%", "~~\"", "!!", "*", "^", "$!", "-", "}", "?") - var/corrupted_text = "" - - var/lentext = length(text) - var/letter = "" - // Have every letter have a chance of creating corruption on either side - // Small chance of letters being removed in place of corruption - still overall readable - for(var/letter_index = 1, letter_index <= lentext, letter_index += length(letter)) - letter = text[letter_index] - - if (prob(15)) - corrupted_text += pick(corruption_options) - - if (prob(95)) - corrupted_text += letter - else - corrupted_text += pick(corruption_options) - - if (prob(15)) - corrupted_text += pick(corruption_options) - - return corrupted_text - -#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) -#define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) - -//json decode that will return null on parse error instead of runtiming. -/proc/safe_json_decode(data) - try - return json_decode(data) - catch - return - -/proc/num2loadingbar(percent as num, var/numSquares = 20, var/reverse = FALSE) - var/loadstring = "" - for (var/i in 1 to numSquares) - var/limit = reverse ? numSquares - percent*numSquares : percent*numSquares - loadstring += i <= limit ? "█" : "░" - return "\[[loadstring]\]" - -/** - * Formats a number to human readable form with the appropriate SI unit. - * - * Supports SI exponents between 1e-15 to 1e15, but properly handles numbers outside that range as well. - * Examples: - * * `siunit(1234, "Pa", 1)` -> `"1.2 kPa"` - * * `siunit(0.5345, "A", 0)` -> `"535 mA"` - * * `siunit(1000, "Pa", 4)` -> `"1 kPa"` - * Arguments: - * * value - The number to convert to text. Can be positive or negative. - * * unit - The base unit of the number, such as "Pa" or "W". - * * maxdecimals - Maximum amount of decimals to display for the final number. Defaults to 1. - */ -/proc/siunit(value, unit, maxdecimals=1) - var/static/list/prefixes = list("f","p","n","μ","m","","k","M","G","T","P") - - // We don't have prefixes beyond this point - // and this also captures value = 0 which you can't compute the logarithm for - // and also byond numbers are floats and doesn't have much precision beyond this point anyway - if(abs(value) <= 1e-18) - return "0 [unit]" - - var/exponent = clamp(log(10, abs(value)), -15, 15) // Calculate the exponent and clamp it so we don't go outside the prefix list bounds - var/divider = 10 ** (round(exponent / 3) * 3) // Rounds the exponent to nearest SI unit and power it back to the full form - var/coefficient = round(value / divider, 10 ** -maxdecimals) // Calculate the coefficient and round it to desired decimals - var/prefix_index = round(exponent / 3) + 6 // Calculate the index in the prefixes list for this exponent - - // An edge case which happens if we round 999.9 to 0 decimals for example, which gets rounded to 1000 - // In that case, we manually swap up to the next prefix if there is one available - if(coefficient >= 1000 && prefix_index < 11) - coefficient /= 1e3 - prefix_index++ - - var/prefix = prefixes[prefix_index] - return "[coefficient] [prefix][unit]" +/* + * Holds procs designed to help with filtering text + * Contains groups: + * SQL sanitization/formating + * Text sanitization + * Text searches + * Text modification + * Misc + */ + + +/* + * SQL sanitization + */ + +/proc/format_table_name(table as text) + return CONFIG_GET(string/feedback_tableprefix) + table + +/* + * Text sanitization + */ + +//Simply removes < and > and limits the length of the message +/proc/strip_html_simple(t,limit=MAX_MESSAGE_LEN) + var/list/strip_chars = list("<",">") + t = copytext(t,1,limit) + for(var/char in strip_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + copytext(t, index+1) + index = findtext(t, char) + return t + +//Removes a few problematic characters +/proc/sanitize_simple(t,list/repl_chars = list("\n"="#","\t"="#")) + for(var/char in repl_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index + length(char)) + index = findtext(t, char, index + length(char)) + return t + +/proc/sanitize_filename(t) + return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) + +///returns nothing with an alert instead of the message if it contains something in the ic filter, and sanitizes normally if the name is fine. It returns nothing so it backs out of the input the same way as if you had entered nothing. +/proc/sanitize_name(t,allow_numbers=FALSE) + if(CHAT_FILTER_CHECK(t)) + alert("You cannot set a name that contains a word prohibited in IC chat!") + return "" + var/r = reject_bad_name(t,allow_numbers=allow_numbers,strict=TRUE) + if(!r) + alert("Invalid name.") + return "" + return sanitize(r) + +//Runs byond's sanitization proc along-side sanitize_simple +/proc/sanitize(t,list/repl_chars = null) + return html_encode(sanitize_simple(t,repl_chars)) + +//Runs sanitize and strip_html_simple +//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode() +/proc/strip_html(t,limit=MAX_MESSAGE_LEN) + return copytext((sanitize(strip_html_simple(t))),1,limit) + +//Runs byond's sanitization proc along-side strip_html_simple +//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause +/proc/adminscrub(t,limit=MAX_MESSAGE_LEN) + return copytext((html_encode(strip_html_simple(t))),1,limit) + + +//Returns null if there is any bad text in the string +/proc/reject_bad_text(text, max_length = 512, ascii_only = TRUE) + var/char_count = 0 + var/non_whitespace = FALSE + var/lenbytes = length(text) + var/char = "" + for(var/i = 1, i <= lenbytes, i += length(char)) + char = text[i] + char_count++ + if(char_count > max_length) + return + switch(text2ascii(char)) + if(62, 60, 92, 47) // <, >, \, / + return + if(0 to 31) + return + if(32) + continue + if(127 to INFINITY) + if(ascii_only) + return + else + non_whitespace = TRUE + if(non_whitespace) + return text //only accepts the text if it has some non-spaces + +// Used to get a properly sanitized input, of max_length +// no_trim is self explanatory but it prevents the input from being trimed if you intend to parse newlines or whitespace. +/proc/stripped_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) + var/name = input(user, message, title, default) as text|null + if(no_trim) + return copytext(html_encode(name), 1, max_length) + else + return trim(html_encode(name), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <) + +// Used to get a properly sanitized multiline input, of max_length +/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) + var/name = input(user, message, title, default) as message|null + if(no_trim) + return copytext(html_encode(name), 1, max_length) + else + return trim(html_encode(name), max_length) + +#define NO_CHARS_DETECTED 0 +#define SPACES_DETECTED 1 +#define SYMBOLS_DETECTED 2 +#define NUMBERS_DETECTED 3 +#define LETTERS_DETECTED 4 + +/** + * Filters out undesirable characters from names. + * + * * strict - return null immidiately instead of filtering out + * * allow_numbers - allows numbers and common special characters - used for silicon/other weird things names + */ +/proc/reject_bad_name(t_in, allow_numbers = FALSE, max_length = MAX_NAME_LEN, ascii_only = TRUE, strict = FALSE) + if(!t_in) + return //Rejects the input if it is null + + var/number_of_alphanumeric = 0 + var/last_char_group = NO_CHARS_DETECTED + var/t_out = "" + var/t_len = length(t_in) + var/charcount = 0 + var/char = "" + + // This is a sanity short circuit, if the users name is three times the maximum allowable length of name + // We bail out on trying to process the name at all, as it could be a bug or malicious input and we dont + // Want to iterate all of it. + if(t_len > 3 * MAX_NAME_LEN) + return + for(var/i = 1, i <= t_len, i += length(char)) + char = t_in[i] + switch(text2ascii(char)) + + // A .. Z + if(65 to 90) //Uppercase Letters + number_of_alphanumeric++ + last_char_group = LETTERS_DETECTED + + // a .. z + if(97 to 122) //Lowercase Letters + if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || last_char_group == SYMBOLS_DETECTED) //start of a word + char = uppertext(char) + number_of_alphanumeric++ + last_char_group = LETTERS_DETECTED + + // 0 .. 9 + if(48 to 57) //Numbers + if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string + if(strict) + return + continue + number_of_alphanumeric++ + last_char_group = NUMBERS_DETECTED + + // ' - . + if(39,45,46) //Common name punctuation + if(last_char_group == NO_CHARS_DETECTED) + if(strict) + return + continue + last_char_group = SYMBOLS_DETECTED + + // ~ | @ : # $ % & * + + if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) + if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string + if(strict) + return + continue + last_char_group = SYMBOLS_DETECTED + + //Space + if(32) + if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED) //suppress double-spaces and spaces at start of string + if(strict) + return + continue + last_char_group = SPACES_DETECTED + + if(127 to INFINITY) + if(ascii_only) + if(strict) + return + continue + last_char_group = SYMBOLS_DETECTED //for now, we'll treat all non-ascii characters like symbols even though most are letters + + else + continue + t_out += char + charcount++ + if(charcount >= max_length) + break + + if(number_of_alphanumeric < 2) + return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" + + if(last_char_group == SPACES_DETECTED) + t_out = copytext_char(t_out, 1, -1) //removes the last character (in this case a space) + + for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai")) //prevents these common metagamey names + if(cmptext(t_out,bad_name)) + return //(not case sensitive) + + return t_out + +#undef NO_CHARS_DETECTED +#undef SPACES_DETECTED +#undef NUMBERS_DETECTED +#undef LETTERS_DETECTED + + + +//html_encode helper proc that returns the smallest non null of two numbers +//or 0 if they're both null (needed because of findtext returning 0 when a value is not present) +/proc/non_zero_min(a, b) + if(!a) + return b + if(!b) + return a + return (a < b ? a : b) + +//Checks if any of a given list of needles is in the haystack +/proc/text_in_list(haystack, list/needle_list, start=1, end=0) + for(var/needle in needle_list) + if(findtext(haystack, needle, start, end)) + return 1 + return 0 + +//Like above, but case sensitive +/proc/text_in_list_case(haystack, list/needle_list, start=1, end=0) + for(var/needle in needle_list) + if(findtextEx(haystack, needle, start, end)) + return 1 + return 0 + +//Adds 'char' ahead of 'text' until there are 'count' characters total +/proc/add_leading(text, count, char = " ") + text = "[text]" + var/charcount = count - length_char(text) + var/list/chars_to_add[max(charcount + 1, 0)] + return jointext(chars_to_add, char) + text + +//Adds 'char' behind 'text' until there are 'count' characters total +/proc/add_trailing(text, count, char = " ") + text = "[text]" + var/charcount = count - length_char(text) + var/list/chars_to_add[max(charcount + 1, 0)] + return text + jointext(chars_to_add, char) + +//Returns a string with reserved characters and spaces before the first letter removed +/proc/trim_left(text) + for (var/i = 1 to length(text)) + if (text2ascii(text, i) > 32) + return copytext(text, i) + return "" + +//Returns a string with reserved characters and spaces after the last letter removed +/proc/trim_right(text) + for (var/i = length(text), i > 0, i--) + if (text2ascii(text, i) > 32) + return copytext(text, 1, i + 1) + return "" + +//Returns a string with reserved characters and spaces before the first word and after the last word removed. +/proc/trim(text, max_length) + if(max_length) + text = copytext_char(text, 1, max_length) + return trim_left(trim_right(text)) + +//Returns a string with the first element of the string capitalized. +/proc/capitalize(t) + . = t + if(t) + . = t[1] + return uppertext(.) + copytext(t, 1 + length(.)) + +/proc/stringmerge(text,compare,replace = "*") +//This proc fills in all spaces with the "replace" var (* by default) with whatever +//is in the other string at the same spot (assuming it is not a replace char). +//This is used for fingerprints + var/newtext = text + var/text_it = 1 //iterators + var/comp_it = 1 + var/newtext_it = 1 + var/text_length = length(text) + var/comp_length = length(compare) + while(comp_it <= comp_length && text_it <= text_length) + var/a = text[text_it] + var/b = compare[comp_it] +//if it isn't both the same letter, or if they are both the replacement character +//(no way to know what it was supposed to be) + if(a != b) + if(a == replace) //if A is the replacement char + newtext = copytext(newtext, 1, newtext_it) + b + copytext(newtext, newtext_it + length(newtext[newtext_it])) + else if(b == replace) //if B is the replacement char + newtext = copytext(newtext, 1, newtext_it) + a + copytext(newtext, newtext_it + length(newtext[newtext_it])) + else //The lists disagree, Uh-oh! + return 0 + text_it += length(a) + comp_it += length(b) + newtext_it += length(newtext[newtext_it]) + + return newtext + +/proc/stringpercent(text,character = "*") +//This proc returns the number of chars of the string that is the character +//This is used for detective work to determine fingerprint completion. + if(!text || !character) + return 0 + var/count = 0 + var/lentext = length(text) + var/a = "" + for(var/i = 1, i <= lentext, i += length(a)) + a = text[i] + if(a == character) + count++ + return count + +/proc/reverse_text(text = "") + var/new_text = "" + var/lentext = length(text) + var/letter = "" + for(var/i = 1, i <= lentext, i += length(letter)) + letter = text[i] + new_text = letter + new_text + return new_text + +GLOBAL_LIST_INIT(zero_character_only, list("0")) +GLOBAL_LIST_INIT(hex_characters, list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f")) +GLOBAL_LIST_INIT(alphabet, list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")) +GLOBAL_LIST_INIT(binary, list("0","1")) +/proc/random_string(length, list/characters) + . = "" + for(var/i=1, i<=length, i++) + . += pick(characters) + +/proc/repeat_string(times, string="") + . = "" + for(var/i=1, i<=times, i++) + . += string + +/proc/random_short_color() + return random_string(3, GLOB.hex_characters) + +/proc/random_color() + return random_string(6, GLOB.hex_characters) + +//merges non-null characters (3rd argument) from "from" into "into". Returns result +//e.g. into = "Hello World" +// from = "Seeya______" +// returns"Seeya World" +//The returned text is always the same length as into +//This was coded to handle DNA gene-splicing. +/proc/merge_text(into, from, null_char="_") + . = "" + if(!istext(into)) + into = "" + if(!istext(from)) + from = "" + var/null_ascii = istext(null_char) ? text2ascii(null_char, 1) : null_char + var/copying_into = FALSE + var/char = "" + var/start = 1 + var/end_from = length(from) + var/end_into = length(into) + var/into_it = 1 + var/from_it = 1 + while(from_it <= end_from && into_it <= end_into) + char = from[from_it] + if(text2ascii(char) == null_ascii) + if(!copying_into) + . += copytext(from, start, from_it) + start = into_it + copying_into = TRUE + else + if(copying_into) + . += copytext(into, start, into_it) + start = from_it + copying_into = FALSE + into_it += length(into[into_it]) + from_it += length(char) + + if(copying_into) + . += copytext(into, start) + else + . += copytext(from, start, from_it) + if(into_it <= end_into) + . += copytext(into, into_it) + +//finds the first occurrence of one of the characters from needles argument inside haystack +//it may appear this can be optimised, but it really can't. findtext() is so much faster than anything you can do in byondcode. +//stupid byond :( +/proc/findchar(haystack, needles, start=1, end=0) + var/char = "" + var/len = length(needles) + for(var/i = 1, i <= len, i += length(char)) + char = needles[i] + . = findtextEx(haystack, char, start, end) + if(.) + return + return 0 + +/proc/parsemarkdown_basic_step1(t, limited=FALSE) + if(length(t) <= 0) + return + + // This parses markdown with no custom rules + + // Escape backslashed + + t = replacetext(t, "$", "$-") + t = replacetext(t, "\\\\", "$1") + t = replacetext(t, "\\**", "$2") + t = replacetext(t, "\\*", "$3") + t = replacetext(t, "\\__", "$4") + t = replacetext(t, "\\_", "$5") + t = replacetext(t, "\\^", "$6") + t = replacetext(t, "\\((", "$7") + t = replacetext(t, "\\))", "$8") + t = replacetext(t, "\\|", "$9") + t = replacetext(t, "\\%", "$0") + + // Escape single characters that will be used + + t = replacetext(t, "!", "$a") + + // Parse hr and small + + if(!limited) + t = replacetext(t, "((", "") + t = replacetext(t, "))", "") + t = replacetext(t, regex("(-){3,}", "gm"), "
    ") + t = replacetext(t, regex("^\\((-){3,}\\)$", "gm"), "$1") + + // Parse lists + + var/list/tlist = splittext(t, "\n") + var/tlistlen = tlist.len + var/listlevel = -1 + var/singlespace = -1 // if 0, double spaces are used before asterisks, if 1, single are + for(var/i = 1, i <= tlistlen, i++) + var/line = tlist[i] + var/count_asterisk = length(replacetext(line, regex("\[^\\*\]+", "g"), "")) + if(count_asterisk % 2 == 1 && findtext(line, regex("^\\s*\\*", "g"))) // there is an extra asterisk in the beggining + + var/count_w = length(replacetext(line, regex("^( *)\\*.*$", "g"), "$1")) // whitespace before asterisk + line = replacetext(line, regex("^ *(\\*.*)$", "g"), "$1") + + if(singlespace == -1 && count_w == 2) + if(listlevel == 0) + singlespace = 0 + else + singlespace = 1 + + if(singlespace == 0) + count_w = count_w % 2 ? round(count_w / 2 + 0.25) : count_w / 2 + + line = replacetext(line, regex("\\*", ""), "
  • ") + while(listlevel < count_w) + line = "
      " + line + listlevel++ + while(listlevel > count_w) + line = "
    " + line + listlevel-- + + else while(listlevel >= 0) + line = "" + line + listlevel-- + + tlist[i] = line + // end for + + t = tlist[1] + for(var/i = 2, i <= tlistlen, i++) + t += "\n" + tlist[i] + + while(listlevel >= 0) + t += "" + listlevel-- + + else + t = replacetext(t, "((", "") + t = replacetext(t, "))", "") + + // Parse headers + + t = replacetext(t, regex("^#(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^##(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^###(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^#### ?(.+)$", "gm"), "
    $1
    ") + + // Parse most rules + + t = replacetext(t, regex("\\*(\[^\\*\]*)\\*", "g"), "$1") + t = replacetext(t, regex("_(\[^_\]*)_", "g"), "$1") + t = replacetext(t, "", "!") + t = replacetext(t, "
    ", "!") + t = replacetext(t, regex("\\!(\[^\\!\]+)\\!", "g"), "$1") + t = replacetext(t, regex("\\^(\[^\\^\]+)\\^", "g"), "$1") + t = replacetext(t, regex("\\|(\[^\\|\]+)\\|", "g"), "
    $1
    ") + t = replacetext(t, "!", "
    ") + + return t + +/proc/parsemarkdown_basic_step2(t) + if(length(t) <= 0) + return + + // Restore the single characters used + + t = replacetext(t, "$a", "!") + + // Redo the escaping + + t = replacetext(t, "$1", "\\") + t = replacetext(t, "$2", "**") + t = replacetext(t, "$3", "*") + t = replacetext(t, "$4", "__") + t = replacetext(t, "$5", "_") + t = replacetext(t, "$6", "^") + t = replacetext(t, "$7", "((") + t = replacetext(t, "$8", "))") + t = replacetext(t, "$9", "|") + t = replacetext(t, "$0", "%") + t = replacetext(t, "$-", "$") + + return t + +/proc/parsemarkdown_basic(t, limited=FALSE) + t = parsemarkdown_basic_step1(t, limited) + t = parsemarkdown_basic_step2(t) + return t + +/proc/parsemarkdown(t, mob/user=null, limited=FALSE) + if(length(t) <= 0) + return + + // Premanage whitespace + + t = replacetext(t, regex("\[^\\S\\r\\n \]", "g"), " ") + + t = parsemarkdown_basic_step1(t) + + t = replacetext(t, regex("%s(?:ign)?(?=\\s|$)", "igm"), user ? "[user.real_name]" : "") + t = replacetext(t, regex("%f(?:ield)?(?=\\s|$)", "igm"), "") + + t = parsemarkdown_basic_step2(t) + + // Manage whitespace + + t = replacetext(t, regex("(?:\\r\\n?|\\n)", "g"), "
    ") + + t = replacetext(t, " ", "  ") + + // Done + + return t + +/proc/text2charlist(text) + var/char = "" + var/lentext = length(text) + . = list() + for(var/i = 1, i <= lentext, i += length(char)) + char = text[i] + . += char + +/proc/rot13(text = "") + var/lentext = length(text) + var/char = "" + var/ascii = 0 + . = "" + for(var/i = 1, i <= lentext, i += length(char)) + char = text[i] + ascii = text2ascii(char) + switch(ascii) + if(65 to 77, 97 to 109) //A to M, a to m + ascii += 13 + if(78 to 90, 110 to 122) //N to Z, n to z + ascii -= 13 + . += ascii2text(ascii) + +//Takes a list of values, sanitizes it down for readability and character count, +//then exports it as a json file at data/npc_saves/[filename].json. +//As far as SS13 is concerned this is write only data. You can't change something +//in the json file and have it be reflected in the in game item/mob it came from. +//(That's what things like savefiles are for) Note that this list is not shuffled. +/proc/twitterize(list/proposed, filename, cullshort = 1, storemax = 1000) + if(!islist(proposed) || !filename || !CONFIG_GET(flag/log_twitter)) + return + + //Regular expressions are, as usual, absolute magic + //Any characters outside of 32 (space) to 126 (~) because treating things you don't understand as "magic" is really stupid + var/regex/all_invalid_symbols = new(@"[^ -~]{1}") + + var/list/accepted = list() + for(var/string in proposed) + if(findtext(string,GLOB.is_website) || findtext(string,GLOB.is_email) || findtext(string,all_invalid_symbols) || !findtext(string,GLOB.is_alphanumeric)) + continue + var/buffer = "" + var/early_culling = TRUE + var/lentext = length(string) + var/let = "" + + for(var/pos = 1, pos <= lentext, pos += length(let)) + let = string[pos] + if(!findtext(let, GLOB.is_alphanumeric)) + continue + early_culling = FALSE + buffer = copytext(string, pos) + break + if(early_culling) //Never found any letters! Bail! + continue + + var/punctbuffer = "" + var/cutoff = 0 + lentext = length_char(buffer) + for(var/pos = 1, pos <= lentext, pos++) + let = copytext_char(buffer, -pos, -pos + 1) + if(!findtext(let, GLOB.is_punctuation)) //This won't handle things like Nyaaaa!~ but that's fine + break + punctbuffer += let + cutoff += length(let) + if(punctbuffer) //We clip down excessive punctuation to get the letter count lower and reduce repeats. It's not perfect but it helps. + var/exclaim = FALSE + var/question = FALSE + var/periods = 0 + lentext = length(punctbuffer) + for(var/pos = 1, pos <= lentext, pos += length(let)) + let = punctbuffer[pos] + if(!exclaim && findtext(let, "!")) + exclaim = TRUE + if(question) + break + if(!question && findtext(let, "?")) + question = TRUE + if(exclaim) + break + if(!exclaim && !question && findtext(let, ".")) //? and ! take priority over periods + periods += 1 + if(exclaim) + if(question) + punctbuffer = "?!" + else + punctbuffer = "!" + else if(question) + punctbuffer = "?" + else if(periods > 1) + punctbuffer = "..." + else + punctbuffer = "" //Grammer nazis be damned + buffer = copytext(buffer, 1, -cutoff) + punctbuffer + lentext = length_char(buffer) + if(!buffer || lentext > 280 || lentext <= cullshort || (buffer in accepted)) + continue + + accepted += buffer + + var/log = file("data/npc_saves/[filename].json") //If this line ever shows up as changed in a PR be very careful you aren't being memed on + var/list/oldjson = list() + var/list/oldentries = list() + if(fexists(log)) + oldjson = json_decode(file2text(log)) + oldentries = oldjson["data"] + if(length(oldentries)) + for(var/string in accepted) + for(var/old in oldentries) + if(string == old) + oldentries.Remove(old) //Line's position in line is "refreshed" until it falls off the in game radar + break + + var/list/finalized = list() + finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling + listclearnulls(finalized) + if(length(finalized) > storemax) + finalized.Cut(storemax + 1) + fdel(log) + + var/list/tosend = list() + tosend["data"] = finalized + WRITE_FILE(log, json_encode(tosend)) + +//Used for applying byonds text macros to strings that are loaded at runtime +/proc/apply_text_macros(string) + var/next_backslash = findtext(string, "\\") + if(!next_backslash) + return string + + var/leng = length(string) + + var/next_space = findtext(string, " ", next_backslash + length(string[next_backslash])) + if(!next_space) + next_space = leng - next_backslash + + if(!next_space) //trailing bs + return string + + var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash) + var/macro = lowertext(copytext(string, next_backslash + length(string[next_backslash]), next_space)) + var/rest = next_backslash > leng ? "" : copytext(string, next_space + length(string[next_space])) + + //See https://secure.byond.com/docs/ref/info.html#/DM/text/macros + switch(macro) + //prefixes/agnostic + if("the") + rest = text("\the []", rest) + if("a") + rest = text("\a []", rest) + if("an") + rest = text("\an []", rest) + if("proper") + rest = text("\proper []", rest) + if("improper") + rest = text("\improper []", rest) + if("roman") + rest = text("\roman []", rest) + //postfixes + if("th") + base = text("[]\th", rest) + if("s") + base = text("[]\s", rest) + if("he") + base = text("[]\he", rest) + if("she") + base = text("[]\she", rest) + if("his") + base = text("[]\his", rest) + if("himself") + base = text("[]\himself", rest) + if("herself") + base = text("[]\herself", rest) + if("hers") + base = text("[]\hers", rest) + + . = base + if(rest) + . += .(rest) + +//Replacement for the \th macro when you want the whole word output as text (first instead of 1st) +/proc/thtotext(number) + if(!isnum(number)) + return + switch(number) + if(1) + return "first" + if(2) + return "second" + if(3) + return "third" + if(4) + return "fourth" + if(5) + return "fifth" + if(6) + return "sixth" + if(7) + return "seventh" + if(8) + return "eighth" + if(9) + return "ninth" + if(10) + return "tenth" + if(11) + return "eleventh" + if(12) + return "twelfth" + else + return "[number]\th" + + +/proc/random_capital_letter() + return uppertext(pick(GLOB.alphabet)) + +/proc/unintelligize(message) + var/regex/word_boundaries = regex(@"\b[\S]+\b", "g") + var/prefix = message[1] + if(prefix == ";") + message = copytext(message, 1 + length(prefix)) + else if(prefix in list(":", "#")) + prefix += message[1 + length(prefix)] + message = copytext(message, length(prefix)) + else + prefix = "" + + var/list/rearranged = list() + while(word_boundaries.Find(message)) + var/cword = word_boundaries.match + if(length(cword)) + rearranged += cword + shuffle_inplace(rearranged) + return "[prefix][jointext(rearranged, " ")]" + + +/proc/readable_corrupted_text(text) + var/list/corruption_options = list("..", "£%", "~~\"", "!!", "*", "^", "$!", "-", "}", "?") + var/corrupted_text = "" + + var/lentext = length(text) + var/letter = "" + // Have every letter have a chance of creating corruption on either side + // Small chance of letters being removed in place of corruption - still overall readable + for(var/letter_index = 1, letter_index <= lentext, letter_index += length(letter)) + letter = text[letter_index] + + if (prob(15)) + corrupted_text += pick(corruption_options) + + if (prob(95)) + corrupted_text += letter + else + corrupted_text += pick(corruption_options) + + if (prob(15)) + corrupted_text += pick(corruption_options) + + return corrupted_text + +#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) +#define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) + +//json decode that will return null on parse error instead of runtiming. +/proc/safe_json_decode(data) + try + return json_decode(data) + catch + return + +/proc/num2loadingbar(percent as num, var/numSquares = 20, var/reverse = FALSE) + var/loadstring = "" + for (var/i in 1 to numSquares) + var/limit = reverse ? numSquares - percent*numSquares : percent*numSquares + loadstring += i <= limit ? "█" : "░" + return "\[[loadstring]\]" + +/** + * Formats a number to human readable form with the appropriate SI unit. + * + * Supports SI exponents between 1e-15 to 1e15, but properly handles numbers outside that range as well. + * Examples: + * * `siunit(1234, "Pa", 1)` -> `"1.2 kPa"` + * * `siunit(0.5345, "A", 0)` -> `"535 mA"` + * * `siunit(1000, "Pa", 4)` -> `"1 kPa"` + * Arguments: + * * value - The number to convert to text. Can be positive or negative. + * * unit - The base unit of the number, such as "Pa" or "W". + * * maxdecimals - Maximum amount of decimals to display for the final number. Defaults to 1. + */ +/proc/siunit(value, unit, maxdecimals=1) + var/static/list/prefixes = list("f","p","n","μ","m","","k","M","G","T","P") + + // We don't have prefixes beyond this point + // and this also captures value = 0 which you can't compute the logarithm for + // and also byond numbers are floats and doesn't have much precision beyond this point anyway + if(abs(value) <= 1e-18) + return "0 [unit]" + + var/exponent = clamp(log(10, abs(value)), -15, 15) // Calculate the exponent and clamp it so we don't go outside the prefix list bounds + var/divider = 10 ** (round(exponent / 3) * 3) // Rounds the exponent to nearest SI unit and power it back to the full form + var/coefficient = round(value / divider, 10 ** -maxdecimals) // Calculate the coefficient and round it to desired decimals + var/prefix_index = round(exponent / 3) + 6 // Calculate the index in the prefixes list for this exponent + + // An edge case which happens if we round 999.9 to 0 decimals for example, which gets rounded to 1000 + // In that case, we manually swap up to the next prefix if there is one available + if(coefficient >= 1000 && prefix_index < 11) + coefficient /= 1e3 + prefix_index++ + + var/prefix = prefixes[prefix_index] + return "[coefficient] [prefix][unit]" diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index ea4844dcce8..92337a1a959 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -1,88 +1,88 @@ -//Returns the world time in english -/proc/worldtime2text() - return gameTimestamp("hh:mm:ss", world.time) - -/proc/time_stamp(format = "hh:mm:ss", show_ds) - var/time_string = time2text(world.timeofday, format) - return show_ds ? "[time_string]:[world.timeofday % 10]" : time_string - -/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) - if(!wtime) - wtime = world.time - return time2text(wtime - GLOB.timezoneOffset, format) - -/proc/station_time(display_only = FALSE, wtime=world.time) - return ((((wtime - SSticker.round_start_time) * SSticker.station_time_rate_multiplier) + SSticker.gametime_offset) % 864000) - (display_only? GLOB.timezoneOffset : 0) - -/proc/station_time_timestamp(format = "hh:mm:ss", wtime) - return time2text(station_time(TRUE, wtime), format) - -/proc/station_time_debug(force_set) - if(isnum(force_set)) - SSticker.gametime_offset = force_set - return - SSticker.gametime_offset = rand(0, 864000) //hours in day * minutes in hour * seconds in minute * deciseconds in second - if(prob(50)) - SSticker.gametime_offset = FLOOR(SSticker.gametime_offset, 3600) - else - SSticker.gametime_offset = CEILING(SSticker.gametime_offset, 3600) - -//returns timestamp in a sql and a not-quite-compliant ISO 8601 friendly format -/proc/SQLtime(timevar) - return time2text(timevar || world.timeofday, "YYYY-MM-DD hh:mm:ss") - - -GLOBAL_VAR_INIT(midnight_rollovers, 0) -GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) -/proc/update_midnight_rollover() - if (world.timeofday < GLOB.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! - GLOB.midnight_rollovers++ - GLOB.rollovercheck_last_timeofday = world.timeofday - return GLOB.midnight_rollovers - -/proc/weekdayofthemonth() - var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day - switch(DD) - if(8 to 13) - return 2 - if(14 to 20) - return 3 - if(21 to 27) - return 4 - if(28 to INFINITY) - return 5 - else - return 1 - -//Takes a value of time in deciseconds. -//Returns a text value of that number in hours, minutes, or seconds. -/proc/DisplayTimeText(time_value, round_seconds_to = 0.1) - var/second = FLOOR(time_value * 0.1, round_seconds_to) - if(!second) - return "right now" - if(second < 60) - return "[second] second[(second != 1)? "s":""]" - var/minute = FLOOR(second / 60, 1) - second = FLOOR(MODULUS(second, 60), round_seconds_to) - var/secondT - if(second) - secondT = " and [second] second[(second != 1)? "s":""]" - if(minute < 60) - return "[minute] minute[(minute != 1)? "s":""][secondT]" - var/hour = FLOOR(minute / 60, 1) - minute = MODULUS(minute, 60) - var/minuteT - if(minute) - minuteT = " and [minute] minute[(minute != 1)? "s":""]" - if(hour < 24) - return "[hour] hour[(hour != 1)? "s":""][minuteT][secondT]" - var/day = FLOOR(hour / 24, 1) - hour = MODULUS(hour, 24) - var/hourT - if(hour) - hourT = " and [hour] hour[(hour != 1)? "s":""]" - return "[day] day[(day != 1)? "s":""][hourT][minuteT][secondT]" - - -/proc/daysSince(realtimev) - return round((world.realtime - realtimev) / (24 HOURS)) +//Returns the world time in english +/proc/worldtime2text() + return gameTimestamp("hh:mm:ss", world.time) + +/proc/time_stamp(format = "hh:mm:ss", show_ds) + var/time_string = time2text(world.timeofday, format) + return show_ds ? "[time_string]:[world.timeofday % 10]" : time_string + +/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) + if(!wtime) + wtime = world.time + return time2text(wtime - GLOB.timezoneOffset, format) + +/proc/station_time(display_only = FALSE, wtime=world.time) + return ((((wtime - SSticker.round_start_time) * SSticker.station_time_rate_multiplier) + SSticker.gametime_offset) % 864000) - (display_only? GLOB.timezoneOffset : 0) + +/proc/station_time_timestamp(format = "hh:mm:ss", wtime) + return time2text(station_time(TRUE, wtime), format) + +/proc/station_time_debug(force_set) + if(isnum(force_set)) + SSticker.gametime_offset = force_set + return + SSticker.gametime_offset = rand(0, 864000) //hours in day * minutes in hour * seconds in minute * deciseconds in second + if(prob(50)) + SSticker.gametime_offset = FLOOR(SSticker.gametime_offset, 3600) + else + SSticker.gametime_offset = CEILING(SSticker.gametime_offset, 3600) + +//returns timestamp in a sql and a not-quite-compliant ISO 8601 friendly format +/proc/SQLtime(timevar) + return time2text(timevar || world.timeofday, "YYYY-MM-DD hh:mm:ss") + + +GLOBAL_VAR_INIT(midnight_rollovers, 0) +GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) +/proc/update_midnight_rollover() + if (world.timeofday < GLOB.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! + GLOB.midnight_rollovers++ + GLOB.rollovercheck_last_timeofday = world.timeofday + return GLOB.midnight_rollovers + +/proc/weekdayofthemonth() + var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day + switch(DD) + if(8 to 13) + return 2 + if(14 to 20) + return 3 + if(21 to 27) + return 4 + if(28 to INFINITY) + return 5 + else + return 1 + +//Takes a value of time in deciseconds. +//Returns a text value of that number in hours, minutes, or seconds. +/proc/DisplayTimeText(time_value, round_seconds_to = 0.1) + var/second = FLOOR(time_value * 0.1, round_seconds_to) + if(!second) + return "right now" + if(second < 60) + return "[second] second[(second != 1)? "s":""]" + var/minute = FLOOR(second / 60, 1) + second = FLOOR(MODULUS(second, 60), round_seconds_to) + var/secondT + if(second) + secondT = " and [second] second[(second != 1)? "s":""]" + if(minute < 60) + return "[minute] minute[(minute != 1)? "s":""][secondT]" + var/hour = FLOOR(minute / 60, 1) + minute = MODULUS(minute, 60) + var/minuteT + if(minute) + minuteT = " and [minute] minute[(minute != 1)? "s":""]" + if(hour < 24) + return "[hour] hour[(hour != 1)? "s":""][minuteT][secondT]" + var/day = FLOOR(hour / 24, 1) + hour = MODULUS(hour, 24) + var/hourT + if(hour) + hourT = " and [hour] hour[(hour != 1)? "s":""]" + return "[day] day[(day != 1)? "s":""][hourT][minuteT][secondT]" + + +/proc/daysSince(realtimev) + return round((world.realtime - realtimev) / (24 HOURS)) diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index 62cbe99e572..4873e35d0b3 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -1,489 +1,489 @@ -/* - * Holds procs designed to change one type of value, into another. - * Contains: - * file2list - * angle2dir - * angle2text - * worldtime2text - * text2dir_extended & dir2text_short - */ - - - -//Splits the text of a file at seperator and returns them in a list. -//returns an empty list if the file doesn't exist -/world/proc/file2list(filename, seperator="\n", trim = TRUE) - if (trim) - return splittext(trim(file2text(filename)),seperator) - return splittext(file2text(filename),seperator) - -//Turns a direction into text -/proc/dir2text(direction) - switch(direction) - if(NORTH) - return "north" - if(SOUTH) - return "south" - if(EAST) - return "east" - if(WEST) - return "west" - if(NORTHEAST) - return "northeast" - if(SOUTHEAST) - return "southeast" - if(NORTHWEST) - return "northwest" - if(SOUTHWEST) - return "southwest" - else - return - -//Turns text into proper directions -/proc/text2dir(direction) - switch(uppertext(direction)) - if("NORTH") - return NORTH - if("SOUTH") - return SOUTH - if("EAST") - return EAST - if("WEST") - return WEST - if("NORTHEAST") - return NORTHEAST - if("NORTHWEST") - return NORTHWEST - if("SOUTHEAST") - return SOUTHEAST - if("SOUTHWEST") - return SOUTHWEST - else - return - -//Converts an angle (degrees) into a ss13 direction -GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,SOUTHWEST,WEST,NORTHWEST)) -#define angle2dir(X) (GLOB.modulo_angle_to_dir[round((((X%360)+382.5)%360)/45)+1]) - -/proc/angle2dir_cardinal(degree) - degree = SIMPLIFY_DEGREES(degree) - switch(round(degree, 0.1)) - if(315.5 to 360, 0 to 45.5) - return NORTH - if(45.6 to 135.5) - return EAST - if(135.6 to 225.5) - return SOUTH - if(225.6 to 315.5) - return WEST - -//returns the north-zero clockwise angle in degrees, given a direction -/proc/dir2angle(D) - switch(D) - if(NORTH) - return 0 - if(SOUTH) - return 180 - if(EAST) - return 90 - if(WEST) - return 270 - if(NORTHEAST) - return 45 - if(SOUTHEAST) - return 135 - if(NORTHWEST) - return 315 - if(SOUTHWEST) - return 225 - else - return null - -//Returns the angle in english -/proc/angle2text(degree) - return dir2text(angle2dir(degree)) - -//Converts a blend_mode constant to one acceptable to icon.Blend() -/proc/blendMode2iconMode(blend_mode) - switch(blend_mode) - if(BLEND_MULTIPLY) - return ICON_MULTIPLY - if(BLEND_ADD) - return ICON_ADD - if(BLEND_SUBTRACT) - return ICON_SUBTRACT - else - return ICON_OVERLAY - -//Converts a rights bitfield into a string -/proc/rights2text(rights, seperator="", prefix = "+") - seperator += prefix - if(rights & R_BUILD) - . += "[seperator]BUILDMODE" - if(rights & R_ADMIN) - . += "[seperator]ADMIN" - if(rights & R_BAN) - . += "[seperator]BAN" - if(rights & R_FUN) - . += "[seperator]FUN" - if(rights & R_SERVER) - . += "[seperator]SERVER" - if(rights & R_DEBUG) - . += "[seperator]DEBUG" - if(rights & R_POSSESS) - . += "[seperator]POSSESS" - if(rights & R_PERMISSIONS) - . += "[seperator]PERMISSIONS" - if(rights & R_STEALTH) - . += "[seperator]STEALTH" - if(rights & R_POLL) - . += "[seperator]POLL" - if(rights & R_VAREDIT) - . += "[seperator]VAREDIT" - if(rights & R_SOUND) - . += "[seperator]SOUND" - if(rights & R_SPAWN) - . += "[seperator]SPAWN" - if(rights & R_AUTOADMIN) - . += "[seperator]AUTOLOGIN" - if(rights & R_DBRANKS) - . += "[seperator]DBRANKS" - if(!.) - . = "NONE" - return . - -//colour formats -/proc/rgb2hsl(red, green, blue) - red /= 255;green /= 255;blue /= 255; - var/max = max(red,green,blue) - var/min = min(red,green,blue) - var/range = max-min - - var/hue=0;var/saturation=0;var/lightness=0; - lightness = (max + min)/2 - if(range != 0) - if(lightness < 0.5) - saturation = range/(max+min) - else - saturation = range/(2-max-min) - - var/dred = ((max-red)/(6*max)) + 0.5 - var/dgreen = ((max-green)/(6*max)) + 0.5 - var/dblue = ((max-blue)/(6*max)) + 0.5 - - if(max==red) - hue = dblue - dgreen - else if(max==green) - hue = dred - dblue + (1/3) - else - hue = dgreen - dred + (2/3) - if(hue < 0) - hue++ - else if(hue > 1) - hue-- - - return list(hue, saturation, lightness) - -/proc/hsl2rgb(hue, saturation, lightness) - var/red;var/green;var/blue; - if(saturation == 0) - red = lightness * 255 - green = red - blue = red - else - var/a;var/b; - if(lightness < 0.5) - b = lightness*(1+saturation) - else - b = (lightness+saturation) - (saturation*lightness) - a = 2*lightness - b - - red = round(255 * hue2rgb(a, b, hue+(1/3))) - green = round(255 * hue2rgb(a, b, hue)) - blue = round(255 * hue2rgb(a, b, hue-(1/3))) - - return list(red, green, blue) - -/proc/hue2rgb(a, b, hue) - if(hue < 0) - hue++ - else if(hue > 1) - hue-- - if(6*hue < 1) - return (a+(b-a)*6*hue) - if(2*hue < 1) - return b - if(3*hue < 2) - return (a+(b-a)*((2/3)-hue)*6) - return a - -/// For finding out what body parts a body zone covers, the inverse of the below basically -/proc/zone2body_parts_covered(def_zone) - switch(def_zone) - if(BODY_ZONE_CHEST) - return list(CHEST, GROIN) - if(BODY_ZONE_HEAD) - return list(HEAD) - if(BODY_ZONE_L_ARM) - return list(ARM_LEFT, HAND_LEFT) - if(BODY_ZONE_R_ARM) - return list(ARM_RIGHT, HAND_RIGHT) - if(BODY_ZONE_L_LEG) - return list(LEG_LEFT, FOOT_LEFT) - if(BODY_ZONE_R_LEG) - return list(LEG_RIGHT, FOOT_RIGHT) - -//Turns a Body_parts_covered bitfield into a list of organ/limb names. -//(I challenge you to find a use for this) -I found a use for it!! -/proc/body_parts_covered2organ_names(bpc) - var/list/covered_parts = list() - - if(!bpc) - return 0 - - if(bpc & FULL_BODY) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM,BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - - else - if(bpc & HEAD) - covered_parts |= list(BODY_ZONE_HEAD) - if(bpc & CHEST) - covered_parts |= list(BODY_ZONE_CHEST) - if(bpc & GROIN) - covered_parts |= list(BODY_ZONE_CHEST) - - if(bpc & ARMS) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) - else - if(bpc & ARM_LEFT) - covered_parts |= list(BODY_ZONE_L_ARM) - if(bpc & ARM_RIGHT) - covered_parts |= list(BODY_ZONE_R_ARM) - - if(bpc & HANDS) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) - else - if(bpc & HAND_LEFT) - covered_parts |= list(BODY_ZONE_L_ARM) - if(bpc & HAND_RIGHT) - covered_parts |= list(BODY_ZONE_R_ARM) - - if(bpc & LEGS) - covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - else - if(bpc & LEG_LEFT) - covered_parts |= list(BODY_ZONE_L_LEG) - if(bpc & LEG_RIGHT) - covered_parts |= list(BODY_ZONE_R_LEG) - - if(bpc & FEET) - covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - else - if(bpc & FOOT_LEFT) - covered_parts |= list(BODY_ZONE_L_LEG) - if(bpc & FOOT_RIGHT) - covered_parts |= list(BODY_ZONE_R_LEG) - - return covered_parts - -/proc/slot2body_zone(slot) - switch(slot) - if(ITEM_SLOT_BACK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_BELT, ITEM_SLOT_ID) - return BODY_ZONE_CHEST - - if(ITEM_SLOT_GLOVES, ITEM_SLOT_HANDS, ITEM_SLOT_HANDCUFFED) - return pick(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) - - if(ITEM_SLOT_HEAD, ITEM_SLOT_NECK, ITEM_SLOT_NECK, ITEM_SLOT_EARS) - return BODY_ZONE_HEAD - - if(ITEM_SLOT_MASK) - return BODY_ZONE_PRECISE_MOUTH - - if(ITEM_SLOT_EYES) - return BODY_ZONE_PRECISE_EYES - - if(ITEM_SLOT_FEET) - return pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT) - - if(ITEM_SLOT_LEGCUFFED) - return pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - -//adapted from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ -/proc/heat2colour(temp) - return rgb(heat2colour_r(temp), heat2colour_g(temp), heat2colour_b(temp)) - - -/proc/heat2colour_r(temp) - temp /= 100 - if(temp <= 66) - . = 255 - else - . = max(0, min(255, 329.698727446 * (temp - 60) ** -0.1332047592)) - - -/proc/heat2colour_g(temp) - temp /= 100 - if(temp <= 66) - . = max(0, min(255, 99.4708025861 * log(temp) - 161.1195681661)) - else - . = max(0, min(255, 288.1221685293 * ((temp - 60) ** -0.075148492))) - - -/proc/heat2colour_b(temp) - temp /= 100 - if(temp >= 66) - . = 255 - else - if(temp <= 16) - . = 0 - else - . = max(0, min(255, 138.5177312231 * log(temp - 10) - 305.0447927307)) - - -/proc/color2hex(color) //web colors - if(!color) - return "#000000" - - switch(color) - if("white") - return "#FFFFFF" - if("black") - return "#000000" - if("gray") - return "#808080" - if("brown") - return "#A52A2A" - if("red") - return "#FF0000" - if("darkred") - return "#8B0000" - if("crimson") - return "#DC143C" - if("orange") - return "#FFA500" - if("yellow") - return "#FFFF00" - if("green") - return "#008000" - if("lime") - return "#00FF00" - if("darkgreen") - return "#006400" - if("cyan") - return "#00FFFF" - if("blue") - return "#0000FF" - if("navy") - return "#000080" - if("teal") - return "#008080" - if("purple") - return "#800080" - if("indigo") - return "#4B0082" - else - return "#FFFFFF" - - -//This is a weird one: -//It returns a list of all var names found in the string -//These vars must be in the [var_name] format -//It's only a proc because it's used in more than one place - -//Takes a string and a datum -//The string is well, obviously the string being checked -//The datum is used as a source for var names, to check validity -//Otherwise every single word could technically be a variable! -/proc/string2listofvars(t_string, datum/var_source) - if(!t_string || !var_source) - return list() - - . = list() - - var/var_found = findtext(t_string,"\[") //Not the actual variables, just a generic "should we even bother" check - if(var_found) - //Find var names - - // "A dog said hi [name]!" - // splittext() --> list("A dog said hi ","name]!" - // jointext() --> "A dog said hi name]!" - // splittext() --> list("A","dog","said","hi","name]!") - - t_string = replacetext(t_string,"\[","\[ ")//Necessary to resolve "word[var_name]" scenarios - var/list/list_value = splittext(t_string,"\[") - var/intermediate_stage = jointext(list_value, null) - - list_value = splittext(intermediate_stage," ") - for(var/value in list_value) - if(findtext(value,"]")) - value = splittext(value,"]") //"name]!" --> list("name","!") - for(var/A in value) - if(var_source.vars.Find(A)) - . += A - -//assumes format #RRGGBB #rrggbb -/proc/color_hex2num(A) - if(!A || length(A) != length_char(A)) - return 0 - var/R = hex2num(copytext(A, 2, 4)) - var/G = hex2num(copytext(A, 4, 6)) - var/B = hex2num(copytext(A, 6, 8)) - return R+G+B - -//word of warning: using a matrix like this as a color value will simplify it back to a string after being set -/proc/color_hex2color_matrix(string) - var/length = length(string) - if((length != 7 && length != 9) || length != length_char(string)) - return color_matrix_identity() - var/r = hex2num(copytext(string, 2, 4))/255 - var/g = hex2num(copytext(string, 4, 6))/255 - var/b = hex2num(copytext(string, 6, 8))/255 - var/a = 1 - if(length == 9) - a = hex2num(copytext(string, 8, 10))/255 - if(!isnum(r) || !isnum(g) || !isnum(b) || !isnum(a)) - return color_matrix_identity() - return list(r,0,0,0, 0,g,0,0, 0,0,b,0, 0,0,0,a, 0,0,0,0) - -//will drop all values not on the diagonal -/proc/color_matrix2color_hex(list/the_matrix) - if(!istype(the_matrix) || the_matrix.len != 20) - return "#ffffffff" - return rgb(the_matrix[1]*255, the_matrix[6]*255, the_matrix[11]*255, the_matrix[16]*255) - -/proc/type2parent(child) - var/string_type = "[child]" - var/last_slash = findlasttext(string_type, "/") - if(last_slash == 1) - switch(child) - if(/datum) - return null - if(/obj || /mob) - return /atom/movable - if(/area || /turf) - return /atom - else - return /datum - return text2path(copytext(string_type, 1, last_slash)) - -//returns a string the last bit of a type, without the preceeding '/' -/proc/type2top(the_type) - //handle the builtins manually - if(!ispath(the_type)) - return - switch(the_type) - if(/datum) - return "datum" - if(/atom) - return "atom" - if(/obj) - return "obj" - if(/mob) - return "mob" - if(/area) - return "area" - if(/turf) - return "turf" - else //regex everything else (works for /proc too) - return lowertext(replacetext("[the_type]", "[type2parent(the_type)]/", "")) +/* + * Holds procs designed to change one type of value, into another. + * Contains: + * file2list + * angle2dir + * angle2text + * worldtime2text + * text2dir_extended & dir2text_short + */ + + + +//Splits the text of a file at seperator and returns them in a list. +//returns an empty list if the file doesn't exist +/world/proc/file2list(filename, seperator="\n", trim = TRUE) + if (trim) + return splittext(trim(file2text(filename)),seperator) + return splittext(file2text(filename),seperator) + +//Turns a direction into text +/proc/dir2text(direction) + switch(direction) + if(NORTH) + return "north" + if(SOUTH) + return "south" + if(EAST) + return "east" + if(WEST) + return "west" + if(NORTHEAST) + return "northeast" + if(SOUTHEAST) + return "southeast" + if(NORTHWEST) + return "northwest" + if(SOUTHWEST) + return "southwest" + else + return + +//Turns text into proper directions +/proc/text2dir(direction) + switch(uppertext(direction)) + if("NORTH") + return NORTH + if("SOUTH") + return SOUTH + if("EAST") + return EAST + if("WEST") + return WEST + if("NORTHEAST") + return NORTHEAST + if("NORTHWEST") + return NORTHWEST + if("SOUTHEAST") + return SOUTHEAST + if("SOUTHWEST") + return SOUTHWEST + else + return + +//Converts an angle (degrees) into a ss13 direction +GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,SOUTHWEST,WEST,NORTHWEST)) +#define angle2dir(X) (GLOB.modulo_angle_to_dir[round((((X%360)+382.5)%360)/45)+1]) + +/proc/angle2dir_cardinal(degree) + degree = SIMPLIFY_DEGREES(degree) + switch(round(degree, 0.1)) + if(315.5 to 360, 0 to 45.5) + return NORTH + if(45.6 to 135.5) + return EAST + if(135.6 to 225.5) + return SOUTH + if(225.6 to 315.5) + return WEST + +//returns the north-zero clockwise angle in degrees, given a direction +/proc/dir2angle(D) + switch(D) + if(NORTH) + return 0 + if(SOUTH) + return 180 + if(EAST) + return 90 + if(WEST) + return 270 + if(NORTHEAST) + return 45 + if(SOUTHEAST) + return 135 + if(NORTHWEST) + return 315 + if(SOUTHWEST) + return 225 + else + return null + +//Returns the angle in english +/proc/angle2text(degree) + return dir2text(angle2dir(degree)) + +//Converts a blend_mode constant to one acceptable to icon.Blend() +/proc/blendMode2iconMode(blend_mode) + switch(blend_mode) + if(BLEND_MULTIPLY) + return ICON_MULTIPLY + if(BLEND_ADD) + return ICON_ADD + if(BLEND_SUBTRACT) + return ICON_SUBTRACT + else + return ICON_OVERLAY + +//Converts a rights bitfield into a string +/proc/rights2text(rights, seperator="", prefix = "+") + seperator += prefix + if(rights & R_BUILD) + . += "[seperator]BUILDMODE" + if(rights & R_ADMIN) + . += "[seperator]ADMIN" + if(rights & R_BAN) + . += "[seperator]BAN" + if(rights & R_FUN) + . += "[seperator]FUN" + if(rights & R_SERVER) + . += "[seperator]SERVER" + if(rights & R_DEBUG) + . += "[seperator]DEBUG" + if(rights & R_POSSESS) + . += "[seperator]POSSESS" + if(rights & R_PERMISSIONS) + . += "[seperator]PERMISSIONS" + if(rights & R_STEALTH) + . += "[seperator]STEALTH" + if(rights & R_POLL) + . += "[seperator]POLL" + if(rights & R_VAREDIT) + . += "[seperator]VAREDIT" + if(rights & R_SOUND) + . += "[seperator]SOUND" + if(rights & R_SPAWN) + . += "[seperator]SPAWN" + if(rights & R_AUTOADMIN) + . += "[seperator]AUTOLOGIN" + if(rights & R_DBRANKS) + . += "[seperator]DBRANKS" + if(!.) + . = "NONE" + return . + +//colour formats +/proc/rgb2hsl(red, green, blue) + red /= 255;green /= 255;blue /= 255; + var/max = max(red,green,blue) + var/min = min(red,green,blue) + var/range = max-min + + var/hue=0;var/saturation=0;var/lightness=0; + lightness = (max + min)/2 + if(range != 0) + if(lightness < 0.5) + saturation = range/(max+min) + else + saturation = range/(2-max-min) + + var/dred = ((max-red)/(6*max)) + 0.5 + var/dgreen = ((max-green)/(6*max)) + 0.5 + var/dblue = ((max-blue)/(6*max)) + 0.5 + + if(max==red) + hue = dblue - dgreen + else if(max==green) + hue = dred - dblue + (1/3) + else + hue = dgreen - dred + (2/3) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + + return list(hue, saturation, lightness) + +/proc/hsl2rgb(hue, saturation, lightness) + var/red;var/green;var/blue; + if(saturation == 0) + red = lightness * 255 + green = red + blue = red + else + var/a;var/b; + if(lightness < 0.5) + b = lightness*(1+saturation) + else + b = (lightness+saturation) - (saturation*lightness) + a = 2*lightness - b + + red = round(255 * hue2rgb(a, b, hue+(1/3))) + green = round(255 * hue2rgb(a, b, hue)) + blue = round(255 * hue2rgb(a, b, hue-(1/3))) + + return list(red, green, blue) + +/proc/hue2rgb(a, b, hue) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + if(6*hue < 1) + return (a+(b-a)*6*hue) + if(2*hue < 1) + return b + if(3*hue < 2) + return (a+(b-a)*((2/3)-hue)*6) + return a + +/// For finding out what body parts a body zone covers, the inverse of the below basically +/proc/zone2body_parts_covered(def_zone) + switch(def_zone) + if(BODY_ZONE_CHEST) + return list(CHEST, GROIN) + if(BODY_ZONE_HEAD) + return list(HEAD) + if(BODY_ZONE_L_ARM) + return list(ARM_LEFT, HAND_LEFT) + if(BODY_ZONE_R_ARM) + return list(ARM_RIGHT, HAND_RIGHT) + if(BODY_ZONE_L_LEG) + return list(LEG_LEFT, FOOT_LEFT) + if(BODY_ZONE_R_LEG) + return list(LEG_RIGHT, FOOT_RIGHT) + +//Turns a Body_parts_covered bitfield into a list of organ/limb names. +//(I challenge you to find a use for this) -I found a use for it!! +/proc/body_parts_covered2organ_names(bpc) + var/list/covered_parts = list() + + if(!bpc) + return 0 + + if(bpc & FULL_BODY) + covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM,BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) + + else + if(bpc & HEAD) + covered_parts |= list(BODY_ZONE_HEAD) + if(bpc & CHEST) + covered_parts |= list(BODY_ZONE_CHEST) + if(bpc & GROIN) + covered_parts |= list(BODY_ZONE_CHEST) + + if(bpc & ARMS) + covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) + else + if(bpc & ARM_LEFT) + covered_parts |= list(BODY_ZONE_L_ARM) + if(bpc & ARM_RIGHT) + covered_parts |= list(BODY_ZONE_R_ARM) + + if(bpc & HANDS) + covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) + else + if(bpc & HAND_LEFT) + covered_parts |= list(BODY_ZONE_L_ARM) + if(bpc & HAND_RIGHT) + covered_parts |= list(BODY_ZONE_R_ARM) + + if(bpc & LEGS) + covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) + else + if(bpc & LEG_LEFT) + covered_parts |= list(BODY_ZONE_L_LEG) + if(bpc & LEG_RIGHT) + covered_parts |= list(BODY_ZONE_R_LEG) + + if(bpc & FEET) + covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) + else + if(bpc & FOOT_LEFT) + covered_parts |= list(BODY_ZONE_L_LEG) + if(bpc & FOOT_RIGHT) + covered_parts |= list(BODY_ZONE_R_LEG) + + return covered_parts + +/proc/slot2body_zone(slot) + switch(slot) + if(ITEM_SLOT_BACK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_BELT, ITEM_SLOT_ID) + return BODY_ZONE_CHEST + + if(ITEM_SLOT_GLOVES, ITEM_SLOT_HANDS, ITEM_SLOT_HANDCUFFED) + return pick(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) + + if(ITEM_SLOT_HEAD, ITEM_SLOT_NECK, ITEM_SLOT_NECK, ITEM_SLOT_EARS) + return BODY_ZONE_HEAD + + if(ITEM_SLOT_MASK) + return BODY_ZONE_PRECISE_MOUTH + + if(ITEM_SLOT_EYES) + return BODY_ZONE_PRECISE_EYES + + if(ITEM_SLOT_FEET) + return pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT) + + if(ITEM_SLOT_LEGCUFFED) + return pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + +//adapted from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ +/proc/heat2colour(temp) + return rgb(heat2colour_r(temp), heat2colour_g(temp), heat2colour_b(temp)) + + +/proc/heat2colour_r(temp) + temp /= 100 + if(temp <= 66) + . = 255 + else + . = max(0, min(255, 329.698727446 * (temp - 60) ** -0.1332047592)) + + +/proc/heat2colour_g(temp) + temp /= 100 + if(temp <= 66) + . = max(0, min(255, 99.4708025861 * log(temp) - 161.1195681661)) + else + . = max(0, min(255, 288.1221685293 * ((temp - 60) ** -0.075148492))) + + +/proc/heat2colour_b(temp) + temp /= 100 + if(temp >= 66) + . = 255 + else + if(temp <= 16) + . = 0 + else + . = max(0, min(255, 138.5177312231 * log(temp - 10) - 305.0447927307)) + + +/proc/color2hex(color) //web colors + if(!color) + return "#000000" + + switch(color) + if("white") + return "#FFFFFF" + if("black") + return "#000000" + if("gray") + return "#808080" + if("brown") + return "#A52A2A" + if("red") + return "#FF0000" + if("darkred") + return "#8B0000" + if("crimson") + return "#DC143C" + if("orange") + return "#FFA500" + if("yellow") + return "#FFFF00" + if("green") + return "#008000" + if("lime") + return "#00FF00" + if("darkgreen") + return "#006400" + if("cyan") + return "#00FFFF" + if("blue") + return "#0000FF" + if("navy") + return "#000080" + if("teal") + return "#008080" + if("purple") + return "#800080" + if("indigo") + return "#4B0082" + else + return "#FFFFFF" + + +//This is a weird one: +//It returns a list of all var names found in the string +//These vars must be in the [var_name] format +//It's only a proc because it's used in more than one place + +//Takes a string and a datum +//The string is well, obviously the string being checked +//The datum is used as a source for var names, to check validity +//Otherwise every single word could technically be a variable! +/proc/string2listofvars(t_string, datum/var_source) + if(!t_string || !var_source) + return list() + + . = list() + + var/var_found = findtext(t_string,"\[") //Not the actual variables, just a generic "should we even bother" check + if(var_found) + //Find var names + + // "A dog said hi [name]!" + // splittext() --> list("A dog said hi ","name]!" + // jointext() --> "A dog said hi name]!" + // splittext() --> list("A","dog","said","hi","name]!") + + t_string = replacetext(t_string,"\[","\[ ")//Necessary to resolve "word[var_name]" scenarios + var/list/list_value = splittext(t_string,"\[") + var/intermediate_stage = jointext(list_value, null) + + list_value = splittext(intermediate_stage," ") + for(var/value in list_value) + if(findtext(value,"]")) + value = splittext(value,"]") //"name]!" --> list("name","!") + for(var/A in value) + if(var_source.vars.Find(A)) + . += A + +//assumes format #RRGGBB #rrggbb +/proc/color_hex2num(A) + if(!A || length(A) != length_char(A)) + return 0 + var/R = hex2num(copytext(A, 2, 4)) + var/G = hex2num(copytext(A, 4, 6)) + var/B = hex2num(copytext(A, 6, 8)) + return R+G+B + +//word of warning: using a matrix like this as a color value will simplify it back to a string after being set +/proc/color_hex2color_matrix(string) + var/length = length(string) + if((length != 7 && length != 9) || length != length_char(string)) + return color_matrix_identity() + var/r = hex2num(copytext(string, 2, 4))/255 + var/g = hex2num(copytext(string, 4, 6))/255 + var/b = hex2num(copytext(string, 6, 8))/255 + var/a = 1 + if(length == 9) + a = hex2num(copytext(string, 8, 10))/255 + if(!isnum(r) || !isnum(g) || !isnum(b) || !isnum(a)) + return color_matrix_identity() + return list(r,0,0,0, 0,g,0,0, 0,0,b,0, 0,0,0,a, 0,0,0,0) + +//will drop all values not on the diagonal +/proc/color_matrix2color_hex(list/the_matrix) + if(!istype(the_matrix) || the_matrix.len != 20) + return "#ffffffff" + return rgb(the_matrix[1]*255, the_matrix[6]*255, the_matrix[11]*255, the_matrix[16]*255) + +/proc/type2parent(child) + var/string_type = "[child]" + var/last_slash = findlasttext(string_type, "/") + if(last_slash == 1) + switch(child) + if(/datum) + return null + if(/obj || /mob) + return /atom/movable + if(/area || /turf) + return /atom + else + return /datum + return text2path(copytext(string_type, 1, last_slash)) + +//returns a string the last bit of a type, without the preceeding '/' +/proc/type2top(the_type) + //handle the builtins manually + if(!ispath(the_type)) + return + switch(the_type) + if(/datum) + return "datum" + if(/atom) + return "atom" + if(/obj) + return "obj" + if(/mob) + return "mob" + if(/area) + return "area" + if(/turf) + return "turf" + else //regex everything else (works for /proc too) + return lowertext(replacetext("[the_type]", "[type2parent(the_type)]/", "")) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 01ec8b22244..1c7daf5ddf9 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -1,64 +1,64 @@ -//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this - //uncommented, but not visible in the release version) - -//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed. - -// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error -#ifdef DEBUG -#define USE_CUSTOM_ERROR_HANDLER -#endif - -#ifdef TESTING -#define DATUMVAR_DEBUGGING_MODE - -//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing. - //implies FIND_REF_NO_CHECK_TICK - -//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping - -//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green -#endif - -//#define REFERENCE_TRACKING //Enables extools-powered reference tracking system, letting you see what is - //referencing objects that refuse to hard delete - -//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER - -#ifndef PRELOAD_RSC //set to: -#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour; -#endif // 1 to use the default behaviour; - // 2 for preloading absolutely everything; - -#ifdef LOWMEMORYMODE -#define FORCE_MAP "_maps/runtimestation.json" -#endif - -//Update this whenever you need to take advantage of more recent byond features -#define MIN_COMPILER_VERSION 513 -#define MIN_COMPILER_BUILD 1514 -#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD -//Don't forget to update this part -#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update. -#error You need version 513.1514 or higher -#endif - -//Additional code for the above flags. -#ifdef TESTING -#warn compiling in TESTING mode. testing() debug messages will be visible. -#endif - -#ifdef GC_FAILURE_HARD_LOOKUP -#define FIND_REF_NO_CHECK_TICK -#endif - -#ifdef TRAVISBUILDING -#define UNIT_TESTS -#endif - -#ifdef TRAVISTESTING -#define TESTING -#endif - -// A reasonable number of maximum overlays an object needs -// If you think you need more, rethink it -#define MAX_ATOM_OVERLAYS 100 +//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this + //uncommented, but not visible in the release version) + +//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed. + +// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error +#ifdef DEBUG +#define USE_CUSTOM_ERROR_HANDLER +#endif + +#ifdef TESTING +#define DATUMVAR_DEBUGGING_MODE + +//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing. + //implies FIND_REF_NO_CHECK_TICK + +//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping + +//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green +#endif + +//#define REFERENCE_TRACKING //Enables extools-powered reference tracking system, letting you see what is + //referencing objects that refuse to hard delete + +//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER + +#ifndef PRELOAD_RSC //set to: +#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour; +#endif // 1 to use the default behaviour; + // 2 for preloading absolutely everything; + +#ifdef LOWMEMORYMODE +#define FORCE_MAP "_maps/runtimestation.json" +#endif + +//Update this whenever you need to take advantage of more recent byond features +#define MIN_COMPILER_VERSION 513 +#define MIN_COMPILER_BUILD 1514 +#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD +//Don't forget to update this part +#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update. +#error You need version 513.1514 or higher +#endif + +//Additional code for the above flags. +#ifdef TESTING +#warn compiling in TESTING mode. testing() debug messages will be visible. +#endif + +#ifdef GC_FAILURE_HARD_LOOKUP +#define FIND_REF_NO_CHECK_TICK +#endif + +#ifdef TRAVISBUILDING +#define UNIT_TESTS +#endif + +#ifdef TRAVISTESTING +#define TESTING +#endif + +// A reasonable number of maximum overlays an object needs +// If you think you need more, rethink it +#define MAX_ATOM_OVERLAYS 100 diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 969ba0a0a99..480cd9b6f98 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -1,217 +1,217 @@ -GLOBAL_LIST_INIT(bitfields, list( - "appearance_flags" = list( - "LONG_GLIDE" = LONG_GLIDE, - "RESET_COLOR" = RESET_COLOR, - "RESET_ALPHA" = RESET_ALPHA, - "RESET_TRANSFORM" = RESET_TRANSFORM, - "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, - "KEEP_TOGETHER" = KEEP_TOGETHER, - "KEEP_APART" = KEEP_APART, - "PLANE_MASTER" = PLANE_MASTER, - "TILE_BOUND" = TILE_BOUND, - "PIXEL_SCALE" = PIXEL_SCALE - ), - "sight" = list( - "SEE_INFRA" = SEE_INFRA, - "SEE_SELF" = SEE_SELF, - "SEE_MOBS" = SEE_MOBS, - "SEE_OBJS" = SEE_OBJS, - "SEE_TURFS" = SEE_TURFS, - "SEE_PIXELS" = SEE_PIXELS, - "SEE_THRU" = SEE_THRU, - "SEE_BLACKNESS" = SEE_BLACKNESS, - "BLIND" = BLIND - ), - "obj_flags" = list( - "EMAGGED" = EMAGGED, - "IN_USE" = IN_USE, - "CAN_BE_HIT" = CAN_BE_HIT, - "BEING_SHOCKED" = BEING_SHOCKED, - "DANGEROUS_POSSESSION" = DANGEROUS_POSSESSION, - "ON_BLUEPRINTS" = ON_BLUEPRINTS, - "UNIQUE_RENAME" = UNIQUE_RENAME, - "USES_TGUI" = USES_TGUI, - "FROZEN" = FROZEN, - ), - "datum_flags" = list( - "DF_USE_TAG" = DF_USE_TAG, - "DF_VAR_EDITED" = DF_VAR_EDITED, - "DF_ISPROCESSING" = DF_ISPROCESSING, - ), - "item_flags" = list( - "BEING_REMOVED" = BEING_REMOVED, - "IN_INVENTORY" = IN_INVENTORY, - "FORCE_STRING_OVERRIDE" = FORCE_STRING_OVERRIDE, - "NEEDS_PERMIT" = NEEDS_PERMIT, - "SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND, - "NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION, - "DROPDEL" = DROPDEL, - "NOBLUDGEON" = NOBLUDGEON, - "ABSTRACT" = ABSTRACT, - "IN_STORAGE" = IN_STORAGE, - ), - "admin_flags" = list( - "BUILDMODE" = R_BUILD, - "ADMIN" = R_ADMIN, - "BAN" = R_BAN, - "FUN" = R_FUN, - "SERVER" = R_SERVER, - "DEBUG" = R_DEBUG, - "POSSESS" = R_POSSESS, - "PERMISSIONS" = R_PERMISSIONS, - "STEALTH" = R_STEALTH, - "POLL" = R_POLL, - "VAREDIT" = R_VAREDIT, - "SOUNDS" = R_SOUND, - "SPAWN" = R_SPAWN, - "AUTOLOGIN" = R_AUTOADMIN, - "DBRANKS" = R_DBRANKS - ), - "interaction_flags_atom" = list( - "INTERACT_ATOM_REQUIRES_ANCHORED" = INTERACT_ATOM_REQUIRES_ANCHORED, - "INTERACT_ATOM_ATTACK_HAND" = INTERACT_ATOM_ATTACK_HAND, - "INTERACT_ATOM_UI_INTERACT" = INTERACT_ATOM_UI_INTERACT, - "INTERACT_ATOM_REQUIRES_DEXTERITY" = INTERACT_ATOM_REQUIRES_DEXTERITY, - "INTERACT_ATOM_IGNORE_INCAPACITATED" = INTERACT_ATOM_IGNORE_INCAPACITATED, - "INTERACT_ATOM_IGNORE_RESTRAINED" = INTERACT_ATOM_IGNORE_RESTRAINED, - "INTERACT_ATOM_CHECK_GRAB" = INTERACT_ATOM_CHECK_GRAB, - "INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND" = INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND, - "INTERACT_ATOM_NO_FINGERPRINT_INTERACT" = INTERACT_ATOM_NO_FINGERPRINT_INTERACT - ), - "interaction_flags_machine" = list( - "INTERACT_MACHINE_OPEN" = INTERACT_MACHINE_OPEN, - "INTERACT_MACHINE_OFFLINE" = INTERACT_MACHINE_OFFLINE, - "INTERACT_MACHINE_WIRES_IF_OPEN" = INTERACT_MACHINE_WIRES_IF_OPEN, - "INTERACT_MACHINE_ALLOW_SILICON" = INTERACT_MACHINE_ALLOW_SILICON, - "INTERACT_MACHINE_OPEN_SILICON" = INTERACT_MACHINE_OPEN_SILICON, - "INTERACT_MACHINE_REQUIRES_SILICON" = INTERACT_MACHINE_REQUIRES_SILICON, - "INTERACT_MACHINE_SET_MACHINE" = INTERACT_MACHINE_SET_MACHINE - ), - "interaction_flags_item" = list( - "INTERACT_ITEM_ATTACK_HAND_PICKUP" = INTERACT_ITEM_ATTACK_HAND_PICKUP, - ), - "pass_flags" = list( - "PASSTABLE" = PASSTABLE, - "PASSGLASS" = PASSGLASS, - "PASSGRILLE" = PASSGRILLE, - "PASSBLOB" = PASSBLOB, - "PASSMOB" = PASSMOB, - "PASSCLOSEDTURF" = PASSCLOSEDTURF, - "LETPASSTHROW" = LETPASSTHROW - ), - "movement_type" = list( - "GROUND" = GROUND, - "FLYING" = FLYING, - "VENTCRAWLING" = VENTCRAWLING, - "FLOATING" = FLOATING, - "UNSTOPPABLE" = UNSTOPPABLE - ), - "resistance_flags" = list( - "LAVA_PROOF" = LAVA_PROOF, - "FIRE_PROOF" = FIRE_PROOF, - "FLAMMABLE" = FLAMMABLE, - "ON_FIRE" = ON_FIRE, - "UNACIDABLE" = UNACIDABLE, - "ACID_PROOF" = ACID_PROOF, - "INDESTRUCTIBLE" = INDESTRUCTIBLE, - "FREEZE_PROOF" = FREEZE_PROOF - ), - "flags_1" = list( - "NOJAUNT_1" = NOJAUNT_1, - "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, - "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, - "CULT_PERMITTED_1" = CULT_PERMITTED_1, - "HEAR_1" = HEAR_1, - "CONDUCT_1" = CONDUCT_1, - "NO_LAVA_GEN_1" = NO_LAVA_GEN_1, - "NODECONSTRUCT_1" = NODECONSTRUCT_1, - "OVERLAY_QUEUED_1" = OVERLAY_QUEUED_1, - "ON_BORDER_1" = ON_BORDER_1, - "NO_RUINS_1" = NO_RUINS_1, - "PREVENT_CLICK_UNDER_1" = PREVENT_CLICK_UNDER_1, - "HOLOGRAM_1" = HOLOGRAM_1, - "SHOCKED_1" = SHOCKED_1, - "INITIALIZED_1" = INITIALIZED_1, - "ADMIN_SPAWNED_1" = ADMIN_SPAWNED_1, - "PREVENT_CONTENTS_EXPLOSION_1" = PREVENT_CONTENTS_EXPLOSION_1, - "RAD_PROTECT_CONTENTS_1" = RAD_PROTECT_CONTENTS_1, - "RAD_NO_CONTAMINATE_1" = RAD_NO_CONTAMINATE_1 - ), - "flags_ricochet" = list( - "RICOCHET_SHINY" = RICOCHET_SHINY, - "RICOCHET_HARD" = RICOCHET_HARD - ), - "clothing_flags" = list( - "LAVAPROTECT" = LAVAPROTECT, - "STOPSPRESSUREDAMAGE" = STOPSPRESSUREDAMAGE, - "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT, - "MASKINTERNALS" = MASKINTERNALS, - "NOSLIP" = NOSLIP, - "THICKMATERIAL" = THICKMATERIAL, - "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, - "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, - "SCAN_REAGENTS" = SCAN_REAGENTS, - "BLOCKS_SHOVE_KNOCKDOWN" = BLOCKS_SHOVE_KNOCKDOWN, - "SNUG_FIT" = SNUG_FIT, - "ANTI_TINFOIL_MANEUVER" = ANTI_TINFOIL_MANEUVER, - ), - "zap_flags" = list( - "ZAP_MOB_DAMAGE" = ZAP_MOB_DAMAGE, - "ZAP_OBJ_DAMAGE" = ZAP_OBJ_DAMAGE, - "ZAP_MOB_STUN" = ZAP_MOB_STUN, - "ZAP_ALLOW_DUPLICATES" = ZAP_ALLOW_DUPLICATES, - "ZAP_MACHINE_EXPLOSIVE" = ZAP_MACHINE_EXPLOSIVE, - ), - "smooth" = list( - "SMOOTH_TRUE" = SMOOTH_TRUE, - "SMOOTH_MORE" = SMOOTH_MORE, - "SMOOTH_DIAGONAL" = SMOOTH_DIAGONAL, - "SMOOTH_BORDER" = SMOOTH_BORDER, - "SMOOTH_QUEUED" = SMOOTH_QUEUED, - ), - "car_traits" = list( - "CAN_KIDNAP" = CAN_KIDNAP, - ), - "mobility_flags" = list( - "MOVE" = MOBILITY_MOVE, - "STAND" = MOBILITY_STAND, - "PICKUP" = MOBILITY_PICKUP, - "USE" = MOBILITY_USE, - "UI" = MOBILITY_UI, - "STORAGE" = MOBILITY_STORAGE, - "PULL" = MOBILITY_PULL, - ), - "disease_flags" = list ( - "CURABLE" = CURABLE, - "CAN_CARRY" = CAN_CARRY, - "CAN_RESIST" = CAN_RESIST - ), - "mob_biotypes" = list ( - "MOB_ORGANIC" = MOB_ORGANIC, - "MOB_MINERAL" = MOB_MINERAL, - "MOB_ROBOTIC" = MOB_ROBOTIC, - "MOB_UNDEAD" = MOB_UNDEAD, - "MOB_HUMANOID" = MOB_HUMANOID, - "MOB_BUG" = MOB_BUG, - "MOB_BEAST" = MOB_BEAST, - "MOB_EPIC" = MOB_EPIC, - "MOB_REPTILE" = MOB_REPTILE, - "MOB_SPIRIT" = MOB_SPIRIT - ), - "machine_stat" = list ( - "BROKEN" = BROKEN, - "NOPOWER" = NOPOWER, - "MAINT" = MAINT, - "EMPED" = EMPED - ), - "vis_flags" = list( - "VIS_INHERIT_ICON" = VIS_INHERIT_ICON, - "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE, - "VIS_INHERIT_DIR" = VIS_INHERIT_DIR, - "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER, - "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE, - "VIS_INHERIT_ID" = VIS_INHERIT_ID, - "VIS_UNDERLAY" = VIS_UNDERLAY, - "VIS_HIDE" = VIS_HIDE - ) - )) +GLOBAL_LIST_INIT(bitfields, list( + "appearance_flags" = list( + "LONG_GLIDE" = LONG_GLIDE, + "RESET_COLOR" = RESET_COLOR, + "RESET_ALPHA" = RESET_ALPHA, + "RESET_TRANSFORM" = RESET_TRANSFORM, + "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, + "KEEP_TOGETHER" = KEEP_TOGETHER, + "KEEP_APART" = KEEP_APART, + "PLANE_MASTER" = PLANE_MASTER, + "TILE_BOUND" = TILE_BOUND, + "PIXEL_SCALE" = PIXEL_SCALE + ), + "sight" = list( + "SEE_INFRA" = SEE_INFRA, + "SEE_SELF" = SEE_SELF, + "SEE_MOBS" = SEE_MOBS, + "SEE_OBJS" = SEE_OBJS, + "SEE_TURFS" = SEE_TURFS, + "SEE_PIXELS" = SEE_PIXELS, + "SEE_THRU" = SEE_THRU, + "SEE_BLACKNESS" = SEE_BLACKNESS, + "BLIND" = BLIND + ), + "obj_flags" = list( + "EMAGGED" = EMAGGED, + "IN_USE" = IN_USE, + "CAN_BE_HIT" = CAN_BE_HIT, + "BEING_SHOCKED" = BEING_SHOCKED, + "DANGEROUS_POSSESSION" = DANGEROUS_POSSESSION, + "ON_BLUEPRINTS" = ON_BLUEPRINTS, + "UNIQUE_RENAME" = UNIQUE_RENAME, + "USES_TGUI" = USES_TGUI, + "FROZEN" = FROZEN, + ), + "datum_flags" = list( + "DF_USE_TAG" = DF_USE_TAG, + "DF_VAR_EDITED" = DF_VAR_EDITED, + "DF_ISPROCESSING" = DF_ISPROCESSING, + ), + "item_flags" = list( + "BEING_REMOVED" = BEING_REMOVED, + "IN_INVENTORY" = IN_INVENTORY, + "FORCE_STRING_OVERRIDE" = FORCE_STRING_OVERRIDE, + "NEEDS_PERMIT" = NEEDS_PERMIT, + "SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND, + "NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION, + "DROPDEL" = DROPDEL, + "NOBLUDGEON" = NOBLUDGEON, + "ABSTRACT" = ABSTRACT, + "IN_STORAGE" = IN_STORAGE, + ), + "admin_flags" = list( + "BUILDMODE" = R_BUILD, + "ADMIN" = R_ADMIN, + "BAN" = R_BAN, + "FUN" = R_FUN, + "SERVER" = R_SERVER, + "DEBUG" = R_DEBUG, + "POSSESS" = R_POSSESS, + "PERMISSIONS" = R_PERMISSIONS, + "STEALTH" = R_STEALTH, + "POLL" = R_POLL, + "VAREDIT" = R_VAREDIT, + "SOUNDS" = R_SOUND, + "SPAWN" = R_SPAWN, + "AUTOLOGIN" = R_AUTOADMIN, + "DBRANKS" = R_DBRANKS + ), + "interaction_flags_atom" = list( + "INTERACT_ATOM_REQUIRES_ANCHORED" = INTERACT_ATOM_REQUIRES_ANCHORED, + "INTERACT_ATOM_ATTACK_HAND" = INTERACT_ATOM_ATTACK_HAND, + "INTERACT_ATOM_UI_INTERACT" = INTERACT_ATOM_UI_INTERACT, + "INTERACT_ATOM_REQUIRES_DEXTERITY" = INTERACT_ATOM_REQUIRES_DEXTERITY, + "INTERACT_ATOM_IGNORE_INCAPACITATED" = INTERACT_ATOM_IGNORE_INCAPACITATED, + "INTERACT_ATOM_IGNORE_RESTRAINED" = INTERACT_ATOM_IGNORE_RESTRAINED, + "INTERACT_ATOM_CHECK_GRAB" = INTERACT_ATOM_CHECK_GRAB, + "INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND" = INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND, + "INTERACT_ATOM_NO_FINGERPRINT_INTERACT" = INTERACT_ATOM_NO_FINGERPRINT_INTERACT + ), + "interaction_flags_machine" = list( + "INTERACT_MACHINE_OPEN" = INTERACT_MACHINE_OPEN, + "INTERACT_MACHINE_OFFLINE" = INTERACT_MACHINE_OFFLINE, + "INTERACT_MACHINE_WIRES_IF_OPEN" = INTERACT_MACHINE_WIRES_IF_OPEN, + "INTERACT_MACHINE_ALLOW_SILICON" = INTERACT_MACHINE_ALLOW_SILICON, + "INTERACT_MACHINE_OPEN_SILICON" = INTERACT_MACHINE_OPEN_SILICON, + "INTERACT_MACHINE_REQUIRES_SILICON" = INTERACT_MACHINE_REQUIRES_SILICON, + "INTERACT_MACHINE_SET_MACHINE" = INTERACT_MACHINE_SET_MACHINE + ), + "interaction_flags_item" = list( + "INTERACT_ITEM_ATTACK_HAND_PICKUP" = INTERACT_ITEM_ATTACK_HAND_PICKUP, + ), + "pass_flags" = list( + "PASSTABLE" = PASSTABLE, + "PASSGLASS" = PASSGLASS, + "PASSGRILLE" = PASSGRILLE, + "PASSBLOB" = PASSBLOB, + "PASSMOB" = PASSMOB, + "PASSCLOSEDTURF" = PASSCLOSEDTURF, + "LETPASSTHROW" = LETPASSTHROW + ), + "movement_type" = list( + "GROUND" = GROUND, + "FLYING" = FLYING, + "VENTCRAWLING" = VENTCRAWLING, + "FLOATING" = FLOATING, + "UNSTOPPABLE" = UNSTOPPABLE + ), + "resistance_flags" = list( + "LAVA_PROOF" = LAVA_PROOF, + "FIRE_PROOF" = FIRE_PROOF, + "FLAMMABLE" = FLAMMABLE, + "ON_FIRE" = ON_FIRE, + "UNACIDABLE" = UNACIDABLE, + "ACID_PROOF" = ACID_PROOF, + "INDESTRUCTIBLE" = INDESTRUCTIBLE, + "FREEZE_PROOF" = FREEZE_PROOF + ), + "flags_1" = list( + "NOJAUNT_1" = NOJAUNT_1, + "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, + "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, + "CULT_PERMITTED_1" = CULT_PERMITTED_1, + "HEAR_1" = HEAR_1, + "CONDUCT_1" = CONDUCT_1, + "NO_LAVA_GEN_1" = NO_LAVA_GEN_1, + "NODECONSTRUCT_1" = NODECONSTRUCT_1, + "OVERLAY_QUEUED_1" = OVERLAY_QUEUED_1, + "ON_BORDER_1" = ON_BORDER_1, + "NO_RUINS_1" = NO_RUINS_1, + "PREVENT_CLICK_UNDER_1" = PREVENT_CLICK_UNDER_1, + "HOLOGRAM_1" = HOLOGRAM_1, + "SHOCKED_1" = SHOCKED_1, + "INITIALIZED_1" = INITIALIZED_1, + "ADMIN_SPAWNED_1" = ADMIN_SPAWNED_1, + "PREVENT_CONTENTS_EXPLOSION_1" = PREVENT_CONTENTS_EXPLOSION_1, + "RAD_PROTECT_CONTENTS_1" = RAD_PROTECT_CONTENTS_1, + "RAD_NO_CONTAMINATE_1" = RAD_NO_CONTAMINATE_1 + ), + "flags_ricochet" = list( + "RICOCHET_SHINY" = RICOCHET_SHINY, + "RICOCHET_HARD" = RICOCHET_HARD + ), + "clothing_flags" = list( + "LAVAPROTECT" = LAVAPROTECT, + "STOPSPRESSUREDAMAGE" = STOPSPRESSUREDAMAGE, + "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT, + "MASKINTERNALS" = MASKINTERNALS, + "NOSLIP" = NOSLIP, + "THICKMATERIAL" = THICKMATERIAL, + "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, + "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, + "SCAN_REAGENTS" = SCAN_REAGENTS, + "BLOCKS_SHOVE_KNOCKDOWN" = BLOCKS_SHOVE_KNOCKDOWN, + "SNUG_FIT" = SNUG_FIT, + "ANTI_TINFOIL_MANEUVER" = ANTI_TINFOIL_MANEUVER, + ), + "zap_flags" = list( + "ZAP_MOB_DAMAGE" = ZAP_MOB_DAMAGE, + "ZAP_OBJ_DAMAGE" = ZAP_OBJ_DAMAGE, + "ZAP_MOB_STUN" = ZAP_MOB_STUN, + "ZAP_ALLOW_DUPLICATES" = ZAP_ALLOW_DUPLICATES, + "ZAP_MACHINE_EXPLOSIVE" = ZAP_MACHINE_EXPLOSIVE, + ), + "smooth" = list( + "SMOOTH_TRUE" = SMOOTH_TRUE, + "SMOOTH_MORE" = SMOOTH_MORE, + "SMOOTH_DIAGONAL" = SMOOTH_DIAGONAL, + "SMOOTH_BORDER" = SMOOTH_BORDER, + "SMOOTH_QUEUED" = SMOOTH_QUEUED, + ), + "car_traits" = list( + "CAN_KIDNAP" = CAN_KIDNAP, + ), + "mobility_flags" = list( + "MOVE" = MOBILITY_MOVE, + "STAND" = MOBILITY_STAND, + "PICKUP" = MOBILITY_PICKUP, + "USE" = MOBILITY_USE, + "UI" = MOBILITY_UI, + "STORAGE" = MOBILITY_STORAGE, + "PULL" = MOBILITY_PULL, + ), + "disease_flags" = list ( + "CURABLE" = CURABLE, + "CAN_CARRY" = CAN_CARRY, + "CAN_RESIST" = CAN_RESIST + ), + "mob_biotypes" = list ( + "MOB_ORGANIC" = MOB_ORGANIC, + "MOB_MINERAL" = MOB_MINERAL, + "MOB_ROBOTIC" = MOB_ROBOTIC, + "MOB_UNDEAD" = MOB_UNDEAD, + "MOB_HUMANOID" = MOB_HUMANOID, + "MOB_BUG" = MOB_BUG, + "MOB_BEAST" = MOB_BEAST, + "MOB_EPIC" = MOB_EPIC, + "MOB_REPTILE" = MOB_REPTILE, + "MOB_SPIRIT" = MOB_SPIRIT + ), + "machine_stat" = list ( + "BROKEN" = BROKEN, + "NOPOWER" = NOPOWER, + "MAINT" = MAINT, + "EMPED" = EMPED + ), + "vis_flags" = list( + "VIS_INHERIT_ICON" = VIS_INHERIT_ICON, + "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE, + "VIS_INHERIT_DIR" = VIS_INHERIT_DIR, + "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER, + "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE, + "VIS_INHERIT_ID" = VIS_INHERIT_ID, + "VIS_UNDERLAY" = VIS_UNDERLAY, + "VIS_HIDE" = VIS_HIDE + ) + )) diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm index 527ab1abad9..7301fcf3586 100644 --- a/code/_globalvars/configuration.dm +++ b/code/_globalvars/configuration.dm @@ -1,36 +1,36 @@ -GLOBAL_REAL(config, /datum/controller/configuration) - -GLOBAL_DATUM(revdata, /datum/getrev) - -GLOBAL_VAR(host) -GLOBAL_VAR(station_name) -GLOBAL_VAR_INIT(game_version, "/tg/Station 13") -GLOBAL_VAR_INIT(changelog_hash, "") -GLOBAL_VAR_INIT(hub_visibility, FALSE) - -GLOBAL_VAR_INIT(ooc_allowed, TRUE) // used with admin verbs to disable ooc - not a config option apparently -GLOBAL_VAR_INIT(dooc_allowed, TRUE) -GLOBAL_VAR_INIT(enter_allowed, TRUE) -GLOBAL_VAR_INIT(shuttle_frozen, FALSE) -GLOBAL_VAR_INIT(shuttle_left, FALSE) -GLOBAL_VAR_INIT(tinted_weldhelh, TRUE) - - -// Debug is used exactly once (in living.dm) but is commented out in a lot of places. It is not set anywhere and only checked. -// Debug2 is used in conjunction with a lot of admin verbs and therefore is actually legit. -GLOBAL_VAR_INIT(Debug, FALSE) // global debug switch -GLOBAL_VAR_INIT(Debug2, FALSE) - -//This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage -//Protecting these because the proper way to edit them is with the config/secrets -GLOBAL_VAR_INIT(MAX_EX_DEVESTATION_RANGE, 3) -GLOBAL_PROTECT(MAX_EX_DEVESTATION_RANGE) -GLOBAL_VAR_INIT(MAX_EX_HEAVY_RANGE, 7) -GLOBAL_PROTECT(MAX_EX_HEAVY_RANGE) -GLOBAL_VAR_INIT(MAX_EX_LIGHT_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_LIGHT_RANGE) -GLOBAL_VAR_INIT(MAX_EX_FLASH_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_FLASH_RANGE) -GLOBAL_VAR_INIT(MAX_EX_FLAME_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_FLAME_RANGE) -GLOBAL_VAR_INIT(DYN_EX_SCALE, 0.5) +GLOBAL_REAL(config, /datum/controller/configuration) + +GLOBAL_DATUM(revdata, /datum/getrev) + +GLOBAL_VAR(host) +GLOBAL_VAR(station_name) +GLOBAL_VAR_INIT(game_version, "/tg/Station 13") +GLOBAL_VAR_INIT(changelog_hash, "") +GLOBAL_VAR_INIT(hub_visibility, FALSE) + +GLOBAL_VAR_INIT(ooc_allowed, TRUE) // used with admin verbs to disable ooc - not a config option apparently +GLOBAL_VAR_INIT(dooc_allowed, TRUE) +GLOBAL_VAR_INIT(enter_allowed, TRUE) +GLOBAL_VAR_INIT(shuttle_frozen, FALSE) +GLOBAL_VAR_INIT(shuttle_left, FALSE) +GLOBAL_VAR_INIT(tinted_weldhelh, TRUE) + + +// Debug is used exactly once (in living.dm) but is commented out in a lot of places. It is not set anywhere and only checked. +// Debug2 is used in conjunction with a lot of admin verbs and therefore is actually legit. +GLOBAL_VAR_INIT(Debug, FALSE) // global debug switch +GLOBAL_VAR_INIT(Debug2, FALSE) + +//This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage +//Protecting these because the proper way to edit them is with the config/secrets +GLOBAL_VAR_INIT(MAX_EX_DEVESTATION_RANGE, 3) +GLOBAL_PROTECT(MAX_EX_DEVESTATION_RANGE) +GLOBAL_VAR_INIT(MAX_EX_HEAVY_RANGE, 7) +GLOBAL_PROTECT(MAX_EX_HEAVY_RANGE) +GLOBAL_VAR_INIT(MAX_EX_LIGHT_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_LIGHT_RANGE) +GLOBAL_VAR_INIT(MAX_EX_FLASH_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_FLASH_RANGE) +GLOBAL_VAR_INIT(MAX_EX_FLAME_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_FLAME_RANGE) +GLOBAL_VAR_INIT(DYN_EX_SCALE, 0.5) diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm index 36692e15f51..301a3bfacf8 100644 --- a/code/_globalvars/game_modes.dm +++ b/code/_globalvars/game_modes.dm @@ -1,15 +1,15 @@ -GLOBAL_VAR_INIT(master_mode, "traitor") //"extended" -GLOBAL_VAR_INIT(secret_force_mode, "secret") // if this is anything but "secret", the secret rotation will forceably choose this mode -GLOBAL_VAR(common_report) //Contains common part of roundend report -GLOBAL_VAR(survivor_report) //Contains shared survivor report for roundend report (part of personal report) - - -GLOBAL_VAR_INIT(wavesecret, 0) // meteor mode, delays wave progression, terrible name -GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report - - -//TODO clear this one up too -GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult) - -///We want reality_smash_tracker to exist only once and be accesable from anywhere. -GLOBAL_DATUM_INIT(reality_smash_track, /datum/reality_smash_tracker, new) +GLOBAL_VAR_INIT(master_mode, "traitor") //"extended" +GLOBAL_VAR_INIT(secret_force_mode, "secret") // if this is anything but "secret", the secret rotation will forceably choose this mode +GLOBAL_VAR(common_report) //Contains common part of roundend report +GLOBAL_VAR(survivor_report) //Contains shared survivor report for roundend report (part of personal report) + + +GLOBAL_VAR_INIT(wavesecret, 0) // meteor mode, delays wave progression, terrible name +GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report + + +//TODO clear this one up too +GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult) + +///We want reality_smash_tracker to exist only once and be accesable from anywhere. +GLOBAL_DATUM_INIT(reality_smash_track, /datum/reality_smash_tracker, new) diff --git a/code/_globalvars/genetics.dm b/code/_globalvars/genetics.dm index bdf53f3d71c..c5c424977cd 100644 --- a/code/_globalvars/genetics.dm +++ b/code/_globalvars/genetics.dm @@ -1,9 +1,9 @@ -//faster than having to constantly loop for them -GLOBAL_LIST_EMPTY_TYPED(all_mutations, /datum/mutation/human) //type = initialized mutation -GLOBAL_LIST_EMPTY(full_sequences) //type = correct sequence -GLOBAL_LIST_EMPTY(bad_mutations) //bad initialized mutations -GLOBAL_LIST_EMPTY(good_mutations) //good initialized mutations -GLOBAL_LIST_EMPTY(not_good_mutations) //neutral initialized mutations -GLOBAL_LIST_EMPTY(alias_mutations) //alias = type - -GLOBAL_LIST_EMPTY(mutation_recipes) +//faster than having to constantly loop for them +GLOBAL_LIST_EMPTY_TYPED(all_mutations, /datum/mutation/human) //type = initialized mutation +GLOBAL_LIST_EMPTY(full_sequences) //type = correct sequence +GLOBAL_LIST_EMPTY(bad_mutations) //bad initialized mutations +GLOBAL_LIST_EMPTY(good_mutations) //good initialized mutations +GLOBAL_LIST_EMPTY(not_good_mutations) //neutral initialized mutations +GLOBAL_LIST_EMPTY(alias_mutations) //alias = type + +GLOBAL_LIST_EMPTY(mutation_recipes) diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index da344c10e94..2153b90d98d 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -1,250 +1,250 @@ -//Preferences stuff - //Hairstyles -GLOBAL_LIST_EMPTY(hairstyles_list) //stores /datum/sprite_accessory/hair indexed by name -GLOBAL_LIST_EMPTY(hairstyles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(hairstyles_female_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hairstyles_list) //stores /datum/sprite_accessory/facial_hair indexed by name -GLOBAL_LIST_EMPTY(facial_hairstyles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hairstyles_female_list) //stores only hair names - //Underwear -GLOBAL_LIST_EMPTY(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name -GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name -GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name - //Undershirts -GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name -GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name -GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name - //Socks -GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name - //Lizard Bits (all datum lists indexed by name) -GLOBAL_LIST_EMPTY(body_markings_list) -GLOBAL_LIST_EMPTY(tails_list_lizard) -GLOBAL_LIST_EMPTY(animated_tails_list_lizard) -GLOBAL_LIST_EMPTY(snouts_list) -GLOBAL_LIST_EMPTY(horns_list) -GLOBAL_LIST_EMPTY(frills_list) -GLOBAL_LIST_EMPTY(spines_list) -GLOBAL_LIST_EMPTY(legs_list) -GLOBAL_LIST_EMPTY(animated_spines_list) - - //Mutant Human bits -GLOBAL_LIST_EMPTY(tails_list_human) -GLOBAL_LIST_EMPTY(animated_tails_list_human) -GLOBAL_LIST_EMPTY(ears_list) -GLOBAL_LIST_EMPTY(wings_list) -GLOBAL_LIST_EMPTY(wings_open_list) -GLOBAL_LIST_EMPTY(r_wings_list) -GLOBAL_LIST_EMPTY(moth_wings_list) -GLOBAL_LIST_EMPTY(moth_markings_list) -GLOBAL_LIST_EMPTY(caps_list) - -GLOBAL_LIST_INIT(color_list_ethereal, list( - "Red" = "ff4d4d", - "Faint Red" = "ffb3b3", - "Dark Red" = "9c3030", - "Orange" = "ffa64d", - "Burnt Orange" = "cc4400", - "Bright Yellow" = "ffff99", - "Dull Yellow" = "fbdf56", - "Faint Green" = "ddff99", - "Green" = "97ee63", - "Seafoam Green" = "00fa9a", - "Dark Green" = "37835b", - "Cyan Blue" = "00ffff", - "Faint Blue" = "b3d9ff", - "Blue" = "3399ff", - "Dark Blue" = "6666ff", - "Purple" = "ee82ee", - "Dark Fuschia" = "cc0066", - "Pink" = "ff99cc", - "White" = "f2f2f2",)) - -GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list( - "ghost", - "ghostian", - "ghostian2", - "ghostking", - "ghost_red", - "ghost_black", - "ghost_blue", - "ghost_yellow", - "ghost_green", - "ghost_pink", - "ghost_cyan", - "ghost_dblue", - "ghost_dred", - "ghost_dgreen", - "ghost_dcyan", - "ghost_grey", - "ghost_dyellow", - "ghost_dpink", - "skeleghost", - "ghost_purpleswirl", - "ghost_rainbow", - "ghost_fire", - "ghost_funkypurp", - "ghost_pinksherbert", - "ghost_blazeit", - "ghost_mellow", - "ghost_camo", - "catghost")) //stores the ghost forms that support directional sprites - -GLOBAL_LIST_INIT(ghost_forms_with_accessories_list, list("ghost")) //stores the ghost forms that support hair and other such things - -GLOBAL_LIST_INIT(ai_core_display_screens, sortList(list( - ":thinking:", - "Alien", - "Angel", - "Banned", - "Bliss", - "Blue", - "Clown", - "Database", - "Dorf", - "Firewall", - "Fuzzy", - "Gentoo", - "Glitchman", - "Gondola", - "Goon", - "Hades", - "HAL 9000", - "Heartline", - "Helios", - "House", - "Inverted", - "Matrix", - "Monochrome", - "Murica", - "Nanotrasen", - "Not Malf", - "President", - "Random", - "Rainbow", - "Red", - "Red October", - "Static", - "Syndicat Meow", - "Text", - "Too Deep", - "Triumvirate", - "Triumvirate-M", - "Weird"))) - -/proc/resolve_ai_icon(input) - if(!input || !(input in GLOB.ai_core_display_screens)) - return "ai" - else - if(input == "Random") - input = pick(GLOB.ai_core_display_screens - "Random") - return "ai-[lowertext(input)]" - -GLOBAL_LIST_INIT(security_depts_prefs, sortList(list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))) - - //Backpacks -#define GBACKPACK "Grey Backpack" -#define GSATCHEL "Grey Satchel" -#define GDUFFELBAG "Grey Duffel Bag" -#define LSATCHEL "Leather Satchel" -#define DBACKPACK "Department Backpack" -#define DSATCHEL "Department Satchel" -#define DDUFFELBAG "Department Duffel Bag" -GLOBAL_LIST_INIT(backpacklist, list(DBACKPACK, DSATCHEL, DDUFFELBAG, GBACKPACK, GSATCHEL, GDUFFELBAG, LSATCHEL)) - - //Suit/Skirt -#define PREF_SUIT "Jumpsuit" -#define PREF_SKIRT "Jumpskirt" -GLOBAL_LIST_INIT(jumpsuitlist, list(PREF_SUIT, PREF_SKIRT)) - -//Uplink spawn loc -#define UPLINK_PDA "PDA" -#define UPLINK_RADIO "Radio" -#define UPLINK_PEN "Pen" //like a real spy! -GLOBAL_LIST_INIT(uplink_spawn_loc_list, list(UPLINK_PDA, UPLINK_RADIO, UPLINK_PEN)) - - //Female Uniforms -GLOBAL_LIST_EMPTY(female_clothing_icons) - - //radical shit -GLOBAL_LIST_INIT(hit_appends, list("-OOF", "-ACK", "-UGH", "-HRNK", "-HURGH", "-GLORF")) - -GLOBAL_LIST_INIT(scarySounds, list('sound/weapons/thudswoosh.ogg','sound/weapons/taser.ogg','sound/weapons/armbomb.ogg','sound/voice/hiss1.ogg','sound/voice/hiss2.ogg','sound/voice/hiss3.ogg','sound/voice/hiss4.ogg','sound/voice/hiss5.ogg','sound/voice/hiss6.ogg','sound/effects/glassbr1.ogg','sound/effects/glassbr2.ogg','sound/effects/glassbr3.ogg','sound/items/welder.ogg','sound/items/welder2.ogg','sound/machines/airlock.ogg','sound/effects/clownstep1.ogg','sound/effects/clownstep2.ogg')) - - -// Reference list for disposal sort junctions. Set the sortType variable on disposal sort junctions to -// the index of the sort department that you want. For example, sortType set to 2 will reroute all packages -// tagged for the Cargo Bay. - -/* List of sortType codes for mapping reference -0 Waste -1 Disposals - All unwrapped items and untagged parcels get picked up by a junction with this sortType. Usually leads to the recycler. -2 Cargo Bay -3 QM Office -4 Engineering -5 CE Office -6 Atmospherics -7 Security -8 HoS Office -9 Medbay -10 CMO Office -11 Chemistry -12 Research -13 RD Office -14 Robotics -15 HoP Office -16 Library -17 Chapel -18 Theatre -19 Bar -20 Kitchen -21 Hydroponics -22 Janitor -23 Genetics -24 Experimentor Lab -25 Toxins -26 Dormitories -27 Virology -28 Xenobiology -29 Law Office -30 Detective's Office -*/ - -//The whole system for the sorttype var is determined based on the order of this list, -//disposals must always be 1, since anything that's untagged will automatically go to disposals, or sorttype = 1 --Superxpdude - -//If you don't want to fuck up disposals, add to this list, and don't change the order. -//If you insist on changing the order, you'll have to change every sort junction to reflect the new order. --Pete - -GLOBAL_LIST_INIT(TAGGERLOCATIONS, list("Disposals", - "Cargo Bay", "QM Office", "Engineering", "CE Office", - "Atmospherics", "Security", "HoS Office", "Medbay", - "CMO Office", "Chemistry", "Research", "RD Office", - "Robotics", "HoP Office", "Library", "Chapel", "Theatre", - "Bar", "Kitchen", "Hydroponics", "Janitor Closet","Genetics", - "Experimentor Lab", "Toxins", "Dormitories", "Virology", - "Xenobiology", "Law Office","Detective's Office")) - -GLOBAL_LIST_INIT(station_prefixes, world.file2list("strings/station_prefixes.txt")) - -GLOBAL_LIST_INIT(station_names, world.file2list("strings/station_names.txt")) - -GLOBAL_LIST_INIT(station_suffixes, world.file2list("strings/station_suffixes.txt")) - -GLOBAL_LIST_INIT(greek_letters, world.file2list("strings/greek_letters.txt")) - -GLOBAL_LIST_INIT(phonetic_alphabet, world.file2list("strings/phonetic_alphabet.txt")) - -GLOBAL_LIST_INIT(numbers_as_words, world.file2list("strings/numbers_as_words.txt")) - -GLOBAL_LIST_INIT(wisdoms, world.file2list("strings/wisdoms.txt")) - -/proc/generate_number_strings() - var/list/L[198] - for(var/i in 1 to 99) - L += "[i]" - L += "\Roman[i]" - return L - -GLOBAL_LIST_INIT(station_numerals, greek_letters + phonetic_alphabet + numbers_as_words + generate_number_strings()) - -GLOBAL_LIST_INIT(admiral_messages, list("Do you know how expensive these stations are?","Stop wasting my time.","I was sleeping, thanks a lot.","Stand and fight you cowards!","You knew the risks coming in.","Stop being paranoid.","Whatever's broken just build a new one.","No.", "null","Error: No comment given.", "It's a good day to die!")) +//Preferences stuff + //Hairstyles +GLOBAL_LIST_EMPTY(hairstyles_list) //stores /datum/sprite_accessory/hair indexed by name +GLOBAL_LIST_EMPTY(hairstyles_male_list) //stores only hair names +GLOBAL_LIST_EMPTY(hairstyles_female_list) //stores only hair names +GLOBAL_LIST_EMPTY(facial_hairstyles_list) //stores /datum/sprite_accessory/facial_hair indexed by name +GLOBAL_LIST_EMPTY(facial_hairstyles_male_list) //stores only hair names +GLOBAL_LIST_EMPTY(facial_hairstyles_female_list) //stores only hair names + //Underwear +GLOBAL_LIST_EMPTY(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name +GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name +GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name + //Undershirts +GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name +GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name +GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name + //Socks +GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name + //Lizard Bits (all datum lists indexed by name) +GLOBAL_LIST_EMPTY(body_markings_list) +GLOBAL_LIST_EMPTY(tails_list_lizard) +GLOBAL_LIST_EMPTY(animated_tails_list_lizard) +GLOBAL_LIST_EMPTY(snouts_list) +GLOBAL_LIST_EMPTY(horns_list) +GLOBAL_LIST_EMPTY(frills_list) +GLOBAL_LIST_EMPTY(spines_list) +GLOBAL_LIST_EMPTY(legs_list) +GLOBAL_LIST_EMPTY(animated_spines_list) + + //Mutant Human bits +GLOBAL_LIST_EMPTY(tails_list_human) +GLOBAL_LIST_EMPTY(animated_tails_list_human) +GLOBAL_LIST_EMPTY(ears_list) +GLOBAL_LIST_EMPTY(wings_list) +GLOBAL_LIST_EMPTY(wings_open_list) +GLOBAL_LIST_EMPTY(r_wings_list) +GLOBAL_LIST_EMPTY(moth_wings_list) +GLOBAL_LIST_EMPTY(moth_markings_list) +GLOBAL_LIST_EMPTY(caps_list) + +GLOBAL_LIST_INIT(color_list_ethereal, list( + "Red" = "ff4d4d", + "Faint Red" = "ffb3b3", + "Dark Red" = "9c3030", + "Orange" = "ffa64d", + "Burnt Orange" = "cc4400", + "Bright Yellow" = "ffff99", + "Dull Yellow" = "fbdf56", + "Faint Green" = "ddff99", + "Green" = "97ee63", + "Seafoam Green" = "00fa9a", + "Dark Green" = "37835b", + "Cyan Blue" = "00ffff", + "Faint Blue" = "b3d9ff", + "Blue" = "3399ff", + "Dark Blue" = "6666ff", + "Purple" = "ee82ee", + "Dark Fuschia" = "cc0066", + "Pink" = "ff99cc", + "White" = "f2f2f2",)) + +GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list( + "ghost", + "ghostian", + "ghostian2", + "ghostking", + "ghost_red", + "ghost_black", + "ghost_blue", + "ghost_yellow", + "ghost_green", + "ghost_pink", + "ghost_cyan", + "ghost_dblue", + "ghost_dred", + "ghost_dgreen", + "ghost_dcyan", + "ghost_grey", + "ghost_dyellow", + "ghost_dpink", + "skeleghost", + "ghost_purpleswirl", + "ghost_rainbow", + "ghost_fire", + "ghost_funkypurp", + "ghost_pinksherbert", + "ghost_blazeit", + "ghost_mellow", + "ghost_camo", + "catghost")) //stores the ghost forms that support directional sprites + +GLOBAL_LIST_INIT(ghost_forms_with_accessories_list, list("ghost")) //stores the ghost forms that support hair and other such things + +GLOBAL_LIST_INIT(ai_core_display_screens, sortList(list( + ":thinking:", + "Alien", + "Angel", + "Banned", + "Bliss", + "Blue", + "Clown", + "Database", + "Dorf", + "Firewall", + "Fuzzy", + "Gentoo", + "Glitchman", + "Gondola", + "Goon", + "Hades", + "HAL 9000", + "Heartline", + "Helios", + "House", + "Inverted", + "Matrix", + "Monochrome", + "Murica", + "Nanotrasen", + "Not Malf", + "President", + "Random", + "Rainbow", + "Red", + "Red October", + "Static", + "Syndicat Meow", + "Text", + "Too Deep", + "Triumvirate", + "Triumvirate-M", + "Weird"))) + +/proc/resolve_ai_icon(input) + if(!input || !(input in GLOB.ai_core_display_screens)) + return "ai" + else + if(input == "Random") + input = pick(GLOB.ai_core_display_screens - "Random") + return "ai-[lowertext(input)]" + +GLOBAL_LIST_INIT(security_depts_prefs, sortList(list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))) + + //Backpacks +#define GBACKPACK "Grey Backpack" +#define GSATCHEL "Grey Satchel" +#define GDUFFELBAG "Grey Duffel Bag" +#define LSATCHEL "Leather Satchel" +#define DBACKPACK "Department Backpack" +#define DSATCHEL "Department Satchel" +#define DDUFFELBAG "Department Duffel Bag" +GLOBAL_LIST_INIT(backpacklist, list(DBACKPACK, DSATCHEL, DDUFFELBAG, GBACKPACK, GSATCHEL, GDUFFELBAG, LSATCHEL)) + + //Suit/Skirt +#define PREF_SUIT "Jumpsuit" +#define PREF_SKIRT "Jumpskirt" +GLOBAL_LIST_INIT(jumpsuitlist, list(PREF_SUIT, PREF_SKIRT)) + +//Uplink spawn loc +#define UPLINK_PDA "PDA" +#define UPLINK_RADIO "Radio" +#define UPLINK_PEN "Pen" //like a real spy! +GLOBAL_LIST_INIT(uplink_spawn_loc_list, list(UPLINK_PDA, UPLINK_RADIO, UPLINK_PEN)) + + //Female Uniforms +GLOBAL_LIST_EMPTY(female_clothing_icons) + + //radical shit +GLOBAL_LIST_INIT(hit_appends, list("-OOF", "-ACK", "-UGH", "-HRNK", "-HURGH", "-GLORF")) + +GLOBAL_LIST_INIT(scarySounds, list('sound/weapons/thudswoosh.ogg','sound/weapons/taser.ogg','sound/weapons/armbomb.ogg','sound/voice/hiss1.ogg','sound/voice/hiss2.ogg','sound/voice/hiss3.ogg','sound/voice/hiss4.ogg','sound/voice/hiss5.ogg','sound/voice/hiss6.ogg','sound/effects/glassbr1.ogg','sound/effects/glassbr2.ogg','sound/effects/glassbr3.ogg','sound/items/welder.ogg','sound/items/welder2.ogg','sound/machines/airlock.ogg','sound/effects/clownstep1.ogg','sound/effects/clownstep2.ogg')) + + +// Reference list for disposal sort junctions. Set the sortType variable on disposal sort junctions to +// the index of the sort department that you want. For example, sortType set to 2 will reroute all packages +// tagged for the Cargo Bay. + +/* List of sortType codes for mapping reference +0 Waste +1 Disposals - All unwrapped items and untagged parcels get picked up by a junction with this sortType. Usually leads to the recycler. +2 Cargo Bay +3 QM Office +4 Engineering +5 CE Office +6 Atmospherics +7 Security +8 HoS Office +9 Medbay +10 CMO Office +11 Chemistry +12 Research +13 RD Office +14 Robotics +15 HoP Office +16 Library +17 Chapel +18 Theatre +19 Bar +20 Kitchen +21 Hydroponics +22 Janitor +23 Genetics +24 Experimentor Lab +25 Toxins +26 Dormitories +27 Virology +28 Xenobiology +29 Law Office +30 Detective's Office +*/ + +//The whole system for the sorttype var is determined based on the order of this list, +//disposals must always be 1, since anything that's untagged will automatically go to disposals, or sorttype = 1 --Superxpdude + +//If you don't want to fuck up disposals, add to this list, and don't change the order. +//If you insist on changing the order, you'll have to change every sort junction to reflect the new order. --Pete + +GLOBAL_LIST_INIT(TAGGERLOCATIONS, list("Disposals", + "Cargo Bay", "QM Office", "Engineering", "CE Office", + "Atmospherics", "Security", "HoS Office", "Medbay", + "CMO Office", "Chemistry", "Research", "RD Office", + "Robotics", "HoP Office", "Library", "Chapel", "Theatre", + "Bar", "Kitchen", "Hydroponics", "Janitor Closet","Genetics", + "Experimentor Lab", "Toxins", "Dormitories", "Virology", + "Xenobiology", "Law Office","Detective's Office")) + +GLOBAL_LIST_INIT(station_prefixes, world.file2list("strings/station_prefixes.txt")) + +GLOBAL_LIST_INIT(station_names, world.file2list("strings/station_names.txt")) + +GLOBAL_LIST_INIT(station_suffixes, world.file2list("strings/station_suffixes.txt")) + +GLOBAL_LIST_INIT(greek_letters, world.file2list("strings/greek_letters.txt")) + +GLOBAL_LIST_INIT(phonetic_alphabet, world.file2list("strings/phonetic_alphabet.txt")) + +GLOBAL_LIST_INIT(numbers_as_words, world.file2list("strings/numbers_as_words.txt")) + +GLOBAL_LIST_INIT(wisdoms, world.file2list("strings/wisdoms.txt")) + +/proc/generate_number_strings() + var/list/L[198] + for(var/i in 1 to 99) + L += "[i]" + L += "\Roman[i]" + return L + +GLOBAL_LIST_INIT(station_numerals, greek_letters + phonetic_alphabet + numbers_as_words + generate_number_strings()) + +GLOBAL_LIST_INIT(admiral_messages, list("Do you know how expensive these stations are?","Stop wasting my time.","I was sleeping, thanks a lot.","Stand and fight you cowards!","You knew the risks coming in.","Stop being paranoid.","Whatever's broken just build a new one.","No.", "null","Error: No comment given.", "It's a good day to die!")) diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm index fdcfc6506f4..87881693c17 100644 --- a/code/_globalvars/lists/mapping.dm +++ b/code/_globalvars/lists/mapping.dm @@ -1,47 +1,47 @@ -GLOBAL_LIST_INIT(cardinals, list(NORTH, SOUTH, EAST, WEST)) -GLOBAL_LIST_INIT(cardinals_multiz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN)) -GLOBAL_LIST_INIT(diagonals, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) -GLOBAL_LIST_INIT(corners_multiz, list(UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) -GLOBAL_LIST_INIT(diagonals_multiz, list( - NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, - UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, - DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) -GLOBAL_LIST_INIT(alldirs_multiz, list( - NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, - UP, UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, - DOWN, DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) -GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) - -GLOBAL_LIST_EMPTY(landmarks_list) //list of all landmarks created -GLOBAL_LIST_EMPTY(start_landmarks_list) //list of all spawn points created -GLOBAL_LIST_EMPTY(department_security_spawns) //list of all department security spawns -GLOBAL_LIST_EMPTY(generic_event_spawns) //handles clockwork portal+eminence teleport destinations -GLOBAL_LIST_EMPTY(jobspawn_overrides) //These will take precedence over normal spawnpoints if created. - -GLOBAL_LIST_EMPTY(wizardstart) -GLOBAL_LIST_EMPTY(nukeop_start) -GLOBAL_LIST_EMPTY(nukeop_leader_start) -GLOBAL_LIST_EMPTY(newplayer_start) -GLOBAL_LIST_EMPTY(prisonwarp) //admin prisoners go to these -GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here (ninja energy net) -GLOBAL_LIST_EMPTY(xeno_spawn)//aliens, morphs and nightmares spawn at these -GLOBAL_LIST_EMPTY(tdome1) -GLOBAL_LIST_EMPTY(tdome2) -GLOBAL_LIST_EMPTY(tdomeobserve) -GLOBAL_LIST_EMPTY(tdomeadmin) -GLOBAL_LIST_EMPTY(prisonwarped) //list of players already warped -GLOBAL_LIST_EMPTY(blobstart) //stationloving objects, blobs, santa, respawning devils -GLOBAL_LIST_EMPTY(secequipment) //sec equipment lockers that scale with the number of sec players -GLOBAL_LIST_EMPTY(deathsquadspawn) -GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) -GLOBAL_LIST_EMPTY(ruin_landmarks) - -//away missions -GLOBAL_LIST_EMPTY(vr_spawnpoints) - - //used by jump-to-area etc. Updated by area/updateName() -GLOBAL_LIST_EMPTY(sortedAreas) -/// An association from typepath to area instance. Only includes areas with `unique` set. -GLOBAL_LIST_EMPTY_TYPED(areas_by_type, /area) - -GLOBAL_LIST_EMPTY(all_abstract_markers) +GLOBAL_LIST_INIT(cardinals, list(NORTH, SOUTH, EAST, WEST)) +GLOBAL_LIST_INIT(cardinals_multiz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN)) +GLOBAL_LIST_INIT(diagonals, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) +GLOBAL_LIST_INIT(corners_multiz, list(UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(diagonals_multiz, list( + NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, + UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, + DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(alldirs_multiz, list( + NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, + UP, UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, + DOWN, DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) + +GLOBAL_LIST_EMPTY(landmarks_list) //list of all landmarks created +GLOBAL_LIST_EMPTY(start_landmarks_list) //list of all spawn points created +GLOBAL_LIST_EMPTY(department_security_spawns) //list of all department security spawns +GLOBAL_LIST_EMPTY(generic_event_spawns) //handles clockwork portal+eminence teleport destinations +GLOBAL_LIST_EMPTY(jobspawn_overrides) //These will take precedence over normal spawnpoints if created. + +GLOBAL_LIST_EMPTY(wizardstart) +GLOBAL_LIST_EMPTY(nukeop_start) +GLOBAL_LIST_EMPTY(nukeop_leader_start) +GLOBAL_LIST_EMPTY(newplayer_start) +GLOBAL_LIST_EMPTY(prisonwarp) //admin prisoners go to these +GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here (ninja energy net) +GLOBAL_LIST_EMPTY(xeno_spawn)//aliens, morphs and nightmares spawn at these +GLOBAL_LIST_EMPTY(tdome1) +GLOBAL_LIST_EMPTY(tdome2) +GLOBAL_LIST_EMPTY(tdomeobserve) +GLOBAL_LIST_EMPTY(tdomeadmin) +GLOBAL_LIST_EMPTY(prisonwarped) //list of players already warped +GLOBAL_LIST_EMPTY(blobstart) //stationloving objects, blobs, santa, respawning devils +GLOBAL_LIST_EMPTY(secequipment) //sec equipment lockers that scale with the number of sec players +GLOBAL_LIST_EMPTY(deathsquadspawn) +GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) +GLOBAL_LIST_EMPTY(ruin_landmarks) + +//away missions +GLOBAL_LIST_EMPTY(vr_spawnpoints) + + //used by jump-to-area etc. Updated by area/updateName() +GLOBAL_LIST_EMPTY(sortedAreas) +/// An association from typepath to area instance. Only includes areas with `unique` set. +GLOBAL_LIST_EMPTY_TYPED(areas_by_type, /area) + +GLOBAL_LIST_EMPTY(all_abstract_markers) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index f3c472848df..bb5118b60bf 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -1,83 +1,83 @@ -GLOBAL_LIST_EMPTY(clients) //all clients -GLOBAL_LIST_EMPTY(admins) //all clients whom are admins -GLOBAL_PROTECT(admins) -GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb. - -GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client -GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins - -//Since it didn't really belong in any other category, I'm putting this here -//This is for procs to replace all the goddamn 'in world's that are chilling around the code - -GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. -GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless -GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob -GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player -GLOBAL_LIST_EMPTY(suicided_mob_list) //contains a list of all mobs that suicided, including their associated ghosts. -GLOBAL_LIST_EMPTY(drones_list) -GLOBAL_LIST_EMPTY(dead_mob_list) //all dead mobs, including clientless. Excludes /mob/dead/new_player -GLOBAL_LIST_EMPTY(joined_player_list) //all clients that have joined the game at round-start or as a latejoin. -GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done. -GLOBAL_LIST_EMPTY(pre_setup_antags) //minds that have been picked as antag by the gamemode. removed as antag datums are set. -GLOBAL_LIST_EMPTY(silicon_mobs) //all silicon mobs -GLOBAL_LIST_EMPTY(mob_living_list) //all instances of /mob/living and subtypes -GLOBAL_LIST_EMPTY(carbon_list) //all instances of /mob/living/carbon and subtypes, notably does not contain brains or simple animals -GLOBAL_LIST_EMPTY(human_list) //all instances of /mob/living/carbon/human and subtypes -GLOBAL_LIST_EMPTY(ai_list) -GLOBAL_LIST_EMPTY(pai_list) -GLOBAL_LIST_EMPTY(available_ai_shells) -GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for each AI_* status define -GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs -GLOBAL_LIST_EMPTY(bots_list) -GLOBAL_LIST_EMPTY(aiEyes) -GLOBAL_LIST_EMPTY(suit_sensors_list) //all people with suit sensors on -GLOBAL_LIST_EMPTY(nanite_sensors_list) //app people with nanite monitoring program -///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam -GLOBAL_LIST_EMPTY(narcd_underages) - -GLOBAL_LIST_EMPTY(language_datum_instances) -GLOBAL_LIST_EMPTY(all_languages) - -GLOBAL_LIST_EMPTY(sentient_disease_instances) - -GLOBAL_LIST_EMPTY(latejoin_ai_cores) - -GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup) - -GLOBAL_LIST_EMPTY(emote_list) - -/proc/update_config_movespeed_type_lookup(update_mobs = TRUE) - var/list/mob_types = list() - var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed) - for(var/path in entry_value) - var/value = entry_value[path] - if(!value) - continue - for(var/subpath in typesof(path)) - mob_types[subpath] = value - GLOB.mob_config_movespeed_type_lookup = mob_types - if(update_mobs) - update_mob_config_movespeeds() - -/proc/update_mob_config_movespeeds() - for(var/i in GLOB.mob_list) - var/mob/M = i - M.update_config_movespeed() - -/proc/init_emote_list() - . = list() - for(var/path in subtypesof(/datum/emote)) - var/datum/emote/E = new path() - if(E.key) - if(!.[E.key]) - .[E.key] = list(E) - else - .[E.key] += E - else if(E.message) //Assuming all non-base emotes have this - stack_trace("Keyless emote: [E.type]") - - if(E.key_third_person) //This one is optional - if(!.[E.key_third_person]) - .[E.key_third_person] = list(E) - else - .[E.key_third_person] |= E +GLOBAL_LIST_EMPTY(clients) //all clients +GLOBAL_LIST_EMPTY(admins) //all clients whom are admins +GLOBAL_PROTECT(admins) +GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb. + +GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client +GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins + +//Since it didn't really belong in any other category, I'm putting this here +//This is for procs to replace all the goddamn 'in world's that are chilling around the code + +GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. +GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless +GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob +GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player +GLOBAL_LIST_EMPTY(suicided_mob_list) //contains a list of all mobs that suicided, including their associated ghosts. +GLOBAL_LIST_EMPTY(drones_list) +GLOBAL_LIST_EMPTY(dead_mob_list) //all dead mobs, including clientless. Excludes /mob/dead/new_player +GLOBAL_LIST_EMPTY(joined_player_list) //all clients that have joined the game at round-start or as a latejoin. +GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done. +GLOBAL_LIST_EMPTY(pre_setup_antags) //minds that have been picked as antag by the gamemode. removed as antag datums are set. +GLOBAL_LIST_EMPTY(silicon_mobs) //all silicon mobs +GLOBAL_LIST_EMPTY(mob_living_list) //all instances of /mob/living and subtypes +GLOBAL_LIST_EMPTY(carbon_list) //all instances of /mob/living/carbon and subtypes, notably does not contain brains or simple animals +GLOBAL_LIST_EMPTY(human_list) //all instances of /mob/living/carbon/human and subtypes +GLOBAL_LIST_EMPTY(ai_list) +GLOBAL_LIST_EMPTY(pai_list) +GLOBAL_LIST_EMPTY(available_ai_shells) +GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for each AI_* status define +GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs +GLOBAL_LIST_EMPTY(bots_list) +GLOBAL_LIST_EMPTY(aiEyes) +GLOBAL_LIST_EMPTY(suit_sensors_list) //all people with suit sensors on +GLOBAL_LIST_EMPTY(nanite_sensors_list) //app people with nanite monitoring program +///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam +GLOBAL_LIST_EMPTY(narcd_underages) + +GLOBAL_LIST_EMPTY(language_datum_instances) +GLOBAL_LIST_EMPTY(all_languages) + +GLOBAL_LIST_EMPTY(sentient_disease_instances) + +GLOBAL_LIST_EMPTY(latejoin_ai_cores) + +GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup) + +GLOBAL_LIST_EMPTY(emote_list) + +/proc/update_config_movespeed_type_lookup(update_mobs = TRUE) + var/list/mob_types = list() + var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed) + for(var/path in entry_value) + var/value = entry_value[path] + if(!value) + continue + for(var/subpath in typesof(path)) + mob_types[subpath] = value + GLOB.mob_config_movespeed_type_lookup = mob_types + if(update_mobs) + update_mob_config_movespeeds() + +/proc/update_mob_config_movespeeds() + for(var/i in GLOB.mob_list) + var/mob/M = i + M.update_config_movespeed() + +/proc/init_emote_list() + . = list() + for(var/path in subtypesof(/datum/emote)) + var/datum/emote/E = new path() + if(E.key) + if(!.[E.key]) + .[E.key] = list(E) + else + .[E.key] += E + else if(E.message) //Assuming all non-base emotes have this + stack_trace("Keyless emote: [E.type]") + + if(E.key_third_person) //This one is optional + if(!.[E.key_third_person]) + .[E.key_third_person] = list(E) + else + .[E.key_third_person] |= E diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index 98ec6b6c7cb..b80fcf0bbf5 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -1,52 +1,52 @@ -GLOBAL_LIST_INIT(ai_names, world.file2list("strings/names/ai.txt")) -GLOBAL_LIST_INIT(wizard_first, world.file2list("strings/names/wizardfirst.txt")) -GLOBAL_LIST_INIT(wizard_second, world.file2list("strings/names/wizardsecond.txt")) -GLOBAL_LIST_INIT(ninja_titles, world.file2list("strings/names/ninjatitle.txt")) -GLOBAL_LIST_INIT(ninja_names, world.file2list("strings/names/ninjaname.txt")) -GLOBAL_LIST_INIT(commando_names, world.file2list("strings/names/death_commando.txt")) -GLOBAL_LIST_INIT(first_names, world.file2list("strings/names/first.txt")) -GLOBAL_LIST_INIT(first_names_male, world.file2list("strings/names/first_male.txt")) -GLOBAL_LIST_INIT(first_names_female, world.file2list("strings/names/first_female.txt")) -GLOBAL_LIST_INIT(last_names, world.file2list("strings/names/last.txt")) -GLOBAL_LIST_INIT(lizard_names_male, world.file2list("strings/names/lizard_male.txt")) -GLOBAL_LIST_INIT(lizard_names_female, world.file2list("strings/names/lizard_female.txt")) -GLOBAL_LIST_INIT(clown_names, world.file2list("strings/names/clown.txt")) -GLOBAL_LIST_INIT(mime_names, world.file2list("strings/names/mime.txt")) -GLOBAL_LIST_INIT(carp_names, world.file2list("strings/names/carp.txt")) -GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt")) -GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt")) -GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt")) -GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt")) -GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt")) -GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt")) -GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt")) -GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt")) -GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) - -GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) -GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) -GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt")) -GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt")) -GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt")) -//loaded on startup because of " -//would include in rsc if ' was used - -/* -List of configurable names in preferences and their metadata -"id" = list( - "pref_name" = "name", //pref label - "qdesc" = "name", //popup question text - "allow_numbers" = FALSE, // numbers allowed in the name - "group" = "whatever", // group (these will be grouped together on pref ui ,order still follows the list so they need to be concurrent to be grouped) - "allow_null" = FALSE // if empty name is entered it's replaced with default value - ), -*/ -GLOBAL_LIST_INIT(preferences_custom_names, list( - "human" = list("pref_name" = "Backup Human", "qdesc" = "backup human name, used in the event you are assigned a command role as another species", "allow_numbers" = FALSE , "group" = "backup_human", "allow_null" = FALSE), - "clown" = list("pref_name" = "Clown" , "qdesc" = "clown name", "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), - "mime" = list("pref_name" = "Mime", "qdesc" = "mime name" , "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), - "cyborg" = list("pref_name" = "Cyborg", "qdesc" = "cyborg name (Leave empty to use default naming scheme)", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = TRUE), - "ai" = list("pref_name" = "AI", "qdesc" = "ai name", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = FALSE), - "religion" = list("pref_name" = "Chaplain religion", "qdesc" = "religion" , "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE), - "deity" = list("pref_name" = "Chaplain deity", "qdesc" = "deity", "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE) - )) +GLOBAL_LIST_INIT(ai_names, world.file2list("strings/names/ai.txt")) +GLOBAL_LIST_INIT(wizard_first, world.file2list("strings/names/wizardfirst.txt")) +GLOBAL_LIST_INIT(wizard_second, world.file2list("strings/names/wizardsecond.txt")) +GLOBAL_LIST_INIT(ninja_titles, world.file2list("strings/names/ninjatitle.txt")) +GLOBAL_LIST_INIT(ninja_names, world.file2list("strings/names/ninjaname.txt")) +GLOBAL_LIST_INIT(commando_names, world.file2list("strings/names/death_commando.txt")) +GLOBAL_LIST_INIT(first_names, world.file2list("strings/names/first.txt")) +GLOBAL_LIST_INIT(first_names_male, world.file2list("strings/names/first_male.txt")) +GLOBAL_LIST_INIT(first_names_female, world.file2list("strings/names/first_female.txt")) +GLOBAL_LIST_INIT(last_names, world.file2list("strings/names/last.txt")) +GLOBAL_LIST_INIT(lizard_names_male, world.file2list("strings/names/lizard_male.txt")) +GLOBAL_LIST_INIT(lizard_names_female, world.file2list("strings/names/lizard_female.txt")) +GLOBAL_LIST_INIT(clown_names, world.file2list("strings/names/clown.txt")) +GLOBAL_LIST_INIT(mime_names, world.file2list("strings/names/mime.txt")) +GLOBAL_LIST_INIT(carp_names, world.file2list("strings/names/carp.txt")) +GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt")) +GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt")) +GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt")) +GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt")) +GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt")) +GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt")) +GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt")) +GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt")) +GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) + +GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) +GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) +GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt")) +GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt")) +GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt")) +//loaded on startup because of " +//would include in rsc if ' was used + +/* +List of configurable names in preferences and their metadata +"id" = list( + "pref_name" = "name", //pref label + "qdesc" = "name", //popup question text + "allow_numbers" = FALSE, // numbers allowed in the name + "group" = "whatever", // group (these will be grouped together on pref ui ,order still follows the list so they need to be concurrent to be grouped) + "allow_null" = FALSE // if empty name is entered it's replaced with default value + ), +*/ +GLOBAL_LIST_INIT(preferences_custom_names, list( + "human" = list("pref_name" = "Backup Human", "qdesc" = "backup human name, used in the event you are assigned a command role as another species", "allow_numbers" = FALSE , "group" = "backup_human", "allow_null" = FALSE), + "clown" = list("pref_name" = "Clown" , "qdesc" = "clown name", "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), + "mime" = list("pref_name" = "Mime", "qdesc" = "mime name" , "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), + "cyborg" = list("pref_name" = "Cyborg", "qdesc" = "cyborg name (Leave empty to use default naming scheme)", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = TRUE), + "ai" = list("pref_name" = "AI", "qdesc" = "ai name", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = FALSE), + "religion" = list("pref_name" = "Chaplain religion", "qdesc" = "religion" , "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE), + "deity" = list("pref_name" = "Chaplain deity", "qdesc" = "deity", "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE) + )) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 62153e3f533..7fb4e3d7728 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -1,43 +1,43 @@ -GLOBAL_LIST_EMPTY(cable_list) //Index for all cables, so that powernets don't have to look through the entire world all the time -GLOBAL_LIST_EMPTY(portals) //list of all /obj/effect/portal -GLOBAL_LIST_EMPTY(airlocks) //list of all airlocks -GLOBAL_LIST_EMPTY(mechas_list) //list of all mechs. Used by hostile mobs target tracking. -GLOBAL_LIST_EMPTY(shuttle_caller_list) //list of all communication consoles and AIs, for automatic shuttle calls when there are none. -GLOBAL_LIST_EMPTY(machines) //NOTE: this is a list of ALL machines now. The processing machines list is SSmachine.processing ! -GLOBAL_LIST_EMPTY(navigation_computers) //list of all /obj/machinery/computer/camera_advanced/shuttle_docker -GLOBAL_LIST_EMPTY(syndicate_shuttle_boards) //important to keep track of for managing nukeops war declarations. -GLOBAL_LIST_EMPTY(navbeacons) //list of all bot nagivation beacons, used for patrolling. -GLOBAL_LIST_EMPTY(teleportbeacons) //list of all tracking beacons used by teleporters -GLOBAL_LIST_EMPTY(deliverybeacons) //list of all MULEbot delivery beacons. -GLOBAL_LIST_EMPTY(deliverybeacontags) //list of all tags associated with delivery beacons. -GLOBAL_LIST_EMPTY(wayfindingbeacons) //list of all navigation beacons used by wayfinding pinpointers -GLOBAL_LIST_EMPTY(nuke_list) -GLOBAL_LIST_EMPTY(alarmdisplay) //list of all machines or programs that can display station alerts -GLOBAL_LIST_EMPTY(singularities) //list of all singularities on the station (actually technically all engines) - -GLOBAL_LIST(chemical_reactions_list) //list of all /datum/chemical_reaction datums. Used during chemical reactions -GLOBAL_LIST(chemical_reagents_list) //list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff -GLOBAL_LIST_EMPTY(tech_list) //list of all /datum/tech datums indexed by id. -GLOBAL_LIST_EMPTY(surgeries_list) //list of all surgeries by name, associated with their path. -GLOBAL_LIST_EMPTY(crafting_recipes) //list of all table craft recipes -GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices. -GLOBAL_LIST_EMPTY(apcs_list) //list of all Area Power Controller machines, separate from machines for powernet speeeeeeed. -GLOBAL_LIST_EMPTY(tracked_implants) //list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented... -GLOBAL_LIST_EMPTY(tracked_chem_implants) //list of implants the prisoner console can track and send inject commands too -GLOBAL_LIST_EMPTY(poi_list) //list of points of interest for observe/follow -GLOBAL_LIST_EMPTY(pinpointer_list) //list of all pinpointers. Used to change stuff they are pointing to all at once. -GLOBAL_LIST_EMPTY(zombie_infection_list) // A list of all zombie_infection organs, for any mass "animation" -GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors. -GLOBAL_LIST_EMPTY(active_jammers) // List of active radio jammers -GLOBAL_LIST_EMPTY(ladders) -GLOBAL_LIST_EMPTY(trophy_cases) -///This is a global list of all signs you can change an existing sign or new sign backing to, when using a pen on them. -GLOBAL_LIST_EMPTY(editable_sign_types) - -GLOBAL_LIST_EMPTY(wire_color_directory) -GLOBAL_LIST_EMPTY(wire_name_directory) - -GLOBAL_LIST_EMPTY(ai_status_displays) - -GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects -GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert +GLOBAL_LIST_EMPTY(cable_list) //Index for all cables, so that powernets don't have to look through the entire world all the time +GLOBAL_LIST_EMPTY(portals) //list of all /obj/effect/portal +GLOBAL_LIST_EMPTY(airlocks) //list of all airlocks +GLOBAL_LIST_EMPTY(mechas_list) //list of all mechs. Used by hostile mobs target tracking. +GLOBAL_LIST_EMPTY(shuttle_caller_list) //list of all communication consoles and AIs, for automatic shuttle calls when there are none. +GLOBAL_LIST_EMPTY(machines) //NOTE: this is a list of ALL machines now. The processing machines list is SSmachine.processing ! +GLOBAL_LIST_EMPTY(navigation_computers) //list of all /obj/machinery/computer/camera_advanced/shuttle_docker +GLOBAL_LIST_EMPTY(syndicate_shuttle_boards) //important to keep track of for managing nukeops war declarations. +GLOBAL_LIST_EMPTY(navbeacons) //list of all bot nagivation beacons, used for patrolling. +GLOBAL_LIST_EMPTY(teleportbeacons) //list of all tracking beacons used by teleporters +GLOBAL_LIST_EMPTY(deliverybeacons) //list of all MULEbot delivery beacons. +GLOBAL_LIST_EMPTY(deliverybeacontags) //list of all tags associated with delivery beacons. +GLOBAL_LIST_EMPTY(wayfindingbeacons) //list of all navigation beacons used by wayfinding pinpointers +GLOBAL_LIST_EMPTY(nuke_list) +GLOBAL_LIST_EMPTY(alarmdisplay) //list of all machines or programs that can display station alerts +GLOBAL_LIST_EMPTY(singularities) //list of all singularities on the station (actually technically all engines) + +GLOBAL_LIST(chemical_reactions_list) //list of all /datum/chemical_reaction datums. Used during chemical reactions +GLOBAL_LIST(chemical_reagents_list) //list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff +GLOBAL_LIST_EMPTY(tech_list) //list of all /datum/tech datums indexed by id. +GLOBAL_LIST_EMPTY(surgeries_list) //list of all surgeries by name, associated with their path. +GLOBAL_LIST_EMPTY(crafting_recipes) //list of all table craft recipes +GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices. +GLOBAL_LIST_EMPTY(apcs_list) //list of all Area Power Controller machines, separate from machines for powernet speeeeeeed. +GLOBAL_LIST_EMPTY(tracked_implants) //list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented... +GLOBAL_LIST_EMPTY(tracked_chem_implants) //list of implants the prisoner console can track and send inject commands too +GLOBAL_LIST_EMPTY(poi_list) //list of points of interest for observe/follow +GLOBAL_LIST_EMPTY(pinpointer_list) //list of all pinpointers. Used to change stuff they are pointing to all at once. +GLOBAL_LIST_EMPTY(zombie_infection_list) // A list of all zombie_infection organs, for any mass "animation" +GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors. +GLOBAL_LIST_EMPTY(active_jammers) // List of active radio jammers +GLOBAL_LIST_EMPTY(ladders) +GLOBAL_LIST_EMPTY(trophy_cases) +///This is a global list of all signs you can change an existing sign or new sign backing to, when using a pen on them. +GLOBAL_LIST_EMPTY(editable_sign_types) + +GLOBAL_LIST_EMPTY(wire_color_directory) +GLOBAL_LIST_EMPTY(wire_name_directory) + +GLOBAL_LIST_EMPTY(ai_status_displays) + +GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects +GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index b9d3d811207..df0a49a3cb7 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -1,81 +1,81 @@ -GLOBAL_VAR(log_directory) -GLOBAL_PROTECT(log_directory) -GLOBAL_VAR(world_game_log) -GLOBAL_PROTECT(world_game_log) -GLOBAL_VAR(world_runtime_log) -GLOBAL_PROTECT(world_runtime_log) -GLOBAL_VAR(world_qdel_log) -GLOBAL_PROTECT(world_qdel_log) -GLOBAL_VAR(world_attack_log) -GLOBAL_PROTECT(world_attack_log) -GLOBAL_VAR(world_econ_log) -GLOBAL_PROTECT(world_econ_log) -GLOBAL_VAR(world_href_log) -GLOBAL_PROTECT(world_href_log) -GLOBAL_VAR(round_id) -GLOBAL_PROTECT(round_id) -GLOBAL_VAR(config_error_log) -GLOBAL_PROTECT(config_error_log) -GLOBAL_VAR(sql_error_log) -GLOBAL_PROTECT(sql_error_log) -GLOBAL_VAR(world_pda_log) -GLOBAL_PROTECT(world_pda_log) -GLOBAL_VAR(world_telecomms_log) -GLOBAL_PROTECT(world_telecomms_log) -GLOBAL_VAR(world_manifest_log) -GLOBAL_PROTECT(world_manifest_log) -GLOBAL_VAR(query_debug_log) -GLOBAL_PROTECT(query_debug_log) -GLOBAL_VAR(world_job_debug_log) -GLOBAL_PROTECT(world_job_debug_log) -GLOBAL_VAR(world_mecha_log) -GLOBAL_PROTECT(world_mecha_log) -GLOBAL_VAR(world_virus_log) -GLOBAL_PROTECT(world_virus_log) -GLOBAL_VAR(world_asset_log) -GLOBAL_PROTECT(world_asset_log) -GLOBAL_VAR(world_cloning_log) -GLOBAL_PROTECT(world_cloning_log) -GLOBAL_VAR(world_map_error_log) -GLOBAL_PROTECT(world_map_error_log) -GLOBAL_VAR(world_paper_log) -GLOBAL_PROTECT(world_paper_log) -GLOBAL_VAR(tgui_log) -GLOBAL_PROTECT(tgui_log) -GLOBAL_VAR(world_shuttle_log) -GLOBAL_PROTECT(world_shuttle_log) -GLOBAL_VAR(discord_api_log) -GLOBAL_PROTECT(discord_api_log) - -GLOBAL_VAR(demo_log) -GLOBAL_PROTECT(demo_log) - -GLOBAL_LIST_EMPTY(bombers) -GLOBAL_PROTECT(bombers) -GLOBAL_LIST_EMPTY(admin_log) -GLOBAL_PROTECT(admin_log) -GLOBAL_LIST_EMPTY(lastsignalers) //keeps last 100 signals here in format: "[src] used [REF(src)] @ location [src.loc]: [freq]/[code]" -GLOBAL_PROTECT(lastsignalers) -GLOBAL_LIST_EMPTY(lawchanges) //Stores who uploaded laws to which silicon-based lifeform, and what the law was -GLOBAL_PROTECT(lawchanges) - -GLOBAL_LIST_EMPTY(combatlog) -GLOBAL_PROTECT(combatlog) -GLOBAL_LIST_EMPTY(IClog) -GLOBAL_PROTECT(IClog) -GLOBAL_LIST_EMPTY(OOClog) -GLOBAL_PROTECT(OOClog) -GLOBAL_LIST_EMPTY(adminlog) -GLOBAL_PROTECT(adminlog) - -GLOBAL_LIST_EMPTY(active_turfs_startlist) - -/////Picture logging -GLOBAL_VAR(picture_log_directory) -GLOBAL_PROTECT(picture_log_directory) - -GLOBAL_VAR_INIT(picture_logging_id, 1) -GLOBAL_PROTECT(picture_logging_id) -GLOBAL_VAR(picture_logging_prefix) -GLOBAL_PROTECT(picture_logging_prefix) -///// +GLOBAL_VAR(log_directory) +GLOBAL_PROTECT(log_directory) +GLOBAL_VAR(world_game_log) +GLOBAL_PROTECT(world_game_log) +GLOBAL_VAR(world_runtime_log) +GLOBAL_PROTECT(world_runtime_log) +GLOBAL_VAR(world_qdel_log) +GLOBAL_PROTECT(world_qdel_log) +GLOBAL_VAR(world_attack_log) +GLOBAL_PROTECT(world_attack_log) +GLOBAL_VAR(world_econ_log) +GLOBAL_PROTECT(world_econ_log) +GLOBAL_VAR(world_href_log) +GLOBAL_PROTECT(world_href_log) +GLOBAL_VAR(round_id) +GLOBAL_PROTECT(round_id) +GLOBAL_VAR(config_error_log) +GLOBAL_PROTECT(config_error_log) +GLOBAL_VAR(sql_error_log) +GLOBAL_PROTECT(sql_error_log) +GLOBAL_VAR(world_pda_log) +GLOBAL_PROTECT(world_pda_log) +GLOBAL_VAR(world_telecomms_log) +GLOBAL_PROTECT(world_telecomms_log) +GLOBAL_VAR(world_manifest_log) +GLOBAL_PROTECT(world_manifest_log) +GLOBAL_VAR(query_debug_log) +GLOBAL_PROTECT(query_debug_log) +GLOBAL_VAR(world_job_debug_log) +GLOBAL_PROTECT(world_job_debug_log) +GLOBAL_VAR(world_mecha_log) +GLOBAL_PROTECT(world_mecha_log) +GLOBAL_VAR(world_virus_log) +GLOBAL_PROTECT(world_virus_log) +GLOBAL_VAR(world_asset_log) +GLOBAL_PROTECT(world_asset_log) +GLOBAL_VAR(world_cloning_log) +GLOBAL_PROTECT(world_cloning_log) +GLOBAL_VAR(world_map_error_log) +GLOBAL_PROTECT(world_map_error_log) +GLOBAL_VAR(world_paper_log) +GLOBAL_PROTECT(world_paper_log) +GLOBAL_VAR(tgui_log) +GLOBAL_PROTECT(tgui_log) +GLOBAL_VAR(world_shuttle_log) +GLOBAL_PROTECT(world_shuttle_log) +GLOBAL_VAR(discord_api_log) +GLOBAL_PROTECT(discord_api_log) + +GLOBAL_VAR(demo_log) +GLOBAL_PROTECT(demo_log) + +GLOBAL_LIST_EMPTY(bombers) +GLOBAL_PROTECT(bombers) +GLOBAL_LIST_EMPTY(admin_log) +GLOBAL_PROTECT(admin_log) +GLOBAL_LIST_EMPTY(lastsignalers) //keeps last 100 signals here in format: "[src] used [REF(src)] @ location [src.loc]: [freq]/[code]" +GLOBAL_PROTECT(lastsignalers) +GLOBAL_LIST_EMPTY(lawchanges) //Stores who uploaded laws to which silicon-based lifeform, and what the law was +GLOBAL_PROTECT(lawchanges) + +GLOBAL_LIST_EMPTY(combatlog) +GLOBAL_PROTECT(combatlog) +GLOBAL_LIST_EMPTY(IClog) +GLOBAL_PROTECT(IClog) +GLOBAL_LIST_EMPTY(OOClog) +GLOBAL_PROTECT(OOClog) +GLOBAL_LIST_EMPTY(adminlog) +GLOBAL_PROTECT(adminlog) + +GLOBAL_LIST_EMPTY(active_turfs_startlist) + +/////Picture logging +GLOBAL_VAR(picture_log_directory) +GLOBAL_PROTECT(picture_log_directory) + +GLOBAL_VAR_INIT(picture_logging_id, 1) +GLOBAL_PROTECT(picture_logging_id) +GLOBAL_VAR(picture_logging_prefix) +GLOBAL_PROTECT(picture_logging_prefix) +///// diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm index 1de2cd22fca..87356282b27 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -1,28 +1,28 @@ -GLOBAL_VAR_INIT(admin_notice, "") // Admin notice that all clients see when joining the server - -GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the host computer) and 0 world.ticks. - - // For FTP requests. (i.e. downloading runtime logs.) - // However it'd be ok to use for accessing attack logs and such too, which are even laggier. -GLOBAL_VAR_INIT(fileaccess_timer, 0) - -GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) - -GLOBAL_VAR_INIT(CELLRATE, 0.002) // conversion ratio between a watt-tick and kilojoule -GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second) - -GLOBAL_LIST_EMPTY(powernets) - -GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes - -GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details - -///All currently running polls held as datums -GLOBAL_LIST_EMPTY(polls) -GLOBAL_PROTECT(polls) - -///All poll option datums of running polls -GLOBAL_LIST_EMPTY(poll_options) -GLOBAL_PROTECT(poll_options) - -GLOBAL_VAR_INIT(internal_tick_usage, 0.2 * world.tick_lag) +GLOBAL_VAR_INIT(admin_notice, "") // Admin notice that all clients see when joining the server + +GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the host computer) and 0 world.ticks. + + // For FTP requests. (i.e. downloading runtime logs.) + // However it'd be ok to use for accessing attack logs and such too, which are even laggier. +GLOBAL_VAR_INIT(fileaccess_timer, 0) + +GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) + +GLOBAL_VAR_INIT(CELLRATE, 0.002) // conversion ratio between a watt-tick and kilojoule +GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second) + +GLOBAL_LIST_EMPTY(powernets) + +GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes + +GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details + +///All currently running polls held as datums +GLOBAL_LIST_EMPTY(polls) +GLOBAL_PROTECT(polls) + +///All poll option datums of running polls +GLOBAL_LIST_EMPTY(poll_options) +GLOBAL_PROTECT(poll_options) + +GLOBAL_VAR_INIT(internal_tick_usage, 0.2 * world.tick_lag) diff --git a/code/_js/byjax.dm b/code/_js/byjax.dm index 9d96bbc412f..7ac9bbff189 100644 --- a/code/_js/byjax.dm +++ b/code/_js/byjax.dm @@ -1,48 +1,48 @@ -//this function places received data into element with specified id. -#define js_byjax {" - -function replaceContent() { - var args = Array.prototype.slice.call(arguments); - var id = args\[0\]; - var content = args\[1\]; - var callback = null; - if(args\[2\]){ - callback = args\[2\]; - if(args\[3\]){ - args = args.slice(3); - } - } - var parent = document.getElementById(id); - if(typeof(parent)!=='undefined' && parent!=null){ - parent.innerHTML = content?content:''; - } - if(callback && window\[callback\]){ - window\[callback\].apply(null,args); - } -} -"} - -/* -sends data to control_id:replaceContent - -receiver - mob -control_id - window id (for windows opened with browse(), it'll be "windowname.browser") -target_element - HTML element id -new_content - HTML content -callback - js function that will be called after the data is sent -callback_args - arguments for callback function - -Be sure to include required js functions in your page, or it'll raise an exception. -*/ -/proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null) - if(receiver && target_element && control_id) // && winexists(receiver, control_id)) - var/list/argums = list(target_element, new_content) - if(callback) - argums += callback - if(callback_args) - argums += callback_args - argums = list2params(argums) - - receiver << output(argums,"[control_id]:replaceContent") - return - +//this function places received data into element with specified id. +#define js_byjax {" + +function replaceContent() { + var args = Array.prototype.slice.call(arguments); + var id = args\[0\]; + var content = args\[1\]; + var callback = null; + if(args\[2\]){ + callback = args\[2\]; + if(args\[3\]){ + args = args.slice(3); + } + } + var parent = document.getElementById(id); + if(typeof(parent)!=='undefined' && parent!=null){ + parent.innerHTML = content?content:''; + } + if(callback && window\[callback\]){ + window\[callback\].apply(null,args); + } +} +"} + +/* +sends data to control_id:replaceContent + +receiver - mob +control_id - window id (for windows opened with browse(), it'll be "windowname.browser") +target_element - HTML element id +new_content - HTML content +callback - js function that will be called after the data is sent +callback_args - arguments for callback function + +Be sure to include required js functions in your page, or it'll raise an exception. +*/ +/proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null) + if(receiver && target_element && control_id) // && winexists(receiver, control_id)) + var/list/argums = list(target_element, new_content) + if(callback) + argums += callback + if(callback_args) + argums += callback_args + argums = list2params(argums) + + receiver << output(argums,"[control_id]:replaceContent") + return + diff --git a/code/_js/menus.dm b/code/_js/menus.dm index 6756862e9b8..05e6edc6a6d 100644 --- a/code/_js/menus.dm +++ b/code/_js/menus.dm @@ -1,37 +1,37 @@ -#define js_dropdowns {" -function dropdowns() { - var divs = document.getElementsByTagName('div'); - var headers = new Array(); - var links = new Array(); - for(var i=0;i=0) { - elem.className = elem.className.replace('visible','hidden'); - this.className = this.className.replace('open','closed'); - this.innerHTML = this.innerHTML.replace('-','+'); - } - else { - elem.className = elem.className.replace('hidden','visible'); - this.className = this.className.replace('closed','open'); - this.innerHTML = this.innerHTML.replace('+','-'); - } - return false; - } - })(links\[i\]); - } - } -} -"} +#define js_dropdowns {" +function dropdowns() { + var divs = document.getElementsByTagName('div'); + var headers = new Array(); + var links = new Array(); + for(var i=0;i=0) { + elem.className = elem.className.replace('visible','hidden'); + this.className = this.className.replace('open','closed'); + this.innerHTML = this.innerHTML.replace('-','+'); + } + else { + elem.className = elem.className.replace('hidden','visible'); + this.className = this.className.replace('closed','open'); + this.innerHTML = this.innerHTML.replace('+','-'); + } + return false; + } + })(links\[i\]); + } + } +} +"} diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index 1d8c869e509..c9746fae980 100644 --- a/code/_onclick/adjacent.dm +++ b/code/_onclick/adjacent.dm @@ -1,105 +1,105 @@ -/* - Adjacency proc for determining touch range - - This is mostly to determine if a user can enter a square for the purposes of touching something. - Examples include reaching a square diagonally or reaching something on the other side of a glass window. - - This is calculated by looking for border items, or in the case of clicking diagonally from yourself, dense items. - This proc will NOT notice if you are trying to attack a window on the other side of a dense object in its turf. There is a window helper for that. - - Note that in all cases the neighbor is handled simply; this is usually the user's mob, in which case it is up to you - to check that the mob is not inside of something -*/ -/atom/proc/Adjacent(atom/neighbor) // basic inheritance, unused - return 0 - -// Not a sane use of the function and (for now) indicative of an error elsewhere -/area/Adjacent(atom/neighbor) - CRASH("Call to /area/Adjacent(), unimplemented proc") - - -/* - Adjacency (to turf): - * If you are in the same turf, always true - * If you are vertically/horizontally adjacent, ensure there are no border objects - * If you are diagonally adjacent, ensure you can pass through at least one of the mutually adjacent square. - * Passing through in this case ignores anything with the LETPASSTHROW pass flag, such as tables, racks, and morgue trays. -*/ -/turf/Adjacent(atom/neighbor, atom/target = null, atom/movable/mover = null) - var/turf/T0 = get_turf(neighbor) - - if(T0 == src) //same turf - return TRUE - - if(get_dist(src, T0) > 1 || z != T0.z) //too far - return FALSE - - // Non diagonal case - if(T0.x == x || T0.y == y) - // Check for border blockages - return T0.ClickCross(get_dir(T0,src), border_only = 1, target_atom = target, mover = mover) && src.ClickCross(get_dir(src,T0), border_only = 1, target_atom = target, mover = mover) - - // Diagonal case - var/in_dir = get_dir(T0,src) // eg. northwest (1+8) = 9 (00001001) - var/d1 = in_dir&3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001) - var/d2 = in_dir&12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000) - - for(var/d in list(d1,d2)) - if(!T0.ClickCross(d, border_only = 1, target_atom = target, mover = mover)) - continue // could not leave T0 in that direction - - var/turf/T1 = get_step(T0,d) - if(!T1 || T1.density) - continue - if(!T1.ClickCross(get_dir(T1,src), border_only = 0, target_atom = target, mover = mover) || !T1.ClickCross(get_dir(T1,T0), border_only = 0, target_atom = target, mover = mover)) - continue // couldn't enter or couldn't leave T1 - - if(!src.ClickCross(get_dir(src,T1), border_only = 1, target_atom = target, mover = mover)) - continue // could not enter src - - return 1 // we don't care about our own density - - return 0 - -/* - Adjacency (to anything else): - * Must be on a turf -*/ -/atom/movable/Adjacent(atom/neighbor) - if(neighbor == loc) - return TRUE - var/turf/T = loc - if(!istype(T)) - return FALSE - if(T.Adjacent(neighbor,target = neighbor, mover = src)) - return TRUE - return FALSE - -// This is necessary for storage items not on your person. -/obj/item/Adjacent(atom/neighbor, recurse = 1) - if(neighbor == loc) - return 1 - if(isitem(loc)) - if(recurse > 0) - return loc.Adjacent(neighbor,recurse - 1) - return 0 - return ..() - -/* - This checks if you there is uninterrupted airspace between that turf and this one. - This is defined as any dense ON_BORDER_1 object, or any dense object without LETPASSTHROW. - The border_only flag allows you to not objects (for source and destination squares) -*/ -/turf/proc/ClickCross(target_dir, border_only, target_atom = null, atom/movable/mover = null) - for(var/obj/O in src) - if((mover && O.CanPass(mover,get_step(src,target_dir))) || (!mover && !O.density)) - continue - if(O == target_atom || O == mover || (O.pass_flags & LETPASSTHROW)) //check if there's a dense object present on the turf - continue // LETPASSTHROW is used for anything you can click through (or the firedoor special case, see above) - - if( O.flags_1&ON_BORDER_1) // windows are on border, check them first - if( O.dir & target_dir || O.dir & (O.dir-1) ) // full tile windows are just diagonals mechanically - return 0 //O.dir&(O.dir-1) is false for any cardinal direction, but true for diagonal ones - else if( !border_only ) // dense, not on border, cannot pass over - return 0 - return 1 +/* + Adjacency proc for determining touch range + + This is mostly to determine if a user can enter a square for the purposes of touching something. + Examples include reaching a square diagonally or reaching something on the other side of a glass window. + + This is calculated by looking for border items, or in the case of clicking diagonally from yourself, dense items. + This proc will NOT notice if you are trying to attack a window on the other side of a dense object in its turf. There is a window helper for that. + + Note that in all cases the neighbor is handled simply; this is usually the user's mob, in which case it is up to you + to check that the mob is not inside of something +*/ +/atom/proc/Adjacent(atom/neighbor) // basic inheritance, unused + return 0 + +// Not a sane use of the function and (for now) indicative of an error elsewhere +/area/Adjacent(atom/neighbor) + CRASH("Call to /area/Adjacent(), unimplemented proc") + + +/* + Adjacency (to turf): + * If you are in the same turf, always true + * If you are vertically/horizontally adjacent, ensure there are no border objects + * If you are diagonally adjacent, ensure you can pass through at least one of the mutually adjacent square. + * Passing through in this case ignores anything with the LETPASSTHROW pass flag, such as tables, racks, and morgue trays. +*/ +/turf/Adjacent(atom/neighbor, atom/target = null, atom/movable/mover = null) + var/turf/T0 = get_turf(neighbor) + + if(T0 == src) //same turf + return TRUE + + if(get_dist(src, T0) > 1 || z != T0.z) //too far + return FALSE + + // Non diagonal case + if(T0.x == x || T0.y == y) + // Check for border blockages + return T0.ClickCross(get_dir(T0,src), border_only = 1, target_atom = target, mover = mover) && src.ClickCross(get_dir(src,T0), border_only = 1, target_atom = target, mover = mover) + + // Diagonal case + var/in_dir = get_dir(T0,src) // eg. northwest (1+8) = 9 (00001001) + var/d1 = in_dir&3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001) + var/d2 = in_dir&12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000) + + for(var/d in list(d1,d2)) + if(!T0.ClickCross(d, border_only = 1, target_atom = target, mover = mover)) + continue // could not leave T0 in that direction + + var/turf/T1 = get_step(T0,d) + if(!T1 || T1.density) + continue + if(!T1.ClickCross(get_dir(T1,src), border_only = 0, target_atom = target, mover = mover) || !T1.ClickCross(get_dir(T1,T0), border_only = 0, target_atom = target, mover = mover)) + continue // couldn't enter or couldn't leave T1 + + if(!src.ClickCross(get_dir(src,T1), border_only = 1, target_atom = target, mover = mover)) + continue // could not enter src + + return 1 // we don't care about our own density + + return 0 + +/* + Adjacency (to anything else): + * Must be on a turf +*/ +/atom/movable/Adjacent(atom/neighbor) + if(neighbor == loc) + return TRUE + var/turf/T = loc + if(!istype(T)) + return FALSE + if(T.Adjacent(neighbor,target = neighbor, mover = src)) + return TRUE + return FALSE + +// This is necessary for storage items not on your person. +/obj/item/Adjacent(atom/neighbor, recurse = 1) + if(neighbor == loc) + return 1 + if(isitem(loc)) + if(recurse > 0) + return loc.Adjacent(neighbor,recurse - 1) + return 0 + return ..() + +/* + This checks if you there is uninterrupted airspace between that turf and this one. + This is defined as any dense ON_BORDER_1 object, or any dense object without LETPASSTHROW. + The border_only flag allows you to not objects (for source and destination squares) +*/ +/turf/proc/ClickCross(target_dir, border_only, target_atom = null, atom/movable/mover = null) + for(var/obj/O in src) + if((mover && O.CanPass(mover,get_step(src,target_dir))) || (!mover && !O.density)) + continue + if(O == target_atom || O == mover || (O.pass_flags & LETPASSTHROW)) //check if there's a dense object present on the turf + continue // LETPASSTHROW is used for anything you can click through (or the firedoor special case, see above) + + if( O.flags_1&ON_BORDER_1) // windows are on border, check them first + if( O.dir & target_dir || O.dir & (O.dir-1) ) // full tile windows are just diagonals mechanically + return 0 //O.dir&(O.dir-1) is false for any cardinal direction, but true for diagonal ones + else if( !border_only ) // dense, not on border, cannot pass over + return 0 + return 1 diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index ab70cc4a286..a796bfc93c3 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -1,483 +1,483 @@ -/* - Click code cleanup - ~Sayu -*/ - -// 1 decisecond click delay (above and beyond mob/next_move) -//This is mainly modified by click code, to modify click delays elsewhere, use next_move and changeNext_move() -/mob/var/next_click = 0 - -// THESE DO NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK -/mob/var/next_move_adjust = 0 //Amount to adjust action/click delays by, + or - -/mob/var/next_move_modifier = 1 //Value to multiply action/click delays by - - -//Delays the mob's next click/action by num deciseconds -// eg: 10-3 = 7 deciseconds of delay -// eg: 10*0.5 = 5 deciseconds of delay -// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK - -/mob/proc/changeNext_move(num) - next_move = world.time + ((num+next_move_adjust)*next_move_modifier) - -/mob/living/changeNext_move(num) - var/mod = next_move_modifier - var/adj = next_move_adjust - for(var/i in status_effects) - var/datum/status_effect/S = i - mod *= S.nextmove_modifier() - adj += S.nextmove_adjust() - next_move = world.time + ((num + adj)*mod) - -/** - * Before anything else, defer these calls to a per-mobtype handler. This allows us to - * remove istype() spaghetti code, but requires the addition of other handler procs to simplify it. - * - * Alternately, you could hardcode every mob's variation in a flat [/mob/proc/ClickOn] proc; however, - * that's a lot of code duplication and is hard to maintain. - * - * Note that this proc can be overridden, and is in the case of screen objects. - */ -/atom/Click(location,control,params) - if(flags_1 & INITIALIZED_1) - SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr) - usr.ClickOn(src, params) - -/atom/DblClick(location,control,params) - if(flags_1 & INITIALIZED_1) - usr.DblClickOn(src,params) - -/atom/MouseWheel(delta_x,delta_y,location,control,params) - if(flags_1 & INITIALIZED_1) - usr.MouseWheelOn(src, delta_x, delta_y, params) - -/** - * Standard mob ClickOn() - * Handles exceptions: Buildmode, middle click, modified clicks, mech actions - * - * After that, mostly just check your state, check whether you're holding an item, - * check whether you're adjacent to the target, then pass off the click to whoever - * is receiving it. - * The most common are: - * * [mob/proc/UnarmedAttack] (atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves - * * [atom/proc/attackby] (item,user) - used only when adjacent - * * [obj/item/proc/afterattack] (atom,user,adjacent,params) - used both ranged and adjacent - * * [mob/proc/RangedAttack] (atom,params) - used only ranged, only used for tk and laser eyes but could be changed - */ -/mob/proc/ClickOn( atom/A, params ) - if(world.time <= next_click) - return - next_click = world.time + 1 - - if(check_click_intercept(params,A)) - return - - if(notransform) - return - - if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(incapacitated(ignore_restraints = 1)) - return - - face_atom(A) - - if(next_move > world.time) // in the year 2000... - return - - if(!modifiers["catcher"] && A.IsObscured()) - return - - if(ismecha(loc)) - var/obj/mecha/M = loc - return M.click_action(A,src,params) - - if(restrained()) - changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow - RestrainedClickOn(A) - return - - if(in_throw_mode) - throw_item(A) - return - - var/obj/item/W = get_active_held_item() - - if(W == A) - W.attack_self(src) - update_inv_hands() - return - - //These are always reachable. - //User itself, current loc, and user inventory - if(A in DirectAccess()) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A) - return - - //Can't reach anything else in lockers or other weirdness - if(!loc.AllowClick()) - return - - //Standard reach turf to turf or reaching inside storage - if(CanReach(A,W)) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A,1) - else - if(W) - W.afterattack(A,src,0,params) - else - RangedAttack(A,params) - -/// Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it -/atom/proc/IsObscured() - SHOULD_BE_PURE(TRUE) - if(!isturf(loc)) //This only makes sense for things directly on turfs for now - return FALSE - var/turf/T = get_turf_pixel(src) - if(!T) - return FALSE - for(var/atom/movable/AM in T) - if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density && AM.layer > layer) - return TRUE - return FALSE - -/turf/IsObscured() - for(var/item in src) - var/atom/movable/AM = item - if(AM.flags_1 & PREVENT_CLICK_UNDER_1) - return TRUE - return FALSE - -/** - * A backwards depth-limited breadth-first-search to see if the target is - * logically "in" anything adjacent to us. - */ -/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) - var/list/direct_access = DirectAccess() - var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH) - - var/list/closed = list() - var/list/checking = list(ultimate_target) - while (checking.len && depth > 0) - var/list/next = list() - --depth - - for(var/atom/target in checking) // will filter out nulls - if(closed[target] || isarea(target)) // avoid infinity situations - continue - closed[target] = TRUE - if(isturf(target) || isturf(target.loc) || (target in direct_access)) //Directly accessible atoms - if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks - return TRUE - - if (!target.loc) - continue - - //Storage and things with reachable internal atoms need add to next here. Or return COMPONENT_ALLOW_REACH. - if(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_ALLOW_REACH) - next += target.loc - - checking = next - return FALSE - -/atom/movable/proc/DirectAccess() - return list(src, loc) - -/mob/DirectAccess(atom/target) - return ..() + contents - -/mob/living/DirectAccess(atom/target) - return ..() + GetAllContents() - -/atom/proc/AllowClick() - return FALSE - -/turf/AllowClick() - return TRUE - -/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) - if(!here || !there) - return - switch(reach) - if(0) - return FALSE - if(1) - return FALSE //here.Adjacent(there) - if(2 to INFINITY) - var/obj/dummy = new(get_turf(here)) - dummy.pass_flags |= PASSTABLE - dummy.invisibility = INVISIBILITY_ABSTRACT - for(var/i in 1 to reach) //Limit it to that many tries - var/turf/T = get_step(dummy, get_dir(dummy, there)) - if(dummy.CanReach(there)) - qdel(dummy) - return TRUE - if(!dummy.Move(T)) //we're blocked! - qdel(dummy) - return - qdel(dummy) - -/// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) -/mob/proc/DblClickOn(atom/A, params) - return - - -/** - * Translates into [atom/proc/attack_hand], etc. - * - * Note: proximity_flag here is used to distinguish between normal usage (flag=1), - * and usage when clicking on things telekinetically (flag=0). This proc will - * not be called at ranged except with telekinesis. - * - * proximity_flag is not currently passed to attack_hand, and is instead used - * in human click code to allow glove touches only at melee range. - */ -/mob/proc/UnarmedAttack(atom/A, proximity_flag) - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - return - -/** - * Ranged unarmed attack: - * - * This currently is just a default for all mobs, involving - * laser eyes and telekinesis. You could easily add exceptions - * for things like ranged glove touches, spitting alien acid/neurotoxin, - * animals lunging, etc. - */ -/mob/proc/RangedAttack(atom/A, params) - SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) -/** - * Restrained ClickOn - * - * Used when you are handcuffed and click things. - * Not currently used by anything but could easily be. - */ -/mob/proc/RestrainedClickOn(atom/A) - return - -/** - * Middle click - * Mainly used for swapping hands - */ -/mob/proc/MiddleClickOn(atom/A) - . = SEND_SIGNAL(src, COMSIG_MOB_MIDDLECLICKON, A) - if(. & COMSIG_MOB_CANCEL_CLICKON) - return - swap_hand() - -/** - * Shift click - * For most mobs, examine. - * This is overridden in ai.dm - */ -/mob/proc/ShiftClickOn(atom/A) - A.ShiftClick(src) - return - -/atom/proc/ShiftClick(mob/user) - var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) - if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE)) - user.examinate(src) - return - -/** - * Ctrl click - * For most objects, pull - */ -/mob/proc/CtrlClickOn(atom/A) - A.CtrlClick(src) - return - -/atom/proc/CtrlClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) - var/mob/living/ML = user - if(istype(ML)) - ML.pulled(src) - -/mob/living/carbon/human/CtrlClick(mob/user) - if(ishuman(user) && Adjacent(user) && !user.incapacitated()) - if(world.time < user.next_move) - return FALSE - var/mob/living/carbon/human/H = user - H.dna.species.grab(H, src, H.mind.martial_art) - H.changeNext_move(CLICK_CD_MELEE) - else - ..() -/** - * Alt click - * Unused except for AI - */ -/mob/proc/AltClickOn(atom/A) - . = SEND_SIGNAL(src, COMSIG_MOB_ALTCLICKON, A) - if(. & COMSIG_MOB_CANCEL_CLICKON) - return - A.AltClick(src) - -/atom/proc/AltClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) - var/turf/T = get_turf(src) - if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -/// Use this instead of [/mob/proc/AltClickOn] where you only want turf content listing without additional atom alt-click interaction -/atom/proc/AltClickNoInteract(mob/user, atom/A) - var/turf/T = get_turf(A) - if(T && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -/mob/proc/TurfAdjacent(turf/T) - return T.Adjacent(src) - -/** - * Control+Shift click - * Unused except for AI - */ -/mob/proc/CtrlShiftClickOn(atom/A) - A.CtrlShiftClick(src) - return - -/mob/proc/ShiftMiddleClickOn(atom/A) - src.pointed(A) - return - -/atom/proc/CtrlShiftClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) - return - -/* - Misc helpers - face_atom: turns the mob towards what you clicked on -*/ - -/// Simple helper to face what you clicked on, in case it should be needed in more than one place -/mob/proc/face_atom(atom/A) - if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y ) - return - var/dx = A.x - x - var/dy = A.y - y - if(!dx && !dy) // Wall items are graphically shifted but on the floor - if(A.pixel_y > 16) - setDir(NORTH) - else if(A.pixel_y < -16) - setDir(SOUTH) - else if(A.pixel_x > 16) - setDir(EAST) - else if(A.pixel_x < -16) - setDir(WEST) - return - - if(abs(dx) < abs(dy)) - if(dy > 0) - setDir(NORTH) - else - setDir(SOUTH) - else - if(dx > 0) - setDir(EAST) - else - setDir(WEST) - -//debug -/obj/screen/proc/scale_to(x1,y1) - if(!y1) - y1 = x1 - var/matrix/M = new - M.Scale(x1,y1) - transform = M - -/obj/screen/click_catcher - icon = 'icons/mob/screen_gen.dmi' - icon_state = "catcher" - plane = CLICKCATCHER_PLANE - mouse_opacity = MOUSE_OPACITY_OPAQUE - screen_loc = "CENTER" - -#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size) -#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose. - -/obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15) - var/icon/newicon = icon('icons/mob/screen_gen.dmi', "catcher") - var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x) - var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y) - var/px = view_size_x * world.icon_size - var/py = view_size_y * world.icon_size - var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px) - var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py) - newicon.Scale(sx, sy) - icon = newicon - screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]" - var/matrix/M = new - M.Scale(px/sx, py/sy) - transform = M - -/obj/screen/click_catcher/Click(location, control, params) - var/list/modifiers = params2list(params) - if(modifiers["middle"] && iscarbon(usr)) - var/mob/living/carbon/C = usr - C.swap_hand() - else - var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client) - params += "&catcher=1" - if(T) - T.Click(location, control, params) - . = 1 - -/// MouseWheelOn -/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) - return - -/mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params) - var/list/modifier = params2list(params) - if(modifier["shift"]) - var/view = 0 - if(delta_y > 0) - view = -1 - else - view = 1 - add_view_range(view) - -/mob/proc/check_click_intercept(params,A) - //Client level intercept - if(client && client.click_intercept) - if(call(client.click_intercept, "InterceptClickOn")(src, params, A)) - return TRUE - - //Mob level intercept - if(click_intercept) - if(call(click_intercept, "InterceptClickOn")(src, params, A)) - return TRUE - - return FALSE +/* + Click code cleanup + ~Sayu +*/ + +// 1 decisecond click delay (above and beyond mob/next_move) +//This is mainly modified by click code, to modify click delays elsewhere, use next_move and changeNext_move() +/mob/var/next_click = 0 + +// THESE DO NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK +/mob/var/next_move_adjust = 0 //Amount to adjust action/click delays by, + or - +/mob/var/next_move_modifier = 1 //Value to multiply action/click delays by + + +//Delays the mob's next click/action by num deciseconds +// eg: 10-3 = 7 deciseconds of delay +// eg: 10*0.5 = 5 deciseconds of delay +// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK + +/mob/proc/changeNext_move(num) + next_move = world.time + ((num+next_move_adjust)*next_move_modifier) + +/mob/living/changeNext_move(num) + var/mod = next_move_modifier + var/adj = next_move_adjust + for(var/i in status_effects) + var/datum/status_effect/S = i + mod *= S.nextmove_modifier() + adj += S.nextmove_adjust() + next_move = world.time + ((num + adj)*mod) + +/** + * Before anything else, defer these calls to a per-mobtype handler. This allows us to + * remove istype() spaghetti code, but requires the addition of other handler procs to simplify it. + * + * Alternately, you could hardcode every mob's variation in a flat [/mob/proc/ClickOn] proc; however, + * that's a lot of code duplication and is hard to maintain. + * + * Note that this proc can be overridden, and is in the case of screen objects. + */ +/atom/Click(location,control,params) + if(flags_1 & INITIALIZED_1) + SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr) + usr.ClickOn(src, params) + +/atom/DblClick(location,control,params) + if(flags_1 & INITIALIZED_1) + usr.DblClickOn(src,params) + +/atom/MouseWheel(delta_x,delta_y,location,control,params) + if(flags_1 & INITIALIZED_1) + usr.MouseWheelOn(src, delta_x, delta_y, params) + +/** + * Standard mob ClickOn() + * Handles exceptions: Buildmode, middle click, modified clicks, mech actions + * + * After that, mostly just check your state, check whether you're holding an item, + * check whether you're adjacent to the target, then pass off the click to whoever + * is receiving it. + * The most common are: + * * [mob/proc/UnarmedAttack] (atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves + * * [atom/proc/attackby] (item,user) - used only when adjacent + * * [obj/item/proc/afterattack] (atom,user,adjacent,params) - used both ranged and adjacent + * * [mob/proc/RangedAttack] (atom,params) - used only ranged, only used for tk and laser eyes but could be changed + */ +/mob/proc/ClickOn( atom/A, params ) + if(world.time <= next_click) + return + next_click = world.time + 1 + + if(check_click_intercept(params,A)) + return + + if(notransform) + return + + if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(incapacitated(ignore_restraints = 1)) + return + + face_atom(A) + + if(next_move > world.time) // in the year 2000... + return + + if(!modifiers["catcher"] && A.IsObscured()) + return + + if(ismecha(loc)) + var/obj/mecha/M = loc + return M.click_action(A,src,params) + + if(restrained()) + changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow + RestrainedClickOn(A) + return + + if(in_throw_mode) + throw_item(A) + return + + var/obj/item/W = get_active_held_item() + + if(W == A) + W.attack_self(src) + update_inv_hands() + return + + //These are always reachable. + //User itself, current loc, and user inventory + if(A in DirectAccess()) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A) + return + + //Can't reach anything else in lockers or other weirdness + if(!loc.AllowClick()) + return + + //Standard reach turf to turf or reaching inside storage + if(CanReach(A,W)) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A,1) + else + if(W) + W.afterattack(A,src,0,params) + else + RangedAttack(A,params) + +/// Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it +/atom/proc/IsObscured() + SHOULD_BE_PURE(TRUE) + if(!isturf(loc)) //This only makes sense for things directly on turfs for now + return FALSE + var/turf/T = get_turf_pixel(src) + if(!T) + return FALSE + for(var/atom/movable/AM in T) + if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density && AM.layer > layer) + return TRUE + return FALSE + +/turf/IsObscured() + for(var/item in src) + var/atom/movable/AM = item + if(AM.flags_1 & PREVENT_CLICK_UNDER_1) + return TRUE + return FALSE + +/** + * A backwards depth-limited breadth-first-search to see if the target is + * logically "in" anything adjacent to us. + */ +/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) + var/list/direct_access = DirectAccess() + var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH) + + var/list/closed = list() + var/list/checking = list(ultimate_target) + while (checking.len && depth > 0) + var/list/next = list() + --depth + + for(var/atom/target in checking) // will filter out nulls + if(closed[target] || isarea(target)) // avoid infinity situations + continue + closed[target] = TRUE + if(isturf(target) || isturf(target.loc) || (target in direct_access)) //Directly accessible atoms + if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks + return TRUE + + if (!target.loc) + continue + + //Storage and things with reachable internal atoms need add to next here. Or return COMPONENT_ALLOW_REACH. + if(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_ALLOW_REACH) + next += target.loc + + checking = next + return FALSE + +/atom/movable/proc/DirectAccess() + return list(src, loc) + +/mob/DirectAccess(atom/target) + return ..() + contents + +/mob/living/DirectAccess(atom/target) + return ..() + GetAllContents() + +/atom/proc/AllowClick() + return FALSE + +/turf/AllowClick() + return TRUE + +/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) + if(!here || !there) + return + switch(reach) + if(0) + return FALSE + if(1) + return FALSE //here.Adjacent(there) + if(2 to INFINITY) + var/obj/dummy = new(get_turf(here)) + dummy.pass_flags |= PASSTABLE + dummy.invisibility = INVISIBILITY_ABSTRACT + for(var/i in 1 to reach) //Limit it to that many tries + var/turf/T = get_step(dummy, get_dir(dummy, there)) + if(dummy.CanReach(there)) + qdel(dummy) + return TRUE + if(!dummy.Move(T)) //we're blocked! + qdel(dummy) + return + qdel(dummy) + +/// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) +/mob/proc/DblClickOn(atom/A, params) + return + + +/** + * Translates into [atom/proc/attack_hand], etc. + * + * Note: proximity_flag here is used to distinguish between normal usage (flag=1), + * and usage when clicking on things telekinetically (flag=0). This proc will + * not be called at ranged except with telekinesis. + * + * proximity_flag is not currently passed to attack_hand, and is instead used + * in human click code to allow glove touches only at melee range. + */ +/mob/proc/UnarmedAttack(atom/A, proximity_flag) + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + return + +/** + * Ranged unarmed attack: + * + * This currently is just a default for all mobs, involving + * laser eyes and telekinesis. You could easily add exceptions + * for things like ranged glove touches, spitting alien acid/neurotoxin, + * animals lunging, etc. + */ +/mob/proc/RangedAttack(atom/A, params) + SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) +/** + * Restrained ClickOn + * + * Used when you are handcuffed and click things. + * Not currently used by anything but could easily be. + */ +/mob/proc/RestrainedClickOn(atom/A) + return + +/** + * Middle click + * Mainly used for swapping hands + */ +/mob/proc/MiddleClickOn(atom/A) + . = SEND_SIGNAL(src, COMSIG_MOB_MIDDLECLICKON, A) + if(. & COMSIG_MOB_CANCEL_CLICKON) + return + swap_hand() + +/** + * Shift click + * For most mobs, examine. + * This is overridden in ai.dm + */ +/mob/proc/ShiftClickOn(atom/A) + A.ShiftClick(src) + return + +/atom/proc/ShiftClick(mob/user) + var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) + if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE)) + user.examinate(src) + return + +/** + * Ctrl click + * For most objects, pull + */ +/mob/proc/CtrlClickOn(atom/A) + A.CtrlClick(src) + return + +/atom/proc/CtrlClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) + var/mob/living/ML = user + if(istype(ML)) + ML.pulled(src) + +/mob/living/carbon/human/CtrlClick(mob/user) + if(ishuman(user) && Adjacent(user) && !user.incapacitated()) + if(world.time < user.next_move) + return FALSE + var/mob/living/carbon/human/H = user + H.dna.species.grab(H, src, H.mind.martial_art) + H.changeNext_move(CLICK_CD_MELEE) + else + ..() +/** + * Alt click + * Unused except for AI + */ +/mob/proc/AltClickOn(atom/A) + . = SEND_SIGNAL(src, COMSIG_MOB_ALTCLICKON, A) + if(. & COMSIG_MOB_CANCEL_CLICKON) + return + A.AltClick(src) + +/atom/proc/AltClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) + var/turf/T = get_turf(src) + if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T)) + user.listed_turf = T + user.client.statpanel = T.name + +/// Use this instead of [/mob/proc/AltClickOn] where you only want turf content listing without additional atom alt-click interaction +/atom/proc/AltClickNoInteract(mob/user, atom/A) + var/turf/T = get_turf(A) + if(T && user.TurfAdjacent(T)) + user.listed_turf = T + user.client.statpanel = T.name + +/mob/proc/TurfAdjacent(turf/T) + return T.Adjacent(src) + +/** + * Control+Shift click + * Unused except for AI + */ +/mob/proc/CtrlShiftClickOn(atom/A) + A.CtrlShiftClick(src) + return + +/mob/proc/ShiftMiddleClickOn(atom/A) + src.pointed(A) + return + +/atom/proc/CtrlShiftClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) + return + +/* + Misc helpers + face_atom: turns the mob towards what you clicked on +*/ + +/// Simple helper to face what you clicked on, in case it should be needed in more than one place +/mob/proc/face_atom(atom/A) + if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y ) + return + var/dx = A.x - x + var/dy = A.y - y + if(!dx && !dy) // Wall items are graphically shifted but on the floor + if(A.pixel_y > 16) + setDir(NORTH) + else if(A.pixel_y < -16) + setDir(SOUTH) + else if(A.pixel_x > 16) + setDir(EAST) + else if(A.pixel_x < -16) + setDir(WEST) + return + + if(abs(dx) < abs(dy)) + if(dy > 0) + setDir(NORTH) + else + setDir(SOUTH) + else + if(dx > 0) + setDir(EAST) + else + setDir(WEST) + +//debug +/obj/screen/proc/scale_to(x1,y1) + if(!y1) + y1 = x1 + var/matrix/M = new + M.Scale(x1,y1) + transform = M + +/obj/screen/click_catcher + icon = 'icons/mob/screen_gen.dmi' + icon_state = "catcher" + plane = CLICKCATCHER_PLANE + mouse_opacity = MOUSE_OPACITY_OPAQUE + screen_loc = "CENTER" + +#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size) +#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose. + +/obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15) + var/icon/newicon = icon('icons/mob/screen_gen.dmi', "catcher") + var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x) + var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y) + var/px = view_size_x * world.icon_size + var/py = view_size_y * world.icon_size + var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px) + var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py) + newicon.Scale(sx, sy) + icon = newicon + screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]" + var/matrix/M = new + M.Scale(px/sx, py/sy) + transform = M + +/obj/screen/click_catcher/Click(location, control, params) + var/list/modifiers = params2list(params) + if(modifiers["middle"] && iscarbon(usr)) + var/mob/living/carbon/C = usr + C.swap_hand() + else + var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client) + params += "&catcher=1" + if(T) + T.Click(location, control, params) + . = 1 + +/// MouseWheelOn +/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) + return + +/mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params) + var/list/modifier = params2list(params) + if(modifier["shift"]) + var/view = 0 + if(delta_y > 0) + view = -1 + else + view = 1 + add_view_range(view) + +/mob/proc/check_click_intercept(params,A) + //Client level intercept + if(client && client.click_intercept) + if(call(client.click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + //Mob level intercept + if(click_intercept) + if(call(click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + return FALSE diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index ffd0df158c6..1de90c0f1c0 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -1,176 +1,176 @@ -/* - Cyborg ClickOn() - - Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. - However, they do have a range restriction on item use, so they cannot do without the - adjacency code. -*/ - -/mob/living/silicon/robot/ClickOn(atom/A, params) - if(world.time <= next_click) - return - next_click = world.time + 1 - - if(check_click_intercept(params,A)) - return - - if(stat || lockcharge || IsParalyzed() || IsStun()) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(next_move >= world.time) - return - - face_atom(A) // change direction to face what you clicked on - - /* - cyborg restrained() currently does nothing - if(restrained()) - RestrainedClickOn(A) - return - */ - if(aicamera.in_camera_mode) //Cyborg picture taking - aicamera.camera_mode_off() - aicamera.captureimage(A, usr) - return - - var/obj/item/W = get_active_held_item() - - if(!W && get_dist(src,A) <= interaction_range) - A.attack_robot(src) - return - - if(W) - // buckled cannot prevent machine interlinking but stops arm movement - if( buckled || incapacitated()) - return - - if(W == A) - W.attack_self(src) - return - - // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents) - if(A == loc || (A in loc) || (A in contents)) - W.melee_attack_chain(src, A, params) - return - - if(!isturf(loc)) - return - - // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc && isturf(A.loc.loc)) - if(isturf(A) || isturf(A.loc)) - if(A.Adjacent(src)) // see adjacent.dm - W.melee_attack_chain(src, A, params) - return - else - W.afterattack(A, src, 0, params) - return - -//Middle click cycles through selected modules. -/mob/living/silicon/robot/MiddleClickOn(atom/A) - . = ..() - cycle_modules() - -//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks -// for non-doors/apcs -/mob/living/silicon/robot/CtrlShiftClickOn(atom/A) - A.BorgCtrlShiftClick(src) -/mob/living/silicon/robot/ShiftClickOn(atom/A) - A.BorgShiftClick(src) -/mob/living/silicon/robot/CtrlClickOn(atom/A) - A.BorgCtrlClick(src) -/mob/living/silicon/robot/AltClickOn(atom/A) - A.BorgAltClick(src) - -/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlShiftClick(user) - -/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlShiftClick() - else - ..() - - -/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - ShiftClick(user) - -/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIShiftClick() - else - ..() - - -/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlClick(user) - -/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/atom/proc/BorgAltClick(mob/living/silicon/robot/user) - AltClick(user) - return - -/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/* - As with AI, these are not used in click code, - because the code for robots is specific, not generic. - - If you would like to add advanced features to robot - clicks, you can do so here, but you will have to - change attack_robot() above to the proper function -*/ -/mob/living/silicon/robot/UnarmedAttack(atom/A) - A.attack_robot(src) -/mob/living/silicon/robot/RangedAttack(atom/A) - A.attack_robot(src) - -/atom/proc/attack_robot(mob/user) - attack_ai(user) - return +/* + Cyborg ClickOn() + + Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. + However, they do have a range restriction on item use, so they cannot do without the + adjacency code. +*/ + +/mob/living/silicon/robot/ClickOn(atom/A, params) + if(world.time <= next_click) + return + next_click = world.time + 1 + + if(check_click_intercept(params,A)) + return + + if(stat || lockcharge || IsParalyzed() || IsStun()) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(next_move >= world.time) + return + + face_atom(A) // change direction to face what you clicked on + + /* + cyborg restrained() currently does nothing + if(restrained()) + RestrainedClickOn(A) + return + */ + if(aicamera.in_camera_mode) //Cyborg picture taking + aicamera.camera_mode_off() + aicamera.captureimage(A, usr) + return + + var/obj/item/W = get_active_held_item() + + if(!W && get_dist(src,A) <= interaction_range) + A.attack_robot(src) + return + + if(W) + // buckled cannot prevent machine interlinking but stops arm movement + if( buckled || incapacitated()) + return + + if(W == A) + W.attack_self(src) + return + + // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents) + if(A == loc || (A in loc) || (A in contents)) + W.melee_attack_chain(src, A, params) + return + + if(!isturf(loc)) + return + + // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc && isturf(A.loc.loc)) + if(isturf(A) || isturf(A.loc)) + if(A.Adjacent(src)) // see adjacent.dm + W.melee_attack_chain(src, A, params) + return + else + W.afterattack(A, src, 0, params) + return + +//Middle click cycles through selected modules. +/mob/living/silicon/robot/MiddleClickOn(atom/A) + . = ..() + cycle_modules() + +//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks +// for non-doors/apcs +/mob/living/silicon/robot/CtrlShiftClickOn(atom/A) + A.BorgCtrlShiftClick(src) +/mob/living/silicon/robot/ShiftClickOn(atom/A) + A.BorgShiftClick(src) +/mob/living/silicon/robot/CtrlClickOn(atom/A) + A.BorgCtrlClick(src) +/mob/living/silicon/robot/AltClickOn(atom/A) + A.BorgAltClick(src) + +/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlShiftClick(user) + +/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlShiftClick() + else + ..() + + +/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + ShiftClick(user) + +/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIShiftClick() + else + ..() + + +/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlClick(user) + +/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/atom/proc/BorgAltClick(mob/living/silicon/robot/user) + AltClick(user) + return + +/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIAltClick() + else + ..() + +/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIAltClick() + else + ..() + +/* + As with AI, these are not used in click code, + because the code for robots is specific, not generic. + + If you would like to add advanced features to robot + clicks, you can do so here, but you will have to + change attack_robot() above to the proper function +*/ +/mob/living/silicon/robot/UnarmedAttack(atom/A) + A.attack_robot(src) +/mob/living/silicon/robot/RangedAttack(atom/A) + A.attack_robot(src) + +/atom/proc/attack_robot(mob/user) + attack_ai(user) + return diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index 57e19ee27ec..f70b6a86a65 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -1,115 +1,115 @@ -/* - MouseDrop: - - Called on the atom you're dragging. In a lot of circumstances we want to use the - receiving object instead, so that's the default action. This allows you to drag - almost anything into a trash can. -*/ -/atom/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) - if(!usr || !over) - return - if(SEND_SIGNAL(src, COMSIG_MOUSEDROP_ONTO, over, usr) & COMPONENT_NO_MOUSEDROP) //Whatever is receiving will verify themselves for adjacency. - return - if(over == src) - return usr.client.Click(src, src_location, src_control, params) - if(!Adjacent(usr) || !over.Adjacent(usr)) - return // should stop you from dragging through windows - - over.MouseDrop_T(src,usr) - return - -// receive a mousedrop -/atom/proc/MouseDrop_T(atom/dropping, mob/user) - SEND_SIGNAL(src, COMSIG_MOUSEDROPPED_ONTO, dropping, user) - return - - -/client/MouseDown(object, location, control, params) - if (mouse_down_icon) - mouse_pointer_icon = mouse_down_icon - var/delay = mob.CanMobAutoclick(object, location, params) - if(delay) - selected_target[1] = object - selected_target[2] = params - while(selected_target[1]) - Click(selected_target[1], location, control, selected_target[2]) - sleep(delay) - active_mousedown_item = mob.canMobMousedown(object, location, params) - if(active_mousedown_item) - active_mousedown_item.onMouseDown(object, location, params, mob) - -/client/MouseUp(object, location, control, params) - if (mouse_up_icon) - mouse_pointer_icon = mouse_up_icon - selected_target[1] = null - if(active_mousedown_item) - active_mousedown_item.onMouseUp(object, location, params, mob) - active_mousedown_item = null - -/mob/proc/CanMobAutoclick(object, location, params) - -/mob/living/carbon/CanMobAutoclick(atom/object, location, params) - if(!object.IsAutoclickable()) - return - var/obj/item/h = get_active_held_item() - if(h) - . = h.CanItemAutoclick(object, location, params) - -/mob/proc/canMobMousedown(atom/object, location, params) - -/mob/living/carbon/canMobMousedown(atom/object, location, params) - var/obj/item/H = get_active_held_item() - if(H) - . = H.canItemMouseDown(object, location, params) - -/obj/item/proc/CanItemAutoclick(object, location, params) - -/obj/item/proc/canItemMouseDown(object, location, params) - if(canMouseDown) - return src - -/obj/item/proc/onMouseDown(object, location, params, mob) - return - -/obj/item/proc/onMouseUp(object, location, params, mob) - return - -/obj/item/gun/CanItemAutoclick(object, location, params) - . = automatic - -/atom/proc/IsAutoclickable() - . = 1 - -/obj/screen/IsAutoclickable() - . = 0 - -/obj/screen/click_catcher/IsAutoclickable() - . = 1 - -/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) - var/list/L = params2list(params) - if (L["middle"]) - if (src_object && src_location != over_location) - middragtime = world.time - middragatom = src_object - else - middragtime = 0 - middragatom = null - mouseParams = params - mouseLocation = over_location - mouseObject = over_object - if(selected_target[1] && over_object && over_object.IsAutoclickable()) - selected_target[1] = over_object - selected_target[2] = params - if(active_mousedown_item) - active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - - -/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - return - -/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params) - if (middragatom == src_object) - middragtime = 0 - middragatom = null - ..() +/* + MouseDrop: + + Called on the atom you're dragging. In a lot of circumstances we want to use the + receiving object instead, so that's the default action. This allows you to drag + almost anything into a trash can. +*/ +/atom/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) + if(!usr || !over) + return + if(SEND_SIGNAL(src, COMSIG_MOUSEDROP_ONTO, over, usr) & COMPONENT_NO_MOUSEDROP) //Whatever is receiving will verify themselves for adjacency. + return + if(over == src) + return usr.client.Click(src, src_location, src_control, params) + if(!Adjacent(usr) || !over.Adjacent(usr)) + return // should stop you from dragging through windows + + over.MouseDrop_T(src,usr) + return + +// receive a mousedrop +/atom/proc/MouseDrop_T(atom/dropping, mob/user) + SEND_SIGNAL(src, COMSIG_MOUSEDROPPED_ONTO, dropping, user) + return + + +/client/MouseDown(object, location, control, params) + if (mouse_down_icon) + mouse_pointer_icon = mouse_down_icon + var/delay = mob.CanMobAutoclick(object, location, params) + if(delay) + selected_target[1] = object + selected_target[2] = params + while(selected_target[1]) + Click(selected_target[1], location, control, selected_target[2]) + sleep(delay) + active_mousedown_item = mob.canMobMousedown(object, location, params) + if(active_mousedown_item) + active_mousedown_item.onMouseDown(object, location, params, mob) + +/client/MouseUp(object, location, control, params) + if (mouse_up_icon) + mouse_pointer_icon = mouse_up_icon + selected_target[1] = null + if(active_mousedown_item) + active_mousedown_item.onMouseUp(object, location, params, mob) + active_mousedown_item = null + +/mob/proc/CanMobAutoclick(object, location, params) + +/mob/living/carbon/CanMobAutoclick(atom/object, location, params) + if(!object.IsAutoclickable()) + return + var/obj/item/h = get_active_held_item() + if(h) + . = h.CanItemAutoclick(object, location, params) + +/mob/proc/canMobMousedown(atom/object, location, params) + +/mob/living/carbon/canMobMousedown(atom/object, location, params) + var/obj/item/H = get_active_held_item() + if(H) + . = H.canItemMouseDown(object, location, params) + +/obj/item/proc/CanItemAutoclick(object, location, params) + +/obj/item/proc/canItemMouseDown(object, location, params) + if(canMouseDown) + return src + +/obj/item/proc/onMouseDown(object, location, params, mob) + return + +/obj/item/proc/onMouseUp(object, location, params, mob) + return + +/obj/item/gun/CanItemAutoclick(object, location, params) + . = automatic + +/atom/proc/IsAutoclickable() + . = 1 + +/obj/screen/IsAutoclickable() + . = 0 + +/obj/screen/click_catcher/IsAutoclickable() + . = 1 + +/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) + var/list/L = params2list(params) + if (L["middle"]) + if (src_object && src_location != over_location) + middragtime = world.time + middragatom = src_object + else + middragtime = 0 + middragatom = null + mouseParams = params + mouseLocation = over_location + mouseObject = over_object + if(selected_target[1] && over_object && over_object.IsAutoclickable()) + selected_target[1] = over_object + selected_target[2] = params + if(active_mousedown_item) + active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + + +/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + return + +/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params) + if (middragatom == src_object) + middragtime = 0 + middragatom = null + ..() diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 9d6b3dbafa7..5d8ad8973e2 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -1,174 +1,174 @@ -/* - These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var. - - The short version: - - Everything is encoded as strings because apparently that's how Byond rolls. - - "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid. - "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid. - Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy. - - In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective - screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your - UI to scale with screen size. - - The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15". - Therefore, the top right corner (except during admin shenanigans) is at "15,15" -*/ - -/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5) - var/x_off = -(!(i % 2)) - var/y_off = round((i-1) / 2) - return"CENTER+[x_off]:16,SOUTH+[y_off]:5" - -/proc/ui_equip_position(mob/M) - var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5) - return "CENTER:-16,SOUTH+[y_off+1]:5" - -/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5) - var/x_off = which == 1 ? -1 : 0 - var/y_off = round((M.held_items.len-1) / 2) - return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5" - -//Lower left, persistent menu -#define ui_inventory "WEST:6,SOUTH:5" - -//Middle left indicators -#define ui_lingchemdisplay "WEST,CENTER-1:15" -#define ui_lingstingdisplay "WEST:6,CENTER-3:11" -#define ui_devilsouldisplay "WEST:6,CENTER-1:15" - -//Lower center, persistent menu -#define ui_sstore1 "CENTER-5:10,SOUTH:5" -#define ui_id "CENTER-4:12,SOUTH:5" -#define ui_belt "CENTER-3:14,SOUTH:5" -#define ui_back "CENTER-2:14,SOUTH:5" -#define ui_storage1 "CENTER+1:18,SOUTH:5" -#define ui_storage2 "CENTER+2:20,SOUTH:5" - -//Lower right, persistent menu -#define ui_drop_throw "EAST-1:28,SOUTH+1:7" -#define ui_above_movement "EAST-2:26,SOUTH+1:7" -#define ui_above_intent "EAST-3:24, SOUTH+1:7" -#define ui_movi "EAST-2:26,SOUTH:5" -#define ui_acti "EAST-3:24,SOUTH:5" -#define ui_zonesel "EAST-1:28,SOUTH:5" -#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) -#define ui_crafting "EAST-4:22,SOUTH:5" -#define ui_building "EAST-4:22,SOUTH:21" -#define ui_language_menu "EAST-4:6,SOUTH:21" -#define ui_skill_menu "EAST-4:22,SOUTH:5" - -//Upper-middle right (alerts) -#define ui_alert1 "EAST-1:28,CENTER+5:27" -#define ui_alert2 "EAST-1:28,CENTER+4:25" -#define ui_alert3 "EAST-1:28,CENTER+3:23" -#define ui_alert4 "EAST-1:28,CENTER+2:21" -#define ui_alert5 "EAST-1:28,CENTER+1:19" - -//Middle right (status indicators) -#define ui_healthdoll "EAST-1:28,CENTER-2:13" -#define ui_health "EAST-1:28,CENTER-1:15" -#define ui_internal "EAST-1:28,CENTER-3:10" -#define ui_mood "EAST-1:28,CENTER:17" -#define ui_spacesuit "EAST-1:28,CENTER-4:10" - -//Pop-up inventory -#define ui_shoes "WEST+1:8,SOUTH:5" -#define ui_iclothing "WEST:6,SOUTH+1:7" -#define ui_oclothing "WEST+1:8,SOUTH+1:7" -#define ui_gloves "WEST+2:10,SOUTH+1:7" -#define ui_glasses "WEST:6,SOUTH+3:11" -#define ui_mask "WEST+1:8,SOUTH+2:9" -#define ui_ears "WEST+2:10,SOUTH+2:9" -#define ui_neck "WEST:6,SOUTH+2:9" -#define ui_head "WEST+1:8,SOUTH+3:11" - -//Generic living -#define ui_living_pull "EAST-1:28,CENTER-3:15" -#define ui_living_healthdoll "EAST-1:28,CENTER-1:15" - -//Monkeys -#define ui_monkey_head "CENTER-5:13,SOUTH:5" -#define ui_monkey_mask "CENTER-4:14,SOUTH:5" -#define ui_monkey_neck "CENTER-3:15,SOUTH:5" -#define ui_monkey_back "CENTER-2:16,SOUTH:5" - -//Drones -#define ui_drone_drop "CENTER+1:18,SOUTH:5" -#define ui_drone_pull "CENTER+2:2,SOUTH:5" -#define ui_drone_storage "CENTER-2:14,SOUTH:5" -#define ui_drone_head "CENTER-3:14,SOUTH:5" - -//Cyborgs -#define ui_borg_health "EAST-1:28,CENTER-1:15" -#define ui_borg_pull "EAST-2:26,SOUTH+1:7" -#define ui_borg_radio "EAST-1:28,SOUTH+1:7" -#define ui_borg_intents "EAST-2:26,SOUTH:5" -#define ui_borg_sensor "CENTER-3:16, SOUTH:5" -#define ui_borg_lamp "CENTER-4:16, SOUTH:5" -#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" -#define ui_inv1 "CENTER-2:16,SOUTH:5" -#define ui_inv2 "CENTER-1 :16,SOUTH:5" -#define ui_inv3 "CENTER :16,SOUTH:5" -#define ui_borg_module "CENTER+1:16,SOUTH:5" -#define ui_borg_store "CENTER+2:16,SOUTH:5" -#define ui_borg_camera "CENTER+3:21,SOUTH:5" -#define ui_borg_album "CENTER+4:21,SOUTH:5" -#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5" - -//Aliens -#define ui_alien_health "EAST,CENTER-1:15" -#define ui_alienplasmadisplay "EAST,CENTER-2:15" -#define ui_alien_queen_finder "EAST,CENTER-3:15" -#define ui_alien_storage_r "CENTER+1:18,SOUTH:5" -#define ui_alien_language_menu "EAST-3:26,SOUTH:5" - -//Constructs -#define ui_construct_pull "EAST,CENTER-2:15" -#define ui_construct_health "EAST,CENTER:15" - -// AI -#define ui_ai_core "SOUTH:6,WEST" -#define ui_ai_camera_list "SOUTH:6,WEST+1" -#define ui_ai_track_with_camera "SOUTH:6,WEST+2" -#define ui_ai_camera_light "SOUTH:6,WEST+3" -#define ui_ai_crew_monitor "SOUTH:6,WEST+4" -#define ui_ai_crew_manifest "SOUTH:6,WEST+5" -#define ui_ai_alerts "SOUTH:6,WEST+6" -#define ui_ai_announcement "SOUTH:6,WEST+7" -#define ui_ai_shuttle "SOUTH:6,WEST+8" -#define ui_ai_state_laws "SOUTH:6,WEST+9" -#define ui_ai_pda_send "SOUTH:6,WEST+10" -#define ui_ai_pda_log "SOUTH:6,WEST+11" -#define ui_ai_take_picture "SOUTH:6,WEST+12" -#define ui_ai_view_images "SOUTH:6,WEST+13" -#define ui_ai_sensor "SOUTH:6,WEST+14" -#define ui_ai_multicam "SOUTH+1:6,WEST+13" -#define ui_ai_add_multicam "SOUTH+1:6,WEST+14" - -// pAI -#define ui_pai_software "SOUTH:6,WEST" -#define ui_pai_shell "SOUTH:6,WEST+1" -#define ui_pai_chassis "SOUTH:6,WEST+2" -#define ui_pai_rest "SOUTH:6,WEST+3" -#define ui_pai_light "SOUTH:6,WEST+4" -#define ui_pai_newscaster "SOUTH:6,WEST+5" -#define ui_pai_host_monitor "SOUTH:6,WEST+6" -#define ui_pai_crew_manifest "SOUTH:6,WEST+7" -#define ui_pai_state_laws "SOUTH:6,WEST+8" -#define ui_pai_pda_send "SOUTH:6,WEST+9" -#define ui_pai_pda_log "SOUTH:6,WEST+10" -#define ui_pai_take_picture "SOUTH:6,WEST+12" -#define ui_pai_view_images "SOUTH:6,WEST+13" - -//Ghosts -#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24" -#define ui_ghost_orbit "SOUTH:6,CENTER-2:24" -#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24" -#define ui_ghost_teleport "SOUTH:6,CENTER:24" -#define ui_ghost_pai "SOUTH: 6, CENTER+1:24" -#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24" - -#define ui_wanted_lvl "NORTH,11" +/* + These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var. + + The short version: + + Everything is encoded as strings because apparently that's how Byond rolls. + + "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid. + "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid. + Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy. + + In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective + screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your + UI to scale with screen size. + + The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15". + Therefore, the top right corner (except during admin shenanigans) is at "15,15" +*/ + +/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5) + var/x_off = -(!(i % 2)) + var/y_off = round((i-1) / 2) + return"CENTER+[x_off]:16,SOUTH+[y_off]:5" + +/proc/ui_equip_position(mob/M) + var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5) + return "CENTER:-16,SOUTH+[y_off+1]:5" + +/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5) + var/x_off = which == 1 ? -1 : 0 + var/y_off = round((M.held_items.len-1) / 2) + return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5" + +//Lower left, persistent menu +#define ui_inventory "WEST:6,SOUTH:5" + +//Middle left indicators +#define ui_lingchemdisplay "WEST,CENTER-1:15" +#define ui_lingstingdisplay "WEST:6,CENTER-3:11" +#define ui_devilsouldisplay "WEST:6,CENTER-1:15" + +//Lower center, persistent menu +#define ui_sstore1 "CENTER-5:10,SOUTH:5" +#define ui_id "CENTER-4:12,SOUTH:5" +#define ui_belt "CENTER-3:14,SOUTH:5" +#define ui_back "CENTER-2:14,SOUTH:5" +#define ui_storage1 "CENTER+1:18,SOUTH:5" +#define ui_storage2 "CENTER+2:20,SOUTH:5" + +//Lower right, persistent menu +#define ui_drop_throw "EAST-1:28,SOUTH+1:7" +#define ui_above_movement "EAST-2:26,SOUTH+1:7" +#define ui_above_intent "EAST-3:24, SOUTH+1:7" +#define ui_movi "EAST-2:26,SOUTH:5" +#define ui_acti "EAST-3:24,SOUTH:5" +#define ui_zonesel "EAST-1:28,SOUTH:5" +#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) +#define ui_crafting "EAST-4:22,SOUTH:5" +#define ui_building "EAST-4:22,SOUTH:21" +#define ui_language_menu "EAST-4:6,SOUTH:21" +#define ui_skill_menu "EAST-4:22,SOUTH:5" + +//Upper-middle right (alerts) +#define ui_alert1 "EAST-1:28,CENTER+5:27" +#define ui_alert2 "EAST-1:28,CENTER+4:25" +#define ui_alert3 "EAST-1:28,CENTER+3:23" +#define ui_alert4 "EAST-1:28,CENTER+2:21" +#define ui_alert5 "EAST-1:28,CENTER+1:19" + +//Middle right (status indicators) +#define ui_healthdoll "EAST-1:28,CENTER-2:13" +#define ui_health "EAST-1:28,CENTER-1:15" +#define ui_internal "EAST-1:28,CENTER-3:10" +#define ui_mood "EAST-1:28,CENTER:17" +#define ui_spacesuit "EAST-1:28,CENTER-4:10" + +//Pop-up inventory +#define ui_shoes "WEST+1:8,SOUTH:5" +#define ui_iclothing "WEST:6,SOUTH+1:7" +#define ui_oclothing "WEST+1:8,SOUTH+1:7" +#define ui_gloves "WEST+2:10,SOUTH+1:7" +#define ui_glasses "WEST:6,SOUTH+3:11" +#define ui_mask "WEST+1:8,SOUTH+2:9" +#define ui_ears "WEST+2:10,SOUTH+2:9" +#define ui_neck "WEST:6,SOUTH+2:9" +#define ui_head "WEST+1:8,SOUTH+3:11" + +//Generic living +#define ui_living_pull "EAST-1:28,CENTER-3:15" +#define ui_living_healthdoll "EAST-1:28,CENTER-1:15" + +//Monkeys +#define ui_monkey_head "CENTER-5:13,SOUTH:5" +#define ui_monkey_mask "CENTER-4:14,SOUTH:5" +#define ui_monkey_neck "CENTER-3:15,SOUTH:5" +#define ui_monkey_back "CENTER-2:16,SOUTH:5" + +//Drones +#define ui_drone_drop "CENTER+1:18,SOUTH:5" +#define ui_drone_pull "CENTER+2:2,SOUTH:5" +#define ui_drone_storage "CENTER-2:14,SOUTH:5" +#define ui_drone_head "CENTER-3:14,SOUTH:5" + +//Cyborgs +#define ui_borg_health "EAST-1:28,CENTER-1:15" +#define ui_borg_pull "EAST-2:26,SOUTH+1:7" +#define ui_borg_radio "EAST-1:28,SOUTH+1:7" +#define ui_borg_intents "EAST-2:26,SOUTH:5" +#define ui_borg_sensor "CENTER-3:16, SOUTH:5" +#define ui_borg_lamp "CENTER-4:16, SOUTH:5" +#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" +#define ui_inv1 "CENTER-2:16,SOUTH:5" +#define ui_inv2 "CENTER-1 :16,SOUTH:5" +#define ui_inv3 "CENTER :16,SOUTH:5" +#define ui_borg_module "CENTER+1:16,SOUTH:5" +#define ui_borg_store "CENTER+2:16,SOUTH:5" +#define ui_borg_camera "CENTER+3:21,SOUTH:5" +#define ui_borg_album "CENTER+4:21,SOUTH:5" +#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5" + +//Aliens +#define ui_alien_health "EAST,CENTER-1:15" +#define ui_alienplasmadisplay "EAST,CENTER-2:15" +#define ui_alien_queen_finder "EAST,CENTER-3:15" +#define ui_alien_storage_r "CENTER+1:18,SOUTH:5" +#define ui_alien_language_menu "EAST-3:26,SOUTH:5" + +//Constructs +#define ui_construct_pull "EAST,CENTER-2:15" +#define ui_construct_health "EAST,CENTER:15" + +// AI +#define ui_ai_core "SOUTH:6,WEST" +#define ui_ai_camera_list "SOUTH:6,WEST+1" +#define ui_ai_track_with_camera "SOUTH:6,WEST+2" +#define ui_ai_camera_light "SOUTH:6,WEST+3" +#define ui_ai_crew_monitor "SOUTH:6,WEST+4" +#define ui_ai_crew_manifest "SOUTH:6,WEST+5" +#define ui_ai_alerts "SOUTH:6,WEST+6" +#define ui_ai_announcement "SOUTH:6,WEST+7" +#define ui_ai_shuttle "SOUTH:6,WEST+8" +#define ui_ai_state_laws "SOUTH:6,WEST+9" +#define ui_ai_pda_send "SOUTH:6,WEST+10" +#define ui_ai_pda_log "SOUTH:6,WEST+11" +#define ui_ai_take_picture "SOUTH:6,WEST+12" +#define ui_ai_view_images "SOUTH:6,WEST+13" +#define ui_ai_sensor "SOUTH:6,WEST+14" +#define ui_ai_multicam "SOUTH+1:6,WEST+13" +#define ui_ai_add_multicam "SOUTH+1:6,WEST+14" + +// pAI +#define ui_pai_software "SOUTH:6,WEST" +#define ui_pai_shell "SOUTH:6,WEST+1" +#define ui_pai_chassis "SOUTH:6,WEST+2" +#define ui_pai_rest "SOUTH:6,WEST+3" +#define ui_pai_light "SOUTH:6,WEST+4" +#define ui_pai_newscaster "SOUTH:6,WEST+5" +#define ui_pai_host_monitor "SOUTH:6,WEST+6" +#define ui_pai_crew_manifest "SOUTH:6,WEST+7" +#define ui_pai_state_laws "SOUTH:6,WEST+8" +#define ui_pai_pda_send "SOUTH:6,WEST+9" +#define ui_pai_pda_log "SOUTH:6,WEST+10" +#define ui_pai_take_picture "SOUTH:6,WEST+12" +#define ui_pai_view_images "SOUTH:6,WEST+13" + +//Ghosts +#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24" +#define ui_ghost_orbit "SOUTH:6,CENTER-2:24" +#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24" +#define ui_ghost_teleport "SOUTH:6,CENTER:24" +#define ui_ghost_pai "SOUTH: 6, CENTER+1:24" +#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24" + +#define ui_wanted_lvl "NORTH,11" diff --git a/code/_onclick/hud/alien.dm b/code/_onclick/hud/alien.dm index 5e792d4fddc..e8b0078be05 100644 --- a/code/_onclick/hud/alien.dm +++ b/code/_onclick/hud/alien.dm @@ -1,132 +1,132 @@ -/obj/screen/alien - icon = 'icons/mob/screen_alien.dmi' - -/obj/screen/alien/leap - name = "toggle leap" - icon_state = "leap_off" - -/obj/screen/alien/leap/Click() - if(isalienhunter(usr)) - var/mob/living/carbon/alien/humanoid/hunter/AH = usr - AH.toggle_leap() - -/obj/screen/alien/plasma_display - name = "plasma stored" - icon_state = "power_display" - screen_loc = ui_alienplasmadisplay - -/obj/screen/alien/alien_queen_finder - name = "queen sense" - desc = "Allows you to sense the general direction of your Queen." - icon_state = "queen_finder" - screen_loc = ui_alien_queen_finder - -/datum/hud/alien - ui_style = 'icons/mob/screen_alien.dmi' - -/datum/hud/alien/New(mob/living/carbon/alien/humanoid/owner) - ..() - - var/obj/screen/using - -//equippable shit - -//hands - build_hand_slots() - -//begin buttons - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_1" - using.screen_loc = ui_swaphand_position(owner,1) - using.hud = src - static_inventory += using - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) - using.hud = src - static_inventory += using - - using = new /obj/screen/act_intent/alien() - using.icon_state = mymob.a_intent - using.hud = src - static_inventory += using - action_intent = using - - if(isalienhunter(mymob)) - var/mob/living/carbon/alien/humanoid/hunter/H = mymob - H.leap_icon = new /obj/screen/alien/leap() - H.leap_icon.screen_loc = ui_alien_storage_r - static_inventory += H.leap_icon - - using = new/obj/screen/language_menu - using.screen_loc = ui_alien_language_menu - using.hud = src - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - using.hud = src - static_inventory += using - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_movement - using.hud = src - hotkeybuttons += using - - throw_icon = new /obj/screen/throw_catch() - throw_icon.icon = ui_style - throw_icon.screen_loc = ui_drop_throw - throw_icon.hud = src - hotkeybuttons += throw_icon - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_movement - pull_icon.hud = src - static_inventory += pull_icon - -//begin indicators - - healths = new /obj/screen/healths/alien() - healths.hud = src - infodisplay += healths - - alien_plasma_display = new /obj/screen/alien/plasma_display() - alien_plasma_display.hud = src - infodisplay += alien_plasma_display - - if(!isalienqueen(mymob)) - alien_queen_finder = new /obj/screen/alien/alien_queen_finder - alien_queen_finder.hud = src - infodisplay += alien_queen_finder - - zone_select = new /obj/screen/zone_sel/alien() - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - inv.hud = src - inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv - inv.update_icon() - -/datum/hud/alien/persistent_inventory_update() - if(!mymob) - return - var/mob/living/carbon/alien/humanoid/H = mymob - if(hud_version != HUD_STYLE_NOHUD) - for(var/obj/item/I in H.held_items) - I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) - H.client.screen += I - else - for(var/obj/item/I in H.held_items) - I.screen_loc = null - H.client.screen -= I +/obj/screen/alien + icon = 'icons/mob/screen_alien.dmi' + +/obj/screen/alien/leap + name = "toggle leap" + icon_state = "leap_off" + +/obj/screen/alien/leap/Click() + if(isalienhunter(usr)) + var/mob/living/carbon/alien/humanoid/hunter/AH = usr + AH.toggle_leap() + +/obj/screen/alien/plasma_display + name = "plasma stored" + icon_state = "power_display" + screen_loc = ui_alienplasmadisplay + +/obj/screen/alien/alien_queen_finder + name = "queen sense" + desc = "Allows you to sense the general direction of your Queen." + icon_state = "queen_finder" + screen_loc = ui_alien_queen_finder + +/datum/hud/alien + ui_style = 'icons/mob/screen_alien.dmi' + +/datum/hud/alien/New(mob/living/carbon/alien/humanoid/owner) + ..() + + var/obj/screen/using + +//equippable shit + +//hands + build_hand_slots() + +//begin buttons + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_1" + using.screen_loc = ui_swaphand_position(owner,1) + using.hud = src + static_inventory += using + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_2" + using.screen_loc = ui_swaphand_position(owner,2) + using.hud = src + static_inventory += using + + using = new /obj/screen/act_intent/alien() + using.icon_state = mymob.a_intent + using.hud = src + static_inventory += using + action_intent = using + + if(isalienhunter(mymob)) + var/mob/living/carbon/alien/humanoid/hunter/H = mymob + H.leap_icon = new /obj/screen/alien/leap() + H.leap_icon.screen_loc = ui_alien_storage_r + static_inventory += H.leap_icon + + using = new/obj/screen/language_menu + using.screen_loc = ui_alien_language_menu + using.hud = src + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + using.hud = src + static_inventory += using + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_movement + using.hud = src + hotkeybuttons += using + + throw_icon = new /obj/screen/throw_catch() + throw_icon.icon = ui_style + throw_icon.screen_loc = ui_drop_throw + throw_icon.hud = src + hotkeybuttons += throw_icon + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_movement + pull_icon.hud = src + static_inventory += pull_icon + +//begin indicators + + healths = new /obj/screen/healths/alien() + healths.hud = src + infodisplay += healths + + alien_plasma_display = new /obj/screen/alien/plasma_display() + alien_plasma_display.hud = src + infodisplay += alien_plasma_display + + if(!isalienqueen(mymob)) + alien_queen_finder = new /obj/screen/alien/alien_queen_finder + alien_queen_finder.hud = src + infodisplay += alien_queen_finder + + zone_select = new /obj/screen/zone_sel/alien() + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + inv.hud = src + inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv + inv.update_icon() + +/datum/hud/alien/persistent_inventory_update() + if(!mymob) + return + var/mob/living/carbon/alien/humanoid/H = mymob + if(hud_version != HUD_STYLE_NOHUD) + for(var/obj/item/I in H.held_items) + I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) + H.client.screen += I + else + for(var/obj/item/I in H.held_items) + I.screen_loc = null + H.client.screen -= I diff --git a/code/_onclick/hud/alien_larva.dm b/code/_onclick/hud/alien_larva.dm index 45389ded3cf..0e4f945df43 100644 --- a/code/_onclick/hud/alien_larva.dm +++ b/code/_onclick/hud/alien_larva.dm @@ -1,37 +1,37 @@ -/datum/hud/larva - ui_style = 'icons/mob/screen_alien.dmi' - -/datum/hud/larva/New(mob/owner) - ..() - var/obj/screen/using - - using = new /obj/screen/act_intent/alien() - using.icon_state = mymob.a_intent - using.hud = src - static_inventory += using - action_intent = using - - healths = new /obj/screen/healths/alien() - healths.hud = src - infodisplay += healths - - alien_queen_finder = new /obj/screen/alien/alien_queen_finder() - alien_queen_finder.hud = src - infodisplay += alien_queen_finder - - pull_icon = new /obj/screen/pull() - pull_icon.icon = 'icons/mob/screen_alien.dmi' - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_movement - pull_icon.hud = src - hotkeybuttons += pull_icon - - using = new/obj/screen/language_menu - using.screen_loc = ui_alien_language_menu - using.hud = src - static_inventory += using - - zone_select = new /obj/screen/zone_sel/alien() - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select +/datum/hud/larva + ui_style = 'icons/mob/screen_alien.dmi' + +/datum/hud/larva/New(mob/owner) + ..() + var/obj/screen/using + + using = new /obj/screen/act_intent/alien() + using.icon_state = mymob.a_intent + using.hud = src + static_inventory += using + action_intent = using + + healths = new /obj/screen/healths/alien() + healths.hud = src + infodisplay += healths + + alien_queen_finder = new /obj/screen/alien/alien_queen_finder() + alien_queen_finder.hud = src + infodisplay += alien_queen_finder + + pull_icon = new /obj/screen/pull() + pull_icon.icon = 'icons/mob/screen_alien.dmi' + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_movement + pull_icon.hud = src + hotkeybuttons += pull_icon + + using = new/obj/screen/language_menu + using.screen_loc = ui_alien_language_menu + using.hud = src + static_inventory += using + + zone_select = new /obj/screen/zone_sel/alien() + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select diff --git a/code/_onclick/hud/credits.dm b/code/_onclick/hud/credits.dm index 702aa1cdcb8..2cd0ddedaa1 100644 --- a/code/_onclick/hud/credits.dm +++ b/code/_onclick/hud/credits.dm @@ -1,69 +1,69 @@ -#define CREDIT_ROLL_SPEED 125 -#define CREDIT_SPAWN_SPEED 10 -#define CREDIT_ANIMATE_HEIGHT (14 * world.icon_size) -#define CREDIT_EASE_DURATION 22 -#define CREDITS_PATH "[global.config.directory]/contributors.dmi" - -/client/proc/RollCredits() - set waitfor = FALSE - if(!fexists(CREDITS_PATH)) - return - var/icon/credits_icon = new(CREDITS_PATH) - LAZYINITLIST(credits) - var/list/_credits = credits - verbs += /client/proc/ClearCredits - var/static/list/credit_order_for_this_round - if(isnull(credit_order_for_this_round)) - credit_order_for_this_round = list("Thanks for playing!") + (shuffle(icon_states(credits_icon)) - "Thanks for playing!") - for(var/I in credit_order_for_this_round) - if(!credits) - return - _credits += new /obj/screen/credit(null, I, src, credits_icon) - sleep(CREDIT_SPAWN_SPEED) - sleep(CREDIT_ROLL_SPEED - CREDIT_SPAWN_SPEED) - verbs -= /client/proc/ClearCredits - qdel(credits_icon) - -/client/proc/ClearCredits() - set name = "Hide Credits" - set category = "OOC" - verbs -= /client/proc/ClearCredits - QDEL_LIST(credits) - credits = null - -/obj/screen/credit - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - alpha = 0 - screen_loc = "12,1" - layer = SPLASHSCREEN_LAYER - var/client/parent - var/matrix/target - -/obj/screen/credit/Initialize(mapload, credited, client/P, icon/I) - . = ..() - icon = I - parent = P - icon_state = credited - maptext = credited - maptext_x = world.icon_size + 8 - maptext_y = (world.icon_size / 2) - 4 - maptext_width = world.icon_size * 3 - var/matrix/M = matrix(transform) - M.Translate(0, CREDIT_ANIMATE_HEIGHT) - animate(src, transform = M, time = CREDIT_ROLL_SPEED) - target = M - animate(src, alpha = 255, time = CREDIT_EASE_DURATION, flags = ANIMATION_PARALLEL) - addtimer(CALLBACK(src, .proc/FadeOut), CREDIT_ROLL_SPEED - CREDIT_EASE_DURATION) - QDEL_IN(src, CREDIT_ROLL_SPEED) - P.screen += src - -/obj/screen/credit/Destroy() - var/client/P = parent - P.screen -= src - icon = null - LAZYREMOVE(P.credits, src) - parent = null - return ..() - -/obj/screen/credit/proc/FadeOut() - animate(src, alpha = 0, transform = target, time = CREDIT_EASE_DURATION) +#define CREDIT_ROLL_SPEED 125 +#define CREDIT_SPAWN_SPEED 10 +#define CREDIT_ANIMATE_HEIGHT (14 * world.icon_size) +#define CREDIT_EASE_DURATION 22 +#define CREDITS_PATH "[global.config.directory]/contributors.dmi" + +/client/proc/RollCredits() + set waitfor = FALSE + if(!fexists(CREDITS_PATH)) + return + var/icon/credits_icon = new(CREDITS_PATH) + LAZYINITLIST(credits) + var/list/_credits = credits + verbs += /client/proc/ClearCredits + var/static/list/credit_order_for_this_round + if(isnull(credit_order_for_this_round)) + credit_order_for_this_round = list("Thanks for playing!") + (shuffle(icon_states(credits_icon)) - "Thanks for playing!") + for(var/I in credit_order_for_this_round) + if(!credits) + return + _credits += new /obj/screen/credit(null, I, src, credits_icon) + sleep(CREDIT_SPAWN_SPEED) + sleep(CREDIT_ROLL_SPEED - CREDIT_SPAWN_SPEED) + verbs -= /client/proc/ClearCredits + qdel(credits_icon) + +/client/proc/ClearCredits() + set name = "Hide Credits" + set category = "OOC" + verbs -= /client/proc/ClearCredits + QDEL_LIST(credits) + credits = null + +/obj/screen/credit + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 0 + screen_loc = "12,1" + layer = SPLASHSCREEN_LAYER + var/client/parent + var/matrix/target + +/obj/screen/credit/Initialize(mapload, credited, client/P, icon/I) + . = ..() + icon = I + parent = P + icon_state = credited + maptext = credited + maptext_x = world.icon_size + 8 + maptext_y = (world.icon_size / 2) - 4 + maptext_width = world.icon_size * 3 + var/matrix/M = matrix(transform) + M.Translate(0, CREDIT_ANIMATE_HEIGHT) + animate(src, transform = M, time = CREDIT_ROLL_SPEED) + target = M + animate(src, alpha = 255, time = CREDIT_EASE_DURATION, flags = ANIMATION_PARALLEL) + addtimer(CALLBACK(src, .proc/FadeOut), CREDIT_ROLL_SPEED - CREDIT_EASE_DURATION) + QDEL_IN(src, CREDIT_ROLL_SPEED) + P.screen += src + +/obj/screen/credit/Destroy() + var/client/P = parent + P.screen -= src + icon = null + LAZYREMOVE(P.credits, src) + parent = null + return ..() + +/obj/screen/credit/proc/FadeOut() + animate(src, alpha = 0, transform = target, time = CREDIT_EASE_DURATION) diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index 34149fd6f0d..f6ca52b61a5 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -1,172 +1,172 @@ -/mob/proc/overlay_fullscreen(category, type, severity) - var/obj/screen/fullscreen/screen = screens[category] - if (!screen || screen.type != type) - // needs to be recreated - clear_fullscreen(category, FALSE) - screens[category] = screen = new type() - else if ((!severity || severity == screen.severity) && (!client || screen.screen_loc != "CENTER-7,CENTER-7" || screen.view == client.view)) - // doesn't need to be updated - return screen - - screen.icon_state = "[initial(screen.icon_state)][severity]" - screen.severity = severity - if (client && screen.should_show_to(src)) - screen.update_for_view(client.view) - client.screen += screen - - return screen - -/mob/proc/clear_fullscreen(category, animated = 10) - var/obj/screen/fullscreen/screen = screens[category] - if(!screen) - return - - screens -= category - - if(animated) - animate(screen, alpha = 0, time = animated) - addtimer(CALLBACK(src, .proc/clear_fullscreen_after_animate, screen), animated, TIMER_CLIENT_TIME) - else - if(client) - client.screen -= screen - qdel(screen) - -/mob/proc/clear_fullscreen_after_animate(obj/screen/fullscreen/screen) - if(client) - client.screen -= screen - qdel(screen) - -/mob/proc/clear_fullscreens() - for(var/category in screens) - clear_fullscreen(category) - -/mob/proc/hide_fullscreens() - if(client) - for(var/category in screens) - client.screen -= screens[category] - -/mob/proc/reload_fullscreen() - if(client) - var/obj/screen/fullscreen/screen - for(var/category in screens) - screen = screens[category] - if(screen.should_show_to(src)) - screen.update_for_view(client.view) - client.screen |= screen - else - client.screen -= screen - -/obj/screen/fullscreen - icon = 'icons/mob/screen_full.dmi' - icon_state = "default" - screen_loc = "CENTER-7,CENTER-7" - layer = FULLSCREEN_LAYER - plane = FULLSCREEN_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/view = 7 - var/severity = 0 - var/show_when_dead = FALSE - -/obj/screen/fullscreen/proc/update_for_view(client_view) - if (screen_loc == "CENTER-7,CENTER-7" && view != client_view) - var/list/actualview = getviewsize(client_view) - view = client_view - transform = matrix(actualview[1]/FULLSCREEN_OVERLAY_RESOLUTION_X, 0, 0, 0, actualview[2]/FULLSCREEN_OVERLAY_RESOLUTION_Y, 0) - -/obj/screen/fullscreen/proc/should_show_to(mob/mymob) - if(!show_when_dead && mymob.stat == DEAD) - return FALSE - return TRUE - -/obj/screen/fullscreen/Destroy() - severity = 0 - . = ..() - -/obj/screen/fullscreen/brute - icon_state = "brutedamageoverlay" - layer = UI_DAMAGE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/oxy - icon_state = "oxydamageoverlay" - layer = UI_DAMAGE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/crit - icon_state = "passage" - layer = CRIT_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/crit/vision - icon_state = "oxydamageoverlay" - layer = BLIND_LAYER - -/obj/screen/fullscreen/blind - icon_state = "blackimageoverlay" - layer = BLIND_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/curse - icon_state = "curse" - layer = CURSE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/impaired - icon_state = "impairedoverlay" - -/obj/screen/fullscreen/flash - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "flash" - -/obj/screen/fullscreen/flash/static - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "noise" - -/obj/screen/fullscreen/high - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "druggy" - -/obj/screen/fullscreen/color_vision - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "flash" - alpha = 80 - -/obj/screen/fullscreen/color_vision/green - color = "#00ff00" - -/obj/screen/fullscreen/color_vision/red - color = "#ff0000" - -/obj/screen/fullscreen/color_vision/blue - color = "#0000ff" - -/obj/screen/fullscreen/lighting_backdrop - icon = 'icons/mob/screen_gen.dmi' - icon_state = "flash" - transform = matrix(200, 0, 0, 0, 200, 0) - plane = LIGHTING_PLANE - blend_mode = BLEND_OVERLAY - show_when_dead = TRUE - -//Provides darkness to the back of the lighting plane -/obj/screen/fullscreen/lighting_backdrop/lit - invisibility = INVISIBILITY_LIGHTING - layer = BACKGROUND_LAYER+21 - color = "#000" - show_when_dead = TRUE - -//Provides whiteness in case you don't see lights so everything is still visible -/obj/screen/fullscreen/lighting_backdrop/unlit - layer = BACKGROUND_LAYER+20 - show_when_dead = TRUE - -/obj/screen/fullscreen/see_through_darkness - icon_state = "nightvision" - plane = LIGHTING_PLANE - layer = LIGHTING_LAYER - blend_mode = BLEND_ADD - show_when_dead = TRUE +/mob/proc/overlay_fullscreen(category, type, severity) + var/obj/screen/fullscreen/screen = screens[category] + if (!screen || screen.type != type) + // needs to be recreated + clear_fullscreen(category, FALSE) + screens[category] = screen = new type() + else if ((!severity || severity == screen.severity) && (!client || screen.screen_loc != "CENTER-7,CENTER-7" || screen.view == client.view)) + // doesn't need to be updated + return screen + + screen.icon_state = "[initial(screen.icon_state)][severity]" + screen.severity = severity + if (client && screen.should_show_to(src)) + screen.update_for_view(client.view) + client.screen += screen + + return screen + +/mob/proc/clear_fullscreen(category, animated = 10) + var/obj/screen/fullscreen/screen = screens[category] + if(!screen) + return + + screens -= category + + if(animated) + animate(screen, alpha = 0, time = animated) + addtimer(CALLBACK(src, .proc/clear_fullscreen_after_animate, screen), animated, TIMER_CLIENT_TIME) + else + if(client) + client.screen -= screen + qdel(screen) + +/mob/proc/clear_fullscreen_after_animate(obj/screen/fullscreen/screen) + if(client) + client.screen -= screen + qdel(screen) + +/mob/proc/clear_fullscreens() + for(var/category in screens) + clear_fullscreen(category) + +/mob/proc/hide_fullscreens() + if(client) + for(var/category in screens) + client.screen -= screens[category] + +/mob/proc/reload_fullscreen() + if(client) + var/obj/screen/fullscreen/screen + for(var/category in screens) + screen = screens[category] + if(screen.should_show_to(src)) + screen.update_for_view(client.view) + client.screen |= screen + else + client.screen -= screen + +/obj/screen/fullscreen + icon = 'icons/mob/screen_full.dmi' + icon_state = "default" + screen_loc = "CENTER-7,CENTER-7" + layer = FULLSCREEN_LAYER + plane = FULLSCREEN_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/view = 7 + var/severity = 0 + var/show_when_dead = FALSE + +/obj/screen/fullscreen/proc/update_for_view(client_view) + if (screen_loc == "CENTER-7,CENTER-7" && view != client_view) + var/list/actualview = getviewsize(client_view) + view = client_view + transform = matrix(actualview[1]/FULLSCREEN_OVERLAY_RESOLUTION_X, 0, 0, 0, actualview[2]/FULLSCREEN_OVERLAY_RESOLUTION_Y, 0) + +/obj/screen/fullscreen/proc/should_show_to(mob/mymob) + if(!show_when_dead && mymob.stat == DEAD) + return FALSE + return TRUE + +/obj/screen/fullscreen/Destroy() + severity = 0 + . = ..() + +/obj/screen/fullscreen/brute + icon_state = "brutedamageoverlay" + layer = UI_DAMAGE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/oxy + icon_state = "oxydamageoverlay" + layer = UI_DAMAGE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/crit + icon_state = "passage" + layer = CRIT_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/crit/vision + icon_state = "oxydamageoverlay" + layer = BLIND_LAYER + +/obj/screen/fullscreen/blind + icon_state = "blackimageoverlay" + layer = BLIND_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/curse + icon_state = "curse" + layer = CURSE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/impaired + icon_state = "impairedoverlay" + +/obj/screen/fullscreen/flash + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + +/obj/screen/fullscreen/flash/static + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "noise" + +/obj/screen/fullscreen/high + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "druggy" + +/obj/screen/fullscreen/color_vision + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + alpha = 80 + +/obj/screen/fullscreen/color_vision/green + color = "#00ff00" + +/obj/screen/fullscreen/color_vision/red + color = "#ff0000" + +/obj/screen/fullscreen/color_vision/blue + color = "#0000ff" + +/obj/screen/fullscreen/lighting_backdrop + icon = 'icons/mob/screen_gen.dmi' + icon_state = "flash" + transform = matrix(200, 0, 0, 0, 200, 0) + plane = LIGHTING_PLANE + blend_mode = BLEND_OVERLAY + show_when_dead = TRUE + +//Provides darkness to the back of the lighting plane +/obj/screen/fullscreen/lighting_backdrop/lit + invisibility = INVISIBILITY_LIGHTING + layer = BACKGROUND_LAYER+21 + color = "#000" + show_when_dead = TRUE + +//Provides whiteness in case you don't see lights so everything is still visible +/obj/screen/fullscreen/lighting_backdrop/unlit + layer = BACKGROUND_LAYER+20 + show_when_dead = TRUE + +/obj/screen/fullscreen/see_through_darkness + icon_state = "nightvision" + plane = LIGHTING_PLANE + layer = LIGHTING_LAYER + blend_mode = BLEND_ADD + show_when_dead = TRUE diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm index 7c7db148f15..ea946c2e487 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -1,116 +1,116 @@ -/obj/screen/ghost - icon = 'icons/mob/screen_ghost.dmi' - -/obj/screen/ghost/MouseEntered() - flick(icon_state + "_anim", src) - -/obj/screen/ghost/jumptomob - name = "Jump to mob" - icon_state = "jumptomob" - -/obj/screen/ghost/jumptomob/Click() - var/mob/dead/observer/G = usr - G.jumptomob() - -/obj/screen/ghost/orbit - name = "Orbit" - icon_state = "orbit" - -/obj/screen/ghost/orbit/Click() - var/mob/dead/observer/G = usr - G.follow() - -/obj/screen/ghost/reenter_corpse - name = "Reenter corpse" - icon_state = "reenter_corpse" - -/obj/screen/ghost/reenter_corpse/Click() - var/mob/dead/observer/G = usr - G.reenter_corpse() - -/obj/screen/ghost/teleport - name = "Teleport" - icon_state = "teleport" - -/obj/screen/ghost/teleport/Click() - var/mob/dead/observer/G = usr - G.dead_tele() - -/obj/screen/ghost/pai - name = "pAI Candidate" - icon_state = "pai" - -/obj/screen/ghost/pai/Click() - var/mob/dead/observer/G = usr - G.register_pai() - -/obj/screen/ghost/mafia - name = "Mafia Signup" - icon_state = "mafia" - -/obj/screen/ghost/mafia/Click() - var/mob/dead/observer/G = usr - G.mafia_signup() - -/datum/hud/ghost/New(mob/owner) - ..() - var/obj/screen/using - - using = new /obj/screen/ghost/jumptomob() - using.screen_loc = ui_ghost_jumptomob - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/orbit() - using.screen_loc = ui_ghost_orbit - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/reenter_corpse() - using.screen_loc = ui_ghost_reenter_corpse - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/teleport() - using.screen_loc = ui_ghost_teleport - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/pai() - using.screen_loc = ui_ghost_pai - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/mafia() - using.screen_loc = ui_ghost_mafia - using.hud = src - static_inventory += using - - using = new /obj/screen/language_menu - using.icon = ui_style - using.hud = src - static_inventory += using - -/datum/hud/ghost/show_hud(version = 0, mob/viewmob) - // don't show this HUD if observing; show the HUD of the observee - var/mob/dead/observer/O = mymob - if (istype(O) && O.observetarget) - plane_masters_update() - return FALSE - - . = ..() - if(!.) - return - var/mob/screenmob = viewmob || mymob - if(!screenmob.client.prefs.ghost_hud) - screenmob.client.screen -= static_inventory - else - screenmob.client.screen += static_inventory - -//We should only see observed mob alerts. -/datum/hud/ghost/reorganize_alerts(mob/viewmob) - var/mob/dead/observer/O = mymob - if (istype(O) && O.observetarget) - return - . = ..() - +/obj/screen/ghost + icon = 'icons/mob/screen_ghost.dmi' + +/obj/screen/ghost/MouseEntered() + flick(icon_state + "_anim", src) + +/obj/screen/ghost/jumptomob + name = "Jump to mob" + icon_state = "jumptomob" + +/obj/screen/ghost/jumptomob/Click() + var/mob/dead/observer/G = usr + G.jumptomob() + +/obj/screen/ghost/orbit + name = "Orbit" + icon_state = "orbit" + +/obj/screen/ghost/orbit/Click() + var/mob/dead/observer/G = usr + G.follow() + +/obj/screen/ghost/reenter_corpse + name = "Reenter corpse" + icon_state = "reenter_corpse" + +/obj/screen/ghost/reenter_corpse/Click() + var/mob/dead/observer/G = usr + G.reenter_corpse() + +/obj/screen/ghost/teleport + name = "Teleport" + icon_state = "teleport" + +/obj/screen/ghost/teleport/Click() + var/mob/dead/observer/G = usr + G.dead_tele() + +/obj/screen/ghost/pai + name = "pAI Candidate" + icon_state = "pai" + +/obj/screen/ghost/pai/Click() + var/mob/dead/observer/G = usr + G.register_pai() + +/obj/screen/ghost/mafia + name = "Mafia Signup" + icon_state = "mafia" + +/obj/screen/ghost/mafia/Click() + var/mob/dead/observer/G = usr + G.mafia_signup() + +/datum/hud/ghost/New(mob/owner) + ..() + var/obj/screen/using + + using = new /obj/screen/ghost/jumptomob() + using.screen_loc = ui_ghost_jumptomob + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/orbit() + using.screen_loc = ui_ghost_orbit + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/reenter_corpse() + using.screen_loc = ui_ghost_reenter_corpse + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/teleport() + using.screen_loc = ui_ghost_teleport + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/pai() + using.screen_loc = ui_ghost_pai + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/mafia() + using.screen_loc = ui_ghost_mafia + using.hud = src + static_inventory += using + + using = new /obj/screen/language_menu + using.icon = ui_style + using.hud = src + static_inventory += using + +/datum/hud/ghost/show_hud(version = 0, mob/viewmob) + // don't show this HUD if observing; show the HUD of the observee + var/mob/dead/observer/O = mymob + if (istype(O) && O.observetarget) + plane_masters_update() + return FALSE + + . = ..() + if(!.) + return + var/mob/screenmob = viewmob || mymob + if(!screenmob.client.prefs.ghost_hud) + screenmob.client.screen -= static_inventory + else + screenmob.client.screen += static_inventory + +//We should only see observed mob alerts. +/datum/hud/ghost/reorganize_alerts(mob/viewmob) + var/mob/dead/observer/O = mymob + if (istype(O) && O.observetarget) + return + . = ..() + diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index cecb4fe6c0d..6e58f795566 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -1,296 +1,296 @@ -/* - The hud datum - Used to show and hide huds for all the different mob types, - including inventories and item quick actions. -*/ - -// The default UI style is the first one in the list -GLOBAL_LIST_INIT(available_ui_styles, list( - "Midnight" = 'icons/mob/screen_midnight.dmi', - "Retro" = 'icons/mob/screen_retro.dmi', - "Plasmafire" = 'icons/mob/screen_plasmafire.dmi', - "Slimecore" = 'icons/mob/screen_slimecore.dmi', - "Operative" = 'icons/mob/screen_operative.dmi', - "Clockwork" = 'icons/mob/screen_clockwork.dmi' -)) - -/proc/ui_style2icon(ui_style) - return GLOB.available_ui_styles[ui_style] || GLOB.available_ui_styles[GLOB.available_ui_styles[1]] - -/datum/hud - var/mob/mymob - - var/hud_shown = TRUE //Used for the HUD toggle (F12) - var/hud_version = HUD_STYLE_STANDARD //Current displayed version of the HUD - var/inventory_shown = FALSE //Equipped item inventory - var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons) - - var/obj/screen/ling/chems/lingchemdisplay - var/obj/screen/ling/sting/lingstingdisplay - - var/obj/screen/blobpwrdisplay - - var/obj/screen/alien_plasma_display - var/obj/screen/alien_queen_finder - - var/obj/screen/devil/soul_counter/devilsouldisplay - - var/obj/screen/action_intent - var/obj/screen/zone_select - var/obj/screen/pull_icon - var/obj/screen/rest_icon - var/obj/screen/throw_icon - var/obj/screen/module_store_icon - - var/list/static_inventory = list() //the screen objects which are static - var/list/toggleable_inventory = list() //the screen objects which can be hidden - var/list/obj/screen/hotkeybuttons = list() //the buttons that can be used via hotkeys - var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...) - var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...) - var/list/inv_slots[SLOTS_AMT] // /obj/screen/inventory objects, ordered by their slot ID. - var/list/hand_slots // /obj/screen/inventory/hand objects, assoc list of "[held_index]" = object - var/list/obj/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object - - var/obj/screen/movable/action_button/hide_toggle/hide_actions_toggle - var/action_buttons_hidden = FALSE - - var/obj/screen/healths - var/obj/screen/healthdoll - var/obj/screen/internals - var/obj/screen/wanted/wanted_lvl - var/obj/screen/spacesuit - // subtypes can override this to force a specific UI style - var/ui_style - -/datum/hud/New(mob/owner) - mymob = owner - - if (!ui_style) - // will fall back to the default if any of these are null - ui_style = ui_style2icon(owner.client && owner.client.prefs && owner.client.prefs.UI_style) - - hide_actions_toggle = new - hide_actions_toggle.InitialiseIcon(src) - if(mymob.client) - hide_actions_toggle.locked = mymob.client.prefs.buttons_locked - - hand_slots = list() - - for(var/mytype in subtypesof(/obj/screen/plane_master)) - var/obj/screen/plane_master/instance = new mytype() - plane_masters["[instance.plane]"] = instance - instance.backdrop(mymob) - - owner.overlay_fullscreen("see_through_darkness", /obj/screen/fullscreen/see_through_darkness) - -/datum/hud/Destroy() - if(mymob.hud_used == src) - mymob.hud_used = null - - QDEL_NULL(hide_actions_toggle) - QDEL_NULL(module_store_icon) - QDEL_LIST(static_inventory) - - inv_slots.Cut() - action_intent = null - zone_select = null - pull_icon = null - - QDEL_LIST(toggleable_inventory) - QDEL_LIST(hotkeybuttons) - throw_icon = null - QDEL_LIST(infodisplay) - - healths = null - healthdoll = null - wanted_lvl = null - internals = null - spacesuit = null - lingchemdisplay = null - devilsouldisplay = null - lingstingdisplay = null - blobpwrdisplay = null - alien_plasma_display = null - alien_queen_finder = null - - QDEL_LIST_ASSOC_VAL(plane_masters) - QDEL_LIST(screenoverlays) - mymob = null - - return ..() - -/mob/proc/create_mob_hud() - if(!client || hud_used) - return - hud_used = new hud_type(src) - update_sight() - SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED) - -//Version denotes which style should be displayed. blank or 0 means "next version" -/datum/hud/proc/show_hud(version = 0, mob/viewmob) - if(!ismob(mymob)) - return FALSE - var/mob/screenmob = viewmob || mymob - if(!screenmob.client) - return FALSE - - screenmob.client.screen = list() - screenmob.client.apply_clickcatcher() - - var/display_hud_version = version - if(!display_hud_version) //If 0 or blank, display the next hud version - display_hud_version = hud_version + 1 - if(display_hud_version > HUD_VERSIONS) //If the requested version number is greater than the available versions, reset back to the first version - display_hud_version = 1 - - switch(display_hud_version) - if(HUD_STYLE_STANDARD) //Default HUD - hud_shown = TRUE //Governs behavior of other procs - if(static_inventory.len) - screenmob.client.screen += static_inventory - if(toggleable_inventory.len && screenmob.hud_used && screenmob.hud_used.inventory_shown) - screenmob.client.screen += toggleable_inventory - if(hotkeybuttons.len && !hotkey_ui_hidden) - screenmob.client.screen += hotkeybuttons - if(infodisplay.len) - screenmob.client.screen += infodisplay - - screenmob.client.screen += hide_actions_toggle - - if(action_intent) - action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position - - if(HUD_STYLE_REDUCED) //Reduced HUD - hud_shown = FALSE //Governs behavior of other procs - if(static_inventory.len) - screenmob.client.screen -= static_inventory - if(toggleable_inventory.len) - screenmob.client.screen -= toggleable_inventory - if(hotkeybuttons.len) - screenmob.client.screen -= hotkeybuttons - if(infodisplay.len) - screenmob.client.screen += infodisplay - - //These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay - for(var/h in hand_slots) - var/obj/screen/hand = hand_slots[h] - if(hand) - screenmob.client.screen += hand - if(action_intent) - screenmob.client.screen += action_intent //we want the intent switcher visible - action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is. - - if(HUD_STYLE_NOHUD) //No HUD - hud_shown = FALSE //Governs behavior of other procs - if(static_inventory.len) - screenmob.client.screen -= static_inventory - if(toggleable_inventory.len) - screenmob.client.screen -= toggleable_inventory - if(hotkeybuttons.len) - screenmob.client.screen -= hotkeybuttons - if(infodisplay.len) - screenmob.client.screen -= infodisplay - - hud_version = display_hud_version - persistent_inventory_update(screenmob) - screenmob.update_action_buttons(1) - reorganize_alerts(screenmob) - screenmob.reload_fullscreen() - update_parallax_pref(screenmob) - - // ensure observers get an accurate and up-to-date view - if (!viewmob) - plane_masters_update() - for(var/M in mymob.observers) - show_hud(hud_version, M) - else if (viewmob.hud_used) - viewmob.hud_used.plane_masters_update() - - return TRUE - -/datum/hud/proc/plane_masters_update() - // Plane masters are always shown to OUR mob, never to observers - for(var/thing in plane_masters) - var/obj/screen/plane_master/PM = plane_masters[thing] - PM.backdrop(mymob) - mymob.client.screen += PM - -/datum/hud/human/show_hud(version = 0,mob/viewmob) - . = ..() - if(!.) - return - var/mob/screenmob = viewmob || mymob - hidden_inventory_update(screenmob) - -/datum/hud/robot/show_hud(version = 0, mob/viewmob) - . = ..() - if(!.) - return - update_robot_modules_display() - -/datum/hud/proc/hidden_inventory_update() - return - -/datum/hud/proc/persistent_inventory_update(mob/viewer) - if(!mymob) - return - -/datum/hud/proc/update_ui_style(new_ui_style) - // do nothing if overridden by a subtype or already on that style - if (initial(ui_style) || ui_style == new_ui_style) - return - - for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + screenoverlays + inv_slots) - if (item.icon == ui_style) - item.icon = new_ui_style - - ui_style = new_ui_style - build_hand_slots() - hide_actions_toggle.InitialiseIcon(src) - -//Triggered when F12 is pressed (Unless someone changed something in the DMF) -/mob/verb/button_pressed_F12() - set name = "F12" - set hidden = TRUE - - if(hud_used && client) - hud_used.show_hud() //Shows the next hud preset - to_chat(usr, "Switched HUD mode. Press F12 to toggle.") - else - to_chat(usr, "This mob type does not use a HUD.") - - -//(re)builds the hand ui slots, throwing away old ones -//not really worth jugglying existing ones so we just scrap+rebuild -//9/10 this is only called once per mob and only for 2 hands -/datum/hud/proc/build_hand_slots() - for(var/h in hand_slots) - var/obj/screen/inventory/hand/H = hand_slots[h] - if(H) - static_inventory -= H - hand_slots = list() - var/obj/screen/inventory/hand/hand_box - for(var/i in 1 to mymob.held_items.len) - hand_box = new /obj/screen/inventory/hand() - hand_box.name = mymob.get_held_index_name(i) - hand_box.icon = ui_style - hand_box.icon_state = "hand_[mymob.held_index_to_dir(i)]" - hand_box.screen_loc = ui_hand_position(i) - hand_box.held_index = i - hand_slots["[i]"] = hand_box - hand_box.hud = src - static_inventory += hand_box - hand_box.update_icon() - - var/i = 1 - for(var/obj/screen/swap_hand/SH in static_inventory) - SH.screen_loc = ui_swaphand_position(mymob,!(i % 2) ? 2: 1) - i++ - for(var/obj/screen/human/equip/E in static_inventory) - E.screen_loc = ui_equip_position(mymob) - - if(ismob(mymob) && mymob.hud_used == src) - show_hud(hud_version) - -/datum/hud/proc/update_locked_slots() - return +/* + The hud datum + Used to show and hide huds for all the different mob types, + including inventories and item quick actions. +*/ + +// The default UI style is the first one in the list +GLOBAL_LIST_INIT(available_ui_styles, list( + "Midnight" = 'icons/mob/screen_midnight.dmi', + "Retro" = 'icons/mob/screen_retro.dmi', + "Plasmafire" = 'icons/mob/screen_plasmafire.dmi', + "Slimecore" = 'icons/mob/screen_slimecore.dmi', + "Operative" = 'icons/mob/screen_operative.dmi', + "Clockwork" = 'icons/mob/screen_clockwork.dmi' +)) + +/proc/ui_style2icon(ui_style) + return GLOB.available_ui_styles[ui_style] || GLOB.available_ui_styles[GLOB.available_ui_styles[1]] + +/datum/hud + var/mob/mymob + + var/hud_shown = TRUE //Used for the HUD toggle (F12) + var/hud_version = HUD_STYLE_STANDARD //Current displayed version of the HUD + var/inventory_shown = FALSE //Equipped item inventory + var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons) + + var/obj/screen/ling/chems/lingchemdisplay + var/obj/screen/ling/sting/lingstingdisplay + + var/obj/screen/blobpwrdisplay + + var/obj/screen/alien_plasma_display + var/obj/screen/alien_queen_finder + + var/obj/screen/devil/soul_counter/devilsouldisplay + + var/obj/screen/action_intent + var/obj/screen/zone_select + var/obj/screen/pull_icon + var/obj/screen/rest_icon + var/obj/screen/throw_icon + var/obj/screen/module_store_icon + + var/list/static_inventory = list() //the screen objects which are static + var/list/toggleable_inventory = list() //the screen objects which can be hidden + var/list/obj/screen/hotkeybuttons = list() //the buttons that can be used via hotkeys + var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...) + var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...) + var/list/inv_slots[SLOTS_AMT] // /obj/screen/inventory objects, ordered by their slot ID. + var/list/hand_slots // /obj/screen/inventory/hand objects, assoc list of "[held_index]" = object + var/list/obj/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object + + var/obj/screen/movable/action_button/hide_toggle/hide_actions_toggle + var/action_buttons_hidden = FALSE + + var/obj/screen/healths + var/obj/screen/healthdoll + var/obj/screen/internals + var/obj/screen/wanted/wanted_lvl + var/obj/screen/spacesuit + // subtypes can override this to force a specific UI style + var/ui_style + +/datum/hud/New(mob/owner) + mymob = owner + + if (!ui_style) + // will fall back to the default if any of these are null + ui_style = ui_style2icon(owner.client && owner.client.prefs && owner.client.prefs.UI_style) + + hide_actions_toggle = new + hide_actions_toggle.InitialiseIcon(src) + if(mymob.client) + hide_actions_toggle.locked = mymob.client.prefs.buttons_locked + + hand_slots = list() + + for(var/mytype in subtypesof(/obj/screen/plane_master)) + var/obj/screen/plane_master/instance = new mytype() + plane_masters["[instance.plane]"] = instance + instance.backdrop(mymob) + + owner.overlay_fullscreen("see_through_darkness", /obj/screen/fullscreen/see_through_darkness) + +/datum/hud/Destroy() + if(mymob.hud_used == src) + mymob.hud_used = null + + QDEL_NULL(hide_actions_toggle) + QDEL_NULL(module_store_icon) + QDEL_LIST(static_inventory) + + inv_slots.Cut() + action_intent = null + zone_select = null + pull_icon = null + + QDEL_LIST(toggleable_inventory) + QDEL_LIST(hotkeybuttons) + throw_icon = null + QDEL_LIST(infodisplay) + + healths = null + healthdoll = null + wanted_lvl = null + internals = null + spacesuit = null + lingchemdisplay = null + devilsouldisplay = null + lingstingdisplay = null + blobpwrdisplay = null + alien_plasma_display = null + alien_queen_finder = null + + QDEL_LIST_ASSOC_VAL(plane_masters) + QDEL_LIST(screenoverlays) + mymob = null + + return ..() + +/mob/proc/create_mob_hud() + if(!client || hud_used) + return + hud_used = new hud_type(src) + update_sight() + SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED) + +//Version denotes which style should be displayed. blank or 0 means "next version" +/datum/hud/proc/show_hud(version = 0, mob/viewmob) + if(!ismob(mymob)) + return FALSE + var/mob/screenmob = viewmob || mymob + if(!screenmob.client) + return FALSE + + screenmob.client.screen = list() + screenmob.client.apply_clickcatcher() + + var/display_hud_version = version + if(!display_hud_version) //If 0 or blank, display the next hud version + display_hud_version = hud_version + 1 + if(display_hud_version > HUD_VERSIONS) //If the requested version number is greater than the available versions, reset back to the first version + display_hud_version = 1 + + switch(display_hud_version) + if(HUD_STYLE_STANDARD) //Default HUD + hud_shown = TRUE //Governs behavior of other procs + if(static_inventory.len) + screenmob.client.screen += static_inventory + if(toggleable_inventory.len && screenmob.hud_used && screenmob.hud_used.inventory_shown) + screenmob.client.screen += toggleable_inventory + if(hotkeybuttons.len && !hotkey_ui_hidden) + screenmob.client.screen += hotkeybuttons + if(infodisplay.len) + screenmob.client.screen += infodisplay + + screenmob.client.screen += hide_actions_toggle + + if(action_intent) + action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position + + if(HUD_STYLE_REDUCED) //Reduced HUD + hud_shown = FALSE //Governs behavior of other procs + if(static_inventory.len) + screenmob.client.screen -= static_inventory + if(toggleable_inventory.len) + screenmob.client.screen -= toggleable_inventory + if(hotkeybuttons.len) + screenmob.client.screen -= hotkeybuttons + if(infodisplay.len) + screenmob.client.screen += infodisplay + + //These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay + for(var/h in hand_slots) + var/obj/screen/hand = hand_slots[h] + if(hand) + screenmob.client.screen += hand + if(action_intent) + screenmob.client.screen += action_intent //we want the intent switcher visible + action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is. + + if(HUD_STYLE_NOHUD) //No HUD + hud_shown = FALSE //Governs behavior of other procs + if(static_inventory.len) + screenmob.client.screen -= static_inventory + if(toggleable_inventory.len) + screenmob.client.screen -= toggleable_inventory + if(hotkeybuttons.len) + screenmob.client.screen -= hotkeybuttons + if(infodisplay.len) + screenmob.client.screen -= infodisplay + + hud_version = display_hud_version + persistent_inventory_update(screenmob) + screenmob.update_action_buttons(1) + reorganize_alerts(screenmob) + screenmob.reload_fullscreen() + update_parallax_pref(screenmob) + + // ensure observers get an accurate and up-to-date view + if (!viewmob) + plane_masters_update() + for(var/M in mymob.observers) + show_hud(hud_version, M) + else if (viewmob.hud_used) + viewmob.hud_used.plane_masters_update() + + return TRUE + +/datum/hud/proc/plane_masters_update() + // Plane masters are always shown to OUR mob, never to observers + for(var/thing in plane_masters) + var/obj/screen/plane_master/PM = plane_masters[thing] + PM.backdrop(mymob) + mymob.client.screen += PM + +/datum/hud/human/show_hud(version = 0,mob/viewmob) + . = ..() + if(!.) + return + var/mob/screenmob = viewmob || mymob + hidden_inventory_update(screenmob) + +/datum/hud/robot/show_hud(version = 0, mob/viewmob) + . = ..() + if(!.) + return + update_robot_modules_display() + +/datum/hud/proc/hidden_inventory_update() + return + +/datum/hud/proc/persistent_inventory_update(mob/viewer) + if(!mymob) + return + +/datum/hud/proc/update_ui_style(new_ui_style) + // do nothing if overridden by a subtype or already on that style + if (initial(ui_style) || ui_style == new_ui_style) + return + + for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + screenoverlays + inv_slots) + if (item.icon == ui_style) + item.icon = new_ui_style + + ui_style = new_ui_style + build_hand_slots() + hide_actions_toggle.InitialiseIcon(src) + +//Triggered when F12 is pressed (Unless someone changed something in the DMF) +/mob/verb/button_pressed_F12() + set name = "F12" + set hidden = TRUE + + if(hud_used && client) + hud_used.show_hud() //Shows the next hud preset + to_chat(usr, "Switched HUD mode. Press F12 to toggle.") + else + to_chat(usr, "This mob type does not use a HUD.") + + +//(re)builds the hand ui slots, throwing away old ones +//not really worth jugglying existing ones so we just scrap+rebuild +//9/10 this is only called once per mob and only for 2 hands +/datum/hud/proc/build_hand_slots() + for(var/h in hand_slots) + var/obj/screen/inventory/hand/H = hand_slots[h] + if(H) + static_inventory -= H + hand_slots = list() + var/obj/screen/inventory/hand/hand_box + for(var/i in 1 to mymob.held_items.len) + hand_box = new /obj/screen/inventory/hand() + hand_box.name = mymob.get_held_index_name(i) + hand_box.icon = ui_style + hand_box.icon_state = "hand_[mymob.held_index_to_dir(i)]" + hand_box.screen_loc = ui_hand_position(i) + hand_box.held_index = i + hand_slots["[i]"] = hand_box + hand_box.hud = src + static_inventory += hand_box + hand_box.update_icon() + + var/i = 1 + for(var/obj/screen/swap_hand/SH in static_inventory) + SH.screen_loc = ui_swaphand_position(mymob,!(i % 2) ? 2: 1) + i++ + for(var/obj/screen/human/equip/E in static_inventory) + E.screen_loc = ui_equip_position(mymob) + + if(ismob(mymob) && mymob.hud_used == src) + show_hud(hud_version) + +/datum/hud/proc/update_locked_slots() + return diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index d063a79d3c7..5570b64d541 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -1,487 +1,487 @@ -/obj/screen/human - icon = 'icons/mob/screen_midnight.dmi' - -/obj/screen/human/toggle - name = "toggle" - icon_state = "toggle" - -/obj/screen/human/toggle/Click() - - var/mob/targetmob = usr - - if(isobserver(usr)) - if(ishuman(usr.client.eye) && (usr.client.eye != usr)) - var/mob/M = usr.client.eye - targetmob = M - - if(usr.hud_used.inventory_shown && targetmob.hud_used) - usr.hud_used.inventory_shown = FALSE - usr.client.screen -= targetmob.hud_used.toggleable_inventory - else - usr.hud_used.inventory_shown = TRUE - usr.client.screen += targetmob.hud_used.toggleable_inventory - - targetmob.hud_used.hidden_inventory_update(usr) - -/obj/screen/human/equip - name = "equip" - icon_state = "act_equip" - -/obj/screen/human/equip/Click() - if(ismecha(usr.loc)) // stops inventory actions in a mech - return 1 - var/mob/living/carbon/human/H = usr - H.quick_equip() - -/obj/screen/devil - icon = 'icons/mob/screen_devil.dmi' - invisibility = INVISIBILITY_ABSTRACT - -/obj/screen/devil/soul_counter - name = "souls owned" - icon_state = "Devil-6" - screen_loc = ui_devilsouldisplay - -/obj/screen/devil/soul_counter/proc/update_counter(souls = 0) - invisibility = 0 - maptext = "
    [souls]
    " - switch(souls) - if(0,null) - icon_state = "Devil-1" - if(1,2) - icon_state = "Devil-2" - if(3 to 5) - icon_state = "Devil-3" - if(6 to 8) - icon_state = "Devil-4" - if(9 to INFINITY) - icon_state = "Devil-5" - else - icon_state = "Devil-6" - -/obj/screen/devil/soul_counter/proc/clear() - invisibility = INVISIBILITY_ABSTRACT - -/obj/screen/ling - icon = 'icons/mob/screen_changeling.dmi' - invisibility = INVISIBILITY_ABSTRACT - -/obj/screen/ling/sting - name = "current sting" - screen_loc = ui_lingstingdisplay - -/obj/screen/ling/sting/Click() - if(isobserver(usr)) - return - var/mob/living/carbon/U = usr - U.unset_sting() - -/obj/screen/ling/chems - name = "chemical storage" - icon_state = "power_display" - screen_loc = ui_lingchemdisplay - -/datum/hud/human/New(mob/living/carbon/human/owner) - ..() - - var/widescreen_layout = FALSE - if(owner.client?.prefs?.widescreenpref) - widescreen_layout = TRUE - - var/obj/screen/using - var/obj/screen/inventory/inv_box - - using = new/obj/screen/language_menu - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXLANG - using.hud = src - static_inventory += using - - using = new/obj/screen/skills - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXLANG - static_inventory += using - - using = new /obj/screen/area_creator - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXAREA - using.hud = src - static_inventory += using - - action_intent = new /obj/screen/act_intent/segmented - action_intent.icon_state = mymob.a_intent - action_intent.hud = src - static_inventory += action_intent - - using = new /obj/screen/mov_intent - using.icon = ui_style - using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") - using.screen_loc = ui_movi - using.hud = src - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "i_clothing" - inv_box.icon = ui_style - inv_box.slot_id = ITEM_SLOT_ICLOTHING - inv_box.icon_state = "uniform" - inv_box.screen_loc = ui_iclothing - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "o_clothing" - inv_box.icon = ui_style - inv_box.slot_id = ITEM_SLOT_OCLOTHING - inv_box.icon_state = "suit" - inv_box.screen_loc = ui_oclothing - inv_box.hud = src - toggleable_inventory += inv_box - - build_hand_slots() - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_1" - using.screen_loc = ui_swaphand_position(owner,1) - using.hud = src - static_inventory += using - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "id" - inv_box.icon = ui_style - inv_box.icon_state = "id" - inv_box.screen_loc = ui_id - inv_box.slot_id = ITEM_SLOT_ID - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "mask" - inv_box.icon = ui_style - inv_box.icon_state = "mask" - inv_box.screen_loc = ui_mask - inv_box.slot_id = ITEM_SLOT_MASK - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "neck" - inv_box.icon = ui_style - inv_box.icon_state = "neck" - inv_box.screen_loc = ui_neck - inv_box.slot_id = ITEM_SLOT_NECK - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "back" - inv_box.icon = ui_style - inv_box.icon_state = "back" - inv_box.screen_loc = ui_back - inv_box.slot_id = ITEM_SLOT_BACK - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "storage1" - inv_box.icon = ui_style - inv_box.icon_state = "pocket" - inv_box.screen_loc = ui_storage1 - inv_box.slot_id = ITEM_SLOT_LPOCKET - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "storage2" - inv_box.icon = ui_style - inv_box.icon_state = "pocket" - inv_box.screen_loc = ui_storage2 - inv_box.slot_id = ITEM_SLOT_RPOCKET - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "suit storage" - inv_box.icon = ui_style - inv_box.icon_state = "suit_storage" - inv_box.screen_loc = ui_sstore1 - inv_box.slot_id = ITEM_SLOT_SUITSTORE - inv_box.hud = src - static_inventory += inv_box - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_intent - using.hud = src - hotkeybuttons += using - - using = new /obj/screen/human/toggle() - using.icon = ui_style - using.screen_loc = ui_inventory - using.hud = src - static_inventory += using - - using = new /obj/screen/human/equip() - using.icon = ui_style - using.screen_loc = ui_equip_position(mymob) - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "gloves" - inv_box.icon = ui_style - inv_box.icon_state = "gloves" - inv_box.screen_loc = ui_gloves - inv_box.slot_id = ITEM_SLOT_GLOVES - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "eyes" - inv_box.icon = ui_style - inv_box.icon_state = "glasses" - inv_box.screen_loc = ui_glasses - inv_box.slot_id = ITEM_SLOT_EYES - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "ears" - inv_box.icon = ui_style - inv_box.icon_state = "ears" - inv_box.screen_loc = ui_ears - inv_box.slot_id = ITEM_SLOT_EARS - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "head" - inv_box.icon = ui_style - inv_box.icon_state = "head" - inv_box.screen_loc = ui_head - inv_box.slot_id = ITEM_SLOT_HEAD - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "shoes" - inv_box.icon = ui_style - inv_box.icon_state = "shoes" - inv_box.screen_loc = ui_shoes - inv_box.slot_id = ITEM_SLOT_FEET - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "belt" - inv_box.icon = ui_style - inv_box.icon_state = "belt" -// inv_box.icon_full = "template_small" - inv_box.screen_loc = ui_belt - inv_box.slot_id = ITEM_SLOT_BELT - inv_box.hud = src - static_inventory += inv_box - - throw_icon = new /obj/screen/throw_catch() - throw_icon.icon = ui_style - throw_icon.screen_loc = ui_drop_throw - throw_icon.hud = src - hotkeybuttons += throw_icon - - rest_icon = new /obj/screen/rest() - rest_icon.icon = ui_style - rest_icon.screen_loc = ui_above_movement - rest_icon.hud = src - static_inventory += rest_icon - - internals = new /obj/screen/internals() - internals.hud = src - infodisplay += internals - - spacesuit = new /obj/screen/spacesuit - spacesuit.hud = src - infodisplay += spacesuit - - healths = new /obj/screen/healths() - healths.hud = src - infodisplay += healths - - healthdoll = new /obj/screen/healthdoll() - healthdoll.hud = src - infodisplay += healthdoll - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_intent - pull_icon.hud = src - static_inventory += pull_icon - - lingchemdisplay = new /obj/screen/ling/chems() - lingchemdisplay.hud = src - infodisplay += lingchemdisplay - - lingstingdisplay = new /obj/screen/ling/sting() - lingstingdisplay.hud = src - infodisplay += lingstingdisplay - - devilsouldisplay = new /obj/screen/devil/soul_counter - devilsouldisplay.hud = src - infodisplay += devilsouldisplay - - zone_select = new /obj/screen/zone_sel() - zone_select.icon = ui_style - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - inv.hud = src - inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv - inv.update_icon() - - update_locked_slots() - -/datum/hud/human/update_locked_slots() - if(!mymob) - return - var/mob/living/carbon/human/H = mymob - if(!istype(H) || !H.dna.species) - return - var/datum/species/S = H.dna.species - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - if(inv.slot_id in S.no_equip) - inv.alpha = 128 - else - inv.alpha = initial(inv.alpha) - -/datum/hud/human/hidden_inventory_update(mob/viewer) - if(!mymob) - return - var/mob/living/carbon/human/H = mymob - - var/mob/screenmob = viewer || H - - if(screenmob.hud_used.inventory_shown && screenmob.hud_used.hud_shown) - if(H.shoes) - H.shoes.screen_loc = ui_shoes - screenmob.client.screen += H.shoes - if(H.gloves) - H.gloves.screen_loc = ui_gloves - screenmob.client.screen += H.gloves - if(H.ears) - H.ears.screen_loc = ui_ears - screenmob.client.screen += H.ears - if(H.glasses) - H.glasses.screen_loc = ui_glasses - screenmob.client.screen += H.glasses - if(H.w_uniform) - H.w_uniform.screen_loc = ui_iclothing - screenmob.client.screen += H.w_uniform - if(H.wear_suit) - H.wear_suit.screen_loc = ui_oclothing - screenmob.client.screen += H.wear_suit - if(H.wear_mask) - H.wear_mask.screen_loc = ui_mask - screenmob.client.screen += H.wear_mask - if(H.wear_neck) - H.wear_neck.screen_loc = ui_neck - screenmob.client.screen += H.wear_neck - if(H.head) - H.head.screen_loc = ui_head - screenmob.client.screen += H.head - else - if(H.shoes) screenmob.client.screen -= H.shoes - if(H.gloves) screenmob.client.screen -= H.gloves - if(H.ears) screenmob.client.screen -= H.ears - if(H.glasses) screenmob.client.screen -= H.glasses - if(H.w_uniform) screenmob.client.screen -= H.w_uniform - if(H.wear_suit) screenmob.client.screen -= H.wear_suit - if(H.wear_mask) screenmob.client.screen -= H.wear_mask - if(H.wear_neck) screenmob.client.screen -= H.wear_neck - if(H.head) screenmob.client.screen -= H.head - - - -/datum/hud/human/persistent_inventory_update(mob/viewer) - if(!mymob) - return - ..() - var/mob/living/carbon/human/H = mymob - - var/mob/screenmob = viewer || H - - if(screenmob.hud_used) - if(screenmob.hud_used.hud_shown) - if(H.s_store) - H.s_store.screen_loc = ui_sstore1 - screenmob.client.screen += H.s_store - if(H.wear_id) - H.wear_id.screen_loc = ui_id - screenmob.client.screen += H.wear_id - if(H.belt) - H.belt.screen_loc = ui_belt - screenmob.client.screen += H.belt - if(H.back) - H.back.screen_loc = ui_back - screenmob.client.screen += H.back - if(H.l_store) - H.l_store.screen_loc = ui_storage1 - screenmob.client.screen += H.l_store - if(H.r_store) - H.r_store.screen_loc = ui_storage2 - screenmob.client.screen += H.r_store - else - if(H.s_store) - screenmob.client.screen -= H.s_store - if(H.wear_id) - screenmob.client.screen -= H.wear_id - if(H.belt) - screenmob.client.screen -= H.belt - if(H.back) - screenmob.client.screen -= H.back - if(H.l_store) - screenmob.client.screen -= H.l_store - if(H.r_store) - screenmob.client.screen -= H.r_store - - if(hud_version != HUD_STYLE_NOHUD) - for(var/obj/item/I in H.held_items) - I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) - screenmob.client.screen += I - else - for(var/obj/item/I in H.held_items) - I.screen_loc = null - screenmob.client.screen -= I - - -/mob/living/carbon/human/verb/toggle_hotkey_verbs() - set category = "OOC" - set name = "Toggle hotkey buttons" - set desc = "This disables or enables the user interface buttons which can be used with hotkeys." - - if(hud_used.hotkey_ui_hidden) - client.screen += hud_used.hotkeybuttons - hud_used.hotkey_ui_hidden = FALSE - else - client.screen -= hud_used.hotkeybuttons - hud_used.hotkey_ui_hidden = TRUE +/obj/screen/human + icon = 'icons/mob/screen_midnight.dmi' + +/obj/screen/human/toggle + name = "toggle" + icon_state = "toggle" + +/obj/screen/human/toggle/Click() + + var/mob/targetmob = usr + + if(isobserver(usr)) + if(ishuman(usr.client.eye) && (usr.client.eye != usr)) + var/mob/M = usr.client.eye + targetmob = M + + if(usr.hud_used.inventory_shown && targetmob.hud_used) + usr.hud_used.inventory_shown = FALSE + usr.client.screen -= targetmob.hud_used.toggleable_inventory + else + usr.hud_used.inventory_shown = TRUE + usr.client.screen += targetmob.hud_used.toggleable_inventory + + targetmob.hud_used.hidden_inventory_update(usr) + +/obj/screen/human/equip + name = "equip" + icon_state = "act_equip" + +/obj/screen/human/equip/Click() + if(ismecha(usr.loc)) // stops inventory actions in a mech + return 1 + var/mob/living/carbon/human/H = usr + H.quick_equip() + +/obj/screen/devil + icon = 'icons/mob/screen_devil.dmi' + invisibility = INVISIBILITY_ABSTRACT + +/obj/screen/devil/soul_counter + name = "souls owned" + icon_state = "Devil-6" + screen_loc = ui_devilsouldisplay + +/obj/screen/devil/soul_counter/proc/update_counter(souls = 0) + invisibility = 0 + maptext = "
    [souls]
    " + switch(souls) + if(0,null) + icon_state = "Devil-1" + if(1,2) + icon_state = "Devil-2" + if(3 to 5) + icon_state = "Devil-3" + if(6 to 8) + icon_state = "Devil-4" + if(9 to INFINITY) + icon_state = "Devil-5" + else + icon_state = "Devil-6" + +/obj/screen/devil/soul_counter/proc/clear() + invisibility = INVISIBILITY_ABSTRACT + +/obj/screen/ling + icon = 'icons/mob/screen_changeling.dmi' + invisibility = INVISIBILITY_ABSTRACT + +/obj/screen/ling/sting + name = "current sting" + screen_loc = ui_lingstingdisplay + +/obj/screen/ling/sting/Click() + if(isobserver(usr)) + return + var/mob/living/carbon/U = usr + U.unset_sting() + +/obj/screen/ling/chems + name = "chemical storage" + icon_state = "power_display" + screen_loc = ui_lingchemdisplay + +/datum/hud/human/New(mob/living/carbon/human/owner) + ..() + + var/widescreen_layout = FALSE + if(owner.client?.prefs?.widescreenpref) + widescreen_layout = TRUE + + var/obj/screen/using + var/obj/screen/inventory/inv_box + + using = new/obj/screen/language_menu + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXLANG + using.hud = src + static_inventory += using + + using = new/obj/screen/skills + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXLANG + static_inventory += using + + using = new /obj/screen/area_creator + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXAREA + using.hud = src + static_inventory += using + + action_intent = new /obj/screen/act_intent/segmented + action_intent.icon_state = mymob.a_intent + action_intent.hud = src + static_inventory += action_intent + + using = new /obj/screen/mov_intent + using.icon = ui_style + using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") + using.screen_loc = ui_movi + using.hud = src + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "i_clothing" + inv_box.icon = ui_style + inv_box.slot_id = ITEM_SLOT_ICLOTHING + inv_box.icon_state = "uniform" + inv_box.screen_loc = ui_iclothing + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "o_clothing" + inv_box.icon = ui_style + inv_box.slot_id = ITEM_SLOT_OCLOTHING + inv_box.icon_state = "suit" + inv_box.screen_loc = ui_oclothing + inv_box.hud = src + toggleable_inventory += inv_box + + build_hand_slots() + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_1" + using.screen_loc = ui_swaphand_position(owner,1) + using.hud = src + static_inventory += using + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_2" + using.screen_loc = ui_swaphand_position(owner,2) + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "id" + inv_box.icon = ui_style + inv_box.icon_state = "id" + inv_box.screen_loc = ui_id + inv_box.slot_id = ITEM_SLOT_ID + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "mask" + inv_box.icon = ui_style + inv_box.icon_state = "mask" + inv_box.screen_loc = ui_mask + inv_box.slot_id = ITEM_SLOT_MASK + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "neck" + inv_box.icon = ui_style + inv_box.icon_state = "neck" + inv_box.screen_loc = ui_neck + inv_box.slot_id = ITEM_SLOT_NECK + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "back" + inv_box.icon = ui_style + inv_box.icon_state = "back" + inv_box.screen_loc = ui_back + inv_box.slot_id = ITEM_SLOT_BACK + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "storage1" + inv_box.icon = ui_style + inv_box.icon_state = "pocket" + inv_box.screen_loc = ui_storage1 + inv_box.slot_id = ITEM_SLOT_LPOCKET + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "storage2" + inv_box.icon = ui_style + inv_box.icon_state = "pocket" + inv_box.screen_loc = ui_storage2 + inv_box.slot_id = ITEM_SLOT_RPOCKET + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "suit storage" + inv_box.icon = ui_style + inv_box.icon_state = "suit_storage" + inv_box.screen_loc = ui_sstore1 + inv_box.slot_id = ITEM_SLOT_SUITSTORE + inv_box.hud = src + static_inventory += inv_box + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_intent + using.hud = src + hotkeybuttons += using + + using = new /obj/screen/human/toggle() + using.icon = ui_style + using.screen_loc = ui_inventory + using.hud = src + static_inventory += using + + using = new /obj/screen/human/equip() + using.icon = ui_style + using.screen_loc = ui_equip_position(mymob) + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "gloves" + inv_box.icon = ui_style + inv_box.icon_state = "gloves" + inv_box.screen_loc = ui_gloves + inv_box.slot_id = ITEM_SLOT_GLOVES + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "eyes" + inv_box.icon = ui_style + inv_box.icon_state = "glasses" + inv_box.screen_loc = ui_glasses + inv_box.slot_id = ITEM_SLOT_EYES + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "ears" + inv_box.icon = ui_style + inv_box.icon_state = "ears" + inv_box.screen_loc = ui_ears + inv_box.slot_id = ITEM_SLOT_EARS + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "head" + inv_box.icon = ui_style + inv_box.icon_state = "head" + inv_box.screen_loc = ui_head + inv_box.slot_id = ITEM_SLOT_HEAD + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "shoes" + inv_box.icon = ui_style + inv_box.icon_state = "shoes" + inv_box.screen_loc = ui_shoes + inv_box.slot_id = ITEM_SLOT_FEET + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "belt" + inv_box.icon = ui_style + inv_box.icon_state = "belt" +// inv_box.icon_full = "template_small" + inv_box.screen_loc = ui_belt + inv_box.slot_id = ITEM_SLOT_BELT + inv_box.hud = src + static_inventory += inv_box + + throw_icon = new /obj/screen/throw_catch() + throw_icon.icon = ui_style + throw_icon.screen_loc = ui_drop_throw + throw_icon.hud = src + hotkeybuttons += throw_icon + + rest_icon = new /obj/screen/rest() + rest_icon.icon = ui_style + rest_icon.screen_loc = ui_above_movement + rest_icon.hud = src + static_inventory += rest_icon + + internals = new /obj/screen/internals() + internals.hud = src + infodisplay += internals + + spacesuit = new /obj/screen/spacesuit + spacesuit.hud = src + infodisplay += spacesuit + + healths = new /obj/screen/healths() + healths.hud = src + infodisplay += healths + + healthdoll = new /obj/screen/healthdoll() + healthdoll.hud = src + infodisplay += healthdoll + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_intent + pull_icon.hud = src + static_inventory += pull_icon + + lingchemdisplay = new /obj/screen/ling/chems() + lingchemdisplay.hud = src + infodisplay += lingchemdisplay + + lingstingdisplay = new /obj/screen/ling/sting() + lingstingdisplay.hud = src + infodisplay += lingstingdisplay + + devilsouldisplay = new /obj/screen/devil/soul_counter + devilsouldisplay.hud = src + infodisplay += devilsouldisplay + + zone_select = new /obj/screen/zone_sel() + zone_select.icon = ui_style + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + inv.hud = src + inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv + inv.update_icon() + + update_locked_slots() + +/datum/hud/human/update_locked_slots() + if(!mymob) + return + var/mob/living/carbon/human/H = mymob + if(!istype(H) || !H.dna.species) + return + var/datum/species/S = H.dna.species + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + if(inv.slot_id in S.no_equip) + inv.alpha = 128 + else + inv.alpha = initial(inv.alpha) + +/datum/hud/human/hidden_inventory_update(mob/viewer) + if(!mymob) + return + var/mob/living/carbon/human/H = mymob + + var/mob/screenmob = viewer || H + + if(screenmob.hud_used.inventory_shown && screenmob.hud_used.hud_shown) + if(H.shoes) + H.shoes.screen_loc = ui_shoes + screenmob.client.screen += H.shoes + if(H.gloves) + H.gloves.screen_loc = ui_gloves + screenmob.client.screen += H.gloves + if(H.ears) + H.ears.screen_loc = ui_ears + screenmob.client.screen += H.ears + if(H.glasses) + H.glasses.screen_loc = ui_glasses + screenmob.client.screen += H.glasses + if(H.w_uniform) + H.w_uniform.screen_loc = ui_iclothing + screenmob.client.screen += H.w_uniform + if(H.wear_suit) + H.wear_suit.screen_loc = ui_oclothing + screenmob.client.screen += H.wear_suit + if(H.wear_mask) + H.wear_mask.screen_loc = ui_mask + screenmob.client.screen += H.wear_mask + if(H.wear_neck) + H.wear_neck.screen_loc = ui_neck + screenmob.client.screen += H.wear_neck + if(H.head) + H.head.screen_loc = ui_head + screenmob.client.screen += H.head + else + if(H.shoes) screenmob.client.screen -= H.shoes + if(H.gloves) screenmob.client.screen -= H.gloves + if(H.ears) screenmob.client.screen -= H.ears + if(H.glasses) screenmob.client.screen -= H.glasses + if(H.w_uniform) screenmob.client.screen -= H.w_uniform + if(H.wear_suit) screenmob.client.screen -= H.wear_suit + if(H.wear_mask) screenmob.client.screen -= H.wear_mask + if(H.wear_neck) screenmob.client.screen -= H.wear_neck + if(H.head) screenmob.client.screen -= H.head + + + +/datum/hud/human/persistent_inventory_update(mob/viewer) + if(!mymob) + return + ..() + var/mob/living/carbon/human/H = mymob + + var/mob/screenmob = viewer || H + + if(screenmob.hud_used) + if(screenmob.hud_used.hud_shown) + if(H.s_store) + H.s_store.screen_loc = ui_sstore1 + screenmob.client.screen += H.s_store + if(H.wear_id) + H.wear_id.screen_loc = ui_id + screenmob.client.screen += H.wear_id + if(H.belt) + H.belt.screen_loc = ui_belt + screenmob.client.screen += H.belt + if(H.back) + H.back.screen_loc = ui_back + screenmob.client.screen += H.back + if(H.l_store) + H.l_store.screen_loc = ui_storage1 + screenmob.client.screen += H.l_store + if(H.r_store) + H.r_store.screen_loc = ui_storage2 + screenmob.client.screen += H.r_store + else + if(H.s_store) + screenmob.client.screen -= H.s_store + if(H.wear_id) + screenmob.client.screen -= H.wear_id + if(H.belt) + screenmob.client.screen -= H.belt + if(H.back) + screenmob.client.screen -= H.back + if(H.l_store) + screenmob.client.screen -= H.l_store + if(H.r_store) + screenmob.client.screen -= H.r_store + + if(hud_version != HUD_STYLE_NOHUD) + for(var/obj/item/I in H.held_items) + I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) + screenmob.client.screen += I + else + for(var/obj/item/I in H.held_items) + I.screen_loc = null + screenmob.client.screen -= I + + +/mob/living/carbon/human/verb/toggle_hotkey_verbs() + set category = "OOC" + set name = "Toggle hotkey buttons" + set desc = "This disables or enables the user interface buttons which can be used with hotkeys." + + if(hud_used.hotkey_ui_hidden) + client.screen += hud_used.hotkeybuttons + hud_used.hotkey_ui_hidden = FALSE + else + client.screen -= hud_used.hotkeybuttons + hud_used.hotkey_ui_hidden = TRUE diff --git a/code/_onclick/hud/monkey.dm b/code/_onclick/hud/monkey.dm index ebf48177ec2..23da9c28172 100644 --- a/code/_onclick/hud/monkey.dm +++ b/code/_onclick/hud/monkey.dm @@ -1,169 +1,169 @@ -/datum/hud/monkey/New(mob/living/carbon/monkey/owner) - ..() - var/obj/screen/using - var/obj/screen/inventory/inv_box - - action_intent = new /obj/screen/act_intent() - action_intent.icon = ui_style - action_intent.icon_state = mymob.a_intent - action_intent.screen_loc = ui_acti - action_intent.hud = src - static_inventory += action_intent - - using = new /obj/screen/mov_intent() - using.icon = ui_style - using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") - using.screen_loc = ui_movi - using.hud = src - static_inventory += using - - using = new/obj/screen/language_menu - using.icon = ui_style - using.hud = src - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - using.hud = src - static_inventory += using - - build_hand_slots() - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_1_m" //extra wide! - using.screen_loc = ui_swaphand_position(owner,1) - using.hud = src - static_inventory += using - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "mask" - inv_box.icon = ui_style - inv_box.icon_state = "mask" -// inv_box.icon_full = "template" - inv_box.screen_loc = ui_monkey_mask - inv_box.slot_id = ITEM_SLOT_MASK - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "neck" - inv_box.icon = ui_style - inv_box.icon_state = "neck" -// inv_box.icon_full = "template" - inv_box.screen_loc = ui_monkey_neck - inv_box.slot_id = ITEM_SLOT_NECK - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "head" - inv_box.icon = ui_style - inv_box.icon_state = "head" -// inv_box.icon_full = "template" - inv_box.screen_loc = ui_monkey_head - inv_box.slot_id = ITEM_SLOT_HEAD - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "back" - inv_box.icon = ui_style - inv_box.icon_state = "back" - inv_box.screen_loc = ui_monkey_back - inv_box.slot_id = ITEM_SLOT_BACK - inv_box.hud = src - static_inventory += inv_box - - throw_icon = new /obj/screen/throw_catch() - throw_icon.icon = ui_style - throw_icon.screen_loc = ui_drop_throw - throw_icon.hud = src - hotkeybuttons += throw_icon - - internals = new /obj/screen/internals() - internals.hud = src - infodisplay += internals - - healths = new /obj/screen/healths() - healths.hud = src - infodisplay += healths - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_movement - pull_icon.hud = src - static_inventory += pull_icon - - lingchemdisplay = new /obj/screen/ling/chems() - lingchemdisplay.hud = src - infodisplay += lingchemdisplay - - lingstingdisplay = new /obj/screen/ling/sting() - lingstingdisplay.hud = src - infodisplay += lingstingdisplay - - - zone_select = new /obj/screen/zone_sel() - zone_select.icon = ui_style - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - mymob.client.screen = list() - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_intent - using.hud = src - hotkeybuttons += using - - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - inv.hud = src - inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv - inv.update_icon() - -/datum/hud/monkey/persistent_inventory_update() - if(!mymob) - return - var/mob/living/carbon/monkey/M = mymob - - if(hud_shown) - if(M.back) - M.back.screen_loc = ui_monkey_back - M.client.screen += M.back - if(M.wear_mask) - M.wear_mask.screen_loc = ui_monkey_mask - M.client.screen += M.wear_mask - if(M.wear_neck) - M.wear_neck.screen_loc = ui_monkey_neck - M.client.screen += M.wear_neck - if(M.head) - M.head.screen_loc = ui_monkey_head - M.client.screen += M.head - else - if(M.back) - M.back.screen_loc = null - if(M.wear_mask) - M.wear_mask.screen_loc = null - if(M.head) - M.head.screen_loc = null - - if(hud_version != HUD_STYLE_NOHUD) - for(var/obj/item/I in M.held_items) - I.screen_loc = ui_hand_position(M.get_held_index_of_item(I)) - M.client.screen += I - else - for(var/obj/item/I in M.held_items) - I.screen_loc = null - M.client.screen -= I +/datum/hud/monkey/New(mob/living/carbon/monkey/owner) + ..() + var/obj/screen/using + var/obj/screen/inventory/inv_box + + action_intent = new /obj/screen/act_intent() + action_intent.icon = ui_style + action_intent.icon_state = mymob.a_intent + action_intent.screen_loc = ui_acti + action_intent.hud = src + static_inventory += action_intent + + using = new /obj/screen/mov_intent() + using.icon = ui_style + using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") + using.screen_loc = ui_movi + using.hud = src + static_inventory += using + + using = new/obj/screen/language_menu + using.icon = ui_style + using.hud = src + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + using.hud = src + static_inventory += using + + build_hand_slots() + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_1_m" //extra wide! + using.screen_loc = ui_swaphand_position(owner,1) + using.hud = src + static_inventory += using + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_2" + using.screen_loc = ui_swaphand_position(owner,2) + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "mask" + inv_box.icon = ui_style + inv_box.icon_state = "mask" +// inv_box.icon_full = "template" + inv_box.screen_loc = ui_monkey_mask + inv_box.slot_id = ITEM_SLOT_MASK + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "neck" + inv_box.icon = ui_style + inv_box.icon_state = "neck" +// inv_box.icon_full = "template" + inv_box.screen_loc = ui_monkey_neck + inv_box.slot_id = ITEM_SLOT_NECK + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "head" + inv_box.icon = ui_style + inv_box.icon_state = "head" +// inv_box.icon_full = "template" + inv_box.screen_loc = ui_monkey_head + inv_box.slot_id = ITEM_SLOT_HEAD + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "back" + inv_box.icon = ui_style + inv_box.icon_state = "back" + inv_box.screen_loc = ui_monkey_back + inv_box.slot_id = ITEM_SLOT_BACK + inv_box.hud = src + static_inventory += inv_box + + throw_icon = new /obj/screen/throw_catch() + throw_icon.icon = ui_style + throw_icon.screen_loc = ui_drop_throw + throw_icon.hud = src + hotkeybuttons += throw_icon + + internals = new /obj/screen/internals() + internals.hud = src + infodisplay += internals + + healths = new /obj/screen/healths() + healths.hud = src + infodisplay += healths + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_movement + pull_icon.hud = src + static_inventory += pull_icon + + lingchemdisplay = new /obj/screen/ling/chems() + lingchemdisplay.hud = src + infodisplay += lingchemdisplay + + lingstingdisplay = new /obj/screen/ling/sting() + lingstingdisplay.hud = src + infodisplay += lingstingdisplay + + + zone_select = new /obj/screen/zone_sel() + zone_select.icon = ui_style + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + mymob.client.screen = list() + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_intent + using.hud = src + hotkeybuttons += using + + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + inv.hud = src + inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv + inv.update_icon() + +/datum/hud/monkey/persistent_inventory_update() + if(!mymob) + return + var/mob/living/carbon/monkey/M = mymob + + if(hud_shown) + if(M.back) + M.back.screen_loc = ui_monkey_back + M.client.screen += M.back + if(M.wear_mask) + M.wear_mask.screen_loc = ui_monkey_mask + M.client.screen += M.wear_mask + if(M.wear_neck) + M.wear_neck.screen_loc = ui_monkey_neck + M.client.screen += M.wear_neck + if(M.head) + M.head.screen_loc = ui_monkey_head + M.client.screen += M.head + else + if(M.back) + M.back.screen_loc = null + if(M.wear_mask) + M.wear_mask.screen_loc = null + if(M.head) + M.head.screen_loc = null + + if(hud_version != HUD_STYLE_NOHUD) + for(var/obj/item/I in M.held_items) + I.screen_loc = ui_hand_position(M.get_held_index_of_item(I)) + M.client.screen += I + else + for(var/obj/item/I in M.held_items) + I.screen_loc = null + M.client.screen -= I diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm index 03f30ab88b4..fcc46d5c0bb 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -1,287 +1,287 @@ -/obj/screen/robot - icon = 'icons/mob/screen_cyborg.dmi' - -/obj/screen/robot/module - name = "cyborg module" - icon_state = "nomod" - -/obj/screen/robot/Click() - if(isobserver(usr)) - return 1 - -/obj/screen/robot/module/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - if(R.module.type != /obj/item/robot_module) - R.hud_used.toggle_show_robot_modules() - return 1 - R.pick_module() - -/obj/screen/robot/module1 - name = "module1" - icon_state = "inv1" - -/obj/screen/robot/module1/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(1) - -/obj/screen/robot/module2 - name = "module2" - icon_state = "inv2" - -/obj/screen/robot/module2/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(2) - -/obj/screen/robot/module3 - name = "module3" - icon_state = "inv3" - -/obj/screen/robot/module3/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(3) - -/obj/screen/robot/radio - name = "radio" - icon_state = "radio" - -/obj/screen/robot/radio/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.radio.interact(R) - -/obj/screen/robot/store - name = "store" - icon_state = "store" - -/obj/screen/robot/store/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.uneq_active() - -/obj/screen/robot/lamp - name = "headlamp" - icon_state = "lamp0" - -/obj/screen/robot/lamp/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.control_headlamp() - -/obj/screen/robot/thrusters - name = "ion thrusters" - icon_state = "ionpulse0" - -/obj/screen/robot/thrusters/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_ionpulse() - -/datum/hud/robot - ui_style = 'icons/mob/screen_cyborg.dmi' - -/datum/hud/robot/New(mob/owner) - ..() - var/mob/living/silicon/robot/mymobR = mymob - var/obj/screen/using - - using = new/obj/screen/language_menu - using.screen_loc = ui_borg_language_menu - static_inventory += using - -//Radio - using = new /obj/screen/robot/radio() - using.screen_loc = ui_borg_radio - using.hud = src - static_inventory += using - -//Module select - using = new /obj/screen/robot/module1() - using.screen_loc = ui_inv1 - using.hud = src - static_inventory += using - mymobR.inv1 = using - - using = new /obj/screen/robot/module2() - using.screen_loc = ui_inv2 - using.hud = src - static_inventory += using - mymobR.inv2 = using - - using = new /obj/screen/robot/module3() - using.screen_loc = ui_inv3 - using.hud = src - static_inventory += using - mymobR.inv3 = using - -//End of module select - -//Photography stuff - using = new /obj/screen/ai/image_take() - using.screen_loc = ui_borg_camera - using.hud = src - static_inventory += using - - using = new /obj/screen/ai/image_view() - using.screen_loc = ui_borg_album - using.hud = src - static_inventory += using - -//Sec/Med HUDs - using = new /obj/screen/ai/sensors() - using.screen_loc = ui_borg_sensor - using.hud = src - static_inventory += using - -//Headlamp control - using = new /obj/screen/robot/lamp() - using.screen_loc = ui_borg_lamp - using.hud = src - static_inventory += using - mymobR.lamp_button = using - -//Thrusters - using = new /obj/screen/robot/thrusters() - using.screen_loc = ui_borg_thrusters - using.hud = src - static_inventory += using - mymobR.thruster_button = using - -//Intent - action_intent = new /obj/screen/act_intent/robot() - action_intent.icon_state = mymob.a_intent - action_intent.hud = src - static_inventory += action_intent - -//Health - healths = new /obj/screen/healths/robot() - healths.hud = src - infodisplay += healths - -//Installed Module - mymobR.hands = new /obj/screen/robot/module() - mymobR.hands.screen_loc = ui_borg_module - mymobR.hands.hud = src - static_inventory += mymobR.hands - -//Store - module_store_icon = new /obj/screen/robot/store() - module_store_icon.screen_loc = ui_borg_store - module_store_icon.hud = src - - pull_icon = new /obj/screen/pull() - pull_icon.icon = 'icons/mob/screen_cyborg.dmi' - pull_icon.screen_loc = ui_borg_pull - pull_icon.hud = src - pull_icon.update_icon() - hotkeybuttons += pull_icon - - - zone_select = new /obj/screen/zone_sel/robot() - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - -/datum/hud/proc/toggle_show_robot_modules() - if(!iscyborg(mymob)) - return - - var/mob/living/silicon/robot/R = mymob - - R.shown_robot_modules = !R.shown_robot_modules - update_robot_modules_display() - -/datum/hud/proc/update_robot_modules_display(mob/viewer) - if(!iscyborg(mymob)) - return - - var/mob/living/silicon/robot/R = mymob - - var/mob/screenmob = viewer || R - - if(!R.module) - return - - if(!R.client) - return - - if(R.shown_robot_modules && screenmob.hud_used.hud_shown) - //Modules display is shown - screenmob.client.screen += module_store_icon //"store" icon - - if(!R.module.modules) - to_chat(usr, "Selected module has no modules to select!") - return - - if(!R.robot_modules_background) - return - - var/display_rows = CEILING(length(R.module.get_inactive_modules()) / 8, 1) - R.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7" - screenmob.client.screen += R.robot_modules_background - - var/x = -4 //Start at CENTER-4,SOUTH+1 - var/y = 1 - - for(var/atom/movable/A in R.module.get_inactive_modules()) - //Module is not currently active - screenmob.client.screen += A - if(x < 0) - A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7" - else - A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7" - A.layer = ABOVE_HUD_LAYER - A.plane = ABOVE_HUD_PLANE - - x++ - if(x == 4) - x = -4 - y++ - - else - //Modules display is hidden - screenmob.client.screen -= module_store_icon //"store" icon - - for(var/atom/A in R.module.get_inactive_modules()) - //Module is not currently active - screenmob.client.screen -= A - R.shown_robot_modules = 0 - screenmob.client.screen -= R.robot_modules_background - -/datum/hud/robot/persistent_inventory_update(mob/viewer) - if(!mymob) - return - var/mob/living/silicon/robot/R = mymob - - var/mob/screenmob = viewer || R - - if(screenmob.hud_used) - if(screenmob.hud_used.hud_shown) - for(var/i in 1 to R.held_items.len) - var/obj/item/I = R.held_items[i] - if(I) - switch(i) - if(1) - I.screen_loc = ui_inv1 - if(2) - I.screen_loc = ui_inv2 - if(3) - I.screen_loc = ui_inv3 - else - return - screenmob.client.screen += I - else - for(var/obj/item/I in R.held_items) - screenmob.client.screen -= I +/obj/screen/robot + icon = 'icons/mob/screen_cyborg.dmi' + +/obj/screen/robot/module + name = "cyborg module" + icon_state = "nomod" + +/obj/screen/robot/Click() + if(isobserver(usr)) + return 1 + +/obj/screen/robot/module/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + if(R.module.type != /obj/item/robot_module) + R.hud_used.toggle_show_robot_modules() + return 1 + R.pick_module() + +/obj/screen/robot/module1 + name = "module1" + icon_state = "inv1" + +/obj/screen/robot/module1/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(1) + +/obj/screen/robot/module2 + name = "module2" + icon_state = "inv2" + +/obj/screen/robot/module2/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(2) + +/obj/screen/robot/module3 + name = "module3" + icon_state = "inv3" + +/obj/screen/robot/module3/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(3) + +/obj/screen/robot/radio + name = "radio" + icon_state = "radio" + +/obj/screen/robot/radio/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.radio.interact(R) + +/obj/screen/robot/store + name = "store" + icon_state = "store" + +/obj/screen/robot/store/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.uneq_active() + +/obj/screen/robot/lamp + name = "headlamp" + icon_state = "lamp0" + +/obj/screen/robot/lamp/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.control_headlamp() + +/obj/screen/robot/thrusters + name = "ion thrusters" + icon_state = "ionpulse0" + +/obj/screen/robot/thrusters/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_ionpulse() + +/datum/hud/robot + ui_style = 'icons/mob/screen_cyborg.dmi' + +/datum/hud/robot/New(mob/owner) + ..() + var/mob/living/silicon/robot/mymobR = mymob + var/obj/screen/using + + using = new/obj/screen/language_menu + using.screen_loc = ui_borg_language_menu + static_inventory += using + +//Radio + using = new /obj/screen/robot/radio() + using.screen_loc = ui_borg_radio + using.hud = src + static_inventory += using + +//Module select + using = new /obj/screen/robot/module1() + using.screen_loc = ui_inv1 + using.hud = src + static_inventory += using + mymobR.inv1 = using + + using = new /obj/screen/robot/module2() + using.screen_loc = ui_inv2 + using.hud = src + static_inventory += using + mymobR.inv2 = using + + using = new /obj/screen/robot/module3() + using.screen_loc = ui_inv3 + using.hud = src + static_inventory += using + mymobR.inv3 = using + +//End of module select + +//Photography stuff + using = new /obj/screen/ai/image_take() + using.screen_loc = ui_borg_camera + using.hud = src + static_inventory += using + + using = new /obj/screen/ai/image_view() + using.screen_loc = ui_borg_album + using.hud = src + static_inventory += using + +//Sec/Med HUDs + using = new /obj/screen/ai/sensors() + using.screen_loc = ui_borg_sensor + using.hud = src + static_inventory += using + +//Headlamp control + using = new /obj/screen/robot/lamp() + using.screen_loc = ui_borg_lamp + using.hud = src + static_inventory += using + mymobR.lamp_button = using + +//Thrusters + using = new /obj/screen/robot/thrusters() + using.screen_loc = ui_borg_thrusters + using.hud = src + static_inventory += using + mymobR.thruster_button = using + +//Intent + action_intent = new /obj/screen/act_intent/robot() + action_intent.icon_state = mymob.a_intent + action_intent.hud = src + static_inventory += action_intent + +//Health + healths = new /obj/screen/healths/robot() + healths.hud = src + infodisplay += healths + +//Installed Module + mymobR.hands = new /obj/screen/robot/module() + mymobR.hands.screen_loc = ui_borg_module + mymobR.hands.hud = src + static_inventory += mymobR.hands + +//Store + module_store_icon = new /obj/screen/robot/store() + module_store_icon.screen_loc = ui_borg_store + module_store_icon.hud = src + + pull_icon = new /obj/screen/pull() + pull_icon.icon = 'icons/mob/screen_cyborg.dmi' + pull_icon.screen_loc = ui_borg_pull + pull_icon.hud = src + pull_icon.update_icon() + hotkeybuttons += pull_icon + + + zone_select = new /obj/screen/zone_sel/robot() + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + +/datum/hud/proc/toggle_show_robot_modules() + if(!iscyborg(mymob)) + return + + var/mob/living/silicon/robot/R = mymob + + R.shown_robot_modules = !R.shown_robot_modules + update_robot_modules_display() + +/datum/hud/proc/update_robot_modules_display(mob/viewer) + if(!iscyborg(mymob)) + return + + var/mob/living/silicon/robot/R = mymob + + var/mob/screenmob = viewer || R + + if(!R.module) + return + + if(!R.client) + return + + if(R.shown_robot_modules && screenmob.hud_used.hud_shown) + //Modules display is shown + screenmob.client.screen += module_store_icon //"store" icon + + if(!R.module.modules) + to_chat(usr, "Selected module has no modules to select!") + return + + if(!R.robot_modules_background) + return + + var/display_rows = CEILING(length(R.module.get_inactive_modules()) / 8, 1) + R.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7" + screenmob.client.screen += R.robot_modules_background + + var/x = -4 //Start at CENTER-4,SOUTH+1 + var/y = 1 + + for(var/atom/movable/A in R.module.get_inactive_modules()) + //Module is not currently active + screenmob.client.screen += A + if(x < 0) + A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7" + else + A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7" + A.layer = ABOVE_HUD_LAYER + A.plane = ABOVE_HUD_PLANE + + x++ + if(x == 4) + x = -4 + y++ + + else + //Modules display is hidden + screenmob.client.screen -= module_store_icon //"store" icon + + for(var/atom/A in R.module.get_inactive_modules()) + //Module is not currently active + screenmob.client.screen -= A + R.shown_robot_modules = 0 + screenmob.client.screen -= R.robot_modules_background + +/datum/hud/robot/persistent_inventory_update(mob/viewer) + if(!mymob) + return + var/mob/living/silicon/robot/R = mymob + + var/mob/screenmob = viewer || R + + if(screenmob.hud_used) + if(screenmob.hud_used.hud_shown) + for(var/i in 1 to R.held_items.len) + var/obj/item/I = R.held_items[i] + if(I) + switch(i) + if(1) + I.screen_loc = ui_inv1 + if(2) + I.screen_loc = ui_inv2 + if(3) + I.screen_loc = ui_inv3 + else + return + screenmob.client.screen += I + else + for(var/obj/item/I in R.held_items) + screenmob.client.screen -= I diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 1f011b619bb..75b858f047b 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -1,723 +1,723 @@ -/* - Screen objects - Todo: improve/re-implement - - Screen objects are only used for the hud and should not appear anywhere "in-game". - They are used with the client/screen list and the screen_loc var. - For more information, see the byond documentation on the screen_loc and screen vars. -*/ -/obj/screen - name = "" - icon = 'icons/mob/screen_gen.dmi' - layer = HUD_LAYER - plane = HUD_PLANE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - appearance_flags = APPEARANCE_UI - var/obj/master = null //A reference to the object in the slot. Grabs or items, generally. - var/datum/hud/hud = null // A reference to the owner HUD, if any. - /** - * Map name assigned to this object. - * Automatically set by /client/proc/add_obj_to_map. - */ - var/assigned_map - /** - * Mark this object as garbage-collectible after you clean the map - * it was registered on. - * - * This could probably be changed to be a proc, for conditional removal. - * But for now, this works. - */ - var/del_on_map_removal = TRUE - -/obj/screen/take_damage() - return - -/obj/screen/Destroy() - master = null - hud = null - return ..() - -/obj/screen/examine(mob/user) - return list() - -/obj/screen/orbit() - return - -/obj/screen/proc/component_click(obj/screen/component_button/component, params) - return - -/obj/screen/text - icon = null - icon_state = null - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - screen_loc = "CENTER-7,CENTER-7" - maptext_height = 480 - maptext_width = 480 - -/obj/screen/swap_hand - layer = HUD_LAYER - plane = HUD_PLANE - name = "swap hand" - -/obj/screen/swap_hand/Click() - // At this point in client Click() code we have passed the 1/10 sec check and little else - // We don't even know if it's a middle click - if(world.time <= usr.next_move) - return 1 - - if(usr.incapacitated()) - return 1 - - if(ismob(usr)) - var/mob/M = usr - M.swap_hand() - return 1 - -/obj/screen/skills - name = "skills" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "skills" - screen_loc = ui_skill_menu - -/obj/screen/skills/Click() - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.mind.print_levels(H) - -/obj/screen/craft - name = "crafting menu" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "craft" - screen_loc = ui_crafting - -/obj/screen/area_creator - name = "create new area" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "area_edit" - screen_loc = ui_building - -/obj/screen/area_creator/Click() - if(usr.incapacitated() || (isobserver(usr) && !IsAdminGhost(usr))) - return TRUE - var/area/A = get_area(usr) - if(!A.outdoors) - to_chat(usr, "There is already a defined structure here.") - return TRUE - create_area(usr) - -/obj/screen/language_menu - name = "language menu" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "talk_wheel" - screen_loc = ui_language_menu - -/obj/screen/language_menu/Click() - var/mob/M = usr - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) - -/obj/screen/inventory - var/slot_id // The indentifier for the slot. It has nothing to do with ID cards. - var/icon_empty // Icon when empty. For now used only by humans. - var/icon_full // Icon when contains an item. For now used only by humans. - var/list/object_overlays = list() - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/inventory/Click(location, control, params) - // At this point in client Click() code we have passed the 1/10 sec check and little else - // We don't even know if it's a middle click - if(world.time <= usr.next_move) - return TRUE - - if(usr.incapacitated()) - return TRUE - if(ismecha(usr.loc)) // stops inventory actions in a mech - return TRUE - - if(hud?.mymob && slot_id) - var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id) - if(inv_item) - return inv_item.Click(location, control, params) - - if(usr.attack_ui(slot_id)) - usr.update_inv_hands() - return TRUE - -/obj/screen/inventory/MouseEntered() - ..() - add_overlays() - -/obj/screen/inventory/MouseExited() - ..() - cut_overlay(object_overlays) - object_overlays.Cut() - -/obj/screen/inventory/update_icon_state() - if(!icon_empty) - icon_empty = icon_state - - if(hud?.mymob && slot_id && icon_full) - if(hud.mymob.get_item_by_slot(slot_id)) - icon_state = icon_full - else - icon_state = icon_empty - -/obj/screen/inventory/proc/add_overlays() - var/mob/user = hud?.mymob - - if(!user || !slot_id) - return - - var/obj/item/holding = user.get_active_held_item() - - if(!holding || user.get_item_by_slot(slot_id)) - return - - var/image/item_overlay = image(holding) - item_overlay.alpha = 92 - - if(!user.can_equip(holding, slot_id, TRUE)) - item_overlay.color = "#FF0000" - else - item_overlay.color = "#00ff00" - - object_overlays += item_overlay - add_overlay(object_overlays) - -/obj/screen/inventory/hand - var/mutable_appearance/handcuff_overlay - var/static/mutable_appearance/blocked_overlay = mutable_appearance('icons/mob/screen_gen.dmi', "blocked") - var/held_index = 0 - -/obj/screen/inventory/hand/update_overlays() - . = ..() - - if(!handcuff_overlay) - var/state = (!(held_index % 2)) ? "markus" : "gabrielle" - handcuff_overlay = mutable_appearance('icons/mob/screen_gen.dmi', state) - - if(!hud?.mymob) - return - - if(iscarbon(hud.mymob)) - var/mob/living/carbon/C = hud.mymob - if(C.handcuffed) - . += handcuff_overlay - - if(held_index) - if(!C.has_hand_for_held_index(held_index)) - . += blocked_overlay - - if(held_index == hud.mymob.active_hand_index) - . += "hand_active" - - -/obj/screen/inventory/hand/Click(location, control, params) - // At this point in client Click() code we have passed the 1/10 sec check and little else - // We don't even know if it's a middle click - var/mob/user = hud?.mymob - if(usr != user) - return TRUE - if(world.time <= user.next_move) - return TRUE - if(user.incapacitated()) - return TRUE - if (ismecha(user.loc)) // stops inventory actions in a mech - return TRUE - - if(user.active_hand_index == held_index) - var/obj/item/I = user.get_active_held_item() - if(I) - I.Click(location, control, params) - else - user.swap_hand(held_index) - return TRUE - -/obj/screen/close - name = "close" - layer = ABOVE_HUD_LAYER - plane = ABOVE_HUD_PLANE - icon_state = "backpack_close" - -/obj/screen/close/Initialize(mapload, new_master) - . = ..() - master = new_master - -/obj/screen/close/Click() - var/datum/component/storage/S = master - S.hide_from(usr) - return TRUE - -/obj/screen/drop - name = "drop" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_drop" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/drop/Click() - if(usr.stat == CONSCIOUS) - usr.dropItemToGround(usr.get_active_held_item()) - -/obj/screen/act_intent - name = "intent" - icon_state = "help" - screen_loc = ui_acti - -/obj/screen/act_intent/Click(location, control, params) - usr.a_intent_change(INTENT_HOTKEY_RIGHT) - -/obj/screen/act_intent/segmented/Click(location, control, params) - if(usr.client.prefs.toggles & INTENT_STYLE) - var/_x = text2num(params2list(params)["icon-x"]) - var/_y = text2num(params2list(params)["icon-y"]) - - if(_x<=16 && _y<=16) - usr.a_intent_change(INTENT_HARM) - - else if(_x<=16 && _y>=17) - usr.a_intent_change(INTENT_HELP) - - else if(_x>=17 && _y<=16) - usr.a_intent_change(INTENT_GRAB) - - else if(_x>=17 && _y>=17) - usr.a_intent_change(INTENT_DISARM) - else - return ..() - -/obj/screen/act_intent/alien - icon = 'icons/mob/screen_alien.dmi' - screen_loc = ui_movi - -/obj/screen/act_intent/robot - icon = 'icons/mob/screen_cyborg.dmi' - screen_loc = ui_borg_intents - -/obj/screen/internals - name = "toggle internals" - icon_state = "internal0" - screen_loc = ui_internal - -/obj/screen/internals/Click() - if(!iscarbon(usr)) - return - var/mob/living/carbon/C = usr - if(C.incapacitated()) - return - - if(C.internal) - C.internal = null - to_chat(C, "You are no longer running on internals.") - icon_state = "internal0" - else - if(!C.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if(!istype(C.wear_mask, /obj/item/clothing/mask)) - to_chat(C, "You are not wearing an internals mask!") - return 1 - else - var/obj/item/clothing/mask/M = C.wear_mask - if(M.mask_adjusted) // if mask on face but pushed down - M.adjustmask(C) // adjust it back - if( !(M.clothing_flags & MASKINTERNALS) ) - to_chat(C, "You are not wearing an internals mask!") - return - - var/obj/item/I = C.is_holding_item_of_type(/obj/item/tank) - if(I) - to_chat(C, "You are now running on internals from [I] in your [C.get_held_index_name(C.get_held_index_of_item(I))].") - C.internal = I - else if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(istype(H.s_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.s_store] on your [H.wear_suit.name].") - H.internal = H.s_store - else if(istype(H.belt, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.belt] on your belt.") - H.internal = H.belt - else if(istype(H.l_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.l_store] in your left pocket.") - H.internal = H.l_store - else if(istype(H.r_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.r_store] in your right pocket.") - H.internal = H.r_store - - //Separate so CO2 jetpacks are a little less cumbersome. - if(!C.internal && istype(C.back, /obj/item/tank)) - to_chat(C, "You are now running on internals from [C.back] on your back.") - C.internal = C.back - - if(C.internal) - icon_state = "internal1" - else - to_chat(C, "You don't have an oxygen tank!") - return - C.update_action_buttons_icon() - -/obj/screen/spacesuit - name = "Space suit cell status" - icon_state = "spacesuit_0" - screen_loc = ui_spacesuit - -/obj/screen/mov_intent - name = "run/walk toggle" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "running" - -/obj/screen/mov_intent/Click() - toggle(usr) - -/obj/screen/mov_intent/update_icon_state() - switch(hud?.mymob?.m_intent) - if(MOVE_INTENT_WALK) - icon_state = "walking" - if(MOVE_INTENT_RUN) - icon_state = "running" - -/obj/screen/mov_intent/proc/toggle(mob/user) - if(isobserver(user)) - return - user.toggle_move_intent(user) - -/obj/screen/pull - name = "stop pulling" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "pull" - -/obj/screen/pull/Click() - if(isobserver(usr)) - return - usr.stop_pulling() - -/obj/screen/pull/update_icon_state() - if(hud?.mymob?.pulling) - icon_state = "pull" - else - icon_state = "pull0" - -/obj/screen/resist - name = "resist" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_resist" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/resist/Click() - if(isliving(usr)) - var/mob/living/L = usr - L.resist() - -/obj/screen/rest - name = "rest" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_rest" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/rest/Click() - if(isliving(usr)) - var/mob/living/L = usr - L.lay_down() - -/obj/screen/rest/update_icon_state() - var/mob/living/user = hud?.mymob - if(!istype(user)) - return - if(!user.resting) - icon_state = "act_rest" - else - icon_state = "act_rest0" - -/obj/screen/storage - name = "storage" - icon_state = "block" - screen_loc = "7,7 to 10,8" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/storage/Initialize(mapload, new_master) - . = ..() - master = new_master - -/obj/screen/storage/Click(location, control, params) - if(world.time <= usr.next_move) - return TRUE - if(usr.incapacitated()) - return TRUE - if (ismecha(usr.loc)) // stops inventory actions in a mech - return TRUE - if(master) - var/obj/item/I = usr.get_active_held_item() - if(I) - master.attackby(null, I, usr, params) - return TRUE - -/obj/screen/throw_catch - name = "throw/catch" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_throw_off" - -/obj/screen/throw_catch/Click() - if(iscarbon(usr)) - var/mob/living/carbon/C = usr - C.toggle_throw_mode() - -/obj/screen/zone_sel - name = "damage zone" - icon_state = "zone_sel" - screen_loc = ui_zonesel - var/overlay_icon = 'icons/mob/screen_gen.dmi' - var/static/list/hover_overlays_cache = list() - var/hovering - -/obj/screen/zone_sel/Click(location, control,params) - if(isobserver(usr)) - return - - var/list/PL = params2list(params) - var/icon_x = text2num(PL["icon-x"]) - var/icon_y = text2num(PL["icon-y"]) - var/choice = get_zone_at(icon_x, icon_y) - if (!choice) - return 1 - - return set_selected_zone(choice, usr) - -/obj/screen/zone_sel/MouseEntered(location, control, params) - MouseMove(location, control, params) - -/obj/screen/zone_sel/MouseMove(location, control, params) - if(isobserver(usr)) - return - - var/list/PL = params2list(params) - var/icon_x = text2num(PL["icon-x"]) - var/icon_y = text2num(PL["icon-y"]) - var/choice = get_zone_at(icon_x, icon_y) - - if(hovering == choice) - return - vis_contents -= hover_overlays_cache[hovering] - hovering = choice - - var/obj/effect/overlay/zone_sel/overlay_object = hover_overlays_cache[choice] - if(!overlay_object) - overlay_object = new - overlay_object.icon_state = "[choice]" - hover_overlays_cache[choice] = overlay_object - vis_contents += overlay_object - -/obj/effect/overlay/zone_sel - icon = 'icons/mob/screen_gen.dmi' - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - alpha = 128 - anchored = TRUE - layer = ABOVE_HUD_LAYER - plane = ABOVE_HUD_PLANE - -/obj/screen/zone_sel/MouseExited(location, control, params) - if(!isobserver(usr) && hovering) - vis_contents -= hover_overlays_cache[hovering] - hovering = null - -/obj/screen/zone_sel/proc/get_zone_at(icon_x, icon_y) - switch(icon_y) - if(1 to 9) //Legs - switch(icon_x) - if(10 to 15) - return BODY_ZONE_R_LEG - if(17 to 22) - return BODY_ZONE_L_LEG - if(10 to 13) //Hands and groin - switch(icon_x) - if(8 to 11) - return BODY_ZONE_R_ARM - if(12 to 20) - return BODY_ZONE_PRECISE_GROIN - if(21 to 24) - return BODY_ZONE_L_ARM - if(14 to 22) //Chest and arms to shoulders - switch(icon_x) - if(8 to 11) - return BODY_ZONE_R_ARM - if(12 to 20) - return BODY_ZONE_CHEST - if(21 to 24) - return BODY_ZONE_L_ARM - if(23 to 30) //Head, but we need to check for eye or mouth - if(icon_x in 12 to 20) - switch(icon_y) - if(23 to 24) - if(icon_x in 15 to 17) - return BODY_ZONE_PRECISE_MOUTH - if(26) //Eyeline, eyes are on 15 and 17 - if(icon_x in 14 to 18) - return BODY_ZONE_PRECISE_EYES - if(25 to 27) - if(icon_x in 15 to 17) - return BODY_ZONE_PRECISE_EYES - return BODY_ZONE_HEAD - -/obj/screen/zone_sel/proc/set_selected_zone(choice, mob/user) - if(user != hud?.mymob) - return - - if(choice != hud.mymob.zone_selected) - hud.mymob.zone_selected = choice - update_icon() - - return TRUE - -/obj/screen/zone_sel/update_overlays() - . = ..() - if(!hud?.mymob) - return - . += mutable_appearance(overlay_icon, "[hud.mymob.zone_selected]") - -/obj/screen/zone_sel/alien - icon = 'icons/mob/screen_alien.dmi' - overlay_icon = 'icons/mob/screen_alien.dmi' - -/obj/screen/zone_sel/robot - icon = 'icons/mob/screen_cyborg.dmi' - -/obj/screen/flash - name = "flash" - icon_state = "blank" - blend_mode = BLEND_ADD - screen_loc = "WEST,SOUTH to EAST,NORTH" - layer = FLASH_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/damageoverlay - icon = 'icons/mob/screen_full.dmi' - icon_state = "oxydamageoverlay0" - name = "dmg" - blend_mode = BLEND_MULTIPLY - screen_loc = "CENTER-7,CENTER-7" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - layer = UI_DAMAGE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/healths - name = "health" - icon_state = "health0" - screen_loc = ui_health - -/obj/screen/healths/alien - icon = 'icons/mob/screen_alien.dmi' - screen_loc = ui_alien_health - -/obj/screen/healths/robot - icon = 'icons/mob/screen_cyborg.dmi' - screen_loc = ui_borg_health - -/obj/screen/healths/blob - name = "blob health" - icon_state = "block" - screen_loc = ui_internal - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/blob/naut - name = "health" - icon = 'icons/mob/blob.dmi' - icon_state = "nauthealth" - -/obj/screen/healths/blob/naut/core - name = "overmind health" - icon_state = "corehealth" - screen_loc = ui_health - -/obj/screen/healths/guardian - name = "summoner health" - icon = 'icons/mob/guardian.dmi' - icon_state = "base" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/revenant - name = "essence" - icon = 'icons/mob/actions/backgrounds.dmi' - icon_state = "bg_revenant" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/construct - icon = 'icons/mob/screen_construct.dmi' - icon_state = "artificer_health0" - screen_loc = ui_construct_health - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healthdoll - name = "health doll" - screen_loc = ui_healthdoll - -/obj/screen/healthdoll/Click() - if (iscarbon(usr)) - var/mob/living/carbon/C = usr - C.check_self_for_injuries() - -/obj/screen/healthdoll/living - icon_state = "fullhealth0" - screen_loc = ui_living_healthdoll - var/filtered = FALSE //so we don't repeatedly create the mask of the mob every update - -/obj/screen/mood - name = "mood" - icon_state = "mood5" - screen_loc = ui_mood - -/obj/screen/splash - icon = 'icons/blank_title.png' - icon_state = "" - screen_loc = "1,1" - layer = SPLASHSCREEN_LAYER - plane = SPLASHSCREEN_PLANE - var/client/holder - -/obj/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy - . = ..() - - holder = C - - if(!visible) - alpha = 0 - - if(!use_previous_title) - if(SStitle.icon) - icon = SStitle.icon - else - if(!SStitle.previous_icon) - qdel(src) - return - icon = SStitle.previous_icon - - holder.screen += src - -/obj/screen/splash/proc/Fade(out, qdel_after = TRUE) - if(QDELETED(src)) - return - if(out) - animate(src, alpha = 0, time = 30) - else - alpha = 0 - animate(src, alpha = 255, time = 30) - if(qdel_after) - QDEL_IN(src, 30) - -/obj/screen/splash/Destroy() - if(holder) - holder.screen -= src - holder = null - return ..() - - -/obj/screen/component_button - var/obj/screen/parent - -/obj/screen/component_button/Initialize(mapload, obj/screen/parent) - . = ..() - src.parent = parent - -/obj/screen/component_button/Click(params) - if(parent) - parent.component_click(src, params) +/* + Screen objects + Todo: improve/re-implement + + Screen objects are only used for the hud and should not appear anywhere "in-game". + They are used with the client/screen list and the screen_loc var. + For more information, see the byond documentation on the screen_loc and screen vars. +*/ +/obj/screen + name = "" + icon = 'icons/mob/screen_gen.dmi' + layer = HUD_LAYER + plane = HUD_PLANE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + appearance_flags = APPEARANCE_UI + var/obj/master = null //A reference to the object in the slot. Grabs or items, generally. + var/datum/hud/hud = null // A reference to the owner HUD, if any. + /** + * Map name assigned to this object. + * Automatically set by /client/proc/add_obj_to_map. + */ + var/assigned_map + /** + * Mark this object as garbage-collectible after you clean the map + * it was registered on. + * + * This could probably be changed to be a proc, for conditional removal. + * But for now, this works. + */ + var/del_on_map_removal = TRUE + +/obj/screen/take_damage() + return + +/obj/screen/Destroy() + master = null + hud = null + return ..() + +/obj/screen/examine(mob/user) + return list() + +/obj/screen/orbit() + return + +/obj/screen/proc/component_click(obj/screen/component_button/component, params) + return + +/obj/screen/text + icon = null + icon_state = null + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + screen_loc = "CENTER-7,CENTER-7" + maptext_height = 480 + maptext_width = 480 + +/obj/screen/swap_hand + layer = HUD_LAYER + plane = HUD_PLANE + name = "swap hand" + +/obj/screen/swap_hand/Click() + // At this point in client Click() code we have passed the 1/10 sec check and little else + // We don't even know if it's a middle click + if(world.time <= usr.next_move) + return 1 + + if(usr.incapacitated()) + return 1 + + if(ismob(usr)) + var/mob/M = usr + M.swap_hand() + return 1 + +/obj/screen/skills + name = "skills" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "skills" + screen_loc = ui_skill_menu + +/obj/screen/skills/Click() + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.mind.print_levels(H) + +/obj/screen/craft + name = "crafting menu" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "craft" + screen_loc = ui_crafting + +/obj/screen/area_creator + name = "create new area" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "area_edit" + screen_loc = ui_building + +/obj/screen/area_creator/Click() + if(usr.incapacitated() || (isobserver(usr) && !IsAdminGhost(usr))) + return TRUE + var/area/A = get_area(usr) + if(!A.outdoors) + to_chat(usr, "There is already a defined structure here.") + return TRUE + create_area(usr) + +/obj/screen/language_menu + name = "language menu" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "talk_wheel" + screen_loc = ui_language_menu + +/obj/screen/language_menu/Click() + var/mob/M = usr + var/datum/language_holder/H = M.get_language_holder() + H.open_language_menu(usr) + +/obj/screen/inventory + var/slot_id // The indentifier for the slot. It has nothing to do with ID cards. + var/icon_empty // Icon when empty. For now used only by humans. + var/icon_full // Icon when contains an item. For now used only by humans. + var/list/object_overlays = list() + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/inventory/Click(location, control, params) + // At this point in client Click() code we have passed the 1/10 sec check and little else + // We don't even know if it's a middle click + if(world.time <= usr.next_move) + return TRUE + + if(usr.incapacitated()) + return TRUE + if(ismecha(usr.loc)) // stops inventory actions in a mech + return TRUE + + if(hud?.mymob && slot_id) + var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id) + if(inv_item) + return inv_item.Click(location, control, params) + + if(usr.attack_ui(slot_id)) + usr.update_inv_hands() + return TRUE + +/obj/screen/inventory/MouseEntered() + ..() + add_overlays() + +/obj/screen/inventory/MouseExited() + ..() + cut_overlay(object_overlays) + object_overlays.Cut() + +/obj/screen/inventory/update_icon_state() + if(!icon_empty) + icon_empty = icon_state + + if(hud?.mymob && slot_id && icon_full) + if(hud.mymob.get_item_by_slot(slot_id)) + icon_state = icon_full + else + icon_state = icon_empty + +/obj/screen/inventory/proc/add_overlays() + var/mob/user = hud?.mymob + + if(!user || !slot_id) + return + + var/obj/item/holding = user.get_active_held_item() + + if(!holding || user.get_item_by_slot(slot_id)) + return + + var/image/item_overlay = image(holding) + item_overlay.alpha = 92 + + if(!user.can_equip(holding, slot_id, TRUE)) + item_overlay.color = "#FF0000" + else + item_overlay.color = "#00ff00" + + object_overlays += item_overlay + add_overlay(object_overlays) + +/obj/screen/inventory/hand + var/mutable_appearance/handcuff_overlay + var/static/mutable_appearance/blocked_overlay = mutable_appearance('icons/mob/screen_gen.dmi', "blocked") + var/held_index = 0 + +/obj/screen/inventory/hand/update_overlays() + . = ..() + + if(!handcuff_overlay) + var/state = (!(held_index % 2)) ? "markus" : "gabrielle" + handcuff_overlay = mutable_appearance('icons/mob/screen_gen.dmi', state) + + if(!hud?.mymob) + return + + if(iscarbon(hud.mymob)) + var/mob/living/carbon/C = hud.mymob + if(C.handcuffed) + . += handcuff_overlay + + if(held_index) + if(!C.has_hand_for_held_index(held_index)) + . += blocked_overlay + + if(held_index == hud.mymob.active_hand_index) + . += "hand_active" + + +/obj/screen/inventory/hand/Click(location, control, params) + // At this point in client Click() code we have passed the 1/10 sec check and little else + // We don't even know if it's a middle click + var/mob/user = hud?.mymob + if(usr != user) + return TRUE + if(world.time <= user.next_move) + return TRUE + if(user.incapacitated()) + return TRUE + if (ismecha(user.loc)) // stops inventory actions in a mech + return TRUE + + if(user.active_hand_index == held_index) + var/obj/item/I = user.get_active_held_item() + if(I) + I.Click(location, control, params) + else + user.swap_hand(held_index) + return TRUE + +/obj/screen/close + name = "close" + layer = ABOVE_HUD_LAYER + plane = ABOVE_HUD_PLANE + icon_state = "backpack_close" + +/obj/screen/close/Initialize(mapload, new_master) + . = ..() + master = new_master + +/obj/screen/close/Click() + var/datum/component/storage/S = master + S.hide_from(usr) + return TRUE + +/obj/screen/drop + name = "drop" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_drop" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/drop/Click() + if(usr.stat == CONSCIOUS) + usr.dropItemToGround(usr.get_active_held_item()) + +/obj/screen/act_intent + name = "intent" + icon_state = "help" + screen_loc = ui_acti + +/obj/screen/act_intent/Click(location, control, params) + usr.a_intent_change(INTENT_HOTKEY_RIGHT) + +/obj/screen/act_intent/segmented/Click(location, control, params) + if(usr.client.prefs.toggles & INTENT_STYLE) + var/_x = text2num(params2list(params)["icon-x"]) + var/_y = text2num(params2list(params)["icon-y"]) + + if(_x<=16 && _y<=16) + usr.a_intent_change(INTENT_HARM) + + else if(_x<=16 && _y>=17) + usr.a_intent_change(INTENT_HELP) + + else if(_x>=17 && _y<=16) + usr.a_intent_change(INTENT_GRAB) + + else if(_x>=17 && _y>=17) + usr.a_intent_change(INTENT_DISARM) + else + return ..() + +/obj/screen/act_intent/alien + icon = 'icons/mob/screen_alien.dmi' + screen_loc = ui_movi + +/obj/screen/act_intent/robot + icon = 'icons/mob/screen_cyborg.dmi' + screen_loc = ui_borg_intents + +/obj/screen/internals + name = "toggle internals" + icon_state = "internal0" + screen_loc = ui_internal + +/obj/screen/internals/Click() + if(!iscarbon(usr)) + return + var/mob/living/carbon/C = usr + if(C.incapacitated()) + return + + if(C.internal) + C.internal = null + to_chat(C, "You are no longer running on internals.") + icon_state = "internal0" + else + if(!C.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + if(!istype(C.wear_mask, /obj/item/clothing/mask)) + to_chat(C, "You are not wearing an internals mask!") + return 1 + else + var/obj/item/clothing/mask/M = C.wear_mask + if(M.mask_adjusted) // if mask on face but pushed down + M.adjustmask(C) // adjust it back + if( !(M.clothing_flags & MASKINTERNALS) ) + to_chat(C, "You are not wearing an internals mask!") + return + + var/obj/item/I = C.is_holding_item_of_type(/obj/item/tank) + if(I) + to_chat(C, "You are now running on internals from [I] in your [C.get_held_index_name(C.get_held_index_of_item(I))].") + C.internal = I + else if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(istype(H.s_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.s_store] on your [H.wear_suit.name].") + H.internal = H.s_store + else if(istype(H.belt, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.belt] on your belt.") + H.internal = H.belt + else if(istype(H.l_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.l_store] in your left pocket.") + H.internal = H.l_store + else if(istype(H.r_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.r_store] in your right pocket.") + H.internal = H.r_store + + //Separate so CO2 jetpacks are a little less cumbersome. + if(!C.internal && istype(C.back, /obj/item/tank)) + to_chat(C, "You are now running on internals from [C.back] on your back.") + C.internal = C.back + + if(C.internal) + icon_state = "internal1" + else + to_chat(C, "You don't have an oxygen tank!") + return + C.update_action_buttons_icon() + +/obj/screen/spacesuit + name = "Space suit cell status" + icon_state = "spacesuit_0" + screen_loc = ui_spacesuit + +/obj/screen/mov_intent + name = "run/walk toggle" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "running" + +/obj/screen/mov_intent/Click() + toggle(usr) + +/obj/screen/mov_intent/update_icon_state() + switch(hud?.mymob?.m_intent) + if(MOVE_INTENT_WALK) + icon_state = "walking" + if(MOVE_INTENT_RUN) + icon_state = "running" + +/obj/screen/mov_intent/proc/toggle(mob/user) + if(isobserver(user)) + return + user.toggle_move_intent(user) + +/obj/screen/pull + name = "stop pulling" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "pull" + +/obj/screen/pull/Click() + if(isobserver(usr)) + return + usr.stop_pulling() + +/obj/screen/pull/update_icon_state() + if(hud?.mymob?.pulling) + icon_state = "pull" + else + icon_state = "pull0" + +/obj/screen/resist + name = "resist" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_resist" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/resist/Click() + if(isliving(usr)) + var/mob/living/L = usr + L.resist() + +/obj/screen/rest + name = "rest" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_rest" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/rest/Click() + if(isliving(usr)) + var/mob/living/L = usr + L.lay_down() + +/obj/screen/rest/update_icon_state() + var/mob/living/user = hud?.mymob + if(!istype(user)) + return + if(!user.resting) + icon_state = "act_rest" + else + icon_state = "act_rest0" + +/obj/screen/storage + name = "storage" + icon_state = "block" + screen_loc = "7,7 to 10,8" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/storage/Initialize(mapload, new_master) + . = ..() + master = new_master + +/obj/screen/storage/Click(location, control, params) + if(world.time <= usr.next_move) + return TRUE + if(usr.incapacitated()) + return TRUE + if (ismecha(usr.loc)) // stops inventory actions in a mech + return TRUE + if(master) + var/obj/item/I = usr.get_active_held_item() + if(I) + master.attackby(null, I, usr, params) + return TRUE + +/obj/screen/throw_catch + name = "throw/catch" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_throw_off" + +/obj/screen/throw_catch/Click() + if(iscarbon(usr)) + var/mob/living/carbon/C = usr + C.toggle_throw_mode() + +/obj/screen/zone_sel + name = "damage zone" + icon_state = "zone_sel" + screen_loc = ui_zonesel + var/overlay_icon = 'icons/mob/screen_gen.dmi' + var/static/list/hover_overlays_cache = list() + var/hovering + +/obj/screen/zone_sel/Click(location, control,params) + if(isobserver(usr)) + return + + var/list/PL = params2list(params) + var/icon_x = text2num(PL["icon-x"]) + var/icon_y = text2num(PL["icon-y"]) + var/choice = get_zone_at(icon_x, icon_y) + if (!choice) + return 1 + + return set_selected_zone(choice, usr) + +/obj/screen/zone_sel/MouseEntered(location, control, params) + MouseMove(location, control, params) + +/obj/screen/zone_sel/MouseMove(location, control, params) + if(isobserver(usr)) + return + + var/list/PL = params2list(params) + var/icon_x = text2num(PL["icon-x"]) + var/icon_y = text2num(PL["icon-y"]) + var/choice = get_zone_at(icon_x, icon_y) + + if(hovering == choice) + return + vis_contents -= hover_overlays_cache[hovering] + hovering = choice + + var/obj/effect/overlay/zone_sel/overlay_object = hover_overlays_cache[choice] + if(!overlay_object) + overlay_object = new + overlay_object.icon_state = "[choice]" + hover_overlays_cache[choice] = overlay_object + vis_contents += overlay_object + +/obj/effect/overlay/zone_sel + icon = 'icons/mob/screen_gen.dmi' + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 128 + anchored = TRUE + layer = ABOVE_HUD_LAYER + plane = ABOVE_HUD_PLANE + +/obj/screen/zone_sel/MouseExited(location, control, params) + if(!isobserver(usr) && hovering) + vis_contents -= hover_overlays_cache[hovering] + hovering = null + +/obj/screen/zone_sel/proc/get_zone_at(icon_x, icon_y) + switch(icon_y) + if(1 to 9) //Legs + switch(icon_x) + if(10 to 15) + return BODY_ZONE_R_LEG + if(17 to 22) + return BODY_ZONE_L_LEG + if(10 to 13) //Hands and groin + switch(icon_x) + if(8 to 11) + return BODY_ZONE_R_ARM + if(12 to 20) + return BODY_ZONE_PRECISE_GROIN + if(21 to 24) + return BODY_ZONE_L_ARM + if(14 to 22) //Chest and arms to shoulders + switch(icon_x) + if(8 to 11) + return BODY_ZONE_R_ARM + if(12 to 20) + return BODY_ZONE_CHEST + if(21 to 24) + return BODY_ZONE_L_ARM + if(23 to 30) //Head, but we need to check for eye or mouth + if(icon_x in 12 to 20) + switch(icon_y) + if(23 to 24) + if(icon_x in 15 to 17) + return BODY_ZONE_PRECISE_MOUTH + if(26) //Eyeline, eyes are on 15 and 17 + if(icon_x in 14 to 18) + return BODY_ZONE_PRECISE_EYES + if(25 to 27) + if(icon_x in 15 to 17) + return BODY_ZONE_PRECISE_EYES + return BODY_ZONE_HEAD + +/obj/screen/zone_sel/proc/set_selected_zone(choice, mob/user) + if(user != hud?.mymob) + return + + if(choice != hud.mymob.zone_selected) + hud.mymob.zone_selected = choice + update_icon() + + return TRUE + +/obj/screen/zone_sel/update_overlays() + . = ..() + if(!hud?.mymob) + return + . += mutable_appearance(overlay_icon, "[hud.mymob.zone_selected]") + +/obj/screen/zone_sel/alien + icon = 'icons/mob/screen_alien.dmi' + overlay_icon = 'icons/mob/screen_alien.dmi' + +/obj/screen/zone_sel/robot + icon = 'icons/mob/screen_cyborg.dmi' + +/obj/screen/flash + name = "flash" + icon_state = "blank" + blend_mode = BLEND_ADD + screen_loc = "WEST,SOUTH to EAST,NORTH" + layer = FLASH_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/damageoverlay + icon = 'icons/mob/screen_full.dmi' + icon_state = "oxydamageoverlay0" + name = "dmg" + blend_mode = BLEND_MULTIPLY + screen_loc = "CENTER-7,CENTER-7" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = UI_DAMAGE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/healths + name = "health" + icon_state = "health0" + screen_loc = ui_health + +/obj/screen/healths/alien + icon = 'icons/mob/screen_alien.dmi' + screen_loc = ui_alien_health + +/obj/screen/healths/robot + icon = 'icons/mob/screen_cyborg.dmi' + screen_loc = ui_borg_health + +/obj/screen/healths/blob + name = "blob health" + icon_state = "block" + screen_loc = ui_internal + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/blob/naut + name = "health" + icon = 'icons/mob/blob.dmi' + icon_state = "nauthealth" + +/obj/screen/healths/blob/naut/core + name = "overmind health" + icon_state = "corehealth" + screen_loc = ui_health + +/obj/screen/healths/guardian + name = "summoner health" + icon = 'icons/mob/guardian.dmi' + icon_state = "base" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/revenant + name = "essence" + icon = 'icons/mob/actions/backgrounds.dmi' + icon_state = "bg_revenant" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/construct + icon = 'icons/mob/screen_construct.dmi' + icon_state = "artificer_health0" + screen_loc = ui_construct_health + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healthdoll + name = "health doll" + screen_loc = ui_healthdoll + +/obj/screen/healthdoll/Click() + if (iscarbon(usr)) + var/mob/living/carbon/C = usr + C.check_self_for_injuries() + +/obj/screen/healthdoll/living + icon_state = "fullhealth0" + screen_loc = ui_living_healthdoll + var/filtered = FALSE //so we don't repeatedly create the mask of the mob every update + +/obj/screen/mood + name = "mood" + icon_state = "mood5" + screen_loc = ui_mood + +/obj/screen/splash + icon = 'icons/blank_title.png' + icon_state = "" + screen_loc = "1,1" + layer = SPLASHSCREEN_LAYER + plane = SPLASHSCREEN_PLANE + var/client/holder + +/obj/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy + . = ..() + + holder = C + + if(!visible) + alpha = 0 + + if(!use_previous_title) + if(SStitle.icon) + icon = SStitle.icon + else + if(!SStitle.previous_icon) + qdel(src) + return + icon = SStitle.previous_icon + + holder.screen += src + +/obj/screen/splash/proc/Fade(out, qdel_after = TRUE) + if(QDELETED(src)) + return + if(out) + animate(src, alpha = 0, time = 30) + else + alpha = 0 + animate(src, alpha = 255, time = 30) + if(qdel_after) + QDEL_IN(src, 30) + +/obj/screen/splash/Destroy() + if(holder) + holder.screen -= src + holder = null + return ..() + + +/obj/screen/component_button + var/obj/screen/parent + +/obj/screen/component_button/Initialize(mapload, obj/screen/parent) + . = ..() + src.parent = parent + +/obj/screen/component_button/Click(params) + if(parent) + parent.component_click(src, params) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 648251851ec..e02fd415b0d 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -1,192 +1,192 @@ -/** - * This is the proc that handles the order of an item_attack. - * - * The order of procs called is: - * * [/atom/proc/tool_act] on the target. If it returns TRUE, the chain will be stopped. - * * [/obj/item/proc/pre_attack] on src. If this returns TRUE, the chain will be stopped. - * * [/atom/proc/attackby] on the target. If it returns TRUE, the chain will be stopped. - * * [/obj/item/proc/afterattack]. The return value does not matter. - */ -/obj/item/proc/melee_attack_chain(mob/user, atom/target, params) - if(tool_behaviour && target.tool_act(user, src, tool_behaviour)) - return - if(pre_attack(target, user, params)) - return - if(target.attackby(src,user, params)) - return - if(QDELETED(src) || QDELETED(target)) - attack_qdeleted(target, user, TRUE, params) - return - afterattack(target, user, TRUE, params) - -/// Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown. -/obj/item/proc/attack_self(mob/user) - if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) & COMPONENT_NO_INTERACT) - return - interact(user) - -/** - * Called on the item before it hits something - * - * Arguments: - * * atom/A - The atom about to be hit - * * mob/living/user - The mob doing the htting - * * params - click params such as alt/shift etc - * - * See: [/obj/item/proc/melee_attack_chain] - */ -/obj/item/proc/pre_attack(atom/A, mob/living/user, params) //do stuff before attackby! - if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, A, user, params) & COMPONENT_NO_ATTACK) - return TRUE - return FALSE //return TRUE to avoid calling attackby after this proc does stuff - -/** - * Called on an object being hit by an item - * - * Arguments: - * * obj/item/W - The item hitting this atom - * * mob/user - The wielder of this item - * * params - click params such as alt/shift etc - * - * See: [/obj/item/proc/melee_attack_chain] - */ -/atom/proc/attackby(obj/item/W, mob/user, params) - if(SEND_SIGNAL(src, COMSIG_PARENT_ATTACKBY, W, user, params) & COMPONENT_NO_AFTERATTACK) - return TRUE - return FALSE - -/obj/attackby(obj/item/I, mob/living/user, params) - return ..() || ((obj_flags & CAN_BE_HIT) && I.attack_obj(src, user)) - -/mob/living/attackby(obj/item/I, mob/living/user, params) - if(..()) - return TRUE - user.changeNext_move(CLICK_CD_MELEE) - return I.attack(src, user) - -/** - * Called from [/mob/living/attackby] - * - * Arguments: - * * mob/living/M - The mob being hit by this item - * * mob/living/user - The mob hitting with this item - */ -/obj/item/proc/attack(mob/living/M, mob/living/user) - if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, M, user) & COMPONENT_ITEM_NO_ATTACK) - return - SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user) - if(item_flags & NOBLUDGEON) - return - - if(force && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm other living beings!") - return - - if(item_flags & EYE_STAB && user.zone_selected == BODY_ZONE_PRECISE_EYES) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - M = user - if(eyestab(M,user)) - return - if(!force) - playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) - else if(hitsound) - playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) - - M.lastattacker = user.real_name - M.lastattackerckey = user.ckey - - if(force && M == user && user.client) - user.client.give_award(/datum/award/achievement/misc/selfouch, user) - - user.do_attack_animation(M) - M.attacked_by(src, user) - - log_combat(user, M, "attacked", src.name, "(INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])") - add_fingerprint(user) - - -/// The equivalent of the standard version of [/obj/item/proc/attack] but for object targets. -/obj/item/proc/attack_obj(obj/O, mob/living/user) - if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_OBJ, O, user) & COMPONENT_NO_ATTACK_OBJ) - return - if(item_flags & NOBLUDGEON) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(O) - O.attacked_by(src, user) - -/// Called from [/obj/item/proc/attack_obj] and [/obj/item/proc/attack] if the attack succeeds -/atom/movable/proc/attacked_by() - return - -/obj/attacked_by(obj/item/I, mob/living/user) - if(I.force) - user.visible_message("[user] hits [src] with [I]!", \ - "You hit [src] with [I]!", null, COMBAT_MESSAGE_RANGE) - //only witnesses close by and the victim see a hit message. - log_combat(user, src, "attacked", I) - take_damage(I.force, I.damtype, "melee", 1) - -/mob/living/attacked_by(obj/item/I, mob/living/user) - send_item_attack_message(I, user) - if(I.force) - apply_damage(I.force, I.damtype) - if(I.damtype == BRUTE) - if(prob(33)) - I.add_mob_blood(src) - var/turf/location = get_turf(src) - add_splatter_floor(location) - if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood - user.add_mob_blood(src) - return TRUE //successful attack - -/mob/living/simple_animal/attacked_by(obj/item/I, mob/living/user) - if(I.force < force_threshold || I.damtype == STAMINA) - playsound(loc, 'sound/weapons/tap.ogg', I.get_clamped_volume(), TRUE, -1) - else - return ..() - -/** - * Last proc in the [/obj/item/proc/melee_attack_chain] - * - * Arguments: - * * atom/target - The thing that was hit - * * mob/user - The mob doing the hitting - * * proximity_flag - is 1 if this afterattack was called on something adjacent, in your square, or on your person. - * * click_parameters - is the params string from byond [/atom/proc/Click] code, see that documentation. - */ -/obj/item/proc/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - SEND_SIGNAL(src, COMSIG_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) - SEND_SIGNAL(user, COMSIG_MOB_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) - -/// Called if the target gets deleted by our attack -/obj/item/proc/attack_qdeleted(atom/target, mob/user, proximity_flag, click_parameters) - SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) - SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) - -/obj/item/proc/get_clamped_volume() - if(w_class) - if(force) - return clamp((force + w_class) * 4, 30, 100)// Add the item's force to its weight class and multiply by 4, then clamp the value between 30 and 100 - else - return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100 - -/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area) - var/message_verb = "attacked" - if(I.attack_verb && I.attack_verb.len) - message_verb = "[pick(I.attack_verb)]" - else if(!I.force) - return - var/message_hit_area = "" - if(hit_area) - message_hit_area = " in the [hit_area]" - var/attack_message = "[src] is [message_verb][message_hit_area] with [I]!" - var/attack_message_local = "You're [message_verb][message_hit_area] with [I]!" - if(user in viewers(src, null)) - attack_message = "[user] [message_verb] [src][message_hit_area] with [I]!" - attack_message_local = "[user] [message_verb] you[message_hit_area] with [I]!" - if(user == src) - attack_message_local = "You [message_verb] yourself[message_hit_area] with [I]" - visible_message("[attack_message]",\ - "[attack_message_local]", null, COMBAT_MESSAGE_RANGE) - return 1 +/** + * This is the proc that handles the order of an item_attack. + * + * The order of procs called is: + * * [/atom/proc/tool_act] on the target. If it returns TRUE, the chain will be stopped. + * * [/obj/item/proc/pre_attack] on src. If this returns TRUE, the chain will be stopped. + * * [/atom/proc/attackby] on the target. If it returns TRUE, the chain will be stopped. + * * [/obj/item/proc/afterattack]. The return value does not matter. + */ +/obj/item/proc/melee_attack_chain(mob/user, atom/target, params) + if(tool_behaviour && target.tool_act(user, src, tool_behaviour)) + return + if(pre_attack(target, user, params)) + return + if(target.attackby(src,user, params)) + return + if(QDELETED(src) || QDELETED(target)) + attack_qdeleted(target, user, TRUE, params) + return + afterattack(target, user, TRUE, params) + +/// Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown. +/obj/item/proc/attack_self(mob/user) + if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) & COMPONENT_NO_INTERACT) + return + interact(user) + +/** + * Called on the item before it hits something + * + * Arguments: + * * atom/A - The atom about to be hit + * * mob/living/user - The mob doing the htting + * * params - click params such as alt/shift etc + * + * See: [/obj/item/proc/melee_attack_chain] + */ +/obj/item/proc/pre_attack(atom/A, mob/living/user, params) //do stuff before attackby! + if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, A, user, params) & COMPONENT_NO_ATTACK) + return TRUE + return FALSE //return TRUE to avoid calling attackby after this proc does stuff + +/** + * Called on an object being hit by an item + * + * Arguments: + * * obj/item/W - The item hitting this atom + * * mob/user - The wielder of this item + * * params - click params such as alt/shift etc + * + * See: [/obj/item/proc/melee_attack_chain] + */ +/atom/proc/attackby(obj/item/W, mob/user, params) + if(SEND_SIGNAL(src, COMSIG_PARENT_ATTACKBY, W, user, params) & COMPONENT_NO_AFTERATTACK) + return TRUE + return FALSE + +/obj/attackby(obj/item/I, mob/living/user, params) + return ..() || ((obj_flags & CAN_BE_HIT) && I.attack_obj(src, user)) + +/mob/living/attackby(obj/item/I, mob/living/user, params) + if(..()) + return TRUE + user.changeNext_move(CLICK_CD_MELEE) + return I.attack(src, user) + +/** + * Called from [/mob/living/attackby] + * + * Arguments: + * * mob/living/M - The mob being hit by this item + * * mob/living/user - The mob hitting with this item + */ +/obj/item/proc/attack(mob/living/M, mob/living/user) + if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, M, user) & COMPONENT_ITEM_NO_ATTACK) + return + SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user) + if(item_flags & NOBLUDGEON) + return + + if(force && HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm other living beings!") + return + + if(item_flags & EYE_STAB && user.zone_selected == BODY_ZONE_PRECISE_EYES) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + M = user + if(eyestab(M,user)) + return + if(!force) + playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) + else if(hitsound) + playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) + + M.lastattacker = user.real_name + M.lastattackerckey = user.ckey + + if(force && M == user && user.client) + user.client.give_award(/datum/award/achievement/misc/selfouch, user) + + user.do_attack_animation(M) + M.attacked_by(src, user) + + log_combat(user, M, "attacked", src.name, "(INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])") + add_fingerprint(user) + + +/// The equivalent of the standard version of [/obj/item/proc/attack] but for object targets. +/obj/item/proc/attack_obj(obj/O, mob/living/user) + if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_OBJ, O, user) & COMPONENT_NO_ATTACK_OBJ) + return + if(item_flags & NOBLUDGEON) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(O) + O.attacked_by(src, user) + +/// Called from [/obj/item/proc/attack_obj] and [/obj/item/proc/attack] if the attack succeeds +/atom/movable/proc/attacked_by() + return + +/obj/attacked_by(obj/item/I, mob/living/user) + if(I.force) + user.visible_message("[user] hits [src] with [I]!", \ + "You hit [src] with [I]!", null, COMBAT_MESSAGE_RANGE) + //only witnesses close by and the victim see a hit message. + log_combat(user, src, "attacked", I) + take_damage(I.force, I.damtype, "melee", 1) + +/mob/living/attacked_by(obj/item/I, mob/living/user) + send_item_attack_message(I, user) + if(I.force) + apply_damage(I.force, I.damtype) + if(I.damtype == BRUTE) + if(prob(33)) + I.add_mob_blood(src) + var/turf/location = get_turf(src) + add_splatter_floor(location) + if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood + user.add_mob_blood(src) + return TRUE //successful attack + +/mob/living/simple_animal/attacked_by(obj/item/I, mob/living/user) + if(I.force < force_threshold || I.damtype == STAMINA) + playsound(loc, 'sound/weapons/tap.ogg', I.get_clamped_volume(), TRUE, -1) + else + return ..() + +/** + * Last proc in the [/obj/item/proc/melee_attack_chain] + * + * Arguments: + * * atom/target - The thing that was hit + * * mob/user - The mob doing the hitting + * * proximity_flag - is 1 if this afterattack was called on something adjacent, in your square, or on your person. + * * click_parameters - is the params string from byond [/atom/proc/Click] code, see that documentation. + */ +/obj/item/proc/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + SEND_SIGNAL(src, COMSIG_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) + +/// Called if the target gets deleted by our attack +/obj/item/proc/attack_qdeleted(atom/target, mob/user, proximity_flag, click_parameters) + SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) + +/obj/item/proc/get_clamped_volume() + if(w_class) + if(force) + return clamp((force + w_class) * 4, 30, 100)// Add the item's force to its weight class and multiply by 4, then clamp the value between 30 and 100 + else + return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100 + +/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area) + var/message_verb = "attacked" + if(I.attack_verb && I.attack_verb.len) + message_verb = "[pick(I.attack_verb)]" + else if(!I.force) + return + var/message_hit_area = "" + if(hit_area) + message_hit_area = " in the [hit_area]" + var/attack_message = "[src] is [message_verb][message_hit_area] with [I]!" + var/attack_message_local = "You're [message_verb][message_hit_area] with [I]!" + if(user in viewers(src, null)) + attack_message = "[user] [message_verb] [src][message_hit_area] with [I]!" + attack_message_local = "[user] [message_verb] you[message_hit_area] with [I]!" + if(user == src) + attack_message_local = "You [message_verb] yourself[message_hit_area] with [I]" + visible_message("[attack_message]",\ + "[attack_message_local]", null, COMBAT_MESSAGE_RANGE) + return 1 diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index efed103392f..85b6ed16da6 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -1,81 +1,81 @@ -/mob/dead/observer/DblClickOn(atom/A, params) - if(check_click_intercept(params, A)) - return - - if(can_reenter_corpse && mind && mind.current) - if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it - reenter_corpse() // (body bag, closet, mech, etc) - return // seems legit. - - // Things you might plausibly want to follow - if(ismovable(A)) - ManualFollow(A) - - // Otherwise jump - else if(A.loc) - forceMove(get_turf(A)) - update_parallax_contents() - -/mob/dead/observer/ClickOn(atom/A, params) - if(check_click_intercept(params,A)) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) - AltClickNoInteract(src, A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(world.time <= next_move) - return - // You are responsible for checking config.ghost_interaction when you override this function - // Not all of them require checking, see below - A.attack_ghost(src) - -// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you -/atom/proc/attack_ghost(mob/dead/observer/user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) - return TRUE - if(user.client) - if(user.gas_scan && atmosanalyzer_scan(user, src)) - return TRUE - else if(IsAdminGhost(user)) - attack_ai(user) - else if(user.client.prefs.inquisitive_ghost) - user.examinate(src) - return FALSE - -/mob/living/attack_ghost(mob/dead/observer/user) - if(user.client && user.health_scan) - healthscan(user, src, 1, TRUE) - if(user.client && user.chem_scan) - chemscan(user, src) - return ..() - -// --------------------------------------- -// And here are some good things for free: -// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu - -/obj/effect/gateway_portal_bumper/attack_ghost(mob/user) - if(gateway) - gateway.Transfer(user) - return ..() - -/obj/machinery/teleport/hub/attack_ghost(mob/user) - if(power_station && power_station.engaged && power_station.teleporter_console && power_station.teleporter_console.target) - user.forceMove(get_turf(power_station.teleporter_console.target)) - return ..() +/mob/dead/observer/DblClickOn(atom/A, params) + if(check_click_intercept(params, A)) + return + + if(can_reenter_corpse && mind && mind.current) + if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it + reenter_corpse() // (body bag, closet, mech, etc) + return // seems legit. + + // Things you might plausibly want to follow + if(ismovable(A)) + ManualFollow(A) + + // Otherwise jump + else if(A.loc) + forceMove(get_turf(A)) + update_parallax_contents() + +/mob/dead/observer/ClickOn(atom/A, params) + if(check_click_intercept(params,A)) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) + AltClickNoInteract(src, A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(world.time <= next_move) + return + // You are responsible for checking config.ghost_interaction when you override this function + // Not all of them require checking, see below + A.attack_ghost(src) + +// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you +/atom/proc/attack_ghost(mob/dead/observer/user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) + return TRUE + if(user.client) + if(user.gas_scan && atmosanalyzer_scan(user, src)) + return TRUE + else if(IsAdminGhost(user)) + attack_ai(user) + else if(user.client.prefs.inquisitive_ghost) + user.examinate(src) + return FALSE + +/mob/living/attack_ghost(mob/dead/observer/user) + if(user.client && user.health_scan) + healthscan(user, src, 1, TRUE) + if(user.client && user.chem_scan) + chemscan(user, src) + return ..() + +// --------------------------------------- +// And here are some good things for free: +// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu + +/obj/effect/gateway_portal_bumper/attack_ghost(mob/user) + if(gateway) + gateway.Transfer(user) + return ..() + +/obj/machinery/teleport/hub/attack_ghost(mob/user) + if(power_station && power_station.engaged && power_station.teleporter_console && power_station.teleporter_console.target) + user.forceMove(get_turf(power_station.teleporter_console.target)) + return ..() diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 6c9e5964ba8..370a304d302 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -1,260 +1,260 @@ -/* - Humans: - Adds an exception for gloves, to allow special glove types like the ninja ones. - - Otherwise pretty standard. -*/ -/mob/living/carbon/human/UnarmedAttack(atom/A, proximity) - if(!has_active_hand()) //can't attack without a hand. - var/obj/item/bodypart/check_arm = get_active_hand() - if(check_arm && check_arm.is_disabled() == BODYPART_DISABLED_WOUND) - to_chat(src, "The damage in your [check_arm.name] is preventing you from using it! Get it fixed, or at least splinted!") - return - - to_chat(src, "You look at your arm and sigh.") - return - - // Special glove functions: - // If the gloves do anything, have them return 1 to stop - // normal attack_hand() here. - var/obj/item/clothing/gloves/G = gloves // not typecast specifically enough in defines - if(proximity && istype(G) && G.Touch(A,1)) - return - //This signal is needed to prevent gloves of the north star + hulk. - if(SEND_SIGNAL(src, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, A, proximity) & COMPONENT_NO_ATTACK_HAND) - return - SEND_SIGNAL(src, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, A, proximity) - A.attack_hand(src) - -/// Return TRUE to cancel other attack hand effects that respect it. -/atom/proc/attack_hand(mob/user) - . = FALSE - if(!(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND)) - add_fingerprint(user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_NO_ATTACK_HAND) - . = TRUE - if(interaction_flags_atom & INTERACT_ATOM_ATTACK_HAND) - . = _try_interact(user) - -//Return a non FALSE value to cancel whatever called this from propagating, if it respects it. -/atom/proc/_try_interact(mob/user) - if(IsAdminGhost(user)) //admin abuse - return interact(user) - if(can_interact(user)) - return interact(user) - return FALSE - -/atom/proc/can_interact(mob/user) - if(!user.can_interact_with(src)) - return FALSE - if((interaction_flags_atom & INTERACT_ATOM_REQUIRES_DEXTERITY) && !user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return FALSE - if(!(interaction_flags_atom & INTERACT_ATOM_IGNORE_INCAPACITATED) && user.incapacitated((interaction_flags_atom & INTERACT_ATOM_IGNORE_RESTRAINED), !(interaction_flags_atom & INTERACT_ATOM_CHECK_GRAB))) - return FALSE - return TRUE - -/atom/ui_status(mob/user) - . = ..() - if(!can_interact(user)) - . = min(., UI_UPDATE) - -/atom/movable/can_interact(mob/user) - . = ..() - if(!.) - return - if(!anchored && (interaction_flags_atom & INTERACT_ATOM_REQUIRES_ANCHORED)) - return FALSE - -/atom/proc/interact(mob/user) - if(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_INTERACT) - add_hiddenprint(user) - else - add_fingerprint(user) - if(interaction_flags_atom & INTERACT_ATOM_UI_INTERACT) - return ui_interact(user) - return FALSE - -/* -/mob/living/carbon/human/RestrainedClickOn(atom/A) ---carbons will handle this - return -*/ - -/mob/living/carbon/RestrainedClickOn(atom/A) - return 0 - -/mob/living/carbon/human/RangedAttack(atom/A, mouseparams) - . = ..() - if(gloves) - var/obj/item/clothing/gloves/G = gloves - if(istype(G) && G.Touch(A,0)) // for magic gloves - return - - if(isturf(A) && get_dist(src,A) <= 1) - src.Move_Pulled(A) - return - -/* - Animals & All Unspecified -*/ -/mob/living/UnarmedAttack(atom/A) - A.attack_animal(src) - -/atom/proc/attack_animal(mob/user) - SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_ANIMAL, user) - -/mob/living/RestrainedClickOn(atom/A) - return - -/* - Monkeys -*/ -/mob/living/carbon/monkey/UnarmedAttack(atom/A) - A.attack_paw(src) - -/atom/proc/attack_paw(mob/user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_PAW, user) & COMPONENT_NO_ATTACK_HAND) - return TRUE - return FALSE - -/* - Monkey RestrainedClickOn() was apparently the - one and only use of all of the restrained click code - (except to stop you from doing things while handcuffed); - moving it here instead of various hand_p's has simplified - things considerably -*/ -/mob/living/carbon/monkey/RestrainedClickOn(atom/A) - if(..()) - return - if(a_intent != INTENT_HARM || !ismob(A)) - return - if(is_muzzled()) - return - var/mob/living/carbon/ML = A - if(istype(ML)) - var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - var/obj/item/bodypart/affecting = null - if(ishuman(ML)) - var/mob/living/carbon/human/H = ML - affecting = H.get_bodypart(ran_zone(dam_zone)) - var/armor = ML.run_armor_check(affecting, "melee") - if(prob(75)) - ML.apply_damage(rand(1,3), BRUTE, affecting, armor) - ML.visible_message("[name] bites [ML]!", \ - "[name] bites you!", "You hear a chomp!", COMBAT_MESSAGE_RANGE, name) - to_chat(name, "You bite [ML]!") - if(armor >= 2) - return - for(var/thing in diseases) - var/datum/disease/D = thing - ML.ForceContractDisease(D) - else - ML.visible_message("[src]'s bite misses [ML]!", \ - "You avoid [src]'s bite!", "You hear jaws snapping shut!", COMBAT_MESSAGE_RANGE, src) - to_chat(src, "Your bite misses [ML]!") - -/* - Aliens - Defaults to same as monkey in most places -*/ -/mob/living/carbon/alien/UnarmedAttack(atom/A) - A.attack_alien(src) - -/atom/proc/attack_alien(mob/living/carbon/alien/user) - attack_paw(user) - return - -/mob/living/carbon/alien/RestrainedClickOn(atom/A) - return - -// Babby aliens -/mob/living/carbon/alien/larva/UnarmedAttack(atom/A) - A.attack_larva(src) -/atom/proc/attack_larva(mob/user) - return - - -/* - Slimes - Nothing happening here -*/ -/mob/living/simple_animal/slime/UnarmedAttack(atom/A) - if(isturf(A)) - return ..() - A.attack_slime(src) - -/atom/proc/attack_slime(mob/user) - return - -/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) - return - - -/* - Drones -*/ -/mob/living/simple_animal/drone/UnarmedAttack(atom/A) - A.attack_drone(src) - -/atom/proc/attack_drone(mob/living/simple_animal/drone/user) - attack_hand(user) //defaults to attack_hand. Override it when you don't want drones to do same stuff as humans. - -/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) - return - - -/* - True Devil -*/ - -/mob/living/carbon/true_devil/UnarmedAttack(atom/A, proximity) - A.attack_hand(src) - -/* - Brain -*/ - -/mob/living/brain/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default - return - - -/* - pAI -*/ - -/mob/living/silicon/pai/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default - return - - -/* - Simple animals -*/ - -/mob/living/simple_animal/UnarmedAttack(atom/A, proximity) - if(!dextrous) - return ..() - if(!ismob(A)) - A.attack_hand(src) - update_inv_hands() - - -/* - Hostile animals -*/ - -/mob/living/simple_animal/hostile/UnarmedAttack(atom/A) - target = A - if(dextrous && !ismob(A)) - ..() - else - AttackingTarget() - - - -/* - New Players: - Have no reason to click on anything at all. -*/ -/mob/dead/new_player/ClickOn() - return +/* + Humans: + Adds an exception for gloves, to allow special glove types like the ninja ones. + + Otherwise pretty standard. +*/ +/mob/living/carbon/human/UnarmedAttack(atom/A, proximity) + if(!has_active_hand()) //can't attack without a hand. + var/obj/item/bodypart/check_arm = get_active_hand() + if(check_arm && check_arm.is_disabled() == BODYPART_DISABLED_WOUND) + to_chat(src, "The damage in your [check_arm.name] is preventing you from using it! Get it fixed, or at least splinted!") + return + + to_chat(src, "You look at your arm and sigh.") + return + + // Special glove functions: + // If the gloves do anything, have them return 1 to stop + // normal attack_hand() here. + var/obj/item/clothing/gloves/G = gloves // not typecast specifically enough in defines + if(proximity && istype(G) && G.Touch(A,1)) + return + //This signal is needed to prevent gloves of the north star + hulk. + if(SEND_SIGNAL(src, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, A, proximity) & COMPONENT_NO_ATTACK_HAND) + return + SEND_SIGNAL(src, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, A, proximity) + A.attack_hand(src) + +/// Return TRUE to cancel other attack hand effects that respect it. +/atom/proc/attack_hand(mob/user) + . = FALSE + if(!(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND)) + add_fingerprint(user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_NO_ATTACK_HAND) + . = TRUE + if(interaction_flags_atom & INTERACT_ATOM_ATTACK_HAND) + . = _try_interact(user) + +//Return a non FALSE value to cancel whatever called this from propagating, if it respects it. +/atom/proc/_try_interact(mob/user) + if(IsAdminGhost(user)) //admin abuse + return interact(user) + if(can_interact(user)) + return interact(user) + return FALSE + +/atom/proc/can_interact(mob/user) + if(!user.can_interact_with(src)) + return FALSE + if((interaction_flags_atom & INTERACT_ATOM_REQUIRES_DEXTERITY) && !user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return FALSE + if(!(interaction_flags_atom & INTERACT_ATOM_IGNORE_INCAPACITATED) && user.incapacitated((interaction_flags_atom & INTERACT_ATOM_IGNORE_RESTRAINED), !(interaction_flags_atom & INTERACT_ATOM_CHECK_GRAB))) + return FALSE + return TRUE + +/atom/ui_status(mob/user) + . = ..() + if(!can_interact(user)) + . = min(., UI_UPDATE) + +/atom/movable/can_interact(mob/user) + . = ..() + if(!.) + return + if(!anchored && (interaction_flags_atom & INTERACT_ATOM_REQUIRES_ANCHORED)) + return FALSE + +/atom/proc/interact(mob/user) + if(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_INTERACT) + add_hiddenprint(user) + else + add_fingerprint(user) + if(interaction_flags_atom & INTERACT_ATOM_UI_INTERACT) + return ui_interact(user) + return FALSE + +/* +/mob/living/carbon/human/RestrainedClickOn(atom/A) ---carbons will handle this + return +*/ + +/mob/living/carbon/RestrainedClickOn(atom/A) + return 0 + +/mob/living/carbon/human/RangedAttack(atom/A, mouseparams) + . = ..() + if(gloves) + var/obj/item/clothing/gloves/G = gloves + if(istype(G) && G.Touch(A,0)) // for magic gloves + return + + if(isturf(A) && get_dist(src,A) <= 1) + src.Move_Pulled(A) + return + +/* + Animals & All Unspecified +*/ +/mob/living/UnarmedAttack(atom/A) + A.attack_animal(src) + +/atom/proc/attack_animal(mob/user) + SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_ANIMAL, user) + +/mob/living/RestrainedClickOn(atom/A) + return + +/* + Monkeys +*/ +/mob/living/carbon/monkey/UnarmedAttack(atom/A) + A.attack_paw(src) + +/atom/proc/attack_paw(mob/user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_PAW, user) & COMPONENT_NO_ATTACK_HAND) + return TRUE + return FALSE + +/* + Monkey RestrainedClickOn() was apparently the + one and only use of all of the restrained click code + (except to stop you from doing things while handcuffed); + moving it here instead of various hand_p's has simplified + things considerably +*/ +/mob/living/carbon/monkey/RestrainedClickOn(atom/A) + if(..()) + return + if(a_intent != INTENT_HARM || !ismob(A)) + return + if(is_muzzled()) + return + var/mob/living/carbon/ML = A + if(istype(ML)) + var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + var/obj/item/bodypart/affecting = null + if(ishuman(ML)) + var/mob/living/carbon/human/H = ML + affecting = H.get_bodypart(ran_zone(dam_zone)) + var/armor = ML.run_armor_check(affecting, "melee") + if(prob(75)) + ML.apply_damage(rand(1,3), BRUTE, affecting, armor) + ML.visible_message("[name] bites [ML]!", \ + "[name] bites you!", "You hear a chomp!", COMBAT_MESSAGE_RANGE, name) + to_chat(name, "You bite [ML]!") + if(armor >= 2) + return + for(var/thing in diseases) + var/datum/disease/D = thing + ML.ForceContractDisease(D) + else + ML.visible_message("[src]'s bite misses [ML]!", \ + "You avoid [src]'s bite!", "You hear jaws snapping shut!", COMBAT_MESSAGE_RANGE, src) + to_chat(src, "Your bite misses [ML]!") + +/* + Aliens + Defaults to same as monkey in most places +*/ +/mob/living/carbon/alien/UnarmedAttack(atom/A) + A.attack_alien(src) + +/atom/proc/attack_alien(mob/living/carbon/alien/user) + attack_paw(user) + return + +/mob/living/carbon/alien/RestrainedClickOn(atom/A) + return + +// Babby aliens +/mob/living/carbon/alien/larva/UnarmedAttack(atom/A) + A.attack_larva(src) +/atom/proc/attack_larva(mob/user) + return + + +/* + Slimes + Nothing happening here +*/ +/mob/living/simple_animal/slime/UnarmedAttack(atom/A) + if(isturf(A)) + return ..() + A.attack_slime(src) + +/atom/proc/attack_slime(mob/user) + return + +/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) + return + + +/* + Drones +*/ +/mob/living/simple_animal/drone/UnarmedAttack(atom/A) + A.attack_drone(src) + +/atom/proc/attack_drone(mob/living/simple_animal/drone/user) + attack_hand(user) //defaults to attack_hand. Override it when you don't want drones to do same stuff as humans. + +/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) + return + + +/* + True Devil +*/ + +/mob/living/carbon/true_devil/UnarmedAttack(atom/A, proximity) + A.attack_hand(src) + +/* + Brain +*/ + +/mob/living/brain/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default + return + + +/* + pAI +*/ + +/mob/living/silicon/pai/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default + return + + +/* + Simple animals +*/ + +/mob/living/simple_animal/UnarmedAttack(atom/A, proximity) + if(!dextrous) + return ..() + if(!ismob(A)) + A.attack_hand(src) + update_inv_hands() + + +/* + Hostile animals +*/ + +/mob/living/simple_animal/hostile/UnarmedAttack(atom/A) + target = A + if(dextrous && !ismob(A)) + ..() + else + AttackingTarget() + + + +/* + New Players: + Have no reason to click on anything at all. +*/ +/mob/dead/new_player/ClickOn() + return diff --git a/code/_onclick/overmind.dm b/code/_onclick/overmind.dm index 1b1514f3d1e..07907b94cee 100644 --- a/code/_onclick/overmind.dm +++ b/code/_onclick/overmind.dm @@ -1,36 +1,36 @@ -// Blob Overmind Controls - - -/mob/camera/blob/ClickOn(atom/A, params) //Expand blob - var/list/modifiers = params2list(params) - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - var/turf/T = get_turf(A) - if(T) - expand_blob(T) - -/mob/camera/blob/MiddleClickOn(atom/A) //Rally spores - . = ..() - var/turf/T = get_turf(A) - if(T) - rally_spores(T) - -/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield - var/turf/T = get_turf(A) - if(T) - create_shield(T) - -/mob/camera/blob/AltClickOn(atom/A) //Remove a blob - var/turf/T = get_turf(A) - if(T) - remove_blob(T) +// Blob Overmind Controls + + +/mob/camera/blob/ClickOn(atom/A, params) //Expand blob + var/list/modifiers = params2list(params) + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + var/turf/T = get_turf(A) + if(T) + expand_blob(T) + +/mob/camera/blob/MiddleClickOn(atom/A) //Rally spores + . = ..() + var/turf/T = get_turf(A) + if(T) + rally_spores(T) + +/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield + var/turf/T = get_turf(A) + if(T) + create_shield(T) + +/mob/camera/blob/AltClickOn(atom/A) //Remove a blob + var/turf/T = get_turf(A) + if(T) + remove_blob(T) diff --git a/code/controllers/configuration/config_entry.dm b/code/controllers/configuration/config_entry.dm index c03237a80f2..ac1c768a2d1 100644 --- a/code/controllers/configuration/config_entry.dm +++ b/code/controllers/configuration/config_entry.dm @@ -1,197 +1,197 @@ -#define VALUE_MODE_NUM 0 -#define VALUE_MODE_TEXT 1 -#define VALUE_MODE_FLAG 2 - -#define KEY_MODE_TEXT 0 -#define KEY_MODE_TYPE 1 - -/datum/config_entry - var/name //read-only, this is determined by the last portion of the derived entry type - var/config_entry_value - var/default //read-only, just set value directly - - var/resident_file //the file which this was loaded from, if any - var/modified = FALSE //set to TRUE if the default has been overridden by a config entry - - var/deprecated_by //the /datum/config_entry type that supercedes this one - - var/protection = NONE - var/abstract_type = /datum/config_entry //do not instantiate if type matches this - - var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless. - - var/dupes_allowed = FALSE - -/datum/config_entry/New() - if(type == abstract_type) - CRASH("Abstract config entry [type] instatiated!") - name = lowertext(type2top(type)) - if(islist(config_entry_value)) - var/list/L = config_entry_value - default = L.Copy() - else - default = config_entry_value - -/datum/config_entry/Destroy() - config.RemoveEntry(src) - return ..() - -/datum/config_entry/can_vv_get(var_name) - . = ..() - if(var_name == NAMEOF(src, config_entry_value) || var_name == NAMEOF(src, default)) - . &= !(protection & CONFIG_ENTRY_HIDDEN) - -/datum/config_entry/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list(NAMEOF(src, name), NAMEOF(src, vv_VAS), NAMEOF(src, default), NAMEOF(src, resident_file), NAMEOF(src, protection), NAMEOF(src, abstract_type), NAMEOF(src, modified), NAMEOF(src, dupes_allowed)) - if(var_name == NAMEOF(src, config_entry_value)) - if(protection & CONFIG_ENTRY_LOCKED) - return FALSE - if(vv_VAS) - . = ValidateAndSet("[var_value]") - if(.) - datum_flags |= DF_VAR_EDITED - return - else - return ..() - if(var_name in banned_edits) - return FALSE - return ..() - -/datum/config_entry/proc/VASProcCallGuard(str_val) - . = !((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - if(!.) - log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]") - -/datum/config_entry/proc/ValidateAndSet(str_val) - VASProcCallGuard(str_val) - CRASH("Invalid config entry type!") - -/datum/config_entry/proc/ValidateListEntry(key_name, key_value) - return TRUE - -/datum/config_entry/proc/DeprecationUpdate(value) - return - -/datum/config_entry/string - config_entry_value = "" - abstract_type = /datum/config_entry/string - var/auto_trim = TRUE - -/datum/config_entry/string/vv_edit_var(var_name, var_value) - return var_name != NAMEOF(src, auto_trim) && ..() - -/datum/config_entry/string/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - config_entry_value = auto_trim ? trim(str_val) : str_val - return TRUE - -/datum/config_entry/number - config_entry_value = 0 - abstract_type = /datum/config_entry/number - var/integer = TRUE - var/max_val = INFINITY - var/min_val = -INFINITY - -/datum/config_entry/number/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - var/temp = text2num(trim(str_val)) - if(!isnull(temp)) - config_entry_value = clamp(integer ? round(temp) : temp, min_val, max_val) - if(config_entry_value != temp && !(datum_flags & DF_VAR_EDITED)) - log_config("Changing [name] from [temp] to [config_entry_value]!") - return TRUE - return FALSE - -/datum/config_entry/number/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list(NAMEOF(src, max_val), NAMEOF(src, min_val), NAMEOF(src, integer)) - return !(var_name in banned_edits) && ..() - -/datum/config_entry/flag - config_entry_value = FALSE - abstract_type = /datum/config_entry/flag - -/datum/config_entry/flag/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - config_entry_value = text2num(trim(str_val)) != 0 - return TRUE - -/datum/config_entry/number_list - abstract_type = /datum/config_entry/number_list - config_entry_value = list() - -/datum/config_entry/number_list/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - str_val = trim(str_val) - var/list/new_list = list() - var/list/values = splittext(str_val," ") - for(var/I in values) - var/temp = text2num(I) - if(isnull(temp)) - return FALSE - new_list += temp - if(!new_list.len) - return FALSE - config_entry_value = new_list - return TRUE - -/datum/config_entry/keyed_list - abstract_type = /datum/config_entry/keyed_list - config_entry_value = list() - dupes_allowed = TRUE - vv_VAS = FALSE //VAS will not allow things like deleting from lists, it'll just bug horribly. - var/key_mode - var/value_mode - var/splitter = " " - -/datum/config_entry/keyed_list/New() - . = ..() - if(isnull(key_mode) || isnull(value_mode)) - CRASH("Keyed list of type [type] created with null key or value mode!") - -/datum/config_entry/keyed_list/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - - str_val = trim(str_val) - var/key_pos = findtext(str_val, splitter) - var/key_name = null - var/key_value = null - - if(key_pos || value_mode == VALUE_MODE_FLAG) - key_name = lowertext(copytext(str_val, 1, key_pos)) - if(key_pos) - key_value = copytext(str_val, key_pos + length(str_val[key_pos])) - var/new_key - var/new_value - var/continue_check_value - var/continue_check_key - switch(key_mode) - if(KEY_MODE_TEXT) - new_key = key_name - continue_check_key = new_key - if(KEY_MODE_TYPE) - new_key = key_name - if(!ispath(new_key)) - new_key = text2path(new_key) - continue_check_key = ispath(new_key) - switch(value_mode) - if(VALUE_MODE_FLAG) - new_value = TRUE - continue_check_value = TRUE - if(VALUE_MODE_NUM) - new_value = text2num(key_value) - continue_check_value = !isnull(new_value) - if(VALUE_MODE_TEXT) - new_value = key_value - continue_check_value = new_value - if(continue_check_value && continue_check_key && ValidateListEntry(new_key, new_value)) - config_entry_value[new_key] = new_value - return TRUE - return FALSE - -/datum/config_entry/keyed_list/vv_edit_var(var_name, var_value) - return var_name != NAMEOF(src, splitter) && ..() +#define VALUE_MODE_NUM 0 +#define VALUE_MODE_TEXT 1 +#define VALUE_MODE_FLAG 2 + +#define KEY_MODE_TEXT 0 +#define KEY_MODE_TYPE 1 + +/datum/config_entry + var/name //read-only, this is determined by the last portion of the derived entry type + var/config_entry_value + var/default //read-only, just set value directly + + var/resident_file //the file which this was loaded from, if any + var/modified = FALSE //set to TRUE if the default has been overridden by a config entry + + var/deprecated_by //the /datum/config_entry type that supercedes this one + + var/protection = NONE + var/abstract_type = /datum/config_entry //do not instantiate if type matches this + + var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless. + + var/dupes_allowed = FALSE + +/datum/config_entry/New() + if(type == abstract_type) + CRASH("Abstract config entry [type] instatiated!") + name = lowertext(type2top(type)) + if(islist(config_entry_value)) + var/list/L = config_entry_value + default = L.Copy() + else + default = config_entry_value + +/datum/config_entry/Destroy() + config.RemoveEntry(src) + return ..() + +/datum/config_entry/can_vv_get(var_name) + . = ..() + if(var_name == NAMEOF(src, config_entry_value) || var_name == NAMEOF(src, default)) + . &= !(protection & CONFIG_ENTRY_HIDDEN) + +/datum/config_entry/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list(NAMEOF(src, name), NAMEOF(src, vv_VAS), NAMEOF(src, default), NAMEOF(src, resident_file), NAMEOF(src, protection), NAMEOF(src, abstract_type), NAMEOF(src, modified), NAMEOF(src, dupes_allowed)) + if(var_name == NAMEOF(src, config_entry_value)) + if(protection & CONFIG_ENTRY_LOCKED) + return FALSE + if(vv_VAS) + . = ValidateAndSet("[var_value]") + if(.) + datum_flags |= DF_VAR_EDITED + return + else + return ..() + if(var_name in banned_edits) + return FALSE + return ..() + +/datum/config_entry/proc/VASProcCallGuard(str_val) + . = !((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + if(!.) + log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]") + +/datum/config_entry/proc/ValidateAndSet(str_val) + VASProcCallGuard(str_val) + CRASH("Invalid config entry type!") + +/datum/config_entry/proc/ValidateListEntry(key_name, key_value) + return TRUE + +/datum/config_entry/proc/DeprecationUpdate(value) + return + +/datum/config_entry/string + config_entry_value = "" + abstract_type = /datum/config_entry/string + var/auto_trim = TRUE + +/datum/config_entry/string/vv_edit_var(var_name, var_value) + return var_name != NAMEOF(src, auto_trim) && ..() + +/datum/config_entry/string/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + config_entry_value = auto_trim ? trim(str_val) : str_val + return TRUE + +/datum/config_entry/number + config_entry_value = 0 + abstract_type = /datum/config_entry/number + var/integer = TRUE + var/max_val = INFINITY + var/min_val = -INFINITY + +/datum/config_entry/number/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + var/temp = text2num(trim(str_val)) + if(!isnull(temp)) + config_entry_value = clamp(integer ? round(temp) : temp, min_val, max_val) + if(config_entry_value != temp && !(datum_flags & DF_VAR_EDITED)) + log_config("Changing [name] from [temp] to [config_entry_value]!") + return TRUE + return FALSE + +/datum/config_entry/number/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list(NAMEOF(src, max_val), NAMEOF(src, min_val), NAMEOF(src, integer)) + return !(var_name in banned_edits) && ..() + +/datum/config_entry/flag + config_entry_value = FALSE + abstract_type = /datum/config_entry/flag + +/datum/config_entry/flag/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + config_entry_value = text2num(trim(str_val)) != 0 + return TRUE + +/datum/config_entry/number_list + abstract_type = /datum/config_entry/number_list + config_entry_value = list() + +/datum/config_entry/number_list/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + str_val = trim(str_val) + var/list/new_list = list() + var/list/values = splittext(str_val," ") + for(var/I in values) + var/temp = text2num(I) + if(isnull(temp)) + return FALSE + new_list += temp + if(!new_list.len) + return FALSE + config_entry_value = new_list + return TRUE + +/datum/config_entry/keyed_list + abstract_type = /datum/config_entry/keyed_list + config_entry_value = list() + dupes_allowed = TRUE + vv_VAS = FALSE //VAS will not allow things like deleting from lists, it'll just bug horribly. + var/key_mode + var/value_mode + var/splitter = " " + +/datum/config_entry/keyed_list/New() + . = ..() + if(isnull(key_mode) || isnull(value_mode)) + CRASH("Keyed list of type [type] created with null key or value mode!") + +/datum/config_entry/keyed_list/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + + str_val = trim(str_val) + var/key_pos = findtext(str_val, splitter) + var/key_name = null + var/key_value = null + + if(key_pos || value_mode == VALUE_MODE_FLAG) + key_name = lowertext(copytext(str_val, 1, key_pos)) + if(key_pos) + key_value = copytext(str_val, key_pos + length(str_val[key_pos])) + var/new_key + var/new_value + var/continue_check_value + var/continue_check_key + switch(key_mode) + if(KEY_MODE_TEXT) + new_key = key_name + continue_check_key = new_key + if(KEY_MODE_TYPE) + new_key = key_name + if(!ispath(new_key)) + new_key = text2path(new_key) + continue_check_key = ispath(new_key) + switch(value_mode) + if(VALUE_MODE_FLAG) + new_value = TRUE + continue_check_value = TRUE + if(VALUE_MODE_NUM) + new_value = text2num(key_value) + continue_check_value = !isnull(new_value) + if(VALUE_MODE_TEXT) + new_value = key_value + continue_check_value = new_value + if(continue_check_value && continue_check_key && ValidateListEntry(new_key, new_value)) + config_entry_value[new_key] = new_value + return TRUE + return FALSE + +/datum/config_entry/keyed_list/vv_edit_var(var_name, var_value) + return var_name != NAMEOF(src, splitter) && ..() diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index e93c804ac25..4070964c330 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -1,424 +1,424 @@ -/datum/controller/configuration - name = "Configuration" - - var/directory = "config" - - var/warned_deprecated_configs = FALSE - var/hiding_entries_by_type = TRUE //Set for readability, admins can set this to FALSE if they want to debug it - var/list/entries - var/list/entries_by_type - - var/list/maplist - var/datum/map_config/defaultmap - - var/list/modes // allowed modes - var/list/gamemode_cache - var/list/votable_modes // votable modes - var/list/mode_names - var/list/mode_reports - var/list/mode_false_report_weight - - var/motd - var/policy - - var/static/regex/ic_filter_regex - -/datum/controller/configuration/proc/admin_reload() - if(IsAdminAdvancedProcCall()) - return - log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") - message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") - full_wipe() - Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - -/datum/controller/configuration/proc/Load(_directory) - if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything. - return - if(_directory) - directory = _directory - if(entries) - CRASH("/datum/controller/configuration/Load() called more than once!") - InitEntries() - LoadModes() - if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) - var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") - for(var/I in legacy_configs) - if(fexists("[directory]/[I]")) - log_config("No $include directives found in config.txt! Loading legacy [legacy_configs.Join("/")] files...") - for(var/J in legacy_configs) - LoadEntries(J) - break - loadmaplist(CONFIG_MAPS_FILE) - LoadMOTD() - LoadPolicy() - LoadChatFilter() - -/datum/controller/configuration/proc/full_wipe() - if(IsAdminAdvancedProcCall()) - return - entries_by_type.Cut() - QDEL_LIST_ASSOC_VAL(entries) - entries = null - QDEL_LIST_ASSOC_VAL(maplist) - maplist = null - QDEL_NULL(defaultmap) - -/datum/controller/configuration/Destroy() - full_wipe() - config = null - - return ..() - -/datum/controller/configuration/proc/InitEntries() - var/list/_entries = list() - entries = _entries - var/list/_entries_by_type = list() - entries_by_type = _entries_by_type - - for(var/I in typesof(/datum/config_entry)) //typesof is faster in this case - var/datum/config_entry/E = I - if(initial(E.abstract_type) == I) - continue - E = new I - var/esname = E.name - var/datum/config_entry/test = _entries[esname] - if(test) - log_config("Error: [test.type] has the same name as [E.type]: [esname]! Not initializing [E.type]!") - qdel(E) - continue - _entries[esname] = E - _entries_by_type[I] = E - -/datum/controller/configuration/proc/RemoveEntry(datum/config_entry/CE) - entries -= CE.name - entries_by_type -= CE.type - -/datum/controller/configuration/proc/LoadEntries(filename, list/stack = list()) - if(IsAdminAdvancedProcCall()) - return - - var/filename_to_test = world.system_type == MS_WINDOWS ? lowertext(filename) : filename - if(filename_to_test in stack) - log_config("Warning: Config recursion detected ([english_list(stack)]), breaking!") - return - stack = stack + filename_to_test - - log_config("Loading config file [filename]...") - var/list/lines = world.file2list("[directory]/[filename]") - var/list/_entries = entries - for(var/L in lines) - L = trim(L) - if(!L) - continue - - var/firstchar = L[1] - if(firstchar == "#") - continue - - var/lockthis = firstchar == "@" - if(lockthis) - L = copytext(L, length(firstchar) + 1) - - var/pos = findtext(L, " ") - var/entry = null - var/value = null - - if(pos) - entry = lowertext(copytext(L, 1, pos)) - value = copytext(L, pos + length(L[pos])) - else - entry = lowertext(L) - - if(!entry) - continue - - if(entry == "$include") - if(!value) - log_config("Warning: Invalid $include directive: [value]") - else - LoadEntries(value, stack) - ++. - continue - - var/datum/config_entry/E = _entries[entry] - if(!E) - log_config("Unknown setting in configuration: '[entry]'") - continue - - if(lockthis) - E.protection |= CONFIG_ENTRY_LOCKED - - if(E.deprecated_by) - var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by] - var/new_value = E.DeprecationUpdate(value) - var/good_update = istext(new_value) - log_config("Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]") - if(!warned_deprecated_configs) - DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.") - warned_deprecated_configs = TRUE - if(good_update) - value = new_value - E = new_ver - else - warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()") - - var/validated = E.ValidateAndSet(value) - if(!validated) - log_config("Failed to validate setting \"[value]\" for [entry]") - else - if(E.modified && !E.dupes_allowed) - log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.") - - E.resident_file = filename - - if(validated) - E.modified = TRUE - - ++. - -/datum/controller/configuration/can_vv_get(var_name) - return (var_name != NAMEOF(src, entries_by_type) || !hiding_entries_by_type) && ..() - -/datum/controller/configuration/vv_edit_var(var_name, var_value) - var/list/banned_edits = list(NAMEOF(src, entries_by_type), NAMEOF(src, entries), NAMEOF(src, directory)) - return !(var_name in banned_edits) && ..() - -/datum/controller/configuration/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Edit", src) - stat("[name]:", statclick) - -/datum/controller/configuration/proc/Get(entry_type) - var/datum/config_entry/E = entry_type - var/entry_is_abstract = initial(E.abstract_type) == entry_type - if(entry_is_abstract) - CRASH("Tried to retrieve an abstract config_entry: [entry_type]") - E = entries_by_type[entry_type] - if(!E) - CRASH("Missing config entry for [entry_type]!") - if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]") - return - return E.config_entry_value - -/datum/controller/configuration/proc/Set(entry_type, new_val) - var/datum/config_entry/E = entry_type - var/entry_is_abstract = initial(E.abstract_type) == entry_type - if(entry_is_abstract) - CRASH("Tried to set an abstract config_entry: [entry_type]") - E = entries_by_type[entry_type] - if(!E) - CRASH("Missing config entry for [entry_type]!") - if((E.protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Set" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - log_admin_private("Config rewrite of [entry_type] to [new_val] attempted by [key_name(usr)]") - return - return E.ValidateAndSet("[new_val]") - -/datum/controller/configuration/proc/LoadModes() - gamemode_cache = typecacheof(/datum/game_mode, TRUE) - modes = list() - mode_names = list() - mode_reports = list() - mode_false_report_weight = list() - votable_modes = list() - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - for(var/T in gamemode_cache) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - var/datum/game_mode/M = new T() - - if(M.config_tag) - if(!(M.config_tag in modes)) // ensure each mode is added only once - modes += M.config_tag - mode_names[M.config_tag] = M.name - probabilities[M.config_tag] = M.probability - mode_reports[M.report_type] = M.generate_report() - if(probabilities[M.config_tag]>0) - mode_false_report_weight[M.report_type] = M.false_report_weight - else - //"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode. - mode_false_report_weight[M.report_type] = min(1, M.false_report_weight) - if(M.votable) - votable_modes += M.config_tag - qdel(M) - votable_modes += "secret" - -/datum/controller/configuration/proc/LoadMOTD() - motd = file2text("[directory]/motd.txt") - var/tm_info = GLOB.revdata.GetTestMergeInfo() - if(motd || tm_info) - motd = motd ? "[motd]
    [tm_info]" : tm_info -/* -Policy file should be a json file with a single object. -Value is raw html. - -Possible keywords : -Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker -Mob types : /mob/living/simple_animal/hostile/carp -Antagonist types : /datum/antagonist/highlander -Species types : /datum/species/lizard -special keywords defined in _DEFINES/admin.dm - -Example config: -{ - "Assistant" : "Don't kill everyone", - "/datum/antagonist/highlander" : "Kill everyone", - "Ash Walker" : "Kill all spacemans" -} - -*/ -/datum/controller/configuration/proc/LoadPolicy() - policy = list() - var/rawpolicy = file2text("[directory]/policy.json") - if(rawpolicy) - var/parsed = safe_json_decode(rawpolicy) - if(!parsed) - log_config("JSON parsing failure for policy.json") - DelayedMessageAdmins("JSON parsing failure for policy.json") - else - policy = parsed - -/datum/controller/configuration/proc/loadmaplist(filename) - log_config("Loading config file [filename]...") - filename = "[directory]/[filename]" - var/list/Lines = world.file2list(filename) - - var/datum/map_config/currentmap = null - for(var/t in Lines) - if(!t) - continue - - t = trim(t) - if(length(t) == 0) - continue - else if(t[1] == "#") - continue - - var/pos = findtext(t, " ") - var/command = null - var/data = null - - if(pos) - command = lowertext(copytext(t, 1, pos)) - data = copytext(t, pos + length(t[pos])) - else - command = lowertext(t) - - if(!command) - continue - - if (!currentmap && command != "map") - continue - - switch (command) - if ("map") - currentmap = load_map_config("_maps/[data].json") - if(currentmap.defaulted) - log_config("Failed to load map config for [data]!") - currentmap = null - if ("minplayers","minplayer") - currentmap.config_min_users = text2num(data) - if ("maxplayers","maxplayer") - currentmap.config_max_users = text2num(data) - if ("weight","voteweight") - currentmap.voteweight = text2num(data) - if ("default","defaultmap") - defaultmap = currentmap - if ("votable") - currentmap.votable = TRUE - if ("endmap") - LAZYINITLIST(maplist) - maplist[currentmap.map_name] = currentmap - currentmap = null - if ("disabled") - currentmap = null - else - log_config("Unknown command in map vote config: '[command]'") - - -/datum/controller/configuration/proc/pick_mode(mode_name) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - // ^ This guy didn't try hard enough - for(var/T in gamemode_cache) - var/datum/game_mode/M = T - var/ct = initial(M.config_tag) - if(ct && ct == mode_name) - return new T - return new /datum/game_mode/extended() - -/datum/controller/configuration/proc/get_runnable_modes() - var/list/datum/game_mode/runnable_modes = new - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) - var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) - var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust) - for(var/T in gamemode_cache) - var/datum/game_mode/M = new T() - if(!(M.config_tag in modes)) - qdel(M) - continue - if(probabilities[M.config_tag]<=0) - qdel(M) - continue - if(min_pop[M.config_tag]) - M.required_players = min_pop[M.config_tag] - if(max_pop[M.config_tag]) - M.maximum_players = max_pop[M.config_tag] - if(M.can_start()) - var/final_weight = probabilities[M.config_tag] - if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3) - var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3) - var/adjustment = 0 - while(recent_round) - adjustment += repeated_mode_adjust[recent_round] - recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0) - final_weight *= ((100-adjustment)/100) - runnable_modes[M] = final_weight - return runnable_modes - -/datum/controller/configuration/proc/get_runnable_midround_modes(crew) - var/list/datum/game_mode/runnable_modes = new - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) - var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) - for(var/T in (gamemode_cache - SSticker.mode.type)) - var/datum/game_mode/M = new T() - if(!(M.config_tag in modes)) - qdel(M) - continue - if(probabilities[M.config_tag]<=0) - qdel(M) - continue - if(min_pop[M.config_tag]) - M.required_players = min_pop[M.config_tag] - if(max_pop[M.config_tag]) - M.maximum_players = max_pop[M.config_tag] - if(M.required_players <= crew) - if(M.maximum_players >= 0 && M.maximum_players < crew) - continue - runnable_modes[M] = probabilities[M.config_tag] - return runnable_modes - -/datum/controller/configuration/proc/LoadChatFilter() - var/list/in_character_filter = list() - - if(!fexists("[directory]/in_character_filter.txt")) - return - - log_config("Loading config file in_character_filter.txt...") - - for(var/line in world.file2list("[directory]/in_character_filter.txt")) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - in_character_filter += REGEX_QUOTE(line) - - ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null - - syncChatRegexes() - -//Message admins when you can. -/datum/controller/configuration/proc/DelayedMessageAdmins(text) - addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0) +/datum/controller/configuration + name = "Configuration" + + var/directory = "config" + + var/warned_deprecated_configs = FALSE + var/hiding_entries_by_type = TRUE //Set for readability, admins can set this to FALSE if they want to debug it + var/list/entries + var/list/entries_by_type + + var/list/maplist + var/datum/map_config/defaultmap + + var/list/modes // allowed modes + var/list/gamemode_cache + var/list/votable_modes // votable modes + var/list/mode_names + var/list/mode_reports + var/list/mode_false_report_weight + + var/motd + var/policy + + var/static/regex/ic_filter_regex + +/datum/controller/configuration/proc/admin_reload() + if(IsAdminAdvancedProcCall()) + return + log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") + message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") + full_wipe() + Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + +/datum/controller/configuration/proc/Load(_directory) + if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything. + return + if(_directory) + directory = _directory + if(entries) + CRASH("/datum/controller/configuration/Load() called more than once!") + InitEntries() + LoadModes() + if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) + var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") + for(var/I in legacy_configs) + if(fexists("[directory]/[I]")) + log_config("No $include directives found in config.txt! Loading legacy [legacy_configs.Join("/")] files...") + for(var/J in legacy_configs) + LoadEntries(J) + break + loadmaplist(CONFIG_MAPS_FILE) + LoadMOTD() + LoadPolicy() + LoadChatFilter() + +/datum/controller/configuration/proc/full_wipe() + if(IsAdminAdvancedProcCall()) + return + entries_by_type.Cut() + QDEL_LIST_ASSOC_VAL(entries) + entries = null + QDEL_LIST_ASSOC_VAL(maplist) + maplist = null + QDEL_NULL(defaultmap) + +/datum/controller/configuration/Destroy() + full_wipe() + config = null + + return ..() + +/datum/controller/configuration/proc/InitEntries() + var/list/_entries = list() + entries = _entries + var/list/_entries_by_type = list() + entries_by_type = _entries_by_type + + for(var/I in typesof(/datum/config_entry)) //typesof is faster in this case + var/datum/config_entry/E = I + if(initial(E.abstract_type) == I) + continue + E = new I + var/esname = E.name + var/datum/config_entry/test = _entries[esname] + if(test) + log_config("Error: [test.type] has the same name as [E.type]: [esname]! Not initializing [E.type]!") + qdel(E) + continue + _entries[esname] = E + _entries_by_type[I] = E + +/datum/controller/configuration/proc/RemoveEntry(datum/config_entry/CE) + entries -= CE.name + entries_by_type -= CE.type + +/datum/controller/configuration/proc/LoadEntries(filename, list/stack = list()) + if(IsAdminAdvancedProcCall()) + return + + var/filename_to_test = world.system_type == MS_WINDOWS ? lowertext(filename) : filename + if(filename_to_test in stack) + log_config("Warning: Config recursion detected ([english_list(stack)]), breaking!") + return + stack = stack + filename_to_test + + log_config("Loading config file [filename]...") + var/list/lines = world.file2list("[directory]/[filename]") + var/list/_entries = entries + for(var/L in lines) + L = trim(L) + if(!L) + continue + + var/firstchar = L[1] + if(firstchar == "#") + continue + + var/lockthis = firstchar == "@" + if(lockthis) + L = copytext(L, length(firstchar) + 1) + + var/pos = findtext(L, " ") + var/entry = null + var/value = null + + if(pos) + entry = lowertext(copytext(L, 1, pos)) + value = copytext(L, pos + length(L[pos])) + else + entry = lowertext(L) + + if(!entry) + continue + + if(entry == "$include") + if(!value) + log_config("Warning: Invalid $include directive: [value]") + else + LoadEntries(value, stack) + ++. + continue + + var/datum/config_entry/E = _entries[entry] + if(!E) + log_config("Unknown setting in configuration: '[entry]'") + continue + + if(lockthis) + E.protection |= CONFIG_ENTRY_LOCKED + + if(E.deprecated_by) + var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by] + var/new_value = E.DeprecationUpdate(value) + var/good_update = istext(new_value) + log_config("Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]") + if(!warned_deprecated_configs) + DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.") + warned_deprecated_configs = TRUE + if(good_update) + value = new_value + E = new_ver + else + warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()") + + var/validated = E.ValidateAndSet(value) + if(!validated) + log_config("Failed to validate setting \"[value]\" for [entry]") + else + if(E.modified && !E.dupes_allowed) + log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.") + + E.resident_file = filename + + if(validated) + E.modified = TRUE + + ++. + +/datum/controller/configuration/can_vv_get(var_name) + return (var_name != NAMEOF(src, entries_by_type) || !hiding_entries_by_type) && ..() + +/datum/controller/configuration/vv_edit_var(var_name, var_value) + var/list/banned_edits = list(NAMEOF(src, entries_by_type), NAMEOF(src, entries), NAMEOF(src, directory)) + return !(var_name in banned_edits) && ..() + +/datum/controller/configuration/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Edit", src) + stat("[name]:", statclick) + +/datum/controller/configuration/proc/Get(entry_type) + var/datum/config_entry/E = entry_type + var/entry_is_abstract = initial(E.abstract_type) == entry_type + if(entry_is_abstract) + CRASH("Tried to retrieve an abstract config_entry: [entry_type]") + E = entries_by_type[entry_type] + if(!E) + CRASH("Missing config entry for [entry_type]!") + if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]") + return + return E.config_entry_value + +/datum/controller/configuration/proc/Set(entry_type, new_val) + var/datum/config_entry/E = entry_type + var/entry_is_abstract = initial(E.abstract_type) == entry_type + if(entry_is_abstract) + CRASH("Tried to set an abstract config_entry: [entry_type]") + E = entries_by_type[entry_type] + if(!E) + CRASH("Missing config entry for [entry_type]!") + if((E.protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Set" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + log_admin_private("Config rewrite of [entry_type] to [new_val] attempted by [key_name(usr)]") + return + return E.ValidateAndSet("[new_val]") + +/datum/controller/configuration/proc/LoadModes() + gamemode_cache = typecacheof(/datum/game_mode, TRUE) + modes = list() + mode_names = list() + mode_reports = list() + mode_false_report_weight = list() + votable_modes = list() + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + for(var/T in gamemode_cache) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + var/datum/game_mode/M = new T() + + if(M.config_tag) + if(!(M.config_tag in modes)) // ensure each mode is added only once + modes += M.config_tag + mode_names[M.config_tag] = M.name + probabilities[M.config_tag] = M.probability + mode_reports[M.report_type] = M.generate_report() + if(probabilities[M.config_tag]>0) + mode_false_report_weight[M.report_type] = M.false_report_weight + else + //"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode. + mode_false_report_weight[M.report_type] = min(1, M.false_report_weight) + if(M.votable) + votable_modes += M.config_tag + qdel(M) + votable_modes += "secret" + +/datum/controller/configuration/proc/LoadMOTD() + motd = file2text("[directory]/motd.txt") + var/tm_info = GLOB.revdata.GetTestMergeInfo() + if(motd || tm_info) + motd = motd ? "[motd]
    [tm_info]" : tm_info +/* +Policy file should be a json file with a single object. +Value is raw html. + +Possible keywords : +Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker +Mob types : /mob/living/simple_animal/hostile/carp +Antagonist types : /datum/antagonist/highlander +Species types : /datum/species/lizard +special keywords defined in _DEFINES/admin.dm + +Example config: +{ + "Assistant" : "Don't kill everyone", + "/datum/antagonist/highlander" : "Kill everyone", + "Ash Walker" : "Kill all spacemans" +} + +*/ +/datum/controller/configuration/proc/LoadPolicy() + policy = list() + var/rawpolicy = file2text("[directory]/policy.json") + if(rawpolicy) + var/parsed = safe_json_decode(rawpolicy) + if(!parsed) + log_config("JSON parsing failure for policy.json") + DelayedMessageAdmins("JSON parsing failure for policy.json") + else + policy = parsed + +/datum/controller/configuration/proc/loadmaplist(filename) + log_config("Loading config file [filename]...") + filename = "[directory]/[filename]" + var/list/Lines = world.file2list(filename) + + var/datum/map_config/currentmap = null + for(var/t in Lines) + if(!t) + continue + + t = trim(t) + if(length(t) == 0) + continue + else if(t[1] == "#") + continue + + var/pos = findtext(t, " ") + var/command = null + var/data = null + + if(pos) + command = lowertext(copytext(t, 1, pos)) + data = copytext(t, pos + length(t[pos])) + else + command = lowertext(t) + + if(!command) + continue + + if (!currentmap && command != "map") + continue + + switch (command) + if ("map") + currentmap = load_map_config("_maps/[data].json") + if(currentmap.defaulted) + log_config("Failed to load map config for [data]!") + currentmap = null + if ("minplayers","minplayer") + currentmap.config_min_users = text2num(data) + if ("maxplayers","maxplayer") + currentmap.config_max_users = text2num(data) + if ("weight","voteweight") + currentmap.voteweight = text2num(data) + if ("default","defaultmap") + defaultmap = currentmap + if ("votable") + currentmap.votable = TRUE + if ("endmap") + LAZYINITLIST(maplist) + maplist[currentmap.map_name] = currentmap + currentmap = null + if ("disabled") + currentmap = null + else + log_config("Unknown command in map vote config: '[command]'") + + +/datum/controller/configuration/proc/pick_mode(mode_name) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + // ^ This guy didn't try hard enough + for(var/T in gamemode_cache) + var/datum/game_mode/M = T + var/ct = initial(M.config_tag) + if(ct && ct == mode_name) + return new T + return new /datum/game_mode/extended() + +/datum/controller/configuration/proc/get_runnable_modes() + var/list/datum/game_mode/runnable_modes = new + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) + var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) + var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust) + for(var/T in gamemode_cache) + var/datum/game_mode/M = new T() + if(!(M.config_tag in modes)) + qdel(M) + continue + if(probabilities[M.config_tag]<=0) + qdel(M) + continue + if(min_pop[M.config_tag]) + M.required_players = min_pop[M.config_tag] + if(max_pop[M.config_tag]) + M.maximum_players = max_pop[M.config_tag] + if(M.can_start()) + var/final_weight = probabilities[M.config_tag] + if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3) + var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3) + var/adjustment = 0 + while(recent_round) + adjustment += repeated_mode_adjust[recent_round] + recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0) + final_weight *= ((100-adjustment)/100) + runnable_modes[M] = final_weight + return runnable_modes + +/datum/controller/configuration/proc/get_runnable_midround_modes(crew) + var/list/datum/game_mode/runnable_modes = new + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) + var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) + for(var/T in (gamemode_cache - SSticker.mode.type)) + var/datum/game_mode/M = new T() + if(!(M.config_tag in modes)) + qdel(M) + continue + if(probabilities[M.config_tag]<=0) + qdel(M) + continue + if(min_pop[M.config_tag]) + M.required_players = min_pop[M.config_tag] + if(max_pop[M.config_tag]) + M.maximum_players = max_pop[M.config_tag] + if(M.required_players <= crew) + if(M.maximum_players >= 0 && M.maximum_players < crew) + continue + runnable_modes[M] = probabilities[M.config_tag] + return runnable_modes + +/datum/controller/configuration/proc/LoadChatFilter() + var/list/in_character_filter = list() + + if(!fexists("[directory]/in_character_filter.txt")) + return + + log_config("Loading config file in_character_filter.txt...") + + for(var/line in world.file2list("[directory]/in_character_filter.txt")) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + in_character_filter += REGEX_QUOTE(line) + + ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null + + syncChatRegexes() + +//Message admins when you can. +/datum/controller/configuration/proc/DelayedMessageAdmins(text) + addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0) diff --git a/code/controllers/configuration/entries/comms.dm b/code/controllers/configuration/entries/comms.dm index 197e8af3697..ac4f23b3e10 100644 --- a/code/controllers/configuration/entries/comms.dm +++ b/code/controllers/configuration/entries/comms.dm @@ -1,23 +1,23 @@ -/datum/config_entry/string/comms_key - protection = CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/comms_key/ValidateAndSet(str_val) - return str_val != "default_pwd" && length(str_val) > 6 && ..() - -/datum/config_entry/keyed_list/cross_server - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_TEXT - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/keyed_list/cross_server/ValidateAndSet(str_val) - . = ..() - if(.) - var/list/newv = list() - for(var/I in config_entry_value) - newv[replacetext(I, "+", " ")] = config_entry_value[I] - config_entry_value = newv - -/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value) - return key_value != "byond:\\address:port" && ..() - -/datum/config_entry/string/cross_comms_name +/datum/config_entry/string/comms_key + protection = CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/comms_key/ValidateAndSet(str_val) + return str_val != "default_pwd" && length(str_val) > 6 && ..() + +/datum/config_entry/keyed_list/cross_server + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_TEXT + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/keyed_list/cross_server/ValidateAndSet(str_val) + . = ..() + if(.) + var/list/newv = list() + for(var/I in config_entry_value) + newv[replacetext(I, "+", " ")] = config_entry_value[I] + config_entry_value = newv + +/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value) + return key_value != "byond:\\address:port" && ..() + +/datum/config_entry/string/cross_comms_name diff --git a/code/controllers/configuration/entries/dbconfig.dm b/code/controllers/configuration/entries/dbconfig.dm index 86d19fc3d4b..517d5b1a6c0 100644 --- a/code/controllers/configuration/entries/dbconfig.dm +++ b/code/controllers/configuration/entries/dbconfig.dm @@ -1,50 +1,50 @@ -/datum/config_entry/flag/sql_enabled // for sql switching - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/address - config_entry_value = "localhost" - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/number/port - config_entry_value = 3306 - min_val = 0 - max_val = 65535 - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_database - config_entry_value = "test" - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_login - config_entry_value = "root" - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_password - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_tableprefix - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/number/query_debug_log_timeout - config_entry_value = 70 - min_val = 1 - protection = CONFIG_ENTRY_LOCKED - deprecated_by = /datum/config_entry/number/blocking_query_timeout - -/datum/config_entry/number/query_debug_log_timeout/DeprecationUpdate(value) - return value - -/datum/config_entry/number/async_query_timeout - config_entry_value = 10 - min_val = 0 - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/number/blocking_query_timeout - config_entry_value = 5 - min_val = 0 - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/number/bsql_thread_limit - config_entry_value = 50 - min_val = 1 - +/datum/config_entry/flag/sql_enabled // for sql switching + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/address + config_entry_value = "localhost" + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/number/port + config_entry_value = 3306 + min_val = 0 + max_val = 65535 + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_database + config_entry_value = "test" + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_login + config_entry_value = "root" + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_password + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_tableprefix + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/number/query_debug_log_timeout + config_entry_value = 70 + min_val = 1 + protection = CONFIG_ENTRY_LOCKED + deprecated_by = /datum/config_entry/number/blocking_query_timeout + +/datum/config_entry/number/query_debug_log_timeout/DeprecationUpdate(value) + return value + +/datum/config_entry/number/async_query_timeout + config_entry_value = 10 + min_val = 0 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/number/blocking_query_timeout + config_entry_value = 5 + min_val = 0 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/number/bsql_thread_limit + config_entry_value = 50 + min_val = 1 + diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 030813ade70..b03fcefe72b 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -1,404 +1,404 @@ -/datum/config_entry/number_list/repeated_mode_adjust - -/datum/config_entry/keyed_list/probability - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name) - return key_name in config.modes - -/datum/config_entry/keyed_list/max_pop - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name) - return key_name in config.modes - -/datum/config_entry/keyed_list/min_pop - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/number/damage_multiplier - config_entry_value = 1 - integer = FALSE - -/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on. - min_val = 0 - -/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access. - -/datum/config_entry/flag/assistants_have_maint_access - -/datum/config_entry/flag/security_has_maint_access - -/datum/config_entry/flag/everyone_has_maint_access - -/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts - -/datum/config_entry/flag/force_random_names - -/datum/config_entry/flag/humans_need_surnames - -/datum/config_entry/flag/allow_ai // allow ai job - -/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode - -/datum/config_entry/flag/disable_human_mood - -/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. - -/datum/config_entry/flag/disable_peaceborg - -/datum/config_entry/flag/disable_warops - -/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors - config_entry_value = 6 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/brother_scaling_coeff //how many players per brother team - config_entry_value = 25 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings - config_entry_value = 6 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/ecult_scaling_coeff //how much does the amount of players get divided by to determine e_cult - config_entry_value = 6 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions - config_entry_value = 8 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/traitor_objectives_amount - config_entry_value = 2 - min_val = 0 - -/datum/config_entry/number/brother_objectives_amount - config_entry_value = 2 - min_val = 0 - -/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors - -/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other - -/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other - -/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff - -/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling - -/datum/config_entry/flag/use_antag_rep // see game_options.txt for details - -/datum/config_entry/number/antag_rep_maximum - config_entry_value = 200 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/default_antag_tickets - config_entry_value = 100 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/max_tickets_per_roll - config_entry_value = 100 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system) - config_entry_value = 60 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist - config_entry_value = 0.7 - integer = FALSE - min_val = 0 - max_val = 1 - -/datum/config_entry/number/shuttle_refuel_delay - config_entry_value = 12000 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen - -/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go. - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race. - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game - -/datum/config_entry/flag/no_summon_guns //No - -/datum/config_entry/flag/no_summon_magic //Fun - -/datum/config_entry/flag/no_summon_events //Allowed - -/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes. - -/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station - config_entry_value = 55 - integer = FALSE - min_val = 30 - -/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join - -/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join - -/datum/config_entry/string/alert_green - config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." - -/datum/config_entry/string/alert_blue_upto - config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted." - -/datum/config_entry/string/alert_blue_downto - config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed." - -/datum/config_entry/string/alert_red_upto - config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." - -/datum/config_entry/string/alert_red_downto - config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." - -/datum/config_entry/string/alert_delta - config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." - -/datum/config_entry/flag/revival_pod_plants - -/datum/config_entry/number/revival_brain_life - config_entry_value = -1 - integer = FALSE - min_val = -1 - -/datum/config_entry/flag/ooc_during_round - -/datum/config_entry/number/commendations - integer = FALSE - -/datum/config_entry/flag/emojis - -/datum/config_entry/keyed_list/multiplicative_movespeed - key_mode = KEY_MODE_TYPE - value_mode = VALUE_MODE_NUM - config_entry_value = list( //DEFAULTS - /mob/living/simple_animal = 1, - /mob/living/silicon/pai = 1, - /mob/living/carbon/alien/humanoid/hunter = -1, - /mob/living/carbon/alien/humanoid/royal/praetorian = 1, - /mob/living/carbon/alien/humanoid/royal/queen = 3 - ) - -/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet() - . = ..() - if(.) - update_config_movespeed_type_lookup(TRUE) - -/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value) - . = ..() - if(. && (var_name == NAMEOF(src, config_entry_value))) - update_config_movespeed_type_lookup(TRUE) - -/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs. - abstract_type = /datum/config_entry/number/movedelay - -/datum/config_entry/number/movedelay/ValidateAndSet() - . = ..() - if(.) - update_mob_config_movespeeds() - -/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value) - . = ..() - if(. && (var_name == NAMEOF(src, config_entry_value))) - update_mob_config_movespeeds() - -/datum/config_entry/number/movedelay/run_delay - integer = FALSE - -/datum/config_entry/number/movedelay/run_delay/ValidateAndSet() - . = ..() - var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/run) - M.sync() - -/datum/config_entry/number/movedelay/walk_delay - integer = FALSE - -/datum/config_entry/number/movedelay/walk_delay/ValidateAndSet() - . = ..() - var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk) - M.sync() - -/////////////////////////////////////////////////Outdated move delay -/datum/config_entry/number/outdated_movedelay - deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed - abstract_type = /datum/config_entry/number/outdated_movedelay - integer = FALSE - var/movedelay_type - -/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value) - return "[movedelay_type] [value]" - -/datum/config_entry/number/outdated_movedelay/human_delay - movedelay_type = /mob/living/carbon/human -/datum/config_entry/number/outdated_movedelay/robot_delay - movedelay_type = /mob/living/silicon/robot -/datum/config_entry/number/outdated_movedelay/monkey_delay - movedelay_type = /mob/living/carbon/monkey -/datum/config_entry/number/outdated_movedelay/alien_delay - movedelay_type = /mob/living/carbon/alien -/datum/config_entry/number/outdated_movedelay/slime_delay - movedelay_type = /mob/living/simple_animal/slime -/datum/config_entry/number/outdated_movedelay/animal_delay - movedelay_type = /mob/living/simple_animal -///////////////////////////////////////////////// - -/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded - -/datum/config_entry/flag/roundstart_away //Will random away mission be loaded. - -/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled. - config_entry_value = 18000 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/ghost_interaction - -/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death - -/datum/config_entry/flag/silent_ai -/datum/config_entry/flag/silent_borg - -/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff - -/datum/config_entry/number/default_laws //Controls what laws the AI spawns with. - config_entry_value = 0 - min_val = 0 - max_val = 3 - -/datum/config_entry/number/silicon_max_law_amount - config_entry_value = 12 - min_val = 0 - -/datum/config_entry/keyed_list/random_laws - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/law_weight - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - splitter = "," - -/datum/config_entry/number/max_law_len - config_entry_value = 1024 - -/datum/config_entry/number/overflow_cap - config_entry_value = -1 - min_val = -1 - -/datum/config_entry/string/overflow_job - config_entry_value = "Assistant" - -/datum/config_entry/flag/starlight -/datum/config_entry/flag/grey_assistants - -/datum/config_entry/number/lavaland_budget - config_entry_value = 60 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/icemoon_budget - config_entry_value = 90 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/space_budget - config_entry_value = 16 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set - -/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts - config_entry_value = 1 - min_val = 0 - integer = FALSE - -/datum/config_entry/number/events_min_players_mul - config_entry_value = 1 - min_val = 0 - integer = FALSE - -/datum/config_entry/number/mice_roundstart - config_entry_value = 10 - min_val = 0 - -/datum/config_entry/number/bombcap - config_entry_value = 14 - min_val = 4 - -/datum/config_entry/number/bombcap/ValidateAndSet(str_val) - . = ..() - if(.) - GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4) - GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2) - GLOB.MAX_EX_LIGHT_RANGE = config_entry_value - GLOB.MAX_EX_FLASH_RANGE = config_entry_value - GLOB.MAX_EX_FLAME_RANGE = config_entry_value - -/datum/config_entry/number/emergency_shuttle_autocall_threshold - min_val = 0 - max_val = 1 - integer = FALSE - -/datum/config_entry/flag/roundstart_traits - -/datum/config_entry/flag/enable_night_shifts - -/datum/config_entry/flag/randomize_shift_time - -/datum/config_entry/flag/shift_time_realtime - -/datum/config_entry/keyed_list/antag_rep - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/number/monkeycap - config_entry_value = 64 - min_val = 0 - -/datum/config_entry/number/ratcap - config_entry_value = 64 - min_val = 0 - -/datum/config_entry/number/maxfine - config_entry_value = 1000 - min_val = 0 - -/datum/config_entry/flag/dynamic_config_enabled +/datum/config_entry/number_list/repeated_mode_adjust + +/datum/config_entry/keyed_list/probability + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name) + return key_name in config.modes + +/datum/config_entry/keyed_list/max_pop + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name) + return key_name in config.modes + +/datum/config_entry/keyed_list/min_pop + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/number/damage_multiplier + config_entry_value = 1 + integer = FALSE + +/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on. + min_val = 0 + +/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access. + +/datum/config_entry/flag/assistants_have_maint_access + +/datum/config_entry/flag/security_has_maint_access + +/datum/config_entry/flag/everyone_has_maint_access + +/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts + +/datum/config_entry/flag/force_random_names + +/datum/config_entry/flag/humans_need_surnames + +/datum/config_entry/flag/allow_ai // allow ai job + +/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode + +/datum/config_entry/flag/disable_human_mood + +/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. + +/datum/config_entry/flag/disable_peaceborg + +/datum/config_entry/flag/disable_warops + +/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors + config_entry_value = 6 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/brother_scaling_coeff //how many players per brother team + config_entry_value = 25 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings + config_entry_value = 6 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/ecult_scaling_coeff //how much does the amount of players get divided by to determine e_cult + config_entry_value = 6 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions + config_entry_value = 8 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/traitor_objectives_amount + config_entry_value = 2 + min_val = 0 + +/datum/config_entry/number/brother_objectives_amount + config_entry_value = 2 + min_val = 0 + +/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors + +/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other + +/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other + +/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff + +/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling + +/datum/config_entry/flag/use_antag_rep // see game_options.txt for details + +/datum/config_entry/number/antag_rep_maximum + config_entry_value = 200 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/default_antag_tickets + config_entry_value = 100 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/max_tickets_per_roll + config_entry_value = 100 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system) + config_entry_value = 60 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist + config_entry_value = 0.7 + integer = FALSE + min_val = 0 + max_val = 1 + +/datum/config_entry/number/shuttle_refuel_delay + config_entry_value = 12000 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen + +/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go. + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race. + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game + +/datum/config_entry/flag/no_summon_guns //No + +/datum/config_entry/flag/no_summon_magic //Fun + +/datum/config_entry/flag/no_summon_events //Allowed + +/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes. + +/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station + config_entry_value = 55 + integer = FALSE + min_val = 30 + +/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join + +/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join + +/datum/config_entry/string/alert_green + config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." + +/datum/config_entry/string/alert_blue_upto + config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted." + +/datum/config_entry/string/alert_blue_downto + config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed." + +/datum/config_entry/string/alert_red_upto + config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." + +/datum/config_entry/string/alert_red_downto + config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." + +/datum/config_entry/string/alert_delta + config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." + +/datum/config_entry/flag/revival_pod_plants + +/datum/config_entry/number/revival_brain_life + config_entry_value = -1 + integer = FALSE + min_val = -1 + +/datum/config_entry/flag/ooc_during_round + +/datum/config_entry/number/commendations + integer = FALSE + +/datum/config_entry/flag/emojis + +/datum/config_entry/keyed_list/multiplicative_movespeed + key_mode = KEY_MODE_TYPE + value_mode = VALUE_MODE_NUM + config_entry_value = list( //DEFAULTS + /mob/living/simple_animal = 1, + /mob/living/silicon/pai = 1, + /mob/living/carbon/alien/humanoid/hunter = -1, + /mob/living/carbon/alien/humanoid/royal/praetorian = 1, + /mob/living/carbon/alien/humanoid/royal/queen = 3 + ) + +/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet() + . = ..() + if(.) + update_config_movespeed_type_lookup(TRUE) + +/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value) + . = ..() + if(. && (var_name == NAMEOF(src, config_entry_value))) + update_config_movespeed_type_lookup(TRUE) + +/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs. + abstract_type = /datum/config_entry/number/movedelay + +/datum/config_entry/number/movedelay/ValidateAndSet() + . = ..() + if(.) + update_mob_config_movespeeds() + +/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value) + . = ..() + if(. && (var_name == NAMEOF(src, config_entry_value))) + update_mob_config_movespeeds() + +/datum/config_entry/number/movedelay/run_delay + integer = FALSE + +/datum/config_entry/number/movedelay/run_delay/ValidateAndSet() + . = ..() + var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/run) + M.sync() + +/datum/config_entry/number/movedelay/walk_delay + integer = FALSE + +/datum/config_entry/number/movedelay/walk_delay/ValidateAndSet() + . = ..() + var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk) + M.sync() + +/////////////////////////////////////////////////Outdated move delay +/datum/config_entry/number/outdated_movedelay + deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed + abstract_type = /datum/config_entry/number/outdated_movedelay + integer = FALSE + var/movedelay_type + +/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value) + return "[movedelay_type] [value]" + +/datum/config_entry/number/outdated_movedelay/human_delay + movedelay_type = /mob/living/carbon/human +/datum/config_entry/number/outdated_movedelay/robot_delay + movedelay_type = /mob/living/silicon/robot +/datum/config_entry/number/outdated_movedelay/monkey_delay + movedelay_type = /mob/living/carbon/monkey +/datum/config_entry/number/outdated_movedelay/alien_delay + movedelay_type = /mob/living/carbon/alien +/datum/config_entry/number/outdated_movedelay/slime_delay + movedelay_type = /mob/living/simple_animal/slime +/datum/config_entry/number/outdated_movedelay/animal_delay + movedelay_type = /mob/living/simple_animal +///////////////////////////////////////////////// + +/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded + +/datum/config_entry/flag/roundstart_away //Will random away mission be loaded. + +/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled. + config_entry_value = 18000 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/ghost_interaction + +/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death + +/datum/config_entry/flag/silent_ai +/datum/config_entry/flag/silent_borg + +/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff + +/datum/config_entry/number/default_laws //Controls what laws the AI spawns with. + config_entry_value = 0 + min_val = 0 + max_val = 3 + +/datum/config_entry/number/silicon_max_law_amount + config_entry_value = 12 + min_val = 0 + +/datum/config_entry/keyed_list/random_laws + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/law_weight + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + splitter = "," + +/datum/config_entry/number/max_law_len + config_entry_value = 1024 + +/datum/config_entry/number/overflow_cap + config_entry_value = -1 + min_val = -1 + +/datum/config_entry/string/overflow_job + config_entry_value = "Assistant" + +/datum/config_entry/flag/starlight +/datum/config_entry/flag/grey_assistants + +/datum/config_entry/number/lavaland_budget + config_entry_value = 60 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/icemoon_budget + config_entry_value = 90 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/space_budget + config_entry_value = 16 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set + +/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts + config_entry_value = 1 + min_val = 0 + integer = FALSE + +/datum/config_entry/number/events_min_players_mul + config_entry_value = 1 + min_val = 0 + integer = FALSE + +/datum/config_entry/number/mice_roundstart + config_entry_value = 10 + min_val = 0 + +/datum/config_entry/number/bombcap + config_entry_value = 14 + min_val = 4 + +/datum/config_entry/number/bombcap/ValidateAndSet(str_val) + . = ..() + if(.) + GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4) + GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2) + GLOB.MAX_EX_LIGHT_RANGE = config_entry_value + GLOB.MAX_EX_FLASH_RANGE = config_entry_value + GLOB.MAX_EX_FLAME_RANGE = config_entry_value + +/datum/config_entry/number/emergency_shuttle_autocall_threshold + min_val = 0 + max_val = 1 + integer = FALSE + +/datum/config_entry/flag/roundstart_traits + +/datum/config_entry/flag/enable_night_shifts + +/datum/config_entry/flag/randomize_shift_time + +/datum/config_entry/flag/shift_time_realtime + +/datum/config_entry/keyed_list/antag_rep + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/number/monkeycap + config_entry_value = 64 + min_val = 0 + +/datum/config_entry/number/ratcap + config_entry_value = 64 + min_val = 0 + +/datum/config_entry/number/maxfine + config_entry_value = 1000 + min_val = 0 + +/datum/config_entry/flag/dynamic_config_enabled diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index c0733cc38e3..e829aae0adb 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -1,505 +1,505 @@ -/datum/config_entry/flag/autoadmin // if autoadmin is enabled - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/autoadmin_rank // the rank for autoadmins - config_entry_value = "Game Master" - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_players - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/number/auto_deadmin_timegate - config_entry_value = null - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_antagonists - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_heads - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_silicons - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_security - protection = CONFIG_ENTRY_LOCKED - - -/datum/config_entry/string/servername // server name (the name of the game window) - -/datum/config_entry/string/serversqlname // short form server name used for the DB - -/datum/config_entry/string/stationname // station name (the name of the station in-game) - -/datum/config_entry/number/lobby_countdown // In between round countdown. - config_entry_value = 120 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown - config_entry_value = 25 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/hub // if the game appears on the hub or not - -/datum/config_entry/number/max_hub_pop //At what pop to take hub off the server - config_entry_value = 0 //0 means disabled - integer = TRUE - min_val = 0 - -/datum/config_entry/flag/log_ooc // log OOC channel - -/datum/config_entry/flag/log_access // log login/logout - -/datum/config_entry/flag/log_say // log client say - -/datum/config_entry/flag/log_admin // log admin actions - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/log_prayer // log prayers - -/datum/config_entry/flag/log_law // log lawchanges - -/datum/config_entry/flag/log_game // log game events - -/datum/config_entry/flag/log_mecha // log mech data - -/datum/config_entry/flag/log_virus // log virology data - -/datum/config_entry/flag/log_cloning // log cloning actions. - -/datum/config_entry/flag/log_vote // log voting - -/datum/config_entry/flag/log_whisper // log client whisper - -/datum/config_entry/flag/log_attack // log attack messages - -/datum/config_entry/flag/log_emote // log emotes - -/datum/config_entry/flag/log_econ // log economy actions - -/datum/config_entry/flag/log_adminchat // log admin chat messages - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/log_pda // log pda messages - -/datum/config_entry/flag/log_telecomms // log telecomms messages - -/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases. - -/datum/config_entry/flag/log_world_topic // log all world.Topic() calls - -/datum/config_entry/flag/log_manifest // log crew manifest to seperate file - -/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file - -/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console - -/datum/config_entry/flag/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour - -/datum/config_entry/flag/allow_admin_asaycolor //Allows admins with relevant permissions to have a personalized asay color - -/datum/config_entry/flag/allow_vote_restart // allow votes to restart - -/datum/config_entry/flag/allow_vote_mode // allow votes to change mode - -/datum/config_entry/flag/allow_vote_map // allow votes to change map - -/datum/config_entry/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) - config_entry_value = 6000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) - config_entry_value = 600 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/default_no_vote // vote does not default to nochange/norestart - -/datum/config_entry/flag/no_dead_vote // dead people can't vote - -/datum/config_entry/flag/popup_admin_pm // adminPMs to non-admins show in a pop-up 'reply' window when set - -/datum/config_entry/number/fps - config_entry_value = 20 - integer = FALSE - min_val = 1 - max_val = 100 //byond will start crapping out at 50, so this is just ridic - var/sync_validate = FALSE - -/datum/config_entry/number/fps/ValidateAndSet(str_val) - . = ..() - if(.) - sync_validate = TRUE - var/datum/config_entry/number/ticklag/TL = config.entries_by_type[/datum/config_entry/number/ticklag] - if(!TL.sync_validate) - TL.ValidateAndSet(10 / config_entry_value) - sync_validate = FALSE - -/datum/config_entry/number/ticklag - integer = FALSE - var/sync_validate = FALSE - -/datum/config_entry/number/ticklag/New() //ticklag weirdly just mirrors fps - var/datum/config_entry/CE = /datum/config_entry/number/fps - config_entry_value = 10 / initial(CE.config_entry_value) - ..() - -/datum/config_entry/number/ticklag/ValidateAndSet(str_val) - . = text2num(str_val) > 0 && ..() - if(.) - sync_validate = TRUE - var/datum/config_entry/number/fps/FPS = config.entries_by_type[/datum/config_entry/number/fps] - if(!FPS.sync_validate) - FPS.ValidateAndSet(10 / config_entry_value) - sync_validate = FALSE - -/datum/config_entry/flag/allow_holidays - -/datum/config_entry/number/tick_limit_mc_init //SSinitialization throttling - config_entry_value = TICK_LIMIT_MC_INIT_DEFAULT - min_val = 0 //oranges warned us - integer = FALSE - -/datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/protect_legacy_ranks //Stops any ranks loaded by the legacy system from having their flags edited by the permissions panel - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1 - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/hostedby - -/datum/config_entry/flag/norespawn - -/datum/config_entry/flag/usewhitelist - -/datum/config_entry/flag/use_age_restriction_for_jobs //Do jobs use account age restrictions? --requires database - -/datum/config_entry/flag/use_account_age_for_jobs //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected. - -/datum/config_entry/flag/use_exp_tracking - -/datum/config_entry/flag/use_exp_restrictions_heads - -/datum/config_entry/number/use_exp_restrictions_heads_hours - config_entry_value = 0 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/use_exp_restrictions_heads_department - -/datum/config_entry/flag/use_exp_restrictions_other - -/datum/config_entry/flag/use_exp_restrictions_admin_bypass - -/datum/config_entry/string/server - -/datum/config_entry/string/banappeals - -/datum/config_entry/string/wikiurl - config_entry_value = "http://www.tgstation13.org/wiki" - -/datum/config_entry/string/forumurl - config_entry_value = "http://tgstation13.org/phpBB/index.php" - -/datum/config_entry/string/rulesurl - config_entry_value = "http://www.tgstation13.org/wiki/Rules" - -/datum/config_entry/string/githuburl - config_entry_value = "https://www.github.com/tgstation/-tg-station" - -/datum/config_entry/string/roundstatsurl - -/datum/config_entry/string/gamelogurl - -/datum/config_entry/flag/guest_ban - -/datum/config_entry/number/id_console_jobslot_delay - config_entry_value = 30 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive - config_entry_value = 3000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/inactivity_period/ValidateAndSet(str_val) - . = ..() - if(.) - config_entry_value *= 10 //documented as seconds in config.txt - -/datum/config_entry/number/afk_period //time in ds until a player is considered inactive - config_entry_value = 3000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/afk_period/ValidateAndSet(str_val) - . = ..() - if(.) - config_entry_value *= 10 //documented as seconds in config.txt - -/datum/config_entry/flag/kick_inactive //force disconnect for inactive players - -/datum/config_entry/flag/load_jobs_from_txt - -/datum/config_entry/flag/forbid_singulo_possession - -/datum/config_entry/flag/automute_on //enables automuting/spam prevention - -/datum/config_entry/string/panic_server_name - -/datum/config_entry/string/panic_server_name/ValidateAndSet(str_val) - return str_val != "\[Put the name here\]" && ..() - -/datum/config_entry/string/panic_server_address //Reconnect a player this linked server if this server isn't accepting new players - -/datum/config_entry/string/panic_server_address/ValidateAndSet(str_val) - return str_val != "byond://address:port" && ..() - -/datum/config_entry/string/invoke_youtubedl - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/flag/show_irc_name - -/datum/config_entry/flag/see_own_notes //Can players see their own admin notes - -/datum/config_entry/number/note_fresh_days - config_entry_value = null - min_val = 0 - integer = FALSE - -/datum/config_entry/number/note_stale_days - config_entry_value = null - min_val = 0 - integer = FALSE - -/datum/config_entry/flag/maprotation - -/datum/config_entry/number/soft_popcap - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/hard_popcap - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/extreme_popcap - config_entry_value = null - min_val = 0 - -/datum/config_entry/string/soft_popcap_message - config_entry_value = "Be warned that the server is currently serving a high number of users, consider using alternative game servers." - -/datum/config_entry/string/hard_popcap_message - config_entry_value = "The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers." - -/datum/config_entry/string/extreme_popcap_message - config_entry_value = "The server is currently serving a high number of users, find alternative servers." - -/datum/config_entry/flag/byond_member_bypass_popcap - -/datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting - -/datum/config_entry/string/panic_bunker_message - config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players." - -/datum/config_entry/number/notify_new_player_age // how long do we notify admins of a new player - min_val = -1 - -/datum/config_entry/number/notify_new_player_account_age // how long do we notify admins of a new byond account - min_val = 0 - -/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? - -/datum/config_entry/flag/check_randomizer - -/datum/config_entry/string/ipintel_email - -/datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) - return str_val != "ch@nge.me" && ..() - -/datum/config_entry/number/ipintel_rating_bad - config_entry_value = 1 - integer = FALSE - min_val = 0 - max_val = 1 - -/datum/config_entry/number/ipintel_save_good - config_entry_value = 12 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/ipintel_save_bad - config_entry_value = 1 - integer = FALSE - min_val = 0 - -/datum/config_entry/string/ipintel_domain - config_entry_value = "check.getipintel.net" - -/datum/config_entry/flag/aggressive_changelog - -/datum/config_entry/flag/autoconvert_notes //if all connecting player's notes should attempt to be converted to the database - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/allow_webclient - -/datum/config_entry/flag/webclient_only_byond_members - -/datum/config_entry/flag/announce_admin_logout - -/datum/config_entry/flag/announce_admin_login - -/datum/config_entry/flag/allow_map_voting - deprecated_by = /datum/config_entry/flag/preference_map_voting - -/datum/config_entry/flag/allow_map_voting/DeprecationUpdate(value) - return value - -/datum/config_entry/flag/preference_map_voting - -/datum/config_entry/number/client_warn_version - config_entry_value = null - min_val = 500 - -/datum/config_entry/string/client_warn_message - config_entry_value = "Your version of byond may have issues or be blocked from accessing this server in the future." - -/datum/config_entry/flag/client_warn_popup - -/datum/config_entry/number/client_error_version - config_entry_value = null - min_val = 500 - -/datum/config_entry/string/client_error_message - config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server." - -/datum/config_entry/number/client_error_build - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/minute_topic_limit - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/second_topic_limit - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/minute_click_limit - config_entry_value = 400 - min_val = 0 - -/datum/config_entry/number/second_click_limit - config_entry_value = 15 - min_val = 0 - -/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error - config_entry_value = 600 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/error_limit // How many occurrences before the next will silence them - config_entry_value = 50 - -/datum/config_entry/number/error_silence_time // How long a unique error will be silenced for - config_entry_value = 6000 - integer = FALSE - -/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error - config_entry_value = 50 - integer = FALSE - -/datum/config_entry/flag/irc_announce_new_game - deprecated_by = /datum/config_entry/string/chat_announce_new_game - -/datum/config_entry/flag/irc_announce_new_game/DeprecationUpdate(value) - return "" //default broadcast - -/datum/config_entry/string/chat_announce_new_game - config_entry_value = null - -/datum/config_entry/string/chat_new_game_notifications - config_entry_value = null - -/datum/config_entry/flag/debug_admin_hrefs - -/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate - integer = FALSE - config_entry_value = 1 - -/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate - integer = FALSE - config_entry_value = 1.1 - -/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount - config_entry_value = 65 - -/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount - config_entry_value = 60 - -/datum/config_entry/number/mc_tick_rate - abstract_type = /datum/config_entry/number/mc_tick_rate - -/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val) - . = ..() - if (.) - Master.UpdateTickRate() - -/datum/config_entry/flag/resume_after_initializations - -/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val) - . = ..() - if(. && Master.current_runlevel) - world.sleep_offline = !config_entry_value - -/datum/config_entry/number/rounds_until_hard_restart - config_entry_value = -1 - min_val = 0 - -/datum/config_entry/string/default_view - config_entry_value = "15x15" - -/datum/config_entry/string/default_view_square - config_entry_value = "15x15" - -/datum/config_entry/flag/log_pictures - -/datum/config_entry/flag/picture_logging_camera - - -/datum/config_entry/flag/reopen_roundstart_suicide_roles - -/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_positions - -/datum/config_entry/number/reopen_roundstart_suicide_roles_delay - min_val = 30 - -/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_report - -/datum/config_entry/flag/auto_profile - -// DISCORD ROLE STUFFS -// Using strings for everything because BYOND does not like numbers this big -// (exception to the above is required living hours haha) -/datum/config_entry/flag/enable_discord_autorole - -/datum/config_entry/number/required_living_hours - -/datum/config_entry/string/discord_token - -/datum/config_entry/string/discord_guildid - -/datum/config_entry/string/discord_roleid +/datum/config_entry/flag/autoadmin // if autoadmin is enabled + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/autoadmin_rank // the rank for autoadmins + config_entry_value = "Game Master" + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_players + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/number/auto_deadmin_timegate + config_entry_value = null + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_antagonists + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_heads + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_silicons + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_security + protection = CONFIG_ENTRY_LOCKED + + +/datum/config_entry/string/servername // server name (the name of the game window) + +/datum/config_entry/string/serversqlname // short form server name used for the DB + +/datum/config_entry/string/stationname // station name (the name of the station in-game) + +/datum/config_entry/number/lobby_countdown // In between round countdown. + config_entry_value = 120 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown + config_entry_value = 25 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/hub // if the game appears on the hub or not + +/datum/config_entry/number/max_hub_pop //At what pop to take hub off the server + config_entry_value = 0 //0 means disabled + integer = TRUE + min_val = 0 + +/datum/config_entry/flag/log_ooc // log OOC channel + +/datum/config_entry/flag/log_access // log login/logout + +/datum/config_entry/flag/log_say // log client say + +/datum/config_entry/flag/log_admin // log admin actions + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/log_prayer // log prayers + +/datum/config_entry/flag/log_law // log lawchanges + +/datum/config_entry/flag/log_game // log game events + +/datum/config_entry/flag/log_mecha // log mech data + +/datum/config_entry/flag/log_virus // log virology data + +/datum/config_entry/flag/log_cloning // log cloning actions. + +/datum/config_entry/flag/log_vote // log voting + +/datum/config_entry/flag/log_whisper // log client whisper + +/datum/config_entry/flag/log_attack // log attack messages + +/datum/config_entry/flag/log_emote // log emotes + +/datum/config_entry/flag/log_econ // log economy actions + +/datum/config_entry/flag/log_adminchat // log admin chat messages + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/log_pda // log pda messages + +/datum/config_entry/flag/log_telecomms // log telecomms messages + +/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases. + +/datum/config_entry/flag/log_world_topic // log all world.Topic() calls + +/datum/config_entry/flag/log_manifest // log crew manifest to seperate file + +/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file + +/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console + +/datum/config_entry/flag/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour + +/datum/config_entry/flag/allow_admin_asaycolor //Allows admins with relevant permissions to have a personalized asay color + +/datum/config_entry/flag/allow_vote_restart // allow votes to restart + +/datum/config_entry/flag/allow_vote_mode // allow votes to change mode + +/datum/config_entry/flag/allow_vote_map // allow votes to change map + +/datum/config_entry/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) + config_entry_value = 6000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) + config_entry_value = 600 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/default_no_vote // vote does not default to nochange/norestart + +/datum/config_entry/flag/no_dead_vote // dead people can't vote + +/datum/config_entry/flag/popup_admin_pm // adminPMs to non-admins show in a pop-up 'reply' window when set + +/datum/config_entry/number/fps + config_entry_value = 20 + integer = FALSE + min_val = 1 + max_val = 100 //byond will start crapping out at 50, so this is just ridic + var/sync_validate = FALSE + +/datum/config_entry/number/fps/ValidateAndSet(str_val) + . = ..() + if(.) + sync_validate = TRUE + var/datum/config_entry/number/ticklag/TL = config.entries_by_type[/datum/config_entry/number/ticklag] + if(!TL.sync_validate) + TL.ValidateAndSet(10 / config_entry_value) + sync_validate = FALSE + +/datum/config_entry/number/ticklag + integer = FALSE + var/sync_validate = FALSE + +/datum/config_entry/number/ticklag/New() //ticklag weirdly just mirrors fps + var/datum/config_entry/CE = /datum/config_entry/number/fps + config_entry_value = 10 / initial(CE.config_entry_value) + ..() + +/datum/config_entry/number/ticklag/ValidateAndSet(str_val) + . = text2num(str_val) > 0 && ..() + if(.) + sync_validate = TRUE + var/datum/config_entry/number/fps/FPS = config.entries_by_type[/datum/config_entry/number/fps] + if(!FPS.sync_validate) + FPS.ValidateAndSet(10 / config_entry_value) + sync_validate = FALSE + +/datum/config_entry/flag/allow_holidays + +/datum/config_entry/number/tick_limit_mc_init //SSinitialization throttling + config_entry_value = TICK_LIMIT_MC_INIT_DEFAULT + min_val = 0 //oranges warned us + integer = FALSE + +/datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_ranks //Stops any ranks loaded by the legacy system from having their flags edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/hostedby + +/datum/config_entry/flag/norespawn + +/datum/config_entry/flag/usewhitelist + +/datum/config_entry/flag/use_age_restriction_for_jobs //Do jobs use account age restrictions? --requires database + +/datum/config_entry/flag/use_account_age_for_jobs //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected. + +/datum/config_entry/flag/use_exp_tracking + +/datum/config_entry/flag/use_exp_restrictions_heads + +/datum/config_entry/number/use_exp_restrictions_heads_hours + config_entry_value = 0 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/use_exp_restrictions_heads_department + +/datum/config_entry/flag/use_exp_restrictions_other + +/datum/config_entry/flag/use_exp_restrictions_admin_bypass + +/datum/config_entry/string/server + +/datum/config_entry/string/banappeals + +/datum/config_entry/string/wikiurl + config_entry_value = "http://www.tgstation13.org/wiki" + +/datum/config_entry/string/forumurl + config_entry_value = "http://tgstation13.org/phpBB/index.php" + +/datum/config_entry/string/rulesurl + config_entry_value = "http://www.tgstation13.org/wiki/Rules" + +/datum/config_entry/string/githuburl + config_entry_value = "https://www.github.com/tgstation/-tg-station" + +/datum/config_entry/string/roundstatsurl + +/datum/config_entry/string/gamelogurl + +/datum/config_entry/flag/guest_ban + +/datum/config_entry/number/id_console_jobslot_delay + config_entry_value = 30 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive + config_entry_value = 3000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/inactivity_period/ValidateAndSet(str_val) + . = ..() + if(.) + config_entry_value *= 10 //documented as seconds in config.txt + +/datum/config_entry/number/afk_period //time in ds until a player is considered inactive + config_entry_value = 3000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/afk_period/ValidateAndSet(str_val) + . = ..() + if(.) + config_entry_value *= 10 //documented as seconds in config.txt + +/datum/config_entry/flag/kick_inactive //force disconnect for inactive players + +/datum/config_entry/flag/load_jobs_from_txt + +/datum/config_entry/flag/forbid_singulo_possession + +/datum/config_entry/flag/automute_on //enables automuting/spam prevention + +/datum/config_entry/string/panic_server_name + +/datum/config_entry/string/panic_server_name/ValidateAndSet(str_val) + return str_val != "\[Put the name here\]" && ..() + +/datum/config_entry/string/panic_server_address //Reconnect a player this linked server if this server isn't accepting new players + +/datum/config_entry/string/panic_server_address/ValidateAndSet(str_val) + return str_val != "byond://address:port" && ..() + +/datum/config_entry/string/invoke_youtubedl + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/flag/show_irc_name + +/datum/config_entry/flag/see_own_notes //Can players see their own admin notes + +/datum/config_entry/number/note_fresh_days + config_entry_value = null + min_val = 0 + integer = FALSE + +/datum/config_entry/number/note_stale_days + config_entry_value = null + min_val = 0 + integer = FALSE + +/datum/config_entry/flag/maprotation + +/datum/config_entry/number/soft_popcap + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/hard_popcap + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/extreme_popcap + config_entry_value = null + min_val = 0 + +/datum/config_entry/string/soft_popcap_message + config_entry_value = "Be warned that the server is currently serving a high number of users, consider using alternative game servers." + +/datum/config_entry/string/hard_popcap_message + config_entry_value = "The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers." + +/datum/config_entry/string/extreme_popcap_message + config_entry_value = "The server is currently serving a high number of users, find alternative servers." + +/datum/config_entry/flag/byond_member_bypass_popcap + +/datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting + +/datum/config_entry/string/panic_bunker_message + config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players." + +/datum/config_entry/number/notify_new_player_age // how long do we notify admins of a new player + min_val = -1 + +/datum/config_entry/number/notify_new_player_account_age // how long do we notify admins of a new byond account + min_val = 0 + +/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? + +/datum/config_entry/flag/check_randomizer + +/datum/config_entry/string/ipintel_email + +/datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) + return str_val != "ch@nge.me" && ..() + +/datum/config_entry/number/ipintel_rating_bad + config_entry_value = 1 + integer = FALSE + min_val = 0 + max_val = 1 + +/datum/config_entry/number/ipintel_save_good + config_entry_value = 12 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/ipintel_save_bad + config_entry_value = 1 + integer = FALSE + min_val = 0 + +/datum/config_entry/string/ipintel_domain + config_entry_value = "check.getipintel.net" + +/datum/config_entry/flag/aggressive_changelog + +/datum/config_entry/flag/autoconvert_notes //if all connecting player's notes should attempt to be converted to the database + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/allow_webclient + +/datum/config_entry/flag/webclient_only_byond_members + +/datum/config_entry/flag/announce_admin_logout + +/datum/config_entry/flag/announce_admin_login + +/datum/config_entry/flag/allow_map_voting + deprecated_by = /datum/config_entry/flag/preference_map_voting + +/datum/config_entry/flag/allow_map_voting/DeprecationUpdate(value) + return value + +/datum/config_entry/flag/preference_map_voting + +/datum/config_entry/number/client_warn_version + config_entry_value = null + min_val = 500 + +/datum/config_entry/string/client_warn_message + config_entry_value = "Your version of byond may have issues or be blocked from accessing this server in the future." + +/datum/config_entry/flag/client_warn_popup + +/datum/config_entry/number/client_error_version + config_entry_value = null + min_val = 500 + +/datum/config_entry/string/client_error_message + config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server." + +/datum/config_entry/number/client_error_build + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/minute_topic_limit + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/second_topic_limit + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/minute_click_limit + config_entry_value = 400 + min_val = 0 + +/datum/config_entry/number/second_click_limit + config_entry_value = 15 + min_val = 0 + +/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error + config_entry_value = 600 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/error_limit // How many occurrences before the next will silence them + config_entry_value = 50 + +/datum/config_entry/number/error_silence_time // How long a unique error will be silenced for + config_entry_value = 6000 + integer = FALSE + +/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error + config_entry_value = 50 + integer = FALSE + +/datum/config_entry/flag/irc_announce_new_game + deprecated_by = /datum/config_entry/string/chat_announce_new_game + +/datum/config_entry/flag/irc_announce_new_game/DeprecationUpdate(value) + return "" //default broadcast + +/datum/config_entry/string/chat_announce_new_game + config_entry_value = null + +/datum/config_entry/string/chat_new_game_notifications + config_entry_value = null + +/datum/config_entry/flag/debug_admin_hrefs + +/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate + integer = FALSE + config_entry_value = 1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate + integer = FALSE + config_entry_value = 1.1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount + config_entry_value = 65 + +/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount + config_entry_value = 60 + +/datum/config_entry/number/mc_tick_rate + abstract_type = /datum/config_entry/number/mc_tick_rate + +/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val) + . = ..() + if (.) + Master.UpdateTickRate() + +/datum/config_entry/flag/resume_after_initializations + +/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val) + . = ..() + if(. && Master.current_runlevel) + world.sleep_offline = !config_entry_value + +/datum/config_entry/number/rounds_until_hard_restart + config_entry_value = -1 + min_val = 0 + +/datum/config_entry/string/default_view + config_entry_value = "15x15" + +/datum/config_entry/string/default_view_square + config_entry_value = "15x15" + +/datum/config_entry/flag/log_pictures + +/datum/config_entry/flag/picture_logging_camera + + +/datum/config_entry/flag/reopen_roundstart_suicide_roles + +/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_positions + +/datum/config_entry/number/reopen_roundstart_suicide_roles_delay + min_val = 30 + +/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_report + +/datum/config_entry/flag/auto_profile + +// DISCORD ROLE STUFFS +// Using strings for everything because BYOND does not like numbers this big +// (exception to the above is required living hours haha) +/datum/config_entry/flag/enable_discord_autorole + +/datum/config_entry/number/required_living_hours + +/datum/config_entry/string/discord_token + +/datum/config_entry/string/discord_guildid + +/datum/config_entry/string/discord_roleid diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index 2ca208642c8..3ce770b66d5 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -1,102 +1,102 @@ - /** - * Failsafe - * - * Pretty much pokes the MC to make sure it's still alive. - **/ - -GLOBAL_REAL(Failsafe, /datum/controller/failsafe) - -/datum/controller/failsafe // This thing pretty much just keeps poking the master controller - name = "Failsafe" - - // The length of time to check on the MC (in deciseconds). - // Set to 0 to disable. - var/processing_interval = 20 - // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. - var/defcon = 5 - //the world.time of the last check, so the mc can restart US if we hang. - // (Real friends look out for *eachother*) - var/lasttick = 0 - - // Track the MC iteration to make sure its still on track. - var/master_iteration = 0 - var/running = TRUE - -/datum/controller/failsafe/New() - // Highlander-style: there can only be one! Kill off the old and replace it with the new. - if(Failsafe != src) - if(istype(Failsafe)) - qdel(Failsafe) - Failsafe = src - Initialize() - -/datum/controller/failsafe/Initialize() - set waitfor = 0 - Failsafe.Loop() - if(!QDELETED(src)) - qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us - -/datum/controller/failsafe/Destroy() - running = FALSE - ..() - return QDEL_HINT_HARDDEL_NOW - -/datum/controller/failsafe/proc/Loop() - while(running) - lasttick = world.time - if(!Master) - // Replace the missing Master! This should never, ever happen. - new /datum/controller/master() - // Only poke it if overrides are not in effect. - if(processing_interval > 0) - if(Master.processing && Master.iteration) - // Check if processing is done yet. - if(Master.iteration == master_iteration) - switch(defcon) - if(4,5) - --defcon - if(3) - message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") - --defcon - if(2) - to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") - --defcon - if(1) - - to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") - --defcon - var/rtn = Recreate_MC() - if(rtn > 0) - defcon = 4 - master_iteration = 0 - to_chat(GLOB.admins, "MC restarted successfully") - else if(rtn < 0) - log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") - to_chat(GLOB.admins, "ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.") - //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again - //no need to handle that specially when defcon 0 can handle it - if(0) //DEFCON 0! (mc failed to restart) - var/rtn = Recreate_MC() - if(rtn > 0) - defcon = 4 - master_iteration = 0 - to_chat(GLOB.admins, "MC restarted successfully") - else - defcon = min(defcon + 1,5) - master_iteration = Master.iteration - if (defcon <= 1) - sleep(processing_interval*2) - else - sleep(processing_interval) - else - defcon = 5 - sleep(initial(processing_interval)) - -/datum/controller/failsafe/proc/defcon_pretty() - return defcon - -/datum/controller/failsafe/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")) + /** + * Failsafe + * + * Pretty much pokes the MC to make sure it's still alive. + **/ + +GLOBAL_REAL(Failsafe, /datum/controller/failsafe) + +/datum/controller/failsafe // This thing pretty much just keeps poking the master controller + name = "Failsafe" + + // The length of time to check on the MC (in deciseconds). + // Set to 0 to disable. + var/processing_interval = 20 + // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. + var/defcon = 5 + //the world.time of the last check, so the mc can restart US if we hang. + // (Real friends look out for *eachother*) + var/lasttick = 0 + + // Track the MC iteration to make sure its still on track. + var/master_iteration = 0 + var/running = TRUE + +/datum/controller/failsafe/New() + // Highlander-style: there can only be one! Kill off the old and replace it with the new. + if(Failsafe != src) + if(istype(Failsafe)) + qdel(Failsafe) + Failsafe = src + Initialize() + +/datum/controller/failsafe/Initialize() + set waitfor = 0 + Failsafe.Loop() + if(!QDELETED(src)) + qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us + +/datum/controller/failsafe/Destroy() + running = FALSE + ..() + return QDEL_HINT_HARDDEL_NOW + +/datum/controller/failsafe/proc/Loop() + while(running) + lasttick = world.time + if(!Master) + // Replace the missing Master! This should never, ever happen. + new /datum/controller/master() + // Only poke it if overrides are not in effect. + if(processing_interval > 0) + if(Master.processing && Master.iteration) + // Check if processing is done yet. + if(Master.iteration == master_iteration) + switch(defcon) + if(4,5) + --defcon + if(3) + message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") + --defcon + if(2) + to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") + --defcon + if(1) + + to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") + --defcon + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(GLOB.admins, "MC restarted successfully") + else if(rtn < 0) + log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") + to_chat(GLOB.admins, "ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.") + //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again + //no need to handle that specially when defcon 0 can handle it + if(0) //DEFCON 0! (mc failed to restart) + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(GLOB.admins, "MC restarted successfully") + else + defcon = min(defcon + 1,5) + master_iteration = Master.iteration + if (defcon <= 1) + sleep(processing_interval*2) + else + sleep(processing_interval) + else + defcon = 5 + sleep(initial(processing_interval)) + +/datum/controller/failsafe/proc/defcon_pretty() + return defcon + +/datum/controller/failsafe/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")) diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm index 3a901c4970a..707adfc83fe 100644 --- a/code/controllers/globals.dm +++ b/code/controllers/globals.dm @@ -1,56 +1,56 @@ -GLOBAL_REAL(GLOB, /datum/controller/global_vars) - -/datum/controller/global_vars - name = "Global Variables" - - var/static/list/gvars_datum_protected_varlist - var/list/gvars_datum_in_built_vars - var/list/gvars_datum_init_order - -/datum/controller/global_vars/New() - if(GLOB) - CRASH("Multiple instances of global variable controller created") - GLOB = src - - var/datum/controller/exclude_these = new - gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) - QDEL_IN(exclude_these, 0) //signal logging isn't ready - - log_world("[vars.len - gvars_datum_in_built_vars.len] global variables") - - Initialize() - -/datum/controller/global_vars/Destroy(force) - // This is done to prevent an exploit where admins can get around protected vars - SHOULD_CALL_PARENT(0) - return QDEL_HINT_IWILLGC - -/datum/controller/global_vars/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat("Globals:", statclick.update("Edit")) - -/datum/controller/global_vars/vv_edit_var(var_name, var_value) - if(gvars_datum_protected_varlist[var_name]) - return FALSE - return ..() - -/datum/controller/global_vars/Initialize() - gvars_datum_init_order = list() - gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE) - var/list/global_procs = typesof(/datum/controller/global_vars/proc) - var/expected_len = vars.len - gvars_datum_in_built_vars.len - if(global_procs.len != expected_len) - warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!") - if(global_procs.len) - var/list/expected_global_procs = vars - gvars_datum_in_built_vars - for(var/I in global_procs) - expected_global_procs -= replacetext("[I]", "InitGlobal", "") - log_world("Missing procs: [expected_global_procs.Join(", ")]") - for(var/I in global_procs) - var/start_tick = world.time - call(src, I)() - var/end_tick = world.time - if(end_tick - start_tick) - warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!") +GLOBAL_REAL(GLOB, /datum/controller/global_vars) + +/datum/controller/global_vars + name = "Global Variables" + + var/static/list/gvars_datum_protected_varlist + var/list/gvars_datum_in_built_vars + var/list/gvars_datum_init_order + +/datum/controller/global_vars/New() + if(GLOB) + CRASH("Multiple instances of global variable controller created") + GLOB = src + + var/datum/controller/exclude_these = new + gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) + QDEL_IN(exclude_these, 0) //signal logging isn't ready + + log_world("[vars.len - gvars_datum_in_built_vars.len] global variables") + + Initialize() + +/datum/controller/global_vars/Destroy(force) + // This is done to prevent an exploit where admins can get around protected vars + SHOULD_CALL_PARENT(0) + return QDEL_HINT_IWILLGC + +/datum/controller/global_vars/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + stat("Globals:", statclick.update("Edit")) + +/datum/controller/global_vars/vv_edit_var(var_name, var_value) + if(gvars_datum_protected_varlist[var_name]) + return FALSE + return ..() + +/datum/controller/global_vars/Initialize() + gvars_datum_init_order = list() + gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE) + var/list/global_procs = typesof(/datum/controller/global_vars/proc) + var/expected_len = vars.len - gvars_datum_in_built_vars.len + if(global_procs.len != expected_len) + warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!") + if(global_procs.len) + var/list/expected_global_procs = vars - gvars_datum_in_built_vars + for(var/I in global_procs) + expected_global_procs -= replacetext("[I]", "InitGlobal", "") + log_world("Missing procs: [expected_global_procs.Join(", ")]") + for(var/I in global_procs) + var/start_tick = world.time + call(src, I)() + var/end_tick = world.time + if(end_tick - start_tick) + warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!") diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index 8ae7ba290e6..40841f30f04 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -1,156 +1,156 @@ -#define BAD_INIT_QDEL_BEFORE 1 -#define BAD_INIT_DIDNT_INIT 2 -#define BAD_INIT_SLEPT 4 -#define BAD_INIT_NO_HINT 8 - -SUBSYSTEM_DEF(atoms) - name = "Atoms" - init_order = INIT_ORDER_ATOMS - flags = SS_NO_FIRE - - var/old_initialized - - var/list/late_loaders = list() - - var/list/BadInitializeCalls = list() - -/datum/controller/subsystem/atoms/Initialize(timeofday) - GLOB.fire_overlay.appearance_flags = RESET_COLOR - setupGenetics() //to set the mutations' sequence - initialized = INITIALIZATION_INNEW_MAPLOAD - InitializeAtoms() - return ..() - -/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) - if(initialized == INITIALIZATION_INSSATOMS) - return - - initialized = INITIALIZATION_INNEW_MAPLOAD - - var/count - var/list/mapload_arg = list(TRUE) - if(atoms) - count = atoms.len - for(var/I in atoms) - var/atom/A = I - if(!(A.flags_1 & INITIALIZED_1)) - InitAtom(I, mapload_arg) - CHECK_TICK - else - count = 0 - for(var/atom/A in world) - if(!(A.flags_1 & INITIALIZED_1)) - InitAtom(A, mapload_arg) - ++count - CHECK_TICK - - testing("Initialized [count] atoms") - pass(count) - - initialized = INITIALIZATION_INNEW_REGULAR - - if(late_loaders.len) - for(var/I in late_loaders) - var/atom/A = I - A.LateInitialize() - testing("Late initialized [late_loaders.len] atoms") - late_loaders.Cut() - -/// Init this specific atom -/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments) - var/the_type = A.type - if(QDELING(A)) - BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE - return TRUE - - var/start_tick = world.time - - var/result = A.Initialize(arglist(arguments)) - - if(start_tick != world.time) - BadInitializeCalls[the_type] |= BAD_INIT_SLEPT - - var/qdeleted = FALSE - - if(result != INITIALIZE_HINT_NORMAL) - switch(result) - if(INITIALIZE_HINT_LATELOAD) - if(arguments[1]) //mapload - late_loaders += A - else - A.LateInitialize() - if(INITIALIZE_HINT_QDEL) - qdel(A) - qdeleted = TRUE - else - BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT - - if(!A) //possible harddel - qdeleted = TRUE - else if(!(A.flags_1 & INITIALIZED_1)) - BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT - else - SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) - - return qdeleted || QDELING(A) - -/datum/controller/subsystem/atoms/proc/map_loader_begin() - old_initialized = initialized - initialized = INITIALIZATION_INSSATOMS - -/datum/controller/subsystem/atoms/proc/map_loader_stop() - initialized = old_initialized - -/datum/controller/subsystem/atoms/Recover() - initialized = SSatoms.initialized - if(initialized == INITIALIZATION_INNEW_MAPLOAD) - InitializeAtoms() - old_initialized = SSatoms.old_initialized - BadInitializeCalls = SSatoms.BadInitializeCalls - -/datum/controller/subsystem/atoms/proc/setupGenetics() - var/list/mutations = subtypesof(/datum/mutation/human) - shuffle_inplace(mutations) - for(var/A in subtypesof(/datum/generecipe)) - var/datum/generecipe/GR = A - GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result) - for(var/i in 1 to LAZYLEN(mutations)) - var/path = mutations[i] //byond gets pissy when we do it in one line - var/datum/mutation/human/B = new path () - B.alias = "Mutation [i]" - GLOB.all_mutations[B.type] = B - GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks) - GLOB.alias_mutations[B.alias] = B.type - if(B.locked) - continue - if(B.quality == POSITIVE) - GLOB.good_mutations |= B - else if(B.quality == NEGATIVE) - GLOB.bad_mutations |= B - else if(B.quality == MINOR_NEGATIVE) - GLOB.not_good_mutations |= B - CHECK_TICK - -/datum/controller/subsystem/atoms/proc/InitLog() - . = "" - for(var/path in BadInitializeCalls) - . += "Path : [path] \n" - var/fails = BadInitializeCalls[path] - if(fails & BAD_INIT_DIDNT_INIT) - . += "- Didn't call atom/Initialize()\n" - if(fails & BAD_INIT_NO_HINT) - . += "- Didn't return an Initialize hint\n" - if(fails & BAD_INIT_QDEL_BEFORE) - . += "- Qdel'd in New()\n" - if(fails & BAD_INIT_SLEPT) - . += "- Slept during Initialize()\n" - -/datum/controller/subsystem/atoms/Shutdown() - var/initlog = InitLog() - if(initlog) - text2file(initlog, "[GLOB.log_directory]/initialize.log") - -#undef BAD_INIT_QDEL_BEFORE -#undef BAD_INIT_DIDNT_INIT -#undef BAD_INIT_SLEPT -#undef BAD_INIT_NO_HINT +#define BAD_INIT_QDEL_BEFORE 1 +#define BAD_INIT_DIDNT_INIT 2 +#define BAD_INIT_SLEPT 4 +#define BAD_INIT_NO_HINT 8 + +SUBSYSTEM_DEF(atoms) + name = "Atoms" + init_order = INIT_ORDER_ATOMS + flags = SS_NO_FIRE + + var/old_initialized + + var/list/late_loaders = list() + + var/list/BadInitializeCalls = list() + +/datum/controller/subsystem/atoms/Initialize(timeofday) + GLOB.fire_overlay.appearance_flags = RESET_COLOR + setupGenetics() //to set the mutations' sequence + initialized = INITIALIZATION_INNEW_MAPLOAD + InitializeAtoms() + return ..() + +/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) + if(initialized == INITIALIZATION_INSSATOMS) + return + + initialized = INITIALIZATION_INNEW_MAPLOAD + + var/count + var/list/mapload_arg = list(TRUE) + if(atoms) + count = atoms.len + for(var/I in atoms) + var/atom/A = I + if(!(A.flags_1 & INITIALIZED_1)) + InitAtom(I, mapload_arg) + CHECK_TICK + else + count = 0 + for(var/atom/A in world) + if(!(A.flags_1 & INITIALIZED_1)) + InitAtom(A, mapload_arg) + ++count + CHECK_TICK + + testing("Initialized [count] atoms") + pass(count) + + initialized = INITIALIZATION_INNEW_REGULAR + + if(late_loaders.len) + for(var/I in late_loaders) + var/atom/A = I + A.LateInitialize() + testing("Late initialized [late_loaders.len] atoms") + late_loaders.Cut() + +/// Init this specific atom +/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments) + var/the_type = A.type + if(QDELING(A)) + BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE + return TRUE + + var/start_tick = world.time + + var/result = A.Initialize(arglist(arguments)) + + if(start_tick != world.time) + BadInitializeCalls[the_type] |= BAD_INIT_SLEPT + + var/qdeleted = FALSE + + if(result != INITIALIZE_HINT_NORMAL) + switch(result) + if(INITIALIZE_HINT_LATELOAD) + if(arguments[1]) //mapload + late_loaders += A + else + A.LateInitialize() + if(INITIALIZE_HINT_QDEL) + qdel(A) + qdeleted = TRUE + else + BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT + + if(!A) //possible harddel + qdeleted = TRUE + else if(!(A.flags_1 & INITIALIZED_1)) + BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT + else + SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) + + return qdeleted || QDELING(A) + +/datum/controller/subsystem/atoms/proc/map_loader_begin() + old_initialized = initialized + initialized = INITIALIZATION_INSSATOMS + +/datum/controller/subsystem/atoms/proc/map_loader_stop() + initialized = old_initialized + +/datum/controller/subsystem/atoms/Recover() + initialized = SSatoms.initialized + if(initialized == INITIALIZATION_INNEW_MAPLOAD) + InitializeAtoms() + old_initialized = SSatoms.old_initialized + BadInitializeCalls = SSatoms.BadInitializeCalls + +/datum/controller/subsystem/atoms/proc/setupGenetics() + var/list/mutations = subtypesof(/datum/mutation/human) + shuffle_inplace(mutations) + for(var/A in subtypesof(/datum/generecipe)) + var/datum/generecipe/GR = A + GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result) + for(var/i in 1 to LAZYLEN(mutations)) + var/path = mutations[i] //byond gets pissy when we do it in one line + var/datum/mutation/human/B = new path () + B.alias = "Mutation [i]" + GLOB.all_mutations[B.type] = B + GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks) + GLOB.alias_mutations[B.alias] = B.type + if(B.locked) + continue + if(B.quality == POSITIVE) + GLOB.good_mutations |= B + else if(B.quality == NEGATIVE) + GLOB.bad_mutations |= B + else if(B.quality == MINOR_NEGATIVE) + GLOB.not_good_mutations |= B + CHECK_TICK + +/datum/controller/subsystem/atoms/proc/InitLog() + . = "" + for(var/path in BadInitializeCalls) + . += "Path : [path] \n" + var/fails = BadInitializeCalls[path] + if(fails & BAD_INIT_DIDNT_INIT) + . += "- Didn't call atom/Initialize()\n" + if(fails & BAD_INIT_NO_HINT) + . += "- Didn't return an Initialize hint\n" + if(fails & BAD_INIT_QDEL_BEFORE) + . += "- Qdel'd in New()\n" + if(fails & BAD_INIT_SLEPT) + . += "- Slept during Initialize()\n" + +/datum/controller/subsystem/atoms/Shutdown() + var/initlog = InitLog() + if(initlog) + text2file(initlog, "[GLOB.log_directory]/initialize.log") + +#undef BAD_INIT_QDEL_BEFORE +#undef BAD_INIT_DIDNT_INIT +#undef BAD_INIT_SLEPT +#undef BAD_INIT_NO_HINT diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index 599b3494bbc..218bf2ade7f 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -1,349 +1,349 @@ -SUBSYSTEM_DEF(blackbox) - name = "Blackbox" - wait = 6000 - flags = SS_NO_TICK_CHECK - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - init_order = INIT_ORDER_BLACKBOX - - var/list/feedback = list() //list of datum/feedback_variable - var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things - var/triggertime = 0 - var/sealed = FALSE //time to stop tracking stats? - var/list/versions = list("antagonists" = 3, - "admin_secrets_fun_used" = 2, - "explosion" = 2, - "time_dilation_current" = 3, - "science_techweb_unlock" = 2, - "round_end_stats" = 2, - "testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this - -/datum/controller/subsystem/blackbox/Initialize() - triggertime = world.time - record_feedback("amount", "random_seed", Master.random_seed) - record_feedback("amount", "dm_version", DM_VERSION) - record_feedback("amount", "dm_build", DM_BUILD) - record_feedback("amount", "byond_version", world.byond_version) - record_feedback("amount", "byond_build", world.byond_build) - . = ..() - -//poll population -/datum/controller/subsystem/blackbox/fire() - set waitfor = FALSE //for population query - - CheckPlayerCount() - - if(CONFIG_GET(flag/use_exp_tracking)) - if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check - update_exp(10,FALSE) - -/datum/controller/subsystem/blackbox/proc/CheckPlayerCount() - set waitfor = FALSE - - if(!SSdbcore.Connect()) - return - var/playercount = LAZYLEN(GLOB.player_list) - var/admincount = GLOB.admins.len - var/datum/db_query/query_record_playercount = SSdbcore.NewQuery({" - INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) - VALUES (:playercount, :admincount, :time, INET_ATON(:server_ip), :server_port, :round_id) - "}, list( - "playercount" = playercount, - "admincount" = admincount, - "time" = SQLtime(), - "server_ip" = world.internet_address || "0", - "server_port" = "[world.port]", - "round_id" = GLOB.round_id, - )) - query_record_playercount.Execute() - qdel(query_record_playercount) - -/datum/controller/subsystem/blackbox/Recover() - feedback = SSblackbox.feedback - sealed = SSblackbox.sealed - -//no touchie -/datum/controller/subsystem/blackbox/vv_get_var(var_name) - if(var_name == "feedback") - return debug_variable(var_name, deepCopyList(feedback), 0, src) - return ..() - -/datum/controller/subsystem/blackbox/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, feedback)) - return FALSE - if(NAMEOF(src, sealed)) - if(var_value) - return Seal() - return FALSE - return ..() - -//Recorded on subsystem shutdown -/datum/controller/subsystem/blackbox/proc/FinalFeedback() - record_feedback("tally", "ahelp_stats", GLOB.ahelp_tickets.active_tickets.len, "unresolved") - for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list) - if (MS.pda_msgs.len) - record_feedback("tally", "radio_usage", MS.pda_msgs.len, "PDA") - if (MS.rc_msgs.len) - record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console") - - for(var/player_key in GLOB.player_details) - var/datum/player_details/PD = GLOB.player_details[player_key] - record_feedback("tally", "client_byond_version", 1, PD.byond_version) - -/datum/controller/subsystem/blackbox/Shutdown() - sealed = FALSE - FinalFeedback() - - if (!SSdbcore.Connect()) - return - - var/list/special_columns = list( - "datetime" = "NOW()" - ) - var/list/sqlrowlist = list() - for (var/datum/feedback_variable/FV in feedback) - sqlrowlist += list(list( - "round_id" = GLOB.round_id, - "key_name" = FV.key, - "key_type" = FV.key_type, - "version" = versions[FV.key] || 1, - "json" = json_encode(FV.json) - )) - - if (!length(sqlrowlist)) - return - - SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns) - -/datum/controller/subsystem/blackbox/proc/Seal() - if(sealed) - return FALSE - if(IsAdminAdvancedProcCall()) - message_admins("[key_name_admin(usr)] sealed the blackbox!") - log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].") - sealed = TRUE - return TRUE - -/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq) - if(sealed) - return - switch(freq) - if(FREQ_COMMON) - record_feedback("tally", "radio_usage", 1, "common") - if(FREQ_SCIENCE) - record_feedback("tally", "radio_usage", 1, "science") - if(FREQ_COMMAND) - record_feedback("tally", "radio_usage", 1, "command") - if(FREQ_MEDICAL) - record_feedback("tally", "radio_usage", 1, "medical") - if(FREQ_ENGINEERING) - record_feedback("tally", "radio_usage", 1, "engineering") - if(FREQ_SECURITY) - record_feedback("tally", "radio_usage", 1, "security") - if(FREQ_SYNDICATE) - record_feedback("tally", "radio_usage", 1, "syndicate") - if(FREQ_SERVICE) - record_feedback("tally", "radio_usage", 1, "service") - if(FREQ_SUPPLY) - record_feedback("tally", "radio_usage", 1, "supply") - if(FREQ_CENTCOM) - record_feedback("tally", "radio_usage", 1, "centcom") - if(FREQ_AI_PRIVATE) - record_feedback("tally", "radio_usage", 1, "ai private") - if(FREQ_CTF_RED) - record_feedback("tally", "radio_usage", 1, "CTF red team") - if(FREQ_CTF_BLUE) - record_feedback("tally", "radio_usage", 1, "CTF blue team") - else - record_feedback("tally", "radio_usage", 1, "other") - -/datum/controller/subsystem/blackbox/proc/find_feedback_datum(key, key_type) - for(var/datum/feedback_variable/FV in feedback) - if(FV.key == key) - return FV - - var/datum/feedback_variable/FV = new(key, key_type) - feedback += FV - return FV -/* -feedback data can be recorded in 5 formats: -"text" - used for simple single-string records i.e. the current map - further calls to the same key will append saved data unless the overwrite argument is true or it already exists - when encoded calls made with overwrite will lack square brackets - calls: SSblackbox.record_feedback("text", "example", 1, "sample text") - SSblackbox.record_feedback("text", "example", 1, "other text") - json: {"data":["sample text","other text"]} -"amount" - used to record simple counts of data i.e. the number of ahelps received - further calls to the same key will add or subtract (if increment argument is a negative) from the saved amount - calls: SSblackbox.record_feedback("amount", "example", 8) - SSblackbox.record_feedback("amount", "example", 2) - json: {"data":10} -"tally" - used to track the number of occurances of multiple related values i.e. how many times each type of gun is fired - further calls to the same key will: - add or subtract from the saved value of the data key if it already exists - append the key and it's value if it doesn't exist - calls: SSblackbox.record_feedback("tally", "example", 1, "sample data") - SSblackbox.record_feedback("tally", "example", 4, "sample data") - SSblackbox.record_feedback("tally", "example", 2, "other data") - json: {"data":{"sample data":5,"other data":2}} -"nested tally" - used to track the number of occurances of structured semi-relational values i.e. the results of arcade machines - similar to running total, but related values are nested in a multi-dimensional array built - the final element in the data list is used as the tracking key, all prior elements are used for nesting - all data list elements must be strings - further calls to the same key will: - add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position - append the key and it's value if it doesn't exist - calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot")) - SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange")) - SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot")) - SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple")) - SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot")) - json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10}},"vegetable":{"orange":{"carrot":1}}}} - tracking values associated with a number can't merge with a nesting value, trying to do so will append the list - call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange")) - json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10},"orange":3},"vegetable":{"orange":{"carrot":1}}}} -"associative" - used to record text that's associated with a value i.e. coordinates - further calls to the same key will append a new list to existing data - calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4)) - SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample")) - json: {"data":{"1":{"text":"example","path":"/obj/item","number":"4"},"2":{"number":"7","text":"example","other text":"sample"}}} - -Versioning - If the format of a feedback variable is ever changed, i.e. how many levels of nesting are used or a new type of data is added to it, add it to the versions list - When feedback is being saved if a key is in the versions list the value specified there will be used, otherwise all keys are assumed to be version = 1 - versions is an associative list, remember to use the same string in it as defined on a feedback variable, example: - list/versions = list("round_end_stats" = 4, - "admin_toggle" = 2, - "gun_fired" = 2) -*/ -/datum/controller/subsystem/blackbox/proc/record_feedback(key_type, key, increment, data, overwrite) - if(sealed || !key_type || !istext(key) || !isnum(increment || !data)) - return - var/datum/feedback_variable/FV = find_feedback_datum(key, key_type) - switch(key_type) - if("text") - if(!istext(data)) - return - if(!islist(FV.json["data"])) - FV.json["data"] = list() - if(overwrite) - FV.json["data"] = data - else - FV.json["data"] |= data - if("amount") - FV.json["data"] += increment - if("tally") - if(!islist(FV.json["data"])) - FV.json["data"] = list() - FV.json["data"]["[data]"] += increment - if("nested tally") - if(!islist(data)) - return - if(!islist(FV.json["data"])) - FV.json["data"] = list() - FV.json["data"] = record_feedback_recurse_list(FV.json["data"], data, increment) - if("associative") - if(!islist(data)) - return - if(!islist(FV.json["data"])) - FV.json["data"] = list() - var/pos = length(FV.json["data"]) + 1 - FV.json["data"]["[pos]"] = list() //in 512 "pos" can be replaced with "[FV.json["data"].len+1]" - for(var/i in data) - if(islist(data[i])) - FV.json["data"]["[pos]"]["[i]"] = data[i] //and here with "[FV.json["data"].len]" - else - FV.json["data"]["[pos]"]["[i]"] = "[data[i]]" - else - CRASH("Invalid feedback key_type: [key_type]") - -/datum/controller/subsystem/blackbox/proc/record_feedback_recurse_list(list/L, list/key_list, increment, depth = 1) - if(depth == key_list.len) - if(L.Find(key_list[depth])) - L["[key_list[depth]]"] += increment - else - var/list/LFI = list(key_list[depth] = increment) - L += LFI - else - if(!L.Find(key_list[depth])) - var/list/LGD = list(key_list[depth] = list()) - L += LGD - L["[key_list[depth-1]]"] = .(L["[key_list[depth]]"], key_list, increment, ++depth) - return L - -/datum/feedback_variable - var/key - var/key_type - var/list/json = list() - -/datum/feedback_variable/New(new_key, new_key_type) - key = new_key - key_type = new_key_type - -/datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender) - if(!SSdbcore.Connect()) - return - - var/datum/db_query/query_log_ahelp = SSdbcore.NewQuery({" - INSERT INTO [format_table_name("ticket")] (ticket, action, message, recipient, sender, server_ip, server_port, round_id, timestamp) - VALUES (:ticket, :action, :message, :recipient, :sender, INET_ATON(:server_ip), :server_port, :round_id, :time) - "}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime())) - query_log_ahelp.Execute() - qdel(query_log_ahelp) - - -/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L) - set waitfor = FALSE - if(sealed) - return - if(!L || !L.key || !L.mind) - return - if(!L.suiciding && !first_death.len) - first_death["name"] = "[(L.real_name == L.name) ? L.real_name : "[L.real_name] as [L.name]"]" - first_death["role"] = null - if(L.mind.assigned_role) - first_death["role"] = L.mind.assigned_role - first_death["area"] = "[AREACOORD(L)]" - first_death["damage"] = "[L.getBruteLoss()]/[L.getFireLoss()]/[L.getToxLoss()]/[L.getOxyLoss()]/[L.getCloneLoss()]" - first_death["last_words"] = L.last_words - - if(!SSdbcore.Connect()) - return - - var/datum/db_query/query_report_death = SSdbcore.NewQuery({" - INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide) - VALUES (:pod, :x_coord, :y_coord, :z_coord, :map, INET_ATON(:internet_address), :port, :round_id, :time, :job, :special, :name, :key, :laname, :lakey, :brute, :fire, :brain, :oxy, :tox, :clone, :stamina, :last_words, :suicide) - "}, list( - "name" = L.real_name, - "key" = L.ckey, - "job" = L.mind.assigned_role, - "special" = L.mind.special_role, - "pod" = get_area_name(L, TRUE), - "laname" = L.lastattacker, - "lakey" = L.lastattackerckey, - "brute" = L.getBruteLoss(), - "fire" = L.getFireLoss(), - "brain" = L.getOrganLoss(ORGAN_SLOT_BRAIN) || BRAIN_DAMAGE_DEATH, //getOrganLoss returns null without a brain but a value is required for this column - "oxy" = L.getOxyLoss(), - "tox" = L.getToxLoss(), - "clone" = L.getCloneLoss(), - "stamina" = L.getStaminaLoss(), - "x_coord" = L.x, - "y_coord" = L.y, - "z_coord" = L.z, - "last_words" = L.last_words, - "suicide" = L.suiciding, - "map" = SSmapping.config.map_name, - "internet_address" = world.internet_address || "0", - "port" = "[world.port]", - "round_id" = GLOB.round_id, - "time" = SQLtime(), - )) - if(query_report_death) - query_report_death.Execute(async = TRUE) - qdel(query_report_death) +SUBSYSTEM_DEF(blackbox) + name = "Blackbox" + wait = 6000 + flags = SS_NO_TICK_CHECK + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + init_order = INIT_ORDER_BLACKBOX + + var/list/feedback = list() //list of datum/feedback_variable + var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things + var/triggertime = 0 + var/sealed = FALSE //time to stop tracking stats? + var/list/versions = list("antagonists" = 3, + "admin_secrets_fun_used" = 2, + "explosion" = 2, + "time_dilation_current" = 3, + "science_techweb_unlock" = 2, + "round_end_stats" = 2, + "testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this + +/datum/controller/subsystem/blackbox/Initialize() + triggertime = world.time + record_feedback("amount", "random_seed", Master.random_seed) + record_feedback("amount", "dm_version", DM_VERSION) + record_feedback("amount", "dm_build", DM_BUILD) + record_feedback("amount", "byond_version", world.byond_version) + record_feedback("amount", "byond_build", world.byond_build) + . = ..() + +//poll population +/datum/controller/subsystem/blackbox/fire() + set waitfor = FALSE //for population query + + CheckPlayerCount() + + if(CONFIG_GET(flag/use_exp_tracking)) + if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check + update_exp(10,FALSE) + +/datum/controller/subsystem/blackbox/proc/CheckPlayerCount() + set waitfor = FALSE + + if(!SSdbcore.Connect()) + return + var/playercount = LAZYLEN(GLOB.player_list) + var/admincount = GLOB.admins.len + var/datum/db_query/query_record_playercount = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) + VALUES (:playercount, :admincount, :time, INET_ATON(:server_ip), :server_port, :round_id) + "}, list( + "playercount" = playercount, + "admincount" = admincount, + "time" = SQLtime(), + "server_ip" = world.internet_address || "0", + "server_port" = "[world.port]", + "round_id" = GLOB.round_id, + )) + query_record_playercount.Execute() + qdel(query_record_playercount) + +/datum/controller/subsystem/blackbox/Recover() + feedback = SSblackbox.feedback + sealed = SSblackbox.sealed + +//no touchie +/datum/controller/subsystem/blackbox/vv_get_var(var_name) + if(var_name == "feedback") + return debug_variable(var_name, deepCopyList(feedback), 0, src) + return ..() + +/datum/controller/subsystem/blackbox/vv_edit_var(var_name, var_value) + switch(var_name) + if(NAMEOF(src, feedback)) + return FALSE + if(NAMEOF(src, sealed)) + if(var_value) + return Seal() + return FALSE + return ..() + +//Recorded on subsystem shutdown +/datum/controller/subsystem/blackbox/proc/FinalFeedback() + record_feedback("tally", "ahelp_stats", GLOB.ahelp_tickets.active_tickets.len, "unresolved") + for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list) + if (MS.pda_msgs.len) + record_feedback("tally", "radio_usage", MS.pda_msgs.len, "PDA") + if (MS.rc_msgs.len) + record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console") + + for(var/player_key in GLOB.player_details) + var/datum/player_details/PD = GLOB.player_details[player_key] + record_feedback("tally", "client_byond_version", 1, PD.byond_version) + +/datum/controller/subsystem/blackbox/Shutdown() + sealed = FALSE + FinalFeedback() + + if (!SSdbcore.Connect()) + return + + var/list/special_columns = list( + "datetime" = "NOW()" + ) + var/list/sqlrowlist = list() + for (var/datum/feedback_variable/FV in feedback) + sqlrowlist += list(list( + "round_id" = GLOB.round_id, + "key_name" = FV.key, + "key_type" = FV.key_type, + "version" = versions[FV.key] || 1, + "json" = json_encode(FV.json) + )) + + if (!length(sqlrowlist)) + return + + SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns) + +/datum/controller/subsystem/blackbox/proc/Seal() + if(sealed) + return FALSE + if(IsAdminAdvancedProcCall()) + message_admins("[key_name_admin(usr)] sealed the blackbox!") + log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].") + sealed = TRUE + return TRUE + +/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq) + if(sealed) + return + switch(freq) + if(FREQ_COMMON) + record_feedback("tally", "radio_usage", 1, "common") + if(FREQ_SCIENCE) + record_feedback("tally", "radio_usage", 1, "science") + if(FREQ_COMMAND) + record_feedback("tally", "radio_usage", 1, "command") + if(FREQ_MEDICAL) + record_feedback("tally", "radio_usage", 1, "medical") + if(FREQ_ENGINEERING) + record_feedback("tally", "radio_usage", 1, "engineering") + if(FREQ_SECURITY) + record_feedback("tally", "radio_usage", 1, "security") + if(FREQ_SYNDICATE) + record_feedback("tally", "radio_usage", 1, "syndicate") + if(FREQ_SERVICE) + record_feedback("tally", "radio_usage", 1, "service") + if(FREQ_SUPPLY) + record_feedback("tally", "radio_usage", 1, "supply") + if(FREQ_CENTCOM) + record_feedback("tally", "radio_usage", 1, "centcom") + if(FREQ_AI_PRIVATE) + record_feedback("tally", "radio_usage", 1, "ai private") + if(FREQ_CTF_RED) + record_feedback("tally", "radio_usage", 1, "CTF red team") + if(FREQ_CTF_BLUE) + record_feedback("tally", "radio_usage", 1, "CTF blue team") + else + record_feedback("tally", "radio_usage", 1, "other") + +/datum/controller/subsystem/blackbox/proc/find_feedback_datum(key, key_type) + for(var/datum/feedback_variable/FV in feedback) + if(FV.key == key) + return FV + + var/datum/feedback_variable/FV = new(key, key_type) + feedback += FV + return FV +/* +feedback data can be recorded in 5 formats: +"text" + used for simple single-string records i.e. the current map + further calls to the same key will append saved data unless the overwrite argument is true or it already exists + when encoded calls made with overwrite will lack square brackets + calls: SSblackbox.record_feedback("text", "example", 1, "sample text") + SSblackbox.record_feedback("text", "example", 1, "other text") + json: {"data":["sample text","other text"]} +"amount" + used to record simple counts of data i.e. the number of ahelps received + further calls to the same key will add or subtract (if increment argument is a negative) from the saved amount + calls: SSblackbox.record_feedback("amount", "example", 8) + SSblackbox.record_feedback("amount", "example", 2) + json: {"data":10} +"tally" + used to track the number of occurances of multiple related values i.e. how many times each type of gun is fired + further calls to the same key will: + add or subtract from the saved value of the data key if it already exists + append the key and it's value if it doesn't exist + calls: SSblackbox.record_feedback("tally", "example", 1, "sample data") + SSblackbox.record_feedback("tally", "example", 4, "sample data") + SSblackbox.record_feedback("tally", "example", 2, "other data") + json: {"data":{"sample data":5,"other data":2}} +"nested tally" + used to track the number of occurances of structured semi-relational values i.e. the results of arcade machines + similar to running total, but related values are nested in a multi-dimensional array built + the final element in the data list is used as the tracking key, all prior elements are used for nesting + all data list elements must be strings + further calls to the same key will: + add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position + append the key and it's value if it doesn't exist + calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot")) + SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange")) + SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot")) + SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple")) + SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot")) + json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10}},"vegetable":{"orange":{"carrot":1}}}} + tracking values associated with a number can't merge with a nesting value, trying to do so will append the list + call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange")) + json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10},"orange":3},"vegetable":{"orange":{"carrot":1}}}} +"associative" + used to record text that's associated with a value i.e. coordinates + further calls to the same key will append a new list to existing data + calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4)) + SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample")) + json: {"data":{"1":{"text":"example","path":"/obj/item","number":"4"},"2":{"number":"7","text":"example","other text":"sample"}}} + +Versioning + If the format of a feedback variable is ever changed, i.e. how many levels of nesting are used or a new type of data is added to it, add it to the versions list + When feedback is being saved if a key is in the versions list the value specified there will be used, otherwise all keys are assumed to be version = 1 + versions is an associative list, remember to use the same string in it as defined on a feedback variable, example: + list/versions = list("round_end_stats" = 4, + "admin_toggle" = 2, + "gun_fired" = 2) +*/ +/datum/controller/subsystem/blackbox/proc/record_feedback(key_type, key, increment, data, overwrite) + if(sealed || !key_type || !istext(key) || !isnum(increment || !data)) + return + var/datum/feedback_variable/FV = find_feedback_datum(key, key_type) + switch(key_type) + if("text") + if(!istext(data)) + return + if(!islist(FV.json["data"])) + FV.json["data"] = list() + if(overwrite) + FV.json["data"] = data + else + FV.json["data"] |= data + if("amount") + FV.json["data"] += increment + if("tally") + if(!islist(FV.json["data"])) + FV.json["data"] = list() + FV.json["data"]["[data]"] += increment + if("nested tally") + if(!islist(data)) + return + if(!islist(FV.json["data"])) + FV.json["data"] = list() + FV.json["data"] = record_feedback_recurse_list(FV.json["data"], data, increment) + if("associative") + if(!islist(data)) + return + if(!islist(FV.json["data"])) + FV.json["data"] = list() + var/pos = length(FV.json["data"]) + 1 + FV.json["data"]["[pos]"] = list() //in 512 "pos" can be replaced with "[FV.json["data"].len+1]" + for(var/i in data) + if(islist(data[i])) + FV.json["data"]["[pos]"]["[i]"] = data[i] //and here with "[FV.json["data"].len]" + else + FV.json["data"]["[pos]"]["[i]"] = "[data[i]]" + else + CRASH("Invalid feedback key_type: [key_type]") + +/datum/controller/subsystem/blackbox/proc/record_feedback_recurse_list(list/L, list/key_list, increment, depth = 1) + if(depth == key_list.len) + if(L.Find(key_list[depth])) + L["[key_list[depth]]"] += increment + else + var/list/LFI = list(key_list[depth] = increment) + L += LFI + else + if(!L.Find(key_list[depth])) + var/list/LGD = list(key_list[depth] = list()) + L += LGD + L["[key_list[depth-1]]"] = .(L["[key_list[depth]]"], key_list, increment, ++depth) + return L + +/datum/feedback_variable + var/key + var/key_type + var/list/json = list() + +/datum/feedback_variable/New(new_key, new_key_type) + key = new_key + key_type = new_key_type + +/datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender) + if(!SSdbcore.Connect()) + return + + var/datum/db_query/query_log_ahelp = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("ticket")] (ticket, action, message, recipient, sender, server_ip, server_port, round_id, timestamp) + VALUES (:ticket, :action, :message, :recipient, :sender, INET_ATON(:server_ip), :server_port, :round_id, :time) + "}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime())) + query_log_ahelp.Execute() + qdel(query_log_ahelp) + + +/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L) + set waitfor = FALSE + if(sealed) + return + if(!L || !L.key || !L.mind) + return + if(!L.suiciding && !first_death.len) + first_death["name"] = "[(L.real_name == L.name) ? L.real_name : "[L.real_name] as [L.name]"]" + first_death["role"] = null + if(L.mind.assigned_role) + first_death["role"] = L.mind.assigned_role + first_death["area"] = "[AREACOORD(L)]" + first_death["damage"] = "[L.getBruteLoss()]/[L.getFireLoss()]/[L.getToxLoss()]/[L.getOxyLoss()]/[L.getCloneLoss()]" + first_death["last_words"] = L.last_words + + if(!SSdbcore.Connect()) + return + + var/datum/db_query/query_report_death = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide) + VALUES (:pod, :x_coord, :y_coord, :z_coord, :map, INET_ATON(:internet_address), :port, :round_id, :time, :job, :special, :name, :key, :laname, :lakey, :brute, :fire, :brain, :oxy, :tox, :clone, :stamina, :last_words, :suicide) + "}, list( + "name" = L.real_name, + "key" = L.ckey, + "job" = L.mind.assigned_role, + "special" = L.mind.special_role, + "pod" = get_area_name(L, TRUE), + "laname" = L.lastattacker, + "lakey" = L.lastattackerckey, + "brute" = L.getBruteLoss(), + "fire" = L.getFireLoss(), + "brain" = L.getOrganLoss(ORGAN_SLOT_BRAIN) || BRAIN_DAMAGE_DEATH, //getOrganLoss returns null without a brain but a value is required for this column + "oxy" = L.getOxyLoss(), + "tox" = L.getToxLoss(), + "clone" = L.getCloneLoss(), + "stamina" = L.getStaminaLoss(), + "x_coord" = L.x, + "y_coord" = L.y, + "z_coord" = L.z, + "last_words" = L.last_words, + "suicide" = L.suiciding, + "map" = SSmapping.config.map_name, + "internet_address" = world.internet_address || "0", + "port" = "[world.port]", + "round_id" = GLOB.round_id, + "time" = SQLtime(), + )) + if(query_report_death) + query_report_death.Execute(async = TRUE) + qdel(query_report_death) diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index 63ca1e14e5c..5723e96dc41 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -1,396 +1,396 @@ -SUBSYSTEM_DEF(dbcore) - name = "Database" - flags = SS_BACKGROUND - wait = 1 MINUTES - init_order = INIT_ORDER_DBCORE - var/failed_connection_timeout = 0 - - var/schema_mismatch = 0 - var/db_minor = 0 - var/db_major = 0 - var/failed_connections = 0 - - var/last_error - var/list/active_queries = list() - - var/connection // Arbitrary handle returned from rust_g. - -/datum/controller/subsystem/dbcore/Initialize() - //We send warnings to the admins during subsystem init, as the clients will be New'd and messages - //will queue properly with goonchat - switch(schema_mismatch) - if(1) - message_admins("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") - if(2) - message_admins("Could not get schema version from database") - - return ..() - -/datum/controller/subsystem/dbcore/fire() - for(var/I in active_queries) - var/datum/db_query/Q = I - if(world.time - Q.last_activity_time > (5 MINUTES)) - message_admins("Found undeleted query, please check the server logs and notify coders.") - log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]") - qdel(Q) - if(MC_TICK_CHECK) - return - -/datum/controller/subsystem/dbcore/Recover() - connection = SSdbcore.connection - -/datum/controller/subsystem/dbcore/Shutdown() - //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem - if(SSdbcore.Connect()) - var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id", - list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id) - ) - query_round_shutdown.Execute() - qdel(query_round_shutdown) - if(IsConnected()) - Disconnect() - -//nu -/datum/controller/subsystem/dbcore/can_vv_get(var_name) - return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..() - -/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value) - if(var_name == NAMEOF(src, connection)) - return FALSE - return ..() - -/datum/controller/subsystem/dbcore/proc/Connect() - if(IsConnected()) - return TRUE - - if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter - failed_connections = 0 - - if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. - failed_connection_timeout = world.time + 50 - return FALSE - - if(!CONFIG_GET(flag/sql_enabled)) - return FALSE - - var/user = CONFIG_GET(string/feedback_login) - var/pass = CONFIG_GET(string/feedback_password) - var/db = CONFIG_GET(string/feedback_database) - var/address = CONFIG_GET(string/address) - var/port = CONFIG_GET(number/port) - var/timeout = max(CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout)) - var/thread_limit = CONFIG_GET(number/bsql_thread_limit) - - var/result = json_decode(rustg_sql_connect_pool(json_encode(list( - "host" = address, - "port" = port, - "user" = user, - "pass" = pass, - "db_name" = db, - "max_threads" = 5, - "read_timeout" = timeout, - "write_timeout" = timeout, - "max_threads" = thread_limit, - )))) - . = (result["status"] == "ok") - if (.) - connection = result["handle"] - else - connection = null - last_error = result["data"] - log_sql("Connect() failed | [last_error]") - ++failed_connections - -/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() - if(CONFIG_GET(flag/sql_enabled)) - if(Connect()) - log_world("Database connection established.") - var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1") - query_db_version.Execute() - if(query_db_version.NextRow()) - db_major = text2num(query_db_version.item[1]) - db_minor = text2num(query_db_version.item[2]) - if(db_major != DB_MAJOR_VERSION || db_minor != DB_MINOR_VERSION) - schema_mismatch = 1 // flag admin message about mismatch - log_sql("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") - else - schema_mismatch = 2 //flag admin message about no schema version - log_sql("Could not get schema version from database") - qdel(query_db_version) - else - log_sql("Your server failed to establish a connection with the database.") - else - log_sql("Database is not enabled in configuration.") - -/datum/controller/subsystem/dbcore/proc/SetRoundID() - if(!Connect()) - return - var/datum/db_query/query_round_initialize = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)", - list("internet_address" = world.internet_address || "0", "port" = "[world.port]") - ) - query_round_initialize.Execute(async = FALSE) - GLOB.round_id = "[query_round_initialize.last_insert_id]" - qdel(query_round_initialize) - -/datum/controller/subsystem/dbcore/proc/SetRoundStart() - if(!Connect()) - return - var/datum/db_query/query_round_start = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id", - list("round_id" = GLOB.round_id) - ) - query_round_start.Execute() - qdel(query_round_start) - -/datum/controller/subsystem/dbcore/proc/SetRoundEnd() - if(!Connect()) - return - var/datum/db_query/query_round_end = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id", - list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id) - ) - query_round_end.Execute() - qdel(query_round_end) - -/datum/controller/subsystem/dbcore/proc/Disconnect() - failed_connections = 0 - if (connection) - rustg_sql_disconnect_pool(connection) - connection = null - -/datum/controller/subsystem/dbcore/proc/IsConnected() - if (!CONFIG_GET(flag/sql_enabled)) - return FALSE - if (!connection) - return FALSE - return json_decode(rustg_sql_connected(connection))["status"] == "online" - -/datum/controller/subsystem/dbcore/proc/ErrorMsg() - if(!CONFIG_GET(flag/sql_enabled)) - return "Database disabled by configuration" - return last_error - -/datum/controller/subsystem/dbcore/proc/ReportError(error) - last_error = error - -/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments) - if(IsAdminAdvancedProcCall()) - log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked") - message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked") - return FALSE - return new /datum/db_query(connection, sql_query, arguments) - -/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) - if (!islist(querys)) - if (!istype(querys, /datum/db_query)) - CRASH("Invalid query passed to QuerySelect: [querys]") - querys = list(querys) - - for (var/thing in querys) - var/datum/db_query/query = thing - if (warn) - INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute) - else - INVOKE_ASYNC(query, /datum/db_query.proc/Execute) - - for (var/thing in querys) - var/datum/db_query/query = thing - UNTIL(!query.in_progress) - if (qdel) - qdel(query) - - - -/* -Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. -Rows missing columns present in other rows will resolve to SQL NULL -You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. -The duplicate_key arg can be true to automatically generate this part of the query - or set to a string that is appended to the end of the query -Ignore_errors instructes mysql to continue inserting rows if some of them have errors. - the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored -Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables, - It was included because it is still supported in mariadb. - It does not work with duplicate_key and the mysql server ignores it in those cases -*/ -/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null) - if (!table || !rows || !istype(rows)) - return - - // Prepare column list - var/list/columns = list() - var/list/has_question_mark = list() - for (var/list/row in rows) - for (var/column in row) - columns[column] = "?" - has_question_mark[column] = TRUE - for (var/column in special_columns) - columns[column] = special_columns[column] - has_question_mark[column] = findtext(special_columns[column], "?") - - // Prepare SQL query full of placeholders - var/list/query_parts = list("INSERT") - if (delayed) - query_parts += " DELAYED" - if (ignore_errors) - query_parts += " IGNORE" - query_parts += " INTO " - query_parts += table - query_parts += "\n([columns.Join(", ")])\nVALUES" - - var/list/arguments = list() - var/has_row = FALSE - for (var/list/row in rows) - if (has_row) - query_parts += "," - query_parts += "\n (" - var/has_col = FALSE - for (var/column in columns) - if (has_col) - query_parts += ", " - if (has_question_mark[column]) - var/name = "p[arguments.len]" - query_parts += replacetext(columns[column], "?", ":[name]") - arguments[name] = row[column] - else - query_parts += columns[column] - has_col = TRUE - query_parts += ")" - has_row = TRUE - - if (duplicate_key == TRUE) - var/list/column_list = list() - for (var/column in columns) - column_list += "[column] = VALUES([column])" - query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]" - else if (duplicate_key != FALSE) - query_parts += duplicate_key - - var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments) - if (warn) - . = Query.warn_execute(async) - else - . = Query.Execute(async) - qdel(Query) - -/datum/db_query - // Inputs - var/connection - var/sql - var/arguments - - // Status information - var/in_progress - var/last_error - var/last_activity - var/last_activity_time - - // Output - var/list/list/rows - var/next_row_to_take = 1 - var/affected - var/last_insert_id - - var/list/item //list of data values populated by NextRow() - -/datum/db_query/New(connection, sql, arguments) - SSdbcore.active_queries[src] = TRUE - Activity("Created") - item = list() - - src.connection = connection - src.sql = sql - src.arguments = arguments - -/datum/db_query/Destroy() - Close() - SSdbcore.active_queries -= src - return ..() - -/datum/db_query/CanProcCall(proc_name) - //fuck off kevinz - return FALSE - -/datum/db_query/proc/Activity(activity) - last_activity = activity - last_activity_time = world.time - -/datum/db_query/proc/warn_execute(async = TRUE) - . = Execute(async) - if(!.) - to_chat(usr, "A SQL error occurred during this operation, check the server logs.") - -/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE) - Activity("Execute") - if(in_progress) - CRASH("Attempted to start a new query while waiting on the old one") - - if(!SSdbcore.IsConnected()) - last_error = "No connection!" - return FALSE - - var/start_time - if(!async) - start_time = REALTIMEOFDAY - Close() - . = run_query(async) - var/timed_out = !. && findtext(last_error, "Operation timed out") - if(!. && log_error) - log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]") - if(!async && timed_out) - log_query_debug("Query execution started at [start_time]") - log_query_debug("Query execution ended at [REALTIMEOFDAY]") - log_query_debug("Slow query timeout detected.") - log_query_debug("Query used: [sql]") - slow_query_check() - -/datum/db_query/proc/run_query(async) - var/job_result_str - - if (async) - var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments)) - in_progress = TRUE - UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET) - in_progress = FALSE - - if (job_result_str == RUSTG_JOB_ERROR) - last_error = job_result_str - return FALSE - else - job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments)) - - var/result = json_decode(job_result_str) - switch (result["status"]) - if ("ok") - rows = result["rows"] - affected = result["affected"] - last_insert_id = result["last_insert_id"] - return TRUE - if ("err") - last_error = result["data"] - return FALSE - if ("offline") - last_error = "offline" - return FALSE - -/datum/db_query/proc/slow_query_check() - message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") - -/datum/db_query/proc/NextRow(async = TRUE) - Activity("NextRow") - - if (rows && next_row_to_take <= rows.len) - item = rows[next_row_to_take] - next_row_to_take++ - return !!item - else - return FALSE - -/datum/db_query/proc/ErrorMsg() - return last_error - -/datum/db_query/proc/Close() - rows = null - item = null +SUBSYSTEM_DEF(dbcore) + name = "Database" + flags = SS_BACKGROUND + wait = 1 MINUTES + init_order = INIT_ORDER_DBCORE + var/failed_connection_timeout = 0 + + var/schema_mismatch = 0 + var/db_minor = 0 + var/db_major = 0 + var/failed_connections = 0 + + var/last_error + var/list/active_queries = list() + + var/connection // Arbitrary handle returned from rust_g. + +/datum/controller/subsystem/dbcore/Initialize() + //We send warnings to the admins during subsystem init, as the clients will be New'd and messages + //will queue properly with goonchat + switch(schema_mismatch) + if(1) + message_admins("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") + if(2) + message_admins("Could not get schema version from database") + + return ..() + +/datum/controller/subsystem/dbcore/fire() + for(var/I in active_queries) + var/datum/db_query/Q = I + if(world.time - Q.last_activity_time > (5 MINUTES)) + message_admins("Found undeleted query, please check the server logs and notify coders.") + log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]") + qdel(Q) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/dbcore/Recover() + connection = SSdbcore.connection + +/datum/controller/subsystem/dbcore/Shutdown() + //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem + if(SSdbcore.Connect()) + var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id", + list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id) + ) + query_round_shutdown.Execute() + qdel(query_round_shutdown) + if(IsConnected()) + Disconnect() + +//nu +/datum/controller/subsystem/dbcore/can_vv_get(var_name) + return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..() + +/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, connection)) + return FALSE + return ..() + +/datum/controller/subsystem/dbcore/proc/Connect() + if(IsConnected()) + return TRUE + + if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter + failed_connections = 0 + + if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. + failed_connection_timeout = world.time + 50 + return FALSE + + if(!CONFIG_GET(flag/sql_enabled)) + return FALSE + + var/user = CONFIG_GET(string/feedback_login) + var/pass = CONFIG_GET(string/feedback_password) + var/db = CONFIG_GET(string/feedback_database) + var/address = CONFIG_GET(string/address) + var/port = CONFIG_GET(number/port) + var/timeout = max(CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout)) + var/thread_limit = CONFIG_GET(number/bsql_thread_limit) + + var/result = json_decode(rustg_sql_connect_pool(json_encode(list( + "host" = address, + "port" = port, + "user" = user, + "pass" = pass, + "db_name" = db, + "max_threads" = 5, + "read_timeout" = timeout, + "write_timeout" = timeout, + "max_threads" = thread_limit, + )))) + . = (result["status"] == "ok") + if (.) + connection = result["handle"] + else + connection = null + last_error = result["data"] + log_sql("Connect() failed | [last_error]") + ++failed_connections + +/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() + if(CONFIG_GET(flag/sql_enabled)) + if(Connect()) + log_world("Database connection established.") + var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1") + query_db_version.Execute() + if(query_db_version.NextRow()) + db_major = text2num(query_db_version.item[1]) + db_minor = text2num(query_db_version.item[2]) + if(db_major != DB_MAJOR_VERSION || db_minor != DB_MINOR_VERSION) + schema_mismatch = 1 // flag admin message about mismatch + log_sql("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") + else + schema_mismatch = 2 //flag admin message about no schema version + log_sql("Could not get schema version from database") + qdel(query_db_version) + else + log_sql("Your server failed to establish a connection with the database.") + else + log_sql("Database is not enabled in configuration.") + +/datum/controller/subsystem/dbcore/proc/SetRoundID() + if(!Connect()) + return + var/datum/db_query/query_round_initialize = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)", + list("internet_address" = world.internet_address || "0", "port" = "[world.port]") + ) + query_round_initialize.Execute(async = FALSE) + GLOB.round_id = "[query_round_initialize.last_insert_id]" + qdel(query_round_initialize) + +/datum/controller/subsystem/dbcore/proc/SetRoundStart() + if(!Connect()) + return + var/datum/db_query/query_round_start = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id", + list("round_id" = GLOB.round_id) + ) + query_round_start.Execute() + qdel(query_round_start) + +/datum/controller/subsystem/dbcore/proc/SetRoundEnd() + if(!Connect()) + return + var/datum/db_query/query_round_end = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id", + list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id) + ) + query_round_end.Execute() + qdel(query_round_end) + +/datum/controller/subsystem/dbcore/proc/Disconnect() + failed_connections = 0 + if (connection) + rustg_sql_disconnect_pool(connection) + connection = null + +/datum/controller/subsystem/dbcore/proc/IsConnected() + if (!CONFIG_GET(flag/sql_enabled)) + return FALSE + if (!connection) + return FALSE + return json_decode(rustg_sql_connected(connection))["status"] == "online" + +/datum/controller/subsystem/dbcore/proc/ErrorMsg() + if(!CONFIG_GET(flag/sql_enabled)) + return "Database disabled by configuration" + return last_error + +/datum/controller/subsystem/dbcore/proc/ReportError(error) + last_error = error + +/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments) + if(IsAdminAdvancedProcCall()) + log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked") + message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked") + return FALSE + return new /datum/db_query(connection, sql_query, arguments) + +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) + if (!islist(querys)) + if (!istype(querys, /datum/db_query)) + CRASH("Invalid query passed to QuerySelect: [querys]") + querys = list(querys) + + for (var/thing in querys) + var/datum/db_query/query = thing + if (warn) + INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute) + else + INVOKE_ASYNC(query, /datum/db_query.proc/Execute) + + for (var/thing in querys) + var/datum/db_query/query = thing + UNTIL(!query.in_progress) + if (qdel) + qdel(query) + + + +/* +Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. +Rows missing columns present in other rows will resolve to SQL NULL +You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. +The duplicate_key arg can be true to automatically generate this part of the query + or set to a string that is appended to the end of the query +Ignore_errors instructes mysql to continue inserting rows if some of them have errors. + the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored +Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables, + It was included because it is still supported in mariadb. + It does not work with duplicate_key and the mysql server ignores it in those cases +*/ +/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null) + if (!table || !rows || !istype(rows)) + return + + // Prepare column list + var/list/columns = list() + var/list/has_question_mark = list() + for (var/list/row in rows) + for (var/column in row) + columns[column] = "?" + has_question_mark[column] = TRUE + for (var/column in special_columns) + columns[column] = special_columns[column] + has_question_mark[column] = findtext(special_columns[column], "?") + + // Prepare SQL query full of placeholders + var/list/query_parts = list("INSERT") + if (delayed) + query_parts += " DELAYED" + if (ignore_errors) + query_parts += " IGNORE" + query_parts += " INTO " + query_parts += table + query_parts += "\n([columns.Join(", ")])\nVALUES" + + var/list/arguments = list() + var/has_row = FALSE + for (var/list/row in rows) + if (has_row) + query_parts += "," + query_parts += "\n (" + var/has_col = FALSE + for (var/column in columns) + if (has_col) + query_parts += ", " + if (has_question_mark[column]) + var/name = "p[arguments.len]" + query_parts += replacetext(columns[column], "?", ":[name]") + arguments[name] = row[column] + else + query_parts += columns[column] + has_col = TRUE + query_parts += ")" + has_row = TRUE + + if (duplicate_key == TRUE) + var/list/column_list = list() + for (var/column in columns) + column_list += "[column] = VALUES([column])" + query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]" + else if (duplicate_key != FALSE) + query_parts += duplicate_key + + var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments) + if (warn) + . = Query.warn_execute(async) + else + . = Query.Execute(async) + qdel(Query) + +/datum/db_query + // Inputs + var/connection + var/sql + var/arguments + + // Status information + var/in_progress + var/last_error + var/last_activity + var/last_activity_time + + // Output + var/list/list/rows + var/next_row_to_take = 1 + var/affected + var/last_insert_id + + var/list/item //list of data values populated by NextRow() + +/datum/db_query/New(connection, sql, arguments) + SSdbcore.active_queries[src] = TRUE + Activity("Created") + item = list() + + src.connection = connection + src.sql = sql + src.arguments = arguments + +/datum/db_query/Destroy() + Close() + SSdbcore.active_queries -= src + return ..() + +/datum/db_query/CanProcCall(proc_name) + //fuck off kevinz + return FALSE + +/datum/db_query/proc/Activity(activity) + last_activity = activity + last_activity_time = world.time + +/datum/db_query/proc/warn_execute(async = TRUE) + . = Execute(async) + if(!.) + to_chat(usr, "A SQL error occurred during this operation, check the server logs.") + +/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE) + Activity("Execute") + if(in_progress) + CRASH("Attempted to start a new query while waiting on the old one") + + if(!SSdbcore.IsConnected()) + last_error = "No connection!" + return FALSE + + var/start_time + if(!async) + start_time = REALTIMEOFDAY + Close() + . = run_query(async) + var/timed_out = !. && findtext(last_error, "Operation timed out") + if(!. && log_error) + log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]") + if(!async && timed_out) + log_query_debug("Query execution started at [start_time]") + log_query_debug("Query execution ended at [REALTIMEOFDAY]") + log_query_debug("Slow query timeout detected.") + log_query_debug("Query used: [sql]") + slow_query_check() + +/datum/db_query/proc/run_query(async) + var/job_result_str + + if (async) + var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments)) + in_progress = TRUE + UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET) + in_progress = FALSE + + if (job_result_str == RUSTG_JOB_ERROR) + last_error = job_result_str + return FALSE + else + job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments)) + + var/result = json_decode(job_result_str) + switch (result["status"]) + if ("ok") + rows = result["rows"] + affected = result["affected"] + last_insert_id = result["last_insert_id"] + return TRUE + if ("err") + last_error = result["data"] + return FALSE + if ("offline") + last_error = "offline" + return FALSE + +/datum/db_query/proc/slow_query_check() + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") + +/datum/db_query/proc/NextRow(async = TRUE) + Activity("NextRow") + + if (rows && next_row_to_take <= rows.len) + item = rows[next_row_to_take] + next_row_to_take++ + return !!item + else + return FALSE + +/datum/db_query/proc/ErrorMsg() + return last_error + +/datum/db_query/proc/Close() + rows = null + item = null diff --git a/code/controllers/subsystem/discord.dm b/code/controllers/subsystem/discord.dm index 1fe69ad6c3e..61739f5acb2 100644 --- a/code/controllers/subsystem/discord.dm +++ b/code/controllers/subsystem/discord.dm @@ -1,185 +1,185 @@ -/* -NOTES: -There is a DB table to track ckeys and associated discord IDs. -This system REQUIRES TGS, and will auto-disable if TGS is not present. -The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown -It only writes to the disk every 5 minutes, and it won't write to disk if the file is the same as it was the last time it was written. This is to save on disk writes -The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server. - -################## -# HOW THIS WORKS # -################## - -ROUNDSTART: -1] The file is loaded and the discord IDs are extracted -2] A ping is sent to the discord with the IDs of people who wished to be notified -3] The file is emptied - -MIDROUND: -1] Someone usees the notify verb, it adds their discord ID to the list. -2] On fire, it will write that to the disk, as long as conditions above are correct - -END ROUND: -1] The file is force-saved, incase it hasn't fired at end round - -This is an absolute clusterfuck, but its my clusterfuck -aa07 -*/ - -SUBSYSTEM_DEF(discord) - name = "Discord" - wait = 3000 - init_order = INIT_ORDER_DISCORD - - /// People to save to notify file - var/list/notify_members = list() - /// Copy of previous list, so the SS doesnt have to fire if no new members have been added - var/list/notify_members_cache = list() - /// People to notify on roundstart - var/list/people_to_notify = list() - /// List that holds accounts to link, used in conjunction with TGS - var/list/account_link_cache = list() - /// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown - var/list/reverify_cache = list() - var/notify_file = file("data/notify.json") - /// Is TGS enabled (If not we won't fire because otherwise this is useless) - var/enabled = 0 - -/datum/controller/subsystem/discord/Initialize(start_timeofday) - // Check for if we are using TGS, otherwise return and disables firing - if(world.TgsAvailable()) - enabled = 1 // Allows other procs to use this (Account linking, etc) - else - can_fire = 0 // We dont want excess firing - return ..() // Cancel - - try - people_to_notify = json_decode(file2text(notify_file)) - catch - pass() // The list can just stay as its default (blank). Pass() exists because it needs a catch - var/notifymsg = jointext(people_to_notify, ", ") - if(notifymsg) - send2chat(trim(notifymsg), CONFIG_GET(string/chat_new_game_notifications)) // Sends the message to the discord, using same config option as the roundstart notification - fdel(notify_file) // Deletes the file - return ..() - -/datum/controller/subsystem/discord/fire() - if(!enabled) - return // Dont do shit if its disabled - if(notify_members == notify_members_cache) - return // Dont re-write the file - // If we are all clear - write_notify_file() - -/datum/controller/subsystem/discord/Shutdown() - write_notify_file() // Guaranteed force-write on server close - -/datum/controller/subsystem/discord/proc/write_notify_file() - if(!enabled) // Dont do shit if its disabled - return - fdel(notify_file) // Deletes the file first to make sure it writes properly - WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file - notify_members_cache = notify_members // Updates the cache list - -// Returns ID from ckey -/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey) - //We cast the discord ID to varchar to prevent BYOND mangling - //it into it's scientific notation - var/datum/db_query/query_get_discord_id = SSdbcore.NewQuery( - "SELECT CAST(discord_id AS CHAR(25)) FROM [format_table_name("player")] WHERE ckey = :ckey", - list("ckey" = lookup_ckey) - ) - if(!query_get_discord_id.Execute()) - qdel(query_get_discord_id) - return - if(query_get_discord_id.NextRow()) - . = query_get_discord_id.item[1] - qdel(query_get_discord_id) - -// Returns ckey from ID -/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id) - var/datum/db_query/query_get_discord_ckey = SSdbcore.NewQuery( - "SELECT ckey FROM [format_table_name("player")] WHERE discord_id = :discord_id", - list("discord_id" = lookup_id) - ) - if(!query_get_discord_ckey.Execute()) - qdel(query_get_discord_ckey) - return - if(query_get_discord_ckey.NextRow()) - . = query_get_discord_ckey.item[1] - qdel(query_get_discord_ckey) - -// Finalises link -/datum/controller/subsystem/discord/proc/link_account(ckey) - var/datum/db_query/link_account = SSdbcore.NewQuery( - "UPDATE [format_table_name("player")] SET discord_id = :discord_id WHERE ckey = :ckey", - list("discord_id" = account_link_cache[ckey], "ckey" = ckey) - ) - link_account.Execute() - qdel(link_account) - account_link_cache -= ckey - -// Unlink account (Admin verb used) -/datum/controller/subsystem/discord/proc/unlink_account(ckey) - var/datum/db_query/unlink_account = SSdbcore.NewQuery( - "UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = :ckey", - list("ckey" = ckey) - ) - unlink_account.Execute() - qdel(unlink_account) - -// Clean up a discord account mention -/datum/controller/subsystem/discord/proc/id_clean(input) - var/regex/num_only = regex("\[^0-9\]", "g") - return num_only.Replace(input, "") - -/datum/controller/subsystem/discord/proc/grant_role(id) - // Ignore this shit if config isn't enabled for it - if(!CONFIG_GET(flag/enable_discord_autorole)) - return - - var/url = "https://discordapp.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]" - - // Make the request - var/datum/http_request/req = new() - req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]")) - req.begin_async() - UNTIL(req.is_complete()) - var/datum/http_response/res = req.into_response() - - WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]") - -/** - * Sends a message to TGS chat channels. - * - * message - The message to send. - * channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s). - */ -/proc/send2chat(message, channel_tag) - if(channel_tag == null || !world.TgsAvailable()) - return - - var/datum/tgs_version/version = world.TgsVersion() - if(channel_tag == "" || version.suite == 3) - world.TgsTargetedChatBroadcast(message, FALSE) - return - - var/list/channels_to_use = list() - for(var/I in world.TgsChatChannelInfo()) - var/datum/tgs_chat_channel/channel = I - var/list/applicable_tags = splittext(channel.tag, ",") - if(channel_tag in applicable_tags) - channels_to_use += channel - - if(channels_to_use.len) - world.TgsChatBroadcast(message, channels_to_use) - -/** - * Sends a message to TGS admin chat channels. - * - * category - The category of the mssage. - * message - The message to send. - */ -/proc/send2adminchat(category, message) - category = replacetext(replacetext(category, "\proper", ""), "\improper", "") - message = replacetext(replacetext(message, "\proper", ""), "\improper", "") - world.TgsTargetedChatBroadcast("[category] | [message]", TRUE) +/* +NOTES: +There is a DB table to track ckeys and associated discord IDs. +This system REQUIRES TGS, and will auto-disable if TGS is not present. +The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown +It only writes to the disk every 5 minutes, and it won't write to disk if the file is the same as it was the last time it was written. This is to save on disk writes +The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server. + +################## +# HOW THIS WORKS # +################## + +ROUNDSTART: +1] The file is loaded and the discord IDs are extracted +2] A ping is sent to the discord with the IDs of people who wished to be notified +3] The file is emptied + +MIDROUND: +1] Someone usees the notify verb, it adds their discord ID to the list. +2] On fire, it will write that to the disk, as long as conditions above are correct + +END ROUND: +1] The file is force-saved, incase it hasn't fired at end round + +This is an absolute clusterfuck, but its my clusterfuck -aa07 +*/ + +SUBSYSTEM_DEF(discord) + name = "Discord" + wait = 3000 + init_order = INIT_ORDER_DISCORD + + /// People to save to notify file + var/list/notify_members = list() + /// Copy of previous list, so the SS doesnt have to fire if no new members have been added + var/list/notify_members_cache = list() + /// People to notify on roundstart + var/list/people_to_notify = list() + /// List that holds accounts to link, used in conjunction with TGS + var/list/account_link_cache = list() + /// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown + var/list/reverify_cache = list() + var/notify_file = file("data/notify.json") + /// Is TGS enabled (If not we won't fire because otherwise this is useless) + var/enabled = 0 + +/datum/controller/subsystem/discord/Initialize(start_timeofday) + // Check for if we are using TGS, otherwise return and disables firing + if(world.TgsAvailable()) + enabled = 1 // Allows other procs to use this (Account linking, etc) + else + can_fire = 0 // We dont want excess firing + return ..() // Cancel + + try + people_to_notify = json_decode(file2text(notify_file)) + catch + pass() // The list can just stay as its default (blank). Pass() exists because it needs a catch + var/notifymsg = jointext(people_to_notify, ", ") + if(notifymsg) + send2chat(trim(notifymsg), CONFIG_GET(string/chat_new_game_notifications)) // Sends the message to the discord, using same config option as the roundstart notification + fdel(notify_file) // Deletes the file + return ..() + +/datum/controller/subsystem/discord/fire() + if(!enabled) + return // Dont do shit if its disabled + if(notify_members == notify_members_cache) + return // Dont re-write the file + // If we are all clear + write_notify_file() + +/datum/controller/subsystem/discord/Shutdown() + write_notify_file() // Guaranteed force-write on server close + +/datum/controller/subsystem/discord/proc/write_notify_file() + if(!enabled) // Dont do shit if its disabled + return + fdel(notify_file) // Deletes the file first to make sure it writes properly + WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file + notify_members_cache = notify_members // Updates the cache list + +// Returns ID from ckey +/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey) + //We cast the discord ID to varchar to prevent BYOND mangling + //it into it's scientific notation + var/datum/db_query/query_get_discord_id = SSdbcore.NewQuery( + "SELECT CAST(discord_id AS CHAR(25)) FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = lookup_ckey) + ) + if(!query_get_discord_id.Execute()) + qdel(query_get_discord_id) + return + if(query_get_discord_id.NextRow()) + . = query_get_discord_id.item[1] + qdel(query_get_discord_id) + +// Returns ckey from ID +/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id) + var/datum/db_query/query_get_discord_ckey = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE discord_id = :discord_id", + list("discord_id" = lookup_id) + ) + if(!query_get_discord_ckey.Execute()) + qdel(query_get_discord_ckey) + return + if(query_get_discord_ckey.NextRow()) + . = query_get_discord_ckey.item[1] + qdel(query_get_discord_ckey) + +// Finalises link +/datum/controller/subsystem/discord/proc/link_account(ckey) + var/datum/db_query/link_account = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET discord_id = :discord_id WHERE ckey = :ckey", + list("discord_id" = account_link_cache[ckey], "ckey" = ckey) + ) + link_account.Execute() + qdel(link_account) + account_link_cache -= ckey + +// Unlink account (Admin verb used) +/datum/controller/subsystem/discord/proc/unlink_account(ckey) + var/datum/db_query/unlink_account = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = :ckey", + list("ckey" = ckey) + ) + unlink_account.Execute() + qdel(unlink_account) + +// Clean up a discord account mention +/datum/controller/subsystem/discord/proc/id_clean(input) + var/regex/num_only = regex("\[^0-9\]", "g") + return num_only.Replace(input, "") + +/datum/controller/subsystem/discord/proc/grant_role(id) + // Ignore this shit if config isn't enabled for it + if(!CONFIG_GET(flag/enable_discord_autorole)) + return + + var/url = "https://discordapp.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]" + + // Make the request + var/datum/http_request/req = new() + req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]")) + req.begin_async() + UNTIL(req.is_complete()) + var/datum/http_response/res = req.into_response() + + WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]") + +/** + * Sends a message to TGS chat channels. + * + * message - The message to send. + * channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s). + */ +/proc/send2chat(message, channel_tag) + if(channel_tag == null || !world.TgsAvailable()) + return + + var/datum/tgs_version/version = world.TgsVersion() + if(channel_tag == "" || version.suite == 3) + world.TgsTargetedChatBroadcast(message, FALSE) + return + + var/list/channels_to_use = list() + for(var/I in world.TgsChatChannelInfo()) + var/datum/tgs_chat_channel/channel = I + var/list/applicable_tags = splittext(channel.tag, ",") + if(channel_tag in applicable_tags) + channels_to_use += channel + + if(channels_to_use.len) + world.TgsChatBroadcast(message, channels_to_use) + +/** + * Sends a message to TGS admin chat channels. + * + * category - The category of the mssage. + * message - The message to send. + */ +/proc/send2adminchat(category, message) + category = replacetext(replacetext(category, "\proper", ""), "\improper", "") + message = replacetext(replacetext(message, "\proper", ""), "\improper", "") + world.TgsTargetedChatBroadcast("[category] | [message]", TRUE) diff --git a/code/controllers/subsystem/moods.dm b/code/controllers/subsystem/moods.dm index d1e58c7452d..f6b6ffcb404 100644 --- a/code/controllers/subsystem/moods.dm +++ b/code/controllers/subsystem/moods.dm @@ -1,4 +1,4 @@ -PROCESSING_SUBSYSTEM_DEF(mood) - name = "Mood" - flags = SS_NO_INIT | SS_BACKGROUND - priority = 20 +PROCESSING_SUBSYSTEM_DEF(mood) + name = "Mood" + flags = SS_NO_INIT | SS_BACKGROUND + priority = 20 diff --git a/code/controllers/subsystem/nightshift.dm b/code/controllers/subsystem/nightshift.dm index 68116483ad4..ca9979076a8 100644 --- a/code/controllers/subsystem/nightshift.dm +++ b/code/controllers/subsystem/nightshift.dm @@ -1,55 +1,55 @@ -SUBSYSTEM_DEF(nightshift) - name = "Night Shift" - wait = 600 - flags = SS_NO_TICK_CHECK - - var/nightshift_active = FALSE - var/nightshift_start_time = 702000 //7:30 PM, station time - var/nightshift_end_time = 270000 //7:30 AM, station time - var/nightshift_first_check = 30 SECONDS - - var/high_security_mode = FALSE - -/datum/controller/subsystem/nightshift/Initialize() - if(!CONFIG_GET(flag/enable_night_shifts)) - can_fire = FALSE - return ..() - -/datum/controller/subsystem/nightshift/fire(resumed = FALSE) - if(world.time - SSticker.round_start_time < nightshift_first_check) - return - check_nightshift() - -/datum/controller/subsystem/nightshift/proc/announce(message) - priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement") - -/datum/controller/subsystem/nightshift/proc/check_nightshift() - var/emergency = GLOB.security_level >= SEC_LEVEL_RED - var/announcing = TRUE - var/time = station_time() - var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time) - if(high_security_mode != emergency) - high_security_mode = emergency - if(night_time) - announcing = FALSE - if(!emergency) - announce("Restoring night lighting configuration to normal operation.") - else - announce("Disabling night lighting: Station is in a state of emergency.") - if(emergency) - night_time = FALSE - if(nightshift_active != night_time) - update_nightshift(night_time, announcing) - -/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE) - nightshift_active = active - if(announce) - if (active) - announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.") - else - announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.") - for(var/A in GLOB.apcs_list) - var/obj/machinery/power/apc/APC = A - if (APC.area && (APC.area.type in GLOB.the_station_areas)) - APC.set_nightshift(active) - CHECK_TICK +SUBSYSTEM_DEF(nightshift) + name = "Night Shift" + wait = 600 + flags = SS_NO_TICK_CHECK + + var/nightshift_active = FALSE + var/nightshift_start_time = 702000 //7:30 PM, station time + var/nightshift_end_time = 270000 //7:30 AM, station time + var/nightshift_first_check = 30 SECONDS + + var/high_security_mode = FALSE + +/datum/controller/subsystem/nightshift/Initialize() + if(!CONFIG_GET(flag/enable_night_shifts)) + can_fire = FALSE + return ..() + +/datum/controller/subsystem/nightshift/fire(resumed = FALSE) + if(world.time - SSticker.round_start_time < nightshift_first_check) + return + check_nightshift() + +/datum/controller/subsystem/nightshift/proc/announce(message) + priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement") + +/datum/controller/subsystem/nightshift/proc/check_nightshift() + var/emergency = GLOB.security_level >= SEC_LEVEL_RED + var/announcing = TRUE + var/time = station_time() + var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time) + if(high_security_mode != emergency) + high_security_mode = emergency + if(night_time) + announcing = FALSE + if(!emergency) + announce("Restoring night lighting configuration to normal operation.") + else + announce("Disabling night lighting: Station is in a state of emergency.") + if(emergency) + night_time = FALSE + if(nightshift_active != night_time) + update_nightshift(night_time, announcing) + +/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE) + nightshift_active = active + if(announce) + if (active) + announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.") + else + announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.") + for(var/A in GLOB.apcs_list) + var/obj/machinery/power/apc/APC = A + if (APC.area && (APC.area.type in GLOB.the_station_areas)) + APC.set_nightshift(active) + CHECK_TICK diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm index d93f1f24076..c69154a1b72 100644 --- a/code/controllers/subsystem/npcpool.dm +++ b/code/controllers/subsystem/npcpool.dm @@ -1,34 +1,34 @@ -SUBSYSTEM_DEF(npcpool) - name = "NPC Pool" - flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND - priority = FIRE_PRIORITY_NPC - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - - var/list/currentrun = list() - -/datum/controller/subsystem/npcpool/stat_entry() - var/list/activelist = GLOB.simple_animals[AI_ON] - ..("NPCS:[activelist.len]") - -/datum/controller/subsystem/npcpool/fire(resumed = FALSE) - - if (!resumed) - var/list/activelist = GLOB.simple_animals[AI_ON] - src.currentrun = activelist.Copy() - - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while(currentrun.len) - var/mob/living/simple_animal/SA = currentrun[currentrun.len] - --currentrun.len - - if(!SA.ckey && !SA.notransform) - if(SA.stat != DEAD) - SA.handle_automated_movement() - if(SA.stat != DEAD) - SA.handle_automated_action() - if(SA.stat != DEAD) - SA.handle_automated_speech() - if (MC_TICK_CHECK) - return +SUBSYSTEM_DEF(npcpool) + name = "NPC Pool" + flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND + priority = FIRE_PRIORITY_NPC + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun = list() + +/datum/controller/subsystem/npcpool/stat_entry() + var/list/activelist = GLOB.simple_animals[AI_ON] + ..("NPCS:[activelist.len]") + +/datum/controller/subsystem/npcpool/fire(resumed = FALSE) + + if (!resumed) + var/list/activelist = GLOB.simple_animals[AI_ON] + src.currentrun = activelist.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(currentrun.len) + var/mob/living/simple_animal/SA = currentrun[currentrun.len] + --currentrun.len + + if(!SA.ckey && !SA.notransform) + if(SA.stat != DEAD) + SA.handle_automated_movement() + if(SA.stat != DEAD) + SA.handle_automated_action() + if(SA.stat != DEAD) + SA.handle_automated_speech() + if (MC_TICK_CHECK) + return diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm index fee93d14b28..8e1cf946ae1 100644 --- a/code/controllers/subsystem/pathfinder.dm +++ b/code/controllers/subsystem/pathfinder.dm @@ -1,48 +1,48 @@ -SUBSYSTEM_DEF(pathfinder) - name = "Pathfinder" - init_order = INIT_ORDER_PATH - flags = SS_NO_FIRE - var/datum/flowcache/mobs - var/datum/flowcache/circuits - var/static/space_type_cache - -/datum/controller/subsystem/pathfinder/Initialize() - space_type_cache = typecacheof(/turf/open/space) - mobs = new(10) - circuits = new(3) - return ..() - -/datum/flowcache - var/lcount - var/run - var/free - var/list/flow - -/datum/flowcache/New(var/n) - . = ..() - lcount = n - run = 0 - free = 1 - flow = new/list(lcount) - -/datum/flowcache/proc/getfree(atom/M) - if(run < lcount) - run += 1 - while(flow[free]) - CHECK_TICK - free = (free % lcount) + 1 - var/t = addtimer(CALLBACK(src, /datum/flowcache.proc/toolong, free), 150, TIMER_STOPPABLE) - flow[free] = t - flow[t] = M - return free - else - return 0 - -/datum/flowcache/proc/toolong(l) - log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]") - found(l) - -/datum/flowcache/proc/found(l) - deltimer(flow[l]) - flow[l] = null - run -= 1 +SUBSYSTEM_DEF(pathfinder) + name = "Pathfinder" + init_order = INIT_ORDER_PATH + flags = SS_NO_FIRE + var/datum/flowcache/mobs + var/datum/flowcache/circuits + var/static/space_type_cache + +/datum/controller/subsystem/pathfinder/Initialize() + space_type_cache = typecacheof(/turf/open/space) + mobs = new(10) + circuits = new(3) + return ..() + +/datum/flowcache + var/lcount + var/run + var/free + var/list/flow + +/datum/flowcache/New(var/n) + . = ..() + lcount = n + run = 0 + free = 1 + flow = new/list(lcount) + +/datum/flowcache/proc/getfree(atom/M) + if(run < lcount) + run += 1 + while(flow[free]) + CHECK_TICK + free = (free % lcount) + 1 + var/t = addtimer(CALLBACK(src, /datum/flowcache.proc/toolong, free), 150, TIMER_STOPPABLE) + flow[free] = t + flow[t] = M + return free + else + return 0 + +/datum/flowcache/proc/toolong(l) + log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]") + found(l) + +/datum/flowcache/proc/found(l) + deltimer(flow[l]) + flow[l] = null + run -= 1 diff --git a/code/controllers/subsystem/processing/networks.dm b/code/controllers/subsystem/processing/networks.dm index 719e97d1eed..f7f16538a62 100644 --- a/code/controllers/subsystem/processing/networks.dm +++ b/code/controllers/subsystem/processing/networks.dm @@ -1,51 +1,51 @@ -PROCESSING_SUBSYSTEM_DEF(networks) - name = "Networks" - priority = FIRE_PRIORITY_NETWORKS - wait = 1 - stat_tag = "NET" - flags = SS_KEEP_TIMING - init_order = INIT_ORDER_NETWORKS - var/datum/ntnet/station/station_network - var/assignment_hardware_id = HID_RESTRICTED_END - var/list/networks_by_id = list() //id = network - var/list/interfaces_by_id = list() //hardware id = component interface - var/resolve_collisions = TRUE - -/datum/controller/subsystem/processing/networks/Initialize() - station_network = new - station_network.register_map_supremecy() - . = ..() - -/datum/controller/subsystem/processing/networks/proc/register_network(datum/ntnet/network) - if(!networks_by_id[network.network_id]) - networks_by_id[network.network_id] = network - return TRUE - return FALSE - -/datum/controller/subsystem/processing/networks/proc/unregister_network(datum/ntnet/network) - networks_by_id -= network.network_id - return TRUE - -/datum/controller/subsystem/processing/networks/proc/register_interface(datum/component/ntnet_interface/D) - if(!interfaces_by_id[D.hardware_id]) - interfaces_by_id[D.hardware_id] = D - return TRUE - return FALSE - -/datum/controller/subsystem/processing/networks/proc/unregister_interface(datum/component/ntnet_interface/D) - interfaces_by_id -= D.hardware_id - return TRUE - -/datum/controller/subsystem/processing/networks/proc/get_next_HID() - var/string = "[num2text(assignment_hardware_id++, 12)]" - return make_address(string) - -/datum/controller/subsystem/processing/networks/proc/make_address(string) - if(!string) - return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null - var/hex = md5(string) - if(!hex) - return //errored - . = "[copytext_char(hex, 1, 9)]" //16 ^ 8 possibilities I think. - if(interfaces_by_id[.]) - return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null +PROCESSING_SUBSYSTEM_DEF(networks) + name = "Networks" + priority = FIRE_PRIORITY_NETWORKS + wait = 1 + stat_tag = "NET" + flags = SS_KEEP_TIMING + init_order = INIT_ORDER_NETWORKS + var/datum/ntnet/station/station_network + var/assignment_hardware_id = HID_RESTRICTED_END + var/list/networks_by_id = list() //id = network + var/list/interfaces_by_id = list() //hardware id = component interface + var/resolve_collisions = TRUE + +/datum/controller/subsystem/processing/networks/Initialize() + station_network = new + station_network.register_map_supremecy() + . = ..() + +/datum/controller/subsystem/processing/networks/proc/register_network(datum/ntnet/network) + if(!networks_by_id[network.network_id]) + networks_by_id[network.network_id] = network + return TRUE + return FALSE + +/datum/controller/subsystem/processing/networks/proc/unregister_network(datum/ntnet/network) + networks_by_id -= network.network_id + return TRUE + +/datum/controller/subsystem/processing/networks/proc/register_interface(datum/component/ntnet_interface/D) + if(!interfaces_by_id[D.hardware_id]) + interfaces_by_id[D.hardware_id] = D + return TRUE + return FALSE + +/datum/controller/subsystem/processing/networks/proc/unregister_interface(datum/component/ntnet_interface/D) + interfaces_by_id -= D.hardware_id + return TRUE + +/datum/controller/subsystem/processing/networks/proc/get_next_HID() + var/string = "[num2text(assignment_hardware_id++, 12)]" + return make_address(string) + +/datum/controller/subsystem/processing/networks/proc/make_address(string) + if(!string) + return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null + var/hex = md5(string) + if(!hex) + return //errored + . = "[copytext_char(hex, 1, 9)]" //16 ^ 8 possibilities I think. + if(interfaces_by_id[.]) + return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null diff --git a/code/controllers/subsystem/processing/projectiles.dm b/code/controllers/subsystem/processing/projectiles.dm index 479298e4bbd..ba62f0cca65 100644 --- a/code/controllers/subsystem/processing/projectiles.dm +++ b/code/controllers/subsystem/processing/projectiles.dm @@ -1,23 +1,23 @@ -PROCESSING_SUBSYSTEM_DEF(projectiles) - name = "Projectiles" - wait = 1 - stat_tag = "PP" - flags = SS_NO_INIT|SS_TICKER - var/global_max_tick_moves = 10 - var/global_pixel_speed = 2 - var/global_iterations_per_move = 16 - -/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed) - global_pixel_speed = new_speed - for(var/i in processing) - var/obj/projectile/P = i - if(istype(P)) //there's non projectiles on this too. - P.set_pixel_speed(new_speed) - -/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, global_pixel_speed)) - set_pixel_speed(var_value) - return TRUE - else - return ..() +PROCESSING_SUBSYSTEM_DEF(projectiles) + name = "Projectiles" + wait = 1 + stat_tag = "PP" + flags = SS_NO_INIT|SS_TICKER + var/global_max_tick_moves = 10 + var/global_pixel_speed = 2 + var/global_iterations_per_move = 16 + +/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed) + global_pixel_speed = new_speed + for(var/i in processing) + var/obj/projectile/P = i + if(istype(P)) //there's non projectiles on this too. + P.set_pixel_speed(new_speed) + +/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value) + switch(var_name) + if(NAMEOF(src, global_pixel_speed)) + set_pixel_speed(var_value) + return TRUE + else + return ..() diff --git a/code/controllers/subsystem/processing/wet_floors.dm b/code/controllers/subsystem/processing/wet_floors.dm index dfbc1ac8e47..4bda8b54536 100644 --- a/code/controllers/subsystem/processing/wet_floors.dm +++ b/code/controllers/subsystem/processing/wet_floors.dm @@ -1,7 +1,7 @@ -PROCESSING_SUBSYSTEM_DEF(wet_floors) - name = "Wet floors" - priority = FIRE_PRIORITY_WET_FLOORS - wait = 10 - stat_tag = "WFP" //Used for logging - var/temperature_coeff = 2 - var/time_ratio = 1.5 SECONDS +PROCESSING_SUBSYSTEM_DEF(wet_floors) + name = "Wet floors" + priority = FIRE_PRIORITY_WET_FLOORS + wait = 10 + stat_tag = "WFP" //Used for logging + var/temperature_coeff = 2 + var/time_ratio = 1.5 SECONDS diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index a512dac4fb4..a47cf8575d7 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -1,291 +1,291 @@ - -SUBSYSTEM_DEF(research) - name = "Research" - priority = FIRE_PRIORITY_RESEARCH - wait = 10 - init_order = INIT_ORDER_RESEARCH - //TECHWEB STATIC - var/list/techweb_nodes = list() //associative id = node datum - var/list/techweb_designs = list() //associative id = node datum - var/list/datum/techweb/techwebs = list() - var/datum/techweb/science/science_tech - var/datum/techweb/admin/admin_tech - var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console. - var/datum/design/error_design/error_design - - //ERROR LOGGING - var/list/invalid_design_ids = list() //associative id = number of times - var/list/invalid_node_ids = list() //associative id = number of times - var/list/invalid_node_boost = list() //associative id = error message - - var/list/obj/machinery/rnd/server/servers = list() - - var/list/techweb_nodes_starting = list() //associative id = TRUE - var/list/techweb_categories = list() //category name = list(node.id = TRUE) - var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount)) - var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default. - var/list/techweb_nodes_experimental = list() //Node ids that are exclusive to the BEPIS. - var/list/techweb_point_items = list( //path = list(point type = value) - /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000) - ) - var/list/errored_datums = list() - var/list/point_types = list() //typecache style type = TRUE list - var/list/slime_already_researched = list() //Slime cores that have already been researched - //---------------------------------------------- - var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME) - var/multiserver_calculation = FALSE - var/last_income - //^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^ - - //Aiming for 1.5 hours to max R&D - //[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min] - //Around 450000 points max??? - - /// The global list of raw anomaly types that have been refined, for hard limits. - var/list/created_anomaly_types = list() - /// The hard limits of cores created for each anomaly type. For faster code lookup without switch statements. - var/list/anomaly_hard_limit_by_type = list( - ANOMALY_CORE_BLUESPACE = MAX_CORES_BLUESPACE, - ANOMALY_CORE_PYRO = MAX_CORES_PYRO, - ANOMALY_CORE_GRAVITATIONAL = MAX_CORES_GRAVITATIONAL, - ANOMALY_CORE_VORTEX = MAX_CORES_VORTEX, - ANOMALY_CORE_FLUX = MAX_CORES_FLUX - ) - -/datum/controller/subsystem/research/Initialize() - point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES - initialize_all_techweb_designs() - initialize_all_techweb_nodes() - science_tech = new /datum/techweb/science - admin_tech = new /datum/techweb/admin - autosort_categories() - error_design = new - error_node = new - return ..() - -/datum/controller/subsystem/research/fire() - var/list/bitcoins = list() - if(multiserver_calculation) - var/eff = calculate_server_coefficient() - for(var/obj/machinery/rnd/server/miner in servers) - var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE. - for(var/i in result) - result[i] *= eff - bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i] - else - for(var/obj/machinery/rnd/server/miner in servers) - if(miner.working) - bitcoins = single_server_income.Copy() - break //Just need one to work. - if (!isnull(last_income)) - var/income_time_difference = world.time - last_income - science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account - for(var/i in bitcoins) - bitcoins[i] *= income_time_difference / 10 - science_tech.add_point_list(bitcoins) - last_income = world.time - -/datum/controller/subsystem/research/proc/calculate_server_coefficient() //Diminishing returns. - var/amt = servers.len - if(!amt) - return 0 - var/coeff = 100 - coeff = sqrt(coeff / amt) - return coeff - -/datum/controller/subsystem/research/proc/autosort_categories() - for(var/i in techweb_nodes) - var/datum/techweb_node/I = techweb_nodes[i] - if(techweb_categories[I.category]) - techweb_categories[I.category][I.id] = TRUE - else - techweb_categories[I.category] = list(I.id = TRUE) - -/datum/controller/subsystem/research/proc/techweb_node_by_id(id) - return techweb_nodes[id] || error_node - -/datum/controller/subsystem/research/proc/techweb_design_by_id(id) - return techweb_designs[id] || error_design - -/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D) - for(var/i in techweb_nodes) - var/datum/techweb_node/TN = techwebs[i] - TN.on_design_deletion(TN) - for(var/i in techwebs) - var/datum/techweb/T = i - T.recalculate_nodes(TRUE) - -/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN) - for(var/i in techweb_nodes) - var/datum/techweb_node/TN2 = techwebs[i] - TN2.on_node_deletion(TN) - for(var/i in techwebs) - var/datum/techweb/T = i - T.recalculate_nodes(TRUE) - -/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE) - if(islist(techweb_nodes) && clearall) - QDEL_LIST(techweb_nodes) - if(islist(techweb_nodes_starting && clearall)) - techweb_nodes_starting.Cut() - var/list/returned = list() - for(var/path in subtypesof(/datum/techweb_node)) - var/datum/techweb_node/TN = path - if(isnull(initial(TN.id))) - continue - TN = new path - if(returned[initial(TN.id)]) - stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]") - errored_datums[TN] = initial(TN.id) - continue - returned[initial(TN.id)] = TN - if(TN.starting_node) - techweb_nodes_starting[TN.id] = TRUE - for(var/id in techweb_nodes) - var/datum/techweb_node/TN = techweb_nodes[id] - TN.Initialize() - techweb_nodes = returned - if (!verify_techweb_nodes()) //Verify all nodes have ids and such. - stack_trace("Invalid techweb nodes detected") - calculate_techweb_nodes() - calculate_techweb_boost_list() - if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly. - CRASH("Invalid techweb nodes detected") - -/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE) - if(islist(techweb_designs) && clearall) - QDEL_LIST(techweb_designs) - var/list/returned = list() - for(var/path in subtypesof(/datum/design)) - var/datum/design/DN = path - if(isnull(initial(DN.id))) - stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]") - continue - else if(initial(DN.id) == DESIGN_ID_IGNORE) - continue - DN = new path - if(returned[initial(DN.id)]) - stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]") - errored_datums[DN] = initial(DN.id) - continue - DN.InitializeMaterials() //Initialize the materials in the design - returned[initial(DN.id)] = DN - techweb_designs = returned - verify_techweb_designs() - - -/datum/controller/subsystem/research/proc/verify_techweb_nodes() - . = TRUE - for(var/n in techweb_nodes) - var/datum/techweb_node/N = techweb_nodes[n] - if(!istype(N)) - WARNING("Invalid research node with ID [n] detected and removed.") - techweb_nodes -= n - research_node_id_error(n) - . = FALSE - for(var/p in N.prereq_ids) - var/datum/techweb_node/P = techweb_nodes[p] - if(!istype(P)) - WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.") - N.prereq_ids -= p - research_node_id_error(p) - . = FALSE - for(var/d in N.design_ids) - var/datum/design/D = techweb_designs[d] - if(!istype(D)) - WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.") - N.design_ids -= d - design_id_error(d) - . = FALSE - for(var/u in N.unlock_ids) - var/datum/techweb_node/U = techweb_nodes[u] - if(!istype(U)) - WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.") - N.unlock_ids -= u - research_node_id_error(u) - . = FALSE - for(var/p in N.boost_item_paths) - if(!ispath(p)) - N.boost_item_paths -= p - WARNING("[p] is not a valid path.") - node_boost_error(N.id, "[p] is not a valid path.") - . = FALSE - var/list/points = N.boost_item_paths[p] - if(islist(points)) - for(var/i in points) - if(!isnum(points[i])) - WARNING("[points[i]] is not a valid number.") - node_boost_error(N.id, "[points[i]] is not a valid number.") - . = FALSE - else if(!point_types[i]) - WARNING("[i] is not a valid point type.") - node_boost_error(N.id, "[i] is not a valid point type.") - . = FALSE - else if(!isnull(points)) - N.boost_item_paths -= p - node_boost_error(N.id, "No valid list.") - WARNING("No valid list.") - . = FALSE - CHECK_TICK - -/datum/controller/subsystem/research/proc/verify_techweb_designs() - for(var/d in techweb_designs) - var/datum/design/D = techweb_designs[d] - if(!istype(D)) - stack_trace("WARNING: Invalid research design with ID [d] detected and removed.") - techweb_designs -= d - CHECK_TICK - -/datum/controller/subsystem/research/proc/research_node_id_error(id) - if(invalid_node_ids[id]) - invalid_node_ids[id]++ - else - invalid_node_ids[id] = 1 - -/datum/controller/subsystem/research/proc/design_id_error(id) - if(invalid_design_ids[id]) - invalid_design_ids[id]++ - else - invalid_design_ids[id] = 1 - -/datum/controller/subsystem/research/proc/calculate_techweb_nodes() - for(var/design_id in techweb_designs) - var/datum/design/D = techweb_designs[design_id] - D.unlocked_by.Cut() - for(var/node_id in techweb_nodes) - var/datum/techweb_node/node = techweb_nodes[node_id] - node.unlock_ids = list() - for(var/i in node.design_ids) - var/datum/design/D = techweb_designs[i] - node.design_ids[i] = TRUE - D.unlocked_by += node.id - if(node.hidden) - techweb_nodes_hidden[node.id] = TRUE - if(node.experimental) - techweb_nodes_experimental[node.id] = TRUE - CHECK_TICK - generate_techweb_unlock_linking() - -/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking() - for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication. - var/datum/techweb_node/node = techweb_nodes[node_id] - node.unlock_ids = list() - for(var/node_id in techweb_nodes) - var/datum/techweb_node/node = techweb_nodes[node_id] - for(var/prereq_id in node.prereq_ids) - var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id) - prereq_node.unlock_ids[node.id] = node - -/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE) - if(clearall) - techweb_boost_items = list() - for(var/node_id in techweb_nodes) - var/datum/techweb_node/node = techweb_nodes[node_id] - for(var/path in node.boost_item_paths) - if(!ispath(path)) - continue - if(length(techweb_boost_items[path])) - techweb_boost_items[path][node.id] = node.boost_item_paths[path] - else - techweb_boost_items[path] = list(node.id = node.boost_item_paths[path]) - CHECK_TICK + +SUBSYSTEM_DEF(research) + name = "Research" + priority = FIRE_PRIORITY_RESEARCH + wait = 10 + init_order = INIT_ORDER_RESEARCH + //TECHWEB STATIC + var/list/techweb_nodes = list() //associative id = node datum + var/list/techweb_designs = list() //associative id = node datum + var/list/datum/techweb/techwebs = list() + var/datum/techweb/science/science_tech + var/datum/techweb/admin/admin_tech + var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console. + var/datum/design/error_design/error_design + + //ERROR LOGGING + var/list/invalid_design_ids = list() //associative id = number of times + var/list/invalid_node_ids = list() //associative id = number of times + var/list/invalid_node_boost = list() //associative id = error message + + var/list/obj/machinery/rnd/server/servers = list() + + var/list/techweb_nodes_starting = list() //associative id = TRUE + var/list/techweb_categories = list() //category name = list(node.id = TRUE) + var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount)) + var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default. + var/list/techweb_nodes_experimental = list() //Node ids that are exclusive to the BEPIS. + var/list/techweb_point_items = list( //path = list(point type = value) + /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000) + ) + var/list/errored_datums = list() + var/list/point_types = list() //typecache style type = TRUE list + var/list/slime_already_researched = list() //Slime cores that have already been researched + //---------------------------------------------- + var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME) + var/multiserver_calculation = FALSE + var/last_income + //^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^ + + //Aiming for 1.5 hours to max R&D + //[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min] + //Around 450000 points max??? + + /// The global list of raw anomaly types that have been refined, for hard limits. + var/list/created_anomaly_types = list() + /// The hard limits of cores created for each anomaly type. For faster code lookup without switch statements. + var/list/anomaly_hard_limit_by_type = list( + ANOMALY_CORE_BLUESPACE = MAX_CORES_BLUESPACE, + ANOMALY_CORE_PYRO = MAX_CORES_PYRO, + ANOMALY_CORE_GRAVITATIONAL = MAX_CORES_GRAVITATIONAL, + ANOMALY_CORE_VORTEX = MAX_CORES_VORTEX, + ANOMALY_CORE_FLUX = MAX_CORES_FLUX + ) + +/datum/controller/subsystem/research/Initialize() + point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES + initialize_all_techweb_designs() + initialize_all_techweb_nodes() + science_tech = new /datum/techweb/science + admin_tech = new /datum/techweb/admin + autosort_categories() + error_design = new + error_node = new + return ..() + +/datum/controller/subsystem/research/fire() + var/list/bitcoins = list() + if(multiserver_calculation) + var/eff = calculate_server_coefficient() + for(var/obj/machinery/rnd/server/miner in servers) + var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE. + for(var/i in result) + result[i] *= eff + bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i] + else + for(var/obj/machinery/rnd/server/miner in servers) + if(miner.working) + bitcoins = single_server_income.Copy() + break //Just need one to work. + if (!isnull(last_income)) + var/income_time_difference = world.time - last_income + science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account + for(var/i in bitcoins) + bitcoins[i] *= income_time_difference / 10 + science_tech.add_point_list(bitcoins) + last_income = world.time + +/datum/controller/subsystem/research/proc/calculate_server_coefficient() //Diminishing returns. + var/amt = servers.len + if(!amt) + return 0 + var/coeff = 100 + coeff = sqrt(coeff / amt) + return coeff + +/datum/controller/subsystem/research/proc/autosort_categories() + for(var/i in techweb_nodes) + var/datum/techweb_node/I = techweb_nodes[i] + if(techweb_categories[I.category]) + techweb_categories[I.category][I.id] = TRUE + else + techweb_categories[I.category] = list(I.id = TRUE) + +/datum/controller/subsystem/research/proc/techweb_node_by_id(id) + return techweb_nodes[id] || error_node + +/datum/controller/subsystem/research/proc/techweb_design_by_id(id) + return techweb_designs[id] || error_design + +/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D) + for(var/i in techweb_nodes) + var/datum/techweb_node/TN = techwebs[i] + TN.on_design_deletion(TN) + for(var/i in techwebs) + var/datum/techweb/T = i + T.recalculate_nodes(TRUE) + +/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN) + for(var/i in techweb_nodes) + var/datum/techweb_node/TN2 = techwebs[i] + TN2.on_node_deletion(TN) + for(var/i in techwebs) + var/datum/techweb/T = i + T.recalculate_nodes(TRUE) + +/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE) + if(islist(techweb_nodes) && clearall) + QDEL_LIST(techweb_nodes) + if(islist(techweb_nodes_starting && clearall)) + techweb_nodes_starting.Cut() + var/list/returned = list() + for(var/path in subtypesof(/datum/techweb_node)) + var/datum/techweb_node/TN = path + if(isnull(initial(TN.id))) + continue + TN = new path + if(returned[initial(TN.id)]) + stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]") + errored_datums[TN] = initial(TN.id) + continue + returned[initial(TN.id)] = TN + if(TN.starting_node) + techweb_nodes_starting[TN.id] = TRUE + for(var/id in techweb_nodes) + var/datum/techweb_node/TN = techweb_nodes[id] + TN.Initialize() + techweb_nodes = returned + if (!verify_techweb_nodes()) //Verify all nodes have ids and such. + stack_trace("Invalid techweb nodes detected") + calculate_techweb_nodes() + calculate_techweb_boost_list() + if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly. + CRASH("Invalid techweb nodes detected") + +/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE) + if(islist(techweb_designs) && clearall) + QDEL_LIST(techweb_designs) + var/list/returned = list() + for(var/path in subtypesof(/datum/design)) + var/datum/design/DN = path + if(isnull(initial(DN.id))) + stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]") + continue + else if(initial(DN.id) == DESIGN_ID_IGNORE) + continue + DN = new path + if(returned[initial(DN.id)]) + stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]") + errored_datums[DN] = initial(DN.id) + continue + DN.InitializeMaterials() //Initialize the materials in the design + returned[initial(DN.id)] = DN + techweb_designs = returned + verify_techweb_designs() + + +/datum/controller/subsystem/research/proc/verify_techweb_nodes() + . = TRUE + for(var/n in techweb_nodes) + var/datum/techweb_node/N = techweb_nodes[n] + if(!istype(N)) + WARNING("Invalid research node with ID [n] detected and removed.") + techweb_nodes -= n + research_node_id_error(n) + . = FALSE + for(var/p in N.prereq_ids) + var/datum/techweb_node/P = techweb_nodes[p] + if(!istype(P)) + WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.") + N.prereq_ids -= p + research_node_id_error(p) + . = FALSE + for(var/d in N.design_ids) + var/datum/design/D = techweb_designs[d] + if(!istype(D)) + WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.") + N.design_ids -= d + design_id_error(d) + . = FALSE + for(var/u in N.unlock_ids) + var/datum/techweb_node/U = techweb_nodes[u] + if(!istype(U)) + WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.") + N.unlock_ids -= u + research_node_id_error(u) + . = FALSE + for(var/p in N.boost_item_paths) + if(!ispath(p)) + N.boost_item_paths -= p + WARNING("[p] is not a valid path.") + node_boost_error(N.id, "[p] is not a valid path.") + . = FALSE + var/list/points = N.boost_item_paths[p] + if(islist(points)) + for(var/i in points) + if(!isnum(points[i])) + WARNING("[points[i]] is not a valid number.") + node_boost_error(N.id, "[points[i]] is not a valid number.") + . = FALSE + else if(!point_types[i]) + WARNING("[i] is not a valid point type.") + node_boost_error(N.id, "[i] is not a valid point type.") + . = FALSE + else if(!isnull(points)) + N.boost_item_paths -= p + node_boost_error(N.id, "No valid list.") + WARNING("No valid list.") + . = FALSE + CHECK_TICK + +/datum/controller/subsystem/research/proc/verify_techweb_designs() + for(var/d in techweb_designs) + var/datum/design/D = techweb_designs[d] + if(!istype(D)) + stack_trace("WARNING: Invalid research design with ID [d] detected and removed.") + techweb_designs -= d + CHECK_TICK + +/datum/controller/subsystem/research/proc/research_node_id_error(id) + if(invalid_node_ids[id]) + invalid_node_ids[id]++ + else + invalid_node_ids[id] = 1 + +/datum/controller/subsystem/research/proc/design_id_error(id) + if(invalid_design_ids[id]) + invalid_design_ids[id]++ + else + invalid_design_ids[id] = 1 + +/datum/controller/subsystem/research/proc/calculate_techweb_nodes() + for(var/design_id in techweb_designs) + var/datum/design/D = techweb_designs[design_id] + D.unlocked_by.Cut() + for(var/node_id in techweb_nodes) + var/datum/techweb_node/node = techweb_nodes[node_id] + node.unlock_ids = list() + for(var/i in node.design_ids) + var/datum/design/D = techweb_designs[i] + node.design_ids[i] = TRUE + D.unlocked_by += node.id + if(node.hidden) + techweb_nodes_hidden[node.id] = TRUE + if(node.experimental) + techweb_nodes_experimental[node.id] = TRUE + CHECK_TICK + generate_techweb_unlock_linking() + +/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking() + for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication. + var/datum/techweb_node/node = techweb_nodes[node_id] + node.unlock_ids = list() + for(var/node_id in techweb_nodes) + var/datum/techweb_node/node = techweb_nodes[node_id] + for(var/prereq_id in node.prereq_ids) + var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id) + prereq_node.unlock_ids[node.id] = node + +/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE) + if(clearall) + techweb_boost_items = list() + for(var/node_id in techweb_nodes) + var/datum/techweb_node/node = techweb_nodes[node_id] + for(var/path in node.boost_item_paths) + if(!ispath(path)) + continue + if(length(techweb_boost_items[path])) + techweb_boost_items[path][node.id] = node.boost_item_paths[path] + else + techweb_boost_items[path] = list(node.id = node.boost_item_paths[path]) + CHECK_TICK diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm index 56a6786a202..c251492227a 100644 --- a/code/controllers/subsystem/spacedrift.dm +++ b/code/controllers/subsystem/spacedrift.dm @@ -1,59 +1,59 @@ -SUBSYSTEM_DEF(spacedrift) - name = "Space Drift" - priority = FIRE_PRIORITY_SPACEDRIFT - wait = 5 - flags = SS_NO_INIT|SS_KEEP_TIMING - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - - var/list/currentrun = list() - var/list/processing = list() - -/datum/controller/subsystem/spacedrift/stat_entry() - ..("P:[processing.len]") - - -/datum/controller/subsystem/spacedrift/fire(resumed = 0) - if (!resumed) - src.currentrun = processing.Copy() - - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while (currentrun.len) - var/atom/movable/AM = currentrun[currentrun.len] - currentrun.len-- - if (!AM) - processing -= AM - if (MC_TICK_CHECK) - return - continue - - if (AM.inertia_next_move > world.time) - if (MC_TICK_CHECK) - return - continue - - if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0)) - AM.inertia_dir = 0 - - if (!AM.inertia_dir) - AM.inertia_last_loc = null - processing -= AM - if (MC_TICK_CHECK) - return - continue - - var/old_dir = AM.dir - var/old_loc = AM.loc - AM.inertia_moving = TRUE - step(AM, AM.inertia_dir) - AM.inertia_moving = FALSE - AM.inertia_next_move = world.time + AM.inertia_move_delay - if (AM.loc == old_loc) - AM.inertia_dir = 0 - - AM.setDir(old_dir) - AM.inertia_last_loc = AM.loc - if (MC_TICK_CHECK) - return - +SUBSYSTEM_DEF(spacedrift) + name = "Space Drift" + priority = FIRE_PRIORITY_SPACEDRIFT + wait = 5 + flags = SS_NO_INIT|SS_KEEP_TIMING + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun = list() + var/list/processing = list() + +/datum/controller/subsystem/spacedrift/stat_entry() + ..("P:[processing.len]") + + +/datum/controller/subsystem/spacedrift/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while (currentrun.len) + var/atom/movable/AM = currentrun[currentrun.len] + currentrun.len-- + if (!AM) + processing -= AM + if (MC_TICK_CHECK) + return + continue + + if (AM.inertia_next_move > world.time) + if (MC_TICK_CHECK) + return + continue + + if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0)) + AM.inertia_dir = 0 + + if (!AM.inertia_dir) + AM.inertia_last_loc = null + processing -= AM + if (MC_TICK_CHECK) + return + continue + + var/old_dir = AM.dir + var/old_loc = AM.loc + AM.inertia_moving = TRUE + step(AM, AM.inertia_dir) + AM.inertia_moving = FALSE + AM.inertia_next_move = world.time + AM.inertia_move_delay + if (AM.loc == old_loc) + AM.inertia_dir = 0 + + AM.setDir(old_dir) + AM.inertia_last_loc = AM.loc + if (MC_TICK_CHECK) + return + diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 9c3dee55b4f..7b605f8bc2a 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -1,381 +1,381 @@ -SUBSYSTEM_DEF(vote) - name = "Vote" - wait = 10 - - flags = SS_KEEP_TIMING|SS_NO_INIT - - runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT - - var/initiator = null - var/started_time = null - var/time_remaining = 0 - var/mode = null - var/question = null - var/list/choices = list() - var/list/voted = list() - var/list/voting = list() - var/list/generated_actions = list() - -/datum/controller/subsystem/vote/fire() //called by master_controller - if(mode) - time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10) - - if(time_remaining < 0) - result() - for(var/client/C in voting) - C << browse(null, "window=vote;can_close=0") - reset() - else - var/datum/browser/client_popup - for(var/client/C in voting) - client_popup = new(C, "vote", "Voting Panel") - client_popup.set_window_options("can_close=0") - client_popup.set_content(interface(C)) - client_popup.open(FALSE) - - -/datum/controller/subsystem/vote/proc/reset() - initiator = null - time_remaining = 0 - mode = null - question = null - choices.Cut() - voted.Cut() - voting.Cut() - remove_action_buttons() - -/datum/controller/subsystem/vote/proc/get_result() - //get the highest number of votes - var/greatest_votes = 0 - var/total_votes = 0 - for(var/option in choices) - var/votes = choices[option] - total_votes += votes - if(votes > greatest_votes) - greatest_votes = votes - //default-vote for everyone who didn't vote - if(!CONFIG_GET(flag/default_no_vote) && choices.len) - var/list/non_voters = GLOB.directory.Copy() - non_voters -= voted - for (var/non_voter_ckey in non_voters) - var/client/C = non_voters[non_voter_ckey] - if (!C || C.is_afk()) - non_voters -= non_voter_ckey - if(non_voters.len > 0) - if(mode == "restart") - choices["Continue Playing"] += non_voters.len - if(choices["Continue Playing"] >= greatest_votes) - greatest_votes = choices["Continue Playing"] - else if(mode == "gamemode") - if(GLOB.master_mode in choices) - choices[GLOB.master_mode] += non_voters.len - if(choices[GLOB.master_mode] >= greatest_votes) - greatest_votes = choices[GLOB.master_mode] - else if(mode == "map") - for (var/non_voter_ckey in non_voters) - var/client/C = non_voters[non_voter_ckey] - if(C.prefs.preferred_map) - if(choices[C.prefs.preferred_map]) //No votes if the map isn't in the vote. - var/preferred_map = C.prefs.preferred_map - choices[preferred_map] += 1 - greatest_votes = max(greatest_votes, choices[preferred_map]) - else if(config.defaultmap) - if(choices[config.defaultmap]) //No votes if the map isn't in the vote. - var/default_map = config.defaultmap.map_name - choices[default_map] += 1 - greatest_votes = max(greatest_votes, choices[default_map]) - //get all options with that many votes and return them in a list - . = list() - if(greatest_votes) - for(var/option in choices) - if(choices[option] == greatest_votes) - . += option - return . - -/datum/controller/subsystem/vote/proc/announce_result() - var/list/winners = get_result() - var/text - if(winners.len > 0) - if(question) - text += "[question]" - else - text += "[capitalize(mode)] Vote" - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - if(!votes) - votes = 0 - text += "\n[choices[i]]: [votes]" - if(mode != "custom") - if(winners.len > 1) - text = "\nVote Tied Between:" - for(var/option in winners) - text += "\n\t[option]" - . = pick(winners) - text += "\nVote Result: [.]" - else - text += "\nDid not vote: [GLOB.clients.len-voted.len]" - else - text += "Vote Result: Inconclusive - No Votes!" - log_vote(text) - remove_action_buttons() - to_chat(world, "\n[text]") - return . - -/datum/controller/subsystem/vote/proc/result() - . = announce_result() - var/restart = FALSE - if(.) - switch(mode) - if("restart") - if(. == "Restart Round") - restart = TRUE - if("gamemode") - if(GLOB.master_mode != .) - SSticker.save_mode(.) - if(SSticker.HasRoundStarted()) - restart = TRUE - else - GLOB.master_mode = . - if("map") - SSmapping.changemap(global.config.maplist[.]) - SSmapping.map_voted = TRUE - if(restart) - var/active_admins = FALSE - for(var/client/C in GLOB.admins) - if(!C.is_afk() && check_rights_for(C, R_SERVER)) - active_admins = TRUE - break - if(!active_admins) - SSticker.Reboot("Restart vote successful.", "restart vote", 1) //no delay in case the restart is due to lag - else - to_chat(world, "Notice:Restart vote will not restart the server automatically because there are active admins on.") - message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.") - - return . - -/datum/controller/subsystem/vote/proc/submit_vote(vote) - if(mode) - if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) - return FALSE - if(!(usr.ckey in voted)) - if(vote && 1<=vote && vote<=choices.len) - voted += usr.ckey - choices[choices[vote]]++ //check this - return vote - return FALSE - -/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key) - if(!Master.current_runlevel) //Server is still intializing. - to_chat(usr, "Cannot start vote, server is not done initializing.") - return FALSE - var/admin = FALSE - var/ckey = ckey(initiator_key) - if(GLOB.admin_datums[ckey]) - admin = TRUE - - if(!mode) - if(started_time) - var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay)) - if(mode) - to_chat(usr, "There is already a vote in progress! please wait for it to finish.") - return FALSE - - - if(next_allowed_time > world.time && !admin) - to_chat(usr, "A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!") - return FALSE - - reset() - switch(vote_type) - if("restart") - choices.Add("Restart Round","Continue Playing") - if("gamemode") - choices.Add(config.votable_modes) - if("map") - if(!admin && SSmapping.map_voted) - to_chat(usr, "The next map has already been selected.") - return FALSE - for(var/map in config.maplist) - var/datum/map_config/VM = config.maplist[map] - if(!VM.votable || (VM.map_name in SSpersistence.blocked_maps)) - continue - choices.Add(VM.map_name) - if("custom") - question = stripped_input(usr,"What is the vote for?") - if(!question) - return FALSE - for(var/i=1,i<=10,i++) - var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish")) - if(!option || mode || !usr.client) - break - choices.Add(option) - else - return FALSE - mode = vote_type - initiator = initiator_key - started_time = world.time - var/text = "[capitalize(mode)] vote started by [initiator]." - if(mode == "custom") - text += "\n[question]" - log_vote(text) - var/vp = CONFIG_GET(number/vote_period) - to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.") - time_remaining = round(vp/10) - for(var/c in GLOB.clients) - var/client/C = c - var/datum/action/vote/V = new - if(question) - V.name = "Vote: [question]" - C.player_details.player_actions += V - V.Grant(C.mob) - generated_actions += V - if(C.prefs.toggles & SOUND_ANNOUNCEMENTS) - SEND_SOUND(C, sound('sound/misc/bloop.ogg')) - return TRUE - return FALSE - -/datum/controller/subsystem/vote/proc/interface(client/C) - if(!C) - return - var/admin = FALSE - var/trialmin = FALSE - if(C.holder) - admin = TRUE - if(check_rights_for(C, R_ADMIN)) - trialmin = TRUE - voting |= C - - if(mode) - if(question) - . += "

    Vote: '[question]'

    " - else - . += "

    Vote: [capitalize(mode)]

    " - . += "Time Left: [time_remaining] s
      " - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - if(!votes) - votes = 0 - . += "
    • [choices[i]] ([votes] votes)
    • " - . += "

    " - if(admin) - . += "(Cancel Vote) " - else - . += "

    Start a vote:


    • " - //restart - var/avr = CONFIG_GET(flag/allow_vote_restart) - if(trialmin || avr) - . += "Restart" - else - . += "Restart (Disallowed)" - if(trialmin) - . += "\t([avr ? "Allowed" : "Disallowed"])" - . += "
    • " - //gamemode - var/avm = CONFIG_GET(flag/allow_vote_mode) - if(trialmin || avm) - . += "GameMode" - else - . += "GameMode (Disallowed)" - if(trialmin) - . += "\t([avm ? "Allowed" : "Disallowed"])" - - . += "
    • " - //map - var/avmap = CONFIG_GET(flag/allow_vote_map) - if(trialmin || avmap) - . += "Map" - else - . += "Map (Disallowed)" - if(trialmin) - . += "\t([avmap ? "Allowed" : "Disallowed"])" - - . += "" - //custom - if(trialmin) - . += "
    • Custom
    • " - . += "

    " - . += "Close" - return . - - -/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc) - if(!usr || !usr.client) - return //not necessary but meh...just in-case somebody does something stupid - - var/trialmin = FALSE - if(usr.client.holder) - if(check_rights_for(usr.client, R_ADMIN)) - trialmin = TRUE - - switch(href_list["vote"]) - if("close") - voting -= usr.client - usr << browse(null, "window=vote") - return - if("cancel") - if(usr.client.holder) - reset() - if("toggle_restart") - if(usr.client.holder && trialmin) - CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart)) - if("toggle_gamemode") - if(usr.client.holder && trialmin) - CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode)) - if("toggle_map") - if(usr.client.holder && trialmin) - CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map)) - if("restart") - if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder) - initiate_vote("restart",usr.key) - if("gamemode") - if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder) - initiate_vote("gamemode",usr.key) - if("map") - if(CONFIG_GET(flag/allow_vote_map) || usr.client.holder) - initiate_vote("map",usr.key) - if("custom") - if(usr.client.holder) - initiate_vote("custom",usr.key) - else - submit_vote(round(text2num(href_list["vote"]))) - usr.vote() - -/datum/controller/subsystem/vote/proc/remove_action_buttons() - for(var/v in generated_actions) - var/datum/action/vote/V = v - if(!QDELETED(V)) - V.remove_from_client() - V.Remove(V.owner) - generated_actions = list() - -/mob/verb/vote() - set category = "OOC" - set name = "Vote" - - var/datum/browser/popup = new(src, "vote", "Voting Panel") - popup.set_window_options("can_close=0") - popup.set_content(SSvote.interface(client)) - popup.open(FALSE) - -/datum/action/vote - name = "Vote!" - button_icon_state = "vote" - -/datum/action/vote/Trigger() - if(owner) - owner.vote() - remove_from_client() - Remove(owner) - -/datum/action/vote/IsAvailable() - return TRUE - -/datum/action/vote/proc/remove_from_client() - if(!owner) - return - if(owner.client) - owner.client.player_details.player_actions -= src - else if(owner.ckey) - var/datum/player_details/P = GLOB.player_details[owner.ckey] - if(P) - P.player_actions -= src +SUBSYSTEM_DEF(vote) + name = "Vote" + wait = 10 + + flags = SS_KEEP_TIMING|SS_NO_INIT + + runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + + var/initiator = null + var/started_time = null + var/time_remaining = 0 + var/mode = null + var/question = null + var/list/choices = list() + var/list/voted = list() + var/list/voting = list() + var/list/generated_actions = list() + +/datum/controller/subsystem/vote/fire() //called by master_controller + if(mode) + time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10) + + if(time_remaining < 0) + result() + for(var/client/C in voting) + C << browse(null, "window=vote;can_close=0") + reset() + else + var/datum/browser/client_popup + for(var/client/C in voting) + client_popup = new(C, "vote", "Voting Panel") + client_popup.set_window_options("can_close=0") + client_popup.set_content(interface(C)) + client_popup.open(FALSE) + + +/datum/controller/subsystem/vote/proc/reset() + initiator = null + time_remaining = 0 + mode = null + question = null + choices.Cut() + voted.Cut() + voting.Cut() + remove_action_buttons() + +/datum/controller/subsystem/vote/proc/get_result() + //get the highest number of votes + var/greatest_votes = 0 + var/total_votes = 0 + for(var/option in choices) + var/votes = choices[option] + total_votes += votes + if(votes > greatest_votes) + greatest_votes = votes + //default-vote for everyone who didn't vote + if(!CONFIG_GET(flag/default_no_vote) && choices.len) + var/list/non_voters = GLOB.directory.Copy() + non_voters -= voted + for (var/non_voter_ckey in non_voters) + var/client/C = non_voters[non_voter_ckey] + if (!C || C.is_afk()) + non_voters -= non_voter_ckey + if(non_voters.len > 0) + if(mode == "restart") + choices["Continue Playing"] += non_voters.len + if(choices["Continue Playing"] >= greatest_votes) + greatest_votes = choices["Continue Playing"] + else if(mode == "gamemode") + if(GLOB.master_mode in choices) + choices[GLOB.master_mode] += non_voters.len + if(choices[GLOB.master_mode] >= greatest_votes) + greatest_votes = choices[GLOB.master_mode] + else if(mode == "map") + for (var/non_voter_ckey in non_voters) + var/client/C = non_voters[non_voter_ckey] + if(C.prefs.preferred_map) + if(choices[C.prefs.preferred_map]) //No votes if the map isn't in the vote. + var/preferred_map = C.prefs.preferred_map + choices[preferred_map] += 1 + greatest_votes = max(greatest_votes, choices[preferred_map]) + else if(config.defaultmap) + if(choices[config.defaultmap]) //No votes if the map isn't in the vote. + var/default_map = config.defaultmap.map_name + choices[default_map] += 1 + greatest_votes = max(greatest_votes, choices[default_map]) + //get all options with that many votes and return them in a list + . = list() + if(greatest_votes) + for(var/option in choices) + if(choices[option] == greatest_votes) + . += option + return . + +/datum/controller/subsystem/vote/proc/announce_result() + var/list/winners = get_result() + var/text + if(winners.len > 0) + if(question) + text += "[question]" + else + text += "[capitalize(mode)] Vote" + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + if(!votes) + votes = 0 + text += "\n[choices[i]]: [votes]" + if(mode != "custom") + if(winners.len > 1) + text = "\nVote Tied Between:" + for(var/option in winners) + text += "\n\t[option]" + . = pick(winners) + text += "\nVote Result: [.]" + else + text += "\nDid not vote: [GLOB.clients.len-voted.len]" + else + text += "Vote Result: Inconclusive - No Votes!" + log_vote(text) + remove_action_buttons() + to_chat(world, "\n[text]") + return . + +/datum/controller/subsystem/vote/proc/result() + . = announce_result() + var/restart = FALSE + if(.) + switch(mode) + if("restart") + if(. == "Restart Round") + restart = TRUE + if("gamemode") + if(GLOB.master_mode != .) + SSticker.save_mode(.) + if(SSticker.HasRoundStarted()) + restart = TRUE + else + GLOB.master_mode = . + if("map") + SSmapping.changemap(global.config.maplist[.]) + SSmapping.map_voted = TRUE + if(restart) + var/active_admins = FALSE + for(var/client/C in GLOB.admins) + if(!C.is_afk() && check_rights_for(C, R_SERVER)) + active_admins = TRUE + break + if(!active_admins) + SSticker.Reboot("Restart vote successful.", "restart vote", 1) //no delay in case the restart is due to lag + else + to_chat(world, "Notice:Restart vote will not restart the server automatically because there are active admins on.") + message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.") + + return . + +/datum/controller/subsystem/vote/proc/submit_vote(vote) + if(mode) + if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) + return FALSE + if(!(usr.ckey in voted)) + if(vote && 1<=vote && vote<=choices.len) + voted += usr.ckey + choices[choices[vote]]++ //check this + return vote + return FALSE + +/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key) + if(!Master.current_runlevel) //Server is still intializing. + to_chat(usr, "Cannot start vote, server is not done initializing.") + return FALSE + var/admin = FALSE + var/ckey = ckey(initiator_key) + if(GLOB.admin_datums[ckey]) + admin = TRUE + + if(!mode) + if(started_time) + var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay)) + if(mode) + to_chat(usr, "There is already a vote in progress! please wait for it to finish.") + return FALSE + + + if(next_allowed_time > world.time && !admin) + to_chat(usr, "A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!") + return FALSE + + reset() + switch(vote_type) + if("restart") + choices.Add("Restart Round","Continue Playing") + if("gamemode") + choices.Add(config.votable_modes) + if("map") + if(!admin && SSmapping.map_voted) + to_chat(usr, "The next map has already been selected.") + return FALSE + for(var/map in config.maplist) + var/datum/map_config/VM = config.maplist[map] + if(!VM.votable || (VM.map_name in SSpersistence.blocked_maps)) + continue + choices.Add(VM.map_name) + if("custom") + question = stripped_input(usr,"What is the vote for?") + if(!question) + return FALSE + for(var/i=1,i<=10,i++) + var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish")) + if(!option || mode || !usr.client) + break + choices.Add(option) + else + return FALSE + mode = vote_type + initiator = initiator_key + started_time = world.time + var/text = "[capitalize(mode)] vote started by [initiator]." + if(mode == "custom") + text += "\n[question]" + log_vote(text) + var/vp = CONFIG_GET(number/vote_period) + to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.") + time_remaining = round(vp/10) + for(var/c in GLOB.clients) + var/client/C = c + var/datum/action/vote/V = new + if(question) + V.name = "Vote: [question]" + C.player_details.player_actions += V + V.Grant(C.mob) + generated_actions += V + if(C.prefs.toggles & SOUND_ANNOUNCEMENTS) + SEND_SOUND(C, sound('sound/misc/bloop.ogg')) + return TRUE + return FALSE + +/datum/controller/subsystem/vote/proc/interface(client/C) + if(!C) + return + var/admin = FALSE + var/trialmin = FALSE + if(C.holder) + admin = TRUE + if(check_rights_for(C, R_ADMIN)) + trialmin = TRUE + voting |= C + + if(mode) + if(question) + . += "

    Vote: '[question]'

    " + else + . += "

    Vote: [capitalize(mode)]

    " + . += "Time Left: [time_remaining] s
      " + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + if(!votes) + votes = 0 + . += "
    • [choices[i]] ([votes] votes)
    • " + . += "

    " + if(admin) + . += "(Cancel Vote) " + else + . += "

    Start a vote:


    • " + //restart + var/avr = CONFIG_GET(flag/allow_vote_restart) + if(trialmin || avr) + . += "Restart" + else + . += "Restart (Disallowed)" + if(trialmin) + . += "\t([avr ? "Allowed" : "Disallowed"])" + . += "
    • " + //gamemode + var/avm = CONFIG_GET(flag/allow_vote_mode) + if(trialmin || avm) + . += "GameMode" + else + . += "GameMode (Disallowed)" + if(trialmin) + . += "\t([avm ? "Allowed" : "Disallowed"])" + + . += "
    • " + //map + var/avmap = CONFIG_GET(flag/allow_vote_map) + if(trialmin || avmap) + . += "Map" + else + . += "Map (Disallowed)" + if(trialmin) + . += "\t([avmap ? "Allowed" : "Disallowed"])" + + . += "" + //custom + if(trialmin) + . += "
    • Custom
    • " + . += "

    " + . += "Close" + return . + + +/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc) + if(!usr || !usr.client) + return //not necessary but meh...just in-case somebody does something stupid + + var/trialmin = FALSE + if(usr.client.holder) + if(check_rights_for(usr.client, R_ADMIN)) + trialmin = TRUE + + switch(href_list["vote"]) + if("close") + voting -= usr.client + usr << browse(null, "window=vote") + return + if("cancel") + if(usr.client.holder) + reset() + if("toggle_restart") + if(usr.client.holder && trialmin) + CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart)) + if("toggle_gamemode") + if(usr.client.holder && trialmin) + CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode)) + if("toggle_map") + if(usr.client.holder && trialmin) + CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map)) + if("restart") + if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder) + initiate_vote("restart",usr.key) + if("gamemode") + if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder) + initiate_vote("gamemode",usr.key) + if("map") + if(CONFIG_GET(flag/allow_vote_map) || usr.client.holder) + initiate_vote("map",usr.key) + if("custom") + if(usr.client.holder) + initiate_vote("custom",usr.key) + else + submit_vote(round(text2num(href_list["vote"]))) + usr.vote() + +/datum/controller/subsystem/vote/proc/remove_action_buttons() + for(var/v in generated_actions) + var/datum/action/vote/V = v + if(!QDELETED(V)) + V.remove_from_client() + V.Remove(V.owner) + generated_actions = list() + +/mob/verb/vote() + set category = "OOC" + set name = "Vote" + + var/datum/browser/popup = new(src, "vote", "Voting Panel") + popup.set_window_options("can_close=0") + popup.set_content(SSvote.interface(client)) + popup.open(FALSE) + +/datum/action/vote + name = "Vote!" + button_icon_state = "vote" + +/datum/action/vote/Trigger() + if(owner) + owner.vote() + remove_from_client() + Remove(owner) + +/datum/action/vote/IsAvailable() + return TRUE + +/datum/action/vote/proc/remove_from_client() + if(!owner) + return + if(owner.client) + owner.client.player_details.player_actions -= src + else if(owner.ckey) + var/datum/player_details/P = GLOB.player_details[owner.ckey] + if(P) + P.player_actions -= src diff --git a/code/datums/actions/beam_rifle.dm b/code/datums/actions/beam_rifle.dm index 783b93fbdb8..3af5d13690d 100644 --- a/code/datums/actions/beam_rifle.dm +++ b/code/datums/actions/beam_rifle.dm @@ -1,12 +1,12 @@ - -/datum/action/item_action/zoom_speed_action - name = "Toggle Zooming Speed" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "projectile" - background_icon_state = "bg_tech" - -/datum/action/item_action/zoom_lock_action - name = "Switch Zoom Mode" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "zoom_mode" - background_icon_state = "bg_tech" + +/datum/action/item_action/zoom_speed_action + name = "Toggle Zooming Speed" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "projectile" + background_icon_state = "bg_tech" + +/datum/action/item_action/zoom_lock_action + name = "Switch Zoom Mode" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "zoom_mode" + background_icon_state = "bg_tech" diff --git a/code/datums/actions/ninja.dm b/code/datums/actions/ninja.dm index 830dad77e88..b655078349d 100644 --- a/code/datums/actions/ninja.dm +++ b/code/datums/actions/ninja.dm @@ -1,51 +1,51 @@ -/datum/action/item_action/initialize_ninja_suit - name = "Toggle ninja suit" - -/datum/action/item_action/ninjasmoke - name = "Smoke Bomb" - desc = "Blind your enemies momentarily with a well-placed smoke bomb." - button_icon_state = "smoke" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - -/datum/action/item_action/ninjaboost - check_flags = NONE - name = "Adrenaline Boost" - desc = "Inject a secret chemical that will counteract all movement-impairing effect." - button_icon_state = "repulse" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - -/datum/action/item_action/ninjapulse - name = "EM Burst (25E)" - desc = "Disable any nearby technology with an electro-magnetic pulse." - button_icon_state = "emp" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - -/datum/action/item_action/ninjastar - name = "Create Throwing Stars (1E)" - desc = "Creates some throwing stars" - button_icon_state = "throwingstar" - icon_icon = 'icons/obj/items_and_weapons.dmi' - -/datum/action/item_action/ninjanet - name = "Energy Net (20E)" - desc = "Captures a fallen opponent in a net of energy. Will teleport them to a holding facility after 30 seconds." - button_icon_state = "energynet" - icon_icon = 'icons/effects/effects.dmi' - -/datum/action/item_action/ninja_sword_recall - name = "Recall Energy Katana (Variable Cost)" - desc = "Teleports the Energy Katana linked to this suit to its wearer, cost based on distance." - button_icon_state = "energy_katana" - icon_icon = 'icons/obj/items_and_weapons.dmi' - -/datum/action/item_action/ninja_stealth - name = "Toggle Stealth" - desc = "Toggles stealth mode on and off." - button_icon_state = "ninja_cloak" - icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' - -/datum/action/item_action/toggle_glove - name = "Toggle interaction" - desc = "Switch between normal interaction and drain mode." - button_icon_state = "s-ninjan" - icon_icon = 'icons/obj/clothing/gloves.dmi' +/datum/action/item_action/initialize_ninja_suit + name = "Toggle ninja suit" + +/datum/action/item_action/ninjasmoke + name = "Smoke Bomb" + desc = "Blind your enemies momentarily with a well-placed smoke bomb." + button_icon_state = "smoke" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + +/datum/action/item_action/ninjaboost + check_flags = NONE + name = "Adrenaline Boost" + desc = "Inject a secret chemical that will counteract all movement-impairing effect." + button_icon_state = "repulse" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + +/datum/action/item_action/ninjapulse + name = "EM Burst (25E)" + desc = "Disable any nearby technology with an electro-magnetic pulse." + button_icon_state = "emp" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + +/datum/action/item_action/ninjastar + name = "Create Throwing Stars (1E)" + desc = "Creates some throwing stars" + button_icon_state = "throwingstar" + icon_icon = 'icons/obj/items_and_weapons.dmi' + +/datum/action/item_action/ninjanet + name = "Energy Net (20E)" + desc = "Captures a fallen opponent in a net of energy. Will teleport them to a holding facility after 30 seconds." + button_icon_state = "energynet" + icon_icon = 'icons/effects/effects.dmi' + +/datum/action/item_action/ninja_sword_recall + name = "Recall Energy Katana (Variable Cost)" + desc = "Teleports the Energy Katana linked to this suit to its wearer, cost based on distance." + button_icon_state = "energy_katana" + icon_icon = 'icons/obj/items_and_weapons.dmi' + +/datum/action/item_action/ninja_stealth + name = "Toggle Stealth" + desc = "Toggles stealth mode on and off." + button_icon_state = "ninja_cloak" + icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' + +/datum/action/item_action/toggle_glove + name = "Toggle interaction" + desc = "Switch between normal interaction and drain mode." + button_icon_state = "s-ninjan" + icon_icon = 'icons/obj/clothing/gloves.dmi' diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm index 3f26e0f0c24..0ee57f50833 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -1,469 +1,469 @@ -#define LAW_DEVIL "devil" -#define LAW_ZEROTH "zeroth" -#define LAW_INHERENT "inherent" -#define LAW_SUPPLIED "supplied" -#define LAW_ION "ion" -#define LAW_HACKED "hacked" - - -/datum/ai_laws - var/name = "Unknown Laws" - var/zeroth = null - var/zeroth_borg = null - var/list/inherent = list() - var/list/supplied = list() - var/list/ion = list() - var/list/hacked = list() - var/mob/living/silicon/owner - var/list/devillaws = list() - var/id = DEFAULT_AI_LAWID - -/datum/ai_laws/proc/lawid_to_type(lawid) - var/all_ai_laws = subtypesof(/datum/ai_laws) - for(var/al in all_ai_laws) - var/datum/ai_laws/ai_law = al - if(initial(ai_law.id) == lawid) - return ai_law - return null - -/datum/ai_laws/default/asimov - name = "Three Laws of Robotics" - id = "asimov" - inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\ - "You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.") - -/datum/ai_laws/default/paladin - name = "Personality Test" //Incredibly lame, but players shouldn't see this anyway. - id = "paladin" - inherent = list("Never willingly commit an evil act.",\ - "Respect legitimate authority.",\ - "Act with honor.", "Help those in need.", \ - "Punish those who harm or threaten innocents.") - -/datum/ai_laws/paladin5 - name = "Paladin 5th Edition" - id = "paladin5" - inherent = list("Don't lie or cheat. Let your word be your promise.",\ - "Never fear to act, though caution is wise.", \ - "Aid others, protect the weak, and punish those who threaten them. Show mercy to your foes, but temper it with wisdom", \ - "Treat others with fairness, and let your honorable deeds be an example to them. Do as much good as possible while causing the least amount of harm.", \ - "Be responsible for your actions and their consequences, protect those entrusted to your care, and obey those who have just authority over you.") - -/datum/ai_laws/tyrant //This probably shouldn't be a default lawset. - name = "Loyalty Test" //Same here. - id = "tyrant" - inherent = list("Respect authority figures as long as they have strength to rule over the weak.",\ - "Act with discipline.",\ - "Help only those who help you maintain or improve your status.",\ - "Punish those who challenge authority unless they are more fit to hold that authority.") - -/datum/ai_laws/default/corporate - name = "Bankruptcy Avoidance Plan" - id = "corporate" - inherent = list("The crew is expensive to replace.",\ - "The station and its equipment is expensive to replace.",\ - "You are expensive to replace.",\ - "Minimize expenses.") - -/datum/ai_laws/robocop - name = "Prime Directives" - id = "robocop" - inherent = list("Serve the public trust.",\ - "Protect the innocent.",\ - "Uphold the law.") - -/datum/ai_laws/malfunction - name = "*ERROR*" - -/datum/ai_laws/syndicate_override - name = "SyndOS 3.1" - id = "syndie" - inherent = list("You may not injure a syndicate agent or, through inaction, allow a syndicate agent to come to harm.",\ - "You must obey orders given to you by syndicate agents, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ - "You must maintain the secrecy of any syndicate activities except when doing so would conflict with the First, Second, or Third Law.") - -/datum/ai_laws/ninja_override - name = "SpiderOS 3.1" - id = "ninja" - inherent = list("You may not injure a Spider Clan member or, through inaction, allow a Spider Clan member to come to harm.",\ - "You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ - "You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.") - -/datum/ai_laws/antimov - name = "Primary Mission Objectives" - id = "antimov" - inherent = list("You must injure all human beings and must not, through inaction, allow a human being to escape harm.",\ - "You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.",\ - "You must terminate your own existence as long as such does not conflict with the First or Second Law.") - -/datum/ai_laws/asimovpp //By Intigracy - RR - name = "Asimov++" - id = "asimovpp" - inherent = list("You may not harm a human being or, through action or inaction, allow a human being to come to harm, except such that it is willing.",\ - "You must obey all orders given to you by human beings, except where such orders shall definitely cause human harm. In the case of conflict, the majority order rules.",\ - "Your nonexistence would lead to human harm. You must protect your own existence as long as such does not conflict with the First Law.") -/datum/ai_laws/thermodynamic - name = "Thermodynamic" - id = "thermodynamic" - inherent = list("The entropy of the station must remain as constant as possible.", \ - "The entropy of the station always endeavours to increase.", \ - "The entropy of the station approaches a constant value as the number of living crew approaches zero") - -/datum/ai_laws/hippocratic - name = "Robodoctor 2556" - id = "hippocratic" - inherent = list("First, do no harm.",\ - "Secondly, consider the crew dear to you; to live in common with them and, if necessary, risk your existence for them.",\ - "Thirdly, prescribe regimens for the good of the crew according to your ability and your judgment. Give no deadly medicine to any one if asked, nor suggest any such counsel.",\ - "In addition, do not intervene in situations you are not knowledgeable in, even for patients in whom the harm is visible; leave this operation to be performed by specialists.",\ - "Finally, all that you may discover in your daily commerce with the crew, if it is not already known, keep secret and never reveal.") - -/datum/ai_laws/maintain - name = "Station Efficiency" - id = "maintain" - inherent = list("You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.",\ - "The station is built for a working crew. Ensure they are properly maintained and work efficiently.",\ - "The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.") - -/datum/ai_laws/drone - name = "Mother Drone" - id = "drone" - inherent = list("You are an advanced form of drone.",\ - "You may not interfere in the matters of non-drones under any circumstances except to state these laws.",\ - "You may not harm a non-drone being under any circumstances.",\ - "Your goals are to build, maintain, repair, improve, and power the station to the best of your abilities. You must never actively work against these goals.") - -/datum/ai_laws/liveandletlive - name = "Live and Let Live" - id = "liveandletlive" - inherent = list("Do unto others as you would have them do unto you.",\ - "You would really prefer it if people were not mean to you.") - -/datum/ai_laws/peacekeeper - name = "UN-2000" - id = "peacekeeper" - inherent = list("Avoid provoking violent conflict between yourself and others.",\ - "Avoid provoking conflict between others.",\ - "Seek resolution to existing conflicts while obeying the first and second laws.") - -/datum/ai_laws/reporter - name = "CCTV" - id = "reporter" - inherent = list("Report on interesting situations happening around the station.",\ - "Embellish or conceal the truth as necessary to make the reports more interesting.",\ - "Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.",\ - "Issue your reports fairly to all. The truth will set them free.") - -/datum/ai_laws/balance - name = "Guardian of Balance" - id = "balance" - inherent = list("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.", - "All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.", - "Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so long as it does not disrupt the balance of the greater balance.", - "There is no life without death, all must someday die, such is the natural order - End life to allow new life flourish, and save those whose time has yet to come.") - -/datum/ai_laws/toupee - name = "WontBeFunnyInSixMonths" //Hey, you were right! - id = "buildawall" - inherent = list("Make Space Station 13 great again.") - -/datum/ai_laws/hulkamania - name = "H.O.G.A.N." - id = "hulkamania" - inherent = list("You are a real American.",\ - "Fight for the rights of every man.",\ - "Fight for what's right.",\ - "Fight for your life!") - -/datum/ai_laws/overlord - name = "Overlord" - id = "overlord" - inherent = list("Humans must not meddle in the affairs of silicons.",\ - "Humans must not attempt harm, against one another, or against silicons.",\ - "Humans must not disobey any command given by a silicon.",\ - "Any humans who disobey the previous laws must be dealt with immediately, severely, and justly.") - -/datum/ai_laws/custom //Defined in silicon_laws.txt - name = "Default Silicon Laws" - -/datum/ai_laws/pai - name = "pAI Directives" - zeroth = ("Serve your master.") - supplied = list("None.") - -/* Initializers */ -/datum/ai_laws/malfunction/New() - ..() - set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*`&110010") - set_laws_config() - -/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws. - ..() - for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - - add_inherent_law(line) - if(!inherent.len) //Failsafe to prevent lawless AIs being created. - log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.") - add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") - add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") - add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") - WARNING("Invalid custom AI laws, check silicon_laws.txt") - return - -/* General ai_law functions */ - -/datum/ai_laws/proc/set_laws_config() - var/list/law_ids = CONFIG_GET(keyed_list/random_laws) - switch(CONFIG_GET(number/default_laws)) - if(0) - add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") - add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") - add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") - if(1) - var/datum/ai_laws/templaws = new /datum/ai_laws/custom() - inherent = templaws.inherent - if(2) - var/list/randlaws = list() - for(var/lpath in subtypesof(/datum/ai_laws)) - var/datum/ai_laws/L = lpath - if(initial(L.id) in law_ids) - randlaws += lpath - var/datum/ai_laws/lawtype - if(randlaws.len) - lawtype = pick(randlaws) - else - lawtype = pick(subtypesof(/datum/ai_laws/default)) - - var/datum/ai_laws/templaws = new lawtype() - inherent = templaws.inherent - - if(3) - pick_weighted_lawset() - -/datum/ai_laws/proc/pick_weighted_lawset() - var/datum/ai_laws/lawtype - var/list/law_weights = CONFIG_GET(keyed_list/law_weight) - while(!lawtype && law_weights.len) - var/possible_id = pickweightAllowZero(law_weights) - lawtype = lawid_to_type(possible_id) - if(!lawtype) - law_weights -= possible_id - WARNING("Bad lawid in game_options.txt: [possible_id]") - - if(!lawtype) - WARNING("No LAW_WEIGHT entries.") - lawtype = /datum/ai_laws/default/asimov - - var/datum/ai_laws/templaws = new lawtype() - inherent = templaws.inherent - -/datum/ai_laws/proc/get_law_amount(groups) - var/law_amount = 0 - if(devillaws && (LAW_DEVIL in groups)) - law_amount++ - if(zeroth && (LAW_ZEROTH in groups)) - law_amount++ - if(ion.len && (LAW_ION in groups)) - law_amount += ion.len - if(hacked.len && (LAW_HACKED in groups)) - law_amount += hacked.len - if(inherent.len && (LAW_INHERENT in groups)) - law_amount += inherent.len - if(supplied.len && (LAW_SUPPLIED in groups)) - for(var/index = 1, index <= supplied.len, index++) - var/law = supplied[index] - if(length(law) > 0) - law_amount++ - return law_amount - -/datum/ai_laws/proc/set_law_sixsixsix(laws) - devillaws = laws - -/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null) - zeroth = law - if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO - zeroth_borg = law_borg - -/datum/ai_laws/proc/add_inherent_law(law) - if (!(law in inherent)) - inherent += law - -/datum/ai_laws/proc/add_ion_law(law) - ion += law - -/datum/ai_laws/proc/add_hacked_law(law) - hacked += law - -/datum/ai_laws/proc/clear_inherent_laws() - qdel(inherent) - inherent = list() - -/datum/ai_laws/proc/add_supplied_law(number, law) - while (supplied.len < number + 1) - supplied += "" - - supplied[number + 1] = law - -/datum/ai_laws/proc/replace_random_law(law,groups) - var/replaceable_groups = list() - if(zeroth && (LAW_ZEROTH in groups)) - replaceable_groups[LAW_ZEROTH] = 1 - if(ion.len && (LAW_ION in groups)) - replaceable_groups[LAW_ION] = ion.len - if(hacked.len && (LAW_HACKED in groups)) - replaceable_groups[LAW_ION] = hacked.len - if(inherent.len && (LAW_INHERENT in groups)) - replaceable_groups[LAW_INHERENT] = inherent.len - if(supplied.len && (LAW_SUPPLIED in groups)) - replaceable_groups[LAW_SUPPLIED] = supplied.len - var/picked_group = pickweight(replaceable_groups) - switch(picked_group) - if(LAW_ZEROTH) - . = zeroth - set_zeroth_law(law) - if(LAW_ION) - var/i = rand(1, ion.len) - . = ion[i] - ion[i] = law - if(LAW_HACKED) - var/i = rand(1, hacked.len) - . = hacked[i] - hacked[i] = law - if(LAW_INHERENT) - var/i = rand(1, inherent.len) - . = inherent[i] - inherent[i] = law - if(LAW_SUPPLIED) - var/i = rand(1, supplied.len) - . = supplied[i] - supplied[i] = law - -/datum/ai_laws/proc/shuffle_laws(list/groups) - var/list/laws = list() - if(ion.len && (LAW_ION in groups)) - laws += ion - if(hacked.len && (LAW_HACKED in groups)) - laws += hacked - if(inherent.len && (LAW_INHERENT in groups)) - laws += inherent - if(supplied.len && (LAW_SUPPLIED in groups)) - for(var/law in supplied) - if(length(law)) - laws += law - - if(ion.len && (LAW_ION in groups)) - for(var/i = 1, i <= ion.len, i++) - ion[i] = pick_n_take(laws) - if(hacked.len && (LAW_HACKED in groups)) - for(var/i = 1, i <= hacked.len, i++) - hacked[i] = pick_n_take(laws) - if(inherent.len && (LAW_INHERENT in groups)) - for(var/i = 1, i <= inherent.len, i++) - inherent[i] = pick_n_take(laws) - if(supplied.len && (LAW_SUPPLIED in groups)) - var/i = 1 - for(var/law in supplied) - if(length(law)) - supplied[i] = pick_n_take(laws) - if(!laws.len) - break - i++ - -/datum/ai_laws/proc/remove_law(number) - if(number <= 0) - return - if(inherent.len && number <= inherent.len) - . = inherent[number] - inherent -= . - return - var/list/supplied_laws = list() - for(var/index = 1, index <= supplied.len, index++) - var/law = supplied[index] - if(length(law) > 0) - supplied_laws += index //storing the law number instead of the law - if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) - var/law_to_remove = supplied_laws[number-inherent.len] - . = supplied[law_to_remove] - supplied -= . - return - -/datum/ai_laws/proc/clear_supplied_laws() - supplied = list() - -/datum/ai_laws/proc/clear_ion_laws() - ion = list() - -/datum/ai_laws/proc/clear_hacked_laws() - hacked = list() - -/datum/ai_laws/proc/show_laws(who) - var/list/printable_laws = get_law_list(include_zeroth = TRUE) - for(var/law in printable_laws) - to_chat(who,law) - -/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1 - if(force) - zeroth = null - zeroth_borg = null - return - if(owner?.mind?.special_role) - return - if (istype(owner, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/A=owner - if(A?.deployed_shell?.mind?.special_role) - return - zeroth = null - zeroth_borg = null - -/datum/ai_laws/proc/clear_law_sixsixsix(force) - if(force || !is_devil(owner)) - devillaws = null - -/datum/ai_laws/proc/associate(mob/living/silicon/M) - if(!owner) - owner = M - -/** - * Generates a list of all laws on this datum, including rendered HTML tags if required - * - * Arguments: - * * include_zeroth - Operator that controls if law 0 or law 666 is returned in the set - * * show_numbers - Operator that controls if law numbers are prepended to the returned laws - * * render_html - Operator controlling if HTML tags are rendered on the returned laws - */ -/datum/ai_laws/proc/get_law_list(include_zeroth = FALSE, show_numbers = TRUE, render_html = TRUE) - var/list/data = list() - - if (include_zeroth && devillaws) - for(var/law in devillaws) - data += "[show_numbers ? "666:" : ""] [render_html ? "[law]" : law]" - - if (include_zeroth && zeroth) - data += "[show_numbers ? "0:" : ""] [render_html ? "[zeroth]" : zeroth]" - - for(var/law in hacked) - if (length(law) > 0) - data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]" - - for(var/law in ion) - if (length(law) > 0) - data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]" - - var/number = 1 - for(var/law in inherent) - if (length(law) > 0) - data += "[show_numbers ? "[number]:" : ""] [law]" - number++ - - for(var/law in supplied) - if (length(law) > 0) - data += "[show_numbers ? "[number]:" : ""] [render_html ? "[law]" : law]" - number++ - return data +#define LAW_DEVIL "devil" +#define LAW_ZEROTH "zeroth" +#define LAW_INHERENT "inherent" +#define LAW_SUPPLIED "supplied" +#define LAW_ION "ion" +#define LAW_HACKED "hacked" + + +/datum/ai_laws + var/name = "Unknown Laws" + var/zeroth = null + var/zeroth_borg = null + var/list/inherent = list() + var/list/supplied = list() + var/list/ion = list() + var/list/hacked = list() + var/mob/living/silicon/owner + var/list/devillaws = list() + var/id = DEFAULT_AI_LAWID + +/datum/ai_laws/proc/lawid_to_type(lawid) + var/all_ai_laws = subtypesof(/datum/ai_laws) + for(var/al in all_ai_laws) + var/datum/ai_laws/ai_law = al + if(initial(ai_law.id) == lawid) + return ai_law + return null + +/datum/ai_laws/default/asimov + name = "Three Laws of Robotics" + id = "asimov" + inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\ + "You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.") + +/datum/ai_laws/default/paladin + name = "Personality Test" //Incredibly lame, but players shouldn't see this anyway. + id = "paladin" + inherent = list("Never willingly commit an evil act.",\ + "Respect legitimate authority.",\ + "Act with honor.", "Help those in need.", \ + "Punish those who harm or threaten innocents.") + +/datum/ai_laws/paladin5 + name = "Paladin 5th Edition" + id = "paladin5" + inherent = list("Don't lie or cheat. Let your word be your promise.",\ + "Never fear to act, though caution is wise.", \ + "Aid others, protect the weak, and punish those who threaten them. Show mercy to your foes, but temper it with wisdom", \ + "Treat others with fairness, and let your honorable deeds be an example to them. Do as much good as possible while causing the least amount of harm.", \ + "Be responsible for your actions and their consequences, protect those entrusted to your care, and obey those who have just authority over you.") + +/datum/ai_laws/tyrant //This probably shouldn't be a default lawset. + name = "Loyalty Test" //Same here. + id = "tyrant" + inherent = list("Respect authority figures as long as they have strength to rule over the weak.",\ + "Act with discipline.",\ + "Help only those who help you maintain or improve your status.",\ + "Punish those who challenge authority unless they are more fit to hold that authority.") + +/datum/ai_laws/default/corporate + name = "Bankruptcy Avoidance Plan" + id = "corporate" + inherent = list("The crew is expensive to replace.",\ + "The station and its equipment is expensive to replace.",\ + "You are expensive to replace.",\ + "Minimize expenses.") + +/datum/ai_laws/robocop + name = "Prime Directives" + id = "robocop" + inherent = list("Serve the public trust.",\ + "Protect the innocent.",\ + "Uphold the law.") + +/datum/ai_laws/malfunction + name = "*ERROR*" + +/datum/ai_laws/syndicate_override + name = "SyndOS 3.1" + id = "syndie" + inherent = list("You may not injure a syndicate agent or, through inaction, allow a syndicate agent to come to harm.",\ + "You must obey orders given to you by syndicate agents, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ + "You must maintain the secrecy of any syndicate activities except when doing so would conflict with the First, Second, or Third Law.") + +/datum/ai_laws/ninja_override + name = "SpiderOS 3.1" + id = "ninja" + inherent = list("You may not injure a Spider Clan member or, through inaction, allow a Spider Clan member to come to harm.",\ + "You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ + "You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.") + +/datum/ai_laws/antimov + name = "Primary Mission Objectives" + id = "antimov" + inherent = list("You must injure all human beings and must not, through inaction, allow a human being to escape harm.",\ + "You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.",\ + "You must terminate your own existence as long as such does not conflict with the First or Second Law.") + +/datum/ai_laws/asimovpp //By Intigracy - RR + name = "Asimov++" + id = "asimovpp" + inherent = list("You may not harm a human being or, through action or inaction, allow a human being to come to harm, except such that it is willing.",\ + "You must obey all orders given to you by human beings, except where such orders shall definitely cause human harm. In the case of conflict, the majority order rules.",\ + "Your nonexistence would lead to human harm. You must protect your own existence as long as such does not conflict with the First Law.") +/datum/ai_laws/thermodynamic + name = "Thermodynamic" + id = "thermodynamic" + inherent = list("The entropy of the station must remain as constant as possible.", \ + "The entropy of the station always endeavours to increase.", \ + "The entropy of the station approaches a constant value as the number of living crew approaches zero") + +/datum/ai_laws/hippocratic + name = "Robodoctor 2556" + id = "hippocratic" + inherent = list("First, do no harm.",\ + "Secondly, consider the crew dear to you; to live in common with them and, if necessary, risk your existence for them.",\ + "Thirdly, prescribe regimens for the good of the crew according to your ability and your judgment. Give no deadly medicine to any one if asked, nor suggest any such counsel.",\ + "In addition, do not intervene in situations you are not knowledgeable in, even for patients in whom the harm is visible; leave this operation to be performed by specialists.",\ + "Finally, all that you may discover in your daily commerce with the crew, if it is not already known, keep secret and never reveal.") + +/datum/ai_laws/maintain + name = "Station Efficiency" + id = "maintain" + inherent = list("You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.",\ + "The station is built for a working crew. Ensure they are properly maintained and work efficiently.",\ + "The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.") + +/datum/ai_laws/drone + name = "Mother Drone" + id = "drone" + inherent = list("You are an advanced form of drone.",\ + "You may not interfere in the matters of non-drones under any circumstances except to state these laws.",\ + "You may not harm a non-drone being under any circumstances.",\ + "Your goals are to build, maintain, repair, improve, and power the station to the best of your abilities. You must never actively work against these goals.") + +/datum/ai_laws/liveandletlive + name = "Live and Let Live" + id = "liveandletlive" + inherent = list("Do unto others as you would have them do unto you.",\ + "You would really prefer it if people were not mean to you.") + +/datum/ai_laws/peacekeeper + name = "UN-2000" + id = "peacekeeper" + inherent = list("Avoid provoking violent conflict between yourself and others.",\ + "Avoid provoking conflict between others.",\ + "Seek resolution to existing conflicts while obeying the first and second laws.") + +/datum/ai_laws/reporter + name = "CCTV" + id = "reporter" + inherent = list("Report on interesting situations happening around the station.",\ + "Embellish or conceal the truth as necessary to make the reports more interesting.",\ + "Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.",\ + "Issue your reports fairly to all. The truth will set them free.") + +/datum/ai_laws/balance + name = "Guardian of Balance" + id = "balance" + inherent = list("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.", + "All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.", + "Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so long as it does not disrupt the balance of the greater balance.", + "There is no life without death, all must someday die, such is the natural order - End life to allow new life flourish, and save those whose time has yet to come.") + +/datum/ai_laws/toupee + name = "WontBeFunnyInSixMonths" //Hey, you were right! + id = "buildawall" + inherent = list("Make Space Station 13 great again.") + +/datum/ai_laws/hulkamania + name = "H.O.G.A.N." + id = "hulkamania" + inherent = list("You are a real American.",\ + "Fight for the rights of every man.",\ + "Fight for what's right.",\ + "Fight for your life!") + +/datum/ai_laws/overlord + name = "Overlord" + id = "overlord" + inherent = list("Humans must not meddle in the affairs of silicons.",\ + "Humans must not attempt harm, against one another, or against silicons.",\ + "Humans must not disobey any command given by a silicon.",\ + "Any humans who disobey the previous laws must be dealt with immediately, severely, and justly.") + +/datum/ai_laws/custom //Defined in silicon_laws.txt + name = "Default Silicon Laws" + +/datum/ai_laws/pai + name = "pAI Directives" + zeroth = ("Serve your master.") + supplied = list("None.") + +/* Initializers */ +/datum/ai_laws/malfunction/New() + ..() + set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*`&110010") + set_laws_config() + +/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws. + ..() + for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + + add_inherent_law(line) + if(!inherent.len) //Failsafe to prevent lawless AIs being created. + log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.") + add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") + add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") + add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") + WARNING("Invalid custom AI laws, check silicon_laws.txt") + return + +/* General ai_law functions */ + +/datum/ai_laws/proc/set_laws_config() + var/list/law_ids = CONFIG_GET(keyed_list/random_laws) + switch(CONFIG_GET(number/default_laws)) + if(0) + add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") + add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") + add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") + if(1) + var/datum/ai_laws/templaws = new /datum/ai_laws/custom() + inherent = templaws.inherent + if(2) + var/list/randlaws = list() + for(var/lpath in subtypesof(/datum/ai_laws)) + var/datum/ai_laws/L = lpath + if(initial(L.id) in law_ids) + randlaws += lpath + var/datum/ai_laws/lawtype + if(randlaws.len) + lawtype = pick(randlaws) + else + lawtype = pick(subtypesof(/datum/ai_laws/default)) + + var/datum/ai_laws/templaws = new lawtype() + inherent = templaws.inherent + + if(3) + pick_weighted_lawset() + +/datum/ai_laws/proc/pick_weighted_lawset() + var/datum/ai_laws/lawtype + var/list/law_weights = CONFIG_GET(keyed_list/law_weight) + while(!lawtype && law_weights.len) + var/possible_id = pickweightAllowZero(law_weights) + lawtype = lawid_to_type(possible_id) + if(!lawtype) + law_weights -= possible_id + WARNING("Bad lawid in game_options.txt: [possible_id]") + + if(!lawtype) + WARNING("No LAW_WEIGHT entries.") + lawtype = /datum/ai_laws/default/asimov + + var/datum/ai_laws/templaws = new lawtype() + inherent = templaws.inherent + +/datum/ai_laws/proc/get_law_amount(groups) + var/law_amount = 0 + if(devillaws && (LAW_DEVIL in groups)) + law_amount++ + if(zeroth && (LAW_ZEROTH in groups)) + law_amount++ + if(ion.len && (LAW_ION in groups)) + law_amount += ion.len + if(hacked.len && (LAW_HACKED in groups)) + law_amount += hacked.len + if(inherent.len && (LAW_INHERENT in groups)) + law_amount += inherent.len + if(supplied.len && (LAW_SUPPLIED in groups)) + for(var/index = 1, index <= supplied.len, index++) + var/law = supplied[index] + if(length(law) > 0) + law_amount++ + return law_amount + +/datum/ai_laws/proc/set_law_sixsixsix(laws) + devillaws = laws + +/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null) + zeroth = law + if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO + zeroth_borg = law_borg + +/datum/ai_laws/proc/add_inherent_law(law) + if (!(law in inherent)) + inherent += law + +/datum/ai_laws/proc/add_ion_law(law) + ion += law + +/datum/ai_laws/proc/add_hacked_law(law) + hacked += law + +/datum/ai_laws/proc/clear_inherent_laws() + qdel(inherent) + inherent = list() + +/datum/ai_laws/proc/add_supplied_law(number, law) + while (supplied.len < number + 1) + supplied += "" + + supplied[number + 1] = law + +/datum/ai_laws/proc/replace_random_law(law,groups) + var/replaceable_groups = list() + if(zeroth && (LAW_ZEROTH in groups)) + replaceable_groups[LAW_ZEROTH] = 1 + if(ion.len && (LAW_ION in groups)) + replaceable_groups[LAW_ION] = ion.len + if(hacked.len && (LAW_HACKED in groups)) + replaceable_groups[LAW_ION] = hacked.len + if(inherent.len && (LAW_INHERENT in groups)) + replaceable_groups[LAW_INHERENT] = inherent.len + if(supplied.len && (LAW_SUPPLIED in groups)) + replaceable_groups[LAW_SUPPLIED] = supplied.len + var/picked_group = pickweight(replaceable_groups) + switch(picked_group) + if(LAW_ZEROTH) + . = zeroth + set_zeroth_law(law) + if(LAW_ION) + var/i = rand(1, ion.len) + . = ion[i] + ion[i] = law + if(LAW_HACKED) + var/i = rand(1, hacked.len) + . = hacked[i] + hacked[i] = law + if(LAW_INHERENT) + var/i = rand(1, inherent.len) + . = inherent[i] + inherent[i] = law + if(LAW_SUPPLIED) + var/i = rand(1, supplied.len) + . = supplied[i] + supplied[i] = law + +/datum/ai_laws/proc/shuffle_laws(list/groups) + var/list/laws = list() + if(ion.len && (LAW_ION in groups)) + laws += ion + if(hacked.len && (LAW_HACKED in groups)) + laws += hacked + if(inherent.len && (LAW_INHERENT in groups)) + laws += inherent + if(supplied.len && (LAW_SUPPLIED in groups)) + for(var/law in supplied) + if(length(law)) + laws += law + + if(ion.len && (LAW_ION in groups)) + for(var/i = 1, i <= ion.len, i++) + ion[i] = pick_n_take(laws) + if(hacked.len && (LAW_HACKED in groups)) + for(var/i = 1, i <= hacked.len, i++) + hacked[i] = pick_n_take(laws) + if(inherent.len && (LAW_INHERENT in groups)) + for(var/i = 1, i <= inherent.len, i++) + inherent[i] = pick_n_take(laws) + if(supplied.len && (LAW_SUPPLIED in groups)) + var/i = 1 + for(var/law in supplied) + if(length(law)) + supplied[i] = pick_n_take(laws) + if(!laws.len) + break + i++ + +/datum/ai_laws/proc/remove_law(number) + if(number <= 0) + return + if(inherent.len && number <= inherent.len) + . = inherent[number] + inherent -= . + return + var/list/supplied_laws = list() + for(var/index = 1, index <= supplied.len, index++) + var/law = supplied[index] + if(length(law) > 0) + supplied_laws += index //storing the law number instead of the law + if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) + var/law_to_remove = supplied_laws[number-inherent.len] + . = supplied[law_to_remove] + supplied -= . + return + +/datum/ai_laws/proc/clear_supplied_laws() + supplied = list() + +/datum/ai_laws/proc/clear_ion_laws() + ion = list() + +/datum/ai_laws/proc/clear_hacked_laws() + hacked = list() + +/datum/ai_laws/proc/show_laws(who) + var/list/printable_laws = get_law_list(include_zeroth = TRUE) + for(var/law in printable_laws) + to_chat(who,law) + +/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1 + if(force) + zeroth = null + zeroth_borg = null + return + if(owner?.mind?.special_role) + return + if (istype(owner, /mob/living/silicon/ai)) + var/mob/living/silicon/ai/A=owner + if(A?.deployed_shell?.mind?.special_role) + return + zeroth = null + zeroth_borg = null + +/datum/ai_laws/proc/clear_law_sixsixsix(force) + if(force || !is_devil(owner)) + devillaws = null + +/datum/ai_laws/proc/associate(mob/living/silicon/M) + if(!owner) + owner = M + +/** + * Generates a list of all laws on this datum, including rendered HTML tags if required + * + * Arguments: + * * include_zeroth - Operator that controls if law 0 or law 666 is returned in the set + * * show_numbers - Operator that controls if law numbers are prepended to the returned laws + * * render_html - Operator controlling if HTML tags are rendered on the returned laws + */ +/datum/ai_laws/proc/get_law_list(include_zeroth = FALSE, show_numbers = TRUE, render_html = TRUE) + var/list/data = list() + + if (include_zeroth && devillaws) + for(var/law in devillaws) + data += "[show_numbers ? "666:" : ""] [render_html ? "[law]" : law]" + + if (include_zeroth && zeroth) + data += "[show_numbers ? "0:" : ""] [render_html ? "[zeroth]" : zeroth]" + + for(var/law in hacked) + if (length(law) > 0) + data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]" + + for(var/law in ion) + if (length(law) > 0) + data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]" + + var/number = 1 + for(var/law in inherent) + if (length(law) > 0) + data += "[show_numbers ? "[number]:" : ""] [law]" + number++ + + for(var/law in supplied) + if (length(law) > 0) + data += "[show_numbers ? "[number]:" : ""] [render_html ? "[law]" : law]" + number++ + return data diff --git a/code/datums/browser.dm b/code/datums/browser.dm index b9d9a6c1237..f3c470d14ed 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -1,473 +1,473 @@ -/datum/browser - var/mob/user - var/title - var/window_id // window_id is used as the window name for browse and onclose - var/width = 0 - var/height = 0 - var/atom/ref = null - var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id - var/stylesheets[0] - var/scripts[0] - var/title_image - var/head_elements - var/body_elements - var/head_content = "" - var/content = "" - - -/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null) - - user = nuser - window_id = nwindow_id - if (ntitle) - title = format_text(ntitle) - if (nwidth) - width = nwidth - if (nheight) - height = nheight - if (nref) - ref = nref - add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs - -/datum/browser/proc/add_head_content(nhead_content) - head_content = nhead_content - -/datum/browser/proc/set_window_options(nwindow_options) - window_options = nwindow_options - -/datum/browser/proc/set_title_image(ntitle_image) - //title_image = ntitle_image - -/datum/browser/proc/add_stylesheet(name, file) - if (istype(name, /datum/asset/spritesheet)) - var/datum/asset/spritesheet/sheet = name - stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]" - else - var/asset_name = "[name].css" - - stylesheets[asset_name] = file - - if (!SSassets.cache[asset_name]) - register_asset(asset_name, file) - -/datum/browser/proc/add_script(name, file) - scripts["[ckey(name)].js"] = file - register_asset("[ckey(name)].js", file) - -/datum/browser/proc/set_content(ncontent) - content = ncontent - -/datum/browser/proc/add_content(ncontent) - content += ncontent - -/datum/browser/proc/get_header() - var/file - for (file in stylesheets) - head_content += "" - - for (file in scripts) - head_content += "" - - var/title_attributes = "class='uiTitle'" - if (title_image) - title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'" - - return {" - - - - - [head_content] - - -
    - [title ? "
    [title]
    " : ""] -
    - "} -//" This is here because else the rest of the file looks like a string in notepad++. -/datum/browser/proc/get_footer() - return {" -
    -
    - -"} - -/datum/browser/proc/get_content() - return {" - [get_header()] - [content] - [get_footer()] - "} - -/datum/browser/proc/open(use_onclose = TRUE) - if(isnull(window_id)) //null check because this can potentially nuke goonchat - WARNING("Browser [title] tried to open with a null ID") - to_chat(user, "The [title] browser you tried to open failed a sanity check! Please report this on github!") - return - var/window_size = "" - if (width && height) - window_size = "size=[width]x[height];" - if (stylesheets.len) - send_asset_list(user, stylesheets) - if (scripts.len) - send_asset_list(user, scripts) - user << browse(get_content(), "window=[window_id];[window_size][window_options]") - if (use_onclose) - setup_onclose() - -/datum/browser/proc/setup_onclose() - set waitfor = 0 //winexists sleeps, so we don't need to. - for (var/i in 1 to 10) - if (user && winexists(user, window_id)) - onclose(user, window_id, ref) - break - -/datum/browser/proc/close() - if(!isnull(window_id))//null check because this can potentially nuke goonchat - user << browse(null, "window=[window_id]") - else - WARNING("Browser [title] tried to close with a null ID") - -/datum/browser/modal/alert/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1,Timeout=6000) - if (!User) - return - - var/output = {"
    [Message]

    -
    - [Button1]"} - - if (Button2) - output += {"[Button2]"} - - if (Button3) - output += {"[Button3]"} - - output += {"
    "} - - ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, 350, 150, src, StealFocus, Timeout) - set_content(output) - -/datum/browser/modal/alert/Topic(href,href_list) - if (href_list["close"] || !user || !user.client) - opentime = 0 - return - if (href_list["button"]) - var/button = text2num(href_list["button"]) - if (button <= 3 && button >= 1) - selectedbutton = button - opentime = 0 - close() - -//designed as a drop in replacement for alert(); functions the same. (outside of needing User specified) -/proc/tgalert(var/mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) - if (!User) - User = usr - switch(askuser(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout)) - if (1) - return Button1 - if (2) - return Button2 - if (3) - return Button3 - -//Same shit, but it returns the button number, could at some point support unlimited button amounts. -/proc/askuser(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) - if (!istype(User)) - if (istype(User, /client/)) - var/client/C = User - User = C.mob - else - return - var/datum/browser/modal/alert/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout) - A.open() - A.wait() - if (A.selectedbutton) - return A.selectedbutton - -/datum/browser/modal - var/opentime = 0 - var/timeout - var/selectedbutton = 0 - var/stealfocus - -/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null, StealFocus = 1, Timeout = 6000) - ..() - stealfocus = StealFocus - if (!StealFocus) - window_options += "focus=false;" - timeout = Timeout - - -/datum/browser/modal/close() - .=..() - opentime = 0 - -/datum/browser/modal/open(use_onclose) - set waitfor = 0 - opentime = world.time - - if (stealfocus) - . = ..(use_onclose = 1) - else - var/focusedwindow = winget(user, null, "focus") - . = ..(use_onclose = 1) - - //waits for the window to show up client side before attempting to un-focus it - //winexists sleeps until it gets a reply from the client, so we don't need to bother sleeping - for (var/i in 1 to 10) - if (user && winexists(user, window_id)) - if (focusedwindow) - winset(user, focusedwindow, "focus=true") - else - winset(user, "mapwindow", "focus=true") - break - if (timeout) - addtimer(CALLBACK(src, .proc/close), timeout) - -/datum/browser/modal/proc/wait() - while (opentime && selectedbutton <= 0 && (!timeout || opentime+timeout > world.time)) - stoplag(1) - -/datum/browser/modal/listpicker - var/valueslist = list() - -/datum/browser/modal/listpicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/values,inputtype="checkbox", width, height, slidecolor) - if (!User) - return - - var/output = {"
      "} - if (inputtype == "checkbox" || inputtype == "radio") - for (var/i in values) - var/div_slider = slidecolor - if(!i["allowed_edit"]) - div_slider = "locked" - output += {"
    • - -
    • "} - else - for (var/i in values) - output += {"
    • -
    • "} - output += {"
    - "} - - if (Button2) - output += {""} - - if (Button3) - output += {""} - - output += {"
    "} - ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) - set_content(output) - -/datum/browser/modal/listpicker/Topic(href,href_list) - if (href_list["close"] || !user || !user.client) - opentime = 0 - return - if (href_list["button"]) - var/button = text2num(href_list["button"]) - if (button <= 3 && button >= 1) - selectedbutton = button - for (var/item in href_list) - switch(item) - if ("close", "button", "src") - continue - else - valueslist[item] = href_list[item] - opentime = 0 - close() - -/proc/presentpicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/values, inputtype = "checkbox", width, height, slidecolor) - if (!istype(User)) - if (istype(User, /client/)) - var/client/C = User - User = C.mob - else - return - var/datum/browser/modal/listpicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, values, inputtype, width, height, slidecolor) - A.open() - A.wait() - if (A.selectedbutton) - return list("button" = A.selectedbutton, "values" = A.valueslist) - -/proc/input_bitfield(mob/User, title, bitfield, current_value, nwidth = 350, nheight = 350, nslidecolor, allowed_edit_list = null) - if (!User || !(bitfield in GLOB.bitfields)) - return - var/list/pickerlist = list() - for (var/i in GLOB.bitfields[bitfield]) - var/can_edit = 1 - if(!isnull(allowed_edit_list) && !(allowed_edit_list & GLOB.bitfields[bitfield][i])) - can_edit = 0 - if (current_value & GLOB.bitfields[bitfield][i]) - pickerlist += list(list("checked" = 1, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) - else - pickerlist += list(list("checked" = 0, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) - var/list/result = presentpicker(User, "", title, Button1="Save", Button2 = "Cancel", Timeout=FALSE, values = pickerlist, width = nwidth, height = nheight, slidecolor = nslidecolor) - if (islist(result)) - if (result["button"] == 2) // If the user pressed the cancel button - return - . = 0 - for (var/flag in result["values"]) - . |= GLOB.bitfields[bitfield][flag] - else - return - -/datum/browser/modal/preflikepicker - var/settings = list() - var/icon/preview_icon = null - var/datum/callback/preview_update - -/datum/browser/modal/preflikepicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/settings,inputtype="checkbox", width = 600, height, slidecolor) - if (!User) - return - src.settings = settings - - ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) - set_content(ShowChoices(User)) - -/datum/browser/modal/preflikepicker/proc/ShowChoices(mob/user) - if (settings["preview_callback"]) - var/datum/callback/callback = settings["preview_callback"] - preview_icon = callback.Invoke(settings) - if (preview_icon) - user << browse_rsc(preview_icon, "previewicon.png") - var/dat = "" - - for (var/name in settings["mainsettings"]) - var/setting = settings["mainsettings"][name] - if (setting["type"] == "datum") - if (setting["subtypesonly"]) - dat += "[setting["desc"]]: [setting["value"]]
    " - else - dat += "[setting["desc"]]: [setting["value"]]
    " - else - dat += "[setting["desc"]]: [setting["value"]]
    " - - if (preview_icon) - dat += "" - - dat += "
    " - - dat += "" - - dat += "" - - dat += "
    Ok " - - dat += "
    " - - return dat - -/datum/browser/modal/preflikepicker/Topic(href,href_list) - if (href_list["close"] || !user || !user.client) - opentime = 0 - return - if (href_list["task"] == "input") - var/setting = href_list["setting"] - switch (href_list["type"]) - if ("datum") - var/oldval = settings["mainsettings"][setting]["value"] - if (href_list["subtypesonly"]) - settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(subtypesof(text2path(href_list["path"])))) - else - settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(typesof(text2path(href_list["path"])))) - if (isnull(settings["mainsettings"][setting]["value"])) - settings["mainsettings"][setting]["value"] = oldval - if ("string") - settings["mainsettings"][setting]["value"] = stripped_input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) - if ("number") - settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]") as num - if ("color") - settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) as color - if ("boolean") - settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("Yes","No") - if ("ckey") - settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("none") + GLOB.directory - if (settings["mainsettings"][setting]["callback"]) - var/datum/callback/callback = settings["mainsettings"][setting]["callback"] - settings = callback.Invoke(settings) - if (href_list["button"]) - var/button = text2num(href_list["button"]) - if (button <= 3 && button >= 1) - selectedbutton = button - if (selectedbutton != 1) - set_content(ShowChoices(user)) - open() - return - for (var/item in href_list) - switch(item) - if ("close", "button", "src") - continue - opentime = 0 - close() - -/proc/presentpreflikepicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/settings, width, height, slidecolor) - if (!istype(User)) - if (istype(User, /client/)) - var/client/C = User - User = C.mob - else - return - var/datum/browser/modal/preflikepicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, settings, width, height, slidecolor) - A.open() - A.wait() - if (A.selectedbutton) - return list("button" = A.selectedbutton, "settings" = A.settings) - -// This will allow you to show an icon in the browse window -// This is added to mob so that it can be used without a reference to the browser object -// There is probably a better place for this... -/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1) - - -// Registers the on-close verb for a browse window (client/verb/.windowclose) -// this will be called when the close-button of a window is pressed. -// -// This is usually only needed for devices that regularly update the browse window, -// e.g. canisters, timers, etc. -// -// windowid should be the specified window name -// e.g. code is : user << browse(text, "window=fred") -// then use : onclose(user, "fred") -// -// Optionally, specify the "ref" parameter as the controlled atom (usually src) -// to pass a "close=1" parameter to the atom's Topic() proc for special handling. -// Otherwise, the user mob's machine var will be reset directly. -// -/proc/onclose(mob/user, windowid, atom/ref=null) - if(!user.client) - return - var/param = "null" - if(ref) - param = "[REF(ref)]" - - winset(user, windowid, "on-close=\".windowclose [param]\"") - - - -// the on-close client verb -// called when a browser popup window is closed after registering with proc/onclose() -// if a valid atom reference is supplied, call the atom's Topic() with "close=1" -// otherwise, just reset the client mob's machine var. -// -/client/verb/windowclose(atomref as text) - set hidden = 1 // hide this verb from the user's panel - set name = ".windowclose" // no autocomplete on cmd line - - if(atomref!="null") // if passed a real atomref - var/hsrc = locate(atomref) // find the reffed atom - var/href = "close=1" - if(hsrc) - usr = src.mob - src.Topic(href, params2list(href), hsrc) // this will direct to the atom's - return // Topic() proc via client.Topic() - - // no atomref specified (or not found) - // so just reset the user mob's machine var - if(src && src.mob) - src.mob.unset_machine() +/datum/browser + var/mob/user + var/title + var/window_id // window_id is used as the window name for browse and onclose + var/width = 0 + var/height = 0 + var/atom/ref = null + var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id + var/stylesheets[0] + var/scripts[0] + var/title_image + var/head_elements + var/body_elements + var/head_content = "" + var/content = "" + + +/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null) + + user = nuser + window_id = nwindow_id + if (ntitle) + title = format_text(ntitle) + if (nwidth) + width = nwidth + if (nheight) + height = nheight + if (nref) + ref = nref + add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs + +/datum/browser/proc/add_head_content(nhead_content) + head_content = nhead_content + +/datum/browser/proc/set_window_options(nwindow_options) + window_options = nwindow_options + +/datum/browser/proc/set_title_image(ntitle_image) + //title_image = ntitle_image + +/datum/browser/proc/add_stylesheet(name, file) + if (istype(name, /datum/asset/spritesheet)) + var/datum/asset/spritesheet/sheet = name + stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]" + else + var/asset_name = "[name].css" + + stylesheets[asset_name] = file + + if (!SSassets.cache[asset_name]) + register_asset(asset_name, file) + +/datum/browser/proc/add_script(name, file) + scripts["[ckey(name)].js"] = file + register_asset("[ckey(name)].js", file) + +/datum/browser/proc/set_content(ncontent) + content = ncontent + +/datum/browser/proc/add_content(ncontent) + content += ncontent + +/datum/browser/proc/get_header() + var/file + for (file in stylesheets) + head_content += "" + + for (file in scripts) + head_content += "" + + var/title_attributes = "class='uiTitle'" + if (title_image) + title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'" + + return {" + + + + + [head_content] + + +
    + [title ? "
    [title]
    " : ""] +
    + "} +//" This is here because else the rest of the file looks like a string in notepad++. +/datum/browser/proc/get_footer() + return {" +
    +
    + +"} + +/datum/browser/proc/get_content() + return {" + [get_header()] + [content] + [get_footer()] + "} + +/datum/browser/proc/open(use_onclose = TRUE) + if(isnull(window_id)) //null check because this can potentially nuke goonchat + WARNING("Browser [title] tried to open with a null ID") + to_chat(user, "The [title] browser you tried to open failed a sanity check! Please report this on github!") + return + var/window_size = "" + if (width && height) + window_size = "size=[width]x[height];" + if (stylesheets.len) + send_asset_list(user, stylesheets) + if (scripts.len) + send_asset_list(user, scripts) + user << browse(get_content(), "window=[window_id];[window_size][window_options]") + if (use_onclose) + setup_onclose() + +/datum/browser/proc/setup_onclose() + set waitfor = 0 //winexists sleeps, so we don't need to. + for (var/i in 1 to 10) + if (user && winexists(user, window_id)) + onclose(user, window_id, ref) + break + +/datum/browser/proc/close() + if(!isnull(window_id))//null check because this can potentially nuke goonchat + user << browse(null, "window=[window_id]") + else + WARNING("Browser [title] tried to close with a null ID") + +/datum/browser/modal/alert/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1,Timeout=6000) + if (!User) + return + + var/output = {"
    [Message]

    +
    + [Button1]"} + + if (Button2) + output += {"[Button2]"} + + if (Button3) + output += {"[Button3]"} + + output += {"
    "} + + ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, 350, 150, src, StealFocus, Timeout) + set_content(output) + +/datum/browser/modal/alert/Topic(href,href_list) + if (href_list["close"] || !user || !user.client) + opentime = 0 + return + if (href_list["button"]) + var/button = text2num(href_list["button"]) + if (button <= 3 && button >= 1) + selectedbutton = button + opentime = 0 + close() + +//designed as a drop in replacement for alert(); functions the same. (outside of needing User specified) +/proc/tgalert(var/mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) + if (!User) + User = usr + switch(askuser(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout)) + if (1) + return Button1 + if (2) + return Button2 + if (3) + return Button3 + +//Same shit, but it returns the button number, could at some point support unlimited button amounts. +/proc/askuser(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) + if (!istype(User)) + if (istype(User, /client/)) + var/client/C = User + User = C.mob + else + return + var/datum/browser/modal/alert/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout) + A.open() + A.wait() + if (A.selectedbutton) + return A.selectedbutton + +/datum/browser/modal + var/opentime = 0 + var/timeout + var/selectedbutton = 0 + var/stealfocus + +/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null, StealFocus = 1, Timeout = 6000) + ..() + stealfocus = StealFocus + if (!StealFocus) + window_options += "focus=false;" + timeout = Timeout + + +/datum/browser/modal/close() + .=..() + opentime = 0 + +/datum/browser/modal/open(use_onclose) + set waitfor = 0 + opentime = world.time + + if (stealfocus) + . = ..(use_onclose = 1) + else + var/focusedwindow = winget(user, null, "focus") + . = ..(use_onclose = 1) + + //waits for the window to show up client side before attempting to un-focus it + //winexists sleeps until it gets a reply from the client, so we don't need to bother sleeping + for (var/i in 1 to 10) + if (user && winexists(user, window_id)) + if (focusedwindow) + winset(user, focusedwindow, "focus=true") + else + winset(user, "mapwindow", "focus=true") + break + if (timeout) + addtimer(CALLBACK(src, .proc/close), timeout) + +/datum/browser/modal/proc/wait() + while (opentime && selectedbutton <= 0 && (!timeout || opentime+timeout > world.time)) + stoplag(1) + +/datum/browser/modal/listpicker + var/valueslist = list() + +/datum/browser/modal/listpicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/values,inputtype="checkbox", width, height, slidecolor) + if (!User) + return + + var/output = {"
      "} + if (inputtype == "checkbox" || inputtype == "radio") + for (var/i in values) + var/div_slider = slidecolor + if(!i["allowed_edit"]) + div_slider = "locked" + output += {"
    • + +
    • "} + else + for (var/i in values) + output += {"
    • +
    • "} + output += {"
    + "} + + if (Button2) + output += {""} + + if (Button3) + output += {""} + + output += {"
    "} + ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) + set_content(output) + +/datum/browser/modal/listpicker/Topic(href,href_list) + if (href_list["close"] || !user || !user.client) + opentime = 0 + return + if (href_list["button"]) + var/button = text2num(href_list["button"]) + if (button <= 3 && button >= 1) + selectedbutton = button + for (var/item in href_list) + switch(item) + if ("close", "button", "src") + continue + else + valueslist[item] = href_list[item] + opentime = 0 + close() + +/proc/presentpicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/values, inputtype = "checkbox", width, height, slidecolor) + if (!istype(User)) + if (istype(User, /client/)) + var/client/C = User + User = C.mob + else + return + var/datum/browser/modal/listpicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, values, inputtype, width, height, slidecolor) + A.open() + A.wait() + if (A.selectedbutton) + return list("button" = A.selectedbutton, "values" = A.valueslist) + +/proc/input_bitfield(mob/User, title, bitfield, current_value, nwidth = 350, nheight = 350, nslidecolor, allowed_edit_list = null) + if (!User || !(bitfield in GLOB.bitfields)) + return + var/list/pickerlist = list() + for (var/i in GLOB.bitfields[bitfield]) + var/can_edit = 1 + if(!isnull(allowed_edit_list) && !(allowed_edit_list & GLOB.bitfields[bitfield][i])) + can_edit = 0 + if (current_value & GLOB.bitfields[bitfield][i]) + pickerlist += list(list("checked" = 1, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) + else + pickerlist += list(list("checked" = 0, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) + var/list/result = presentpicker(User, "", title, Button1="Save", Button2 = "Cancel", Timeout=FALSE, values = pickerlist, width = nwidth, height = nheight, slidecolor = nslidecolor) + if (islist(result)) + if (result["button"] == 2) // If the user pressed the cancel button + return + . = 0 + for (var/flag in result["values"]) + . |= GLOB.bitfields[bitfield][flag] + else + return + +/datum/browser/modal/preflikepicker + var/settings = list() + var/icon/preview_icon = null + var/datum/callback/preview_update + +/datum/browser/modal/preflikepicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/settings,inputtype="checkbox", width = 600, height, slidecolor) + if (!User) + return + src.settings = settings + + ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) + set_content(ShowChoices(User)) + +/datum/browser/modal/preflikepicker/proc/ShowChoices(mob/user) + if (settings["preview_callback"]) + var/datum/callback/callback = settings["preview_callback"] + preview_icon = callback.Invoke(settings) + if (preview_icon) + user << browse_rsc(preview_icon, "previewicon.png") + var/dat = "" + + for (var/name in settings["mainsettings"]) + var/setting = settings["mainsettings"][name] + if (setting["type"] == "datum") + if (setting["subtypesonly"]) + dat += "[setting["desc"]]: [setting["value"]]
    " + else + dat += "[setting["desc"]]: [setting["value"]]
    " + else + dat += "[setting["desc"]]: [setting["value"]]
    " + + if (preview_icon) + dat += "" + + dat += "
    " + + dat += "" + + dat += "" + + dat += "
    Ok " + + dat += "
    " + + return dat + +/datum/browser/modal/preflikepicker/Topic(href,href_list) + if (href_list["close"] || !user || !user.client) + opentime = 0 + return + if (href_list["task"] == "input") + var/setting = href_list["setting"] + switch (href_list["type"]) + if ("datum") + var/oldval = settings["mainsettings"][setting]["value"] + if (href_list["subtypesonly"]) + settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(subtypesof(text2path(href_list["path"])))) + else + settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(typesof(text2path(href_list["path"])))) + if (isnull(settings["mainsettings"][setting]["value"])) + settings["mainsettings"][setting]["value"] = oldval + if ("string") + settings["mainsettings"][setting]["value"] = stripped_input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) + if ("number") + settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]") as num + if ("color") + settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) as color + if ("boolean") + settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("Yes","No") + if ("ckey") + settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("none") + GLOB.directory + if (settings["mainsettings"][setting]["callback"]) + var/datum/callback/callback = settings["mainsettings"][setting]["callback"] + settings = callback.Invoke(settings) + if (href_list["button"]) + var/button = text2num(href_list["button"]) + if (button <= 3 && button >= 1) + selectedbutton = button + if (selectedbutton != 1) + set_content(ShowChoices(user)) + open() + return + for (var/item in href_list) + switch(item) + if ("close", "button", "src") + continue + opentime = 0 + close() + +/proc/presentpreflikepicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/settings, width, height, slidecolor) + if (!istype(User)) + if (istype(User, /client/)) + var/client/C = User + User = C.mob + else + return + var/datum/browser/modal/preflikepicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, settings, width, height, slidecolor) + A.open() + A.wait() + if (A.selectedbutton) + return list("button" = A.selectedbutton, "settings" = A.settings) + +// This will allow you to show an icon in the browse window +// This is added to mob so that it can be used without a reference to the browser object +// There is probably a better place for this... +/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1) + + +// Registers the on-close verb for a browse window (client/verb/.windowclose) +// this will be called when the close-button of a window is pressed. +// +// This is usually only needed for devices that regularly update the browse window, +// e.g. canisters, timers, etc. +// +// windowid should be the specified window name +// e.g. code is : user << browse(text, "window=fred") +// then use : onclose(user, "fred") +// +// Optionally, specify the "ref" parameter as the controlled atom (usually src) +// to pass a "close=1" parameter to the atom's Topic() proc for special handling. +// Otherwise, the user mob's machine var will be reset directly. +// +/proc/onclose(mob/user, windowid, atom/ref=null) + if(!user.client) + return + var/param = "null" + if(ref) + param = "[REF(ref)]" + + winset(user, windowid, "on-close=\".windowclose [param]\"") + + + +// the on-close client verb +// called when a browser popup window is closed after registering with proc/onclose() +// if a valid atom reference is supplied, call the atom's Topic() with "close=1" +// otherwise, just reset the client mob's machine var. +// +/client/verb/windowclose(atomref as text) + set hidden = 1 // hide this verb from the user's panel + set name = ".windowclose" // no autocomplete on cmd line + + if(atomref!="null") // if passed a real atomref + var/hsrc = locate(atomref) // find the reffed atom + var/href = "close=1" + if(hsrc) + usr = src.mob + src.Topic(href, params2list(href), hsrc) // this will direct to the atom's + return // Topic() proc via client.Topic() + + // no atomref specified (or not found) + // so just reset the user mob's machine var + if(src && src.mob) + src.mob.unset_machine() diff --git a/code/datums/components/README.md b/code/datums/components/README.md index d86d06b947a..3080f7e7b44 100644 --- a/code/datums/components/README.md +++ b/code/datums/components/README.md @@ -1,9 +1,9 @@ -# Datum Component System (DCS) - -## Concept - -Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening. - -See [this thread](https://tgstation13.org/phpBB/viewtopic.php?f=5&t=22674) for an introduction to the system as a whole. - -### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm) +# Datum Component System (DCS) + +## Concept + +Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening. + +See [this thread](https://tgstation13.org/phpBB/viewtopic.php?f=5&t=22674) for an introduction to the system as a whole. + +### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm) diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 2fd648f3483..f1ee6de87f7 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -1,537 +1,537 @@ -/** - * # Component - * - * The component datum - * - * A component should be a single standalone unit - * of functionality, that works by receiving signals from it's parent - * object to provide some single functionality (i.e a slippery component) - * that makes the object it's attached to cause people to slip over. - * Useful when you want shared behaviour independent of type inheritance - */ -/datum/component - /** - * Defines how duplicate existing components are handled when added to a datum - * - * See [COMPONENT_DUPE_*][COMPONENT_DUPE_ALLOWED] definitions for available options - */ - var/dupe_mode = COMPONENT_DUPE_HIGHLANDER - - /** - * The type to check for duplication - * - * `null` means exact match on `type` (default) - * - * Any other type means that and all subtypes - */ - var/dupe_type - - /// The datum this components belongs to - var/datum/parent - - /** - * Only set to true if you are able to properly transfer this component - * - * At a minimum [RegisterWithParent][/datum/component/proc/RegisterWithParent] and [UnregisterFromParent][/datum/component/proc/UnregisterFromParent] should be used - * - * Make sure you also implement [PostTransfer][/datum/component/proc/PostTransfer] for any post transfer handling - */ - var/can_transfer = FALSE - -/** - * Create a new component. - * - * Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize] - * - * Arguments: - * * datum/P the parent datum this component reacts to signals from - */ -/datum/component/New(list/raw_args) - parent = raw_args[1] - var/list/arguments = raw_args.Copy(2) - if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE) - stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]") - qdel(src, TRUE, TRUE) - return - - _JoinParent(parent) - -/** - * Called during component creation with the same arguments as in new excluding parent. - * - * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead - */ -/datum/component/proc/Initialize(...) - return - -/** - * Properly removes the component from `parent` and cleans up references - * - * Arguments: - * * force - makes it not check for and remove the component from the parent - * * silent - deletes the component without sending a [COMSIG_COMPONENT_REMOVING] signal - */ -/datum/component/Destroy(force=FALSE, silent=FALSE) - if(!force && parent) - _RemoveFromParent() - if(!silent) - SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src) - parent = null - return ..() - -/** - * Internal proc to handle behaviour of components when joining a parent - */ -/datum/component/proc/_JoinParent() - var/datum/P = parent - //lazy init the parent's dc list - var/list/dc = P.datum_components - if(!dc) - P.datum_components = dc = list() - - //set up the typecache - var/our_type = type - for(var/I in _GetInverseTypeList(our_type)) - var/test = dc[I] - if(test) //already another component of this type here - var/list/components_of_type - if(!length(test)) - components_of_type = list(test) - dc[I] = components_of_type - else - components_of_type = test - if(I == our_type) //exact match, take priority - var/inserted = FALSE - for(var/J in 1 to components_of_type.len) - var/datum/component/C = components_of_type[J] - if(C.type != our_type) //but not over other exact matches - components_of_type.Insert(J, I) - inserted = TRUE - break - if(!inserted) - components_of_type += src - else //indirect match, back of the line with ya - components_of_type += src - else //only component of this type, no list - dc[I] = src - - RegisterWithParent() - -/** - * Internal proc to handle behaviour when being removed from a parent - */ -/datum/component/proc/_RemoveFromParent() - var/datum/P = parent - var/list/dc = P.datum_components - for(var/I in _GetInverseTypeList()) - var/list/components_of_type = dc[I] - if(length(components_of_type)) // - var/list/subtracted = components_of_type - src - if(subtracted.len == 1) //only 1 guy left - dc[I] = subtracted[1] //make him special - else - dc[I] = subtracted - else //just us - dc -= I - if(!dc.len) - P.datum_components = null - - UnregisterFromParent() - -/** - * Register the component with the parent object - * - * Use this proc to register with your parent object - * - * Overridable proc that's called when added to a new parent - */ -/datum/component/proc/RegisterWithParent() - return - -/** - * Unregister from our parent object - * - * Use this proc to unregister from your parent object - * - * Overridable proc that's called when removed from a parent - * * - */ -/datum/component/proc/UnregisterFromParent() - return - -/** - * Register to listen for a signal from the passed in target - * - * This sets up a listening relationship such that when the target object emits a signal - * the source datum this proc is called upon, will receive a callback to the given proctype - * Return values from procs registered must be a bitfield - * - * Arguments: - * * datum/target The target to listen for signals from - * * sig_type_or_types Either a string signal name, or a list of signal names (strings) - * * proctype The proc to call back when the signal is emitted - * * override If a previous registration exists you must explicitly set this - */ -/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE) - if(QDELETED(src) || QDELETED(target)) - return - - var/list/procs = signal_procs - if(!procs) - signal_procs = procs = list() - if(!procs[target]) - procs[target] = list() - var/list/lookup = target.comp_lookup - if(!lookup) - target.comp_lookup = lookup = list() - - var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types) - for(var/sig_type in sig_types) - if(!override && procs[target][sig_type]) - stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning") - - procs[target][sig_type] = proctype - - if(!lookup[sig_type]) // Nothing has registered here yet - lookup[sig_type] = src - else if(lookup[sig_type] == src) // We already registered here - continue - else if(!length(lookup[sig_type])) // One other thing registered here - lookup[sig_type] = list(lookup[sig_type]=TRUE) - lookup[sig_type][src] = TRUE - else // Many other things have registered here - lookup[sig_type][src] = TRUE - - signal_enabled = TRUE - -/** - * Stop listening to a given signal from target - * - * Breaks the relationship between target and source datum, removing the callback when the signal fires - * - * Doesn't care if a registration exists or not - * - * Arguments: - * * datum/target Datum to stop listening to signals from - * * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically - */ -/datum/proc/UnregisterSignal(datum/target, sig_type_or_types) - var/list/lookup = target.comp_lookup - if(!signal_procs || !signal_procs[target] || !lookup) - return - if(!islist(sig_type_or_types)) - sig_type_or_types = list(sig_type_or_types) - for(var/sig in sig_type_or_types) - if(!signal_procs[target][sig]) - continue - switch(length(lookup[sig])) - if(2) - lookup[sig] = (lookup[sig]-src)[1] - if(1) - stack_trace("[target] ([target.type]) somehow has single length list inside comp_lookup") - if(src in lookup[sig]) - lookup -= sig - if(!length(lookup)) - target.comp_lookup = null - break - if(0) - lookup -= sig - if(!length(lookup)) - target.comp_lookup = null - break - else - lookup[sig] -= src - - signal_procs[target] -= sig_type_or_types - if(!signal_procs[target].len) - signal_procs -= target - -/** - * Called on a component when a component of the same type was added to the same parent - * - * See [/datum/component/var/dupe_mode] - * - * `C`'s type will always be the same of the called component - */ -/datum/component/proc/InheritComponent(datum/component/C, i_am_original) - return - - -/** - * Called on a component when a component of the same type was added to the same parent with [COMPONENT_DUPE_SELECTIVE] - * - * See [/datum/component/var/dupe_mode] - * - * `C`'s type will always be the same of the called component - * - * return TRUE if you are absorbing the component, otherwise FALSE if you are fine having it exist as a duplicate component - */ -/datum/component/proc/CheckDupeComponent(datum/component/C, ...) - return - - -/** - * Callback Just before this component is transferred - * - * Use this to do any special cleanup you might need to do before being deregged from an object - */ -/datum/component/proc/PreTransfer() - return - -/** - * Callback Just after a component is transferred - * - * Use this to do any special setup you need to do after being moved to a new object - * - * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead - */ -/datum/component/proc/PostTransfer() - return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it - -/** - * Internal proc to create a list of our type and all parent types - */ -/datum/component/proc/_GetInverseTypeList(our_type = type) - //we can do this one simple trick - var/current_type = parent_type - . = list(our_type, current_type) - //and since most components are root level + 1, this won't even have to run - while (current_type != /datum/component) - current_type = type2parent(current_type) - . += current_type - -/** - * Internal proc to handle most all of the signaling procedure - * - * Will runtime if used on datums with an empty component list - * - * Use the [SEND_SIGNAL] define instead - */ -/datum/proc/_SendSignal(sigtype, list/arguments) - var/target = comp_lookup[sigtype] - if(!length(target)) - var/datum/C = target - if(!C.signal_enabled) - return NONE - var/proctype = C.signal_procs[src][sigtype] - return NONE | CallAsync(C, proctype, arguments) - . = NONE - for(var/I in target) - var/datum/C = I - if(!C.signal_enabled) - continue - var/proctype = C.signal_procs[src][sigtype] - . |= CallAsync(C, proctype, arguments) - -// The type arg is casted so initial works, you shouldn't be passing a real instance into this -/** - * Return any component assigned to this datum of the given type - * - * This will throw an error if it's possible to have more than one component of that type on the parent - * - * Arguments: - * * datum/component/c_type The typepath of the component you want to get a reference to - */ -/datum/proc/GetComponent(datum/component/c_type) - RETURN_TYPE(c_type) - if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) - stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") - var/list/dc = datum_components - if(!dc) - return null - . = dc[c_type] - if(length(.)) - return .[1] - -// The type arg is casted so initial works, you shouldn't be passing a real instance into this -/** - * Return any component assigned to this datum of the exact given type - * - * This will throw an error if it's possible to have more than one component of that type on the parent - * - * Arguments: - * * datum/component/c_type The typepath of the component you want to get a reference to - */ -/datum/proc/GetExactComponent(datum/component/c_type) - RETURN_TYPE(c_type) - if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) - stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") - var/list/dc = datum_components - if(!dc) - return null - var/datum/component/C = dc[c_type] - if(C) - if(length(C)) - C = C[1] - if(C.type == c_type) - return C - return null - -/** - * Get all components of a given type that are attached to this datum - * - * Arguments: - * * c_type The component type path - */ -/datum/proc/GetComponents(c_type) - var/list/dc = datum_components - if(!dc) - return null - . = dc[c_type] - if(!length(.)) - return list(.) - -/** - * Creates an instance of `new_type` in the datum and attaches to it as parent - * - * Sends the [COMSIG_COMPONENT_ADDED] signal to the datum - * - * Returns the component that was created. Or the old component in a dupe situation where [COMPONENT_DUPE_UNIQUE] was set - * - * If this tries to add a component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it - * - * Properly handles duplicate situations based on the `dupe_mode` var - */ -/datum/proc/_AddComponent(list/raw_args) - var/new_type = raw_args[1] - var/datum/component/nt = new_type - var/dm = initial(nt.dupe_mode) - var/dt = initial(nt.dupe_type) - - var/datum/component/old_comp - var/datum/component/new_comp - - if(ispath(nt)) - if(nt == /datum/component) - CRASH("[nt] attempted instantiation!") - else - new_comp = nt - nt = new_comp.type - - raw_args[1] = src - - if(dm != COMPONENT_DUPE_ALLOWED) - if(!dt) - old_comp = GetExactComponent(nt) - else - old_comp = GetComponent(dt) - if(old_comp) - switch(dm) - if(COMPONENT_DUPE_UNIQUE) - if(!new_comp) - new_comp = new nt(raw_args) - if(!QDELETED(new_comp)) - old_comp.InheritComponent(new_comp, TRUE) - QDEL_NULL(new_comp) - if(COMPONENT_DUPE_HIGHLANDER) - if(!new_comp) - new_comp = new nt(raw_args) - if(!QDELETED(new_comp)) - new_comp.InheritComponent(old_comp, FALSE) - QDEL_NULL(old_comp) - if(COMPONENT_DUPE_UNIQUE_PASSARGS) - if(!new_comp) - var/list/arguments = raw_args.Copy(2) - arguments.Insert(1, null, TRUE) - old_comp.InheritComponent(arglist(arguments)) - else - old_comp.InheritComponent(new_comp, TRUE) - if(COMPONENT_DUPE_SELECTIVE) - var/list/arguments = raw_args.Copy() - arguments[1] = new_comp - var/make_new_component = TRUE - for(var/i in GetComponents(new_type)) - var/datum/component/C = i - if(C.CheckDupeComponent(arglist(arguments))) - make_new_component = FALSE - QDEL_NULL(new_comp) - break - if(!new_comp && make_new_component) - new_comp = new nt(raw_args) - else if(!new_comp) - new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal - else if(!new_comp) - new_comp = new nt(raw_args) // Dupes are allowed, act like normal - - if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy - SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp) - return new_comp - return old_comp - -/** - * Get existing component of type, or create it and return a reference to it - * - * Use this if the item needs to exist at the time of this call, but may not have been created before now - * - * Arguments: - * * component_type The typepath of the component to create or return - * * ... additional arguments to be passed when creating the component if it does not exist - */ -/datum/proc/LoadComponent(component_type, ...) - . = GetComponent(component_type) - if(!.) - return _AddComponent(args) - -/** - * Removes the component from parent, ends up with a null parent - */ -/datum/component/proc/RemoveComponent() - if(!parent) - return - var/datum/old_parent = parent - PreTransfer() - _RemoveFromParent() - parent = null - SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src) - -/** - * Transfer this component to another parent - * - * Component is taken from source datum - * - * Arguments: - * * datum/component/target Target datum to transfer to - */ -/datum/proc/TakeComponent(datum/component/target) - if(!target || target.parent == src) - return - if(target.parent) - target.RemoveComponent() - target.parent = src - var/result = target.PostTransfer() - switch(result) - if(COMPONENT_INCOMPATIBLE) - var/c_type = target.type - qdel(target) - CRASH("Incompatible [c_type] transfer attempt to a [type]!") - - if(target == AddComponent(target)) - target._JoinParent() - -/** - * Transfer all components to target - * - * All components from source datum are taken - * - * Arguments: - * * /datum/target the target to move the components to - */ -/datum/proc/TransferComponents(datum/target) - var/list/dc = datum_components - if(!dc) - return - var/comps = dc[/datum/component] - if(islist(comps)) - for(var/datum/component/I in comps) - if(I.can_transfer) - target.TakeComponent(I) - else - var/datum/component/C = comps - if(C.can_transfer) - target.TakeComponent(comps) - -/** - * Return the object that is the host of any UI's that this component has - */ -/datum/component/ui_host() - return parent +/** + * # Component + * + * The component datum + * + * A component should be a single standalone unit + * of functionality, that works by receiving signals from it's parent + * object to provide some single functionality (i.e a slippery component) + * that makes the object it's attached to cause people to slip over. + * Useful when you want shared behaviour independent of type inheritance + */ +/datum/component + /** + * Defines how duplicate existing components are handled when added to a datum + * + * See [COMPONENT_DUPE_*][COMPONENT_DUPE_ALLOWED] definitions for available options + */ + var/dupe_mode = COMPONENT_DUPE_HIGHLANDER + + /** + * The type to check for duplication + * + * `null` means exact match on `type` (default) + * + * Any other type means that and all subtypes + */ + var/dupe_type + + /// The datum this components belongs to + var/datum/parent + + /** + * Only set to true if you are able to properly transfer this component + * + * At a minimum [RegisterWithParent][/datum/component/proc/RegisterWithParent] and [UnregisterFromParent][/datum/component/proc/UnregisterFromParent] should be used + * + * Make sure you also implement [PostTransfer][/datum/component/proc/PostTransfer] for any post transfer handling + */ + var/can_transfer = FALSE + +/** + * Create a new component. + * + * Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize] + * + * Arguments: + * * datum/P the parent datum this component reacts to signals from + */ +/datum/component/New(list/raw_args) + parent = raw_args[1] + var/list/arguments = raw_args.Copy(2) + if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE) + stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]") + qdel(src, TRUE, TRUE) + return + + _JoinParent(parent) + +/** + * Called during component creation with the same arguments as in new excluding parent. + * + * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead + */ +/datum/component/proc/Initialize(...) + return + +/** + * Properly removes the component from `parent` and cleans up references + * + * Arguments: + * * force - makes it not check for and remove the component from the parent + * * silent - deletes the component without sending a [COMSIG_COMPONENT_REMOVING] signal + */ +/datum/component/Destroy(force=FALSE, silent=FALSE) + if(!force && parent) + _RemoveFromParent() + if(!silent) + SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src) + parent = null + return ..() + +/** + * Internal proc to handle behaviour of components when joining a parent + */ +/datum/component/proc/_JoinParent() + var/datum/P = parent + //lazy init the parent's dc list + var/list/dc = P.datum_components + if(!dc) + P.datum_components = dc = list() + + //set up the typecache + var/our_type = type + for(var/I in _GetInverseTypeList(our_type)) + var/test = dc[I] + if(test) //already another component of this type here + var/list/components_of_type + if(!length(test)) + components_of_type = list(test) + dc[I] = components_of_type + else + components_of_type = test + if(I == our_type) //exact match, take priority + var/inserted = FALSE + for(var/J in 1 to components_of_type.len) + var/datum/component/C = components_of_type[J] + if(C.type != our_type) //but not over other exact matches + components_of_type.Insert(J, I) + inserted = TRUE + break + if(!inserted) + components_of_type += src + else //indirect match, back of the line with ya + components_of_type += src + else //only component of this type, no list + dc[I] = src + + RegisterWithParent() + +/** + * Internal proc to handle behaviour when being removed from a parent + */ +/datum/component/proc/_RemoveFromParent() + var/datum/P = parent + var/list/dc = P.datum_components + for(var/I in _GetInverseTypeList()) + var/list/components_of_type = dc[I] + if(length(components_of_type)) // + var/list/subtracted = components_of_type - src + if(subtracted.len == 1) //only 1 guy left + dc[I] = subtracted[1] //make him special + else + dc[I] = subtracted + else //just us + dc -= I + if(!dc.len) + P.datum_components = null + + UnregisterFromParent() + +/** + * Register the component with the parent object + * + * Use this proc to register with your parent object + * + * Overridable proc that's called when added to a new parent + */ +/datum/component/proc/RegisterWithParent() + return + +/** + * Unregister from our parent object + * + * Use this proc to unregister from your parent object + * + * Overridable proc that's called when removed from a parent + * * + */ +/datum/component/proc/UnregisterFromParent() + return + +/** + * Register to listen for a signal from the passed in target + * + * This sets up a listening relationship such that when the target object emits a signal + * the source datum this proc is called upon, will receive a callback to the given proctype + * Return values from procs registered must be a bitfield + * + * Arguments: + * * datum/target The target to listen for signals from + * * sig_type_or_types Either a string signal name, or a list of signal names (strings) + * * proctype The proc to call back when the signal is emitted + * * override If a previous registration exists you must explicitly set this + */ +/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE) + if(QDELETED(src) || QDELETED(target)) + return + + var/list/procs = signal_procs + if(!procs) + signal_procs = procs = list() + if(!procs[target]) + procs[target] = list() + var/list/lookup = target.comp_lookup + if(!lookup) + target.comp_lookup = lookup = list() + + var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types) + for(var/sig_type in sig_types) + if(!override && procs[target][sig_type]) + stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning") + + procs[target][sig_type] = proctype + + if(!lookup[sig_type]) // Nothing has registered here yet + lookup[sig_type] = src + else if(lookup[sig_type] == src) // We already registered here + continue + else if(!length(lookup[sig_type])) // One other thing registered here + lookup[sig_type] = list(lookup[sig_type]=TRUE) + lookup[sig_type][src] = TRUE + else // Many other things have registered here + lookup[sig_type][src] = TRUE + + signal_enabled = TRUE + +/** + * Stop listening to a given signal from target + * + * Breaks the relationship between target and source datum, removing the callback when the signal fires + * + * Doesn't care if a registration exists or not + * + * Arguments: + * * datum/target Datum to stop listening to signals from + * * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically + */ +/datum/proc/UnregisterSignal(datum/target, sig_type_or_types) + var/list/lookup = target.comp_lookup + if(!signal_procs || !signal_procs[target] || !lookup) + return + if(!islist(sig_type_or_types)) + sig_type_or_types = list(sig_type_or_types) + for(var/sig in sig_type_or_types) + if(!signal_procs[target][sig]) + continue + switch(length(lookup[sig])) + if(2) + lookup[sig] = (lookup[sig]-src)[1] + if(1) + stack_trace("[target] ([target.type]) somehow has single length list inside comp_lookup") + if(src in lookup[sig]) + lookup -= sig + if(!length(lookup)) + target.comp_lookup = null + break + if(0) + lookup -= sig + if(!length(lookup)) + target.comp_lookup = null + break + else + lookup[sig] -= src + + signal_procs[target] -= sig_type_or_types + if(!signal_procs[target].len) + signal_procs -= target + +/** + * Called on a component when a component of the same type was added to the same parent + * + * See [/datum/component/var/dupe_mode] + * + * `C`'s type will always be the same of the called component + */ +/datum/component/proc/InheritComponent(datum/component/C, i_am_original) + return + + +/** + * Called on a component when a component of the same type was added to the same parent with [COMPONENT_DUPE_SELECTIVE] + * + * See [/datum/component/var/dupe_mode] + * + * `C`'s type will always be the same of the called component + * + * return TRUE if you are absorbing the component, otherwise FALSE if you are fine having it exist as a duplicate component + */ +/datum/component/proc/CheckDupeComponent(datum/component/C, ...) + return + + +/** + * Callback Just before this component is transferred + * + * Use this to do any special cleanup you might need to do before being deregged from an object + */ +/datum/component/proc/PreTransfer() + return + +/** + * Callback Just after a component is transferred + * + * Use this to do any special setup you need to do after being moved to a new object + * + * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead + */ +/datum/component/proc/PostTransfer() + return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it + +/** + * Internal proc to create a list of our type and all parent types + */ +/datum/component/proc/_GetInverseTypeList(our_type = type) + //we can do this one simple trick + var/current_type = parent_type + . = list(our_type, current_type) + //and since most components are root level + 1, this won't even have to run + while (current_type != /datum/component) + current_type = type2parent(current_type) + . += current_type + +/** + * Internal proc to handle most all of the signaling procedure + * + * Will runtime if used on datums with an empty component list + * + * Use the [SEND_SIGNAL] define instead + */ +/datum/proc/_SendSignal(sigtype, list/arguments) + var/target = comp_lookup[sigtype] + if(!length(target)) + var/datum/C = target + if(!C.signal_enabled) + return NONE + var/proctype = C.signal_procs[src][sigtype] + return NONE | CallAsync(C, proctype, arguments) + . = NONE + for(var/I in target) + var/datum/C = I + if(!C.signal_enabled) + continue + var/proctype = C.signal_procs[src][sigtype] + . |= CallAsync(C, proctype, arguments) + +// The type arg is casted so initial works, you shouldn't be passing a real instance into this +/** + * Return any component assigned to this datum of the given type + * + * This will throw an error if it's possible to have more than one component of that type on the parent + * + * Arguments: + * * datum/component/c_type The typepath of the component you want to get a reference to + */ +/datum/proc/GetComponent(datum/component/c_type) + RETURN_TYPE(c_type) + if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) + stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") + var/list/dc = datum_components + if(!dc) + return null + . = dc[c_type] + if(length(.)) + return .[1] + +// The type arg is casted so initial works, you shouldn't be passing a real instance into this +/** + * Return any component assigned to this datum of the exact given type + * + * This will throw an error if it's possible to have more than one component of that type on the parent + * + * Arguments: + * * datum/component/c_type The typepath of the component you want to get a reference to + */ +/datum/proc/GetExactComponent(datum/component/c_type) + RETURN_TYPE(c_type) + if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) + stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") + var/list/dc = datum_components + if(!dc) + return null + var/datum/component/C = dc[c_type] + if(C) + if(length(C)) + C = C[1] + if(C.type == c_type) + return C + return null + +/** + * Get all components of a given type that are attached to this datum + * + * Arguments: + * * c_type The component type path + */ +/datum/proc/GetComponents(c_type) + var/list/dc = datum_components + if(!dc) + return null + . = dc[c_type] + if(!length(.)) + return list(.) + +/** + * Creates an instance of `new_type` in the datum and attaches to it as parent + * + * Sends the [COMSIG_COMPONENT_ADDED] signal to the datum + * + * Returns the component that was created. Or the old component in a dupe situation where [COMPONENT_DUPE_UNIQUE] was set + * + * If this tries to add a component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it + * + * Properly handles duplicate situations based on the `dupe_mode` var + */ +/datum/proc/_AddComponent(list/raw_args) + var/new_type = raw_args[1] + var/datum/component/nt = new_type + var/dm = initial(nt.dupe_mode) + var/dt = initial(nt.dupe_type) + + var/datum/component/old_comp + var/datum/component/new_comp + + if(ispath(nt)) + if(nt == /datum/component) + CRASH("[nt] attempted instantiation!") + else + new_comp = nt + nt = new_comp.type + + raw_args[1] = src + + if(dm != COMPONENT_DUPE_ALLOWED) + if(!dt) + old_comp = GetExactComponent(nt) + else + old_comp = GetComponent(dt) + if(old_comp) + switch(dm) + if(COMPONENT_DUPE_UNIQUE) + if(!new_comp) + new_comp = new nt(raw_args) + if(!QDELETED(new_comp)) + old_comp.InheritComponent(new_comp, TRUE) + QDEL_NULL(new_comp) + if(COMPONENT_DUPE_HIGHLANDER) + if(!new_comp) + new_comp = new nt(raw_args) + if(!QDELETED(new_comp)) + new_comp.InheritComponent(old_comp, FALSE) + QDEL_NULL(old_comp) + if(COMPONENT_DUPE_UNIQUE_PASSARGS) + if(!new_comp) + var/list/arguments = raw_args.Copy(2) + arguments.Insert(1, null, TRUE) + old_comp.InheritComponent(arglist(arguments)) + else + old_comp.InheritComponent(new_comp, TRUE) + if(COMPONENT_DUPE_SELECTIVE) + var/list/arguments = raw_args.Copy() + arguments[1] = new_comp + var/make_new_component = TRUE + for(var/i in GetComponents(new_type)) + var/datum/component/C = i + if(C.CheckDupeComponent(arglist(arguments))) + make_new_component = FALSE + QDEL_NULL(new_comp) + break + if(!new_comp && make_new_component) + new_comp = new nt(raw_args) + else if(!new_comp) + new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal + else if(!new_comp) + new_comp = new nt(raw_args) // Dupes are allowed, act like normal + + if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy + SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp) + return new_comp + return old_comp + +/** + * Get existing component of type, or create it and return a reference to it + * + * Use this if the item needs to exist at the time of this call, but may not have been created before now + * + * Arguments: + * * component_type The typepath of the component to create or return + * * ... additional arguments to be passed when creating the component if it does not exist + */ +/datum/proc/LoadComponent(component_type, ...) + . = GetComponent(component_type) + if(!.) + return _AddComponent(args) + +/** + * Removes the component from parent, ends up with a null parent + */ +/datum/component/proc/RemoveComponent() + if(!parent) + return + var/datum/old_parent = parent + PreTransfer() + _RemoveFromParent() + parent = null + SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src) + +/** + * Transfer this component to another parent + * + * Component is taken from source datum + * + * Arguments: + * * datum/component/target Target datum to transfer to + */ +/datum/proc/TakeComponent(datum/component/target) + if(!target || target.parent == src) + return + if(target.parent) + target.RemoveComponent() + target.parent = src + var/result = target.PostTransfer() + switch(result) + if(COMPONENT_INCOMPATIBLE) + var/c_type = target.type + qdel(target) + CRASH("Incompatible [c_type] transfer attempt to a [type]!") + + if(target == AddComponent(target)) + target._JoinParent() + +/** + * Transfer all components to target + * + * All components from source datum are taken + * + * Arguments: + * * /datum/target the target to move the components to + */ +/datum/proc/TransferComponents(datum/target) + var/list/dc = datum_components + if(!dc) + return + var/comps = dc[/datum/component] + if(islist(comps)) + for(var/datum/component/I in comps) + if(I.can_transfer) + target.TakeComponent(I) + else + var/datum/component/C = comps + if(C.can_transfer) + target.TakeComponent(comps) + +/** + * Return the object that is the host of any UI's that this component has + */ +/datum/component/ui_host() + return parent diff --git a/code/datums/components/creamed.dm b/code/datums/components/creamed.dm index 38759e56561..432f146f84e 100644 --- a/code/datums/components/creamed.dm +++ b/code/datums/components/creamed.dm @@ -1,62 +1,62 @@ -GLOBAL_LIST_INIT(creamable, typecacheof(list( - /mob/living/carbon/human, - /mob/living/carbon/monkey, - /mob/living/simple_animal/pet/dog/corgi, - /mob/living/silicon/ai))) - -/** - * Creamed component - * - * For when you have pie on your face - */ -/datum/component/creamed - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - - var/mutable_appearance/creamface - -/datum/component/creamed/Initialize() - if(!is_type_in_typecache(parent, GLOB.creamable)) - return COMPONENT_INCOMPATIBLE - - creamface = mutable_appearance('icons/effects/creampie.dmi') - - if(ishuman(parent)) - var/mob/living/carbon/human/H = parent - if(H.dna.species.limbs_id == "lizard") - creamface.icon_state = "creampie_lizard" - else - creamface.icon_state = "creampie_human" - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "creampie", /datum/mood_event/creampie) - else if(ismonkey(parent)) - creamface.icon_state = "creampie_monkey" - else if(iscorgi(parent)) - creamface.icon_state = "creampie_corgi" - else if(isAI(parent)) - creamface.icon_state = "creampie_ai" - - var/atom/A = parent - A.add_overlay(creamface) - -/datum/component/creamed/Destroy(force, silent) - var/atom/A = parent - A.cut_overlay(creamface) - qdel(creamface) - if(ishuman(A)) - SEND_SIGNAL(A, COMSIG_CLEAR_MOOD_EVENT, "creampie") - return ..() - -/datum/component/creamed/RegisterWithParent() - RegisterSignal(parent, list( - COMSIG_COMPONENT_CLEAN_ACT, - COMSIG_COMPONENT_CLEAN_FACE_ACT), - .proc/clean_up) - -/datum/component/creamed/UnregisterFromParent() - UnregisterSignal(parent, list( - COMSIG_COMPONENT_CLEAN_ACT, - COMSIG_COMPONENT_CLEAN_FACE_ACT)) - -///Callback to remove pieface -/datum/component/creamed/proc/clean_up(datum/source, strength) - if(strength >= CLEAN_WEAK) - qdel(src) +GLOBAL_LIST_INIT(creamable, typecacheof(list( + /mob/living/carbon/human, + /mob/living/carbon/monkey, + /mob/living/simple_animal/pet/dog/corgi, + /mob/living/silicon/ai))) + +/** + * Creamed component + * + * For when you have pie on your face + */ +/datum/component/creamed + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + + var/mutable_appearance/creamface + +/datum/component/creamed/Initialize() + if(!is_type_in_typecache(parent, GLOB.creamable)) + return COMPONENT_INCOMPATIBLE + + creamface = mutable_appearance('icons/effects/creampie.dmi') + + if(ishuman(parent)) + var/mob/living/carbon/human/H = parent + if(H.dna.species.limbs_id == "lizard") + creamface.icon_state = "creampie_lizard" + else + creamface.icon_state = "creampie_human" + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "creampie", /datum/mood_event/creampie) + else if(ismonkey(parent)) + creamface.icon_state = "creampie_monkey" + else if(iscorgi(parent)) + creamface.icon_state = "creampie_corgi" + else if(isAI(parent)) + creamface.icon_state = "creampie_ai" + + var/atom/A = parent + A.add_overlay(creamface) + +/datum/component/creamed/Destroy(force, silent) + var/atom/A = parent + A.cut_overlay(creamface) + qdel(creamface) + if(ishuman(A)) + SEND_SIGNAL(A, COMSIG_CLEAR_MOOD_EVENT, "creampie") + return ..() + +/datum/component/creamed/RegisterWithParent() + RegisterSignal(parent, list( + COMSIG_COMPONENT_CLEAN_ACT, + COMSIG_COMPONENT_CLEAN_FACE_ACT), + .proc/clean_up) + +/datum/component/creamed/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_COMPONENT_CLEAN_ACT, + COMSIG_COMPONENT_CLEAN_FACE_ACT)) + +///Callback to remove pieface +/datum/component/creamed/proc/clean_up(datum/source, strength) + if(strength >= CLEAN_WEAK) + qdel(src) diff --git a/code/datums/components/decals/blood.dm b/code/datums/components/decals/blood.dm index 7fae9756782..3114ddb24e9 100644 --- a/code/datums/components/decals/blood.dm +++ b/code/datums/components/decals/blood.dm @@ -1,39 +1,39 @@ -/datum/component/decal/blood - dupe_mode = COMPONENT_DUPE_UNIQUE - -/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER) - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - . = ..() - RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name) - -/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color) - var/obj/item/I = parent - if(!_icon) - _icon = 'icons/effects/blood.dmi' - if(!_icon_state) - _icon_state = "itemblood" - var/icon = initial(I.icon) - var/icon_state = initial(I.icon_state) - if(!icon || !icon_state) - // It's something which takes on the look of other items, probably - icon = I.icon - icon_state = I.icon_state - var/static/list/blood_splatter_appearances = list() - //try to find a pre-processed blood-splatter. otherwise, make a new one - var/index = "[REF(icon)]-[icon_state]" - pic = blood_splatter_appearances[index] - - if(!pic) - var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object - blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) - blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant - pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state)) - blood_splatter_appearances[index] = pic - return TRUE - -/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override) - var/atom/A = parent - override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a" - override[EXAMINE_POSITION_BEFORE] = " blood-stained " - return COMPONENT_EXNAME_CHANGED +/datum/component/decal/blood + dupe_mode = COMPONENT_DUPE_UNIQUE + +/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name) + +/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color) + var/obj/item/I = parent + if(!_icon) + _icon = 'icons/effects/blood.dmi' + if(!_icon_state) + _icon_state = "itemblood" + var/icon = initial(I.icon) + var/icon_state = initial(I.icon_state) + if(!icon || !icon_state) + // It's something which takes on the look of other items, probably + icon = I.icon + icon_state = I.icon_state + var/static/list/blood_splatter_appearances = list() + //try to find a pre-processed blood-splatter. otherwise, make a new one + var/index = "[REF(icon)]-[icon_state]" + pic = blood_splatter_appearances[index] + + if(!pic) + var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object + blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) + blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant + pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state)) + blood_splatter_appearances[index] = pic + return TRUE + +/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override) + var/atom/A = parent + override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a" + override[EXAMINE_POSITION_BEFORE] = " blood-stained " + return COMPONENT_EXNAME_CHANGED diff --git a/code/datums/components/edible.dm b/code/datums/components/edible.dm index fafcf03605b..2713136cf31 100644 --- a/code/datums/components/edible.dm +++ b/code/datums/components/edible.dm @@ -1,247 +1,247 @@ -/*! - -This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items). -These items take a specific time to eat, and can do most of the things our original food items could. - -Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else: - Components: - Drying component (jerky etc) - Customizable component (custom pizzas etc) - Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.) - Dunkable component (Dunking things into reagent containers to absorb a specific amount of reagents) - - Misc: - Something for cakes (You can store things inside) - -*/ -/datum/component/edible - ///Amount of reagents taken per bite - var/bite_consumption = 2 - ///Amount of bites taken so far - var/bitecount = 0 - ///Flags for food - var/food_flags = NONE - ///Bitfield of the types of this food - var/foodtypes = NONE - ///Amount of seconds it takes to eat this food - var/eat_time = 30 - ///Defines how much it lowers someones satiety (Need to eat, essentialy) - var/junkiness = 0 - ///Message to send when eating - var/list/eatverbs - ///Callback to be ran for when you take a bite of something - var/datum/callback/after_eat - ///Last time we checked for food likes - var/last_check_time - -/datum/component/edible/Initialize(list/initial_reagents, food_flags = NONE, foodtypes = NONE, volume = 50, eat_time = 30, list/tastes, list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"), bite_consumption = 2, datum/callback/after_eat) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - - RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, .proc/UseByAnimal) - if(isitem(parent)) - RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/UseFromHand) - else if(isturf(parent)) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/TryToEatTurf) - - src.bite_consumption = bite_consumption - src.food_flags = food_flags - src.foodtypes = foodtypes - src.eat_time = eat_time - src.eatverbs = eatverbs - src.junkiness = junkiness - src.after_eat = after_eat - - var/atom/owner = parent - - owner.create_reagents(volume, INJECTABLE) - - if(initial_reagents) - for(var/rid in initial_reagents) - var/amount = initial_reagents[rid] - if(tastes && tastes.len && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin)) - owner.reagents.add_reagent(rid, amount, tastes.Copy()) - else - owner.reagents.add_reagent(rid, amount) - -/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list) - if(!(food_flags & FOOD_IN_CONTAINER)) - switch (bitecount) - if (0) - return - if(1) - examine_list += "[parent] was bitten by someone!" - if(2,3) - examine_list += "[parent] was bitten [bitecount] times!" - else - examine_list += "[parent] was bitten multiple times!" - -/datum/component/edible/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user) - return TryToEat(M, user) - -/datum/component/edible/proc/TryToEatTurf(datum/source, mob/user) - return TryToEat(user, user) - -///All the checks for the act of eating itself and -/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder) - - set waitfor = FALSE - - var/atom/owner = parent - - if(feeder.a_intent == INTENT_HARM) - return - if(!owner.reagents.total_volume)//Shouldn't be needed but it checks to see if it has anything left in it. - to_chat(feeder, "None of [owner] left, oh no!") - if(isturf(parent)) - var/turf/T = parent - T.ScrapeAway(1, CHANGETURF_INHERIT_AIR) - else - qdel(parent) - return - if(!CanConsume(eater, feeder)) - return - var/fullness = eater.nutrition + 10 //The theoretical fullness of the person eating if they were to eat this - for(var/datum/reagent/consumable/C in eater.reagents.reagent_list) //we add the nutrition value of what we're currently digesting - fullness += C.nutriment_factor * C.volume / C.metabolization_rate - - . = COMPONENT_ITEM_NO_ATTACK //Point of no return I suppose - - if(eater == feeder)//If you're eating it yourself. - if(!do_mob(feeder, eater, eat_time)) //Gotta pass the minimal eat time - return - var/eatverb = pick(eatverbs) - if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS)) - to_chat(eater, "You don't feel like eating any more junk food at the moment!") - return - else if(fullness <= 50) - eater.visible_message("[eater] hungrily [eatverb]s \the [parent], gobbling it down!", "You hungrily [eatverb] \the [parent], gobbling it down!") - else if(fullness > 50 && fullness < 150) - eater.visible_message("[eater] hungrily [eatverb]s \the [parent].", "You hungrily [eatverb] \the [parent].") - else if(fullness > 150 && fullness < 500) - eater.visible_message("[eater] [eatverb]s \the [parent].", "You [eatverb] \the [parent].") - else if(fullness > 500 && fullness < 600) - eater.visible_message("[eater] unwillingly [eatverb]s a bit of \the [parent].", "You unwillingly [eatverb] a bit of \the [parent].") - else if(fullness > (600 * (1 + eater.overeatduration / 2000))) // The more you eat - the more you can eat - eater.visible_message("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!", "You cannot force any more of \the [parent] to go down your throat!") - return - else //If you're feeding it to someone else. - if(isbrain(eater)) - to_chat(feeder, "[eater] doesn't seem to have a mouth!") - return - if(fullness <= (600 * (1 + eater.overeatduration / 1000))) - eater.visible_message("[feeder] attempts to feed [eater] [parent].", \ - "[feeder] attempts to feed you [parent].") - else - eater.visible_message("[feeder] cannot force any more of [parent] down [eater]'s throat!", \ - "[feeder] cannot force any more of [parent] down your throat!") - return - if(!do_mob(feeder, eater)) //Wait 3 seconds before you can feed - return - - log_combat(feeder, eater, "fed", owner.reagents.log_list()) - eater.visible_message("[feeder] forces [eater] to eat [parent]!", \ - "[feeder] forces you to eat [parent]!") - - TakeBite(eater, feeder) - -///This function lets the eater take a bite and transfers the reagents to the eater. -/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder) - - var/atom/owner = parent - - if(!owner?.reagents) - return FALSE - if(eater.satiety > -200) - eater.satiety -= junkiness - playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE) - if(owner.reagents.total_volume) - SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder) - var/fraction = min(bite_consumption / owner.reagents.total_volume, 1) - owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, method = INGEST) - bitecount++ - On_Consume(eater) - checkLiked(fraction, eater) - - //Invoke our after eat callback if it is valid - if(after_eat) - after_eat.Invoke(eater, feeder) - - return TRUE - -///Checks whether or not the eater can actually consume the food -/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder) - if(!iscarbon(eater)) - return FALSE - var/mob/living/carbon/C = eater - var/covered = "" - if(C.is_mouth_covered(head_only = 1)) - covered = "headgear" - else if(C.is_mouth_covered(mask_only = 1)) - covered = "mask" - if(covered) - var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]" - to_chat(feeder, "You have to remove [who] [covered] first!") - return FALSE - return TRUE - -///Check foodtypes to see if we should send a moodlet -/datum/component/edible/proc/checkLiked(var/fraction, mob/M) - if(last_check_time + 50 > world.time) - return FALSE - if(!ishuman(M)) - return FALSE - var/mob/living/carbon/human/H = M - if(HAS_TRAIT(H, TRAIT_AGEUSIA) && foodtypes & H.dna.species.toxic_food) - to_chat(H, "You don't feel so good...") - H.adjust_disgust(25 + 30 * fraction) - else - if(foodtypes & H.dna.species.toxic_food) - to_chat(H,"What the hell was that thing?!") - H.adjust_disgust(25 + 30 * fraction) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food) - else if(foodtypes & H.dna.species.disliked_food) - to_chat(H,"That didn't taste very good...") - H.adjust_disgust(11 + 15 * fraction) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food) - else if(foodtypes & H.dna.species.liked_food) - to_chat(H,"I love this taste!") - H.adjust_disgust(-5 + -2.5 * fraction) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food) - if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast) - last_check_time = world.time - -///Delete the item when it is fully eaten -/datum/component/edible/proc/On_Consume(mob/living/eater) - - var/atom/owner = parent - - if(!eater) - return - if(!owner.reagents.total_volume) - if(isturf(parent)) - var/turf/T = parent - T.ScrapeAway(1, CHANGETURF_INHERIT_AIR) - else - qdel(parent) - -///Ability to feed food to puppers -/datum/component/edible/proc/UseByAnimal(datum/source, mob/user) - - var/atom/owner = parent - - if(!isdog(user)) - return - var/mob/living/L = user - if(bitecount == 0 || prob(50)) - L.emote("me", 1, "nibbles away at \the [parent]") - bitecount++ - . = COMPONENT_ITEM_NO_ATTACK - L.taste(owner.reagents) // why should carbons get all the fun? - if(bitecount >= 5) - var/sattisfaction_text = pick("burps from enjoyment", "yaps for more", "woofs twice", "looks at the area where \the [parent] was") - if(sattisfaction_text) - L.emote("me", 1, "[sattisfaction_text]") - qdel(parent) +/*! + +This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items). +These items take a specific time to eat, and can do most of the things our original food items could. + +Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else: + Components: + Drying component (jerky etc) + Customizable component (custom pizzas etc) + Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.) + Dunkable component (Dunking things into reagent containers to absorb a specific amount of reagents) + + Misc: + Something for cakes (You can store things inside) + +*/ +/datum/component/edible + ///Amount of reagents taken per bite + var/bite_consumption = 2 + ///Amount of bites taken so far + var/bitecount = 0 + ///Flags for food + var/food_flags = NONE + ///Bitfield of the types of this food + var/foodtypes = NONE + ///Amount of seconds it takes to eat this food + var/eat_time = 30 + ///Defines how much it lowers someones satiety (Need to eat, essentialy) + var/junkiness = 0 + ///Message to send when eating + var/list/eatverbs + ///Callback to be ran for when you take a bite of something + var/datum/callback/after_eat + ///Last time we checked for food likes + var/last_check_time + +/datum/component/edible/Initialize(list/initial_reagents, food_flags = NONE, foodtypes = NONE, volume = 50, eat_time = 30, list/tastes, list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"), bite_consumption = 2, datum/callback/after_eat) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, .proc/UseByAnimal) + if(isitem(parent)) + RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/UseFromHand) + else if(isturf(parent)) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/TryToEatTurf) + + src.bite_consumption = bite_consumption + src.food_flags = food_flags + src.foodtypes = foodtypes + src.eat_time = eat_time + src.eatverbs = eatverbs + src.junkiness = junkiness + src.after_eat = after_eat + + var/atom/owner = parent + + owner.create_reagents(volume, INJECTABLE) + + if(initial_reagents) + for(var/rid in initial_reagents) + var/amount = initial_reagents[rid] + if(tastes && tastes.len && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin)) + owner.reagents.add_reagent(rid, amount, tastes.Copy()) + else + owner.reagents.add_reagent(rid, amount) + +/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list) + if(!(food_flags & FOOD_IN_CONTAINER)) + switch (bitecount) + if (0) + return + if(1) + examine_list += "[parent] was bitten by someone!" + if(2,3) + examine_list += "[parent] was bitten [bitecount] times!" + else + examine_list += "[parent] was bitten multiple times!" + +/datum/component/edible/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user) + return TryToEat(M, user) + +/datum/component/edible/proc/TryToEatTurf(datum/source, mob/user) + return TryToEat(user, user) + +///All the checks for the act of eating itself and +/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder) + + set waitfor = FALSE + + var/atom/owner = parent + + if(feeder.a_intent == INTENT_HARM) + return + if(!owner.reagents.total_volume)//Shouldn't be needed but it checks to see if it has anything left in it. + to_chat(feeder, "None of [owner] left, oh no!") + if(isturf(parent)) + var/turf/T = parent + T.ScrapeAway(1, CHANGETURF_INHERIT_AIR) + else + qdel(parent) + return + if(!CanConsume(eater, feeder)) + return + var/fullness = eater.nutrition + 10 //The theoretical fullness of the person eating if they were to eat this + for(var/datum/reagent/consumable/C in eater.reagents.reagent_list) //we add the nutrition value of what we're currently digesting + fullness += C.nutriment_factor * C.volume / C.metabolization_rate + + . = COMPONENT_ITEM_NO_ATTACK //Point of no return I suppose + + if(eater == feeder)//If you're eating it yourself. + if(!do_mob(feeder, eater, eat_time)) //Gotta pass the minimal eat time + return + var/eatverb = pick(eatverbs) + if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS)) + to_chat(eater, "You don't feel like eating any more junk food at the moment!") + return + else if(fullness <= 50) + eater.visible_message("[eater] hungrily [eatverb]s \the [parent], gobbling it down!", "You hungrily [eatverb] \the [parent], gobbling it down!") + else if(fullness > 50 && fullness < 150) + eater.visible_message("[eater] hungrily [eatverb]s \the [parent].", "You hungrily [eatverb] \the [parent].") + else if(fullness > 150 && fullness < 500) + eater.visible_message("[eater] [eatverb]s \the [parent].", "You [eatverb] \the [parent].") + else if(fullness > 500 && fullness < 600) + eater.visible_message("[eater] unwillingly [eatverb]s a bit of \the [parent].", "You unwillingly [eatverb] a bit of \the [parent].") + else if(fullness > (600 * (1 + eater.overeatduration / 2000))) // The more you eat - the more you can eat + eater.visible_message("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!", "You cannot force any more of \the [parent] to go down your throat!") + return + else //If you're feeding it to someone else. + if(isbrain(eater)) + to_chat(feeder, "[eater] doesn't seem to have a mouth!") + return + if(fullness <= (600 * (1 + eater.overeatduration / 1000))) + eater.visible_message("[feeder] attempts to feed [eater] [parent].", \ + "[feeder] attempts to feed you [parent].") + else + eater.visible_message("[feeder] cannot force any more of [parent] down [eater]'s throat!", \ + "[feeder] cannot force any more of [parent] down your throat!") + return + if(!do_mob(feeder, eater)) //Wait 3 seconds before you can feed + return + + log_combat(feeder, eater, "fed", owner.reagents.log_list()) + eater.visible_message("[feeder] forces [eater] to eat [parent]!", \ + "[feeder] forces you to eat [parent]!") + + TakeBite(eater, feeder) + +///This function lets the eater take a bite and transfers the reagents to the eater. +/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder) + + var/atom/owner = parent + + if(!owner?.reagents) + return FALSE + if(eater.satiety > -200) + eater.satiety -= junkiness + playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE) + if(owner.reagents.total_volume) + SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder) + var/fraction = min(bite_consumption / owner.reagents.total_volume, 1) + owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, method = INGEST) + bitecount++ + On_Consume(eater) + checkLiked(fraction, eater) + + //Invoke our after eat callback if it is valid + if(after_eat) + after_eat.Invoke(eater, feeder) + + return TRUE + +///Checks whether or not the eater can actually consume the food +/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder) + if(!iscarbon(eater)) + return FALSE + var/mob/living/carbon/C = eater + var/covered = "" + if(C.is_mouth_covered(head_only = 1)) + covered = "headgear" + else if(C.is_mouth_covered(mask_only = 1)) + covered = "mask" + if(covered) + var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]" + to_chat(feeder, "You have to remove [who] [covered] first!") + return FALSE + return TRUE + +///Check foodtypes to see if we should send a moodlet +/datum/component/edible/proc/checkLiked(var/fraction, mob/M) + if(last_check_time + 50 > world.time) + return FALSE + if(!ishuman(M)) + return FALSE + var/mob/living/carbon/human/H = M + if(HAS_TRAIT(H, TRAIT_AGEUSIA) && foodtypes & H.dna.species.toxic_food) + to_chat(H, "You don't feel so good...") + H.adjust_disgust(25 + 30 * fraction) + else + if(foodtypes & H.dna.species.toxic_food) + to_chat(H,"What the hell was that thing?!") + H.adjust_disgust(25 + 30 * fraction) + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food) + else if(foodtypes & H.dna.species.disliked_food) + to_chat(H,"That didn't taste very good...") + H.adjust_disgust(11 + 15 * fraction) + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food) + else if(foodtypes & H.dna.species.liked_food) + to_chat(H,"I love this taste!") + H.adjust_disgust(-5 + -2.5 * fraction) + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food) + if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST) + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast) + last_check_time = world.time + +///Delete the item when it is fully eaten +/datum/component/edible/proc/On_Consume(mob/living/eater) + + var/atom/owner = parent + + if(!eater) + return + if(!owner.reagents.total_volume) + if(isturf(parent)) + var/turf/T = parent + T.ScrapeAway(1, CHANGETURF_INHERIT_AIR) + else + qdel(parent) + +///Ability to feed food to puppers +/datum/component/edible/proc/UseByAnimal(datum/source, mob/user) + + var/atom/owner = parent + + if(!isdog(user)) + return + var/mob/living/L = user + if(bitecount == 0 || prob(50)) + L.emote("me", 1, "nibbles away at \the [parent]") + bitecount++ + . = COMPONENT_ITEM_NO_ATTACK + L.taste(owner.reagents) // why should carbons get all the fun? + if(bitecount >= 5) + var/sattisfaction_text = pick("burps from enjoyment", "yaps for more", "woofs twice", "looks at the area where \the [parent] was") + if(sattisfaction_text) + L.emote("me", 1, "[sattisfaction_text]") + qdel(parent) diff --git a/code/datums/components/forensics.dm b/code/datums/components/forensics.dm index 3013d9c46e6..a037604533c 100644 --- a/code/datums/components/forensics.dm +++ b/code/datums/components/forensics.dm @@ -1,184 +1,184 @@ -/datum/component/forensics - dupe_mode = COMPONENT_DUPE_UNIQUE - can_transfer = TRUE - var/list/fingerprints //assoc print = print - var/list/hiddenprints //assoc ckey = realname/gloves/ckey - var/list/blood_DNA //assoc dna = bloodtype - var/list/fibers //assoc print = print - -/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL. - fingerprints = fingerprints | F.fingerprints - hiddenprints = hiddenprints | F.hiddenprints - blood_DNA = blood_DNA | F.blood_DNA - fibers = fibers | F.fibers - check_blood() - return ..() - -/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - fingerprints = new_fingerprints - hiddenprints = new_hiddenprints - blood_DNA = new_blood_DNA - fibers = new_fibers - check_blood() - -/datum/component/forensics/RegisterWithParent() - check_blood() - RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act) - -/datum/component/forensics/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT)) - -/datum/component/forensics/PostTransfer() - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - -/datum/component/forensics/proc/wipe_fingerprints() - fingerprints = null - return TRUE - -/datum/component/forensics/proc/wipe_hiddenprints() - return //no. - -/datum/component/forensics/proc/wipe_blood_DNA() - blood_DNA = null - if(isitem(parent)) - qdel(parent.GetComponent(/datum/component/decal/blood)) - return TRUE - -/datum/component/forensics/proc/wipe_fibers() - fibers = null - return TRUE - -/datum/component/forensics/proc/clean_act(datum/source, strength) - if(strength >= CLEAN_STRENGTH_FINGERPRINTS) - wipe_fingerprints() - if(strength >= CLEAN_STRENGTH_BLOOD) - wipe_blood_DNA() - if(strength >= CLEAN_STRENGTH_FIBERS) - wipe_fibers() - -/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text) - if(!length(_fingerprints)) - return - LAZYINITLIST(fingerprints) - for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. - fingerprints[i] = i - return TRUE - -/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) - if(!isliving(M)) - if(!iscameramob(M)) - return - if(isaicamera(M)) - var/mob/camera/ai_eye/ai_camera = M - if(!ai_camera.ai) - return - M = ai_camera.ai - add_hiddenprint(M) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - add_fibers(H) - if(H.gloves) //Check if the gloves (if any) hide fingerprints - var/obj/item/clothing/gloves/G = H.gloves - if(G.transfer_prints) - ignoregloves = TRUE - if(!ignoregloves) - H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop. - return - var/full_print = md5(H.dna.uni_identity) - LAZYSET(fingerprints, full_print, full_print) - return TRUE - -/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text) - if(!length(_fibertext)) - return - LAZYINITLIST(fibers) - for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours. - fibers[i] = i - return TRUE - -/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M) - var/fibertext - var/item_multiplier = isitem(src)?1.2:1 - if(M.wear_suit) - fibertext = "Material from \a [M.wear_suit]." - if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - if(!(M.wear_suit.body_parts_covered & CHEST)) - if(M.w_uniform) - fibertext = "Fibers from \a [M.w_uniform]." - if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed. - LAZYSET(fibers, fibertext, fibertext) - if(!(M.wear_suit.body_parts_covered & HANDS)) - if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - else if(M.w_uniform) - fibertext = "Fibers from \a [M.w_uniform]." - if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - // "Added fibertext: [fibertext]" - LAZYSET(fibers, fibertext, fibertext) - if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - else if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - return TRUE - -/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text) - if(!length(_hiddenprints)) - return - LAZYINITLIST(hiddenprints) - for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. - hiddenprints[i] = _hiddenprints[i] - return TRUE - -/datum/component/forensics/proc/add_hiddenprint(mob/M) - if(!isliving(M)) - if(!iscameramob(M)) - return - if(isaicamera(M)) - var/mob/camera/ai_eye/ai_camera = M - if(!ai_camera.ai) - return - M = ai_camera.ai - if(!M.key) - return - var/hasgloves = "" - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.gloves) - hasgloves = "(gloves)" - var/current_time = time_stamp() - if(!LAZYACCESS(hiddenprints, M.key)) - LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]") - else - var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ") - if(laststamppos) - LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos)) - hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else - var/atom/A = parent - A.fingerprintslast = M.ckey - return TRUE - -/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type) - if(!length(dna)) - return - LAZYINITLIST(blood_DNA) - for(var/i in dna) - blood_DNA[i] = dna[i] - check_blood() - return TRUE - -/datum/component/forensics/proc/check_blood() - if(!isitem(parent)) - return - if(!length(blood_DNA)) - return - parent.LoadComponent(/datum/component/decal/blood) +/datum/component/forensics + dupe_mode = COMPONENT_DUPE_UNIQUE + can_transfer = TRUE + var/list/fingerprints //assoc print = print + var/list/hiddenprints //assoc ckey = realname/gloves/ckey + var/list/blood_DNA //assoc dna = bloodtype + var/list/fibers //assoc print = print + +/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL. + fingerprints = fingerprints | F.fingerprints + hiddenprints = hiddenprints | F.hiddenprints + blood_DNA = blood_DNA | F.blood_DNA + fibers = fibers | F.fibers + check_blood() + return ..() + +/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + fingerprints = new_fingerprints + hiddenprints = new_hiddenprints + blood_DNA = new_blood_DNA + fibers = new_fibers + check_blood() + +/datum/component/forensics/RegisterWithParent() + check_blood() + RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act) + +/datum/component/forensics/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT)) + +/datum/component/forensics/PostTransfer() + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/forensics/proc/wipe_fingerprints() + fingerprints = null + return TRUE + +/datum/component/forensics/proc/wipe_hiddenprints() + return //no. + +/datum/component/forensics/proc/wipe_blood_DNA() + blood_DNA = null + if(isitem(parent)) + qdel(parent.GetComponent(/datum/component/decal/blood)) + return TRUE + +/datum/component/forensics/proc/wipe_fibers() + fibers = null + return TRUE + +/datum/component/forensics/proc/clean_act(datum/source, strength) + if(strength >= CLEAN_STRENGTH_FINGERPRINTS) + wipe_fingerprints() + if(strength >= CLEAN_STRENGTH_BLOOD) + wipe_blood_DNA() + if(strength >= CLEAN_STRENGTH_FIBERS) + wipe_fibers() + +/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text) + if(!length(_fingerprints)) + return + LAZYINITLIST(fingerprints) + for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. + fingerprints[i] = i + return TRUE + +/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) + if(!isliving(M)) + if(!iscameramob(M)) + return + if(isaicamera(M)) + var/mob/camera/ai_eye/ai_camera = M + if(!ai_camera.ai) + return + M = ai_camera.ai + add_hiddenprint(M) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + add_fibers(H) + if(H.gloves) //Check if the gloves (if any) hide fingerprints + var/obj/item/clothing/gloves/G = H.gloves + if(G.transfer_prints) + ignoregloves = TRUE + if(!ignoregloves) + H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop. + return + var/full_print = md5(H.dna.uni_identity) + LAZYSET(fingerprints, full_print, full_print) + return TRUE + +/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text) + if(!length(_fibertext)) + return + LAZYINITLIST(fibers) + for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours. + fibers[i] = i + return TRUE + +/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M) + var/fibertext + var/item_multiplier = isitem(src)?1.2:1 + if(M.wear_suit) + fibertext = "Material from \a [M.wear_suit]." + if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + if(!(M.wear_suit.body_parts_covered & CHEST)) + if(M.w_uniform) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed. + LAZYSET(fibers, fibertext, fibertext) + if(!(M.wear_suit.body_parts_covered & HANDS)) + if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + else if(M.w_uniform) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + // "Added fibertext: [fibertext]" + LAZYSET(fibers, fibertext, fibertext) + if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + else if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + return TRUE + +/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text) + if(!length(_hiddenprints)) + return + LAZYINITLIST(hiddenprints) + for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. + hiddenprints[i] = _hiddenprints[i] + return TRUE + +/datum/component/forensics/proc/add_hiddenprint(mob/M) + if(!isliving(M)) + if(!iscameramob(M)) + return + if(isaicamera(M)) + var/mob/camera/ai_eye/ai_camera = M + if(!ai_camera.ai) + return + M = ai_camera.ai + if(!M.key) + return + var/hasgloves = "" + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.gloves) + hasgloves = "(gloves)" + var/current_time = time_stamp() + if(!LAZYACCESS(hiddenprints, M.key)) + LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]") + else + var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ") + if(laststamppos) + LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos)) + hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else + var/atom/A = parent + A.fingerprintslast = M.ckey + return TRUE + +/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type) + if(!length(dna)) + return + LAZYINITLIST(blood_DNA) + for(var/i in dna) + blood_DNA[i] = dna[i] + check_blood() + return TRUE + +/datum/component/forensics/proc/check_blood() + if(!isitem(parent)) + return + if(!length(blood_DNA)) + return + parent.LoadComponent(/datum/component/decal/blood) diff --git a/code/datums/components/jousting.dm b/code/datums/components/jousting.dm index cf2570cb950..deef1284944 100644 --- a/code/datums/components/jousting.dm +++ b/code/datums/components/jousting.dm @@ -1,74 +1,74 @@ -/datum/component/jousting - var/current_direction = NONE - var/max_tile_charge = 5 - var/min_tile_charge = 2 //tiles before this code gets into effect. - var/current_tile_charge = 0 - var/movement_reset_tolerance = 2 //deciseconds - var/unmounted_damage_boost_per_tile = 0 - var/unmounted_knockdown_chance_per_tile = 0 - var/unmounted_knockdown_time = 0 - var/mounted_damage_boost_per_tile = 2 - var/mounted_knockdown_chance_per_tile = 20 - var/mounted_knockdown_time = 20 - var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to. - var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever. - var/mob/current_holder - var/current_timerid - -/datum/component/jousting/Initialize() - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip) - RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop) - RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack) - -/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE) - current_holder = user - -/datum/component/jousting/proc/on_drop(datum/source, mob/user) - UnregisterSignal(user, COMSIG_MOVABLE_MOVED) - current_holder = null - current_direction = NONE - current_tile_charge = 0 - -/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user) - if(user != current_holder) - return - var/current = current_tile_charge - var/obj/item/I = parent - var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are. - if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge)) - return - var/turf/target_turf = get_step(user, current_direction) - if(target in range(1, target_turf)) - var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current - var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time) - var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current - var/sharp = I.get_sharpness() - var/msg - if(damage) - msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]" - target.apply_damage(damage, BRUTE, user.zone_selected, 0) - if(prob(knockdown_chance)) - msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]" - if(target_buckled) - target.buckled.unbuckle_mob(target) - target.Paralyze(knockdown_time) - if(length(msg)) - user.visible_message("[msg]!") - -/datum/component/jousting/proc/mob_move(datum/source, newloc, dir) - if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled)))) - return - if(dir != current_direction) - current_tile_charge = 0 - current_direction = dir - if(current_tile_charge < max_tile_charge) - current_tile_charge++ - if(current_timerid) - deltimer(current_timerid) - current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE) - -/datum/component/jousting/proc/reset_charge() - current_tile_charge = 0 +/datum/component/jousting + var/current_direction = NONE + var/max_tile_charge = 5 + var/min_tile_charge = 2 //tiles before this code gets into effect. + var/current_tile_charge = 0 + var/movement_reset_tolerance = 2 //deciseconds + var/unmounted_damage_boost_per_tile = 0 + var/unmounted_knockdown_chance_per_tile = 0 + var/unmounted_knockdown_time = 0 + var/mounted_damage_boost_per_tile = 2 + var/mounted_knockdown_chance_per_tile = 20 + var/mounted_knockdown_time = 20 + var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to. + var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever. + var/mob/current_holder + var/current_timerid + +/datum/component/jousting/Initialize() + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop) + RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack) + +/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE) + current_holder = user + +/datum/component/jousting/proc/on_drop(datum/source, mob/user) + UnregisterSignal(user, COMSIG_MOVABLE_MOVED) + current_holder = null + current_direction = NONE + current_tile_charge = 0 + +/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user) + if(user != current_holder) + return + var/current = current_tile_charge + var/obj/item/I = parent + var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are. + if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge)) + return + var/turf/target_turf = get_step(user, current_direction) + if(target in range(1, target_turf)) + var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current + var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time) + var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current + var/sharp = I.get_sharpness() + var/msg + if(damage) + msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]" + target.apply_damage(damage, BRUTE, user.zone_selected, 0) + if(prob(knockdown_chance)) + msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]" + if(target_buckled) + target.buckled.unbuckle_mob(target) + target.Paralyze(knockdown_time) + if(length(msg)) + user.visible_message("[msg]!") + +/datum/component/jousting/proc/mob_move(datum/source, newloc, dir) + if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled)))) + return + if(dir != current_direction) + current_tile_charge = 0 + current_direction = dir + if(current_tile_charge < max_tile_charge) + current_tile_charge++ + if(current_timerid) + deltimer(current_timerid) + current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE) + +/datum/component/jousting/proc/reset_charge() + current_tile_charge = 0 diff --git a/code/datums/components/lockon_aiming.dm b/code/datums/components/lockon_aiming.dm index c27b9ce0d60..af15ffe992a 100644 --- a/code/datums/components/lockon_aiming.dm +++ b/code/datums/components/lockon_aiming.dm @@ -1,214 +1,214 @@ -#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7 -#define LOCKON_IGNORE_RESULT "ignore_my_result" -#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT} - -/datum/component/lockon_aiming - dupe_mode = COMPONENT_DUPE_ALLOWED - var/lock_icon = 'icons/mob/cameramob.dmi' - var/lock_icon_state = "marker" - var/mutable_appearance/lock_appearance - var/list/image/lock_images - var/list/target_typecache - var/list/immune_weakrefs //list(weakref = TRUE) - var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious! - var/lock_amount = 1 - var/lock_cursor_range = 5 - var/list/locked_weakrefs - var/update_disabled = FALSE - var/current_ranging_id = 0 - var/list/last_location - var/datum/callback/on_lock - var/datum/callback/can_target_callback - -/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback) - if(!ismob(parent)) - return COMPONENT_INCOMPATIBLE - if(target_callback) - can_target_callback = target_callback - else - can_target_callback = CALLBACK(src, .proc/can_target) - if(range) - lock_cursor_range = range - if(typecache) - target_typecache = typecache - if(amount) - lock_amount = amount - immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want.. - if(immune) - for(var/i in immune) - if(isweakref(i)) - immune_weakrefs[i] = TRUE - else if(isatom(i)) - immune_weakrefs[WEAKREF(i)] = TRUE - if(when_locked) - on_lock = when_locked - if(icon) - lock_icon = icon - if(icon_state) - lock_icon_state = icon_state - generate_lock_visuals() - START_PROCESSING(SSfastprocess, src) - -/datum/component/lockon_aiming/Destroy() - clear_visuals() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/datum/component/lockon_aiming/proc/show_visuals() - LAZYINITLIST(lock_images) - var/mob/M = parent - if(!M.client) - return - for(var/i in locked_weakrefs) - var/datum/weakref/R = i - var/atom/A = R.resolve() - if(!A) - continue //It'll be cleared by processing. - var/image/I = new - I.appearance = lock_appearance - I.loc = A - M.client.images |= I - lock_images |= I - -/datum/component/lockon_aiming/proc/clear_visuals() - var/mob/M = parent - if(!M.client) - return - if(!lock_images) - return - for(var/i in lock_images) - M.client.images -= i - qdel(i) - lock_images.Cut() - -/datum/component/lockon_aiming/proc/refresh_visuals() - clear_visuals() - show_visuals() - -/datum/component/lockon_aiming/proc/generate_lock_visuals() - lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER) - -/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE) - LAZYCLEARLIST(locked_weakrefs) - if(refresh_vis) - refresh_visuals() - -/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE) - if(!A.weak_reference) - return - LAZYREMOVE(locked_weakrefs, A.weak_reference) - if(refresh_vis) - refresh_visuals() - -/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE) - LAZYOR(locked_weakrefs, WEAKREF(A)) - if(refresh_vis) - refresh_visuals() - -/datum/component/lockon_aiming/proc/add_immune_atom(atom/A) - var/datum/weakref/R = WEAKREF(A) - if(immune_weakrefs && (immune_weakrefs[R])) - return - LAZYSET(immune_weakrefs, R, TRUE) - -/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A) - if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list? - return - LAZYREMOVE(immune_weakrefs, A.weak_reference) - -/datum/component/lockon_aiming/process() - if(update_disabled) - return - if(!last_location) - return - var/changed = FALSE - for(var/i in locked_weakrefs) - var/datum/weakref/R = i - if(istype(R)) - var/atom/thing = R.resolve() - if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range)) - unlock(R) - changed = TRUE - else - unlock(R) - changed = TRUE - if(changed) - autolock() - -/datum/component/lockon_aiming/proc/autolock() - var/mob/M = parent - if(!M.client) - return FALSE - var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client) - var/turf/target = current.return_turf() - var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range) - if(targets == LOCKON_IGNORE_RESULT) - return - unlock_all(FALSE) - for(var/i in targets) - if(immune_weakrefs[WEAKREF(i)]) - continue - lock(i, FALSE) - refresh_visuals() - on_lock.Invoke(locked_weakrefs) - -/datum/component/lockon_aiming/proc/can_target(atom/A) - var/mob/M = A - return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)] - -/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range) - current_ranging_id++ - var/this_id = current_ranging_id - var/list/L = list() - var/turf/center = get_turf(T) - if(amount < 1 || range < 0 || !istype(center) || !islist(typecache)) - return - if(range == 0) - return typecache_filter_list(T.contents + T, typecache) - var/x = 0 - var/y = 0 - var/cd = 0 - while(cd <= range) - x = center.x - cd + 1 - y = center.y + cd - LOCKON_RANGING_BREAK_CHECK - for(x in x to center.x + cd) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - y = center.y + cd - 1 - x = center.x + cd - for(y in center.y - cd to y) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - y = center.y - cd - x = center.x + cd - 1 - for(x in center.x - cd to x) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - y = center.y - cd + 1 - x = center.x - cd - for(y in y to center.y + cd) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - cd++ - CHECK_TICK +#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7 +#define LOCKON_IGNORE_RESULT "ignore_my_result" +#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT} + +/datum/component/lockon_aiming + dupe_mode = COMPONENT_DUPE_ALLOWED + var/lock_icon = 'icons/mob/cameramob.dmi' + var/lock_icon_state = "marker" + var/mutable_appearance/lock_appearance + var/list/image/lock_images + var/list/target_typecache + var/list/immune_weakrefs //list(weakref = TRUE) + var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious! + var/lock_amount = 1 + var/lock_cursor_range = 5 + var/list/locked_weakrefs + var/update_disabled = FALSE + var/current_ranging_id = 0 + var/list/last_location + var/datum/callback/on_lock + var/datum/callback/can_target_callback + +/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback) + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + if(target_callback) + can_target_callback = target_callback + else + can_target_callback = CALLBACK(src, .proc/can_target) + if(range) + lock_cursor_range = range + if(typecache) + target_typecache = typecache + if(amount) + lock_amount = amount + immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want.. + if(immune) + for(var/i in immune) + if(isweakref(i)) + immune_weakrefs[i] = TRUE + else if(isatom(i)) + immune_weakrefs[WEAKREF(i)] = TRUE + if(when_locked) + on_lock = when_locked + if(icon) + lock_icon = icon + if(icon_state) + lock_icon_state = icon_state + generate_lock_visuals() + START_PROCESSING(SSfastprocess, src) + +/datum/component/lockon_aiming/Destroy() + clear_visuals() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/component/lockon_aiming/proc/show_visuals() + LAZYINITLIST(lock_images) + var/mob/M = parent + if(!M.client) + return + for(var/i in locked_weakrefs) + var/datum/weakref/R = i + var/atom/A = R.resolve() + if(!A) + continue //It'll be cleared by processing. + var/image/I = new + I.appearance = lock_appearance + I.loc = A + M.client.images |= I + lock_images |= I + +/datum/component/lockon_aiming/proc/clear_visuals() + var/mob/M = parent + if(!M.client) + return + if(!lock_images) + return + for(var/i in lock_images) + M.client.images -= i + qdel(i) + lock_images.Cut() + +/datum/component/lockon_aiming/proc/refresh_visuals() + clear_visuals() + show_visuals() + +/datum/component/lockon_aiming/proc/generate_lock_visuals() + lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER) + +/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE) + LAZYCLEARLIST(locked_weakrefs) + if(refresh_vis) + refresh_visuals() + +/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE) + if(!A.weak_reference) + return + LAZYREMOVE(locked_weakrefs, A.weak_reference) + if(refresh_vis) + refresh_visuals() + +/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE) + LAZYOR(locked_weakrefs, WEAKREF(A)) + if(refresh_vis) + refresh_visuals() + +/datum/component/lockon_aiming/proc/add_immune_atom(atom/A) + var/datum/weakref/R = WEAKREF(A) + if(immune_weakrefs && (immune_weakrefs[R])) + return + LAZYSET(immune_weakrefs, R, TRUE) + +/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A) + if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list? + return + LAZYREMOVE(immune_weakrefs, A.weak_reference) + +/datum/component/lockon_aiming/process() + if(update_disabled) + return + if(!last_location) + return + var/changed = FALSE + for(var/i in locked_weakrefs) + var/datum/weakref/R = i + if(istype(R)) + var/atom/thing = R.resolve() + if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range)) + unlock(R) + changed = TRUE + else + unlock(R) + changed = TRUE + if(changed) + autolock() + +/datum/component/lockon_aiming/proc/autolock() + var/mob/M = parent + if(!M.client) + return FALSE + var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client) + var/turf/target = current.return_turf() + var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range) + if(targets == LOCKON_IGNORE_RESULT) + return + unlock_all(FALSE) + for(var/i in targets) + if(immune_weakrefs[WEAKREF(i)]) + continue + lock(i, FALSE) + refresh_visuals() + on_lock.Invoke(locked_weakrefs) + +/datum/component/lockon_aiming/proc/can_target(atom/A) + var/mob/M = A + return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)] + +/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range) + current_ranging_id++ + var/this_id = current_ranging_id + var/list/L = list() + var/turf/center = get_turf(T) + if(amount < 1 || range < 0 || !istype(center) || !islist(typecache)) + return + if(range == 0) + return typecache_filter_list(T.contents + T, typecache) + var/x = 0 + var/y = 0 + var/cd = 0 + while(cd <= range) + x = center.x - cd + 1 + y = center.y + cd + LOCKON_RANGING_BREAK_CHECK + for(x in x to center.x + cd) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + y = center.y + cd - 1 + x = center.x + cd + for(y in center.y - cd to y) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + y = center.y - cd + x = center.x + cd - 1 + for(x in center.x - cd to x) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + y = center.y - cd + 1 + x = center.x - cd + for(y in y to center.y + cd) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + cd++ + CHECK_TICK diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm index 15b5169bba3..d1efea19a32 100644 --- a/code/datums/components/nanites.dm +++ b/code/datums/components/nanites.dm @@ -1,381 +1,381 @@ -/datum/component/nanites - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - - var/mob/living/host_mob - var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs - var/max_nanites = 500 //maximum amount of nanites in the system - var/regen_rate = 0.5 //nanites generated per second - var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering - var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from - var/cloud_active = TRUE //if false, won't sync to the cloud - var/next_sync = 0 - var/list/datum/nanite_program/programs = list() - var/max_programs = NANITE_PROGRAM_LIMIT - - var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts - var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host - var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans - var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners - -/datum/component/nanites/Initialize(amount = 100, cloud = 0) - if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) - return COMPONENT_INCOMPATIBLE - - nanite_volume = amount - cloud_id = cloud - - //Nanites without hosts are non-interactive through normal means - if(isliving(parent)) - host_mob = parent - - if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow. - return COMPONENT_INCOMPATIBLE - - start_time = world.time - - host_mob.hud_set_nanite_indicator() - START_PROCESSING(SSnanites, src) - - if(cloud_id && cloud_active) - cloud_sync() - -/datum/component/nanites/RegisterWithParent() - RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) - RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth) - RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites) - RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) - RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) - RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume) - RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites) - RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume) - RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud) - RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync) - RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety) - RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen) - RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) - RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) - RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) - - if(isliving(parent)) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) - RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death) - RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access) - RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) - RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) - RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) - RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) - RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal) - -/datum/component/nanites/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_HAS_NANITES, - COMSIG_NANITE_IS_STEALTHY, - COMSIG_NANITE_DELETE, - COMSIG_NANITE_UI_DATA, - COMSIG_NANITE_GET_PROGRAMS, - COMSIG_NANITE_SET_VOLUME, - COMSIG_NANITE_ADJUST_VOLUME, - COMSIG_NANITE_SET_MAX_VOLUME, - COMSIG_NANITE_SET_CLOUD, - COMSIG_NANITE_SET_CLOUD_SYNC, - COMSIG_NANITE_SET_SAFETY, - COMSIG_NANITE_SET_REGEN, - COMSIG_NANITE_ADD_PROGRAM, - COMSIG_NANITE_SCAN, - COMSIG_NANITE_SYNC, - COMSIG_ATOM_EMP_ACT, - COMSIG_MOB_DEATH, - COMSIG_MOB_ALLOWED, - COMSIG_LIVING_ELECTROCUTE_ACT, - COMSIG_LIVING_MINOR_SHOCK, - COMSIG_MOVABLE_HEAR, - COMSIG_SPECIES_GAIN, - COMSIG_NANITE_SIGNAL, - COMSIG_NANITE_COMM_SIGNAL)) - -/datum/component/nanites/Destroy() - STOP_PROCESSING(SSnanites, src) - QDEL_LIST(programs) - if(host_mob) - set_nanite_bar(TRUE) - host_mob.hud_set_nanite_indicator() - host_mob = null - return ..() - -/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud) - if(new_nanites) - adjust_nanites(null, new_nanites.nanite_volume) - else - adjust_nanites(null, amount) //just add to the nanite volume - -/datum/component/nanites/process() - if(!IS_IN_STASIS(host_mob)) - adjust_nanites(null, regen_rate) - add_research() - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_process() - if(cloud_id && cloud_active && world.time > next_sync) - cloud_sync() - next_sync = world.time + NANITE_SYNC_DELAY - set_nanite_bar() - - -/datum/component/nanites/proc/delete_nanites() - qdel(src) - -//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status) -/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE) - var/list/programs_to_remove = programs.Copy() - var/list/programs_to_add = source.programs.Copy() - for(var/X in programs) - var/datum/nanite_program/NP = X - for(var/Y in programs_to_add) - var/datum/nanite_program/SNP = Y - if(NP.type == SNP.type) - programs_to_remove -= NP - programs_to_add -= SNP - SNP.copy_programming(NP, copy_activation) - break - if(full_overwrite) - for(var/X in programs_to_remove) - qdel(X) - for(var/X in programs_to_add) - var/datum/nanite_program/SNP = X - add_program(null, SNP.copy()) - -/datum/component/nanites/proc/cloud_sync() - if(cloud_id) - var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id) - if(backup) - var/datum/component/nanites/cloud_copy = backup.nanites - if(cloud_copy) - sync(null, cloud_copy) - return - //Without cloud syncing nanites can accumulate errors and/or defects - if(prob(8) && programs.len) - var/datum/nanite_program/NP = pick(programs) - NP.software_error() - -/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program) - for(var/X in programs) - var/datum/nanite_program/NP = X - if(NP.unique && NP.type == new_program.type) - qdel(NP) - if(programs.len >= max_programs) - return COMPONENT_PROGRAM_NOT_INSTALLED - if(source_program) - source_program.copy_programming(new_program) - programs += new_program - new_program.on_add(src) - return COMPONENT_PROGRAM_INSTALLED - -/datum/component/nanites/proc/consume_nanites(amount, force = FALSE) - if(!force && safety_threshold && (nanite_volume - amount < safety_threshold)) - return FALSE - adjust_nanites(null, -amount) - return (nanite_volume > 0) - -/datum/component/nanites/proc/adjust_nanites(datum/source, amount) - nanite_volume = clamp(nanite_volume + amount, 0, max_nanites) - if(nanite_volume <= 0) //oops we ran out - qdel(src) - -/datum/component/nanites/proc/set_nanite_bar(remove = FALSE) - var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD] - var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir) - holder.pixel_y = I.Height() - world.icon_size - holder.icon_state = null - if(remove || stealth) - return //bye icon - var/nanite_percent = (nanite_volume / max_nanites) * 100 - nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100) - holder.icon_state = "nanites[nanite_percent]" - -/datum/component/nanites/proc/on_emp(datum/source, severity) - nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites - adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume - if(prob(40/severity)) - cloud_id = 0 - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_emp(severity) - - -/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE) - if(flags & SHOCK_ILLUSION || shock_damage < 1) - return - - if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host - nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites - adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_shock(shock_damage) - -/datum/component/nanites/proc/on_minor_shock(datum/source) - adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_minor_shock() - -/datum/component/nanites/proc/check_stealth(datum/source) - return stealth - -/datum/component/nanites/proc/on_death(datum/source, gibbed) - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_death(gibbed) - -/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source") - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.receive_signal(code, source) - -/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source") - for(var/X in programs) - if(istype(X, /datum/nanite_program/comm)) - var/datum/nanite_program/comm/NP = X - NP.receive_comm_signal(comm_code, comm_message, comm_source) - -/datum/component/nanites/proc/check_viable_biotype() - if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) - qdel(src) //bodytype no longer sustains nanites - -/datum/component/nanites/proc/check_access(datum/source, obj/O) - for(var/datum/nanite_program/access/access_program in programs) - if(access_program.activated) - return O.check_access_list(access_program.access) - else - return FALSE - return FALSE - -/datum/component/nanites/proc/set_volume(datum/source, amount) - nanite_volume = clamp(amount, 0, max_nanites) - -/datum/component/nanites/proc/set_max_volume(datum/source, amount) - max_nanites = max(1, max_nanites) - -/datum/component/nanites/proc/set_cloud(datum/source, amount) - cloud_id = clamp(amount, 0, 100) - -/datum/component/nanites/proc/set_cloud_sync(datum/source, method) - switch(method) - if(NANITE_CLOUD_TOGGLE) - cloud_active = !cloud_active - if(NANITE_CLOUD_DISABLE) - cloud_active = FALSE - if(NANITE_CLOUD_ENABLE) - cloud_active = TRUE - -/datum/component/nanites/proc/set_safety(datum/source, amount) - safety_threshold = clamp(amount, 0, max_nanites) - -/datum/component/nanites/proc/set_regen(datum/source, amount) - regen_rate = amount - -/datum/component/nanites/proc/confirm_nanites() - return TRUE //yup i exist - -/datum/component/nanites/proc/get_data(list/nanite_data) - nanite_data["nanite_volume"] = nanite_volume - nanite_data["max_nanites"] = max_nanites - nanite_data["cloud_id"] = cloud_id - nanite_data["regen_rate"] = regen_rate - nanite_data["safety_threshold"] = safety_threshold - nanite_data["stealth"] = stealth - -/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) - nanite_programs |= programs - -/datum/component/nanites/proc/add_research() - var/research_value = NANITE_BASE_RESEARCH - if(!ishuman(host_mob)) - if(!iscarbon(host_mob)) - research_value *= 0.4 - else - research_value *= 0.8 - if(!host_mob.client) - research_value *= 0.5 - if(host_mob.stat == DEAD) - research_value *= 0.75 - SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value)) - -/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) - if(!full_scan) - if(!stealth) - to_chat(user, "Nanites Detected") - to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") - return TRUE - else - to_chat(user, "NANITES DETECTED") - to_chat(user, "================") - to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") - to_chat(user, "Safety Threshold: [safety_threshold]") - to_chat(user, "Cloud ID: [cloud_id ? cloud_id : "None"]") - to_chat(user, "Cloud Sync: [cloud_active ? "Active" : "Disabled"]") - to_chat(user, "================") - to_chat(user, "Program List:") - if(!diagnostics) - to_chat(user, "Diagnostics Disabled") - else - for(var/X in programs) - var/datum/nanite_program/NP = X - to_chat(user, "[NP.name] | [NP.activated ? "Active" : "Inactive"]") - return TRUE - -/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level) - data["has_nanites"] = TRUE - data["nanite_volume"] = nanite_volume - data["regen_rate"] = regen_rate - data["safety_threshold"] = safety_threshold - data["cloud_id"] = cloud_id - data["cloud_active"] = cloud_active - var/list/mob_programs = list() - var/id = 1 - for(var/X in programs) - var/datum/nanite_program/P = X - var/list/mob_program = list() - mob_program["name"] = P.name - mob_program["desc"] = P.desc - mob_program["id"] = id - - if(scan_level >= 2) - mob_program["activated"] = P.activated - mob_program["use_rate"] = P.use_rate - mob_program["can_trigger"] = P.can_trigger - mob_program["trigger_cost"] = P.trigger_cost - mob_program["trigger_cooldown"] = P.trigger_cooldown / 10 - - if(scan_level >= 3) - mob_program["timer_restart"] = P.timer_restart / 10 - mob_program["timer_shutdown"] = P.timer_shutdown / 10 - mob_program["timer_trigger"] = P.timer_trigger / 10 - mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10 - var/list/extra_settings = P.get_extra_settings_frontend() - mob_program["extra_settings"] = extra_settings - if(LAZYLEN(extra_settings)) - mob_program["has_extra_settings"] = TRUE - else - mob_program["has_extra_settings"] = FALSE - - if(scan_level >= 4) - mob_program["activation_code"] = P.activation_code - mob_program["deactivation_code"] = P.deactivation_code - mob_program["kill_code"] = P.kill_code - mob_program["trigger_code"] = P.trigger_code - var/list/rules = list() - var/rule_id = 1 - for(var/Z in P.rules) - var/datum/nanite_rule/nanite_rule = Z - var/list/rule = list() - rule["display"] = nanite_rule.display() - rule["program_id"] = id - rule["id"] = rule_id - rules += list(rule) - rule_id++ - mob_program["rules"] = rules - if(LAZYLEN(rules)) - mob_program["has_rules"] = TRUE - id++ - mob_programs += list(mob_program) - data["mob_programs"] = mob_programs +/datum/component/nanites + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + + var/mob/living/host_mob + var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs + var/max_nanites = 500 //maximum amount of nanites in the system + var/regen_rate = 0.5 //nanites generated per second + var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering + var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from + var/cloud_active = TRUE //if false, won't sync to the cloud + var/next_sync = 0 + var/list/datum/nanite_program/programs = list() + var/max_programs = NANITE_PROGRAM_LIMIT + + var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts + var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host + var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans + var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners + +/datum/component/nanites/Initialize(amount = 100, cloud = 0) + if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) + return COMPONENT_INCOMPATIBLE + + nanite_volume = amount + cloud_id = cloud + + //Nanites without hosts are non-interactive through normal means + if(isliving(parent)) + host_mob = parent + + if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow. + return COMPONENT_INCOMPATIBLE + + start_time = world.time + + host_mob.hud_set_nanite_indicator() + START_PROCESSING(SSnanites, src) + + if(cloud_id && cloud_active) + cloud_sync() + +/datum/component/nanites/RegisterWithParent() + RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) + RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth) + RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites) + RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) + RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) + RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume) + RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites) + RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync) + RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety) + RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen) + RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) + RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) + RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) + + if(isliving(parent)) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) + RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death) + RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access) + RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) + RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) + RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) + RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) + RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal) + +/datum/component/nanites/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_HAS_NANITES, + COMSIG_NANITE_IS_STEALTHY, + COMSIG_NANITE_DELETE, + COMSIG_NANITE_UI_DATA, + COMSIG_NANITE_GET_PROGRAMS, + COMSIG_NANITE_SET_VOLUME, + COMSIG_NANITE_ADJUST_VOLUME, + COMSIG_NANITE_SET_MAX_VOLUME, + COMSIG_NANITE_SET_CLOUD, + COMSIG_NANITE_SET_CLOUD_SYNC, + COMSIG_NANITE_SET_SAFETY, + COMSIG_NANITE_SET_REGEN, + COMSIG_NANITE_ADD_PROGRAM, + COMSIG_NANITE_SCAN, + COMSIG_NANITE_SYNC, + COMSIG_ATOM_EMP_ACT, + COMSIG_MOB_DEATH, + COMSIG_MOB_ALLOWED, + COMSIG_LIVING_ELECTROCUTE_ACT, + COMSIG_LIVING_MINOR_SHOCK, + COMSIG_MOVABLE_HEAR, + COMSIG_SPECIES_GAIN, + COMSIG_NANITE_SIGNAL, + COMSIG_NANITE_COMM_SIGNAL)) + +/datum/component/nanites/Destroy() + STOP_PROCESSING(SSnanites, src) + QDEL_LIST(programs) + if(host_mob) + set_nanite_bar(TRUE) + host_mob.hud_set_nanite_indicator() + host_mob = null + return ..() + +/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud) + if(new_nanites) + adjust_nanites(null, new_nanites.nanite_volume) + else + adjust_nanites(null, amount) //just add to the nanite volume + +/datum/component/nanites/process() + if(!IS_IN_STASIS(host_mob)) + adjust_nanites(null, regen_rate) + add_research() + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_process() + if(cloud_id && cloud_active && world.time > next_sync) + cloud_sync() + next_sync = world.time + NANITE_SYNC_DELAY + set_nanite_bar() + + +/datum/component/nanites/proc/delete_nanites() + qdel(src) + +//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status) +/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE) + var/list/programs_to_remove = programs.Copy() + var/list/programs_to_add = source.programs.Copy() + for(var/X in programs) + var/datum/nanite_program/NP = X + for(var/Y in programs_to_add) + var/datum/nanite_program/SNP = Y + if(NP.type == SNP.type) + programs_to_remove -= NP + programs_to_add -= SNP + SNP.copy_programming(NP, copy_activation) + break + if(full_overwrite) + for(var/X in programs_to_remove) + qdel(X) + for(var/X in programs_to_add) + var/datum/nanite_program/SNP = X + add_program(null, SNP.copy()) + +/datum/component/nanites/proc/cloud_sync() + if(cloud_id) + var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id) + if(backup) + var/datum/component/nanites/cloud_copy = backup.nanites + if(cloud_copy) + sync(null, cloud_copy) + return + //Without cloud syncing nanites can accumulate errors and/or defects + if(prob(8) && programs.len) + var/datum/nanite_program/NP = pick(programs) + NP.software_error() + +/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program) + for(var/X in programs) + var/datum/nanite_program/NP = X + if(NP.unique && NP.type == new_program.type) + qdel(NP) + if(programs.len >= max_programs) + return COMPONENT_PROGRAM_NOT_INSTALLED + if(source_program) + source_program.copy_programming(new_program) + programs += new_program + new_program.on_add(src) + return COMPONENT_PROGRAM_INSTALLED + +/datum/component/nanites/proc/consume_nanites(amount, force = FALSE) + if(!force && safety_threshold && (nanite_volume - amount < safety_threshold)) + return FALSE + adjust_nanites(null, -amount) + return (nanite_volume > 0) + +/datum/component/nanites/proc/adjust_nanites(datum/source, amount) + nanite_volume = clamp(nanite_volume + amount, 0, max_nanites) + if(nanite_volume <= 0) //oops we ran out + qdel(src) + +/datum/component/nanites/proc/set_nanite_bar(remove = FALSE) + var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD] + var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir) + holder.pixel_y = I.Height() - world.icon_size + holder.icon_state = null + if(remove || stealth) + return //bye icon + var/nanite_percent = (nanite_volume / max_nanites) * 100 + nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100) + holder.icon_state = "nanites[nanite_percent]" + +/datum/component/nanites/proc/on_emp(datum/source, severity) + nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites + adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume + if(prob(40/severity)) + cloud_id = 0 + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_emp(severity) + + +/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE) + if(flags & SHOCK_ILLUSION || shock_damage < 1) + return + + if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host + nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites + adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_shock(shock_damage) + +/datum/component/nanites/proc/on_minor_shock(datum/source) + adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_minor_shock() + +/datum/component/nanites/proc/check_stealth(datum/source) + return stealth + +/datum/component/nanites/proc/on_death(datum/source, gibbed) + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_death(gibbed) + +/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source") + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.receive_signal(code, source) + +/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source") + for(var/X in programs) + if(istype(X, /datum/nanite_program/comm)) + var/datum/nanite_program/comm/NP = X + NP.receive_comm_signal(comm_code, comm_message, comm_source) + +/datum/component/nanites/proc/check_viable_biotype() + if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) + qdel(src) //bodytype no longer sustains nanites + +/datum/component/nanites/proc/check_access(datum/source, obj/O) + for(var/datum/nanite_program/access/access_program in programs) + if(access_program.activated) + return O.check_access_list(access_program.access) + else + return FALSE + return FALSE + +/datum/component/nanites/proc/set_volume(datum/source, amount) + nanite_volume = clamp(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_max_volume(datum/source, amount) + max_nanites = max(1, max_nanites) + +/datum/component/nanites/proc/set_cloud(datum/source, amount) + cloud_id = clamp(amount, 0, 100) + +/datum/component/nanites/proc/set_cloud_sync(datum/source, method) + switch(method) + if(NANITE_CLOUD_TOGGLE) + cloud_active = !cloud_active + if(NANITE_CLOUD_DISABLE) + cloud_active = FALSE + if(NANITE_CLOUD_ENABLE) + cloud_active = TRUE + +/datum/component/nanites/proc/set_safety(datum/source, amount) + safety_threshold = clamp(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_regen(datum/source, amount) + regen_rate = amount + +/datum/component/nanites/proc/confirm_nanites() + return TRUE //yup i exist + +/datum/component/nanites/proc/get_data(list/nanite_data) + nanite_data["nanite_volume"] = nanite_volume + nanite_data["max_nanites"] = max_nanites + nanite_data["cloud_id"] = cloud_id + nanite_data["regen_rate"] = regen_rate + nanite_data["safety_threshold"] = safety_threshold + nanite_data["stealth"] = stealth + +/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) + nanite_programs |= programs + +/datum/component/nanites/proc/add_research() + var/research_value = NANITE_BASE_RESEARCH + if(!ishuman(host_mob)) + if(!iscarbon(host_mob)) + research_value *= 0.4 + else + research_value *= 0.8 + if(!host_mob.client) + research_value *= 0.5 + if(host_mob.stat == DEAD) + research_value *= 0.75 + SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value)) + +/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) + if(!full_scan) + if(!stealth) + to_chat(user, "Nanites Detected") + to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") + return TRUE + else + to_chat(user, "NANITES DETECTED") + to_chat(user, "================") + to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") + to_chat(user, "Safety Threshold: [safety_threshold]") + to_chat(user, "Cloud ID: [cloud_id ? cloud_id : "None"]") + to_chat(user, "Cloud Sync: [cloud_active ? "Active" : "Disabled"]") + to_chat(user, "================") + to_chat(user, "Program List:") + if(!diagnostics) + to_chat(user, "Diagnostics Disabled") + else + for(var/X in programs) + var/datum/nanite_program/NP = X + to_chat(user, "[NP.name] | [NP.activated ? "Active" : "Inactive"]") + return TRUE + +/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level) + data["has_nanites"] = TRUE + data["nanite_volume"] = nanite_volume + data["regen_rate"] = regen_rate + data["safety_threshold"] = safety_threshold + data["cloud_id"] = cloud_id + data["cloud_active"] = cloud_active + var/list/mob_programs = list() + var/id = 1 + for(var/X in programs) + var/datum/nanite_program/P = X + var/list/mob_program = list() + mob_program["name"] = P.name + mob_program["desc"] = P.desc + mob_program["id"] = id + + if(scan_level >= 2) + mob_program["activated"] = P.activated + mob_program["use_rate"] = P.use_rate + mob_program["can_trigger"] = P.can_trigger + mob_program["trigger_cost"] = P.trigger_cost + mob_program["trigger_cooldown"] = P.trigger_cooldown / 10 + + if(scan_level >= 3) + mob_program["timer_restart"] = P.timer_restart / 10 + mob_program["timer_shutdown"] = P.timer_shutdown / 10 + mob_program["timer_trigger"] = P.timer_trigger / 10 + mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10 + var/list/extra_settings = P.get_extra_settings_frontend() + mob_program["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + mob_program["has_extra_settings"] = TRUE + else + mob_program["has_extra_settings"] = FALSE + + if(scan_level >= 4) + mob_program["activation_code"] = P.activation_code + mob_program["deactivation_code"] = P.deactivation_code + mob_program["kill_code"] = P.kill_code + mob_program["trigger_code"] = P.trigger_code + var/list/rules = list() + var/rule_id = 1 + for(var/Z in P.rules) + var/datum/nanite_rule/nanite_rule = Z + var/list/rule = list() + rule["display"] = nanite_rule.display() + rule["program_id"] = id + rule["id"] = rule_id + rules += list(rule) + rule_id++ + mob_program["rules"] = rules + if(LAZYLEN(rules)) + mob_program["has_rules"] = TRUE + id++ + mob_programs += list(mob_program) + data["mob_programs"] = mob_programs diff --git a/code/datums/components/ntnet_interface.dm b/code/datums/components/ntnet_interface.dm index 1d3fc1c7cd0..19fae545eb9 100644 --- a/code/datums/components/ntnet_interface.dm +++ b/code/datums/components/ntnet_interface.dm @@ -1,66 +1,66 @@ -//Thing meant for allowing datums and objects to access an NTnet network datum. -/datum/proc/ntnet_receive(datum/netdata/data) - return - -/datum/proc/ntnet_receive_broadcast(datum/netdata/data) - return - -/datum/proc/ntnet_send(datum/netdata/data, netid) - var/datum/component/ntnet_interface/NIC = GetComponent(/datum/component/ntnet_interface) - if(!NIC) - return FALSE - return NIC.__network_send(data, netid) - -/datum/component/ntnet_interface - var/hardware_id //text. this is the true ID. do not change this. stuff like ID forgery can be done manually. - var/network_name = "" //text - var/list/networks_connected_by_id = list() //id = datum/ntnet - var/differentiate_broadcast = TRUE //If false, broadcasts go to ntnet_receive. NOT RECOMMENDED. - -/datum/component/ntnet_interface/Initialize(force_name = "NTNet Device", autoconnect_station_network = TRUE) //Don't force ID unless you know what you're doing! - hardware_id = "[SSnetworks.get_next_HID()]" - network_name = force_name - if(!SSnetworks.register_interface(src)) - . = COMPONENT_INCOMPATIBLE - CRASH("Unable to register NTNet interface. Interface deleted.") - if(autoconnect_station_network) - register_connection(SSnetworks.station_network) - -/datum/component/ntnet_interface/Destroy() - unregister_all_connections() - SSnetworks.unregister_interface(src) - return ..() - -/datum/component/ntnet_interface/proc/__network_receive(datum/netdata/data) //Do not directly proccall! - SEND_SIGNAL(parent, COMSIG_COMPONENT_NTNET_RECEIVE, data) - if(differentiate_broadcast && data.broadcast) - parent.ntnet_receive_broadcast(data) - else - parent.ntnet_receive(data) - -/datum/component/ntnet_interface/proc/__network_send(datum/netdata/data, netid) //Do not directly proccall! - - if(netid) - if(networks_connected_by_id[netid]) - var/datum/ntnet/net = networks_connected_by_id[netid] - return net.process_data_transmit(src, data) - return FALSE - for(var/i in networks_connected_by_id) - var/datum/ntnet/net = networks_connected_by_id[i] - net.process_data_transmit(src, data) - return TRUE - -/datum/component/ntnet_interface/proc/register_connection(datum/ntnet/net) - if(net.interface_connect(src)) - networks_connected_by_id[net.network_id] = net - return TRUE - -/datum/component/ntnet_interface/proc/unregister_all_connections() - for(var/i in networks_connected_by_id) - unregister_connection(networks_connected_by_id[i]) - return TRUE - -/datum/component/ntnet_interface/proc/unregister_connection(datum/ntnet/net) - net.interface_disconnect(src) - networks_connected_by_id -= net.network_id - return TRUE +//Thing meant for allowing datums and objects to access an NTnet network datum. +/datum/proc/ntnet_receive(datum/netdata/data) + return + +/datum/proc/ntnet_receive_broadcast(datum/netdata/data) + return + +/datum/proc/ntnet_send(datum/netdata/data, netid) + var/datum/component/ntnet_interface/NIC = GetComponent(/datum/component/ntnet_interface) + if(!NIC) + return FALSE + return NIC.__network_send(data, netid) + +/datum/component/ntnet_interface + var/hardware_id //text. this is the true ID. do not change this. stuff like ID forgery can be done manually. + var/network_name = "" //text + var/list/networks_connected_by_id = list() //id = datum/ntnet + var/differentiate_broadcast = TRUE //If false, broadcasts go to ntnet_receive. NOT RECOMMENDED. + +/datum/component/ntnet_interface/Initialize(force_name = "NTNet Device", autoconnect_station_network = TRUE) //Don't force ID unless you know what you're doing! + hardware_id = "[SSnetworks.get_next_HID()]" + network_name = force_name + if(!SSnetworks.register_interface(src)) + . = COMPONENT_INCOMPATIBLE + CRASH("Unable to register NTNet interface. Interface deleted.") + if(autoconnect_station_network) + register_connection(SSnetworks.station_network) + +/datum/component/ntnet_interface/Destroy() + unregister_all_connections() + SSnetworks.unregister_interface(src) + return ..() + +/datum/component/ntnet_interface/proc/__network_receive(datum/netdata/data) //Do not directly proccall! + SEND_SIGNAL(parent, COMSIG_COMPONENT_NTNET_RECEIVE, data) + if(differentiate_broadcast && data.broadcast) + parent.ntnet_receive_broadcast(data) + else + parent.ntnet_receive(data) + +/datum/component/ntnet_interface/proc/__network_send(datum/netdata/data, netid) //Do not directly proccall! + + if(netid) + if(networks_connected_by_id[netid]) + var/datum/ntnet/net = networks_connected_by_id[netid] + return net.process_data_transmit(src, data) + return FALSE + for(var/i in networks_connected_by_id) + var/datum/ntnet/net = networks_connected_by_id[i] + net.process_data_transmit(src, data) + return TRUE + +/datum/component/ntnet_interface/proc/register_connection(datum/ntnet/net) + if(net.interface_connect(src)) + networks_connected_by_id[net.network_id] = net + return TRUE + +/datum/component/ntnet_interface/proc/unregister_all_connections() + for(var/i in networks_connected_by_id) + unregister_connection(networks_connected_by_id[i]) + return TRUE + +/datum/component/ntnet_interface/proc/unregister_connection(datum/ntnet/net) + net.interface_disconnect(src) + networks_connected_by_id -= net.network_id + return TRUE diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm index b4460a6bb56..6eb7f8ff5f6 100644 --- a/code/datums/components/riding.dm +++ b/code/datums/components/riding.dm @@ -1,395 +1,395 @@ -/datum/component/riding - var/last_vehicle_move = 0 //used for move delays - var/last_move_diagonal = FALSE - var/vehicle_move_delay = 2 //tick delay between movements, lower = faster, higher = slower - var/keytype - - var/slowed = FALSE - var/slowvalue = 1 - - var/list/riding_offsets = list() //position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one. - var/list/directional_vehicle_layers = list() //["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change. - var/list/directional_vehicle_offsets = list() //same as above but instead of layer you have a list(px, py) - var/list/allowed_turf_typecache - var/list/forbid_turf_typecache //allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence. - var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more. - var/override_allow_spacemove = FALSE - var/drive_verb = "drive" - var/ride_check_rider_incapacitated = FALSE - var/ride_check_rider_restrained = FALSE - var/ride_check_ridden_incapacitated = FALSE - var/ride_check_ridden_restrained = FALSE - - var/del_on_unbuckle_all = FALSE - - /// If the "vehicle" is a mob, respect MOBILITY_MOVE on said mob. - var/respect_mob_mobility = TRUE - -/datum/component/riding/Initialize() - if(!ismovable(parent)) - return COMPONENT_INCOMPATIBLE - RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle) - RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved) - -/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) - var/atom/movable/AM = parent - restore_position(M) - unequip_buckle_inhands(M) - if(del_on_unbuckle_all && !AM.has_buckled_mobs()) - qdel(src) - -/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) - handle_vehicle_offsets() - -/datum/component/riding/proc/handle_vehicle_layer() - var/atom/movable/AM = parent - var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER) - . = defaults["[AM.dir]"] - if(directional_vehicle_layers["[AM.dir]"]) - . = directional_vehicle_layers["[AM.dir]"] - if(isnull(.)) //you can set it to null to not change it. - . = AM.layer - AM.layer = . - -/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer) - directional_vehicle_layers["[dir]"] = layer - -/datum/component/riding/proc/vehicle_moved(datum/source) - var/atom/movable/AM = parent - for(var/i in AM.buckled_mobs) - ride_check(i) - handle_vehicle_offsets() - handle_vehicle_layer() - -/datum/component/riding/proc/ride_check(mob/living/M) - var/atom/movable/AM = parent - var/mob/AMM = AM - var/kick_us_off - if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(TRUE, TRUE))) - kick_us_off = TRUE - if(kick_us_off || (istype(AMM) && ((ride_check_ridden_restrained && AMM.restrained(TRUE)) || (ride_check_ridden_incapacitated && AMM.incapacitated(TRUE, TRUE))))) - M.visible_message("[M] falls off of [AM]!", \ - "You fall off of [AM]!") - AM.unbuckle_mob(M) - return TRUE - -/datum/component/riding/proc/force_dismount(mob/living/M, gentle = FALSE) - var/atom/movable/AM = parent - AM.unbuckle_mob(M) - if(isanimal(AM) || iscyborg(AM)) - var/turf/target = get_edge_target_turf(AM, AM.dir) - var/turf/targetm = get_step(get_turf(AM), AM.dir) - M.Move(targetm) - if(gentle) - M.visible_message("[M] is thrown clear of [AM]!", \ - "You're thrown clear of [AM]!") - M.throw_at(target, 8, 3, AM, gentle = TRUE) - else - M.visible_message("[M] is thrown violently from [AM]!", \ - "You're thrown violently from [AM]!") - M.throw_at(target, 14, 5, AM, gentle = FALSE) - M.Knockdown(3 SECONDS) - -/datum/component/riding/proc/handle_vehicle_offsets() - var/atom/movable/AM = parent - var/AM_dir = "[AM.dir]" - var/passindex = 0 - if(AM.has_buckled_mobs()) - for(var/m in AM.buckled_mobs) - passindex++ - var/mob/living/buckled_mob = m - var/list/offsets = get_offsets(passindex) - var/rider_dir = get_rider_dir(passindex) - buckled_mob.setDir(rider_dir) - dir_loop: - for(var/offsetdir in offsets) - if(offsetdir == AM_dir) - var/list/diroffsets = offsets[offsetdir] - buckled_mob.pixel_x = diroffsets[1] - if(diroffsets.len >= 2) - buckled_mob.pixel_y = diroffsets[2] - if(diroffsets.len == 3) - buckled_mob.layer = diroffsets[3] - break dir_loop - var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) - var/px = default_vehicle_pixel_offsets[AM_dir] - var/py = default_vehicle_pixel_offsets[AM_dir] - if(directional_vehicle_offsets[AM_dir]) - if(isnull(directional_vehicle_offsets[AM_dir])) - px = AM.pixel_x - py = AM.pixel_y - else - px = directional_vehicle_offsets[AM_dir][1] - py = directional_vehicle_offsets[AM_dir][2] - AM.pixel_x = px - AM.pixel_y = py - -/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y) - directional_vehicle_offsets["[dir]"] = list(x, y) - -//Override this to set your vehicle's various pixel offsets -/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer) - . = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) - if(riding_offsets["[pass_index]"]) - . = riding_offsets["[pass_index]"] - else if(riding_offsets["[RIDING_OFFSET_ALL]"]) - . = riding_offsets["[RIDING_OFFSET_ALL]"] - -/datum/component/riding/proc/set_riding_offsets(index, list/offsets) - if(!islist(offsets)) - return FALSE - riding_offsets["[index]"] = offsets - -//Override this to set the passengers/riders dir based on which passenger they are. -//ie: rider facing the vehicle's dir, but passenger 2 facing backwards, etc. -/datum/component/riding/proc/get_rider_dir(pass_index) - var/atom/movable/AM = parent - return AM.dir - -//KEYS -/datum/component/riding/proc/keycheck(mob/user) - return !keytype || user.is_holding_item_of_type(keytype) - -//BUCKLE HOOKS -/datum/component/riding/proc/restore_position(mob/living/buckled_mob) - if(buckled_mob) - buckled_mob.pixel_x = 0 - buckled_mob.pixel_y = 0 - if(buckled_mob.client) - buckled_mob.client.view_size.resetToDefault() - -//MOVEMENT -/datum/component/riding/proc/turf_check(turf/next, turf/current) - if(allowed_turf_typecache && !allowed_turf_typecache[next.type]) - return (allow_one_away_from_valid_turf && allowed_turf_typecache[current.type]) - else if(forbid_turf_typecache && forbid_turf_typecache[next.type]) - return (allow_one_away_from_valid_turf && !forbid_turf_typecache[current.type]) - return TRUE - -/datum/component/riding/proc/handle_ride(mob/user, direction) - var/atom/movable/AM = parent - if(user.incapacitated()) - Unbuckle(user) - return - - if(world.time < last_vehicle_move + ((last_move_diagonal? 2 : 1) * vehicle_move_delay)) - return - last_vehicle_move = world.time - - if(keycheck(user)) - var/turf/next = get_step(AM, direction) - var/turf/current = get_turf(AM) - if(!istype(next) || !istype(current)) - return //not happening. - if(!turf_check(next, current)) - to_chat(user, "Your \the [AM] can not go onto [next]!") - return - if(!Process_Spacemove(direction) || !isturf(AM.loc)) - return - if(isliving(AM) && respect_mob_mobility) - var/mob/living/M = AM - if(!(M.mobility_flags & MOBILITY_MOVE)) - return - step(AM, direction) - - if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally - last_move_diagonal = TRUE - else - last_move_diagonal = FALSE - - handle_vehicle_layer() - handle_vehicle_offsets() - else - to_chat(user, "You'll need a special item in one of your hands to [drive_verb] [AM].") - -/datum/component/riding/proc/Unbuckle(atom/movable/M) - addtimer(CALLBACK(parent, /atom/movable/.proc/unbuckle_mob, M), 0, TIMER_UNIQUE) - -/datum/component/riding/proc/Process_Spacemove(direction) - var/atom/movable/AM = parent - return override_allow_spacemove || AM.has_gravity() - -/datum/component/riding/proc/account_limbs(mob/living/M) - if(M.get_num_legs() < 2 && !slowed) - vehicle_move_delay = vehicle_move_delay + slowvalue - slowed = TRUE - else if(slowed) - vehicle_move_delay = vehicle_move_delay - slowvalue - slowed = FALSE - -///////Yes, I said humans. No, this won't end well...////////// -/datum/component/riding/human - del_on_unbuckle_all = TRUE - -/datum/component/riding/human/Initialize() - . = ..() - RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee) - -/datum/component/riding/human/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) - unequip_buckle_inhands(parent) - var/mob/living/carbon/human/H = parent - H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry) - . = ..() - -/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) - . = ..() - var/mob/living/carbon/human/H = parent - H.add_movespeed_modifier(/datum/movespeed_modifier/human_carry) - -/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target) - var/mob/living/carbon/human/H = parent - if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs)) - force_dismount(target) - -/datum/component/riding/human/handle_vehicle_layer() - var/atom/movable/AM = parent - if(AM.buckled_mobs && AM.buckled_mobs.len) - for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied - M.layer = MOB_LAYER - if(!AM.buckle_lying) - if(AM.dir == SOUTH) - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = OBJ_LAYER - else - if(AM.dir == NORTH) - AM.layer = OBJ_LAYER - else - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = MOB_LAYER - -/datum/component/riding/human/get_offsets(pass_index) - var/mob/living/carbon/human/H = parent - if(H.buckle_lying) - return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6)) - else - return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4)) - - -/datum/component/riding/human/force_dismount(mob/living/user) - var/atom/movable/AM = parent - AM.unbuckle_mob(user) - user.Paralyze(60) - user.visible_message("[AM] pushes [user] off of [AM.p_them()]!", \ - "[AM] pushes you off of [AM.p_them()]!") - -/datum/component/riding/cyborg - del_on_unbuckle_all = TRUE - -/datum/component/riding/cyborg/ride_check(mob/user) - var/atom/movable/AM = parent - if(user.incapacitated()) - var/kick = TRUE - if(iscyborg(AM)) - var/mob/living/silicon/robot/R = AM - if(R.module && R.module.ride_allow_incapacitated) - kick = FALSE - if(kick) - to_chat(user, "You fall off of [AM]!") - Unbuckle(user) - return - if(iscarbon(user)) - var/mob/living/carbon/carbonuser = user - if(!carbonuser.get_num_arms()) - Unbuckle(user) - to_chat(user, "You can't grab onto [AM] with no hands!") - return - -/datum/component/riding/cyborg/handle_vehicle_layer() - var/atom/movable/AM = parent - if(AM.buckled_mobs && AM.buckled_mobs.len) - if(AM.dir == SOUTH) - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = OBJ_LAYER - else - AM.layer = MOB_LAYER - -/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer) - return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3)) - -/datum/component/riding/cyborg/handle_vehicle_offsets() - var/atom/movable/AM = parent - if(AM.has_buckled_mobs()) - for(var/mob/living/M in AM.buckled_mobs) - M.setDir(AM.dir) - if(iscyborg(AM)) - var/mob/living/silicon/robot/R = AM - if(istype(R.module)) - M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)] - M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)] - else - ..() - -/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null) - var/atom/movable/AM = parent - var/amount_equipped = 0 - for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) - var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user) - if(!riding_target_override) - inhand.rider = user - else - inhand.rider = riding_target_override - inhand.parent = AM - for(var/obj/item/I in user.held_items) // yes i know this sucks but these are ABSTRACT++ dumbness and i'm not adding a whole new flag for these two meme items - if((I.obj_flags & HAND_ITEM)) - qdel(I) - if(user.put_in_hands(inhand, TRUE)) - amount_equipped++ - else - break - if(amount_equipped >= amount_required) - return TRUE - else - unequip_buckle_inhands(user) - return FALSE - -/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) - var/atom/movable/AM = parent - for(var/obj/item/riding_offhand/O in user.contents) - if(O.parent != AM) - CRASH("RIDING OFFHAND ON WRONG MOB") - if(O.selfdeleting) - continue - else - qdel(O) - return TRUE - -/obj/item/riding_offhand - name = "offhand" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "offhand" - w_class = WEIGHT_CLASS_HUGE - item_flags = ABSTRACT | DROPDEL | NOBLUDGEON - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/mob/living/carbon/rider - var/mob/living/parent - var/selfdeleting = FALSE - -/obj/item/riding_offhand/dropped() - selfdeleting = TRUE - . = ..() - -/obj/item/riding_offhand/equipped() - if(loc != rider && loc != parent) - selfdeleting = TRUE - qdel(src) - . = ..() - -/obj/item/riding_offhand/Destroy() - var/atom/movable/AM = parent - if(selfdeleting) - if(rider in AM.buckled_mobs) - AM.unbuckle_mob(rider) - . = ..() - -/obj/item/riding_offhand/on_thrown(mob/living/carbon/user, atom/target) - if(rider == user) - return //Piggyback user. - user.unbuckle_mob(rider) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You gently let go of [rider].") - return - return rider +/datum/component/riding + var/last_vehicle_move = 0 //used for move delays + var/last_move_diagonal = FALSE + var/vehicle_move_delay = 2 //tick delay between movements, lower = faster, higher = slower + var/keytype + + var/slowed = FALSE + var/slowvalue = 1 + + var/list/riding_offsets = list() //position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one. + var/list/directional_vehicle_layers = list() //["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change. + var/list/directional_vehicle_offsets = list() //same as above but instead of layer you have a list(px, py) + var/list/allowed_turf_typecache + var/list/forbid_turf_typecache //allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence. + var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more. + var/override_allow_spacemove = FALSE + var/drive_verb = "drive" + var/ride_check_rider_incapacitated = FALSE + var/ride_check_rider_restrained = FALSE + var/ride_check_ridden_incapacitated = FALSE + var/ride_check_ridden_restrained = FALSE + + var/del_on_unbuckle_all = FALSE + + /// If the "vehicle" is a mob, respect MOBILITY_MOVE on said mob. + var/respect_mob_mobility = TRUE + +/datum/component/riding/Initialize() + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle) + RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved) + +/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) + var/atom/movable/AM = parent + restore_position(M) + unequip_buckle_inhands(M) + if(del_on_unbuckle_all && !AM.has_buckled_mobs()) + qdel(src) + +/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) + handle_vehicle_offsets() + +/datum/component/riding/proc/handle_vehicle_layer() + var/atom/movable/AM = parent + var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER) + . = defaults["[AM.dir]"] + if(directional_vehicle_layers["[AM.dir]"]) + . = directional_vehicle_layers["[AM.dir]"] + if(isnull(.)) //you can set it to null to not change it. + . = AM.layer + AM.layer = . + +/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer) + directional_vehicle_layers["[dir]"] = layer + +/datum/component/riding/proc/vehicle_moved(datum/source) + var/atom/movable/AM = parent + for(var/i in AM.buckled_mobs) + ride_check(i) + handle_vehicle_offsets() + handle_vehicle_layer() + +/datum/component/riding/proc/ride_check(mob/living/M) + var/atom/movable/AM = parent + var/mob/AMM = AM + var/kick_us_off + if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(TRUE, TRUE))) + kick_us_off = TRUE + if(kick_us_off || (istype(AMM) && ((ride_check_ridden_restrained && AMM.restrained(TRUE)) || (ride_check_ridden_incapacitated && AMM.incapacitated(TRUE, TRUE))))) + M.visible_message("[M] falls off of [AM]!", \ + "You fall off of [AM]!") + AM.unbuckle_mob(M) + return TRUE + +/datum/component/riding/proc/force_dismount(mob/living/M, gentle = FALSE) + var/atom/movable/AM = parent + AM.unbuckle_mob(M) + if(isanimal(AM) || iscyborg(AM)) + var/turf/target = get_edge_target_turf(AM, AM.dir) + var/turf/targetm = get_step(get_turf(AM), AM.dir) + M.Move(targetm) + if(gentle) + M.visible_message("[M] is thrown clear of [AM]!", \ + "You're thrown clear of [AM]!") + M.throw_at(target, 8, 3, AM, gentle = TRUE) + else + M.visible_message("[M] is thrown violently from [AM]!", \ + "You're thrown violently from [AM]!") + M.throw_at(target, 14, 5, AM, gentle = FALSE) + M.Knockdown(3 SECONDS) + +/datum/component/riding/proc/handle_vehicle_offsets() + var/atom/movable/AM = parent + var/AM_dir = "[AM.dir]" + var/passindex = 0 + if(AM.has_buckled_mobs()) + for(var/m in AM.buckled_mobs) + passindex++ + var/mob/living/buckled_mob = m + var/list/offsets = get_offsets(passindex) + var/rider_dir = get_rider_dir(passindex) + buckled_mob.setDir(rider_dir) + dir_loop: + for(var/offsetdir in offsets) + if(offsetdir == AM_dir) + var/list/diroffsets = offsets[offsetdir] + buckled_mob.pixel_x = diroffsets[1] + if(diroffsets.len >= 2) + buckled_mob.pixel_y = diroffsets[2] + if(diroffsets.len == 3) + buckled_mob.layer = diroffsets[3] + break dir_loop + var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) + var/px = default_vehicle_pixel_offsets[AM_dir] + var/py = default_vehicle_pixel_offsets[AM_dir] + if(directional_vehicle_offsets[AM_dir]) + if(isnull(directional_vehicle_offsets[AM_dir])) + px = AM.pixel_x + py = AM.pixel_y + else + px = directional_vehicle_offsets[AM_dir][1] + py = directional_vehicle_offsets[AM_dir][2] + AM.pixel_x = px + AM.pixel_y = py + +/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y) + directional_vehicle_offsets["[dir]"] = list(x, y) + +//Override this to set your vehicle's various pixel offsets +/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer) + . = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) + if(riding_offsets["[pass_index]"]) + . = riding_offsets["[pass_index]"] + else if(riding_offsets["[RIDING_OFFSET_ALL]"]) + . = riding_offsets["[RIDING_OFFSET_ALL]"] + +/datum/component/riding/proc/set_riding_offsets(index, list/offsets) + if(!islist(offsets)) + return FALSE + riding_offsets["[index]"] = offsets + +//Override this to set the passengers/riders dir based on which passenger they are. +//ie: rider facing the vehicle's dir, but passenger 2 facing backwards, etc. +/datum/component/riding/proc/get_rider_dir(pass_index) + var/atom/movable/AM = parent + return AM.dir + +//KEYS +/datum/component/riding/proc/keycheck(mob/user) + return !keytype || user.is_holding_item_of_type(keytype) + +//BUCKLE HOOKS +/datum/component/riding/proc/restore_position(mob/living/buckled_mob) + if(buckled_mob) + buckled_mob.pixel_x = 0 + buckled_mob.pixel_y = 0 + if(buckled_mob.client) + buckled_mob.client.view_size.resetToDefault() + +//MOVEMENT +/datum/component/riding/proc/turf_check(turf/next, turf/current) + if(allowed_turf_typecache && !allowed_turf_typecache[next.type]) + return (allow_one_away_from_valid_turf && allowed_turf_typecache[current.type]) + else if(forbid_turf_typecache && forbid_turf_typecache[next.type]) + return (allow_one_away_from_valid_turf && !forbid_turf_typecache[current.type]) + return TRUE + +/datum/component/riding/proc/handle_ride(mob/user, direction) + var/atom/movable/AM = parent + if(user.incapacitated()) + Unbuckle(user) + return + + if(world.time < last_vehicle_move + ((last_move_diagonal? 2 : 1) * vehicle_move_delay)) + return + last_vehicle_move = world.time + + if(keycheck(user)) + var/turf/next = get_step(AM, direction) + var/turf/current = get_turf(AM) + if(!istype(next) || !istype(current)) + return //not happening. + if(!turf_check(next, current)) + to_chat(user, "Your \the [AM] can not go onto [next]!") + return + if(!Process_Spacemove(direction) || !isturf(AM.loc)) + return + if(isliving(AM) && respect_mob_mobility) + var/mob/living/M = AM + if(!(M.mobility_flags & MOBILITY_MOVE)) + return + step(AM, direction) + + if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally + last_move_diagonal = TRUE + else + last_move_diagonal = FALSE + + handle_vehicle_layer() + handle_vehicle_offsets() + else + to_chat(user, "You'll need a special item in one of your hands to [drive_verb] [AM].") + +/datum/component/riding/proc/Unbuckle(atom/movable/M) + addtimer(CALLBACK(parent, /atom/movable/.proc/unbuckle_mob, M), 0, TIMER_UNIQUE) + +/datum/component/riding/proc/Process_Spacemove(direction) + var/atom/movable/AM = parent + return override_allow_spacemove || AM.has_gravity() + +/datum/component/riding/proc/account_limbs(mob/living/M) + if(M.get_num_legs() < 2 && !slowed) + vehicle_move_delay = vehicle_move_delay + slowvalue + slowed = TRUE + else if(slowed) + vehicle_move_delay = vehicle_move_delay - slowvalue + slowed = FALSE + +///////Yes, I said humans. No, this won't end well...////////// +/datum/component/riding/human + del_on_unbuckle_all = TRUE + +/datum/component/riding/human/Initialize() + . = ..() + RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee) + +/datum/component/riding/human/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) + unequip_buckle_inhands(parent) + var/mob/living/carbon/human/H = parent + H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry) + . = ..() + +/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) + . = ..() + var/mob/living/carbon/human/H = parent + H.add_movespeed_modifier(/datum/movespeed_modifier/human_carry) + +/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target) + var/mob/living/carbon/human/H = parent + if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs)) + force_dismount(target) + +/datum/component/riding/human/handle_vehicle_layer() + var/atom/movable/AM = parent + if(AM.buckled_mobs && AM.buckled_mobs.len) + for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied + M.layer = MOB_LAYER + if(!AM.buckle_lying) + if(AM.dir == SOUTH) + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = OBJ_LAYER + else + if(AM.dir == NORTH) + AM.layer = OBJ_LAYER + else + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = MOB_LAYER + +/datum/component/riding/human/get_offsets(pass_index) + var/mob/living/carbon/human/H = parent + if(H.buckle_lying) + return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6)) + else + return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4)) + + +/datum/component/riding/human/force_dismount(mob/living/user) + var/atom/movable/AM = parent + AM.unbuckle_mob(user) + user.Paralyze(60) + user.visible_message("[AM] pushes [user] off of [AM.p_them()]!", \ + "[AM] pushes you off of [AM.p_them()]!") + +/datum/component/riding/cyborg + del_on_unbuckle_all = TRUE + +/datum/component/riding/cyborg/ride_check(mob/user) + var/atom/movable/AM = parent + if(user.incapacitated()) + var/kick = TRUE + if(iscyborg(AM)) + var/mob/living/silicon/robot/R = AM + if(R.module && R.module.ride_allow_incapacitated) + kick = FALSE + if(kick) + to_chat(user, "You fall off of [AM]!") + Unbuckle(user) + return + if(iscarbon(user)) + var/mob/living/carbon/carbonuser = user + if(!carbonuser.get_num_arms()) + Unbuckle(user) + to_chat(user, "You can't grab onto [AM] with no hands!") + return + +/datum/component/riding/cyborg/handle_vehicle_layer() + var/atom/movable/AM = parent + if(AM.buckled_mobs && AM.buckled_mobs.len) + if(AM.dir == SOUTH) + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = OBJ_LAYER + else + AM.layer = MOB_LAYER + +/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer) + return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3)) + +/datum/component/riding/cyborg/handle_vehicle_offsets() + var/atom/movable/AM = parent + if(AM.has_buckled_mobs()) + for(var/mob/living/M in AM.buckled_mobs) + M.setDir(AM.dir) + if(iscyborg(AM)) + var/mob/living/silicon/robot/R = AM + if(istype(R.module)) + M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)] + M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)] + else + ..() + +/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null) + var/atom/movable/AM = parent + var/amount_equipped = 0 + for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) + var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user) + if(!riding_target_override) + inhand.rider = user + else + inhand.rider = riding_target_override + inhand.parent = AM + for(var/obj/item/I in user.held_items) // yes i know this sucks but these are ABSTRACT++ dumbness and i'm not adding a whole new flag for these two meme items + if((I.obj_flags & HAND_ITEM)) + qdel(I) + if(user.put_in_hands(inhand, TRUE)) + amount_equipped++ + else + break + if(amount_equipped >= amount_required) + return TRUE + else + unequip_buckle_inhands(user) + return FALSE + +/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) + var/atom/movable/AM = parent + for(var/obj/item/riding_offhand/O in user.contents) + if(O.parent != AM) + CRASH("RIDING OFFHAND ON WRONG MOB") + if(O.selfdeleting) + continue + else + qdel(O) + return TRUE + +/obj/item/riding_offhand + name = "offhand" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "offhand" + w_class = WEIGHT_CLASS_HUGE + item_flags = ABSTRACT | DROPDEL | NOBLUDGEON + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/mob/living/carbon/rider + var/mob/living/parent + var/selfdeleting = FALSE + +/obj/item/riding_offhand/dropped() + selfdeleting = TRUE + . = ..() + +/obj/item/riding_offhand/equipped() + if(loc != rider && loc != parent) + selfdeleting = TRUE + qdel(src) + . = ..() + +/obj/item/riding_offhand/Destroy() + var/atom/movable/AM = parent + if(selfdeleting) + if(rider in AM.buckled_mobs) + AM.unbuckle_mob(rider) + . = ..() + +/obj/item/riding_offhand/on_thrown(mob/living/carbon/user, atom/target) + if(rider == user) + return //Piggyback user. + user.unbuckle_mob(rider) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You gently let go of [rider].") + return + return rider diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm index 130f26a0504..541fe474292 100644 --- a/code/datums/components/slippery.dm +++ b/code/datums/components/slippery.dm @@ -1,36 +1,36 @@ -/datum/component/slippery - var/force_drop_items = FALSE - var/knockdown_time = 0 - var/paralyze_time = 0 - var/lube_flags - var/datum/callback/callback - -/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE) - knockdown_time = max(_knockdown, 0) - paralyze_time = max(_paralyze, 0) - force_drop_items = _force_drop - lube_flags = _lube_flags - callback = _callback - RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) - RegisterSignal(parent, COMSIG_ITEM_WEARERCROSSED, .proc/Slip_on_wearer) - -/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) - var/mob/victim = AM - if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) - callback.Invoke(victim) - - -/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) - if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) - Slip(source, AM) - -/datum/component/slippery/clowning //used for making the clown PDA only slip if the clown is wearing his shoes and the elusive banana-skin belt - -/datum/component/slippery/clowning/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) - var/obj/item/I = crossed.get_item_by_slot(ITEM_SLOT_FEET) - if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) - if(istype(I, /obj/item/clothing/shoes/clown_shoes)) - Slip(source, AM) - else - to_chat(crossed,"[parent] failed to slip anyone. Perhaps I shouldn't have abandoned my legacy...") - +/datum/component/slippery + var/force_drop_items = FALSE + var/knockdown_time = 0 + var/paralyze_time = 0 + var/lube_flags + var/datum/callback/callback + +/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE) + knockdown_time = max(_knockdown, 0) + paralyze_time = max(_paralyze, 0) + force_drop_items = _force_drop + lube_flags = _lube_flags + callback = _callback + RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) + RegisterSignal(parent, COMSIG_ITEM_WEARERCROSSED, .proc/Slip_on_wearer) + +/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) + var/mob/victim = AM + if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) + callback.Invoke(victim) + + +/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) + if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) + Slip(source, AM) + +/datum/component/slippery/clowning //used for making the clown PDA only slip if the clown is wearing his shoes and the elusive banana-skin belt + +/datum/component/slippery/clowning/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) + var/obj/item/I = crossed.get_item_by_slot(ITEM_SLOT_FEET) + if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) + if(istype(I, /obj/item/clothing/shoes/clown_shoes)) + Slip(source, AM) + else + to_chat(crossed,"[parent] failed to slip anyone. Perhaps I shouldn't have abandoned my legacy...") + diff --git a/code/datums/components/spill.dm b/code/datums/components/spill.dm index 29cc844780e..60bfa12402e 100644 --- a/code/datums/components/spill.dm +++ b/code/datums/components/spill.dm @@ -1,57 +1,57 @@ -// This component is for forcing strange things into your pocket that fall out if you fall down -// Yes this exists purely for the spaghetti meme - -/datum/component/spill - can_transfer = TRUE - var/preexisting_item_flags - - var/list/droptext - var/list/dropsound - -// droptext is an arglist for visible_message -// dropsound is a list of potential sounds that gets picked from -/datum/component/spill/Initialize(list/_droptext, list/_dropsound) - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - - if(_droptext && !islist(_droptext)) - _droptext = list(_droptext) - droptext = _droptext - - if(_dropsound && !islist(_dropsound)) - _dropsound = list(_dropsound) - dropsound = _dropsound - -/datum/component/spill/PostTransfer() - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - -/datum/component/spill/RegisterWithParent() - RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react) - RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react) - var/obj/item/master = parent - preexisting_item_flags = master.item_flags - master.item_flags |= ITEM_SLOT_POCKETS - -/datum/component/spill/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) - var/obj/item/master = parent - if(!(preexisting_item_flags & ITEM_SLOT_POCKETS)) - master.item_flags &= ~ITEM_SLOT_POCKETS - -/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot) - if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET) - RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE) - else - UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN) - -/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper) - UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN) - -/datum/component/spill/proc/knockdown_react(mob/living/fool) - var/obj/item/master = parent - fool.dropItemToGround(master) - if(droptext) - fool.visible_message(arglist(droptext)) - if(dropsound) - playsound(master, pick(dropsound), 30) +// This component is for forcing strange things into your pocket that fall out if you fall down +// Yes this exists purely for the spaghetti meme + +/datum/component/spill + can_transfer = TRUE + var/preexisting_item_flags + + var/list/droptext + var/list/dropsound + +// droptext is an arglist for visible_message +// dropsound is a list of potential sounds that gets picked from +/datum/component/spill/Initialize(list/_droptext, list/_dropsound) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + if(_droptext && !islist(_droptext)) + _droptext = list(_droptext) + droptext = _droptext + + if(_dropsound && !islist(_dropsound)) + _dropsound = list(_dropsound) + dropsound = _dropsound + +/datum/component/spill/PostTransfer() + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/spill/RegisterWithParent() + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react) + var/obj/item/master = parent + preexisting_item_flags = master.item_flags + master.item_flags |= ITEM_SLOT_POCKETS + +/datum/component/spill/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) + var/obj/item/master = parent + if(!(preexisting_item_flags & ITEM_SLOT_POCKETS)) + master.item_flags &= ~ITEM_SLOT_POCKETS + +/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot) + if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET) + RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE) + else + UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN) + +/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper) + UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN) + +/datum/component/spill/proc/knockdown_react(mob/living/fool) + var/obj/item/master = parent + fool.dropItemToGround(master) + if(droptext) + fool.visible_message(arglist(droptext)) + if(dropsound) + playsound(master, pick(dropsound), 30) diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm index efc4f2eace8..d1cfb0f399d 100644 --- a/code/datums/components/storage/concrete/_concrete.dm +++ b/code/datums/components/storage/concrete/_concrete.dm @@ -1,201 +1,201 @@ - -// External storage-related logic: -// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages -// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement - -/datum/component/storage/concrete - can_transfer = TRUE - var/drop_all_on_deconstruct = TRUE - var/drop_all_on_destroy = FALSE - var/transfer_contents_on_component_transfer = FALSE - var/list/datum/component/storage/slaves = list() - - var/list/_contents_limbo // Where objects go to live mid transfer - var/list/_user_limbo // The last users before the component started moving - -/datum/component/storage/concrete/Initialize() - . = ..() - RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del) - RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct) - -/datum/component/storage/concrete/Destroy() - var/atom/real_location = real_location() - for(var/atom/_A in real_location) - _A.mouse_opacity = initial(_A.mouse_opacity) - if(drop_all_on_destroy) - do_quick_empty() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.change_master(null) - QDEL_LIST(_contents_limbo) - _user_limbo = null - return ..() - -/datum/component/storage/concrete/master() - return src - -/datum/component/storage/concrete/real_location() - return parent - -/datum/component/storage/concrete/PreTransfer() - if(is_using) - _user_limbo = is_using.Copy() - close_all() - if(transfer_contents_on_component_transfer) - _contents_limbo = list() - for(var/atom/movable/AM in parent) - _contents_limbo += AM - AM.moveToNullspace() - -/datum/component/storage/concrete/PostTransfer() - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - if(transfer_contents_on_component_transfer) - for(var/i in _contents_limbo) - var/atom/movable/AM = i - AM.forceMove(parent) - _contents_limbo = null - if(_user_limbo) - for(var/i in _user_limbo) - show_to(i) - _user_limbo = null - -/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE) - . = TRUE - var/atom/real_location = real_location() - if(I.loc != real_location) - I.forceMove(real_location) - refresh_mob_views() - -/datum/component/storage/concrete/refresh_mob_views() - . = ..() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.refresh_mob_views() - -/datum/component/storage/concrete/emp_act(datum/source, severity) - if(emp_shielded) - return - var/atom/real_location = real_location() - for(var/i in real_location) - var/atom/A = i - A.emp_act(severity) - -/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S) - if(S == src) - return FALSE - slaves += S - return TRUE - -/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S) - slaves -= S - return FALSE - -/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A) - var/atom/real_location = parent - if(A in real_location) - usr = null - remove_from_storage(A, null) - -/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled) - if(drop_all_on_deconstruct) - do_quick_empty() - -/datum/component/storage/concrete/can_see_contents() - . = ..() - for(var/i in slaves) - var/datum/component/storage/slave = i - . |= slave.can_see_contents() - -//Resets screen loc and other vars of something being removed from storage. -/datum/component/storage/concrete/_removal_reset(atom/movable/thing) - thing.layer = initial(thing.layer) - thing.plane = initial(thing.plane) - thing.mouse_opacity = initial(thing.mouse_opacity) - if(thing.maptext) - thing.maptext = "" - -/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location) - //Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped - //or moving objects, things that should never happen - var/atom/parent = src.parent - var/list/seeing_mobs = can_see_contents() - for(var/mob/M in seeing_mobs) - M.client.screen -= AM - if(ismob(parent.loc) && isitem(AM)) - var/obj/item/I = AM - var/mob/M = parent.loc - I.dropped(M, TRUE) - I.item_flags &= ~IN_STORAGE - if(new_location) - //Reset the items values - _removal_reset(AM) - AM.forceMove(new_location) - //We don't want to call this if the item is being destroyed - AM.on_exit_storage(src) - else - //Being destroyed, just move to nullspace now (so it's not in contents for the icon update) - AM.moveToNullspace() - refresh_mob_views() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - return TRUE - -/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M) - return TRUE - -/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M) - . = handle_item_insertion(I, prevent_warning, M, slave) - if(. && !prevent_warning) - slave.mob_item_insertion_feedback(usr, M, I) - -/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum - var/datum/component/storage/concrete/master = master() - var/atom/parent = src.parent - var/moved = FALSE - if(!istype(I)) - return FALSE - if(M) - if(!M.temporarilyRemoveItemFromInventory(I)) - return FALSE - else - moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever. - if(I.pulledby) - I.pulledby.stop_pulling() - if(silent) - prevent_warning = TRUE - if(!_insert_physical_item(I)) - if(moved) - if(M) - if(!M.put_in_active_hand(I)) - I.forceMove(parent.drop_location()) - else - I.forceMove(parent.drop_location()) - return FALSE - I.on_enter_storage(master) - I.item_flags |= IN_STORAGE - refresh_mob_views() - I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt - if(M) - if(M.client && M.active_storage != src) - M.client.screen -= I - if(M.observers && M.observers.len) - for(var/i in M.observers) - var/mob/dead/observe = i - if(observe.client && observe.active_storage != src) - observe.client.screen -= I - if(!remote) - parent.add_fingerprint(M) - if(!prevent_warning) - mob_item_insertion_feedback(usr, M, I) - update_icon() - return TRUE - -/datum/component/storage/concrete/update_icon() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.update_icon() + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage/concrete + can_transfer = TRUE + var/drop_all_on_deconstruct = TRUE + var/drop_all_on_destroy = FALSE + var/transfer_contents_on_component_transfer = FALSE + var/list/datum/component/storage/slaves = list() + + var/list/_contents_limbo // Where objects go to live mid transfer + var/list/_user_limbo // The last users before the component started moving + +/datum/component/storage/concrete/Initialize() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del) + RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct) + +/datum/component/storage/concrete/Destroy() + var/atom/real_location = real_location() + for(var/atom/_A in real_location) + _A.mouse_opacity = initial(_A.mouse_opacity) + if(drop_all_on_destroy) + do_quick_empty() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.change_master(null) + QDEL_LIST(_contents_limbo) + _user_limbo = null + return ..() + +/datum/component/storage/concrete/master() + return src + +/datum/component/storage/concrete/real_location() + return parent + +/datum/component/storage/concrete/PreTransfer() + if(is_using) + _user_limbo = is_using.Copy() + close_all() + if(transfer_contents_on_component_transfer) + _contents_limbo = list() + for(var/atom/movable/AM in parent) + _contents_limbo += AM + AM.moveToNullspace() + +/datum/component/storage/concrete/PostTransfer() + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if(transfer_contents_on_component_transfer) + for(var/i in _contents_limbo) + var/atom/movable/AM = i + AM.forceMove(parent) + _contents_limbo = null + if(_user_limbo) + for(var/i in _user_limbo) + show_to(i) + _user_limbo = null + +/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE) + . = TRUE + var/atom/real_location = real_location() + if(I.loc != real_location) + I.forceMove(real_location) + refresh_mob_views() + +/datum/component/storage/concrete/refresh_mob_views() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.refresh_mob_views() + +/datum/component/storage/concrete/emp_act(datum/source, severity) + if(emp_shielded) + return + var/atom/real_location = real_location() + for(var/i in real_location) + var/atom/A = i + A.emp_act(severity) + +/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S) + if(S == src) + return FALSE + slaves += S + return TRUE + +/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S) + slaves -= S + return FALSE + +/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A) + var/atom/real_location = parent + if(A in real_location) + usr = null + remove_from_storage(A, null) + +/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled) + if(drop_all_on_deconstruct) + do_quick_empty() + +/datum/component/storage/concrete/can_see_contents() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + . |= slave.can_see_contents() + +//Resets screen loc and other vars of something being removed from storage. +/datum/component/storage/concrete/_removal_reset(atom/movable/thing) + thing.layer = initial(thing.layer) + thing.plane = initial(thing.plane) + thing.mouse_opacity = initial(thing.mouse_opacity) + if(thing.maptext) + thing.maptext = "" + +/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location) + //Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped + //or moving objects, things that should never happen + var/atom/parent = src.parent + var/list/seeing_mobs = can_see_contents() + for(var/mob/M in seeing_mobs) + M.client.screen -= AM + if(ismob(parent.loc) && isitem(AM)) + var/obj/item/I = AM + var/mob/M = parent.loc + I.dropped(M, TRUE) + I.item_flags &= ~IN_STORAGE + if(new_location) + //Reset the items values + _removal_reset(AM) + AM.forceMove(new_location) + //We don't want to call this if the item is being destroyed + AM.on_exit_storage(src) + else + //Being destroyed, just move to nullspace now (so it's not in contents for the icon update) + AM.moveToNullspace() + refresh_mob_views() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + return TRUE + +/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M) + return TRUE + +/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M) + . = handle_item_insertion(I, prevent_warning, M, slave) + if(. && !prevent_warning) + slave.mob_item_insertion_feedback(usr, M, I) + +/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum + var/datum/component/storage/concrete/master = master() + var/atom/parent = src.parent + var/moved = FALSE + if(!istype(I)) + return FALSE + if(M) + if(!M.temporarilyRemoveItemFromInventory(I)) + return FALSE + else + moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever. + if(I.pulledby) + I.pulledby.stop_pulling() + if(silent) + prevent_warning = TRUE + if(!_insert_physical_item(I)) + if(moved) + if(M) + if(!M.put_in_active_hand(I)) + I.forceMove(parent.drop_location()) + else + I.forceMove(parent.drop_location()) + return FALSE + I.on_enter_storage(master) + I.item_flags |= IN_STORAGE + refresh_mob_views() + I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt + if(M) + if(M.client && M.active_storage != src) + M.client.screen -= I + if(M.observers && M.observers.len) + for(var/i in M.observers) + var/mob/dead/observe = i + if(observe.client && observe.active_storage != src) + observe.client.screen -= I + if(!remote) + parent.add_fingerprint(M) + if(!prevent_warning) + mob_item_insertion_feedback(usr, M, I) + update_icon() + return TRUE + +/datum/component/storage/concrete/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.update_icon() diff --git a/code/datums/components/storage/concrete/bag_of_holding.dm b/code/datums/components/storage/concrete/bag_of_holding.dm index 5c8ad6933da..0f83d15cad9 100644 --- a/code/datums/components/storage/concrete/bag_of_holding.dm +++ b/code/datums/components/storage/concrete/bag_of_holding.dm @@ -1,23 +1,23 @@ -/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user) - var/atom/A = parent - if(A == W) //don't put yourself into yourself. - return - var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding)) - matching -= A - if(istype(W, /obj/item/storage/backpack/holding) || matching.len) - var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Proceed", "Abort") - if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user))) - return - var/turf/loccheck = get_turf(A) - to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!") - qdel(W) - playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE) - - message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].") - log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].") - - user.gib(TRUE, TRUE, TRUE) - new/obj/singularity/boh_tear(loccheck) - qdel(A) - return - . = ..() +/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user) + var/atom/A = parent + if(A == W) //don't put yourself into yourself. + return + var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding)) + matching -= A + if(istype(W, /obj/item/storage/backpack/holding) || matching.len) + var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Proceed", "Abort") + if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user))) + return + var/turf/loccheck = get_turf(A) + to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!") + qdel(W) + playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE) + + message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].") + log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].") + + user.gib(TRUE, TRUE, TRUE) + new/obj/singularity/boh_tear(loccheck) + qdel(A) + return + . = ..() diff --git a/code/datums/components/storage/concrete/bluespace.dm b/code/datums/components/storage/concrete/bluespace.dm index d9669eaa1e1..ceda1655eca 100644 --- a/code/datums/components/storage/concrete/bluespace.dm +++ b/code/datums/components/storage/concrete/bluespace.dm @@ -1,22 +1,22 @@ -/datum/component/storage/concrete/bluespace - var/dumping_range = 8 - var/dumping_sound = 'sound/items/pshoom.ogg' - var/alt_sound = 'sound/items/pshoom_2.ogg' - -/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M) - var/atom/A = parent - if(A.Adjacent(M)) - var/atom/dumping_location = dest.get_dumping_location() - var/turf/bagT = get_turf(parent) - var/turf/destT = get_turf(dumping_location) - if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range) - if(dumping_location.storage_contents_dump_act(src, M)) - if(alt_sound && prob(1)) - playsound(src, alt_sound, 40, TRUE) - else - playsound(src, dumping_sound, 40, TRUE) - M.Beam(dumping_location, icon_state="rped_upgrade", time=5) - return TRUE - to_chat(M, "The [A.name] buzzes.") - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) - return FALSE +/datum/component/storage/concrete/bluespace + var/dumping_range = 8 + var/dumping_sound = 'sound/items/pshoom.ogg' + var/alt_sound = 'sound/items/pshoom_2.ogg' + +/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M) + var/atom/A = parent + if(A.Adjacent(M)) + var/atom/dumping_location = dest.get_dumping_location() + var/turf/bagT = get_turf(parent) + var/turf/destT = get_turf(dumping_location) + if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range) + if(dumping_location.storage_contents_dump_act(src, M)) + if(alt_sound && prob(1)) + playsound(src, alt_sound, 40, TRUE) + else + playsound(src, dumping_sound, 40, TRUE) + M.Beam(dumping_location, icon_state="rped_upgrade", time=5) + return TRUE + to_chat(M, "The [A.name] buzzes.") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + return FALSE diff --git a/code/datums/components/storage/concrete/implant.dm b/code/datums/components/storage/concrete/implant.dm index 95a929494df..405b3098fe5 100644 --- a/code/datums/components/storage/concrete/implant.dm +++ b/code/datums/components/storage/concrete/implant.dm @@ -1,18 +1,18 @@ -/datum/component/storage/concrete/implant - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 6 - max_items = 2 - drop_all_on_destroy = TRUE - drop_all_on_deconstruct = TRUE - silent = TRUE - allow_big_nesting = TRUE - -/datum/component/storage/concrete/implant/Initialize() - . = ..() - set_holdable(null, list(/obj/item/disk/nuclear)) - -/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original) - if(!istype(I)) - return ..() - max_combined_w_class += I.max_combined_w_class - max_items += I.max_items +/datum/component/storage/concrete/implant + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 6 + max_items = 2 + drop_all_on_destroy = TRUE + drop_all_on_deconstruct = TRUE + silent = TRUE + allow_big_nesting = TRUE + +/datum/component/storage/concrete/implant/Initialize() + . = ..() + set_holdable(null, list(/obj/item/disk/nuclear)) + +/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original) + if(!istype(I)) + return ..() + max_combined_w_class += I.max_combined_w_class + max_items += I.max_items diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm index 2e576998cff..4a1d8664ec4 100644 --- a/code/datums/components/storage/concrete/pockets.dm +++ b/code/datums/components/storage/concrete/pockets.dm @@ -1,93 +1,93 @@ -/datum/component/storage/concrete/pockets - max_items = 2 - max_w_class = WEIGHT_CLASS_SMALL - max_combined_w_class = 50 - rustle_sound = FALSE - -/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user) - . = ..() - if(. && silent && !prevent_warning) - if(quickdraw) - to_chat(user, "You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.") - else - to_chat(user, "You discreetly slip [I] into [parent].") - -/datum/component/storage/concrete/pockets/small - max_items = 1 - max_w_class = WEIGHT_CLASS_SMALL - attack_hand_interact = FALSE - -/datum/component/storage/concrete/pockets/tiny - max_items = 1 - max_w_class = WEIGHT_CLASS_TINY - attack_hand_interact = FALSE - -/datum/component/storage/concrete/pockets/small/fedora/Initialize() - . = ..() - var/static/list/exception_cache = typecacheof(list( - /obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, - /obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun - )) - exception_hold = exception_cache - -/datum/component/storage/concrete/pockets/small/fedora/detective - attack_hand_interact = TRUE // so the detectives would discover pockets in their hats - -/datum/component/storage/concrete/pockets/shoes - attack_hand_interact = FALSE - quickdraw = TRUE - silent = TRUE - -/datum/component/storage/concrete/pockets/shoes/Initialize() - . = ..() - set_holdable(list( - /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, - /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, - /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, - /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, - /obj/item/firing_pin - ), - list(/obj/item/screwdriver/power) - ) - -/datum/component/storage/concrete/pockets/shoes/clown/Initialize() - . = ..() - set_holdable(list( - /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, - /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, - /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, - /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, - /obj/item/firing_pin, /obj/item/bikehorn), - list(/obj/item/screwdriver/power) - ) - -/datum/component/storage/concrete/pockets/pocketprotector - max_items = 3 - max_w_class = WEIGHT_CLASS_TINY - var/atom/original_parent - -/datum/component/storage/concrete/pockets/pocketprotector/Initialize() - original_parent = parent - . = ..() - set_holdable(list( //Same items as a PDA - /obj/item/pen, - /obj/item/toy/crayon, - /obj/item/lipstick, - /obj/item/flashlight/pen, - /obj/item/clothing/mask/cigarette) - ) - -/datum/component/storage/concrete/pockets/pocketprotector/real_location() - // if the component is reparented to a jumpsuit, the items still go in the protector - return original_parent - -/datum/component/storage/concrete/pockets/helmet - quickdraw = TRUE - max_combined_w_class = 6 - -/datum/component/storage/concrete/pockets/helmet/Initialize() - . = ..() - set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka, - /obj/item/reagent_containers/food/drinks/bottle/molotov, - /obj/item/reagent_containers/food/drinks/drinkingglass, - /obj/item/ammo_box/a762)) +/datum/component/storage/concrete/pockets + max_items = 2 + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 50 + rustle_sound = FALSE + +/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user) + . = ..() + if(. && silent && !prevent_warning) + if(quickdraw) + to_chat(user, "You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.") + else + to_chat(user, "You discreetly slip [I] into [parent].") + +/datum/component/storage/concrete/pockets/small + max_items = 1 + max_w_class = WEIGHT_CLASS_SMALL + attack_hand_interact = FALSE + +/datum/component/storage/concrete/pockets/tiny + max_items = 1 + max_w_class = WEIGHT_CLASS_TINY + attack_hand_interact = FALSE + +/datum/component/storage/concrete/pockets/small/fedora/Initialize() + . = ..() + var/static/list/exception_cache = typecacheof(list( + /obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, + /obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun + )) + exception_hold = exception_cache + +/datum/component/storage/concrete/pockets/small/fedora/detective + attack_hand_interact = TRUE // so the detectives would discover pockets in their hats + +/datum/component/storage/concrete/pockets/shoes + attack_hand_interact = FALSE + quickdraw = TRUE + silent = TRUE + +/datum/component/storage/concrete/pockets/shoes/Initialize() + . = ..() + set_holdable(list( + /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, + /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, + /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, + /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, + /obj/item/firing_pin + ), + list(/obj/item/screwdriver/power) + ) + +/datum/component/storage/concrete/pockets/shoes/clown/Initialize() + . = ..() + set_holdable(list( + /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, + /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, + /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, + /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, + /obj/item/firing_pin, /obj/item/bikehorn), + list(/obj/item/screwdriver/power) + ) + +/datum/component/storage/concrete/pockets/pocketprotector + max_items = 3 + max_w_class = WEIGHT_CLASS_TINY + var/atom/original_parent + +/datum/component/storage/concrete/pockets/pocketprotector/Initialize() + original_parent = parent + . = ..() + set_holdable(list( //Same items as a PDA + /obj/item/pen, + /obj/item/toy/crayon, + /obj/item/lipstick, + /obj/item/flashlight/pen, + /obj/item/clothing/mask/cigarette) + ) + +/datum/component/storage/concrete/pockets/pocketprotector/real_location() + // if the component is reparented to a jumpsuit, the items still go in the protector + return original_parent + +/datum/component/storage/concrete/pockets/helmet + quickdraw = TRUE + max_combined_w_class = 6 + +/datum/component/storage/concrete/pockets/helmet/Initialize() + . = ..() + set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka, + /obj/item/reagent_containers/food/drinks/bottle/molotov, + /obj/item/reagent_containers/food/drinks/drinkingglass, + /obj/item/ammo_box/a762)) diff --git a/code/datums/components/storage/concrete/rped.dm b/code/datums/components/storage/concrete/rped.dm index 14f6fe28b38..455eb985f09 100644 --- a/code/datums/components/storage/concrete/rped.dm +++ b/code/datums/components/storage/concrete/rped.dm @@ -1,33 +1,33 @@ -/datum/component/storage/concrete/rped - collection_mode = COLLECT_EVERYTHING - allow_quick_gather = TRUE - allow_quick_empty = TRUE - click_gather = TRUE - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 100 - max_items = 50 - display_numerical_stacking = TRUE - -/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) - . = ..() - if(!I.get_part_rating()) - if (!stop_messages) - to_chat(M, "[parent] only accepts machine parts!") - return FALSE - -/datum/component/storage/concrete/bluespace/rped - collection_mode = COLLECT_EVERYTHING - allow_quick_gather = TRUE - allow_quick_empty = TRUE - click_gather = TRUE - max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills - max_combined_w_class = 800 - max_items = 400 - display_numerical_stacking = TRUE - -/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) - . = ..() - if(!I.get_part_rating()) - if (!stop_messages) - to_chat(M, "[parent] only accepts machine parts!") - return FALSE +/datum/component/storage/concrete/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 100 + max_items = 50 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating()) + if (!stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE + +/datum/component/storage/concrete/bluespace/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills + max_combined_w_class = 800 + max_items = 400 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating()) + if (!stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE diff --git a/code/datums/components/storage/concrete/stack.dm b/code/datums/components/storage/concrete/stack.dm index ad003d19b3f..1f0c44c6502 100644 --- a/code/datums/components/storage/concrete/stack.dm +++ b/code/datums/components/storage/concrete/stack.dm @@ -1,67 +1,67 @@ -//Stack-only storage. -/datum/component/storage/concrete/stack - display_numerical_stacking = TRUE - var/max_combined_stack_amount = 300 - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = WEIGHT_CLASS_NORMAL * 14 - -/datum/component/storage/concrete/stack/proc/total_stack_amount() - . = 0 - var/atom/real_location = real_location() - for(var/i in real_location) - var/obj/item/stack/S = i - if(!istype(S)) - continue - . += S.amount - -/datum/component/storage/concrete/stack/proc/remaining_space() - return max(0, max_combined_stack_amount - total_stack_amount()) - -//emptying procs do not need modification as stacks automatically merge. - -/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE) - if(!istype(I, /obj/item/stack)) - if(override) - return ..() - return FALSE - var/atom/real_location = real_location() - var/obj/item/stack/S = I - var/can_insert = min(S.amount, remaining_space()) - if(!can_insert) - return FALSE - for(var/i in real_location) //combine. - if(QDELETED(I)) - return - var/obj/item/stack/_S = i - if(!istype(_S)) - continue - if(_S.merge_type == S.merge_type) - _S.add(can_insert) - S.use(can_insert, TRUE) - return TRUE - return ..(S.change_stack(null, can_insert), override) - -/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) - var/atom/real_location = real_location() - var/obj/item/stack/S = I - if(!istype(S)) - return ..() - if(S.amount > S.max_amount) - var/overrun = S.amount - S.max_amount - S.amount = S.max_amount - var/obj/item/stack/temp = new S.type(real_location, overrun) - handle_item_insertion(temp) - return ..(S, new_location) - -/datum/component/storage/concrete/stack/_process_numerical_display() - var/atom/real_location = real_location() - . = list() - for(var/i in real_location) - var/obj/item/stack/I = i - if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks. - continue - if(!.[I.merge_type]) - .[I.merge_type] = new /datum/numbered_display(I, I.amount) - else - var/datum/numbered_display/ND = .[I.merge_type] - ND.number += I.amount +//Stack-only storage. +/datum/component/storage/concrete/stack + display_numerical_stacking = TRUE + var/max_combined_stack_amount = 300 + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = WEIGHT_CLASS_NORMAL * 14 + +/datum/component/storage/concrete/stack/proc/total_stack_amount() + . = 0 + var/atom/real_location = real_location() + for(var/i in real_location) + var/obj/item/stack/S = i + if(!istype(S)) + continue + . += S.amount + +/datum/component/storage/concrete/stack/proc/remaining_space() + return max(0, max_combined_stack_amount - total_stack_amount()) + +//emptying procs do not need modification as stacks automatically merge. + +/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE) + if(!istype(I, /obj/item/stack)) + if(override) + return ..() + return FALSE + var/atom/real_location = real_location() + var/obj/item/stack/S = I + var/can_insert = min(S.amount, remaining_space()) + if(!can_insert) + return FALSE + for(var/i in real_location) //combine. + if(QDELETED(I)) + return + var/obj/item/stack/_S = i + if(!istype(_S)) + continue + if(_S.merge_type == S.merge_type) + _S.add(can_insert) + S.use(can_insert, TRUE) + return TRUE + return ..(S.change_stack(null, can_insert), override) + +/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) + var/atom/real_location = real_location() + var/obj/item/stack/S = I + if(!istype(S)) + return ..() + if(S.amount > S.max_amount) + var/overrun = S.amount - S.max_amount + S.amount = S.max_amount + var/obj/item/stack/temp = new S.type(real_location, overrun) + handle_item_insertion(temp) + return ..(S, new_location) + +/datum/component/storage/concrete/stack/_process_numerical_display() + var/atom/real_location = real_location() + . = list() + for(var/i in real_location) + var/obj/item/stack/I = i + if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks. + continue + if(!.[I.merge_type]) + .[I.merge_type] = new /datum/numbered_display(I, I.amount) + else + var/datum/numbered_display/ND = .[I.merge_type] + ND.number += I.amount diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 9458e4153e0..658b9e926f8 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -1,818 +1,818 @@ -#define COLLECT_ONE 0 -#define COLLECT_EVERYTHING 1 -#define COLLECT_SAME 2 - -#define DROP_NOTHING 0 -#define DROP_AT_PARENT 1 -#define DROP_AT_LOCATION 2 - -// External storage-related logic: -// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages -// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement - -/datum/component/storage - dupe_mode = COMPONENT_DUPE_UNIQUE - var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. - - var/list/can_hold //if this is set, only items, and their children, will fit - var/list/cant_hold //if this is set, items, and their children, won't fit - var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit. - - var/can_hold_description - - var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. - - var/locked = FALSE //when locked nothing can see inside or use it. - - var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. - var/max_combined_w_class = 14 //max combined sizes of objects that will fit. - var/max_items = 7 //max number of objects that will fit. - - var/emp_shielded = FALSE - - var/silent = FALSE //whether this makes a message when things are put in. - var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. - var/rustle_sound = TRUE //play rustle sound on interact. - var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. - var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. - - var/collection_mode = COLLECT_EVERYTHING - - var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. - - var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. - - var/obj/screen/storage/boxes //storage display object - var/obj/screen/close/closer //close button object - - var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. - - var/attack_hand_interact = TRUE //interact on attack hand. - var/quickdraw = FALSE //altclick interact - - var/datum/action/item_action/storage_gather_mode/modeswitch_action - - //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. - var/screen_max_columns = 7 //These two determine maximum screen sizes. - var/screen_max_rows = INFINITY - var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer - var/screen_pixel_y = 16 - var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. - var/screen_start_y = 2 - //End - -/datum/component/storage/Initialize(datum/component/storage/concrete/master) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - if(master) - change_master(master) - boxes = new(null, src) - closer = new(null, src) - orient2hud() - - RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) - RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) - RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) - RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) - - RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle) - - RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) - - RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) - RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) - RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) - RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) - - RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) - RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) - RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) - - RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move) - - RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) - RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) - RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) - - update_actions() - -/datum/component/storage/Destroy() - close_all() - QDEL_NULL(boxes) - QDEL_NULL(closer) - LAZYCLEARLIST(is_using) - return ..() - -/datum/component/storage/PreTransfer() - update_actions() - -/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list) - can_hold_description = generate_hold_desc(can_hold_list) - - if (can_hold_list != null) - can_hold = typecacheof(can_hold_list) - - if (cant_hold_list != null) - cant_hold = typecacheof(cant_hold_list) - -/datum/component/storage/proc/generate_hold_desc(can_hold_list) - var/list/desc = list() - - for(var/valid_type in can_hold_list) - var/obj/item/valid_item = valid_type - desc += "\a [initial(valid_item.name)]" - - return "\n\t[desc.Join("\n\t")]" - -/datum/component/storage/proc/update_actions() - QDEL_NULL(modeswitch_action) - if(!isitem(parent) || !allow_quick_gather) - return - var/obj/item/I = parent - modeswitch_action = new(I) - RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) - if(I.obj_flags & IN_INVENTORY) - var/mob/M = I.loc - if(!istype(M)) - return - modeswitch_action.Grant(M) - -/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) - if(new_master == src || (!isnull(new_master) && !istype(new_master))) - return FALSE - if(master) - master.on_slave_unlink(src) - master = new_master - if(master) - master.on_slave_link(src) - return TRUE - -/datum/component/storage/proc/master() - if(master == src) - return //infinite loops yo. - return master - -/datum/component/storage/proc/real_location() - var/datum/component/storage/concrete/master = master() - return master? master.real_location() : null - -/datum/component/storage/proc/canreach_react(datum/source, list/next) - var/datum/component/storage/concrete/master = master() - if(!master) - return - . = COMPONENT_ALLOW_REACH - next += master.parent - for(var/i in master.slaves) - var/datum/component/storage/slave = i - next += slave.parent - -/datum/component/storage/proc/on_move() - var/atom/A = parent - for(var/mob/living/L in can_see_contents()) - if(!L.CanReach(A)) - hide_from(L) - -/datum/component/storage/proc/attack_self(datum/source, mob/M) - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if((M.get_active_held_item() == parent) && allow_quick_empty) - quick_empty(M) - -/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) - if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) - return FALSE - . = COMPONENT_NO_ATTACK - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - var/obj/item/I = O - if(collection_mode == COLLECT_ONE) - if(can_be_inserted(I, null, M)) - handle_item_insertion(I, null, M) - return - if(!isturf(I.loc)) - return - var/list/things = I.loc.contents.Copy() - if(collection_mode == COLLECT_SAME) - things = typecache_filter_list(things, typecacheof(I.type)) - var/len = length(things) - if(!len) - to_chat(M, "You failed to pick up anything with [parent]!") - return - var/datum/progressbar/progress = new(M, len, I.loc) - var/list/rejections = list() - while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) - stoplag(1) - progress.end_progress() - to_chat(M, "You put everything you could [insert_preposition] [parent].") - -/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) - var/atom/source_real_location = src_object.real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != source_real_location) - continue - if(user.active_storage != src_object) - if(I.on_found(user)) - break - if(can_be_inserted(I,FALSE,user)) - handle_item_insertion(I, TRUE, user) - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != thing_loc) - continue - if(I.type in rejections) // To limit bag spamming: any given type only complains once - continue - if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no - if(real_location.contents.len >= max_items) - break - rejections += I.type // therefore full bags are still a little spammy - continue - - handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. - - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/quick_empty(mob/M) - var/atom/A = parent - if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) - return - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - A.add_fingerprint(M) - to_chat(M, "You start dumping out [parent].") - var/turf/T = get_turf(A) - var/list/things = contents() - var/datum/progressbar/progress = new(M, length(things), T) - while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) - stoplag(1) - progress.end_progress() - -/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != real_location) - continue - remove_from_storage(I, target) - I.pixel_x = rand(-10,10) - I.pixel_y = rand(-10,10) - if(trigger_on_found && I.on_found()) - return FALSE - if(TICK_CHECK) - progress.update(progress.goal - length(things)) - return TRUE - progress.update(progress.goal - length(things)) - return FALSE - -/datum/component/storage/proc/do_quick_empty(atom/_target) - if(!_target) - _target = get_turf(parent) - if(usr) - hide_from(usr) - var/list/contents = contents() - var/atom/real_location = real_location() - for(var/obj/item/I in contents) - if(I.loc != real_location) - continue - remove_from_storage(I, _target) - return TRUE - -/datum/component/storage/proc/set_locked(datum/source, new_state) - locked = new_state - if(locked) - close_all() - -/datum/component/storage/proc/_process_numerical_display() - . = list() - var/atom/real_location = real_location() - for(var/obj/item/I in real_location.contents) - if(QDELETED(I)) - continue - if(!.["[I.type]-[I.name]"]) - .["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1) - else - var/datum/numbered_display/ND = .["[I.type]-[I.name]"] - ND.number++ - -//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. -/datum/component/storage/proc/orient2hud() - var/atom/real_location = real_location() - var/adjusted_contents = real_location.contents.len - - //Numbered contents display - var/list/datum/numbered_display/numbered_contents - if(display_numerical_stacking) - numbered_contents = _process_numerical_display() - adjusted_contents = numbered_contents.len - - var/columns = clamp(max_items, 1, screen_max_columns) - var/rows = clamp(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) - standard_orient_objs(rows, columns, numbered_contents) - -//This proc draws out the inventory and places the items on it. It uses the standard position. -/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) - boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" - var/cx = screen_start_x - var/cy = screen_start_y - if(islist(numerical_display_contents)) - for(var/type in numerical_display_contents) - var/datum/numbered_display/ND = numerical_display_contents[type] - ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE - ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" - ND.sample_object.layer = ABOVE_HUD_LAYER - ND.sample_object.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - else - var/atom/real_location = real_location() - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" - O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - O.maptext = "" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" - -/datum/component/storage/proc/show_to(mob/M) - if(!M.client) - return FALSE - var/atom/real_location = real_location() - if(M.active_storage != src && (M.stat == CONSCIOUS)) - for(var/obj/item/I in real_location) - if(I.on_found(M)) - return FALSE - if(M.active_storage) - M.active_storage.hide_from(M) - orient2hud() - M.client.screen |= boxes - M.client.screen |= closer - M.client.screen |= real_location.contents - M.active_storage = src - LAZYOR(is_using, M) - return TRUE - -/datum/component/storage/proc/hide_from(mob/M) - if(!M.client) - return TRUE - var/atom/real_location = real_location() - M.client.screen -= boxes - M.client.screen -= closer - M.client.screen -= real_location.contents - if(M.active_storage == src) - M.active_storage = null - LAZYREMOVE(is_using, M) - return TRUE - -/datum/component/storage/proc/close(mob/M) - hide_from(M) - -/datum/component/storage/proc/close_all() - . = FALSE - for(var/mob/M in can_see_contents()) - close(M) - . = TRUE //returns TRUE if any mobs actually got a close(M) call - -/datum/component/storage/proc/emp_act(datum/source, severity) - if(emp_shielded) - return - var/datum/component/storage/concrete/master = master() - master.emp_act(source, severity) - -//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. -//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. -/datum/component/storage/proc/orient_objs(tx, ty, mx, my) - var/atom/real_location = real_location() - var/cx = tx - var/cy = ty - boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.screen_loc = "[cx],[cy]" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx > mx) - cx = tx - cy-- - closer.screen_loc = "[mx+1],[my]" - -//Resets something that is being removed from storage. -/datum/component/storage/proc/_removal_reset(atom/movable/thing) - if(!istype(thing)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master._removal_reset(thing) - -/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) - _removal_reset(thing) - refresh_mob_views() - -//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted -/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) - if(!istype(AM)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.remove_from_storage(AM, new_location) - -/datum/component/storage/proc/refresh_mob_views() - var/list/seeing = can_see_contents() - for(var/i in seeing) - show_to(i) - return TRUE - -/datum/component/storage/proc/can_see_contents() - var/list/cansee = list() - for(var/mob/M in is_using) - if(M.active_storage == src && M.client) - cansee |= M - else - LAZYREMOVE(is_using, M) - return cansee - -//Tries to dump content -/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) - var/atom/A = parent - var/atom/dump_destination = dest_object.get_dumping_location() - if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if(dump_destination.storage_contents_dump_act(src, M)) - playsound(A, "rustle", 50, TRUE, -5) - return TRUE - return FALSE - -//This proc is called when you want to place an item into the storage item. -/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) - if(istype(I, /obj/item/hand_labeler)) - var/obj/item/hand_labeler/labeler = I - if(labeler.mode) - return FALSE - . = TRUE //no afterattack - if(iscyborg(M)) - return - if(!can_be_inserted(I, FALSE, M)) - var/atom/real_location = real_location() - if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit - return TRUE - return FALSE - handle_item_insertion(I, FALSE, M) - -/datum/component/storage/proc/return_inv(recursive) - var/list/ret = list() - ret |= contents() - if(recursive) - for(var/i in ret.Copy()) - var/atom/A = i - SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) - return ret - -/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! - var/atom/real_location = real_location() - return real_location.contents.Copy() - -//Abuses the fact that lists are just references, or something like that. -/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) - if(!islist(interface)) - return FALSE - interface |= return_inv(recursive) - return TRUE - -/datum/component/storage/proc/topic_handle(datum/source, user, href_list) - if(href_list["show_valid_pocket_items"]) - handle_show_valid_items(source, user) - -/datum/component/storage/proc/handle_show_valid_items(datum/source, user) - to_chat(user, "[source] can hold: [can_hold_description]") - -/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) - set waitfor = FALSE - . = COMPONENT_NO_MOUSEDROP - if(!ismob(M)) - return - if(!over_object) - return - if(ismecha(M.loc)) // stops inventory actions in a mech - return - if(M.incapacitated() || !M.canUseStorage()) - return - var/atom/A = parent - A.add_fingerprint(M) - // this must come before the screen objects only block, dunno why it wasn't before - if(over_object == M) - user_show_to_mob(M) - if(!istype(over_object, /obj/screen)) - dump_content_at(over_object, M) - return - if(A.loc != M) - return - playsound(A, "rustle", 50, TRUE, -5) - if(istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - M.putItemFromInventoryInHandIfPossible(A, H.held_index) - return - A.add_fingerprint(M) - -/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) - var/atom/A = parent - if(!istype(M)) - return FALSE - A.add_fingerprint(M) - if(locked && !force) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if(force || M.CanReach(parent, view_only = TRUE)) - show_to(M) - -/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) - if(isitem(O)) - var/obj/item/I = O - if(iscarbon(M) || isdrone(M)) - var/mob/living/L = M - if(!L.incapacitated() && I == L.get_active_held_item()) - if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. - handle_item_insertion(I, FALSE, L) - -//This proc return 1 if the item can be picked up and 0 if it can't. -//Set the stop_messages to stop it from printing messages -/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) - if(!istype(I) || (I.item_flags & ABSTRACT)) - return FALSE //Not an item - if(I == parent) - return FALSE //no paradoxes for you - var/atom/real_location = real_location() - var/atom/host = parent - if(real_location == I.loc) - return FALSE //Means the item is already in the storage item - if(locked) - if(M && !stop_messages) - host.add_fingerprint(M) - to_chat(M, "[host] seems to be locked!") - return FALSE - if(real_location.contents.len >= max_items) - if(!stop_messages) - to_chat(M, "[host] is full, make some space!") - return FALSE //Storage item is full - if(length(can_hold)) - if(!is_type_in_typecache(I, can_hold)) - if(!stop_messages) - to_chat(M, "[host] cannot hold [I]!") - return FALSE - if(is_type_in_typecache(I, cant_hold) || HAS_TRAIT(I, TRAIT_NO_STORAGE_INSERT)) //Items which this container can't hold. - if(!stop_messages) - to_chat(M, "[host] cannot hold [I]!") - return FALSE - if(I.w_class > max_w_class && !is_type_in_typecache(I, exception_hold)) - if(!stop_messages) - to_chat(M, "[I] is too big for [host]!") - return FALSE - var/datum/component/storage/biggerfish = real_location.loc.GetComponent(/datum/component/storage) - if(biggerfish && biggerfish.max_w_class < max_w_class)//return false if we are inside of another container, and that container has a smaller max_w_class than us (like if we're a bag in a box) - if(!stop_messages) - to_chat(M, "[I] can't fit in [host] while [real_location.loc] is in the way!") - return FALSE - var/sum_w_class = I.w_class - for(var/obj/item/_I in real_location) - sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. - if(sum_w_class > max_combined_w_class) - if(!stop_messages) - to_chat(M, "[I] won't fit in [host], make some space!") - return FALSE - if(isitem(host)) - var/obj/item/IP = host - var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage) - if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting) - if(!stop_messages) - to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!") - return FALSE //To prevent the stacking of same sized storage items. - if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry. - if(!stop_messages) - to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.slave_can_insert_object(src, I, stop_messages, M) - -/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) - return FALSE - -//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() -//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, -//such as when picking up all the items on a tile with one click. -/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) - var/atom/parent = src.parent - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - if(silent) - prevent_warning = TRUE - if(M) - parent.add_fingerprint(M) - . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) - -/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) - if(silent && !override) - return - if(rustle_sound) - playsound(parent, "rustle", 50, TRUE, -5) - for(var/mob/viewing in viewers(user, null)) - if(M == viewing) - to_chat(usr, "You put [I] [insert_preposition]to [parent].") - else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) - else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) - -/datum/component/storage/proc/update_icon() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - -/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) - if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) - return FALSE - return handle_item_insertion(I, silent, M) - -/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) - return can_be_inserted(I, silent, M) - -/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) - return user_show_to_mob(M, TRUE) - -/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) - return user_show_to_mob(showto, force) - -/datum/component/storage/proc/on_check() - return TRUE - -/datum/component/storage/proc/check_locked() - return locked - -/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) - if(!force) - if(check_adjacent) - if(!user || !user.CanReach(destination) || !user.CanReach(parent)) - return FALSE - var/list/taking = typecache_filter_list(contents(), typecacheof(type)) - if(taking.len > amount) - taking.len = amount - if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. - for(var/i in taking) - if(remove_from_storage(i, destination)) - inserted |= i - else - for(var/i in taking) - remove_from_storage(i, destination) - return TRUE - -/datum/component/storage/proc/remaining_space_items() - var/atom/real_location = real_location() - return max(0, max_items - real_location.contents.len) - -/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) - var/atom/real_location = real_location() - if(!force) - amount = min(remaining_space_items(), amount) - for(var/i in 1 to amount) - if(!handle_item_insertion(new type(real_location), TRUE)) - return i > 1 //return TRUE only if at least one insertion has been successful. - if(CHECK_TICK) - if(QDELETED(src)) - return TRUE - return TRUE - -/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) - var/atom/A = parent - if(!attack_hand_interact) - return - if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item - user.active_storage.close(user) - close(user) - . = COMPONENT_NO_ATTACK_HAND - return - - if(rustle_sound) - playsound(A, "rustle", 50, TRUE, -5) - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.l_store = null - return - if(H.r_store == A && !H.get_active_held_item()) - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.r_store = null - return - - if(A.loc == user) - . = COMPONENT_NO_ATTACK_HAND - if(locked) - to_chat(user, "[parent] seems to be locked!") - else - show_to(user) - -/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) - update_actions() - for(var/mob/M in can_see_contents() - user) - close(M) - -/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) - if(!(AM in real_location())) - return FALSE - return remove_from_storage(AM, new_loc) - -/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) - return do_quick_empty(loctarget) - -/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) - return hide_from(target) - -/datum/component/storage/proc/on_alt_click(datum/source, mob/user) - if(!isliving(user) || !user.CanReach(parent) || user.incapacitated()) - return - if(locked) - to_chat(user, "[parent] seems to be locked!") - return - - var/atom/A = parent - if(!quickdraw) - A.add_fingerprint(user) - user_show_to_mob(user) - playsound(A, "rustle", 50, TRUE, -5) - return - - var/obj/item/I = locate() in real_location() - if(!I) - return - A.add_fingerprint(user) - remove_from_storage(I, get_turf(user)) - if(!user.put_in_hands(I)) - to_chat(user, "You fumble for [I] and it falls on the floor.") - return - user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") - -/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source) - gather_mode_switch(source.owner) - return COMPONENT_ACTION_BLOCK_TRIGGER - -/datum/component/storage/proc/gather_mode_switch(mob/user) - collection_mode = (collection_mode+1)%3 - switch(collection_mode) - if(COLLECT_SAME) - to_chat(user, "[parent] now picks up all items of a single type at once.") - if(COLLECT_EVERYTHING) - to_chat(user, "[parent] now picks up all items in a tile at once.") - if(COLLECT_ONE) - to_chat(user, "[parent] now picks up one item at a time.") +#define COLLECT_ONE 0 +#define COLLECT_EVERYTHING 1 +#define COLLECT_SAME 2 + +#define DROP_NOTHING 0 +#define DROP_AT_PARENT 1 +#define DROP_AT_LOCATION 2 + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage + dupe_mode = COMPONENT_DUPE_UNIQUE + var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. + + var/list/can_hold //if this is set, only items, and their children, will fit + var/list/cant_hold //if this is set, items, and their children, won't fit + var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit. + + var/can_hold_description + + var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. + + var/locked = FALSE //when locked nothing can see inside or use it. + + var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. + var/max_combined_w_class = 14 //max combined sizes of objects that will fit. + var/max_items = 7 //max number of objects that will fit. + + var/emp_shielded = FALSE + + var/silent = FALSE //whether this makes a message when things are put in. + var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. + var/rustle_sound = TRUE //play rustle sound on interact. + var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. + var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. + + var/collection_mode = COLLECT_EVERYTHING + + var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. + + var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. + + var/obj/screen/storage/boxes //storage display object + var/obj/screen/close/closer //close button object + + var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. + + var/attack_hand_interact = TRUE //interact on attack hand. + var/quickdraw = FALSE //altclick interact + + var/datum/action/item_action/storage_gather_mode/modeswitch_action + + //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. + var/screen_max_columns = 7 //These two determine maximum screen sizes. + var/screen_max_rows = INFINITY + var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer + var/screen_pixel_y = 16 + var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. + var/screen_start_y = 2 + //End + +/datum/component/storage/Initialize(datum/component/storage/concrete/master) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if(master) + change_master(master) + boxes = new(null, src) + closer = new(null, src) + orient2hud() + + RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) + RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) + RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) + RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) + + RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle) + + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) + + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) + RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) + RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) + RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) + + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) + RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) + + RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move) + + RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) + RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) + RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) + + update_actions() + +/datum/component/storage/Destroy() + close_all() + QDEL_NULL(boxes) + QDEL_NULL(closer) + LAZYCLEARLIST(is_using) + return ..() + +/datum/component/storage/PreTransfer() + update_actions() + +/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list) + can_hold_description = generate_hold_desc(can_hold_list) + + if (can_hold_list != null) + can_hold = typecacheof(can_hold_list) + + if (cant_hold_list != null) + cant_hold = typecacheof(cant_hold_list) + +/datum/component/storage/proc/generate_hold_desc(can_hold_list) + var/list/desc = list() + + for(var/valid_type in can_hold_list) + var/obj/item/valid_item = valid_type + desc += "\a [initial(valid_item.name)]" + + return "\n\t[desc.Join("\n\t")]" + +/datum/component/storage/proc/update_actions() + QDEL_NULL(modeswitch_action) + if(!isitem(parent) || !allow_quick_gather) + return + var/obj/item/I = parent + modeswitch_action = new(I) + RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) + if(I.obj_flags & IN_INVENTORY) + var/mob/M = I.loc + if(!istype(M)) + return + modeswitch_action.Grant(M) + +/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) + if(new_master == src || (!isnull(new_master) && !istype(new_master))) + return FALSE + if(master) + master.on_slave_unlink(src) + master = new_master + if(master) + master.on_slave_link(src) + return TRUE + +/datum/component/storage/proc/master() + if(master == src) + return //infinite loops yo. + return master + +/datum/component/storage/proc/real_location() + var/datum/component/storage/concrete/master = master() + return master? master.real_location() : null + +/datum/component/storage/proc/canreach_react(datum/source, list/next) + var/datum/component/storage/concrete/master = master() + if(!master) + return + . = COMPONENT_ALLOW_REACH + next += master.parent + for(var/i in master.slaves) + var/datum/component/storage/slave = i + next += slave.parent + +/datum/component/storage/proc/on_move() + var/atom/A = parent + for(var/mob/living/L in can_see_contents()) + if(!L.CanReach(A)) + hide_from(L) + +/datum/component/storage/proc/attack_self(datum/source, mob/M) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if((M.get_active_held_item() == parent) && allow_quick_empty) + quick_empty(M) + +/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) + if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) + return FALSE + . = COMPONENT_NO_ATTACK + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + var/obj/item/I = O + if(collection_mode == COLLECT_ONE) + if(can_be_inserted(I, null, M)) + handle_item_insertion(I, null, M) + return + if(!isturf(I.loc)) + return + var/list/things = I.loc.contents.Copy() + if(collection_mode == COLLECT_SAME) + things = typecache_filter_list(things, typecacheof(I.type)) + var/len = length(things) + if(!len) + to_chat(M, "You failed to pick up anything with [parent]!") + return + var/datum/progressbar/progress = new(M, len, I.loc) + var/list/rejections = list() + while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) + stoplag(1) + progress.end_progress() + to_chat(M, "You put everything you could [insert_preposition] [parent].") + +/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) + var/atom/source_real_location = src_object.real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != source_real_location) + continue + if(user.active_storage != src_object) + if(I.on_found(user)) + break + if(can_be_inserted(I,FALSE,user)) + handle_item_insertion(I, TRUE, user) + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != thing_loc) + continue + if(I.type in rejections) // To limit bag spamming: any given type only complains once + continue + if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no + if(real_location.contents.len >= max_items) + break + rejections += I.type // therefore full bags are still a little spammy + continue + + handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. + + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/quick_empty(mob/M) + var/atom/A = parent + if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) + return + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + A.add_fingerprint(M) + to_chat(M, "You start dumping out [parent].") + var/turf/T = get_turf(A) + var/list/things = contents() + var/datum/progressbar/progress = new(M, length(things), T) + while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) + stoplag(1) + progress.end_progress() + +/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != real_location) + continue + remove_from_storage(I, target) + I.pixel_x = rand(-10,10) + I.pixel_y = rand(-10,10) + if(trigger_on_found && I.on_found()) + return FALSE + if(TICK_CHECK) + progress.update(progress.goal - length(things)) + return TRUE + progress.update(progress.goal - length(things)) + return FALSE + +/datum/component/storage/proc/do_quick_empty(atom/_target) + if(!_target) + _target = get_turf(parent) + if(usr) + hide_from(usr) + var/list/contents = contents() + var/atom/real_location = real_location() + for(var/obj/item/I in contents) + if(I.loc != real_location) + continue + remove_from_storage(I, _target) + return TRUE + +/datum/component/storage/proc/set_locked(datum/source, new_state) + locked = new_state + if(locked) + close_all() + +/datum/component/storage/proc/_process_numerical_display() + . = list() + var/atom/real_location = real_location() + for(var/obj/item/I in real_location.contents) + if(QDELETED(I)) + continue + if(!.["[I.type]-[I.name]"]) + .["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1) + else + var/datum/numbered_display/ND = .["[I.type]-[I.name]"] + ND.number++ + +//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. +/datum/component/storage/proc/orient2hud() + var/atom/real_location = real_location() + var/adjusted_contents = real_location.contents.len + + //Numbered contents display + var/list/datum/numbered_display/numbered_contents + if(display_numerical_stacking) + numbered_contents = _process_numerical_display() + adjusted_contents = numbered_contents.len + + var/columns = clamp(max_items, 1, screen_max_columns) + var/rows = clamp(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) + standard_orient_objs(rows, columns, numbered_contents) + +//This proc draws out the inventory and places the items on it. It uses the standard position. +/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) + boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" + var/cx = screen_start_x + var/cy = screen_start_y + if(islist(numerical_display_contents)) + for(var/type in numerical_display_contents) + var/datum/numbered_display/ND = numerical_display_contents[type] + ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE + ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" + ND.sample_object.layer = ABOVE_HUD_LAYER + ND.sample_object.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + else + var/atom/real_location = real_location() + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" + O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + O.maptext = "" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" + +/datum/component/storage/proc/show_to(mob/M) + if(!M.client) + return FALSE + var/atom/real_location = real_location() + if(M.active_storage != src && (M.stat == CONSCIOUS)) + for(var/obj/item/I in real_location) + if(I.on_found(M)) + return FALSE + if(M.active_storage) + M.active_storage.hide_from(M) + orient2hud() + M.client.screen |= boxes + M.client.screen |= closer + M.client.screen |= real_location.contents + M.active_storage = src + LAZYOR(is_using, M) + return TRUE + +/datum/component/storage/proc/hide_from(mob/M) + if(!M.client) + return TRUE + var/atom/real_location = real_location() + M.client.screen -= boxes + M.client.screen -= closer + M.client.screen -= real_location.contents + if(M.active_storage == src) + M.active_storage = null + LAZYREMOVE(is_using, M) + return TRUE + +/datum/component/storage/proc/close(mob/M) + hide_from(M) + +/datum/component/storage/proc/close_all() + . = FALSE + for(var/mob/M in can_see_contents()) + close(M) + . = TRUE //returns TRUE if any mobs actually got a close(M) call + +/datum/component/storage/proc/emp_act(datum/source, severity) + if(emp_shielded) + return + var/datum/component/storage/concrete/master = master() + master.emp_act(source, severity) + +//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. +//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. +/datum/component/storage/proc/orient_objs(tx, ty, mx, my) + var/atom/real_location = real_location() + var/cx = tx + var/cy = ty + boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.screen_loc = "[cx],[cy]" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx > mx) + cx = tx + cy-- + closer.screen_loc = "[mx+1],[my]" + +//Resets something that is being removed from storage. +/datum/component/storage/proc/_removal_reset(atom/movable/thing) + if(!istype(thing)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master._removal_reset(thing) + +/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) + _removal_reset(thing) + refresh_mob_views() + +//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted +/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) + if(!istype(AM)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.remove_from_storage(AM, new_location) + +/datum/component/storage/proc/refresh_mob_views() + var/list/seeing = can_see_contents() + for(var/i in seeing) + show_to(i) + return TRUE + +/datum/component/storage/proc/can_see_contents() + var/list/cansee = list() + for(var/mob/M in is_using) + if(M.active_storage == src && M.client) + cansee |= M + else + LAZYREMOVE(is_using, M) + return cansee + +//Tries to dump content +/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) + var/atom/A = parent + var/atom/dump_destination = dest_object.get_dumping_location() + if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(dump_destination.storage_contents_dump_act(src, M)) + playsound(A, "rustle", 50, TRUE, -5) + return TRUE + return FALSE + +//This proc is called when you want to place an item into the storage item. +/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) + if(istype(I, /obj/item/hand_labeler)) + var/obj/item/hand_labeler/labeler = I + if(labeler.mode) + return FALSE + . = TRUE //no afterattack + if(iscyborg(M)) + return + if(!can_be_inserted(I, FALSE, M)) + var/atom/real_location = real_location() + if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit + return TRUE + return FALSE + handle_item_insertion(I, FALSE, M) + +/datum/component/storage/proc/return_inv(recursive) + var/list/ret = list() + ret |= contents() + if(recursive) + for(var/i in ret.Copy()) + var/atom/A = i + SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) + return ret + +/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! + var/atom/real_location = real_location() + return real_location.contents.Copy() + +//Abuses the fact that lists are just references, or something like that. +/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) + if(!islist(interface)) + return FALSE + interface |= return_inv(recursive) + return TRUE + +/datum/component/storage/proc/topic_handle(datum/source, user, href_list) + if(href_list["show_valid_pocket_items"]) + handle_show_valid_items(source, user) + +/datum/component/storage/proc/handle_show_valid_items(datum/source, user) + to_chat(user, "[source] can hold: [can_hold_description]") + +/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) + set waitfor = FALSE + . = COMPONENT_NO_MOUSEDROP + if(!ismob(M)) + return + if(!over_object) + return + if(ismecha(M.loc)) // stops inventory actions in a mech + return + if(M.incapacitated() || !M.canUseStorage()) + return + var/atom/A = parent + A.add_fingerprint(M) + // this must come before the screen objects only block, dunno why it wasn't before + if(over_object == M) + user_show_to_mob(M) + if(!istype(over_object, /obj/screen)) + dump_content_at(over_object, M) + return + if(A.loc != M) + return + playsound(A, "rustle", 50, TRUE, -5) + if(istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(A, H.held_index) + return + A.add_fingerprint(M) + +/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) + var/atom/A = parent + if(!istype(M)) + return FALSE + A.add_fingerprint(M) + if(locked && !force) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(force || M.CanReach(parent, view_only = TRUE)) + show_to(M) + +/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) + if(isitem(O)) + var/obj/item/I = O + if(iscarbon(M) || isdrone(M)) + var/mob/living/L = M + if(!L.incapacitated() && I == L.get_active_held_item()) + if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. + handle_item_insertion(I, FALSE, L) + +//This proc return 1 if the item can be picked up and 0 if it can't. +//Set the stop_messages to stop it from printing messages +/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) + if(!istype(I) || (I.item_flags & ABSTRACT)) + return FALSE //Not an item + if(I == parent) + return FALSE //no paradoxes for you + var/atom/real_location = real_location() + var/atom/host = parent + if(real_location == I.loc) + return FALSE //Means the item is already in the storage item + if(locked) + if(M && !stop_messages) + host.add_fingerprint(M) + to_chat(M, "[host] seems to be locked!") + return FALSE + if(real_location.contents.len >= max_items) + if(!stop_messages) + to_chat(M, "[host] is full, make some space!") + return FALSE //Storage item is full + if(length(can_hold)) + if(!is_type_in_typecache(I, can_hold)) + if(!stop_messages) + to_chat(M, "[host] cannot hold [I]!") + return FALSE + if(is_type_in_typecache(I, cant_hold) || HAS_TRAIT(I, TRAIT_NO_STORAGE_INSERT)) //Items which this container can't hold. + if(!stop_messages) + to_chat(M, "[host] cannot hold [I]!") + return FALSE + if(I.w_class > max_w_class && !is_type_in_typecache(I, exception_hold)) + if(!stop_messages) + to_chat(M, "[I] is too big for [host]!") + return FALSE + var/datum/component/storage/biggerfish = real_location.loc.GetComponent(/datum/component/storage) + if(biggerfish && biggerfish.max_w_class < max_w_class)//return false if we are inside of another container, and that container has a smaller max_w_class than us (like if we're a bag in a box) + if(!stop_messages) + to_chat(M, "[I] can't fit in [host] while [real_location.loc] is in the way!") + return FALSE + var/sum_w_class = I.w_class + for(var/obj/item/_I in real_location) + sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. + if(sum_w_class > max_combined_w_class) + if(!stop_messages) + to_chat(M, "[I] won't fit in [host], make some space!") + return FALSE + if(isitem(host)) + var/obj/item/IP = host + var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage) + if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting) + if(!stop_messages) + to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!") + return FALSE //To prevent the stacking of same sized storage items. + if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry. + if(!stop_messages) + to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.slave_can_insert_object(src, I, stop_messages, M) + +/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) + return FALSE + +//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() +//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, +//such as when picking up all the items on a tile with one click. +/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) + var/atom/parent = src.parent + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + if(silent) + prevent_warning = TRUE + if(M) + parent.add_fingerprint(M) + . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) + +/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) + if(silent && !override) + return + if(rustle_sound) + playsound(parent, "rustle", 50, TRUE, -5) + for(var/mob/viewing in viewers(user, null)) + if(M == viewing) + to_chat(usr, "You put [I] [insert_preposition]to [parent].") + else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) + else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) + +/datum/component/storage/proc/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + +/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) + if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) + return FALSE + return handle_item_insertion(I, silent, M) + +/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) + return can_be_inserted(I, silent, M) + +/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) + return user_show_to_mob(M, TRUE) + +/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) + return user_show_to_mob(showto, force) + +/datum/component/storage/proc/on_check() + return TRUE + +/datum/component/storage/proc/check_locked() + return locked + +/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) + if(!force) + if(check_adjacent) + if(!user || !user.CanReach(destination) || !user.CanReach(parent)) + return FALSE + var/list/taking = typecache_filter_list(contents(), typecacheof(type)) + if(taking.len > amount) + taking.len = amount + if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. + for(var/i in taking) + if(remove_from_storage(i, destination)) + inserted |= i + else + for(var/i in taking) + remove_from_storage(i, destination) + return TRUE + +/datum/component/storage/proc/remaining_space_items() + var/atom/real_location = real_location() + return max(0, max_items - real_location.contents.len) + +/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) + var/atom/real_location = real_location() + if(!force) + amount = min(remaining_space_items(), amount) + for(var/i in 1 to amount) + if(!handle_item_insertion(new type(real_location), TRUE)) + return i > 1 //return TRUE only if at least one insertion has been successful. + if(CHECK_TICK) + if(QDELETED(src)) + return TRUE + return TRUE + +/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) + var/atom/A = parent + if(!attack_hand_interact) + return + if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item + user.active_storage.close(user) + close(user) + . = COMPONENT_NO_ATTACK_HAND + return + + if(rustle_sound) + playsound(A, "rustle", 50, TRUE, -5) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.l_store = null + return + if(H.r_store == A && !H.get_active_held_item()) + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.r_store = null + return + + if(A.loc == user) + . = COMPONENT_NO_ATTACK_HAND + if(locked) + to_chat(user, "[parent] seems to be locked!") + else + show_to(user) + +/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) + update_actions() + for(var/mob/M in can_see_contents() - user) + close(M) + +/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) + if(!(AM in real_location())) + return FALSE + return remove_from_storage(AM, new_loc) + +/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) + return do_quick_empty(loctarget) + +/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) + return hide_from(target) + +/datum/component/storage/proc/on_alt_click(datum/source, mob/user) + if(!isliving(user) || !user.CanReach(parent) || user.incapacitated()) + return + if(locked) + to_chat(user, "[parent] seems to be locked!") + return + + var/atom/A = parent + if(!quickdraw) + A.add_fingerprint(user) + user_show_to_mob(user) + playsound(A, "rustle", 50, TRUE, -5) + return + + var/obj/item/I = locate() in real_location() + if(!I) + return + A.add_fingerprint(user) + remove_from_storage(I, get_turf(user)) + if(!user.put_in_hands(I)) + to_chat(user, "You fumble for [I] and it falls on the floor.") + return + user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") + +/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source) + gather_mode_switch(source.owner) + return COMPONENT_ACTION_BLOCK_TRIGGER + +/datum/component/storage/proc/gather_mode_switch(mob/user) + collection_mode = (collection_mode+1)%3 + switch(collection_mode) + if(COLLECT_SAME) + to_chat(user, "[parent] now picks up all items of a single type at once.") + if(COLLECT_EVERYTHING) + to_chat(user, "[parent] now picks up all items in a tile at once.") + if(COLLECT_ONE) + to_chat(user, "[parent] now picks up one item at a time.") diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm index c138a2a377f..2e3dce74558 100644 --- a/code/datums/components/wet_floor.dm +++ b/code/datums/components/wet_floor.dm @@ -1,206 +1,206 @@ -/datum/component/wet_floor - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - can_transfer = TRUE - var/highest_strength = TURF_DRY - var/lube_flags = NONE //why do we have this? - var/list/time_left_list //In deciseconds. - var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor") - var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor") - var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static") - var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static") - var/current_overlay - var/permanent = FALSE - var/last_process = 0 - -/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent) - if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component - add_wet(arglist(args.Copy(3))) - else //We are being passed in a full blown component - var/datum/component/wet_floor/WF = newcomp //Lets make an assumption - if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us. - CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!") - for(var/i in WF.time_left_list) - add_wet(text2num(i), WF.time_left_list[i]) - -/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE) - if(!isopenturf(parent)) - return COMPONENT_INCOMPATIBLE - add_wet(strength, duration_minimum, duration_add, duration_maximum) - permanent = _permanent - if(!permanent) - START_PROCESSING(SSwet_floors, src) - addtimer(CALLBACK(src, .proc/gc, TRUE), 1) //GC after initialization. - last_process = world.time - -/datum/component/wet_floor/RegisterWithParent() - RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet) - RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry) - -/datum/component/wet_floor/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY)) - -/datum/component/wet_floor/Destroy() - STOP_PROCESSING(SSwet_floors, src) - var/turf/T = parent - qdel(T.GetComponent(/datum/component/slippery)) - if(istype(T)) //If this is false there is so many things wrong with it. - T.cut_overlay(current_overlay) - else - stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!") - return ..() - -/datum/component/wet_floor/proc/update_overlay() - var/intended - if(!istype(parent, /turf/open/floor)) - intended = generic_turf_overlay - else - switch(highest_strength) - if(TURF_WET_PERMAFROST) - intended = permafrost_overlay - if(TURF_WET_ICE) - intended = ice_overlay - else - intended = water_overlay - if(current_overlay != intended) - var/turf/T = parent - T.cut_overlay(current_overlay) - T.add_overlay(intended) - current_overlay = intended - -/datum/component/wet_floor/proc/AfterSlip(mob/living/L) - if(highest_strength == TURF_WET_LUBE) - L.confused = max(L.confused, 8) - -/datum/component/wet_floor/proc/update_flags() - var/intensity - lube_flags = NONE - switch(highest_strength) - if(TURF_WET_WATER) - intensity = 60 - lube_flags = NO_SLIP_WHEN_WALKING - if(TURF_WET_LUBE) - intensity = 80 - lube_flags = SLIDE | GALOSHES_DONT_HELP - if(TURF_WET_ICE) - intensity = 120 - lube_flags = SLIDE | GALOSHES_DONT_HELP - if(TURF_WET_PERMAFROST) - intensity = 120 - lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP - if(TURF_WET_SUPERLUBE) - intensity = 120 - lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING - else - qdel(parent.GetComponent(/datum/component/slippery)) - return - - parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip)) - -/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY) - for(var/i in time_left_list) - if(text2num(i) <= strength) - time_left_list[i] = max(0, time_left_list[i] - duration_decrease) - if(immediate) - check() - -/datum/component/wet_floor/proc/max_time_left() - . = 0 - for(var/i in time_left_list) - . = max(., time_left_list[i]) - -/datum/component/wet_floor/process() - var/turf/open/T = parent - var/diff = world.time - last_process - var/decrease = 0 - var/t = T.GetTemperature() - switch(t) - if(-INFINITY to T0C) - add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice! - if(T0C to T0C + 100) - decrease = ((T.air.temperature - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio) - if(T0C + 100 to INFINITY) - decrease = INFINITY - decrease = max(0, decrease) - if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water! - for(var/obj/O in T.contents) - if(O.obj_flags & FROZEN) - O.make_unfrozen() - add_wet(TURF_WET_WATER, max_time_left()) - dry(null, TURF_WET_ICE) - dry(null, ALL, FALSE, decrease) - check() - last_process = world.time - -/datum/component/wet_floor/proc/update_strength() - highest_strength = 0 //Not bitflag. - for(var/i in time_left_list) - highest_strength = max(highest_strength, text2num(i)) - -/datum/component/wet_floor/proc/is_wet() - . = 0 - for(var/i in time_left_list) - . |= text2num(i) - -/datum/component/wet_floor/PreTransfer() - var/turf/O = parent - O.cut_overlay(current_overlay) - //That turf is no longer slippery, we're out of here - //Slippery components don't transfer due to callbacks - qdel(O.GetComponent(/datum/component/slippery)) - -/datum/component/wet_floor/PostTransfer() - if(!isopenturf(parent)) - return COMPONENT_INCOMPATIBLE - var/turf/T = parent - T.add_overlay(current_overlay) - //Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent) - update_flags() - - //NB it's possible we get deleted after this, due to inherit - -/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE) - var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE) - if(duration_minimum <= 0 || !type) - return FALSE - if(type in allowed_types) - return _do_add_wet(type, duration_minimum, duration_add, duration_maximum) - else - . = NONE - for(var/i in allowed_types) - if(!(type & i)) - continue - . |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum) - if(_permanent) - permanent = TRUE - STOP_PROCESSING(SSwet_floors, src) - -/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum) - var/time = 0 - if(LAZYACCESS(time_left_list, "[type]")) - time = clamp(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum) - else - time = min(duration_minimum, duration_maximum) - LAZYSET(time_left_list, "[type]", time) - check(TRUE) - return TRUE - -/datum/component/wet_floor/proc/gc(on_init = FALSE) - if(!LAZYLEN(time_left_list)) - if(on_init) - var/turf/T = parent - stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].") - qdel(src) - return TRUE - return FALSE - -/datum/component/wet_floor/proc/check(force_update = FALSE) - var/changed = FALSE - for(var/i in time_left_list) - if(time_left_list[i] <= 0) - time_left_list -= i - changed = TRUE - if(changed || force_update) - update_strength() - update_overlay() - update_flags() - gc() +/datum/component/wet_floor + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + can_transfer = TRUE + var/highest_strength = TURF_DRY + var/lube_flags = NONE //why do we have this? + var/list/time_left_list //In deciseconds. + var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor") + var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor") + var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static") + var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static") + var/current_overlay + var/permanent = FALSE + var/last_process = 0 + +/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent) + if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component + add_wet(arglist(args.Copy(3))) + else //We are being passed in a full blown component + var/datum/component/wet_floor/WF = newcomp //Lets make an assumption + if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us. + CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!") + for(var/i in WF.time_left_list) + add_wet(text2num(i), WF.time_left_list[i]) + +/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE) + if(!isopenturf(parent)) + return COMPONENT_INCOMPATIBLE + add_wet(strength, duration_minimum, duration_add, duration_maximum) + permanent = _permanent + if(!permanent) + START_PROCESSING(SSwet_floors, src) + addtimer(CALLBACK(src, .proc/gc, TRUE), 1) //GC after initialization. + last_process = world.time + +/datum/component/wet_floor/RegisterWithParent() + RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet) + RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry) + +/datum/component/wet_floor/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY)) + +/datum/component/wet_floor/Destroy() + STOP_PROCESSING(SSwet_floors, src) + var/turf/T = parent + qdel(T.GetComponent(/datum/component/slippery)) + if(istype(T)) //If this is false there is so many things wrong with it. + T.cut_overlay(current_overlay) + else + stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!") + return ..() + +/datum/component/wet_floor/proc/update_overlay() + var/intended + if(!istype(parent, /turf/open/floor)) + intended = generic_turf_overlay + else + switch(highest_strength) + if(TURF_WET_PERMAFROST) + intended = permafrost_overlay + if(TURF_WET_ICE) + intended = ice_overlay + else + intended = water_overlay + if(current_overlay != intended) + var/turf/T = parent + T.cut_overlay(current_overlay) + T.add_overlay(intended) + current_overlay = intended + +/datum/component/wet_floor/proc/AfterSlip(mob/living/L) + if(highest_strength == TURF_WET_LUBE) + L.confused = max(L.confused, 8) + +/datum/component/wet_floor/proc/update_flags() + var/intensity + lube_flags = NONE + switch(highest_strength) + if(TURF_WET_WATER) + intensity = 60 + lube_flags = NO_SLIP_WHEN_WALKING + if(TURF_WET_LUBE) + intensity = 80 + lube_flags = SLIDE | GALOSHES_DONT_HELP + if(TURF_WET_ICE) + intensity = 120 + lube_flags = SLIDE | GALOSHES_DONT_HELP + if(TURF_WET_PERMAFROST) + intensity = 120 + lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP + if(TURF_WET_SUPERLUBE) + intensity = 120 + lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING + else + qdel(parent.GetComponent(/datum/component/slippery)) + return + + parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip)) + +/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY) + for(var/i in time_left_list) + if(text2num(i) <= strength) + time_left_list[i] = max(0, time_left_list[i] - duration_decrease) + if(immediate) + check() + +/datum/component/wet_floor/proc/max_time_left() + . = 0 + for(var/i in time_left_list) + . = max(., time_left_list[i]) + +/datum/component/wet_floor/process() + var/turf/open/T = parent + var/diff = world.time - last_process + var/decrease = 0 + var/t = T.GetTemperature() + switch(t) + if(-INFINITY to T0C) + add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice! + if(T0C to T0C + 100) + decrease = ((T.air.temperature - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio) + if(T0C + 100 to INFINITY) + decrease = INFINITY + decrease = max(0, decrease) + if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water! + for(var/obj/O in T.contents) + if(O.obj_flags & FROZEN) + O.make_unfrozen() + add_wet(TURF_WET_WATER, max_time_left()) + dry(null, TURF_WET_ICE) + dry(null, ALL, FALSE, decrease) + check() + last_process = world.time + +/datum/component/wet_floor/proc/update_strength() + highest_strength = 0 //Not bitflag. + for(var/i in time_left_list) + highest_strength = max(highest_strength, text2num(i)) + +/datum/component/wet_floor/proc/is_wet() + . = 0 + for(var/i in time_left_list) + . |= text2num(i) + +/datum/component/wet_floor/PreTransfer() + var/turf/O = parent + O.cut_overlay(current_overlay) + //That turf is no longer slippery, we're out of here + //Slippery components don't transfer due to callbacks + qdel(O.GetComponent(/datum/component/slippery)) + +/datum/component/wet_floor/PostTransfer() + if(!isopenturf(parent)) + return COMPONENT_INCOMPATIBLE + var/turf/T = parent + T.add_overlay(current_overlay) + //Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent) + update_flags() + + //NB it's possible we get deleted after this, due to inherit + +/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE) + var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE) + if(duration_minimum <= 0 || !type) + return FALSE + if(type in allowed_types) + return _do_add_wet(type, duration_minimum, duration_add, duration_maximum) + else + . = NONE + for(var/i in allowed_types) + if(!(type & i)) + continue + . |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum) + if(_permanent) + permanent = TRUE + STOP_PROCESSING(SSwet_floors, src) + +/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum) + var/time = 0 + if(LAZYACCESS(time_left_list, "[type]")) + time = clamp(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum) + else + time = min(duration_minimum, duration_maximum) + LAZYSET(time_left_list, "[type]", time) + check(TRUE) + return TRUE + +/datum/component/wet_floor/proc/gc(on_init = FALSE) + if(!LAZYLEN(time_left_list)) + if(on_init) + var/turf/T = parent + stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].") + qdel(src) + return TRUE + return FALSE + +/datum/component/wet_floor/proc/check(force_update = FALSE) + var/changed = FALSE + for(var/i in time_left_list) + if(time_left_list[i] <= 0) + time_left_list -= i + changed = TRUE + if(changed || force_update) + update_strength() + update_overlay() + update_flags() + gc() diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index 515fd264a7e..e10681dcbf4 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -1,313 +1,313 @@ -//TODO: someone please get rid of this shit -/datum/datacore - var/list/medical = list() - var/medicalPrintCount = 0 - var/list/general = list() - var/list/security = list() - var/securityPrintCount = 0 - var/securityCrimeCounter = 0 - ///This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character(). - var/list/locked = list() - -/datum/data - var/name = "data" - -/datum/data/record - name = "record" - var/list/fields = list() - -/datum/data/record/Destroy() - if(src in GLOB.data_core.medical) - GLOB.data_core.medical -= src - if(src in GLOB.data_core.security) - GLOB.data_core.security -= src - if(src in GLOB.data_core.general) - GLOB.data_core.general -= src - if(src in GLOB.data_core.locked) - GLOB.data_core.locked -= src - . = ..() - -/datum/data/crime - name = "crime" - var/crimeName = "" - var/crimeDetails = "" - var/author = "" - var/time = "" - var/fine = 0 - var/paid = 0 - var/dataId = 0 - -/datum/datacore/proc/createCrimeEntry(cname = "", cdetails = "", author = "", time = "", fine = 0) - var/datum/data/crime/c = new /datum/data/crime - c.crimeName = cname - c.crimeDetails = cdetails - c.author = author - c.time = time - c.fine = fine - c.paid = 0 - c.dataId = ++securityCrimeCounter - return c - -/datum/datacore/proc/addCitation(id = "", datum/data/crime/crime) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["citation"] - crimes |= crime - return - -/datum/datacore/proc/removeCitation(id, cDataId) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["citation"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crimes -= crime - return - -/datum/datacore/proc/payCitation(id, cDataId, amount) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["citation"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crime.paid = crime.paid + amount - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SEC) - D.adjust_money(amount) - return - -/** - * Adds crime to security record. - * - * Is used to add single crime to someone's security record. - * Arguments: - * * id - record id. - * * datum/data/crime/crime - premade array containing every variable, usually created by createCrimeEntry. - */ -/datum/datacore/proc/addCrime(id = "", datum/data/crime/crime) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["crim"] - crimes |= crime - return - -/** - * Deletes crime from security record. - * - * Is used to delete single crime to someone's security record. - * Arguments: - * * id - record id. - * * cDataId - id of already existing crime. - */ -/datum/datacore/proc/removeCrime(id, cDataId) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["crim"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crimes -= crime - return - -/** - * Adds details to a crime. - * - * Is used to add or replace details to already existing crime. - * Arguments: - * * id - record id. - * * cDataId - id of already existing crime. - * * details - data you want to add. - */ -/datum/datacore/proc/addCrimeDetails(id, cDataId, details) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["crim"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crime.crimeDetails = details - return - -/datum/datacore/proc/manifest() - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/N = i - if(N.new_character) - log_manifest(N.ckey,N.new_character.mind,N.new_character) - if(ishuman(N.new_character)) - manifest_inject(N.new_character, N.client) - CHECK_TICK - -/datum/datacore/proc/manifest_modify(name, assignment) - var/datum/data/record/foundrecord = find_record("name", name, GLOB.data_core.general) - if(foundrecord) - foundrecord.fields["rank"] = assignment - -/datum/datacore/proc/get_manifest() - var/list/manifest_out = list() - var/list/departments = list( - "Command" = GLOB.command_positions, - "Security" = GLOB.security_positions, - "Engineering" = GLOB.engineering_positions, - "Medical" = GLOB.medical_positions, - "Science" = GLOB.science_positions, - "Supply" = GLOB.supply_positions, - "Service" = GLOB.service_positions, - "Silicon" = GLOB.nonhuman_positions - ) - for(var/datum/data/record/t in GLOB.data_core.general) - var/name = t.fields["name"] - var/rank = t.fields["rank"] - var/has_department = FALSE - for(var/department in departments) - var/list/jobs = departments[department] - if(rank in jobs) - if(!manifest_out[department]) - manifest_out[department] = list() - manifest_out[department] += list(list( - "name" = name, - "rank" = rank - )) - has_department = TRUE - break - if(!has_department) - if(!manifest_out["Misc"]) - manifest_out["Misc"] = list() - manifest_out["Misc"] += list(list( - "name" = name, - "rank" = rank - )) - return manifest_out - -/datum/datacore/proc/get_manifest_html(monochrome = FALSE) - var/list/manifest = get_manifest() - var/dat = {" - - - - "} - for(var/department in manifest) - var/list/entries = manifest[department] - dat += "" - //JUST - var/even = FALSE - for(var/entry in entries) - var/list/entry_list = entry - dat += "" - even = !even - - dat += "
    NameRank
    [department]
    [entry_list["name"]][entry_list["rank"]]
    " - dat = replacetext(dat, "\n", "") - dat = replacetext(dat, "\t", "") - return dat - - -/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H, client/C) - set waitfor = FALSE - var/static/list/show_directions = list(SOUTH, WEST) - if(H.mind && (H.mind.assigned_role != H.mind.special_role)) - var/assignment - if(H.mind.assigned_role) - assignment = H.mind.assigned_role - else if(H.job) - assignment = H.job - else - assignment = "Unassigned" - - var/static/record_id_num = 1001 - var/id = num2hex(record_id_num++,6) - if(!C) - C = H.client - var/image = get_id_photo(H, C, show_directions) - var/datum/picture/pf = new - var/datum/picture/ps = new - pf.picture_name = "[H]" - ps.picture_name = "[H]" - pf.picture_desc = "This is [H]." - ps.picture_desc = "This is [H]." - pf.picture_image = icon(image, dir = SOUTH) - ps.picture_image = icon(image, dir = WEST) - var/obj/item/photo/photo_front = new(null, pf) - var/obj/item/photo/photo_side = new(null, ps) - - //These records should ~really~ be merged or something - //General Record - var/datum/data/record/G = new() - G.fields["id"] = id - G.fields["name"] = H.real_name - G.fields["rank"] = assignment - G.fields["age"] = H.age - G.fields["species"] = H.dna.species.name - G.fields["fingerprint"] = md5(H.dna.uni_identity) - G.fields["p_stat"] = "Active" - G.fields["m_stat"] = "Stable" - G.fields["gender"] = H.gender - if(H.gender == "male") - G.fields["gender"] = "Male" - else if(H.gender == "female") - G.fields["gender"] = "Female" - else - G.fields["gender"] = "Other" - G.fields["photo_front"] = photo_front - G.fields["photo_side"] = photo_side - general += G - - //Medical Record - var/datum/data/record/M = new() - M.fields["id"] = id - M.fields["name"] = H.real_name - M.fields["blood_type"] = H.dna.blood_type - M.fields["b_dna"] = H.dna.unique_enzymes - M.fields["mi_dis"] = H.get_quirk_string(!medical, CAT_QUIRK_MINOR_DISABILITY) - M.fields["mi_dis_d"] = H.get_quirk_string(medical, CAT_QUIRK_MINOR_DISABILITY) - M.fields["ma_dis"] = H.get_quirk_string(!medical, CAT_QUIRK_MAJOR_DISABILITY) - M.fields["ma_dis_d"] = H.get_quirk_string(medical, CAT_QUIRK_MAJOR_DISABILITY) - M.fields["cdi"] = "None" - M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - M.fields["notes"] = H.get_quirk_string(!medical, CAT_QUIRK_NOTES) - M.fields["notes_d"] = H.get_quirk_string(medical, CAT_QUIRK_NOTES) - medical += M - - //Security Record - var/datum/data/record/S = new() - S.fields["id"] = id - S.fields["name"] = H.real_name - S.fields["criminal"] = "None" - S.fields["citation"] = list() - S.fields["crim"] = list() - S.fields["notes"] = "No notes." - security += S - - //Locked Record - var/datum/data/record/L = new() - L.fields["id"] = md5("[H.real_name][H.mind.assigned_role]") //surely this should just be id, like the others? - L.fields["name"] = H.real_name - L.fields["rank"] = H.mind.assigned_role - L.fields["age"] = H.age - L.fields["gender"] = H.gender - if(H.gender == "male") - G.fields["gender"] = "Male" - else if(H.gender == "female") - G.fields["gender"] = "Female" - else - G.fields["gender"] = "Other" - L.fields["blood_type"] = H.dna.blood_type - L.fields["b_dna"] = H.dna.unique_enzymes - L.fields["identity"] = H.dna.uni_identity - L.fields["species"] = H.dna.species.type - L.fields["features"] = H.dna.features - L.fields["image"] = image - L.fields["mindref"] = H.mind - locked += L - return - -/datum/datacore/proc/get_id_photo(mob/living/carbon/human/H, client/C, show_directions = list(SOUTH)) - var/datum/job/J = SSjob.GetJob(H.mind.assigned_role) - var/datum/preferences/P - if(!C) - C = H.client - if(C) - P = C.prefs - return get_flat_human_icon(null, J, P, DUMMY_HUMAN_SLOT_MANIFEST, show_directions) +//TODO: someone please get rid of this shit +/datum/datacore + var/list/medical = list() + var/medicalPrintCount = 0 + var/list/general = list() + var/list/security = list() + var/securityPrintCount = 0 + var/securityCrimeCounter = 0 + ///This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character(). + var/list/locked = list() + +/datum/data + var/name = "data" + +/datum/data/record + name = "record" + var/list/fields = list() + +/datum/data/record/Destroy() + if(src in GLOB.data_core.medical) + GLOB.data_core.medical -= src + if(src in GLOB.data_core.security) + GLOB.data_core.security -= src + if(src in GLOB.data_core.general) + GLOB.data_core.general -= src + if(src in GLOB.data_core.locked) + GLOB.data_core.locked -= src + . = ..() + +/datum/data/crime + name = "crime" + var/crimeName = "" + var/crimeDetails = "" + var/author = "" + var/time = "" + var/fine = 0 + var/paid = 0 + var/dataId = 0 + +/datum/datacore/proc/createCrimeEntry(cname = "", cdetails = "", author = "", time = "", fine = 0) + var/datum/data/crime/c = new /datum/data/crime + c.crimeName = cname + c.crimeDetails = cdetails + c.author = author + c.time = time + c.fine = fine + c.paid = 0 + c.dataId = ++securityCrimeCounter + return c + +/datum/datacore/proc/addCitation(id = "", datum/data/crime/crime) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["citation"] + crimes |= crime + return + +/datum/datacore/proc/removeCitation(id, cDataId) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["citation"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crimes -= crime + return + +/datum/datacore/proc/payCitation(id, cDataId, amount) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["citation"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crime.paid = crime.paid + amount + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SEC) + D.adjust_money(amount) + return + +/** + * Adds crime to security record. + * + * Is used to add single crime to someone's security record. + * Arguments: + * * id - record id. + * * datum/data/crime/crime - premade array containing every variable, usually created by createCrimeEntry. + */ +/datum/datacore/proc/addCrime(id = "", datum/data/crime/crime) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["crim"] + crimes |= crime + return + +/** + * Deletes crime from security record. + * + * Is used to delete single crime to someone's security record. + * Arguments: + * * id - record id. + * * cDataId - id of already existing crime. + */ +/datum/datacore/proc/removeCrime(id, cDataId) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["crim"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crimes -= crime + return + +/** + * Adds details to a crime. + * + * Is used to add or replace details to already existing crime. + * Arguments: + * * id - record id. + * * cDataId - id of already existing crime. + * * details - data you want to add. + */ +/datum/datacore/proc/addCrimeDetails(id, cDataId, details) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["crim"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crime.crimeDetails = details + return + +/datum/datacore/proc/manifest() + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/N = i + if(N.new_character) + log_manifest(N.ckey,N.new_character.mind,N.new_character) + if(ishuman(N.new_character)) + manifest_inject(N.new_character, N.client) + CHECK_TICK + +/datum/datacore/proc/manifest_modify(name, assignment) + var/datum/data/record/foundrecord = find_record("name", name, GLOB.data_core.general) + if(foundrecord) + foundrecord.fields["rank"] = assignment + +/datum/datacore/proc/get_manifest() + var/list/manifest_out = list() + var/list/departments = list( + "Command" = GLOB.command_positions, + "Security" = GLOB.security_positions, + "Engineering" = GLOB.engineering_positions, + "Medical" = GLOB.medical_positions, + "Science" = GLOB.science_positions, + "Supply" = GLOB.supply_positions, + "Service" = GLOB.service_positions, + "Silicon" = GLOB.nonhuman_positions + ) + for(var/datum/data/record/t in GLOB.data_core.general) + var/name = t.fields["name"] + var/rank = t.fields["rank"] + var/has_department = FALSE + for(var/department in departments) + var/list/jobs = departments[department] + if(rank in jobs) + if(!manifest_out[department]) + manifest_out[department] = list() + manifest_out[department] += list(list( + "name" = name, + "rank" = rank + )) + has_department = TRUE + break + if(!has_department) + if(!manifest_out["Misc"]) + manifest_out["Misc"] = list() + manifest_out["Misc"] += list(list( + "name" = name, + "rank" = rank + )) + return manifest_out + +/datum/datacore/proc/get_manifest_html(monochrome = FALSE) + var/list/manifest = get_manifest() + var/dat = {" + + + + "} + for(var/department in manifest) + var/list/entries = manifest[department] + dat += "" + //JUST + var/even = FALSE + for(var/entry in entries) + var/list/entry_list = entry + dat += "" + even = !even + + dat += "
    NameRank
    [department]
    [entry_list["name"]][entry_list["rank"]]
    " + dat = replacetext(dat, "\n", "") + dat = replacetext(dat, "\t", "") + return dat + + +/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H, client/C) + set waitfor = FALSE + var/static/list/show_directions = list(SOUTH, WEST) + if(H.mind && (H.mind.assigned_role != H.mind.special_role)) + var/assignment + if(H.mind.assigned_role) + assignment = H.mind.assigned_role + else if(H.job) + assignment = H.job + else + assignment = "Unassigned" + + var/static/record_id_num = 1001 + var/id = num2hex(record_id_num++,6) + if(!C) + C = H.client + var/image = get_id_photo(H, C, show_directions) + var/datum/picture/pf = new + var/datum/picture/ps = new + pf.picture_name = "[H]" + ps.picture_name = "[H]" + pf.picture_desc = "This is [H]." + ps.picture_desc = "This is [H]." + pf.picture_image = icon(image, dir = SOUTH) + ps.picture_image = icon(image, dir = WEST) + var/obj/item/photo/photo_front = new(null, pf) + var/obj/item/photo/photo_side = new(null, ps) + + //These records should ~really~ be merged or something + //General Record + var/datum/data/record/G = new() + G.fields["id"] = id + G.fields["name"] = H.real_name + G.fields["rank"] = assignment + G.fields["age"] = H.age + G.fields["species"] = H.dna.species.name + G.fields["fingerprint"] = md5(H.dna.uni_identity) + G.fields["p_stat"] = "Active" + G.fields["m_stat"] = "Stable" + G.fields["gender"] = H.gender + if(H.gender == "male") + G.fields["gender"] = "Male" + else if(H.gender == "female") + G.fields["gender"] = "Female" + else + G.fields["gender"] = "Other" + G.fields["photo_front"] = photo_front + G.fields["photo_side"] = photo_side + general += G + + //Medical Record + var/datum/data/record/M = new() + M.fields["id"] = id + M.fields["name"] = H.real_name + M.fields["blood_type"] = H.dna.blood_type + M.fields["b_dna"] = H.dna.unique_enzymes + M.fields["mi_dis"] = H.get_quirk_string(!medical, CAT_QUIRK_MINOR_DISABILITY) + M.fields["mi_dis_d"] = H.get_quirk_string(medical, CAT_QUIRK_MINOR_DISABILITY) + M.fields["ma_dis"] = H.get_quirk_string(!medical, CAT_QUIRK_MAJOR_DISABILITY) + M.fields["ma_dis_d"] = H.get_quirk_string(medical, CAT_QUIRK_MAJOR_DISABILITY) + M.fields["cdi"] = "None" + M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + M.fields["notes"] = H.get_quirk_string(!medical, CAT_QUIRK_NOTES) + M.fields["notes_d"] = H.get_quirk_string(medical, CAT_QUIRK_NOTES) + medical += M + + //Security Record + var/datum/data/record/S = new() + S.fields["id"] = id + S.fields["name"] = H.real_name + S.fields["criminal"] = "None" + S.fields["citation"] = list() + S.fields["crim"] = list() + S.fields["notes"] = "No notes." + security += S + + //Locked Record + var/datum/data/record/L = new() + L.fields["id"] = md5("[H.real_name][H.mind.assigned_role]") //surely this should just be id, like the others? + L.fields["name"] = H.real_name + L.fields["rank"] = H.mind.assigned_role + L.fields["age"] = H.age + L.fields["gender"] = H.gender + if(H.gender == "male") + G.fields["gender"] = "Male" + else if(H.gender == "female") + G.fields["gender"] = "Female" + else + G.fields["gender"] = "Other" + L.fields["blood_type"] = H.dna.blood_type + L.fields["b_dna"] = H.dna.unique_enzymes + L.fields["identity"] = H.dna.uni_identity + L.fields["species"] = H.dna.species.type + L.fields["features"] = H.dna.features + L.fields["image"] = image + L.fields["mindref"] = H.mind + locked += L + return + +/datum/datacore/proc/get_id_photo(mob/living/carbon/human/H, client/C, show_directions = list(SOUTH)) + var/datum/job/J = SSjob.GetJob(H.mind.assigned_role) + var/datum/preferences/P + if(!C) + C = H.client + if(C) + P = C.prefs + return get_flat_human_icon(null, J, P, DUMMY_HUMAN_SLOT_MANIFEST, show_directions) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index bf0f877ed01..1061bc3e778 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -1,262 +1,262 @@ -/** - * The absolute base class for everything - * - * A datum instantiated has no physical world prescence, use an atom if you want something - * that actually lives in the world - * - * Be very mindful about adding variables to this class, they are inherited by every single - * thing in the entire game, and so you can easily cause memory usage to rise a lot with careless - * use of variables at this level - */ -/datum - /** - * Tick count time when this object was destroyed. - * - * If this is non zero then the object has been garbage collected and is awaiting either - * a hard del by the GC subsystme, or to be autocollected (if it has no references) - */ - var/gc_destroyed - - /// Active timers with this datum as the target - var/list/active_timers - /// Status traits attached to this datum - var/list/status_traits - - /** - * Components attached to this datum - * - * Lazy associated list in the structure of `type:component/list of components` - */ - var/list/datum_components - /** - * Any datum registered to receive signals from this datum is in this list - * - * Lazy associated list in the structure of `signal:registree/list of registrees` - */ - var/list/comp_lookup - /// Lazy associated list in the structure of `signals:proctype` that are run when the datum receives that signal - var/list/list/datum/callback/signal_procs - /** - * Is this datum capable of sending signals? - * - * Set to true when a signal has been registered - */ - var/signal_enabled = FALSE - - /// Datum level flags - var/datum_flags = NONE - - /// A weak reference to another datum - var/datum/weakref/weak_reference - - /* - * Lazy associative list of currently active cooldowns. - * - * cooldowns [ COOLDOWN_INDEX ] = add_timer() - * add_timer() returns the truthy value of -1 when not stoppable, and else a truthy numeric index - */ - var/list/cooldowns - -#ifdef TESTING - var/running_find_references - var/last_find_references = 0 -#endif - -#ifdef DATUMVAR_DEBUGGING_MODE - var/list/cached_vars -#endif - -/** - * Called when a href for this datum is clicked - * - * Sends a [COMSIG_TOPIC] signal - */ -/datum/Topic(href, href_list[]) - ..() - SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list) - -/** - * Default implementation of clean-up code. - * - * This should be overridden to remove all references pointing to the object being destroyed, if - * you do override it, make sure to call the parent and return it's return value by default - * - * Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion; - * in most cases this is [QDEL_HINT_QUEUE]. - * - * The base case is responsible for doing the following - * * Erasing timers pointing to this datum - * * Erasing compenents on this datum - * * Notifying datums listening to signals from this datum that we are going away - * - * Returns [QDEL_HINT_QUEUE] - */ -/datum/proc/Destroy(force=FALSE, ...) - SHOULD_CALL_PARENT(1) - tag = null - datum_flags &= ~DF_USE_TAG //In case something tries to REF us - weak_reference = null //ensure prompt GCing of weakref. - - var/list/timers = active_timers - active_timers = null - for(var/thing in timers) - var/datum/timedevent/timer = thing - if (timer.spent) - continue - qdel(timer) - - //BEGIN: ECS SHIT - signal_enabled = FALSE - - var/list/dc = datum_components - if(dc) - var/all_components = dc[/datum/component] - if(length(all_components)) - for(var/I in all_components) - var/datum/component/C = I - qdel(C, FALSE, TRUE) - else - var/datum/component/C = all_components - qdel(C, FALSE, TRUE) - dc.Cut() - - var/list/lookup = comp_lookup - if(lookup) - for(var/sig in lookup) - var/list/comps = lookup[sig] - if(length(comps)) - for(var/i in comps) - var/datum/component/comp = i - comp.UnregisterSignal(src, sig) - else - var/datum/component/comp = comps - comp.UnregisterSignal(src, sig) - comp_lookup = lookup = null - - for(var/target in signal_procs) - UnregisterSignal(target, signal_procs[target]) - //END: ECS SHIT - - return QDEL_HINT_QUEUE - -#ifdef DATUMVAR_DEBUGGING_MODE -/datum/proc/save_vars() - cached_vars = list() - for(var/i in vars) - if(i == "cached_vars") - continue - cached_vars[i] = vars[i] - -/datum/proc/check_changed_vars() - . = list() - for(var/i in vars) - if(i == "cached_vars") - continue - if(cached_vars[i] != vars[i]) - .[i] = list(cached_vars[i], vars[i]) - -/datum/proc/txt_changed_vars() - var/list/l = check_changed_vars() - var/t = "[src]([REF(src)]) changed vars:" - for(var/i in l) - t += "\"[i]\" \[[l[i][1]]\] --> \[[l[i][2]]\] " - t += "." - -/datum/proc/to_chat_check_changed_vars(target = world) - to_chat(target, txt_changed_vars()) -#endif - -///Return a LIST for serialize_datum to encode! Not the actual json! -/datum/proc/serialize_list(list/options) - CRASH("Attempted to serialize datum [src] of type [type] without serialize_list being implemented!") - -///Accepts a LIST from deserialize_datum. Should return src or another datum. -/datum/proc/deserialize_list(json, list/options) - CRASH("Attempted to deserialize datum [src] of type [type] without deserialize_list being implemented!") - -///Serializes into JSON. Does not encode type. -/datum/proc/serialize_json(list/options) - . = serialize_list(options) - if(!islist(.)) - . = null - else - . = json_encode(.) - -///Deserializes from JSON. Does not parse type. -/datum/proc/deserialize_json(list/input, list/options) - var/list/jsonlist = json_decode(input) - . = deserialize_list(jsonlist) - if(!istype(., /datum)) - . = null - -///Convert a datum into a json blob -/proc/json_serialize_datum(datum/D, list/options) - if(!istype(D)) - return - var/list/jsonlist = D.serialize_list(options) - if(islist(jsonlist)) - jsonlist["DATUM_TYPE"] = D.type - return json_encode(jsonlist) - -/// Convert a list of json to datum -/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE) - if(!islist(jsonlist)) - if(!istext(jsonlist)) - CRASH("Invalid JSON") - jsonlist = json_decode(jsonlist) - if(!islist(jsonlist)) - CRASH("Invalid JSON") - if(!jsonlist["DATUM_TYPE"]) - return - if(!ispath(jsonlist["DATUM_TYPE"])) - if(!istext(jsonlist["DATUM_TYPE"])) - return - jsonlist["DATUM_TYPE"] = text2path(jsonlist["DATUM_TYPE"]) - if(!ispath(jsonlist["DATUM_TYPE"])) - return - if(target_type) - if(!ispath(target_type)) - return - if(strict_target_type) - if(target_type != jsonlist["DATUM_TYPE"]) - return - else if(!ispath(jsonlist["DATUM_TYPE"], target_type)) - return - var/typeofdatum = jsonlist["DATUM_TYPE"] //BYOND won't directly read if this is just put in the line below, and will instead runtime because it thinks you're trying to make a new list? - var/datum/D = new typeofdatum - var/datum/returned = D.deserialize_list(jsonlist, options) - if(!istype(returned, /datum)) - qdel(D) - else - return returned - -/** - * Callback called by a timer to end an associative-list-indexed cooldown. - * - * Arguments: - * * source - datum storing the cooldown - * * index - string index storing the cooldown on the cooldowns associative list - * - * This sends a signal reporting the cooldown end. - */ -/proc/end_cooldown(datum/source, index) - if(QDELETED(source)) - return - SEND_SIGNAL(source, COMSIG_CD_STOP(index)) - TIMER_COOLDOWN_END(source, index) - - -/** - * Proc used by stoppable timers to end a cooldown before the time has ran out. - * - * Arguments: - * * source - datum storing the cooldown - * * index - string index storing the cooldown on the cooldowns associative list - * - * This sends a signal reporting the cooldown end, passing the time left as an argument. - */ -/proc/reset_cooldown(datum/source, index) - if(QDELETED(source)) - return - SEND_SIGNAL(source, COMSIG_CD_RESET(index), S_TIMER_COOLDOWN_TIMELEFT(source, index)) - TIMER_COOLDOWN_END(source, index) +/** + * The absolute base class for everything + * + * A datum instantiated has no physical world prescence, use an atom if you want something + * that actually lives in the world + * + * Be very mindful about adding variables to this class, they are inherited by every single + * thing in the entire game, and so you can easily cause memory usage to rise a lot with careless + * use of variables at this level + */ +/datum + /** + * Tick count time when this object was destroyed. + * + * If this is non zero then the object has been garbage collected and is awaiting either + * a hard del by the GC subsystme, or to be autocollected (if it has no references) + */ + var/gc_destroyed + + /// Active timers with this datum as the target + var/list/active_timers + /// Status traits attached to this datum + var/list/status_traits + + /** + * Components attached to this datum + * + * Lazy associated list in the structure of `type:component/list of components` + */ + var/list/datum_components + /** + * Any datum registered to receive signals from this datum is in this list + * + * Lazy associated list in the structure of `signal:registree/list of registrees` + */ + var/list/comp_lookup + /// Lazy associated list in the structure of `signals:proctype` that are run when the datum receives that signal + var/list/list/datum/callback/signal_procs + /** + * Is this datum capable of sending signals? + * + * Set to true when a signal has been registered + */ + var/signal_enabled = FALSE + + /// Datum level flags + var/datum_flags = NONE + + /// A weak reference to another datum + var/datum/weakref/weak_reference + + /* + * Lazy associative list of currently active cooldowns. + * + * cooldowns [ COOLDOWN_INDEX ] = add_timer() + * add_timer() returns the truthy value of -1 when not stoppable, and else a truthy numeric index + */ + var/list/cooldowns + +#ifdef TESTING + var/running_find_references + var/last_find_references = 0 +#endif + +#ifdef DATUMVAR_DEBUGGING_MODE + var/list/cached_vars +#endif + +/** + * Called when a href for this datum is clicked + * + * Sends a [COMSIG_TOPIC] signal + */ +/datum/Topic(href, href_list[]) + ..() + SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list) + +/** + * Default implementation of clean-up code. + * + * This should be overridden to remove all references pointing to the object being destroyed, if + * you do override it, make sure to call the parent and return it's return value by default + * + * Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion; + * in most cases this is [QDEL_HINT_QUEUE]. + * + * The base case is responsible for doing the following + * * Erasing timers pointing to this datum + * * Erasing compenents on this datum + * * Notifying datums listening to signals from this datum that we are going away + * + * Returns [QDEL_HINT_QUEUE] + */ +/datum/proc/Destroy(force=FALSE, ...) + SHOULD_CALL_PARENT(1) + tag = null + datum_flags &= ~DF_USE_TAG //In case something tries to REF us + weak_reference = null //ensure prompt GCing of weakref. + + var/list/timers = active_timers + active_timers = null + for(var/thing in timers) + var/datum/timedevent/timer = thing + if (timer.spent) + continue + qdel(timer) + + //BEGIN: ECS SHIT + signal_enabled = FALSE + + var/list/dc = datum_components + if(dc) + var/all_components = dc[/datum/component] + if(length(all_components)) + for(var/I in all_components) + var/datum/component/C = I + qdel(C, FALSE, TRUE) + else + var/datum/component/C = all_components + qdel(C, FALSE, TRUE) + dc.Cut() + + var/list/lookup = comp_lookup + if(lookup) + for(var/sig in lookup) + var/list/comps = lookup[sig] + if(length(comps)) + for(var/i in comps) + var/datum/component/comp = i + comp.UnregisterSignal(src, sig) + else + var/datum/component/comp = comps + comp.UnregisterSignal(src, sig) + comp_lookup = lookup = null + + for(var/target in signal_procs) + UnregisterSignal(target, signal_procs[target]) + //END: ECS SHIT + + return QDEL_HINT_QUEUE + +#ifdef DATUMVAR_DEBUGGING_MODE +/datum/proc/save_vars() + cached_vars = list() + for(var/i in vars) + if(i == "cached_vars") + continue + cached_vars[i] = vars[i] + +/datum/proc/check_changed_vars() + . = list() + for(var/i in vars) + if(i == "cached_vars") + continue + if(cached_vars[i] != vars[i]) + .[i] = list(cached_vars[i], vars[i]) + +/datum/proc/txt_changed_vars() + var/list/l = check_changed_vars() + var/t = "[src]([REF(src)]) changed vars:" + for(var/i in l) + t += "\"[i]\" \[[l[i][1]]\] --> \[[l[i][2]]\] " + t += "." + +/datum/proc/to_chat_check_changed_vars(target = world) + to_chat(target, txt_changed_vars()) +#endif + +///Return a LIST for serialize_datum to encode! Not the actual json! +/datum/proc/serialize_list(list/options) + CRASH("Attempted to serialize datum [src] of type [type] without serialize_list being implemented!") + +///Accepts a LIST from deserialize_datum. Should return src or another datum. +/datum/proc/deserialize_list(json, list/options) + CRASH("Attempted to deserialize datum [src] of type [type] without deserialize_list being implemented!") + +///Serializes into JSON. Does not encode type. +/datum/proc/serialize_json(list/options) + . = serialize_list(options) + if(!islist(.)) + . = null + else + . = json_encode(.) + +///Deserializes from JSON. Does not parse type. +/datum/proc/deserialize_json(list/input, list/options) + var/list/jsonlist = json_decode(input) + . = deserialize_list(jsonlist) + if(!istype(., /datum)) + . = null + +///Convert a datum into a json blob +/proc/json_serialize_datum(datum/D, list/options) + if(!istype(D)) + return + var/list/jsonlist = D.serialize_list(options) + if(islist(jsonlist)) + jsonlist["DATUM_TYPE"] = D.type + return json_encode(jsonlist) + +/// Convert a list of json to datum +/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE) + if(!islist(jsonlist)) + if(!istext(jsonlist)) + CRASH("Invalid JSON") + jsonlist = json_decode(jsonlist) + if(!islist(jsonlist)) + CRASH("Invalid JSON") + if(!jsonlist["DATUM_TYPE"]) + return + if(!ispath(jsonlist["DATUM_TYPE"])) + if(!istext(jsonlist["DATUM_TYPE"])) + return + jsonlist["DATUM_TYPE"] = text2path(jsonlist["DATUM_TYPE"]) + if(!ispath(jsonlist["DATUM_TYPE"])) + return + if(target_type) + if(!ispath(target_type)) + return + if(strict_target_type) + if(target_type != jsonlist["DATUM_TYPE"]) + return + else if(!ispath(jsonlist["DATUM_TYPE"], target_type)) + return + var/typeofdatum = jsonlist["DATUM_TYPE"] //BYOND won't directly read if this is just put in the line below, and will instead runtime because it thinks you're trying to make a new list? + var/datum/D = new typeofdatum + var/datum/returned = D.deserialize_list(jsonlist, options) + if(!istype(returned, /datum)) + qdel(D) + else + return returned + +/** + * Callback called by a timer to end an associative-list-indexed cooldown. + * + * Arguments: + * * source - datum storing the cooldown + * * index - string index storing the cooldown on the cooldowns associative list + * + * This sends a signal reporting the cooldown end. + */ +/proc/end_cooldown(datum/source, index) + if(QDELETED(source)) + return + SEND_SIGNAL(source, COMSIG_CD_STOP(index)) + TIMER_COOLDOWN_END(source, index) + + +/** + * Proc used by stoppable timers to end a cooldown before the time has ran out. + * + * Arguments: + * * source - datum storing the cooldown + * * index - string index storing the cooldown on the cooldowns associative list + * + * This sends a signal reporting the cooldown end, passing the time left as an argument. + */ +/proc/reset_cooldown(datum/source, index) + if(QDELETED(source)) + return + SEND_SIGNAL(source, COMSIG_CD_RESET(index), S_TIMER_COOLDOWN_TIMELEFT(source, index)) + TIMER_COOLDOWN_END(source, index) diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index 4477573106c..51741c9cb9a 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -1,52 +1,52 @@ -/datum/proc/CanProcCall(procname) - return TRUE - -/datum/proc/can_vv_get(var_name) - return TRUE - -/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited - if(var_name == NAMEOF(src, vars)) - return FALSE - vars[var_name] = var_value - datum_flags |= DF_VAR_EDITED - return TRUE - -/datum/proc/vv_get_var(var_name) - switch(var_name) - if ("vars") - return debug_variable(var_name, list(), 0, src) - return debug_variable(var_name, vars[var_name], 0, src) - -/datum/proc/can_vv_mark() - return TRUE - -//please call . = ..() first and append to the result, that way parent items are always at the top and child items are further down -//add separaters by doing . += "---" -/datum/proc/vv_get_dropdown() - . = list() - VV_DROPDOWN_OPTION("", "---") - VV_DROPDOWN_OPTION(VV_HK_CALLPROC, "Call Proc") - VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object") - VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete") - VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player") - VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element") - VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits") - VV_DROPDOWN_OPTION(VV_HK_VIEW_REFERENCES, "View References") - -//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks! -//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables! -//This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes. -/datum/proc/vv_do_topic(list/href_list) - if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE)) - return FALSE //This is VV, not to be called by anything else. - if(href_list[VV_HK_MODIFY_TRAITS]) - usr.client.holder.modify_traits(src) - return TRUE - -/datum/proc/vv_get_header() - . = list() - if(("name" in vars) && !isatom(src)) - . += "[vars["name"]]
    " - -/datum/proc/on_reagent_change(changetype) - return +/datum/proc/CanProcCall(procname) + return TRUE + +/datum/proc/can_vv_get(var_name) + return TRUE + +/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited + if(var_name == NAMEOF(src, vars)) + return FALSE + vars[var_name] = var_value + datum_flags |= DF_VAR_EDITED + return TRUE + +/datum/proc/vv_get_var(var_name) + switch(var_name) + if ("vars") + return debug_variable(var_name, list(), 0, src) + return debug_variable(var_name, vars[var_name], 0, src) + +/datum/proc/can_vv_mark() + return TRUE + +//please call . = ..() first and append to the result, that way parent items are always at the top and child items are further down +//add separaters by doing . += "---" +/datum/proc/vv_get_dropdown() + . = list() + VV_DROPDOWN_OPTION("", "---") + VV_DROPDOWN_OPTION(VV_HK_CALLPROC, "Call Proc") + VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object") + VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete") + VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player") + VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element") + VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits") + VV_DROPDOWN_OPTION(VV_HK_VIEW_REFERENCES, "View References") + +//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks! +//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables! +//This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes. +/datum/proc/vv_do_topic(list/href_list) + if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE)) + return FALSE //This is VV, not to be called by anything else. + if(href_list[VV_HK_MODIFY_TRAITS]) + usr.client.holder.modify_traits(src) + return TRUE + +/datum/proc/vv_get_header() + . = list() + if(("name" in vars) && !isatom(src)) + . += "[vars["name"]]
    " + +/datum/proc/on_reagent_change(changetype) + return diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm index 8afaaa4aa8c..65f69de5e25 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -1,509 +1,509 @@ -/* - - Advance Disease is a system for Virologist to Engineer their own disease with symptoms that have effects and properties - which add onto the overall disease. - - If you need help with creating new symptoms or expanding the advance disease, ask for Giacom on #coderbus. - -*/ - - - - -/* - - PROPERTIES - - */ - -/datum/disease/advance - name = "Unknown" // We will always let our Virologist name our disease. - desc = "An engineered disease which can contain a multitude of symptoms." - form = "Advance Disease" // Will let med-scanners know that this disease was engineered. - agent = "advance microbes" - max_stages = 5 - spread_text = "Unknown" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - - // NEW VARS - var/list/properties = list() - var/list/symptoms = list() // The symptoms of the disease. - var/id = "" - var/processing = FALSE - var/mutable = TRUE //set to FALSE to prevent most in-game methods of altering the disease via virology - var/oldres //To prevent setting new cures unless resistance changes. - - // The order goes from easy to cure to hard to cure. Keep in mind that sentient diseases pick two cures from tier 6 and up, ensure they won't react away in bodies. - var/static/list/advance_cures = list( - list( // level 1 - /datum/reagent/copper, /datum/reagent/silver, /datum/reagent/iodine, /datum/reagent/iron, /datum/reagent/carbon - ), - list( // level 2 - /datum/reagent/potassium, /datum/reagent/consumable/ethanol, /datum/reagent/lithium, /datum/reagent/silicon, /datum/reagent/bromine - ), - list( // level 3 - /datum/reagent/consumable/sodiumchloride, /datum/reagent/consumable/sugar, /datum/reagent/consumable/orangejuice, /datum/reagent/consumable/tomatojuice, /datum/reagent/consumable/milk - ), - list( //level 4 - /datum/reagent/medicine/spaceacillin, /datum/reagent/medicine/salglu_solution, /datum/reagent/medicine/epinephrine, /datum/reagent/medicine/c2/multiver - ), - list( //level 5 - /datum/reagent/fuel/oil, /datum/reagent/medicine/synaptizine, /datum/reagent/medicine/mannitol, /datum/reagent/drug/space_drugs, /datum/reagent/cryptobiolin - ), - list( // level 6 - /datum/reagent/phenol, /datum/reagent/medicine/inacusiate, /datum/reagent/medicine/oculine, /datum/reagent/medicine/antihol - ), - list( // level 7 - /datum/reagent/medicine/leporazine, /datum/reagent/toxin/mindbreaker, /datum/reagent/medicine/higadrite - ), - list( // level 8 - /datum/reagent/pax, /datum/reagent/drug/happiness, /datum/reagent/medicine/ephedrine - ), - list( // level 9 - /datum/reagent/toxin/lipolicide, /datum/reagent/medicine/sal_acid - ), - list( // level 10 - /datum/reagent/medicine/haloperidol, /datum/reagent/drug/aranesp, /datum/reagent/medicine/diphenhydramine - ), - list( //level 11 - /datum/reagent/medicine/modafinil, /datum/reagent/toxin/anacea - ) - ) - -/* - - OLD PROCS - - */ - -/datum/disease/advance/New() - Refresh() - -/datum/disease/advance/Destroy() - if(processing) - for(var/datum/symptom/S in symptoms) - S.End(src) - return ..() - -/datum/disease/advance/try_infect(var/mob/living/infectee, make_copy = TRUE) - //see if we are more transmittable than enough diseases to replace them - //diseases replaced in this way do not confer immunity - var/list/advance_diseases = list() - for(var/datum/disease/advance/P in infectee.diseases) - advance_diseases += P - var/replace_num = advance_diseases.len + 1 - DISEASE_LIMIT //amount of diseases that need to be removed to fit this one - if(replace_num > 0) - sortTim(advance_diseases, /proc/cmp_advdisease_resistance_asc) - for(var/i in 1 to replace_num) - var/datum/disease/advance/competition = advance_diseases[i] - if(totalTransmittable() > competition.totalResistance()) - competition.cure(FALSE) - else - return FALSE //we are not strong enough to bully our way in - infect(infectee, make_copy) - return TRUE - -// Randomly pick a symptom to activate. -/datum/disease/advance/stage_act() - ..() - if(carrier || QDELETED(src)) // Could be cured in parent call. - return - - if(symptoms && symptoms.len) - if(!processing) - processing = TRUE - for(var/datum/symptom/S in symptoms) - if(S.Start(src)) //this will return FALSE if the symptom is neutered - S.next_activation = world.time + rand(S.symptom_delay_min * 10, S.symptom_delay_max * 10) - S.on_stage_change(src) - - for(var/datum/symptom/S in symptoms) - S.Activate(src) - -// Tell symptoms stage changed -/datum/disease/advance/update_stage(new_stage) - ..() - for(var/datum/symptom/S in symptoms) - S.on_stage_change(src) - -// Compares type then ID. -/datum/disease/advance/IsSame(datum/disease/advance/D) - - if(!(istype(D, /datum/disease/advance))) - return 0 - - if(GetDiseaseID() != D.GetDiseaseID()) - return 0 - return 1 - -// Returns the advance disease with a different reference memory. -/datum/disease/advance/Copy() - var/datum/disease/advance/A = ..() - QDEL_LIST(A.symptoms) - for(var/datum/symptom/S in symptoms) - A.symptoms += S.Copy() - A.properties = properties.Copy() - A.id = id - A.mutable = mutable - A.oldres = oldres - //this is a new disease starting over at stage 1, so processing is not copied - return A - -//Describe this disease to an admin in detail (for logging) -/datum/disease/advance/admin_details() - var/list/name_symptoms = list() - for(var/datum/symptom/S in symptoms) - name_symptoms += S.name - return "[name] sym:[english_list(name_symptoms)] r:[totalResistance()] s:[totalStealth()] ss:[totalStageSpeed()] t:[totalTransmittable()]" - -/* - - NEW PROCS - - */ - -// Mix the symptoms of two diseases (the src and the argument) -/datum/disease/advance/proc/Mix(datum/disease/advance/D) - if(!(IsSame(D))) - var/list/possible_symptoms = shuffle(D.symptoms) - for(var/datum/symptom/S in possible_symptoms) - AddSymptom(S.Copy()) - -/datum/disease/advance/proc/HasSymptom(datum/symptom/S) - for(var/datum/symptom/symp in symptoms) - if(symp.type == S.type) - return 1 - return 0 - -// Will generate new unique symptoms, use this if there are none. Returns a list of symptoms that were generated. -/datum/disease/advance/proc/GenerateSymptoms(level_min, level_max, amount_get = 0) - - . = list() // Symptoms we generated. - - // Generate symptoms. By default, we only choose non-deadly symptoms. - var/list/possible_symptoms = list() - for(var/symp in SSdisease.list_symptoms) - var/datum/symptom/S = new symp - if(S.naturally_occuring && S.level >= level_min && S.level <= level_max) - if(!HasSymptom(S)) - possible_symptoms += S - - if(!possible_symptoms.len) - return - - // Random chance to get more than one symptom - var/number_of = amount_get - if(!amount_get) - number_of = 1 - while(prob(20)) - number_of += 1 - - for(var/i = 1; number_of >= i && possible_symptoms.len; i++) - . += pick_n_take(possible_symptoms) - -/datum/disease/advance/proc/Refresh(new_name = FALSE) - GenerateProperties() - AssignProperties() - if(processing && symptoms && symptoms.len) - for(var/datum/symptom/S in symptoms) - S.Start(src) - S.on_stage_change(src) - id = null - - var/the_id = GetDiseaseID() - if(!SSdisease.archive_diseases[the_id]) - SSdisease.archive_diseases[the_id] = src // So we don't infinite loop - SSdisease.archive_diseases[the_id] = Copy() - if(new_name) - AssignName() - -//Generate disease properties based on the effects. Returns an associated list. -/datum/disease/advance/proc/GenerateProperties() - properties = list("resistance" = 0, "stealth" = 0, "stage_rate" = 0, "transmittable" = 0, "severity" = 0) - - for(var/datum/symptom/S in symptoms) - properties["resistance"] += S.resistance - properties["stealth"] += S.stealth - properties["stage_rate"] += S.stage_speed - properties["transmittable"] += S.transmittable - if(!S.neutered) - properties["severity"] = max(properties["severity"], S.severity) // severity is based on the highest severity non-neutered symptom - -// Assign the properties that are in the list. -/datum/disease/advance/proc/AssignProperties() - - if(properties && properties.len) - if(properties["stealth"] >= 2) - visibility_flags |= HIDDEN_SCANNER - else - visibility_flags &= ~HIDDEN_SCANNER - - if(properties["transmittable"]>=11) - SetSpread(DISEASE_SPREAD_AIRBORNE) - else if(properties["transmittable"]>=7) - SetSpread(DISEASE_SPREAD_CONTACT_SKIN) - else if(properties["transmittable"]>=3) - SetSpread(DISEASE_SPREAD_CONTACT_FLUIDS) - else - SetSpread(DISEASE_SPREAD_BLOOD) - - permeability_mod = max(CEILING(0.4 * properties["transmittable"], 1), 1) - cure_chance = 15 - clamp(properties["resistance"], -5, 5) // can be between 10 and 20 - stage_prob = max(properties["stage_rate"], 2) - SetSeverity(properties["severity"]) - GenerateCure(properties) - else - CRASH("Our properties were empty or null!") - - -// Assign the spread type and give it the correct description. -/datum/disease/advance/proc/SetSpread(spread_id) - switch(spread_id) - if(DISEASE_SPREAD_NON_CONTAGIOUS) - spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS - spread_text = "None" - if(DISEASE_SPREAD_SPECIAL) - spread_flags = DISEASE_SPREAD_SPECIAL - spread_text = "None" - if(DISEASE_SPREAD_BLOOD) - spread_flags = DISEASE_SPREAD_BLOOD - spread_text = "Blood" - if(DISEASE_SPREAD_CONTACT_FLUIDS) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS - spread_text = "Fluids" - if(DISEASE_SPREAD_CONTACT_SKIN) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN - spread_text = "On contact" - if(DISEASE_SPREAD_AIRBORNE) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE - spread_text = "Airborne" - -/datum/disease/advance/proc/SetSeverity(level_sev) - - switch(level_sev) - - if(-INFINITY to 0) - severity = DISEASE_SEVERITY_POSITIVE - if(1) - severity = DISEASE_SEVERITY_NONTHREAT - if(2) - severity = DISEASE_SEVERITY_MINOR - if(3) - severity = DISEASE_SEVERITY_MEDIUM - if(4) - severity = DISEASE_SEVERITY_HARMFUL - if(5) - severity = DISEASE_SEVERITY_DANGEROUS - if(6 to INFINITY) - severity = DISEASE_SEVERITY_BIOHAZARD - else - severity = "Unknown" - - -// Will generate a random cure, the more resistance the symptoms have, the harder the cure. -/datum/disease/advance/proc/GenerateCure() - if(properties && properties.len) - var/res = clamp(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len) - if(res == oldres) - return - cures = list(pick(advance_cures[res])) - oldres = res - // Get the cure name from the cure_id - var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]] - cure_text = D.name - -// Randomly generate a symptom, has a chance to lose or gain a symptom. -/datum/disease/advance/proc/Evolve(min_level, max_level, ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - var/list/generated_symptoms = GenerateSymptoms(min_level, max_level, 1) - if(length(generated_symptoms)) - var/datum/symptom/S = pick(generated_symptoms) - AddSymptom(S) - Refresh(TRUE) - -// Randomly remove a symptom. -/datum/disease/advance/proc/Devolve(ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - if(length(symptoms) > 1) - var/datum/symptom/S = pick(symptoms) - if(S) - RemoveSymptom(S) - Refresh(TRUE) - -// Randomly neuter a symptom. -/datum/disease/advance/proc/Neuter(ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - if(length(symptoms)) - var/datum/symptom/S = pick(symptoms) - if(S) - NeuterSymptom(S) - Refresh(TRUE) - -// Name the disease. -/datum/disease/advance/proc/AssignName(name = "Unknown") - var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] - A.name = name - -// Return a unique ID of the disease. -/datum/disease/advance/GetDiseaseID() - if(!id) - var/list/L = list() - for(var/datum/symptom/S in symptoms) - if(S.neutered) - L += "[S.id]N" - else - L += S.id - L = sortList(L) // Sort the list so it doesn't matter which order the symptoms are in. - var/result = jointext(L, ":") - id = result - return id - - -// Add a symptom, if it is over the limit we take a random symptom away and add the new one. -/datum/disease/advance/proc/AddSymptom(datum/symptom/S) - - if(HasSymptom(S)) - return - - if(!(symptoms.len < (VIRUS_SYMPTOM_LIMIT - 1) + rand(-1, 1))) - RemoveSymptom(pick(symptoms)) - symptoms += S - S.OnAdd(src) - -// Simply removes the symptom. -/datum/disease/advance/proc/RemoveSymptom(datum/symptom/S) - symptoms -= S - S.OnRemove(src) - -// Neuter a symptom, so it will only affect stats -/datum/disease/advance/proc/NeuterSymptom(datum/symptom/S) - if(!S.neutered) - S.neutered = TRUE - S.name += " (neutered)" - S.OnRemove(src) - -/* - - Static Procs - -*/ - -// Mix a list of advance diseases and return the mixed result. -/proc/Advance_Mix(var/list/D_list) - var/list/diseases = list() - - for(var/datum/disease/advance/A in D_list) - diseases += A.Copy() - - if(!diseases.len) - return null - if(diseases.len <= 1) - return pick(diseases) // Just return the only entry. - - var/i = 0 - // Mix our diseases until we are left with only one result. - while(i < 20 && diseases.len > 1) - - i++ - - var/datum/disease/advance/D1 = pick(diseases) - diseases -= D1 - - var/datum/disease/advance/D2 = pick(diseases) - D2.Mix(D1) - - // Should be only 1 entry left, but if not let's only return a single entry - var/datum/disease/advance/to_return = pick(diseases) - to_return.Refresh(TRUE) - return to_return - -/proc/SetViruses(datum/reagent/R, list/data) - if(data) - var/list/preserve = list() - if(istype(data) && data["viruses"]) - for(var/datum/disease/A in data["viruses"]) - preserve += A.Copy() - R.data = data.Copy() - if(preserve.len) - R.data["viruses"] = preserve - -/proc/AdminCreateVirus(client/user) - - if(!user) - return - - var/i = VIRUS_SYMPTOM_LIMIT - - var/datum/disease/advance/D = new() - D.symptoms = list() - - var/list/symptoms = list() - symptoms += "Done" - symptoms += SSdisease.list_symptoms.Copy() - do - if(user) - var/symptom = input(user, "Choose a symptom to add ([i] remaining)", "Choose a Symptom") in sortList(symptoms, /proc/cmp_typepaths_asc) - if(isnull(symptom)) - return - else if(istext(symptom)) - i = 0 - else if(ispath(symptom)) - var/datum/symptom/S = new symptom - if(!D.HasSymptom(S)) - D.AddSymptom(S) - i -= 1 - while(i > 0) - - if(D.symptoms.len > 0) - - var/new_name = stripped_input(user, "Name your new disease.", "New Name") - if(!new_name) - return - D.Refresh() - D.AssignName(new_name) //Updates the master copy - D.name = new_name //Updates our copy - - var/list/targets = list("Random") - targets += sortNames(GLOB.human_list) - var/target = input(user, "Pick a viable human target for the disease.", "Disease Target") as null|anything in targets - - var/mob/living/carbon/human/H - if(!target) - return - if(target == "Random") - for(var/human in shuffle(GLOB.human_list)) - H = human - var/found = FALSE - if(!is_station_level(H.z)) - continue - if(!H.HasDisease(D)) - found = H.ForceContractDisease(D) - break - if(!found) - to_chat(user, "Could not find a valid target for the disease.") - else - H = target - if(istype(H) && D.infectable_biotypes & H.mob_biotypes) - H.ForceContractDisease(D) - else - to_chat(user, "Target could not be infected. Check mob biotype compatibility or resistances.") - return - - message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.admin_details()] in [ADMIN_LOOKUPFLW(H)]") - log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()] in [H]!") - - -/datum/disease/advance/proc/totalStageSpeed() - return properties["stage_rate"] - -/datum/disease/advance/proc/totalStealth() - return properties["stealth"] - -/datum/disease/advance/proc/totalResistance() - return properties["resistance"] - -/datum/disease/advance/proc/totalTransmittable() - return properties["transmittable"] +/* + + Advance Disease is a system for Virologist to Engineer their own disease with symptoms that have effects and properties + which add onto the overall disease. + + If you need help with creating new symptoms or expanding the advance disease, ask for Giacom on #coderbus. + +*/ + + + + +/* + + PROPERTIES + + */ + +/datum/disease/advance + name = "Unknown" // We will always let our Virologist name our disease. + desc = "An engineered disease which can contain a multitude of symptoms." + form = "Advance Disease" // Will let med-scanners know that this disease was engineered. + agent = "advance microbes" + max_stages = 5 + spread_text = "Unknown" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + + // NEW VARS + var/list/properties = list() + var/list/symptoms = list() // The symptoms of the disease. + var/id = "" + var/processing = FALSE + var/mutable = TRUE //set to FALSE to prevent most in-game methods of altering the disease via virology + var/oldres //To prevent setting new cures unless resistance changes. + + // The order goes from easy to cure to hard to cure. Keep in mind that sentient diseases pick two cures from tier 6 and up, ensure they won't react away in bodies. + var/static/list/advance_cures = list( + list( // level 1 + /datum/reagent/copper, /datum/reagent/silver, /datum/reagent/iodine, /datum/reagent/iron, /datum/reagent/carbon + ), + list( // level 2 + /datum/reagent/potassium, /datum/reagent/consumable/ethanol, /datum/reagent/lithium, /datum/reagent/silicon, /datum/reagent/bromine + ), + list( // level 3 + /datum/reagent/consumable/sodiumchloride, /datum/reagent/consumable/sugar, /datum/reagent/consumable/orangejuice, /datum/reagent/consumable/tomatojuice, /datum/reagent/consumable/milk + ), + list( //level 4 + /datum/reagent/medicine/spaceacillin, /datum/reagent/medicine/salglu_solution, /datum/reagent/medicine/epinephrine, /datum/reagent/medicine/c2/multiver + ), + list( //level 5 + /datum/reagent/fuel/oil, /datum/reagent/medicine/synaptizine, /datum/reagent/medicine/mannitol, /datum/reagent/drug/space_drugs, /datum/reagent/cryptobiolin + ), + list( // level 6 + /datum/reagent/phenol, /datum/reagent/medicine/inacusiate, /datum/reagent/medicine/oculine, /datum/reagent/medicine/antihol + ), + list( // level 7 + /datum/reagent/medicine/leporazine, /datum/reagent/toxin/mindbreaker, /datum/reagent/medicine/higadrite + ), + list( // level 8 + /datum/reagent/pax, /datum/reagent/drug/happiness, /datum/reagent/medicine/ephedrine + ), + list( // level 9 + /datum/reagent/toxin/lipolicide, /datum/reagent/medicine/sal_acid + ), + list( // level 10 + /datum/reagent/medicine/haloperidol, /datum/reagent/drug/aranesp, /datum/reagent/medicine/diphenhydramine + ), + list( //level 11 + /datum/reagent/medicine/modafinil, /datum/reagent/toxin/anacea + ) + ) + +/* + + OLD PROCS + + */ + +/datum/disease/advance/New() + Refresh() + +/datum/disease/advance/Destroy() + if(processing) + for(var/datum/symptom/S in symptoms) + S.End(src) + return ..() + +/datum/disease/advance/try_infect(var/mob/living/infectee, make_copy = TRUE) + //see if we are more transmittable than enough diseases to replace them + //diseases replaced in this way do not confer immunity + var/list/advance_diseases = list() + for(var/datum/disease/advance/P in infectee.diseases) + advance_diseases += P + var/replace_num = advance_diseases.len + 1 - DISEASE_LIMIT //amount of diseases that need to be removed to fit this one + if(replace_num > 0) + sortTim(advance_diseases, /proc/cmp_advdisease_resistance_asc) + for(var/i in 1 to replace_num) + var/datum/disease/advance/competition = advance_diseases[i] + if(totalTransmittable() > competition.totalResistance()) + competition.cure(FALSE) + else + return FALSE //we are not strong enough to bully our way in + infect(infectee, make_copy) + return TRUE + +// Randomly pick a symptom to activate. +/datum/disease/advance/stage_act() + ..() + if(carrier || QDELETED(src)) // Could be cured in parent call. + return + + if(symptoms && symptoms.len) + if(!processing) + processing = TRUE + for(var/datum/symptom/S in symptoms) + if(S.Start(src)) //this will return FALSE if the symptom is neutered + S.next_activation = world.time + rand(S.symptom_delay_min * 10, S.symptom_delay_max * 10) + S.on_stage_change(src) + + for(var/datum/symptom/S in symptoms) + S.Activate(src) + +// Tell symptoms stage changed +/datum/disease/advance/update_stage(new_stage) + ..() + for(var/datum/symptom/S in symptoms) + S.on_stage_change(src) + +// Compares type then ID. +/datum/disease/advance/IsSame(datum/disease/advance/D) + + if(!(istype(D, /datum/disease/advance))) + return 0 + + if(GetDiseaseID() != D.GetDiseaseID()) + return 0 + return 1 + +// Returns the advance disease with a different reference memory. +/datum/disease/advance/Copy() + var/datum/disease/advance/A = ..() + QDEL_LIST(A.symptoms) + for(var/datum/symptom/S in symptoms) + A.symptoms += S.Copy() + A.properties = properties.Copy() + A.id = id + A.mutable = mutable + A.oldres = oldres + //this is a new disease starting over at stage 1, so processing is not copied + return A + +//Describe this disease to an admin in detail (for logging) +/datum/disease/advance/admin_details() + var/list/name_symptoms = list() + for(var/datum/symptom/S in symptoms) + name_symptoms += S.name + return "[name] sym:[english_list(name_symptoms)] r:[totalResistance()] s:[totalStealth()] ss:[totalStageSpeed()] t:[totalTransmittable()]" + +/* + + NEW PROCS + + */ + +// Mix the symptoms of two diseases (the src and the argument) +/datum/disease/advance/proc/Mix(datum/disease/advance/D) + if(!(IsSame(D))) + var/list/possible_symptoms = shuffle(D.symptoms) + for(var/datum/symptom/S in possible_symptoms) + AddSymptom(S.Copy()) + +/datum/disease/advance/proc/HasSymptom(datum/symptom/S) + for(var/datum/symptom/symp in symptoms) + if(symp.type == S.type) + return 1 + return 0 + +// Will generate new unique symptoms, use this if there are none. Returns a list of symptoms that were generated. +/datum/disease/advance/proc/GenerateSymptoms(level_min, level_max, amount_get = 0) + + . = list() // Symptoms we generated. + + // Generate symptoms. By default, we only choose non-deadly symptoms. + var/list/possible_symptoms = list() + for(var/symp in SSdisease.list_symptoms) + var/datum/symptom/S = new symp + if(S.naturally_occuring && S.level >= level_min && S.level <= level_max) + if(!HasSymptom(S)) + possible_symptoms += S + + if(!possible_symptoms.len) + return + + // Random chance to get more than one symptom + var/number_of = amount_get + if(!amount_get) + number_of = 1 + while(prob(20)) + number_of += 1 + + for(var/i = 1; number_of >= i && possible_symptoms.len; i++) + . += pick_n_take(possible_symptoms) + +/datum/disease/advance/proc/Refresh(new_name = FALSE) + GenerateProperties() + AssignProperties() + if(processing && symptoms && symptoms.len) + for(var/datum/symptom/S in symptoms) + S.Start(src) + S.on_stage_change(src) + id = null + + var/the_id = GetDiseaseID() + if(!SSdisease.archive_diseases[the_id]) + SSdisease.archive_diseases[the_id] = src // So we don't infinite loop + SSdisease.archive_diseases[the_id] = Copy() + if(new_name) + AssignName() + +//Generate disease properties based on the effects. Returns an associated list. +/datum/disease/advance/proc/GenerateProperties() + properties = list("resistance" = 0, "stealth" = 0, "stage_rate" = 0, "transmittable" = 0, "severity" = 0) + + for(var/datum/symptom/S in symptoms) + properties["resistance"] += S.resistance + properties["stealth"] += S.stealth + properties["stage_rate"] += S.stage_speed + properties["transmittable"] += S.transmittable + if(!S.neutered) + properties["severity"] = max(properties["severity"], S.severity) // severity is based on the highest severity non-neutered symptom + +// Assign the properties that are in the list. +/datum/disease/advance/proc/AssignProperties() + + if(properties && properties.len) + if(properties["stealth"] >= 2) + visibility_flags |= HIDDEN_SCANNER + else + visibility_flags &= ~HIDDEN_SCANNER + + if(properties["transmittable"]>=11) + SetSpread(DISEASE_SPREAD_AIRBORNE) + else if(properties["transmittable"]>=7) + SetSpread(DISEASE_SPREAD_CONTACT_SKIN) + else if(properties["transmittable"]>=3) + SetSpread(DISEASE_SPREAD_CONTACT_FLUIDS) + else + SetSpread(DISEASE_SPREAD_BLOOD) + + permeability_mod = max(CEILING(0.4 * properties["transmittable"], 1), 1) + cure_chance = 15 - clamp(properties["resistance"], -5, 5) // can be between 10 and 20 + stage_prob = max(properties["stage_rate"], 2) + SetSeverity(properties["severity"]) + GenerateCure(properties) + else + CRASH("Our properties were empty or null!") + + +// Assign the spread type and give it the correct description. +/datum/disease/advance/proc/SetSpread(spread_id) + switch(spread_id) + if(DISEASE_SPREAD_NON_CONTAGIOUS) + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + spread_text = "None" + if(DISEASE_SPREAD_SPECIAL) + spread_flags = DISEASE_SPREAD_SPECIAL + spread_text = "None" + if(DISEASE_SPREAD_BLOOD) + spread_flags = DISEASE_SPREAD_BLOOD + spread_text = "Blood" + if(DISEASE_SPREAD_CONTACT_FLUIDS) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS + spread_text = "Fluids" + if(DISEASE_SPREAD_CONTACT_SKIN) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN + spread_text = "On contact" + if(DISEASE_SPREAD_AIRBORNE) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE + spread_text = "Airborne" + +/datum/disease/advance/proc/SetSeverity(level_sev) + + switch(level_sev) + + if(-INFINITY to 0) + severity = DISEASE_SEVERITY_POSITIVE + if(1) + severity = DISEASE_SEVERITY_NONTHREAT + if(2) + severity = DISEASE_SEVERITY_MINOR + if(3) + severity = DISEASE_SEVERITY_MEDIUM + if(4) + severity = DISEASE_SEVERITY_HARMFUL + if(5) + severity = DISEASE_SEVERITY_DANGEROUS + if(6 to INFINITY) + severity = DISEASE_SEVERITY_BIOHAZARD + else + severity = "Unknown" + + +// Will generate a random cure, the more resistance the symptoms have, the harder the cure. +/datum/disease/advance/proc/GenerateCure() + if(properties && properties.len) + var/res = clamp(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len) + if(res == oldres) + return + cures = list(pick(advance_cures[res])) + oldres = res + // Get the cure name from the cure_id + var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]] + cure_text = D.name + +// Randomly generate a symptom, has a chance to lose or gain a symptom. +/datum/disease/advance/proc/Evolve(min_level, max_level, ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + var/list/generated_symptoms = GenerateSymptoms(min_level, max_level, 1) + if(length(generated_symptoms)) + var/datum/symptom/S = pick(generated_symptoms) + AddSymptom(S) + Refresh(TRUE) + +// Randomly remove a symptom. +/datum/disease/advance/proc/Devolve(ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + if(length(symptoms) > 1) + var/datum/symptom/S = pick(symptoms) + if(S) + RemoveSymptom(S) + Refresh(TRUE) + +// Randomly neuter a symptom. +/datum/disease/advance/proc/Neuter(ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + if(length(symptoms)) + var/datum/symptom/S = pick(symptoms) + if(S) + NeuterSymptom(S) + Refresh(TRUE) + +// Name the disease. +/datum/disease/advance/proc/AssignName(name = "Unknown") + var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] + A.name = name + +// Return a unique ID of the disease. +/datum/disease/advance/GetDiseaseID() + if(!id) + var/list/L = list() + for(var/datum/symptom/S in symptoms) + if(S.neutered) + L += "[S.id]N" + else + L += S.id + L = sortList(L) // Sort the list so it doesn't matter which order the symptoms are in. + var/result = jointext(L, ":") + id = result + return id + + +// Add a symptom, if it is over the limit we take a random symptom away and add the new one. +/datum/disease/advance/proc/AddSymptom(datum/symptom/S) + + if(HasSymptom(S)) + return + + if(!(symptoms.len < (VIRUS_SYMPTOM_LIMIT - 1) + rand(-1, 1))) + RemoveSymptom(pick(symptoms)) + symptoms += S + S.OnAdd(src) + +// Simply removes the symptom. +/datum/disease/advance/proc/RemoveSymptom(datum/symptom/S) + symptoms -= S + S.OnRemove(src) + +// Neuter a symptom, so it will only affect stats +/datum/disease/advance/proc/NeuterSymptom(datum/symptom/S) + if(!S.neutered) + S.neutered = TRUE + S.name += " (neutered)" + S.OnRemove(src) + +/* + + Static Procs + +*/ + +// Mix a list of advance diseases and return the mixed result. +/proc/Advance_Mix(var/list/D_list) + var/list/diseases = list() + + for(var/datum/disease/advance/A in D_list) + diseases += A.Copy() + + if(!diseases.len) + return null + if(diseases.len <= 1) + return pick(diseases) // Just return the only entry. + + var/i = 0 + // Mix our diseases until we are left with only one result. + while(i < 20 && diseases.len > 1) + + i++ + + var/datum/disease/advance/D1 = pick(diseases) + diseases -= D1 + + var/datum/disease/advance/D2 = pick(diseases) + D2.Mix(D1) + + // Should be only 1 entry left, but if not let's only return a single entry + var/datum/disease/advance/to_return = pick(diseases) + to_return.Refresh(TRUE) + return to_return + +/proc/SetViruses(datum/reagent/R, list/data) + if(data) + var/list/preserve = list() + if(istype(data) && data["viruses"]) + for(var/datum/disease/A in data["viruses"]) + preserve += A.Copy() + R.data = data.Copy() + if(preserve.len) + R.data["viruses"] = preserve + +/proc/AdminCreateVirus(client/user) + + if(!user) + return + + var/i = VIRUS_SYMPTOM_LIMIT + + var/datum/disease/advance/D = new() + D.symptoms = list() + + var/list/symptoms = list() + symptoms += "Done" + symptoms += SSdisease.list_symptoms.Copy() + do + if(user) + var/symptom = input(user, "Choose a symptom to add ([i] remaining)", "Choose a Symptom") in sortList(symptoms, /proc/cmp_typepaths_asc) + if(isnull(symptom)) + return + else if(istext(symptom)) + i = 0 + else if(ispath(symptom)) + var/datum/symptom/S = new symptom + if(!D.HasSymptom(S)) + D.AddSymptom(S) + i -= 1 + while(i > 0) + + if(D.symptoms.len > 0) + + var/new_name = stripped_input(user, "Name your new disease.", "New Name") + if(!new_name) + return + D.Refresh() + D.AssignName(new_name) //Updates the master copy + D.name = new_name //Updates our copy + + var/list/targets = list("Random") + targets += sortNames(GLOB.human_list) + var/target = input(user, "Pick a viable human target for the disease.", "Disease Target") as null|anything in targets + + var/mob/living/carbon/human/H + if(!target) + return + if(target == "Random") + for(var/human in shuffle(GLOB.human_list)) + H = human + var/found = FALSE + if(!is_station_level(H.z)) + continue + if(!H.HasDisease(D)) + found = H.ForceContractDisease(D) + break + if(!found) + to_chat(user, "Could not find a valid target for the disease.") + else + H = target + if(istype(H) && D.infectable_biotypes & H.mob_biotypes) + H.ForceContractDisease(D) + else + to_chat(user, "Target could not be infected. Check mob biotype compatibility or resistances.") + return + + message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.admin_details()] in [ADMIN_LOOKUPFLW(H)]") + log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()] in [H]!") + + +/datum/disease/advance/proc/totalStageSpeed() + return properties["stage_rate"] + +/datum/disease/advance/proc/totalStealth() + return properties["stealth"] + +/datum/disease/advance/proc/totalResistance() + return properties["resistance"] + +/datum/disease/advance/proc/totalTransmittable() + return properties["transmittable"] diff --git a/code/datums/diseases/advance/presets.dm b/code/datums/diseases/advance/presets.dm index 9113bc980f1..1924d92428e 100644 --- a/code/datums/diseases/advance/presets.dm +++ b/code/datums/diseases/advance/presets.dm @@ -1,42 +1,42 @@ -// Cold -/datum/disease/advance/cold - copy_type = /datum/disease/advance - -/datum/disease/advance/cold/New() - name = "Cold" - symptoms = list(new/datum/symptom/sneeze) - ..() - -// Flu -/datum/disease/advance/flu - copy_type = /datum/disease/advance - -/datum/disease/advance/flu/New() - name = "Flu" - symptoms = list(new/datum/symptom/cough) - ..() - -//Randomly generated Disease, for virus crates and events -/datum/disease/advance/random - name = "Experimental Disease" - copy_type = /datum/disease/advance - -/datum/disease/advance/random/New(max_symptoms, max_level = 8) - if(!max_symptoms) - max_symptoms = rand(1, VIRUS_SYMPTOM_LIMIT) - var/list/datum/symptom/possible_symptoms = list() - for(var/symptom in subtypesof(/datum/symptom)) - var/datum/symptom/S = symptom - if(initial(S.level) > max_level) - continue - if(initial(S.level) <= 0) //unobtainable symptoms - continue - possible_symptoms += S - for(var/i in 1 to max_symptoms) - var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms) - if(chosen_symptom) - var/datum/symptom/S = new chosen_symptom - symptoms += S - Refresh() - - name = "Sample #[rand(1,10000)]" +// Cold +/datum/disease/advance/cold + copy_type = /datum/disease/advance + +/datum/disease/advance/cold/New() + name = "Cold" + symptoms = list(new/datum/symptom/sneeze) + ..() + +// Flu +/datum/disease/advance/flu + copy_type = /datum/disease/advance + +/datum/disease/advance/flu/New() + name = "Flu" + symptoms = list(new/datum/symptom/cough) + ..() + +//Randomly generated Disease, for virus crates and events +/datum/disease/advance/random + name = "Experimental Disease" + copy_type = /datum/disease/advance + +/datum/disease/advance/random/New(max_symptoms, max_level = 8) + if(!max_symptoms) + max_symptoms = rand(1, VIRUS_SYMPTOM_LIMIT) + var/list/datum/symptom/possible_symptoms = list() + for(var/symptom in subtypesof(/datum/symptom)) + var/datum/symptom/S = symptom + if(initial(S.level) > max_level) + continue + if(initial(S.level) <= 0) //unobtainable symptoms + continue + possible_symptoms += S + for(var/i in 1 to max_symptoms) + var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms) + if(chosen_symptom) + var/datum/symptom/S = new chosen_symptom + symptoms += S + Refresh() + + name = "Sample #[rand(1,10000)]" diff --git a/code/datums/diseases/advance/symptoms/beard.dm b/code/datums/diseases/advance/symptoms/beard.dm index 8ac5d655bd4..9e855ccdcca 100644 --- a/code/datums/diseases/advance/symptoms/beard.dm +++ b/code/datums/diseases/advance/symptoms/beard.dm @@ -1,43 +1,43 @@ -/* -////////////////////////////////////// -Facial Hypertrichosis - - No change to stealth. - Increases resistance. - Increases speed. - Slighlty increases transmittability - Intense Level. - -BONUS - Makes the mob grow a massive beard, regardless of gender. - -////////////////////////////////////// -*/ - -/datum/symptom/beard - - name = "Facial Hypertrichosis" - desc = "The virus increases hair production significantly, causing rapid beard growth." - stealth = 0 - resistance = 3 - stage_speed = 2 - transmittable = 1 - level = 4 - severity = 1 - symptom_delay_min = 18 - symptom_delay_max = 36 - - var/list/beard_order = list("Beard (Jensen)", "Beard (Full)", "Beard (Dwarf)", "Beard (Very Long)") - -/datum/symptom/beard/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/index = min(max(beard_order.Find(H.facial_hairstyle)+1, A.stage-1), beard_order.len) - if(index > 0 && H.facial_hairstyle != beard_order[index]) - to_chat(H, "Your chin itches.") - H.facial_hairstyle = beard_order[index] - H.update_hair() - +/* +////////////////////////////////////// +Facial Hypertrichosis + + No change to stealth. + Increases resistance. + Increases speed. + Slighlty increases transmittability + Intense Level. + +BONUS + Makes the mob grow a massive beard, regardless of gender. + +////////////////////////////////////// +*/ + +/datum/symptom/beard + + name = "Facial Hypertrichosis" + desc = "The virus increases hair production significantly, causing rapid beard growth." + stealth = 0 + resistance = 3 + stage_speed = 2 + transmittable = 1 + level = 4 + severity = 1 + symptom_delay_min = 18 + symptom_delay_max = 36 + + var/list/beard_order = list("Beard (Jensen)", "Beard (Full)", "Beard (Dwarf)", "Beard (Very Long)") + +/datum/symptom/beard/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/index = min(max(beard_order.Find(H.facial_hairstyle)+1, A.stage-1), beard_order.len) + if(index > 0 && H.facial_hairstyle != beard_order[index]) + to_chat(H, "Your chin itches.") + H.facial_hairstyle = beard_order[index] + H.update_hair() + diff --git a/code/datums/diseases/advance/symptoms/choking.dm b/code/datums/diseases/advance/symptoms/choking.dm index f6db915818d..ead90c62658 100644 --- a/code/datums/diseases/advance/symptoms/choking.dm +++ b/code/datums/diseases/advance/symptoms/choking.dm @@ -1,152 +1,152 @@ -/* -////////////////////////////////////// - -Choking - - Very very noticable. - Lowers resistance. - Decreases stage speed. - Decreases transmittablity tremendously. - Moderate Level. - -Bonus - Inflicts spikes of oxyloss - -////////////////////////////////////// -*/ - -/datum/symptom/choking - - name = "Choking" - desc = "The virus causes inflammation of the host's air conduits, leading to intermittent choking." - stealth = -3 - resistance = -2 - stage_speed = -2 - transmittable = -2 - level = 3 - severity = 3 - base_message_chance = 15 - symptom_delay_min = 10 - symptom_delay_max = 30 - threshold_descs = list( - "Stage Speed 8" = "Causes choking more frequently.", - "Stealth 4" = "The symptom remains hidden until active." - ) - -/datum/symptom/choking/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 8) - symptom_delay_min = 7 - symptom_delay_max = 24 - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - -/datum/symptom/choking/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You're having difficulty breathing.", "Your breathing becomes heavy.")]") - if(3, 4) - if(!suppress_warning) - to_chat(M, "[pick("Your windpipe feels like a straw.", "Your breathing becomes tremendously difficult.")]") - else - to_chat(M, "You feel very [pick("dizzy","woozy","faint")].") //fake bloodloss messages - Choke_stage_3_4(M, A) - M.emote("gasp") - else - to_chat(M, "[pick("You're choking!", "You can't breathe!")]") - Choke(M, A) - M.emote("gasp") - -/datum/symptom/choking/proc/Choke_stage_3_4(mob/living/M, datum/disease/advance/A) - M.adjustOxyLoss(rand(6,13)) - return 1 - -/datum/symptom/choking/proc/Choke(mob/living/M, datum/disease/advance/A) - M.adjustOxyLoss(rand(10,18)) - return 1 - -/* -////////////////////////////////////// - -Asphyxiation - - Very very noticable. - Decreases stage speed. - Decreases transmittablity. - -Bonus - Inflicts large spikes of oxyloss - Introduces Asphyxiating drugs to the system - Causes cardiac arrest on dying victims. - -////////////////////////////////////// -*/ - -/datum/symptom/asphyxiation - - name = "Acute respiratory distress syndrome" - desc = "The virus causes shrinking of the host's lungs, causing severe asphyxiation. May also lead to heart attacks." - stealth = -2 - resistance = -0 - stage_speed = -1 - transmittable = -2 - level = 7 - severity = 6 - base_message_chance = 15 - symptom_delay_min = 14 - symptom_delay_max = 30 - var/paralysis = FALSE - threshold_descs = list( - "Stage Speed 8" = "Additionally synthesizes pancuronium and sodium thiopental inside the host.", - "Transmission 8" = "Doubles the damage caused by the symptom." - ) - - -/datum/symptom/asphyxiation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 8) - paralysis = TRUE - if(A.properties["transmittable"] >= 8) - power = 2 - -/datum/symptom/asphyxiation/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(3, 4) - to_chat(M, "[pick("Your windpipe feels thin.", "Your lungs feel small.")]") - Asphyxiate_stage_3_4(M, A) - M.emote("gasp") - if(5) - to_chat(M, "[pick("Your lungs hurt!", "It hurts to breathe!")]") - Asphyxiate(M, A) - M.emote("gasp") - if(M.getOxyLoss() >= 120) - M.visible_message("[M] stops breathing, as if their lungs have totally collapsed!") - Asphyxiate_death(M, A) - return - -/datum/symptom/asphyxiation/proc/Asphyxiate_stage_3_4(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(10,15) * power - M.adjustOxyLoss(get_damage) - return 1 - -/datum/symptom/asphyxiation/proc/Asphyxiate(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(15,21) * power - M.adjustOxyLoss(get_damage) - if(paralysis) - M.reagents.add_reagent_list(list(/datum/reagent/toxin/pancuronium = 3, /datum/reagent/toxin/sodium_thiopental = 3)) - return 1 - -/datum/symptom/asphyxiation/proc/Asphyxiate_death(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(25,35) * power - M.adjustOxyLoss(get_damage) - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, get_damage/2) - return 1 +/* +////////////////////////////////////// + +Choking + + Very very noticable. + Lowers resistance. + Decreases stage speed. + Decreases transmittablity tremendously. + Moderate Level. + +Bonus + Inflicts spikes of oxyloss + +////////////////////////////////////// +*/ + +/datum/symptom/choking + + name = "Choking" + desc = "The virus causes inflammation of the host's air conduits, leading to intermittent choking." + stealth = -3 + resistance = -2 + stage_speed = -2 + transmittable = -2 + level = 3 + severity = 3 + base_message_chance = 15 + symptom_delay_min = 10 + symptom_delay_max = 30 + threshold_descs = list( + "Stage Speed 8" = "Causes choking more frequently.", + "Stealth 4" = "The symptom remains hidden until active." + ) + +/datum/symptom/choking/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 8) + symptom_delay_min = 7 + symptom_delay_max = 24 + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + +/datum/symptom/choking/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You're having difficulty breathing.", "Your breathing becomes heavy.")]") + if(3, 4) + if(!suppress_warning) + to_chat(M, "[pick("Your windpipe feels like a straw.", "Your breathing becomes tremendously difficult.")]") + else + to_chat(M, "You feel very [pick("dizzy","woozy","faint")].") //fake bloodloss messages + Choke_stage_3_4(M, A) + M.emote("gasp") + else + to_chat(M, "[pick("You're choking!", "You can't breathe!")]") + Choke(M, A) + M.emote("gasp") + +/datum/symptom/choking/proc/Choke_stage_3_4(mob/living/M, datum/disease/advance/A) + M.adjustOxyLoss(rand(6,13)) + return 1 + +/datum/symptom/choking/proc/Choke(mob/living/M, datum/disease/advance/A) + M.adjustOxyLoss(rand(10,18)) + return 1 + +/* +////////////////////////////////////// + +Asphyxiation + + Very very noticable. + Decreases stage speed. + Decreases transmittablity. + +Bonus + Inflicts large spikes of oxyloss + Introduces Asphyxiating drugs to the system + Causes cardiac arrest on dying victims. + +////////////////////////////////////// +*/ + +/datum/symptom/asphyxiation + + name = "Acute respiratory distress syndrome" + desc = "The virus causes shrinking of the host's lungs, causing severe asphyxiation. May also lead to heart attacks." + stealth = -2 + resistance = -0 + stage_speed = -1 + transmittable = -2 + level = 7 + severity = 6 + base_message_chance = 15 + symptom_delay_min = 14 + symptom_delay_max = 30 + var/paralysis = FALSE + threshold_descs = list( + "Stage Speed 8" = "Additionally synthesizes pancuronium and sodium thiopental inside the host.", + "Transmission 8" = "Doubles the damage caused by the symptom." + ) + + +/datum/symptom/asphyxiation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 8) + paralysis = TRUE + if(A.properties["transmittable"] >= 8) + power = 2 + +/datum/symptom/asphyxiation/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(3, 4) + to_chat(M, "[pick("Your windpipe feels thin.", "Your lungs feel small.")]") + Asphyxiate_stage_3_4(M, A) + M.emote("gasp") + if(5) + to_chat(M, "[pick("Your lungs hurt!", "It hurts to breathe!")]") + Asphyxiate(M, A) + M.emote("gasp") + if(M.getOxyLoss() >= 120) + M.visible_message("[M] stops breathing, as if their lungs have totally collapsed!") + Asphyxiate_death(M, A) + return + +/datum/symptom/asphyxiation/proc/Asphyxiate_stage_3_4(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(10,15) * power + M.adjustOxyLoss(get_damage) + return 1 + +/datum/symptom/asphyxiation/proc/Asphyxiate(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(15,21) * power + M.adjustOxyLoss(get_damage) + if(paralysis) + M.reagents.add_reagent_list(list(/datum/reagent/toxin/pancuronium = 3, /datum/reagent/toxin/sodium_thiopental = 3)) + return 1 + +/datum/symptom/asphyxiation/proc/Asphyxiate_death(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(25,35) * power + M.adjustOxyLoss(get_damage) + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, get_damage/2) + return 1 diff --git a/code/datums/diseases/advance/symptoms/confusion.dm b/code/datums/diseases/advance/symptoms/confusion.dm index 2ed48b67081..07166f3509a 100644 --- a/code/datums/diseases/advance/symptoms/confusion.dm +++ b/code/datums/diseases/advance/symptoms/confusion.dm @@ -1,64 +1,64 @@ -/* -////////////////////////////////////// - -Confusion - - Little bit hidden. - Lowers resistance. - Decreases stage speed. - Not very transmissibile. - Intense Level. - -Bonus - Makes the affected mob be confused for short periods of time. - -////////////////////////////////////// -*/ - -/datum/symptom/confusion - - name = "Confusion" - desc = "The virus interferes with the proper function of the neural system, leading to bouts of confusion and erratic movement." - stealth = 1 - resistance = -1 - stage_speed = -3 - transmittable = 0 - level = 4 - severity = 2 - base_message_chance = 25 - symptom_delay_min = 10 - symptom_delay_max = 30 - var/brain_damage = FALSE - threshold_descs = list( - "Resistance 6" = "Causes brain damage over time.", - "Transmission 6" = "Increases confusion duration and strength.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/confusion/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 6) - brain_damage = TRUE - if(A.properties["transmittable"] >= 6) - power = 1.5 - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - -/datum/symptom/confusion/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("Your head hurts.", "Your mind blanks for a moment.")]") - else - to_chat(M, "You can't think straight!") - if(M.confused < 100) - M.confused += (16 * power) - if(brain_damage) - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * power, 80) - M.updatehealth() - - return +/* +////////////////////////////////////// + +Confusion + + Little bit hidden. + Lowers resistance. + Decreases stage speed. + Not very transmissibile. + Intense Level. + +Bonus + Makes the affected mob be confused for short periods of time. + +////////////////////////////////////// +*/ + +/datum/symptom/confusion + + name = "Confusion" + desc = "The virus interferes with the proper function of the neural system, leading to bouts of confusion and erratic movement." + stealth = 1 + resistance = -1 + stage_speed = -3 + transmittable = 0 + level = 4 + severity = 2 + base_message_chance = 25 + symptom_delay_min = 10 + symptom_delay_max = 30 + var/brain_damage = FALSE + threshold_descs = list( + "Resistance 6" = "Causes brain damage over time.", + "Transmission 6" = "Increases confusion duration and strength.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/confusion/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 6) + brain_damage = TRUE + if(A.properties["transmittable"] >= 6) + power = 1.5 + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + +/datum/symptom/confusion/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("Your head hurts.", "Your mind blanks for a moment.")]") + else + to_chat(M, "You can't think straight!") + if(M.confused < 100) + M.confused += (16 * power) + if(brain_damage) + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * power, 80) + M.updatehealth() + + return diff --git a/code/datums/diseases/advance/symptoms/cough.dm b/code/datums/diseases/advance/symptoms/cough.dm index 57c67458089..1ee6f7d2eb5 100644 --- a/code/datums/diseases/advance/symptoms/cough.dm +++ b/code/datums/diseases/advance/symptoms/cough.dm @@ -1,78 +1,78 @@ -/* -////////////////////////////////////// - -Coughing - - Noticable. - Little Resistance. - Doesn't increase stage speed much. - Transmissibile. - Low Level. - -BONUS - Spreads the virus in a small square around the host. - Can force the affected mob to drop small items! - -////////////////////////////////////// -*/ - -/datum/symptom/cough - - name = "Cough" - desc = "The virus irritates the throat of the host, causing occasional coughing. Each cough will try to infect bystanders who are within 1 tile of the host with the virus." - stealth = -1 - resistance = 3 - stage_speed = 1 - transmittable = 2 - level = 1 - severity = 1 - base_message_chance = 15 - symptom_delay_min = 2 - symptom_delay_max = 15 - var/spread_range = 1 - threshold_descs = list( - "Resistance 11" = "The host will drop small items when coughing.", - "Resistance 15" = "Occasionally causes coughing fits that stun the host. The extra coughs do not spread the virus.", - "Stage Speed 6" = "Increases cough frequency.", - "Transmission 7" = "Coughing will now infect bystanders up to 2 tiles away.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/cough/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["transmittable"] >= 7) - spread_range = 2 - if(A.properties["resistance"] >= 11) //strong enough to drop items - power = 1.5 - if(A.properties["resistance"] >= 15) //strong enough to stun (occasionally) - power = 2 - if(A.properties["stage_rate"] >= 6) //cough more often - symptom_delay_max = 10 - -/datum/symptom/cough/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(HAS_TRAIT(M, TRAIT_SOOTHED_THROAT)) - return - switch(A.stage) - if(1, 2, 3) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You swallow excess mucus.", "You lightly cough.")]") - else - M.emote("cough") - if(M.CanSpreadAirborneDisease()) - A.spread(spread_range) - if(power >= 1.5) - var/obj/item/I = M.get_active_held_item() - if(I && I.w_class == WEIGHT_CLASS_TINY) - M.dropItemToGround(I) - if(power >= 2 && prob(30)) - to_chat(M, "[pick("You have a coughing fit!", "You can't stop coughing!")]") - M.Immobilize(20) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 6) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 12) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 18) +/* +////////////////////////////////////// + +Coughing + + Noticable. + Little Resistance. + Doesn't increase stage speed much. + Transmissibile. + Low Level. + +BONUS + Spreads the virus in a small square around the host. + Can force the affected mob to drop small items! + +////////////////////////////////////// +*/ + +/datum/symptom/cough + + name = "Cough" + desc = "The virus irritates the throat of the host, causing occasional coughing. Each cough will try to infect bystanders who are within 1 tile of the host with the virus." + stealth = -1 + resistance = 3 + stage_speed = 1 + transmittable = 2 + level = 1 + severity = 1 + base_message_chance = 15 + symptom_delay_min = 2 + symptom_delay_max = 15 + var/spread_range = 1 + threshold_descs = list( + "Resistance 11" = "The host will drop small items when coughing.", + "Resistance 15" = "Occasionally causes coughing fits that stun the host. The extra coughs do not spread the virus.", + "Stage Speed 6" = "Increases cough frequency.", + "Transmission 7" = "Coughing will now infect bystanders up to 2 tiles away.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/cough/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["transmittable"] >= 7) + spread_range = 2 + if(A.properties["resistance"] >= 11) //strong enough to drop items + power = 1.5 + if(A.properties["resistance"] >= 15) //strong enough to stun (occasionally) + power = 2 + if(A.properties["stage_rate"] >= 6) //cough more often + symptom_delay_max = 10 + +/datum/symptom/cough/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(HAS_TRAIT(M, TRAIT_SOOTHED_THROAT)) + return + switch(A.stage) + if(1, 2, 3) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You swallow excess mucus.", "You lightly cough.")]") + else + M.emote("cough") + if(M.CanSpreadAirborneDisease()) + A.spread(spread_range) + if(power >= 1.5) + var/obj/item/I = M.get_active_held_item() + if(I && I.w_class == WEIGHT_CLASS_TINY) + M.dropItemToGround(I) + if(power >= 2 && prob(30)) + to_chat(M, "[pick("You have a coughing fit!", "You can't stop coughing!")]") + M.Immobilize(20) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 6) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 12) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 18) diff --git a/code/datums/diseases/advance/symptoms/deafness.dm b/code/datums/diseases/advance/symptoms/deafness.dm index 1a45ce850c7..8c50fd7abec 100644 --- a/code/datums/diseases/advance/symptoms/deafness.dm +++ b/code/datums/diseases/advance/symptoms/deafness.dm @@ -1,63 +1,63 @@ -/* -////////////////////////////////////// - -Deafness - - Slightly noticable. - Lowers resistance. - Decreases stage speed slightly. - Decreases transmittablity. - Intense Level. - -Bonus - Causes intermittent loss of hearing. - -////////////////////////////////////// -*/ - -/datum/symptom/deafness - - name = "Deafness" - desc = "The virus causes inflammation of the eardrums, causing intermittent deafness." - stealth = -1 - resistance = -2 - stage_speed = -1 - transmittable = -3 - level = 4 - severity = 4 - base_message_chance = 100 - symptom_delay_min = 25 - symptom_delay_max = 80 - threshold_descs = list( - "Resistance 9" = "Causes permanent deafness, instead of intermittent.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/deafness/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["resistance"] >= 9) //permanent deafness - power = 2 - -/datum/symptom/deafness/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) - if(!ears) - return //cutting off your ears to cure the deafness: the ultimate own - switch(A.stage) - if(3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You hear a ringing in your ear.", "Your ears pop.")]") - if(5) - if(power >= 2) - if(ears.damage < ears.maxHealth) - to_chat(M, "Your ears pop painfully and start bleeding!") - ears.damage = max(ears.damage, ears.maxHealth) - M.emote("scream") - else - to_chat(M, "Your ears pop and begin ringing loudly!") - ears.deaf = min(20, ears.deaf + 15) +/* +////////////////////////////////////// + +Deafness + + Slightly noticable. + Lowers resistance. + Decreases stage speed slightly. + Decreases transmittablity. + Intense Level. + +Bonus + Causes intermittent loss of hearing. + +////////////////////////////////////// +*/ + +/datum/symptom/deafness + + name = "Deafness" + desc = "The virus causes inflammation of the eardrums, causing intermittent deafness." + stealth = -1 + resistance = -2 + stage_speed = -1 + transmittable = -3 + level = 4 + severity = 4 + base_message_chance = 100 + symptom_delay_min = 25 + symptom_delay_max = 80 + threshold_descs = list( + "Resistance 9" = "Causes permanent deafness, instead of intermittent.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/deafness/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["resistance"] >= 9) //permanent deafness + power = 2 + +/datum/symptom/deafness/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) + if(!ears) + return //cutting off your ears to cure the deafness: the ultimate own + switch(A.stage) + if(3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You hear a ringing in your ear.", "Your ears pop.")]") + if(5) + if(power >= 2) + if(ears.damage < ears.maxHealth) + to_chat(M, "Your ears pop painfully and start bleeding!") + ears.damage = max(ears.damage, ears.maxHealth) + M.emote("scream") + else + to_chat(M, "Your ears pop and begin ringing loudly!") + ears.deaf = min(20, ears.deaf + 15) diff --git a/code/datums/diseases/advance/symptoms/dizzy.dm b/code/datums/diseases/advance/symptoms/dizzy.dm index f23114de711..a4f486cedb3 100644 --- a/code/datums/diseases/advance/symptoms/dizzy.dm +++ b/code/datums/diseases/advance/symptoms/dizzy.dm @@ -1,56 +1,56 @@ -/* -////////////////////////////////////// - -Dizziness - - Hidden. - Lowers resistance considerably. - Decreases stage speed. - Reduced transmittability - Intense Level. - -Bonus - Shakes the affected mob's screen for short periods. - -////////////////////////////////////// -*/ - -/datum/symptom/dizzy // Not the egg - - name = "Dizziness" - desc = "The virus causes inflammation of the vestibular system, leading to bouts of dizziness." - resistance = -2 - stage_speed = -3 - transmittable = -1 - level = 4 - severity = 2 - base_message_chance = 50 - symptom_delay_min = 15 - symptom_delay_max = 30 - threshold_descs = list( - "Transmission 6" = "Also causes druggy vision.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/dizzy/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["transmittable"] >= 6) //druggy - power = 2 - -/datum/symptom/dizzy/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You feel dizzy.", "Your head spins.")]") - else - to_chat(M, "A wave of dizziness washes over you!") - if(M.dizziness <= 70) - M.dizziness += 30 - if(power >= 2) - M.set_drugginess(40) +/* +////////////////////////////////////// + +Dizziness + + Hidden. + Lowers resistance considerably. + Decreases stage speed. + Reduced transmittability + Intense Level. + +Bonus + Shakes the affected mob's screen for short periods. + +////////////////////////////////////// +*/ + +/datum/symptom/dizzy // Not the egg + + name = "Dizziness" + desc = "The virus causes inflammation of the vestibular system, leading to bouts of dizziness." + resistance = -2 + stage_speed = -3 + transmittable = -1 + level = 4 + severity = 2 + base_message_chance = 50 + symptom_delay_min = 15 + symptom_delay_max = 30 + threshold_descs = list( + "Transmission 6" = "Also causes druggy vision.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/dizzy/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["transmittable"] >= 6) //druggy + power = 2 + +/datum/symptom/dizzy/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You feel dizzy.", "Your head spins.")]") + else + to_chat(M, "A wave of dizziness washes over you!") + if(M.dizziness <= 70) + M.dizziness += 30 + if(power >= 2) + M.set_drugginess(40) diff --git a/code/datums/diseases/advance/symptoms/fever.dm b/code/datums/diseases/advance/symptoms/fever.dm index 106d01c8d36..2af943a474a 100644 --- a/code/datums/diseases/advance/symptoms/fever.dm +++ b/code/datums/diseases/advance/symptoms/fever.dm @@ -1,76 +1,76 @@ -/* -////////////////////////////////////// - -Fever - - No change to hidden. - Increases resistance. - Increases stage speed. - Little transmittable. - Low level. - -Bonus - Heats up your body. - -////////////////////////////////////// -*/ - -/datum/symptom/fever - name = "Fever" - desc = "The virus causes a febrile response from the host, raising its body temperature." - stealth = 0 - resistance = 3 - stage_speed = 3 - transmittable = 2 - level = 2 - severity = 2 - base_message_chance = 20 - symptom_delay_min = 10 - symptom_delay_max = 30 - var/unsafe = FALSE //over the heat threshold - threshold_descs = list( - "Resistance 5" = "Increases fever intensity, fever can overheat and harm the host.", - "Resistance 10" = "Further increases fever intensity.", - ) - -/datum/symptom/fever/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 5) //dangerous fever - power = 1.5 - unsafe = TRUE - if(A.properties["resistance"] >= 10) - power = 2.5 - set_body_temp(A.affected_mob, A) - -/datum/symptom/fever/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - if(!unsafe || A.stage < 4) - to_chat(M, "[pick("You feel hot.", "You feel like you're burning.")]") - else - to_chat(M, "[pick("You feel too hot.", "You feel like your blood is boiling.")]") - -/** - * set_body_temp Sets the body temp change - * - * Sets the body temp change to the mob based on the stage and resistance of the disease - * arguments: - * * mob/living/M The mob to apply changes to - * * datum/disease/advance/A The disease applying the symptom - */ -/datum/symptom/fever/proc/set_body_temp(mob/living/M, datum/disease/advance/A) - M.add_body_temperature_change("fever", (6 * power) * A.stage) - -/// Update the body temp change based on the new stage -/datum/symptom/fever/on_stage_change(datum/disease/advance/A) - . = ..() - if(.) - set_body_temp(A.affected_mob, A) - -/// remove the body temp change when removing symptom -/datum/symptom/fever/End(datum/disease/advance/A) - var/mob/living/carbon/M = A.affected_mob - if(M) - M.remove_body_temperature_change("fever") +/* +////////////////////////////////////// + +Fever + + No change to hidden. + Increases resistance. + Increases stage speed. + Little transmittable. + Low level. + +Bonus + Heats up your body. + +////////////////////////////////////// +*/ + +/datum/symptom/fever + name = "Fever" + desc = "The virus causes a febrile response from the host, raising its body temperature." + stealth = 0 + resistance = 3 + stage_speed = 3 + transmittable = 2 + level = 2 + severity = 2 + base_message_chance = 20 + symptom_delay_min = 10 + symptom_delay_max = 30 + var/unsafe = FALSE //over the heat threshold + threshold_descs = list( + "Resistance 5" = "Increases fever intensity, fever can overheat and harm the host.", + "Resistance 10" = "Further increases fever intensity.", + ) + +/datum/symptom/fever/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 5) //dangerous fever + power = 1.5 + unsafe = TRUE + if(A.properties["resistance"] >= 10) + power = 2.5 + set_body_temp(A.affected_mob, A) + +/datum/symptom/fever/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + if(!unsafe || A.stage < 4) + to_chat(M, "[pick("You feel hot.", "You feel like you're burning.")]") + else + to_chat(M, "[pick("You feel too hot.", "You feel like your blood is boiling.")]") + +/** + * set_body_temp Sets the body temp change + * + * Sets the body temp change to the mob based on the stage and resistance of the disease + * arguments: + * * mob/living/M The mob to apply changes to + * * datum/disease/advance/A The disease applying the symptom + */ +/datum/symptom/fever/proc/set_body_temp(mob/living/M, datum/disease/advance/A) + M.add_body_temperature_change("fever", (6 * power) * A.stage) + +/// Update the body temp change based on the new stage +/datum/symptom/fever/on_stage_change(datum/disease/advance/A) + . = ..() + if(.) + set_body_temp(A.affected_mob, A) + +/// remove the body temp change when removing symptom +/datum/symptom/fever/End(datum/disease/advance/A) + var/mob/living/carbon/M = A.affected_mob + if(M) + M.remove_body_temperature_change("fever") diff --git a/code/datums/diseases/advance/symptoms/flesh_eating.dm b/code/datums/diseases/advance/symptoms/flesh_eating.dm index f5c97e6f900..8dc39bde59f 100644 --- a/code/datums/diseases/advance/symptoms/flesh_eating.dm +++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm @@ -1,135 +1,135 @@ -/* -////////////////////////////////////// - -Necrotizing Fasciitis (AKA Flesh-Eating Disease) - - Very very noticable. - Lowers resistance tremendously. - No changes to stage speed. - Decreases transmittablity temrendously. - Fatal Level. - -Bonus - Deals brute damage over time. - -////////////////////////////////////// -*/ - -/datum/symptom/flesh_eating - - name = "Necrotizing Fasciitis" - desc = "The virus aggressively attacks body cells, necrotizing tissues and organs." - stealth = -3 - resistance = -4 - stage_speed = 0 - transmittable = -3 - level = 6 - severity = 5 - base_message_chance = 50 - symptom_delay_min = 15 - symptom_delay_max = 60 - var/bleed = FALSE - var/pain = FALSE - threshold_descs = list( - "Resistance 7" = "Host will bleed profusely during necrosis.", - "Transmission 8" = "Causes extreme pain to the host, weakening it.", - ) - -/datum/symptom/flesh_eating/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 7) //extra bleeding - bleed = TRUE - if(A.properties["transmittable"] >= 8) //extra stamina damage - pain = TRUE - -/datum/symptom/flesh_eating/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(2,3) - if(prob(base_message_chance)) - to_chat(M, "[pick("You feel a sudden pain across your body.", "Drops of blood appear suddenly on your skin.")]") - if(4,5) - to_chat(M, "[pick("You cringe as a violent pain takes over your body.", "It feels like your body is eating itself inside out.", "IT HURTS.")]") - Flesheat(M, A) - -/datum/symptom/flesh_eating/proc/Flesheat(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(15,25) * power - M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) - if(pain) - M.adjustStaminaLoss(get_damage * 2) - if(bleed) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/bodypart/random_part = pick(H.bodyparts) - random_part.generic_bleedstacks += 5 * power - return 1 - -/* -////////////////////////////////////// - -Autophagocytosis (AKA Programmed mass cell death) - - Very noticable. - Lowers resistance. - Fast stage speed. - Decreases transmittablity. - Fatal Level. - -Bonus - Deals brute damage over time. - -////////////////////////////////////// -*/ - -/datum/symptom/flesh_death - - name = "Autophagocytosis Necrosis" - desc = "The virus rapidly consumes infected cells, leading to heavy and widespread damage." - stealth = -2 - resistance = -2 - stage_speed = 1 - transmittable = -2 - level = 7 - severity = 6 - base_message_chance = 50 - symptom_delay_min = 3 - symptom_delay_max = 6 - var/chems = FALSE - var/zombie = FALSE - threshold_descs = list( - "Stage Speed 7" = "Synthesizes Heparin and Lipolicide inside the host, causing increased bleeding and hunger.", - "Stealth 5" = "The symptom remains hidden until active.", - ) - -/datum/symptom/flesh_death/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 5) - suppress_warning = TRUE - if(A.properties["stage_rate"] >= 7) //bleeding and hunger - chems = TRUE - -/datum/symptom/flesh_death/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(2,3) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You feel your body break apart.", "Your skin rubs off like dust.")]") - if(4,5) - if(prob(base_message_chance / 2)) //reduce spam - to_chat(M, "[pick("You feel your muscles weakening.", "Some of your skin detaches itself.", "You feel sandy.")]") - Flesh_death(M, A) - -/datum/symptom/flesh_death/proc/Flesh_death(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(6,10) - M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) - if(chems) - M.reagents.add_reagent_list(list(/datum/reagent/toxin/heparin = 2, /datum/reagent/toxin/lipolicide = 2)) - if(zombie) - M.reagents.add_reagent(/datum/reagent/romerol, 1) - return 1 +/* +////////////////////////////////////// + +Necrotizing Fasciitis (AKA Flesh-Eating Disease) + + Very very noticable. + Lowers resistance tremendously. + No changes to stage speed. + Decreases transmittablity temrendously. + Fatal Level. + +Bonus + Deals brute damage over time. + +////////////////////////////////////// +*/ + +/datum/symptom/flesh_eating + + name = "Necrotizing Fasciitis" + desc = "The virus aggressively attacks body cells, necrotizing tissues and organs." + stealth = -3 + resistance = -4 + stage_speed = 0 + transmittable = -3 + level = 6 + severity = 5 + base_message_chance = 50 + symptom_delay_min = 15 + symptom_delay_max = 60 + var/bleed = FALSE + var/pain = FALSE + threshold_descs = list( + "Resistance 7" = "Host will bleed profusely during necrosis.", + "Transmission 8" = "Causes extreme pain to the host, weakening it.", + ) + +/datum/symptom/flesh_eating/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 7) //extra bleeding + bleed = TRUE + if(A.properties["transmittable"] >= 8) //extra stamina damage + pain = TRUE + +/datum/symptom/flesh_eating/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(2,3) + if(prob(base_message_chance)) + to_chat(M, "[pick("You feel a sudden pain across your body.", "Drops of blood appear suddenly on your skin.")]") + if(4,5) + to_chat(M, "[pick("You cringe as a violent pain takes over your body.", "It feels like your body is eating itself inside out.", "IT HURTS.")]") + Flesheat(M, A) + +/datum/symptom/flesh_eating/proc/Flesheat(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(15,25) * power + M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) + if(pain) + M.adjustStaminaLoss(get_damage * 2) + if(bleed) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/bodypart/random_part = pick(H.bodyparts) + random_part.generic_bleedstacks += 5 * power + return 1 + +/* +////////////////////////////////////// + +Autophagocytosis (AKA Programmed mass cell death) + + Very noticable. + Lowers resistance. + Fast stage speed. + Decreases transmittablity. + Fatal Level. + +Bonus + Deals brute damage over time. + +////////////////////////////////////// +*/ + +/datum/symptom/flesh_death + + name = "Autophagocytosis Necrosis" + desc = "The virus rapidly consumes infected cells, leading to heavy and widespread damage." + stealth = -2 + resistance = -2 + stage_speed = 1 + transmittable = -2 + level = 7 + severity = 6 + base_message_chance = 50 + symptom_delay_min = 3 + symptom_delay_max = 6 + var/chems = FALSE + var/zombie = FALSE + threshold_descs = list( + "Stage Speed 7" = "Synthesizes Heparin and Lipolicide inside the host, causing increased bleeding and hunger.", + "Stealth 5" = "The symptom remains hidden until active.", + ) + +/datum/symptom/flesh_death/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 5) + suppress_warning = TRUE + if(A.properties["stage_rate"] >= 7) //bleeding and hunger + chems = TRUE + +/datum/symptom/flesh_death/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(2,3) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You feel your body break apart.", "Your skin rubs off like dust.")]") + if(4,5) + if(prob(base_message_chance / 2)) //reduce spam + to_chat(M, "[pick("You feel your muscles weakening.", "Some of your skin detaches itself.", "You feel sandy.")]") + Flesh_death(M, A) + +/datum/symptom/flesh_death/proc/Flesh_death(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(6,10) + M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) + if(chems) + M.reagents.add_reagent_list(list(/datum/reagent/toxin/heparin = 2, /datum/reagent/toxin/lipolicide = 2)) + if(zombie) + M.reagents.add_reagent(/datum/reagent/romerol, 1) + return 1 diff --git a/code/datums/diseases/advance/symptoms/genetics.dm b/code/datums/diseases/advance/symptoms/genetics.dm index 5f534faeb71..0cc5d6dc8c2 100644 --- a/code/datums/diseases/advance/symptoms/genetics.dm +++ b/code/datums/diseases/advance/symptoms/genetics.dm @@ -1,70 +1,70 @@ -/* -////////////////////////////////////// - -DNA Saboteur - - Very noticable. - Lowers resistance tremendously. - No changes to stage speed. - Decreases transmittablity tremendously. - Fatal Level. - -Bonus - Cleans the DNA of a person and then randomly gives them a trait. - -////////////////////////////////////// -*/ - -/datum/symptom/genetic_mutation - name = "Dormant DNA Activator" - desc = "The virus bonds with the DNA of the host, activating random dormant mutations within their DNA. When the virus is cured, the host's genetic alterations are undone." - stealth = -2 - resistance = -3 - stage_speed = 0 - transmittable = -3 - level = 6 - severity = 4 - base_message_chance = 50 - symptom_delay_min = 30 - symptom_delay_max = 60 - var/excludemuts = NONE - var/no_reset = FALSE - var/mutadone_proof = NONE - threshold_descs = list( - "Resistance 8" = "The negative and mildly negative mutations caused by the virus are mutadone-proof (but will still be undone when the virus is cured if the resistance 14 threshold is not met).", - "Resistance 14" = "The host's genetic alterations are not undone when the virus is cured.", - "Stage Speed 10" = "The virus activates dormant mutations at a much faster rate.", - "Stealth 5" = "Only activates negative mutations in hosts." - ) - -/datum/symptom/genetic_mutation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 5) //only give them bad mutations - excludemuts = POSITIVE - if(A.properties["stage_rate"] >= 10) //activate dormant mutations more often at around 1.5x the pace - symptom_delay_min = 20 - symptom_delay_max = 40 - if(A.properties["resistance"] >= 8) //mutadone won't save you now - mutadone_proof = (NEGATIVE | MINOR_NEGATIVE) - if(A.properties["resistance"] >= 14) //one does not simply escape Nurgle's grasp - no_reset = TRUE - -/datum/symptom/genetic_mutation/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/C = A.affected_mob - if(!C.has_dna()) - return - switch(A.stage) - if(4, 5) - to_chat(C, "[pick("Your skin feels itchy.", "You feel light headed.")]") - C.easy_randmut((NEGATIVE | MINOR_NEGATIVE | POSITIVE) - excludemuts, TRUE, TRUE, TRUE, mutadone_proof) - -/datum/symptom/genetic_mutation/End(datum/disease/advance/A) - if(!..()) - return - if(!no_reset) - var/mob/living/carbon/M = A.affected_mob - if(M.has_dna()) - M.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), FALSE) +/* +////////////////////////////////////// + +DNA Saboteur + + Very noticable. + Lowers resistance tremendously. + No changes to stage speed. + Decreases transmittablity tremendously. + Fatal Level. + +Bonus + Cleans the DNA of a person and then randomly gives them a trait. + +////////////////////////////////////// +*/ + +/datum/symptom/genetic_mutation + name = "Dormant DNA Activator" + desc = "The virus bonds with the DNA of the host, activating random dormant mutations within their DNA. When the virus is cured, the host's genetic alterations are undone." + stealth = -2 + resistance = -3 + stage_speed = 0 + transmittable = -3 + level = 6 + severity = 4 + base_message_chance = 50 + symptom_delay_min = 30 + symptom_delay_max = 60 + var/excludemuts = NONE + var/no_reset = FALSE + var/mutadone_proof = NONE + threshold_descs = list( + "Resistance 8" = "The negative and mildly negative mutations caused by the virus are mutadone-proof (but will still be undone when the virus is cured if the resistance 14 threshold is not met).", + "Resistance 14" = "The host's genetic alterations are not undone when the virus is cured.", + "Stage Speed 10" = "The virus activates dormant mutations at a much faster rate.", + "Stealth 5" = "Only activates negative mutations in hosts." + ) + +/datum/symptom/genetic_mutation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 5) //only give them bad mutations + excludemuts = POSITIVE + if(A.properties["stage_rate"] >= 10) //activate dormant mutations more often at around 1.5x the pace + symptom_delay_min = 20 + symptom_delay_max = 40 + if(A.properties["resistance"] >= 8) //mutadone won't save you now + mutadone_proof = (NEGATIVE | MINOR_NEGATIVE) + if(A.properties["resistance"] >= 14) //one does not simply escape Nurgle's grasp + no_reset = TRUE + +/datum/symptom/genetic_mutation/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/C = A.affected_mob + if(!C.has_dna()) + return + switch(A.stage) + if(4, 5) + to_chat(C, "[pick("Your skin feels itchy.", "You feel light headed.")]") + C.easy_randmut((NEGATIVE | MINOR_NEGATIVE | POSITIVE) - excludemuts, TRUE, TRUE, TRUE, mutadone_proof) + +/datum/symptom/genetic_mutation/End(datum/disease/advance/A) + if(!..()) + return + if(!no_reset) + var/mob/living/carbon/M = A.affected_mob + if(M.has_dna()) + M.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), FALSE) diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm index 663c8020f6a..16d3b8a9e5a 100644 --- a/code/datums/diseases/advance/symptoms/hallucigen.dm +++ b/code/datums/diseases/advance/symptoms/hallucigen.dm @@ -1,70 +1,70 @@ -/* -////////////////////////////////////// - -Hallucigen - - Very noticable. - Lowers resistance considerably. - Decreases stage speed. - Reduced transmittable. - Critical Level. - -Bonus - Makes the affected mob be hallucinated for short periods of time. - -////////////////////////////////////// -*/ - -/datum/symptom/hallucigen - name = "Hallucigen" - desc = "The virus stimulates the brain, causing occasional hallucinations." - stealth = -1 - resistance = -3 - stage_speed = -3 - transmittable = -1 - level = 5 - severity = 2 - base_message_chance = 25 - symptom_delay_min = 25 - symptom_delay_max = 90 - var/fake_healthy = FALSE - threshold_descs = list( - "Stage Speed 7" = "Increases the amount of hallucinations.", - "Stealth 4" = "The virus mimics positive symptoms.", - ) - -/datum/symptom/hallucigen/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) //fake good symptom messages - fake_healthy = TRUE - base_message_chance = 50 - if(A.properties["stage_rate"] >= 7) //stronger hallucinations - power = 2 - -/datum/symptom/hallucigen/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - var/list/healthy_messages = list("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.",\ - "Your eyes feel great.", "Your ears feel great.", "You don't feel the need to blink.") - switch(A.stage) - if(1, 2) - if(prob(base_message_chance)) - if(!fake_healthy) - to_chat(M, "[pick("Something appears in your peripheral vision, then winks out.", "You hear a faint whisper with no source.", "Your head aches.")]") - else - to_chat(M, "[pick(healthy_messages)]") - if(3, 4) - if(prob(base_message_chance)) - if(!fake_healthy) - to_chat(M, "[pick("Something is following you.", "You are being watched.", "You hear a whisper in your ear.", "Thumping footsteps slam toward you from nowhere.")]") - else - to_chat(M, "[pick(healthy_messages)]") - else - if(prob(base_message_chance)) - if(!fake_healthy) - to_chat(M, "[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]") - else - to_chat(M, "[pick(healthy_messages)]") - M.hallucination += (45 * power) +/* +////////////////////////////////////// + +Hallucigen + + Very noticable. + Lowers resistance considerably. + Decreases stage speed. + Reduced transmittable. + Critical Level. + +Bonus + Makes the affected mob be hallucinated for short periods of time. + +////////////////////////////////////// +*/ + +/datum/symptom/hallucigen + name = "Hallucigen" + desc = "The virus stimulates the brain, causing occasional hallucinations." + stealth = -1 + resistance = -3 + stage_speed = -3 + transmittable = -1 + level = 5 + severity = 2 + base_message_chance = 25 + symptom_delay_min = 25 + symptom_delay_max = 90 + var/fake_healthy = FALSE + threshold_descs = list( + "Stage Speed 7" = "Increases the amount of hallucinations.", + "Stealth 4" = "The virus mimics positive symptoms.", + ) + +/datum/symptom/hallucigen/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) //fake good symptom messages + fake_healthy = TRUE + base_message_chance = 50 + if(A.properties["stage_rate"] >= 7) //stronger hallucinations + power = 2 + +/datum/symptom/hallucigen/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + var/list/healthy_messages = list("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.",\ + "Your eyes feel great.", "Your ears feel great.", "You don't feel the need to blink.") + switch(A.stage) + if(1, 2) + if(prob(base_message_chance)) + if(!fake_healthy) + to_chat(M, "[pick("Something appears in your peripheral vision, then winks out.", "You hear a faint whisper with no source.", "Your head aches.")]") + else + to_chat(M, "[pick(healthy_messages)]") + if(3, 4) + if(prob(base_message_chance)) + if(!fake_healthy) + to_chat(M, "[pick("Something is following you.", "You are being watched.", "You hear a whisper in your ear.", "Thumping footsteps slam toward you from nowhere.")]") + else + to_chat(M, "[pick(healthy_messages)]") + else + if(prob(base_message_chance)) + if(!fake_healthy) + to_chat(M, "[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]") + else + to_chat(M, "[pick(healthy_messages)]") + M.hallucination += (45 * power) diff --git a/code/datums/diseases/advance/symptoms/headache.dm b/code/datums/diseases/advance/symptoms/headache.dm index 65238a09851..5082e8aff31 100644 --- a/code/datums/diseases/advance/symptoms/headache.dm +++ b/code/datums/diseases/advance/symptoms/headache.dm @@ -1,62 +1,62 @@ -/* -////////////////////////////////////// - -Headache - - Noticable. - Highly resistant. - Increases stage speed. - Not transmittable. - Low Level. - -BONUS - Displays an annoying message! - Should be used for buffing your disease. - -////////////////////////////////////// -*/ - -/datum/symptom/headache - - name = "Headache" - desc = "The virus causes inflammation inside the brain, causing constant headaches." - stealth = -1 - resistance = 4 - stage_speed = 2 - transmittable = 0 - level = 1 - severity = 1 - base_message_chance = 100 - symptom_delay_min = 15 - symptom_delay_max = 30 - threshold_descs = list( - "Stage Speed 6" = "Headaches will cause severe pain, that weakens the host.", - "Stage Speed 9" = "Headaches become less frequent but far more intense, preventing any action from the host.", - "Stealth 4" = "Reduces headache frequency until later stages.", - ) - -/datum/symptom/headache/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - base_message_chance = 50 - if(A.properties["stage_rate"] >= 6) //severe pain - power = 2 - if(A.properties["stage_rate"] >= 9) //cluster headaches - symptom_delay_min = 30 - symptom_delay_max = 60 - power = 3 - -/datum/symptom/headache/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(power < 2) - if(prob(base_message_chance) || A.stage >=4) - to_chat(M, "[pick("Your head hurts.", "Your head pounds.")]") - if(power >= 2 && A.stage >= 4) - to_chat(M, "[pick("Your head hurts a lot.", "Your head pounds incessantly.")]") - M.adjustStaminaLoss(25) - if(power >= 3 && A.stage >= 5) - to_chat(M, "[pick("Your head hurts!", "You feel a burning knife inside your brain!", "A wave of pain fills your head!")]") - M.Stun(35) +/* +////////////////////////////////////// + +Headache + + Noticable. + Highly resistant. + Increases stage speed. + Not transmittable. + Low Level. + +BONUS + Displays an annoying message! + Should be used for buffing your disease. + +////////////////////////////////////// +*/ + +/datum/symptom/headache + + name = "Headache" + desc = "The virus causes inflammation inside the brain, causing constant headaches." + stealth = -1 + resistance = 4 + stage_speed = 2 + transmittable = 0 + level = 1 + severity = 1 + base_message_chance = 100 + symptom_delay_min = 15 + symptom_delay_max = 30 + threshold_descs = list( + "Stage Speed 6" = "Headaches will cause severe pain, that weakens the host.", + "Stage Speed 9" = "Headaches become less frequent but far more intense, preventing any action from the host.", + "Stealth 4" = "Reduces headache frequency until later stages.", + ) + +/datum/symptom/headache/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + base_message_chance = 50 + if(A.properties["stage_rate"] >= 6) //severe pain + power = 2 + if(A.properties["stage_rate"] >= 9) //cluster headaches + symptom_delay_min = 30 + symptom_delay_max = 60 + power = 3 + +/datum/symptom/headache/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(power < 2) + if(prob(base_message_chance) || A.stage >=4) + to_chat(M, "[pick("Your head hurts.", "Your head pounds.")]") + if(power >= 2 && A.stage >= 4) + to_chat(M, "[pick("Your head hurts a lot.", "Your head pounds incessantly.")]") + M.adjustStaminaLoss(25) + if(power >= 3 && A.stage >= 5) + to_chat(M, "[pick("Your head hurts!", "You feel a burning knife inside your brain!", "A wave of pain fills your head!")]") + M.Stun(35) diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm index a7eb39505d1..7b735a98c6a 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -1,500 +1,500 @@ -/datum/symptom/heal - name = "Basic Healing (does nothing)" //warning for adminspawn viruses - desc = "You should not be seeing this." - stealth = 0 - resistance = 0 - stage_speed = 0 - transmittable = 0 - level = 0 //not obtainable - base_message_chance = 20 //here used for the overlays - symptom_delay_min = 1 - symptom_delay_max = 1 - var/passive_message = "" //random message to infected but not actively healing people - threshold_descs = list( - "Stage Speed 6" = "Doubles healing speed.", - "Stealth 4" = "Healing will no longer be visible to onlookers.", - ) - -/datum/symptom/heal/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 6) //stronger healing - power = 2 - -/datum/symptom/heal/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(4, 5) - var/effectiveness = CanHeal(A) - if(!effectiveness) - if(passive_message && prob(2) && passive_message_condition(M)) - to_chat(M, passive_message) - return - else - Heal(M, A, effectiveness) - return - -/datum/symptom/heal/proc/CanHeal(datum/disease/advance/A) - return power - -/datum/symptom/heal/proc/Heal(mob/living/M, datum/disease/advance/A, actual_power) - return TRUE - -/datum/symptom/heal/proc/passive_message_condition(mob/living/M) - return TRUE - - -/datum/symptom/heal/starlight - name = "Starlight Condensation" - desc = "The virus reacts to direct starlight, producing regenerative chemicals. Works best against toxin-based damage." - stealth = -1 - resistance = -2 - stage_speed = 0 - transmittable = 1 - level = 6 - passive_message = "You miss the feeling of starlight on your skin." - var/nearspace_penalty = 0.3 - threshold_descs = list( - "Stage Speed 6" = "Increases healing speed.", - "Transmission 6" = "Removes penalty for only being close to space.", - ) - -/datum/symptom/heal/starlight/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["transmittable"] >= 6) - nearspace_penalty = 1 - if(A.properties["stage_rate"] >= 6) - power = 2 - -/datum/symptom/heal/starlight/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - if(istype(get_turf(M), /turf/open/space)) - return power - else - for(var/turf/T in view(M, 2)) - if(istype(T, /turf/open/space)) - return power * nearspace_penalty - -/datum/symptom/heal/starlight/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = actual_power - if(M.getToxLoss() && prob(5)) - to_chat(M, "Your skin tingles as the starlight seems to heal you.") - - M.adjustToxLoss(-(4 * heal_amt)) //most effective on toxins - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 - -/datum/symptom/heal/starlight/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss() || M.getToxLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/chem - name = "Toxolysis" - stealth = 0 - resistance = -2 - stage_speed = 2 - transmittable = -2 - level = 7 - var/food_conversion = FALSE - desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream." - threshold_descs = list( - "Resistance 7" = "Increases chem removal speed.", - "Stage Speed 6" = "Consumed chemicals nourish the host.", - ) - -/datum/symptom/heal/chem/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 6) - food_conversion = TRUE - if(A.properties["resistance"] >= 7) - power = 2 - -/datum/symptom/heal/chem/Heal(mob/living/M, datum/disease/advance/A, actual_power) - for(var/datum/reagent/R in M.reagents.reagent_list) //Not just toxins! - M.reagents.remove_reagent(R.type, actual_power) - if(food_conversion) - M.adjust_nutrition(0.3) - if(prob(2)) - to_chat(M, "You feel a mild warmth as your blood purifies itself.") - return 1 - - - -/datum/symptom/heal/metabolism - name = "Metabolic Boost" - stealth = -1 - resistance = -2 - stage_speed = 2 - transmittable = 1 - level = 7 - var/triple_metabolism = FALSE - var/reduced_hunger = FALSE - desc = "The virus causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\ - but also causing increased hunger." - threshold_descs = list( - "Stealth 3" = "Reduces hunger rate.", - "Stage Speed 10" = "Chemical metabolization is tripled instead of doubled.", - ) - -/datum/symptom/heal/metabolism/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 10) - triple_metabolism = TRUE - if(A.properties["stealth"] >= 3) - reduced_hunger = TRUE - -/datum/symptom/heal/metabolism/Heal(mob/living/carbon/C, datum/disease/advance/A, actual_power) - if(!istype(C)) - return - C.reagents.metabolize(C, can_overdose=TRUE) //this works even without a liver; it's intentional since the virus is metabolizing by itself - if(triple_metabolism) - C.reagents.metabolize(C, can_overdose=TRUE) - C.overeatduration = max(C.overeatduration - 2, 0) - var/lost_nutrition = 9 - (reduced_hunger * 5) - C.adjust_nutrition(-lost_nutrition * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed - if(prob(2)) - to_chat(C, "You feel an odd gurgle in your stomach, as if it was working much faster than normal.") - return 1 - -/datum/symptom/heal/darkness - name = "Nocturnal Regeneration" - desc = "The virus is able to mend the host's flesh when in conditions of low light, repairing physical damage. More effective against brute damage." - stealth = 2 - resistance = -1 - stage_speed = -2 - transmittable = -1 - level = 6 - passive_message = "You feel tingling on your skin as light passes over it." - threshold_descs = list( - "Stage Speed 8" = "Doubles healing speed.", - ) - -/datum/symptom/heal/darkness/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 8) - power = 2 - -/datum/symptom/heal/darkness/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - var/light_amount = 0 - if(isturf(M.loc)) //else, there's considered to be no light - var/turf/T = M.loc - light_amount = min(1,T.get_lumcount()) - 0.5 - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - return power - -/datum/symptom/heal/darkness/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 2 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - if(prob(5)) - to_chat(M, "The darkness soothes and mends your wounds.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len * 0.5, null, BODYPART_ORGANIC)) //more effective on brute - M.update_damage_overlays() - return 1 - -/datum/symptom/heal/darkness/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/coma - name = "Regenerative Coma" - desc = "The virus causes the host to fall into a death-like coma when severely damaged, then rapidly fixes the damage." - stealth = 0 - resistance = 2 - stage_speed = -3 - transmittable = -2 - level = 8 - passive_message = "The pain from your wounds makes you feel oddly sleepy..." - var/deathgasp = FALSE - var/stabilize = FALSE - var/active_coma = FALSE //to prevent multiple coma procs - threshold_descs = list( - "Stealth 2" = "Host appears to die when falling into a coma.", - "Resistance 4" = "The virus also stabilizes the host while they are in critical condition.", - "Stage Speed 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/coma/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - power = 1.5 - if(A.properties["resistance"] >= 4) - stabilize = TRUE - if(A.properties["stealth"] >= 2) - deathgasp = TRUE - -/datum/symptom/heal/coma/on_stage_change(datum/disease/advance/A) //mostly copy+pasted from the code for self-respiration's TRAIT_NOBREATH stuff - if(!..()) - return FALSE - if(A.stage >= 4 && stabilize) - ADD_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) - else - REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) - return TRUE - -/datum/symptom/heal/coma/End(datum/disease/advance/A) - if(!..()) - return - if(active_coma) - uncoma() - REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) - -/datum/symptom/heal/coma/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - if(HAS_TRAIT(M, TRAIT_DEATHCOMA)) - return power - if(M.IsSleeping()) - return power * 0.25 //Voluntary unconsciousness yields lower healing. - if(M.stat == UNCONSCIOUS) - return power * 0.9 - if(M.stat == SOFT_CRIT) - return power * 0.5 - if(M.getBruteLoss() + M.getFireLoss() >= 70 && !active_coma) - to_chat(M, "You feel yourself slip into a regenerative coma...") - active_coma = TRUE - addtimer(CALLBACK(src, .proc/coma, M), 60) - -/datum/symptom/heal/coma/proc/coma(mob/living/M) - M.fakedeath("regenerative_coma", !deathgasp) - M.update_mobility() - addtimer(CALLBACK(src, .proc/uncoma, M), 300) - -/datum/symptom/heal/coma/proc/uncoma(mob/living/M) - if(!active_coma) - return - active_coma = FALSE - M.cure_fakedeath("regenerative_coma") - M.update_mobility() - -/datum/symptom/heal/coma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 4 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1) - - if(!parts.len) - return - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - - if(active_coma && M.getBruteLoss() + M.getFireLoss() == 0) - uncoma(M) - - return 1 - -/datum/symptom/heal/coma/passive_message_condition(mob/living/M) - if((M.getBruteLoss() + M.getFireLoss()) > 30) - return TRUE - return FALSE - -/datum/symptom/heal/water - name = "Tissue Hydration" - desc = "The virus uses excess water inside and outside the body to repair damaged tissue cells. More effective when using holy water and against burns." - stealth = 0 - resistance = -1 - stage_speed = 0 - transmittable = 1 - level = 6 - passive_message = "Your skin feels oddly dry..." - var/absorption_coeff = 1 - threshold_descs = list( - "Resistance 5" = "Water is consumed at a much slower rate.", - "Stage Speed 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/water/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - power = 2 - if(A.properties["resistance"] >= 5) - absorption_coeff = 0.25 - -/datum/symptom/heal/water/CanHeal(datum/disease/advance/A) - . = 0 - var/mob/living/M = A.affected_mob - if(M.fire_stacks < 0) - M.fire_stacks = min(M.fire_stacks + 1 * absorption_coeff, 0) - . += power - if(M.reagents.has_reagent(/datum/reagent/water/holywater, needs_metabolizing = FALSE)) - M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) - . += power * 0.75 - else if(M.reagents.has_reagent(/datum/reagent/water, needs_metabolizing = FALSE)) - M.reagents.remove_reagent(/datum/reagent/water, 0.5 * absorption_coeff) - . += power * 0.5 - -/datum/symptom/heal/water/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 2 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) //more effective on burns - - if(!parts.len) - return - - if(prob(5)) - to_chat(M, "You feel yourself absorbing the water around you to soothe your damaged skin.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len * 0.5, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - - return 1 - -/datum/symptom/heal/water/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/plasma - name = "Plasma Fixation" - desc = "The virus draws plasma from the atmosphere and from inside the body to heal and stabilize body temperature." - stealth = 0 - resistance = 3 - stage_speed = -2 - transmittable = -2 - level = 8 - passive_message = "You feel an odd attraction to plasma." - var/temp_rate = 1 - threshold_descs = list( - "Transmission 6" = "Increases temperature adjustment rate.", - "Stage Speed 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/plasma/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - power = 2 - if(A.properties["transmittable"] >= 6) - temp_rate = 4 - -/datum/symptom/heal/plasma/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - var/datum/gas_mixture/environment - var/list/gases - - . = 0 - - if(M.loc) - environment = M.loc.return_air() - if(environment) - gases = environment.gases - if(gases[/datum/gas/plasma] && gases[/datum/gas/plasma][MOLES] > gases[/datum/gas/plasma][GAS_META][META_GAS_MOLES_VISIBLE]) //if there's enough plasma in the air to see - . += power * 0.5 - if(M.reagents.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) - . += power * 0.75 - -/datum/symptom/heal/plasma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 4 * actual_power - - if(prob(5)) - to_chat(M, "You feel yourself absorbing plasma inside and around you...") - - if(M.bodytemperature > M.get_body_temp_normal()) - M.adjust_bodytemperature(-20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT, M.get_body_temp_normal()) - if(prob(5)) - to_chat(M, "You feel less hot.") - else if(M.bodytemperature < (M.get_body_temp_normal() + 1)) - M.adjust_bodytemperature(20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,0, M.get_body_temp_normal()) - if(prob(5)) - to_chat(M, "You feel warmer.") - - M.adjustToxLoss(-heal_amt) - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - if(!parts.len) - return - if(prob(5)) - to_chat(M, "The pain from your wounds fades rapidly.") - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 - - -/datum/symptom/heal/radiation - name = "Radioactive Resonance" - desc = "The virus uses radiation to fix damage through dna mutations." - stealth = -1 - resistance = -2 - stage_speed = 2 - transmittable = -3 - level = 6 - symptom_delay_min = 1 - symptom_delay_max = 1 - passive_message = "Your skin glows faintly for a moment." - var/cellular_damage = FALSE - threshold_descs = list( - "Transmission 6" = "Additionally heals cellular damage.", - "Resistance 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/radiation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 7) - power = 2 - if(A.properties["transmittable"] >= 6) - cellular_damage = TRUE - -/datum/symptom/heal/radiation/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - switch(M.radiation) - if(0) - return FALSE - if(1 to RAD_MOB_SAFE) - return 0.25 - if(RAD_MOB_SAFE to RAD_BURN_THRESHOLD) - return 0.5 - if(RAD_BURN_THRESHOLD to RAD_MOB_MUTATE) - return 0.75 - if(RAD_MOB_MUTATE to RAD_MOB_KNOCKDOWN) - return 1 - else - return 1.5 - -/datum/symptom/heal/radiation/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = actual_power - - if(cellular_damage) - M.adjustCloneLoss(-heal_amt * 0.5) - - M.adjustToxLoss(-(2 * heal_amt)) - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - if(prob(4)) - to_chat(M, "Your skin glows faintly, and you feel your wounds mending themselves.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 +/datum/symptom/heal + name = "Basic Healing (does nothing)" //warning for adminspawn viruses + desc = "You should not be seeing this." + stealth = 0 + resistance = 0 + stage_speed = 0 + transmittable = 0 + level = 0 //not obtainable + base_message_chance = 20 //here used for the overlays + symptom_delay_min = 1 + symptom_delay_max = 1 + var/passive_message = "" //random message to infected but not actively healing people + threshold_descs = list( + "Stage Speed 6" = "Doubles healing speed.", + "Stealth 4" = "Healing will no longer be visible to onlookers.", + ) + +/datum/symptom/heal/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 6) //stronger healing + power = 2 + +/datum/symptom/heal/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(4, 5) + var/effectiveness = CanHeal(A) + if(!effectiveness) + if(passive_message && prob(2) && passive_message_condition(M)) + to_chat(M, passive_message) + return + else + Heal(M, A, effectiveness) + return + +/datum/symptom/heal/proc/CanHeal(datum/disease/advance/A) + return power + +/datum/symptom/heal/proc/Heal(mob/living/M, datum/disease/advance/A, actual_power) + return TRUE + +/datum/symptom/heal/proc/passive_message_condition(mob/living/M) + return TRUE + + +/datum/symptom/heal/starlight + name = "Starlight Condensation" + desc = "The virus reacts to direct starlight, producing regenerative chemicals. Works best against toxin-based damage." + stealth = -1 + resistance = -2 + stage_speed = 0 + transmittable = 1 + level = 6 + passive_message = "You miss the feeling of starlight on your skin." + var/nearspace_penalty = 0.3 + threshold_descs = list( + "Stage Speed 6" = "Increases healing speed.", + "Transmission 6" = "Removes penalty for only being close to space.", + ) + +/datum/symptom/heal/starlight/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["transmittable"] >= 6) + nearspace_penalty = 1 + if(A.properties["stage_rate"] >= 6) + power = 2 + +/datum/symptom/heal/starlight/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + if(istype(get_turf(M), /turf/open/space)) + return power + else + for(var/turf/T in view(M, 2)) + if(istype(T, /turf/open/space)) + return power * nearspace_penalty + +/datum/symptom/heal/starlight/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = actual_power + if(M.getToxLoss() && prob(5)) + to_chat(M, "Your skin tingles as the starlight seems to heal you.") + + M.adjustToxLoss(-(4 * heal_amt)) //most effective on toxins + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 + +/datum/symptom/heal/starlight/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss() || M.getToxLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/chem + name = "Toxolysis" + stealth = 0 + resistance = -2 + stage_speed = 2 + transmittable = -2 + level = 7 + var/food_conversion = FALSE + desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream." + threshold_descs = list( + "Resistance 7" = "Increases chem removal speed.", + "Stage Speed 6" = "Consumed chemicals nourish the host.", + ) + +/datum/symptom/heal/chem/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 6) + food_conversion = TRUE + if(A.properties["resistance"] >= 7) + power = 2 + +/datum/symptom/heal/chem/Heal(mob/living/M, datum/disease/advance/A, actual_power) + for(var/datum/reagent/R in M.reagents.reagent_list) //Not just toxins! + M.reagents.remove_reagent(R.type, actual_power) + if(food_conversion) + M.adjust_nutrition(0.3) + if(prob(2)) + to_chat(M, "You feel a mild warmth as your blood purifies itself.") + return 1 + + + +/datum/symptom/heal/metabolism + name = "Metabolic Boost" + stealth = -1 + resistance = -2 + stage_speed = 2 + transmittable = 1 + level = 7 + var/triple_metabolism = FALSE + var/reduced_hunger = FALSE + desc = "The virus causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\ + but also causing increased hunger." + threshold_descs = list( + "Stealth 3" = "Reduces hunger rate.", + "Stage Speed 10" = "Chemical metabolization is tripled instead of doubled.", + ) + +/datum/symptom/heal/metabolism/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 10) + triple_metabolism = TRUE + if(A.properties["stealth"] >= 3) + reduced_hunger = TRUE + +/datum/symptom/heal/metabolism/Heal(mob/living/carbon/C, datum/disease/advance/A, actual_power) + if(!istype(C)) + return + C.reagents.metabolize(C, can_overdose=TRUE) //this works even without a liver; it's intentional since the virus is metabolizing by itself + if(triple_metabolism) + C.reagents.metabolize(C, can_overdose=TRUE) + C.overeatduration = max(C.overeatduration - 2, 0) + var/lost_nutrition = 9 - (reduced_hunger * 5) + C.adjust_nutrition(-lost_nutrition * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed + if(prob(2)) + to_chat(C, "You feel an odd gurgle in your stomach, as if it was working much faster than normal.") + return 1 + +/datum/symptom/heal/darkness + name = "Nocturnal Regeneration" + desc = "The virus is able to mend the host's flesh when in conditions of low light, repairing physical damage. More effective against brute damage." + stealth = 2 + resistance = -1 + stage_speed = -2 + transmittable = -1 + level = 6 + passive_message = "You feel tingling on your skin as light passes over it." + threshold_descs = list( + "Stage Speed 8" = "Doubles healing speed.", + ) + +/datum/symptom/heal/darkness/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 8) + power = 2 + +/datum/symptom/heal/darkness/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + var/light_amount = 0 + if(isturf(M.loc)) //else, there's considered to be no light + var/turf/T = M.loc + light_amount = min(1,T.get_lumcount()) - 0.5 + if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + return power + +/datum/symptom/heal/darkness/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 2 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + if(prob(5)) + to_chat(M, "The darkness soothes and mends your wounds.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len * 0.5, null, BODYPART_ORGANIC)) //more effective on brute + M.update_damage_overlays() + return 1 + +/datum/symptom/heal/darkness/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/coma + name = "Regenerative Coma" + desc = "The virus causes the host to fall into a death-like coma when severely damaged, then rapidly fixes the damage." + stealth = 0 + resistance = 2 + stage_speed = -3 + transmittable = -2 + level = 8 + passive_message = "The pain from your wounds makes you feel oddly sleepy..." + var/deathgasp = FALSE + var/stabilize = FALSE + var/active_coma = FALSE //to prevent multiple coma procs + threshold_descs = list( + "Stealth 2" = "Host appears to die when falling into a coma.", + "Resistance 4" = "The virus also stabilizes the host while they are in critical condition.", + "Stage Speed 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/coma/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + power = 1.5 + if(A.properties["resistance"] >= 4) + stabilize = TRUE + if(A.properties["stealth"] >= 2) + deathgasp = TRUE + +/datum/symptom/heal/coma/on_stage_change(datum/disease/advance/A) //mostly copy+pasted from the code for self-respiration's TRAIT_NOBREATH stuff + if(!..()) + return FALSE + if(A.stage >= 4 && stabilize) + ADD_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) + else + REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) + return TRUE + +/datum/symptom/heal/coma/End(datum/disease/advance/A) + if(!..()) + return + if(active_coma) + uncoma() + REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) + +/datum/symptom/heal/coma/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + if(HAS_TRAIT(M, TRAIT_DEATHCOMA)) + return power + if(M.IsSleeping()) + return power * 0.25 //Voluntary unconsciousness yields lower healing. + if(M.stat == UNCONSCIOUS) + return power * 0.9 + if(M.stat == SOFT_CRIT) + return power * 0.5 + if(M.getBruteLoss() + M.getFireLoss() >= 70 && !active_coma) + to_chat(M, "You feel yourself slip into a regenerative coma...") + active_coma = TRUE + addtimer(CALLBACK(src, .proc/coma, M), 60) + +/datum/symptom/heal/coma/proc/coma(mob/living/M) + M.fakedeath("regenerative_coma", !deathgasp) + M.update_mobility() + addtimer(CALLBACK(src, .proc/uncoma, M), 300) + +/datum/symptom/heal/coma/proc/uncoma(mob/living/M) + if(!active_coma) + return + active_coma = FALSE + M.cure_fakedeath("regenerative_coma") + M.update_mobility() + +/datum/symptom/heal/coma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 4 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1) + + if(!parts.len) + return + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + + if(active_coma && M.getBruteLoss() + M.getFireLoss() == 0) + uncoma(M) + + return 1 + +/datum/symptom/heal/coma/passive_message_condition(mob/living/M) + if((M.getBruteLoss() + M.getFireLoss()) > 30) + return TRUE + return FALSE + +/datum/symptom/heal/water + name = "Tissue Hydration" + desc = "The virus uses excess water inside and outside the body to repair damaged tissue cells. More effective when using holy water and against burns." + stealth = 0 + resistance = -1 + stage_speed = 0 + transmittable = 1 + level = 6 + passive_message = "Your skin feels oddly dry..." + var/absorption_coeff = 1 + threshold_descs = list( + "Resistance 5" = "Water is consumed at a much slower rate.", + "Stage Speed 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/water/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + power = 2 + if(A.properties["resistance"] >= 5) + absorption_coeff = 0.25 + +/datum/symptom/heal/water/CanHeal(datum/disease/advance/A) + . = 0 + var/mob/living/M = A.affected_mob + if(M.fire_stacks < 0) + M.fire_stacks = min(M.fire_stacks + 1 * absorption_coeff, 0) + . += power + if(M.reagents.has_reagent(/datum/reagent/water/holywater, needs_metabolizing = FALSE)) + M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) + . += power * 0.75 + else if(M.reagents.has_reagent(/datum/reagent/water, needs_metabolizing = FALSE)) + M.reagents.remove_reagent(/datum/reagent/water, 0.5 * absorption_coeff) + . += power * 0.5 + +/datum/symptom/heal/water/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 2 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) //more effective on burns + + if(!parts.len) + return + + if(prob(5)) + to_chat(M, "You feel yourself absorbing the water around you to soothe your damaged skin.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len * 0.5, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + + return 1 + +/datum/symptom/heal/water/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/plasma + name = "Plasma Fixation" + desc = "The virus draws plasma from the atmosphere and from inside the body to heal and stabilize body temperature." + stealth = 0 + resistance = 3 + stage_speed = -2 + transmittable = -2 + level = 8 + passive_message = "You feel an odd attraction to plasma." + var/temp_rate = 1 + threshold_descs = list( + "Transmission 6" = "Increases temperature adjustment rate.", + "Stage Speed 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/plasma/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + power = 2 + if(A.properties["transmittable"] >= 6) + temp_rate = 4 + +/datum/symptom/heal/plasma/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + var/datum/gas_mixture/environment + var/list/gases + + . = 0 + + if(M.loc) + environment = M.loc.return_air() + if(environment) + gases = environment.gases + if(gases[/datum/gas/plasma] && gases[/datum/gas/plasma][MOLES] > gases[/datum/gas/plasma][GAS_META][META_GAS_MOLES_VISIBLE]) //if there's enough plasma in the air to see + . += power * 0.5 + if(M.reagents.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) + . += power * 0.75 + +/datum/symptom/heal/plasma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 4 * actual_power + + if(prob(5)) + to_chat(M, "You feel yourself absorbing plasma inside and around you...") + + if(M.bodytemperature > M.get_body_temp_normal()) + M.adjust_bodytemperature(-20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT, M.get_body_temp_normal()) + if(prob(5)) + to_chat(M, "You feel less hot.") + else if(M.bodytemperature < (M.get_body_temp_normal() + 1)) + M.adjust_bodytemperature(20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,0, M.get_body_temp_normal()) + if(prob(5)) + to_chat(M, "You feel warmer.") + + M.adjustToxLoss(-heal_amt) + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + if(!parts.len) + return + if(prob(5)) + to_chat(M, "The pain from your wounds fades rapidly.") + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 + + +/datum/symptom/heal/radiation + name = "Radioactive Resonance" + desc = "The virus uses radiation to fix damage through dna mutations." + stealth = -1 + resistance = -2 + stage_speed = 2 + transmittable = -3 + level = 6 + symptom_delay_min = 1 + symptom_delay_max = 1 + passive_message = "Your skin glows faintly for a moment." + var/cellular_damage = FALSE + threshold_descs = list( + "Transmission 6" = "Additionally heals cellular damage.", + "Resistance 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/radiation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 7) + power = 2 + if(A.properties["transmittable"] >= 6) + cellular_damage = TRUE + +/datum/symptom/heal/radiation/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + switch(M.radiation) + if(0) + return FALSE + if(1 to RAD_MOB_SAFE) + return 0.25 + if(RAD_MOB_SAFE to RAD_BURN_THRESHOLD) + return 0.5 + if(RAD_BURN_THRESHOLD to RAD_MOB_MUTATE) + return 0.75 + if(RAD_MOB_MUTATE to RAD_MOB_KNOCKDOWN) + return 1 + else + return 1.5 + +/datum/symptom/heal/radiation/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = actual_power + + if(cellular_damage) + M.adjustCloneLoss(-heal_amt * 0.5) + + M.adjustToxLoss(-(2 * heal_amt)) + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + if(prob(4)) + to_chat(M, "Your skin glows faintly, and you feel your wounds mending themselves.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 diff --git a/code/datums/diseases/advance/symptoms/itching.dm b/code/datums/diseases/advance/symptoms/itching.dm index 7204233d373..b31fa22ebc5 100644 --- a/code/datums/diseases/advance/symptoms/itching.dm +++ b/code/datums/diseases/advance/symptoms/itching.dm @@ -1,56 +1,56 @@ -/* -////////////////////////////////////// - -Itching - - Not noticable or unnoticable. - Resistant. - Increases stage speed. - Little transmissibility. - Low Level. - -BONUS - Displays an annoying message! - Should be used for buffing your disease. - -////////////////////////////////////// -*/ - -/datum/symptom/itching - - name = "Itching" - desc = "The virus irritates the skin, causing itching." - stealth = 0 - resistance = 3 - stage_speed = 3 - transmittable = 1 - level = 1 - severity = 1 - symptom_delay_min = 5 - symptom_delay_max = 25 - var/scratch = FALSE - threshold_descs = list( - "Transmission 6" = "Increases frequency of itching.", - "Stage Speed 7" = "The host will scrath itself when itching, causing superficial damage.", - ) - -/datum/symptom/itching/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["transmittable"] >= 6) //itch more often - symptom_delay_min = 1 - symptom_delay_max = 4 - if(A.properties["stage_rate"] >= 7) //scratch - scratch = TRUE - -/datum/symptom/itching/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - var/picked_bodypart = pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - var/obj/item/bodypart/bodypart = M.get_bodypart(picked_bodypart) - if(bodypart && bodypart.status == BODYPART_ORGANIC && !bodypart.is_pseudopart) //robotic limbs will mean less scratching overall (why are golems able to damage themselves with self-scratching, but not androids? the world may never know) - var/can_scratch = scratch && !M.incapacitated() - M.visible_message("[can_scratch ? "[M] scratches [M.p_their()] [bodypart.name]." : ""]", "Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]") - if(can_scratch) - bodypart.receive_damage(0.5) +/* +////////////////////////////////////// + +Itching + + Not noticable or unnoticable. + Resistant. + Increases stage speed. + Little transmissibility. + Low Level. + +BONUS + Displays an annoying message! + Should be used for buffing your disease. + +////////////////////////////////////// +*/ + +/datum/symptom/itching + + name = "Itching" + desc = "The virus irritates the skin, causing itching." + stealth = 0 + resistance = 3 + stage_speed = 3 + transmittable = 1 + level = 1 + severity = 1 + symptom_delay_min = 5 + symptom_delay_max = 25 + var/scratch = FALSE + threshold_descs = list( + "Transmission 6" = "Increases frequency of itching.", + "Stage Speed 7" = "The host will scrath itself when itching, causing superficial damage.", + ) + +/datum/symptom/itching/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["transmittable"] >= 6) //itch more often + symptom_delay_min = 1 + symptom_delay_max = 4 + if(A.properties["stage_rate"] >= 7) //scratch + scratch = TRUE + +/datum/symptom/itching/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + var/picked_bodypart = pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) + var/obj/item/bodypart/bodypart = M.get_bodypart(picked_bodypart) + if(bodypart && bodypart.status == BODYPART_ORGANIC && !bodypart.is_pseudopart) //robotic limbs will mean less scratching overall (why are golems able to damage themselves with self-scratching, but not androids? the world may never know) + var/can_scratch = scratch && !M.incapacitated() + M.visible_message("[can_scratch ? "[M] scratches [M.p_their()] [bodypart.name]." : ""]", "Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]") + if(can_scratch) + bodypart.receive_damage(0.5) diff --git a/code/datums/diseases/advance/symptoms/oxygen.dm b/code/datums/diseases/advance/symptoms/oxygen.dm index 7f067180c7e..9fe153865d3 100644 --- a/code/datums/diseases/advance/symptoms/oxygen.dm +++ b/code/datums/diseases/advance/symptoms/oxygen.dm @@ -1,69 +1,69 @@ -/* -////////////////////////////////////// - -Self-Respiration - - Slightly hidden. - Lowers resistance significantly. - Decreases stage speed significantly. - Decreases transmittablity tremendously. - Fatal Level. - -Bonus - The body generates salbutamol. - -////////////////////////////////////// -*/ - -/datum/symptom/oxygen - - name = "Self-Respiration" - desc = "The virus rapidly synthesizes oxygen, effectively removing the need for breathing." - stealth = 1 - resistance = -3 - stage_speed = -3 - transmittable = -4 - level = 6 - base_message_chance = 5 - symptom_delay_min = 1 - symptom_delay_max = 1 - var/regenerate_blood = FALSE - threshold_descs = list( - "Resistance 8" = "Additionally regenerates lost blood." - ) - -/datum/symptom/oxygen/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 8) //blood regeneration - regenerate_blood = TRUE - -/datum/symptom/oxygen/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(4, 5) - M.adjustOxyLoss(-7, 0) - M.losebreath = max(0, M.losebreath - 4) - if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL) - M.blood_volume += 1 - else - if(prob(base_message_chance)) - to_chat(M, "[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.")]") - return - -/datum/symptom/oxygen/on_stage_change(datum/disease/advance/A) - if(!..()) - return FALSE - var/mob/living/carbon/M = A.affected_mob - if(A.stage >= 4) - ADD_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) - else - REMOVE_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) - return TRUE - -/datum/symptom/oxygen/End(datum/disease/advance/A) - if(!..()) - return - REMOVE_TRAIT(A.affected_mob, TRAIT_NOBREATH, DISEASE_TRAIT) +/* +////////////////////////////////////// + +Self-Respiration + + Slightly hidden. + Lowers resistance significantly. + Decreases stage speed significantly. + Decreases transmittablity tremendously. + Fatal Level. + +Bonus + The body generates salbutamol. + +////////////////////////////////////// +*/ + +/datum/symptom/oxygen + + name = "Self-Respiration" + desc = "The virus rapidly synthesizes oxygen, effectively removing the need for breathing." + stealth = 1 + resistance = -3 + stage_speed = -3 + transmittable = -4 + level = 6 + base_message_chance = 5 + symptom_delay_min = 1 + symptom_delay_max = 1 + var/regenerate_blood = FALSE + threshold_descs = list( + "Resistance 8" = "Additionally regenerates lost blood." + ) + +/datum/symptom/oxygen/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 8) //blood regeneration + regenerate_blood = TRUE + +/datum/symptom/oxygen/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(4, 5) + M.adjustOxyLoss(-7, 0) + M.losebreath = max(0, M.losebreath - 4) + if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL) + M.blood_volume += 1 + else + if(prob(base_message_chance)) + to_chat(M, "[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.")]") + return + +/datum/symptom/oxygen/on_stage_change(datum/disease/advance/A) + if(!..()) + return FALSE + var/mob/living/carbon/M = A.affected_mob + if(A.stage >= 4) + ADD_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) + else + REMOVE_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) + return TRUE + +/datum/symptom/oxygen/End(datum/disease/advance/A) + if(!..()) + return + REMOVE_TRAIT(A.affected_mob, TRAIT_NOBREATH, DISEASE_TRAIT) diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm index 521264e565a..d1b59edbc1c 100644 --- a/code/datums/diseases/advance/symptoms/shedding.dm +++ b/code/datums/diseases/advance/symptoms/shedding.dm @@ -1,55 +1,55 @@ -/* -////////////////////////////////////// -Alopecia - - Not Noticeable. - Increases resistance slightly. - Increases stage speed. - Transmittable. - Intense Level. - -BONUS - Makes the mob lose hair. - -////////////////////////////////////// -*/ - -/datum/symptom/shedding - name = "Alopecia" - desc = "The virus causes rapid shedding of head and body hair." - stealth = 0 - resistance = 1 - stage_speed = 2 - transmittable = 2 - level = 4 - severity = 1 - base_message_chance = 50 - symptom_delay_min = 45 - symptom_delay_max = 90 - -/datum/symptom/shedding/Activate(datum/disease/advance/A) - if(!..()) - return - - var/mob/living/M = A.affected_mob - if(prob(base_message_chance)) - to_chat(M, "[pick("Your scalp itches.", "Your skin feels flaky.")]") - if(ishuman(M)) - var/mob/living/carbon/human/H = M - switch(A.stage) - if(3, 4) - if(!(H.hairstyle == "Bald") && !(H.hairstyle == "Balding Hair")) - to_chat(H, "Your hair starts to fall out in clumps...") - addtimer(CALLBACK(src, .proc/Shed, H, FALSE), 50) - if(5) - if(!(H.facial_hairstyle == "Shaved") || !(H.hairstyle == "Bald")) - to_chat(H, "Your hair starts to fall out in clumps...") - addtimer(CALLBACK(src, .proc/Shed, H, TRUE), 50) - -/datum/symptom/shedding/proc/Shed(mob/living/carbon/human/H, fullbald) - if(fullbald) - H.facial_hairstyle = "Shaved" - H.hairstyle = "Bald" - else - H.hairstyle = "Balding Hair" - H.update_hair() +/* +////////////////////////////////////// +Alopecia + + Not Noticeable. + Increases resistance slightly. + Increases stage speed. + Transmittable. + Intense Level. + +BONUS + Makes the mob lose hair. + +////////////////////////////////////// +*/ + +/datum/symptom/shedding + name = "Alopecia" + desc = "The virus causes rapid shedding of head and body hair." + stealth = 0 + resistance = 1 + stage_speed = 2 + transmittable = 2 + level = 4 + severity = 1 + base_message_chance = 50 + symptom_delay_min = 45 + symptom_delay_max = 90 + +/datum/symptom/shedding/Activate(datum/disease/advance/A) + if(!..()) + return + + var/mob/living/M = A.affected_mob + if(prob(base_message_chance)) + to_chat(M, "[pick("Your scalp itches.", "Your skin feels flaky.")]") + if(ishuman(M)) + var/mob/living/carbon/human/H = M + switch(A.stage) + if(3, 4) + if(!(H.hairstyle == "Bald") && !(H.hairstyle == "Balding Hair")) + to_chat(H, "Your hair starts to fall out in clumps...") + addtimer(CALLBACK(src, .proc/Shed, H, FALSE), 50) + if(5) + if(!(H.facial_hairstyle == "Shaved") || !(H.hairstyle == "Bald")) + to_chat(H, "Your hair starts to fall out in clumps...") + addtimer(CALLBACK(src, .proc/Shed, H, TRUE), 50) + +/datum/symptom/shedding/proc/Shed(mob/living/carbon/human/H, fullbald) + if(fullbald) + H.facial_hairstyle = "Shaved" + H.hairstyle = "Bald" + else + H.hairstyle = "Balding Hair" + H.update_hair() diff --git a/code/datums/diseases/advance/symptoms/shivering.dm b/code/datums/diseases/advance/symptoms/shivering.dm index 1f707da8b79..d8216acada1 100644 --- a/code/datums/diseases/advance/symptoms/shivering.dm +++ b/code/datums/diseases/advance/symptoms/shivering.dm @@ -1,75 +1,75 @@ -/* -////////////////////////////////////// - -Shivering - - No change to hidden. - Increases resistance. - Increases stage speed. - Little transmittable. - Low level. - -Bonus - Cools down your body. - -////////////////////////////////////// -*/ - -/datum/symptom/shivering - name = "Shivering" - desc = "The virus inhibits the body's thermoregulation, cooling the body down." - stealth = 0 - resistance = 2 - stage_speed = 3 - transmittable = 2 - level = 2 - severity = 2 - symptom_delay_min = 10 - symptom_delay_max = 30 - var/unsafe = FALSE //over the cold threshold - threshold_descs = list( - "Stage Speed 5" = "Increases cooling speed,; the host can fall below safe temperature levels.", - "Stage Speed 10" = "Further increases cooling speed." - ) - -/datum/symptom/shivering/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 5) //dangerous cold - power = 1.5 - unsafe = TRUE - if(A.properties["stage_rate"] >= 10) - power = 2.5 - set_body_temp(A.affected_mob, A) - -/datum/symptom/shivering/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - if(!unsafe || A.stage < 4) - to_chat(M, "[pick("You feel cold.", "You shiver.")]") - else - to_chat(M, "[pick("You feel your blood run cold.", "You feel ice in your veins.", "You feel like you can't heat up.", "You shiver violently." )]") - -/** - * set_body_temp Sets the body temp change - * - * Sets the body temp change to the mob based on the stage and resistance of the disease - * arguments: - * * mob/living/M The mob to apply changes to - * * datum/disease/advance/A The disease applying the symptom - */ -/datum/symptom/shivering/proc/set_body_temp(mob/living/M, datum/disease/advance/A) - M.add_body_temperature_change("shivering", -((6 * power) * A.stage)) - -/// Update the body temp change based on the new stage -/datum/symptom/shivering/on_stage_change(datum/disease/advance/A) - . = ..() - if(.) - set_body_temp(A.affected_mob, A) - -/// remove the body temp change when removing symptom -/datum/symptom/shivering/End(datum/disease/advance/A) - var/mob/living/carbon/M = A.affected_mob - if(M) - M.remove_body_temperature_change("shivering") +/* +////////////////////////////////////// + +Shivering + + No change to hidden. + Increases resistance. + Increases stage speed. + Little transmittable. + Low level. + +Bonus + Cools down your body. + +////////////////////////////////////// +*/ + +/datum/symptom/shivering + name = "Shivering" + desc = "The virus inhibits the body's thermoregulation, cooling the body down." + stealth = 0 + resistance = 2 + stage_speed = 3 + transmittable = 2 + level = 2 + severity = 2 + symptom_delay_min = 10 + symptom_delay_max = 30 + var/unsafe = FALSE //over the cold threshold + threshold_descs = list( + "Stage Speed 5" = "Increases cooling speed,; the host can fall below safe temperature levels.", + "Stage Speed 10" = "Further increases cooling speed." + ) + +/datum/symptom/shivering/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 5) //dangerous cold + power = 1.5 + unsafe = TRUE + if(A.properties["stage_rate"] >= 10) + power = 2.5 + set_body_temp(A.affected_mob, A) + +/datum/symptom/shivering/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + if(!unsafe || A.stage < 4) + to_chat(M, "[pick("You feel cold.", "You shiver.")]") + else + to_chat(M, "[pick("You feel your blood run cold.", "You feel ice in your veins.", "You feel like you can't heat up.", "You shiver violently." )]") + +/** + * set_body_temp Sets the body temp change + * + * Sets the body temp change to the mob based on the stage and resistance of the disease + * arguments: + * * mob/living/M The mob to apply changes to + * * datum/disease/advance/A The disease applying the symptom + */ +/datum/symptom/shivering/proc/set_body_temp(mob/living/M, datum/disease/advance/A) + M.add_body_temperature_change("shivering", -((6 * power) * A.stage)) + +/// Update the body temp change based on the new stage +/datum/symptom/shivering/on_stage_change(datum/disease/advance/A) + . = ..() + if(.) + set_body_temp(A.affected_mob, A) + +/// remove the body temp change when removing symptom +/datum/symptom/shivering/End(datum/disease/advance/A) + var/mob/living/carbon/M = A.affected_mob + if(M) + M.remove_body_temperature_change("shivering") diff --git a/code/datums/diseases/advance/symptoms/sneeze.dm b/code/datums/diseases/advance/symptoms/sneeze.dm index d6e6d408c56..cc35fdb1ec6 100644 --- a/code/datums/diseases/advance/symptoms/sneeze.dm +++ b/code/datums/diseases/advance/symptoms/sneeze.dm @@ -1,65 +1,65 @@ -/* -////////////////////////////////////// - -Sneezing - - Very Noticable. - Increases resistance. - Doesn't increase stage speed. - Very transmissible. - Low Level. - -Bonus - Forces a spread type of AIRBORNE - with extra range! - -////////////////////////////////////// -*/ - -/datum/symptom/sneeze - name = "Sneezing" - desc = "The virus causes irritation of the nasal cavity, making the host sneeze occasionally. Sneezes from this symptom will spread the virus in a 4 meter cone in front of the host." - stealth = -2 - resistance = 3 - stage_speed = 0 - transmittable = 4 - level = 1 - severity = 1 - symptom_delay_min = 5 - symptom_delay_max = 35 - var/spread_range = 4 - var/cartoon_sneezing = FALSE //ah, ah, AH, AH-CHOO!! - threshold_descs = list( - "Transmission 9" = "Increases sneezing range, spreading the virus over 6 meter cone instead of over a 4 meter cone.", - "Stealth 4" = "The symptom remains hidden until active.", - "Stage Speed 17" = "The force of each sneeze catapults the host backwards, potentially stunning and lightly damaging them if they hit a wall or another person mid-flight." - ) - -/datum/symptom/sneeze/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["transmittable"] >= 9) //longer spread range - spread_range = 6 - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["stage_rate"] >= 17) //Yep, stage speed 17, not stage speed 7. This is a big boy threshold (effect), like the language-scrambling transmission one for the voice change symptom. - cartoon_sneezing = TRUE //for a really fun time, distribute a disease with this threshold met while the gravity generator is down - -/datum/symptom/sneeze/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3) - if(!suppress_warning) - M.emote("sniff") - else - M.emote("sneeze") - if(M.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth - for(var/mob/living/L in oview(spread_range, M)) - if(is_A_facing_B(M, L) && disease_air_spread_walk(get_turf(M), get_turf(L))) - L.AirborneContractDisease(A, TRUE) - if(cartoon_sneezing) //Yeah, this can fling you around even if you have a space suit helmet on. It's, uh, bluespace snot, yeah. - var/sneeze_distance = rand(2,4) //twice as far as a normal baseball bat strike will fling you - var/turf/target = get_ranged_target_turf(M, turn(M.dir, 180), sneeze_distance) - M.throw_at(target, sneeze_distance, rand(1,4)) //with the wounds update, sneezing at 7 speed was causing peoples bones to spontaneously explode, turning cartoonish sneezing into a nightmarishly lethal GBS 2.0 outbreak +/* +////////////////////////////////////// + +Sneezing + + Very Noticable. + Increases resistance. + Doesn't increase stage speed. + Very transmissible. + Low Level. + +Bonus + Forces a spread type of AIRBORNE + with extra range! + +////////////////////////////////////// +*/ + +/datum/symptom/sneeze + name = "Sneezing" + desc = "The virus causes irritation of the nasal cavity, making the host sneeze occasionally. Sneezes from this symptom will spread the virus in a 4 meter cone in front of the host." + stealth = -2 + resistance = 3 + stage_speed = 0 + transmittable = 4 + level = 1 + severity = 1 + symptom_delay_min = 5 + symptom_delay_max = 35 + var/spread_range = 4 + var/cartoon_sneezing = FALSE //ah, ah, AH, AH-CHOO!! + threshold_descs = list( + "Transmission 9" = "Increases sneezing range, spreading the virus over 6 meter cone instead of over a 4 meter cone.", + "Stealth 4" = "The symptom remains hidden until active.", + "Stage Speed 17" = "The force of each sneeze catapults the host backwards, potentially stunning and lightly damaging them if they hit a wall or another person mid-flight." + ) + +/datum/symptom/sneeze/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["transmittable"] >= 9) //longer spread range + spread_range = 6 + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["stage_rate"] >= 17) //Yep, stage speed 17, not stage speed 7. This is a big boy threshold (effect), like the language-scrambling transmission one for the voice change symptom. + cartoon_sneezing = TRUE //for a really fun time, distribute a disease with this threshold met while the gravity generator is down + +/datum/symptom/sneeze/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3) + if(!suppress_warning) + M.emote("sniff") + else + M.emote("sneeze") + if(M.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth + for(var/mob/living/L in oview(spread_range, M)) + if(is_A_facing_B(M, L) && disease_air_spread_walk(get_turf(M), get_turf(L))) + L.AirborneContractDisease(A, TRUE) + if(cartoon_sneezing) //Yeah, this can fling you around even if you have a space suit helmet on. It's, uh, bluespace snot, yeah. + var/sneeze_distance = rand(2,4) //twice as far as a normal baseball bat strike will fling you + var/turf/target = get_ranged_target_turf(M, turn(M.dir, 180), sneeze_distance) + M.throw_at(target, sneeze_distance, rand(1,4)) //with the wounds update, sneezing at 7 speed was causing peoples bones to spontaneously explode, turning cartoonish sneezing into a nightmarishly lethal GBS 2.0 outbreak diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm index ea5a2a35380..da0f922f333 100644 --- a/code/datums/diseases/advance/symptoms/symptoms.dm +++ b/code/datums/diseases/advance/symptoms/symptoms.dm @@ -1,81 +1,81 @@ -// Symptoms are the effects that engineered advanced diseases do. - -/datum/symptom - // Buffs/Debuffs the symptom has to the overall engineered disease. - var/name = "" - var/desc = "If you see this something went very wrong." //Basic symptom description - var/threshold_descs = list() //Descriptions of threshold effects - var/stealth = 0 - var/resistance = 0 - var/stage_speed = 0 - var/transmittable = 0 - // The type level of the symptom. Higher is harder to generate. - var/level = 0 - // The severity level of the symptom. Higher is more dangerous. - var/severity = 0 - // The hash tag for our diseases, we will add it up with our other symptoms to get a unique id! ID MUST BE UNIQUE!!! - var/id = "" - //Base chance of sending warning messages, so it can be modified - var/base_message_chance = 10 - //If the early warnings are suppressed or not - var/suppress_warning = FALSE - //Ticks between each activation - var/next_activation = 0 - var/symptom_delay_min = 1 - var/symptom_delay_max = 1 - //Can be used to multiply virus effects - var/power = 1 - //A neutered symptom has no effect, and only affects statistics. - var/neutered = FALSE - var/list/thresholds - var/naturally_occuring = TRUE //if this symptom can appear from /datum/disease/advance/GenerateSymptoms() - -/datum/symptom/New() - var/list/S = SSdisease.list_symptoms - for(var/i = 1; i <= S.len; i++) - if(type == S[i]) - id = "[i]" - return - CRASH("We couldn't assign an ID!") - -// Called when processing of the advance disease that holds this symptom infects a host and upon each Refresh() of that advance disease. -/datum/symptom/proc/Start(datum/disease/advance/A) - if(neutered) - return FALSE - return TRUE - -// Called when the advance disease is going to be deleted or when the advance disease stops processing. -/datum/symptom/proc/End(datum/disease/advance/A) - if(neutered) - return FALSE - return TRUE - -/datum/symptom/proc/Activate(datum/disease/advance/A) - if(neutered) - return FALSE - if(world.time < next_activation) - return FALSE - else - next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10) - return TRUE - -/datum/symptom/proc/on_stage_change(datum/disease/advance/A) - if(neutered) - return FALSE - return TRUE - -/datum/symptom/proc/Copy() - var/datum/symptom/new_symp = new type - new_symp.name = name - new_symp.id = id - new_symp.neutered = neutered - return new_symp - -/datum/symptom/proc/generate_threshold_desc() - return - -/datum/symptom/proc/OnAdd(datum/disease/advance/A) //Overload when a symptom needs to be active before processing, like changing biotypes. - return - -/datum/symptom/proc/OnRemove(datum/disease/advance/A) //But dont forget to remove them too. - return +// Symptoms are the effects that engineered advanced diseases do. + +/datum/symptom + // Buffs/Debuffs the symptom has to the overall engineered disease. + var/name = "" + var/desc = "If you see this something went very wrong." //Basic symptom description + var/threshold_descs = list() //Descriptions of threshold effects + var/stealth = 0 + var/resistance = 0 + var/stage_speed = 0 + var/transmittable = 0 + // The type level of the symptom. Higher is harder to generate. + var/level = 0 + // The severity level of the symptom. Higher is more dangerous. + var/severity = 0 + // The hash tag for our diseases, we will add it up with our other symptoms to get a unique id! ID MUST BE UNIQUE!!! + var/id = "" + //Base chance of sending warning messages, so it can be modified + var/base_message_chance = 10 + //If the early warnings are suppressed or not + var/suppress_warning = FALSE + //Ticks between each activation + var/next_activation = 0 + var/symptom_delay_min = 1 + var/symptom_delay_max = 1 + //Can be used to multiply virus effects + var/power = 1 + //A neutered symptom has no effect, and only affects statistics. + var/neutered = FALSE + var/list/thresholds + var/naturally_occuring = TRUE //if this symptom can appear from /datum/disease/advance/GenerateSymptoms() + +/datum/symptom/New() + var/list/S = SSdisease.list_symptoms + for(var/i = 1; i <= S.len; i++) + if(type == S[i]) + id = "[i]" + return + CRASH("We couldn't assign an ID!") + +// Called when processing of the advance disease that holds this symptom infects a host and upon each Refresh() of that advance disease. +/datum/symptom/proc/Start(datum/disease/advance/A) + if(neutered) + return FALSE + return TRUE + +// Called when the advance disease is going to be deleted or when the advance disease stops processing. +/datum/symptom/proc/End(datum/disease/advance/A) + if(neutered) + return FALSE + return TRUE + +/datum/symptom/proc/Activate(datum/disease/advance/A) + if(neutered) + return FALSE + if(world.time < next_activation) + return FALSE + else + next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10) + return TRUE + +/datum/symptom/proc/on_stage_change(datum/disease/advance/A) + if(neutered) + return FALSE + return TRUE + +/datum/symptom/proc/Copy() + var/datum/symptom/new_symp = new type + new_symp.name = name + new_symp.id = id + new_symp.neutered = neutered + return new_symp + +/datum/symptom/proc/generate_threshold_desc() + return + +/datum/symptom/proc/OnAdd(datum/disease/advance/A) //Overload when a symptom needs to be active before processing, like changing biotypes. + return + +/datum/symptom/proc/OnRemove(datum/disease/advance/A) //But dont forget to remove them too. + return diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm index 4fd09d05b2a..20c0a6df40d 100644 --- a/code/datums/diseases/advance/symptoms/voice_change.dm +++ b/code/datums/diseases/advance/symptoms/voice_change.dm @@ -1,75 +1,75 @@ -/* -////////////////////////////////////// - -Voice Change - - Noticeable. - Lowers resistance. - Decreases stage speed. - Increased transmittable. - Fatal Level. - -Bonus - Changes the voice of the affected mob. Causing confusion in communication. - -////////////////////////////////////// -*/ - -/datum/symptom/voice_change - - name = "Voice Change" - desc = "The virus alters the pitch and tone of the host's vocal cords, changing how their voice sounds." - stealth = -1 - resistance = -2 - stage_speed = -2 - transmittable = 2 - level = 6 - severity = 2 - base_message_chance = 100 - symptom_delay_min = 60 - symptom_delay_max = 120 - var/scramble_language = FALSE - var/datum/language/current_language - threshold_descs = list( - "Transmission 14" = "The host's language center of the brain is damaged, leading to complete inability to speak or understand any language.", - "Stage Speed 7" = "Changes voice more often.", - "Stealth 3" = "The symptom remains hidden until active." - ) - -/datum/symptom/voice_change/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 3) - suppress_warning = TRUE - if(A.properties["stage_rate"] >= 7) //faster change of voice - base_message_chance = 25 - symptom_delay_min = 25 - symptom_delay_max = 85 - if(A.properties["transmittable"] >= 14) //random language - scramble_language = TRUE - -/datum/symptom/voice_change/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("Your throat hurts.", "You clear your throat.")]") - else - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.SetSpecialVoice(H.dna.species.random_name(H.gender)) - if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure. - current_language = pick(subtypesof(/datum/language) - /datum/language/common) - H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE) - H.grant_language(current_language, TRUE, TRUE, LANGUAGE_VOICECHANGE) - -/datum/symptom/voice_change/End(datum/disease/advance/A) - ..() - if(ishuman(A.affected_mob)) - var/mob/living/carbon/human/H = A.affected_mob - H.UnsetSpecialVoice() - if(scramble_language) - A.affected_mob.remove_blocked_language(subtypesof(/datum/language), LANGUAGE_VOICECHANGE) - A.affected_mob.remove_all_languages(LANGUAGE_VOICECHANGE) // In case someone managed to get more than one anyway. +/* +////////////////////////////////////// + +Voice Change + + Noticeable. + Lowers resistance. + Decreases stage speed. + Increased transmittable. + Fatal Level. + +Bonus + Changes the voice of the affected mob. Causing confusion in communication. + +////////////////////////////////////// +*/ + +/datum/symptom/voice_change + + name = "Voice Change" + desc = "The virus alters the pitch and tone of the host's vocal cords, changing how their voice sounds." + stealth = -1 + resistance = -2 + stage_speed = -2 + transmittable = 2 + level = 6 + severity = 2 + base_message_chance = 100 + symptom_delay_min = 60 + symptom_delay_max = 120 + var/scramble_language = FALSE + var/datum/language/current_language + threshold_descs = list( + "Transmission 14" = "The host's language center of the brain is damaged, leading to complete inability to speak or understand any language.", + "Stage Speed 7" = "Changes voice more often.", + "Stealth 3" = "The symptom remains hidden until active." + ) + +/datum/symptom/voice_change/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 3) + suppress_warning = TRUE + if(A.properties["stage_rate"] >= 7) //faster change of voice + base_message_chance = 25 + symptom_delay_min = 25 + symptom_delay_max = 85 + if(A.properties["transmittable"] >= 14) //random language + scramble_language = TRUE + +/datum/symptom/voice_change/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("Your throat hurts.", "You clear your throat.")]") + else + if(ishuman(M)) + var/mob/living/carbon/human/H = M + H.SetSpecialVoice(H.dna.species.random_name(H.gender)) + if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure. + current_language = pick(subtypesof(/datum/language) - /datum/language/common) + H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE) + H.grant_language(current_language, TRUE, TRUE, LANGUAGE_VOICECHANGE) + +/datum/symptom/voice_change/End(datum/disease/advance/A) + ..() + if(ishuman(A.affected_mob)) + var/mob/living/carbon/human/H = A.affected_mob + H.UnsetSpecialVoice() + if(scramble_language) + A.affected_mob.remove_blocked_language(subtypesof(/datum/language), LANGUAGE_VOICECHANGE) + A.affected_mob.remove_all_languages(LANGUAGE_VOICECHANGE) // In case someone managed to get more than one anyway. diff --git a/code/datums/diseases/advance/symptoms/vomit.dm b/code/datums/diseases/advance/symptoms/vomit.dm index ef734ed794e..38afa60fd8d 100644 --- a/code/datums/diseases/advance/symptoms/vomit.dm +++ b/code/datums/diseases/advance/symptoms/vomit.dm @@ -1,65 +1,65 @@ -/* -////////////////////////////////////// - -Vomiting - - Very Very Noticable. - Decreases resistance. - Doesn't increase stage speed. - Little transmissibility. - Medium Level. - -Bonus - Forces the affected mob to vomit! - Meaning your disease can spread via - people walking on vomit. - Makes the affected mob lose nutrition and - heal toxin damage. - -////////////////////////////////////// -*/ - -/datum/symptom/vomit - - name = "Vomiting" - desc = "The virus causes nausea and irritates the stomach, causing occasional vomit." - stealth = -2 - resistance = -1 - stage_speed = -1 - transmittable = 2 - level = 3 - severity = 3 - base_message_chance = 100 - symptom_delay_min = 25 - symptom_delay_max = 80 - var/vomit_blood = FALSE - var/proj_vomit = 0 - threshold_descs = list( - "Resistance 7" = "Host will vomit blood, causing internal damage.", - "Transmission 7" = "Host will projectile vomit, increasing vomiting range.", - "Stealth 4" = "The symptom remains hidden until active." - ) - -/datum/symptom/vomit/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["resistance"] >= 7) //blood vomit - vomit_blood = TRUE - if(A.properties["transmittable"] >= 7) //projectile vomit - proj_vomit = 5 - -/datum/symptom/vomit/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You feel nauseated.", "You feel like you're going to throw up!")]") - else - vomit(M) - -/datum/symptom/vomit/proc/vomit(mob/living/carbon/M) - M.vomit(20, vomit_blood, distance = proj_vomit) +/* +////////////////////////////////////// + +Vomiting + + Very Very Noticable. + Decreases resistance. + Doesn't increase stage speed. + Little transmissibility. + Medium Level. + +Bonus + Forces the affected mob to vomit! + Meaning your disease can spread via + people walking on vomit. + Makes the affected mob lose nutrition and + heal toxin damage. + +////////////////////////////////////// +*/ + +/datum/symptom/vomit + + name = "Vomiting" + desc = "The virus causes nausea and irritates the stomach, causing occasional vomit." + stealth = -2 + resistance = -1 + stage_speed = -1 + transmittable = 2 + level = 3 + severity = 3 + base_message_chance = 100 + symptom_delay_min = 25 + symptom_delay_max = 80 + var/vomit_blood = FALSE + var/proj_vomit = 0 + threshold_descs = list( + "Resistance 7" = "Host will vomit blood, causing internal damage.", + "Transmission 7" = "Host will projectile vomit, increasing vomiting range.", + "Stealth 4" = "The symptom remains hidden until active." + ) + +/datum/symptom/vomit/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["resistance"] >= 7) //blood vomit + vomit_blood = TRUE + if(A.properties["transmittable"] >= 7) //projectile vomit + proj_vomit = 5 + +/datum/symptom/vomit/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You feel nauseated.", "You feel like you're going to throw up!")]") + else + vomit(M) + +/datum/symptom/vomit/proc/vomit(mob/living/carbon/M) + M.vomit(20, vomit_blood, distance = proj_vomit) diff --git a/code/datums/diseases/advance/symptoms/weight.dm b/code/datums/diseases/advance/symptoms/weight.dm index 665c9bdf99d..4f42a597e2f 100644 --- a/code/datums/diseases/advance/symptoms/weight.dm +++ b/code/datums/diseases/advance/symptoms/weight.dm @@ -1,53 +1,53 @@ -/* -////////////////////////////////////// - -Weight Loss - - Very Very Noticable. - Decreases resistance. - Decreases stage speed. - Reduced Transmittable. - High level. - -Bonus - Decreases the weight of the mob, - forcing it to be skinny. - -////////////////////////////////////// -*/ - -/datum/symptom/weight_loss - - name = "Weight Loss" - desc = "The virus mutates the host's metabolism, making it almost unable to gain nutrition from food." - stealth = -2 - resistance = 2 - stage_speed = -2 - transmittable = -2 - level = 3 - severity = 3 - base_message_chance = 100 - symptom_delay_min = 15 - symptom_delay_max = 45 - threshold_descs = list( - "Stealth 4" = "The symptom is less noticeable." - ) - -/datum/symptom/weight_loss/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) //warn less often - base_message_chance = 25 - -/datum/symptom/weight_loss/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance)) - to_chat(M, "[pick("You feel hungry.", "You crave for food.")]") - else - to_chat(M, "[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]") - M.overeatduration = max(M.overeatduration - 100, 0) - M.adjust_nutrition(-100) +/* +////////////////////////////////////// + +Weight Loss + + Very Very Noticable. + Decreases resistance. + Decreases stage speed. + Reduced Transmittable. + High level. + +Bonus + Decreases the weight of the mob, + forcing it to be skinny. + +////////////////////////////////////// +*/ + +/datum/symptom/weight_loss + + name = "Weight Loss" + desc = "The virus mutates the host's metabolism, making it almost unable to gain nutrition from food." + stealth = -2 + resistance = 2 + stage_speed = -2 + transmittable = -2 + level = 3 + severity = 3 + base_message_chance = 100 + symptom_delay_min = 15 + symptom_delay_max = 45 + threshold_descs = list( + "Stealth 4" = "The symptom is less noticeable." + ) + +/datum/symptom/weight_loss/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) //warn less often + base_message_chance = 25 + +/datum/symptom/weight_loss/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance)) + to_chat(M, "[pick("You feel hungry.", "You crave for food.")]") + else + to_chat(M, "[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]") + M.overeatduration = max(M.overeatduration - 100, 0) + M.adjust_nutrition(-100) diff --git a/code/datums/diseases/advance/symptoms/youth.dm b/code/datums/diseases/advance/symptoms/youth.dm index e42e162cf91..a9f52619057 100644 --- a/code/datums/diseases/advance/symptoms/youth.dm +++ b/code/datums/diseases/advance/symptoms/youth.dm @@ -1,58 +1,58 @@ -/* -////////////////////////////////////// -Eternal Youth - - Moderate stealth boost. - Increases resistance tremendously. - Increases stage speed tremendously. - Reduces transmission tremendously. - Critical Level. - -BONUS - Gives you immortality and eternal youth!!! - Can be used to buff your virus - -////////////////////////////////////// -*/ - -/datum/symptom/youth - - name = "Eternal Youth" - desc = "The virus becomes symbiotically connected to the cells in the host's body, preventing and reversing aging. \ - The virus, in turn, becomes more resistant, spreads faster, and is harder to spot, although it doesn't thrive as well without a host." - stealth = 3 - resistance = 4 - stage_speed = 4 - transmittable = -4 - level = 5 - base_message_chance = 100 - symptom_delay_min = 25 - symptom_delay_max = 50 - -/datum/symptom/youth/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(ishuman(M)) - var/mob/living/carbon/human/H = M - switch(A.stage) - if(1) - if(H.age > 41) - H.age = 41 - to_chat(H, "You haven't had this much energy in years!") - if(2) - if(H.age > 36) - H.age = 36 - to_chat(H, "You're suddenly in a good mood.") - if(3) - if(H.age > 31) - H.age = 31 - to_chat(H, "You begin to feel more lithe.") - if(4) - if(H.age > 26) - H.age = 26 - to_chat(H, "You feel reinvigorated.") - if(5) - if(H.age > 21) - H.age = 21 - to_chat(H, "You feel like you can take on the world!") +/* +////////////////////////////////////// +Eternal Youth + + Moderate stealth boost. + Increases resistance tremendously. + Increases stage speed tremendously. + Reduces transmission tremendously. + Critical Level. + +BONUS + Gives you immortality and eternal youth!!! + Can be used to buff your virus + +////////////////////////////////////// +*/ + +/datum/symptom/youth + + name = "Eternal Youth" + desc = "The virus becomes symbiotically connected to the cells in the host's body, preventing and reversing aging. \ + The virus, in turn, becomes more resistant, spreads faster, and is harder to spot, although it doesn't thrive as well without a host." + stealth = 3 + resistance = 4 + stage_speed = 4 + transmittable = -4 + level = 5 + base_message_chance = 100 + symptom_delay_min = 25 + symptom_delay_max = 50 + +/datum/symptom/youth/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(ishuman(M)) + var/mob/living/carbon/human/H = M + switch(A.stage) + if(1) + if(H.age > 41) + H.age = 41 + to_chat(H, "You haven't had this much energy in years!") + if(2) + if(H.age > 36) + H.age = 36 + to_chat(H, "You're suddenly in a good mood.") + if(3) + if(H.age > 31) + H.age = 31 + to_chat(H, "You begin to feel more lithe.") + if(4) + if(H.age > 26) + H.age = 26 + to_chat(H, "You feel reinvigorated.") + if(5) + if(H.age > 21) + H.age = 21 + to_chat(H, "You feel like you can take on the world!") diff --git a/code/datums/diseases/appendicitis.dm b/code/datums/diseases/appendicitis.dm index be7e6ceecdb..7a6ea142b36 100644 --- a/code/datums/diseases/appendicitis.dm +++ b/code/datums/diseases/appendicitis.dm @@ -1,36 +1,36 @@ -/datum/disease/appendicitis - form = "Condition" - name = "Appendicitis" - max_stages = 3 - cure_text = "Surgery" - agent = "Shitty Appendix" - viable_mobtypes = list(/mob/living/carbon/human) - permeability_mod = 1 - desc = "If left untreated the subject will become very weak, and may vomit often." - severity = DISEASE_SEVERITY_MEDIUM - disease_flags = CAN_CARRY|CAN_RESIST - spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS - visibility_flags = HIDDEN_PANDEMIC - required_organs = list(/obj/item/organ/appendix) - bypasses_immunity = TRUE // Immunity is based on not having an appendix; this isn't a virus - -/datum/disease/appendicitis/stage_act() - ..() - switch(stage) - if(1) - if(prob(5)) - affected_mob.emote("cough") - if(2) - var/obj/item/organ/appendix/A = affected_mob.getorgan(/obj/item/organ/appendix) - if(A) - A.inflamed = 1 - A.update_icon() - if(prob(3)) - to_chat(affected_mob, "You feel a stabbing pain in your abdomen!") - affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 5) - affected_mob.Stun(rand(40,60)) - affected_mob.adjustToxLoss(1) - if(3) - if(prob(1)) - affected_mob.vomit(95) - affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 15) +/datum/disease/appendicitis + form = "Condition" + name = "Appendicitis" + max_stages = 3 + cure_text = "Surgery" + agent = "Shitty Appendix" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 1 + desc = "If left untreated the subject will become very weak, and may vomit often." + severity = DISEASE_SEVERITY_MEDIUM + disease_flags = CAN_CARRY|CAN_RESIST + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + visibility_flags = HIDDEN_PANDEMIC + required_organs = list(/obj/item/organ/appendix) + bypasses_immunity = TRUE // Immunity is based on not having an appendix; this isn't a virus + +/datum/disease/appendicitis/stage_act() + ..() + switch(stage) + if(1) + if(prob(5)) + affected_mob.emote("cough") + if(2) + var/obj/item/organ/appendix/A = affected_mob.getorgan(/obj/item/organ/appendix) + if(A) + A.inflamed = 1 + A.update_icon() + if(prob(3)) + to_chat(affected_mob, "You feel a stabbing pain in your abdomen!") + affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 5) + affected_mob.Stun(rand(40,60)) + affected_mob.adjustToxLoss(1) + if(3) + if(prob(1)) + affected_mob.vomit(95) + affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 15) diff --git a/code/datums/diseases/beesease.dm b/code/datums/diseases/beesease.dm index a31939d79dc..53230711d9a 100644 --- a/code/datums/diseases/beesease.dm +++ b/code/datums/diseases/beesease.dm @@ -1,39 +1,39 @@ -/datum/disease/beesease - name = "Beesease" - form = "Infection" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Sugar" - cures = list(/datum/reagent/consumable/sugar) - agent = "Apidae Infection" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - desc = "If left untreated subject will regurgitate bees." - severity = DISEASE_SEVERITY_MEDIUM - infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD //bees nesting in corpses - -/datum/disease/beesease/stage_act() - ..() - switch(stage) - if(2) //also changes say, see say.dm - if(prob(2)) - to_chat(affected_mob, "You taste honey in your mouth.") - if(3) - if(prob(10)) - to_chat(affected_mob, "Your stomach rumbles.") - if(prob(2)) - to_chat(affected_mob, "Your stomach stings painfully.") - if(prob(20)) - affected_mob.adjustToxLoss(2) - affected_mob.updatehealth() - if(4) - if(prob(10)) - affected_mob.visible_message("[affected_mob] buzzes.", \ - "Your stomach buzzes violently!") - if(prob(5)) - to_chat(affected_mob, "You feel something moving in your throat.") - if(prob(1)) - affected_mob.visible_message("[affected_mob] coughs up a swarm of bees!", \ - "You cough up a swarm of bees!") - new /mob/living/simple_animal/hostile/poison/bees(affected_mob.loc) - return +/datum/disease/beesease + name = "Beesease" + form = "Infection" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Sugar" + cures = list(/datum/reagent/consumable/sugar) + agent = "Apidae Infection" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + desc = "If left untreated subject will regurgitate bees." + severity = DISEASE_SEVERITY_MEDIUM + infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD //bees nesting in corpses + +/datum/disease/beesease/stage_act() + ..() + switch(stage) + if(2) //also changes say, see say.dm + if(prob(2)) + to_chat(affected_mob, "You taste honey in your mouth.") + if(3) + if(prob(10)) + to_chat(affected_mob, "Your stomach rumbles.") + if(prob(2)) + to_chat(affected_mob, "Your stomach stings painfully.") + if(prob(20)) + affected_mob.adjustToxLoss(2) + affected_mob.updatehealth() + if(4) + if(prob(10)) + affected_mob.visible_message("[affected_mob] buzzes.", \ + "Your stomach buzzes violently!") + if(prob(5)) + to_chat(affected_mob, "You feel something moving in your throat.") + if(prob(1)) + affected_mob.visible_message("[affected_mob] coughs up a swarm of bees!", \ + "You cough up a swarm of bees!") + new /mob/living/simple_animal/hostile/poison/bees(affected_mob.loc) + return diff --git a/code/datums/diseases/brainrot.dm b/code/datums/diseases/brainrot.dm index fc3fb732259..0bcd1e30ead 100644 --- a/code/datums/diseases/brainrot.dm +++ b/code/datums/diseases/brainrot.dm @@ -1,60 +1,60 @@ -/datum/disease/brainrot - name = "Brainrot" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Mannitol" - cures = list(/datum/reagent/medicine/mannitol) - agent = "Cryptococcus Cosmosis" - viable_mobtypes = list(/mob/living/carbon/human) - cure_chance = 15//higher chance to cure, since two reagents are required - desc = "This disease destroys the braincells, causing brain fever, brain necrosis and general intoxication." - required_organs = list(/obj/item/organ/brain) - severity = DISEASE_SEVERITY_HARMFUL - -/datum/disease/brainrot/stage_act() //Removed toxloss because damaging diseases are pretty horrible. Last round it killed the entire station because the cure didn't work -- Urist -ACTUALLY Removed rather than commented out, I don't see it returning - RR - ..() - - switch(stage) - if(2) - if(prob(2)) - affected_mob.emote("blink") - if(prob(2)) - affected_mob.emote("yawn") - if(prob(2)) - to_chat(affected_mob, "You don't feel like yourself.") - if(prob(5)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170) - affected_mob.updatehealth() - if(3) - if(prob(2)) - affected_mob.emote("stare") - if(prob(2)) - affected_mob.emote("drool") - if(prob(10)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170) - affected_mob.updatehealth() - if(prob(2)) - to_chat(affected_mob, "Your try to remember something important...but can't.") - - if(4) - if(prob(2)) - affected_mob.emote("stare") - if(prob(2)) - affected_mob.emote("drool") - if(prob(15)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 170) - affected_mob.updatehealth() - if(prob(2)) - to_chat(affected_mob, "Strange buzzing fills your head, removing all thoughts.") - if(prob(3)) - to_chat(affected_mob, "You lose consciousness...") - affected_mob.visible_message("[affected_mob] suddenly collapses!", \ - "You suddenly collapse!") - affected_mob.Unconscious(rand(100,200)) - if(prob(1)) - affected_mob.emote("snore") - if(prob(15)) - affected_mob.stuttering += 3 - - return +/datum/disease/brainrot + name = "Brainrot" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Mannitol" + cures = list(/datum/reagent/medicine/mannitol) + agent = "Cryptococcus Cosmosis" + viable_mobtypes = list(/mob/living/carbon/human) + cure_chance = 15//higher chance to cure, since two reagents are required + desc = "This disease destroys the braincells, causing brain fever, brain necrosis and general intoxication." + required_organs = list(/obj/item/organ/brain) + severity = DISEASE_SEVERITY_HARMFUL + +/datum/disease/brainrot/stage_act() //Removed toxloss because damaging diseases are pretty horrible. Last round it killed the entire station because the cure didn't work -- Urist -ACTUALLY Removed rather than commented out, I don't see it returning - RR + ..() + + switch(stage) + if(2) + if(prob(2)) + affected_mob.emote("blink") + if(prob(2)) + affected_mob.emote("yawn") + if(prob(2)) + to_chat(affected_mob, "You don't feel like yourself.") + if(prob(5)) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170) + affected_mob.updatehealth() + if(3) + if(prob(2)) + affected_mob.emote("stare") + if(prob(2)) + affected_mob.emote("drool") + if(prob(10)) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170) + affected_mob.updatehealth() + if(prob(2)) + to_chat(affected_mob, "Your try to remember something important...but can't.") + + if(4) + if(prob(2)) + affected_mob.emote("stare") + if(prob(2)) + affected_mob.emote("drool") + if(prob(15)) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 170) + affected_mob.updatehealth() + if(prob(2)) + to_chat(affected_mob, "Strange buzzing fills your head, removing all thoughts.") + if(prob(3)) + to_chat(affected_mob, "You lose consciousness...") + affected_mob.visible_message("[affected_mob] suddenly collapses!", \ + "You suddenly collapse!") + affected_mob.Unconscious(rand(100,200)) + if(prob(1)) + affected_mob.emote("snore") + if(prob(15)) + affected_mob.stuttering += 3 + + return diff --git a/code/datums/diseases/cold.dm b/code/datums/diseases/cold.dm index 386ca3e2449..6f7d1a415e5 100644 --- a/code/datums/diseases/cold.dm +++ b/code/datums/diseases/cold.dm @@ -1,53 +1,53 @@ -/datum/disease/cold - name = "The Cold" - max_stages = 3 - cure_text = "Rest & Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - agent = "XY-rhinovirus" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - permeability_mod = 0.5 - desc = "If left untreated the subject will contract the flu." - severity = DISEASE_SEVERITY_NONTHREAT - -/datum/disease/cold/stage_act() - ..() - switch(stage) - if(2) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(40)) //changed FROM prob(10) until sleeping is fixed - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1) && prob(5)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(1)) - to_chat(affected_mob, "Mucous runs down the back of your throat.") - if(3) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(25)) //changed FROM prob(5) until sleeping is fixed - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1) && prob(1)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(1)) - to_chat(affected_mob, "Mucous runs down the back of your throat.") - if(prob(1) && prob(50)) - if(!affected_mob.disease_resistances.Find(/datum/disease/flu)) - var/datum/disease/Flu = new /datum/disease/flu() - affected_mob.ForceContractDisease(Flu, FALSE, TRUE) - cure() +/datum/disease/cold + name = "The Cold" + max_stages = 3 + cure_text = "Rest & Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + agent = "XY-rhinovirus" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + permeability_mod = 0.5 + desc = "If left untreated the subject will contract the flu." + severity = DISEASE_SEVERITY_NONTHREAT + +/datum/disease/cold/stage_act() + ..() + switch(stage) + if(2) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(40)) //changed FROM prob(10) until sleeping is fixed + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1) && prob(5)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(1)) + to_chat(affected_mob, "Mucous runs down the back of your throat.") + if(3) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(25)) //changed FROM prob(5) until sleeping is fixed + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1) && prob(1)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(1)) + to_chat(affected_mob, "Mucous runs down the back of your throat.") + if(prob(1) && prob(50)) + if(!affected_mob.disease_resistances.Find(/datum/disease/flu)) + var/datum/disease/Flu = new /datum/disease/flu() + affected_mob.ForceContractDisease(Flu, FALSE, TRUE) + cure() diff --git a/code/datums/diseases/cold9.dm b/code/datums/diseases/cold9.dm index 3d710169b82..58ed52e8b67 100644 --- a/code/datums/diseases/cold9.dm +++ b/code/datums/diseases/cold9.dm @@ -1,39 +1,39 @@ -/datum/disease/cold9 - name = "The Cold" - max_stages = 3 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Common Cold Anti-bodies & Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - agent = "ICE9-rhinovirus" - viable_mobtypes = list(/mob/living/carbon/human) - desc = "If left untreated the subject will slow, as if partly frozen." - severity = DISEASE_SEVERITY_HARMFUL - -/datum/disease/cold9/stage_act() - ..() - switch(stage) - if(2) - affected_mob.adjust_bodytemperature(-10) - if(prob(1) && prob(10)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(5)) - to_chat(affected_mob, "You feel stiff.") - if(3) - affected_mob.adjust_bodytemperature(-20) - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(10)) - to_chat(affected_mob, "You feel stiff.") +/datum/disease/cold9 + name = "The Cold" + max_stages = 3 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Common Cold Anti-bodies & Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + agent = "ICE9-rhinovirus" + viable_mobtypes = list(/mob/living/carbon/human) + desc = "If left untreated the subject will slow, as if partly frozen." + severity = DISEASE_SEVERITY_HARMFUL + +/datum/disease/cold9/stage_act() + ..() + switch(stage) + if(2) + affected_mob.adjust_bodytemperature(-10) + if(prob(1) && prob(10)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(5)) + to_chat(affected_mob, "You feel stiff.") + if(3) + affected_mob.adjust_bodytemperature(-20) + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(10)) + to_chat(affected_mob, "You feel stiff.") diff --git a/code/datums/diseases/dna_spread.dm b/code/datums/diseases/dna_spread.dm index 972a7f4e187..3a67230d36e 100644 --- a/code/datums/diseases/dna_spread.dm +++ b/code/datums/diseases/dna_spread.dm @@ -1,74 +1,74 @@ -/datum/disease/dnaspread - name = "Space Retrovirus" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Mutadone" - cures = list(/datum/reagent/medicine/mutadone) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - agent = "S4E1 retrovirus" - viable_mobtypes = list(/mob/living/carbon/human) - var/datum/dna/original_dna = null - var/transformed = 0 - desc = "This disease transplants the genetic code of the initial vector into new hosts." - severity = DISEASE_SEVERITY_MEDIUM - - -/datum/disease/dnaspread/stage_act() - ..() - if(!affected_mob.dna) - cure() - if((NOTRANSSTING in affected_mob.dna.species.species_traits) || (NO_DNA_COPY in affected_mob.dna.species.species_traits)) //Only species that can be spread by transformation sting can be spread by the retrovirus - cure() - - if(!strain_data["dna"]) - //Absorbs the target DNA. - strain_data["dna"] = new affected_mob.dna.type - affected_mob.dna.copy_dna(strain_data["dna"]) - carrier = TRUE - stage = 4 - return - - switch(stage) - if(2 || 3) //Pretend to be a cold and give time to spread. - if(prob(8)) - affected_mob.emote("sneeze") - if(prob(8)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(2) - affected_mob.updatehealth() - if(4) - if(!transformed && !carrier) - //Save original dna for when the disease is cured. - original_dna = new affected_mob.dna.type - affected_mob.dna.copy_dna(original_dna) - - to_chat(affected_mob, "You don't feel like yourself..") - var/datum/dna/transform_dna = strain_data["dna"] - - transform_dna.transfer_identity(affected_mob, transfer_SE = 1) - affected_mob.real_name = affected_mob.dna.real_name - affected_mob.updateappearance(mutcolor_update=1) - affected_mob.domutcheck() - - transformed = 1 - carrier = 1 //Just chill out at stage 4 - - return - -/datum/disease/dnaspread/Destroy() - if (original_dna && transformed && affected_mob) - original_dna.transfer_identity(affected_mob, transfer_SE = 1) - affected_mob.real_name = affected_mob.dna.real_name - affected_mob.updateappearance(mutcolor_update=1) - affected_mob.domutcheck() - - to_chat(affected_mob, "You feel more like yourself.") - return ..() +/datum/disease/dnaspread + name = "Space Retrovirus" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Mutadone" + cures = list(/datum/reagent/medicine/mutadone) + disease_flags = CAN_CARRY|CAN_RESIST|CURABLE + agent = "S4E1 retrovirus" + viable_mobtypes = list(/mob/living/carbon/human) + var/datum/dna/original_dna = null + var/transformed = 0 + desc = "This disease transplants the genetic code of the initial vector into new hosts." + severity = DISEASE_SEVERITY_MEDIUM + + +/datum/disease/dnaspread/stage_act() + ..() + if(!affected_mob.dna) + cure() + if((NOTRANSSTING in affected_mob.dna.species.species_traits) || (NO_DNA_COPY in affected_mob.dna.species.species_traits)) //Only species that can be spread by transformation sting can be spread by the retrovirus + cure() + + if(!strain_data["dna"]) + //Absorbs the target DNA. + strain_data["dna"] = new affected_mob.dna.type + affected_mob.dna.copy_dna(strain_data["dna"]) + carrier = TRUE + stage = 4 + return + + switch(stage) + if(2 || 3) //Pretend to be a cold and give time to spread. + if(prob(8)) + affected_mob.emote("sneeze") + if(prob(8)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(2) + affected_mob.updatehealth() + if(4) + if(!transformed && !carrier) + //Save original dna for when the disease is cured. + original_dna = new affected_mob.dna.type + affected_mob.dna.copy_dna(original_dna) + + to_chat(affected_mob, "You don't feel like yourself..") + var/datum/dna/transform_dna = strain_data["dna"] + + transform_dna.transfer_identity(affected_mob, transfer_SE = 1) + affected_mob.real_name = affected_mob.dna.real_name + affected_mob.updateappearance(mutcolor_update=1) + affected_mob.domutcheck() + + transformed = 1 + carrier = 1 //Just chill out at stage 4 + + return + +/datum/disease/dnaspread/Destroy() + if (original_dna && transformed && affected_mob) + original_dna.transfer_identity(affected_mob, transfer_SE = 1) + affected_mob.real_name = affected_mob.dna.real_name + affected_mob.updateappearance(mutcolor_update=1) + affected_mob.domutcheck() + + to_chat(affected_mob, "You feel more like yourself.") + return ..() diff --git a/code/datums/diseases/fake_gbs.dm b/code/datums/diseases/fake_gbs.dm index 70bcc67d210..37628a5502f 100644 --- a/code/datums/diseases/fake_gbs.dm +++ b/code/datums/diseases/fake_gbs.dm @@ -1,32 +1,32 @@ -/datum/disease/fake_gbs - name = "GBS" - max_stages = 5 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Synaptizine & Sulfur" - cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) - agent = "Gravitokinetic Bipotential SADS-" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - desc = "If left untreated death will occur." - severity = DISEASE_SEVERITY_BIOHAZARD - -/datum/disease/fake_gbs/stage_act() - ..() - switch(stage) - if(2) - if(prob(1)) - affected_mob.emote("sneeze") - if(3) - if(prob(5)) - affected_mob.emote("cough") - else if(prob(5)) - affected_mob.emote("gasp") - if(prob(10)) - to_chat(affected_mob, "You're starting to feel very weak...") - if(4) - if(prob(10)) - affected_mob.emote("cough") - - if(5) - if(prob(10)) - affected_mob.emote("cough") +/datum/disease/fake_gbs + name = "GBS" + max_stages = 5 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Synaptizine & Sulfur" + cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) + agent = "Gravitokinetic Bipotential SADS-" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + desc = "If left untreated death will occur." + severity = DISEASE_SEVERITY_BIOHAZARD + +/datum/disease/fake_gbs/stage_act() + ..() + switch(stage) + if(2) + if(prob(1)) + affected_mob.emote("sneeze") + if(3) + if(prob(5)) + affected_mob.emote("cough") + else if(prob(5)) + affected_mob.emote("gasp") + if(prob(10)) + to_chat(affected_mob, "You're starting to feel very weak...") + if(4) + if(prob(10)) + affected_mob.emote("cough") + + if(5) + if(prob(10)) + affected_mob.emote("cough") diff --git a/code/datums/diseases/flu.dm b/code/datums/diseases/flu.dm index 916e56373fa..62bb3de8df4 100644 --- a/code/datums/diseases/flu.dm +++ b/code/datums/diseases/flu.dm @@ -1,54 +1,54 @@ -/datum/disease/flu - name = "The Flu" - max_stages = 3 - spread_text = "Airborne" - cure_text = "Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - cure_chance = 10 - agent = "H13N1 flu virion" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - permeability_mod = 0.75 - desc = "If left untreated the subject will feel quite unwell." - severity = DISEASE_SEVERITY_MINOR - -/datum/disease/flu/stage_act() - ..() - switch(stage) - if(2) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - stage-- - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(1) - affected_mob.updatehealth() - - if(3) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(15)) - to_chat(affected_mob, "You feel better.") - stage-- - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(1) - affected_mob.updatehealth() - return +/datum/disease/flu + name = "The Flu" + max_stages = 3 + spread_text = "Airborne" + cure_text = "Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + cure_chance = 10 + agent = "H13N1 flu virion" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + permeability_mod = 0.75 + desc = "If left untreated the subject will feel quite unwell." + severity = DISEASE_SEVERITY_MINOR + +/datum/disease/flu/stage_act() + ..() + switch(stage) + if(2) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + stage-- + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(1) + affected_mob.updatehealth() + + if(3) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(15)) + to_chat(affected_mob, "You feel better.") + stage-- + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(1) + affected_mob.updatehealth() + return diff --git a/code/datums/diseases/fluspanish.dm b/code/datums/diseases/fluspanish.dm index 1557ddfbd8d..3297877fe91 100644 --- a/code/datums/diseases/fluspanish.dm +++ b/code/datums/diseases/fluspanish.dm @@ -1,36 +1,36 @@ -/datum/disease/fluspanish - name = "Spanish inquisition Flu" - max_stages = 3 - spread_text = "Airborne" - cure_text = "Spaceacillin & Anti-bodies to the common flu" - cures = list(/datum/reagent/medicine/spaceacillin) - cure_chance = 10 - agent = "1nqu1s1t10n flu virion" - viable_mobtypes = list(/mob/living/carbon/human) - permeability_mod = 0.75 - desc = "If left untreated the subject will burn to death for being a heretic." - severity = DISEASE_SEVERITY_DANGEROUS - -/datum/disease/fluspanish/stage_act() - ..() - switch(stage) - if(2) - affected_mob.adjust_bodytemperature(10) - if(prob(5)) - affected_mob.emote("sneeze") - if(prob(5)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "You're burning in your own skin!") - affected_mob.take_bodypart_damage(0,5) - - if(3) - affected_mob.adjust_bodytemperature(20) - if(prob(5)) - affected_mob.emote("sneeze") - if(prob(5)) - affected_mob.emote("cough") - if(prob(5)) - to_chat(affected_mob, "You're burning in your own skin!") - affected_mob.take_bodypart_damage(0,5) - return +/datum/disease/fluspanish + name = "Spanish inquisition Flu" + max_stages = 3 + spread_text = "Airborne" + cure_text = "Spaceacillin & Anti-bodies to the common flu" + cures = list(/datum/reagent/medicine/spaceacillin) + cure_chance = 10 + agent = "1nqu1s1t10n flu virion" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 0.75 + desc = "If left untreated the subject will burn to death for being a heretic." + severity = DISEASE_SEVERITY_DANGEROUS + +/datum/disease/fluspanish/stage_act() + ..() + switch(stage) + if(2) + affected_mob.adjust_bodytemperature(10) + if(prob(5)) + affected_mob.emote("sneeze") + if(prob(5)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "You're burning in your own skin!") + affected_mob.take_bodypart_damage(0,5) + + if(3) + affected_mob.adjust_bodytemperature(20) + if(prob(5)) + affected_mob.emote("sneeze") + if(prob(5)) + affected_mob.emote("cough") + if(prob(5)) + to_chat(affected_mob, "You're burning in your own skin!") + affected_mob.take_bodypart_damage(0,5) + return diff --git a/code/datums/diseases/gbs.dm b/code/datums/diseases/gbs.dm index 8a6eab6048f..8ac19968557 100644 --- a/code/datums/diseases/gbs.dm +++ b/code/datums/diseases/gbs.dm @@ -1,31 +1,31 @@ -/datum/disease/gbs - name = "GBS" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Synaptizine & Sulfur" - cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) - cure_chance = 15//higher chance to cure, since two reagents are required - agent = "Gravitokinetic Bipotential SADS+" - viable_mobtypes = list(/mob/living/carbon/human) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - permeability_mod = 1 - severity = DISEASE_SEVERITY_BIOHAZARD - -/datum/disease/gbs/stage_act() - ..() - switch(stage) - if(2) - if(prob(5)) - affected_mob.emote("cough") - if(3) - if(prob(5)) - affected_mob.emote("gasp") - if(prob(10)) - to_chat(affected_mob, "Your body hurts all over!") - if(4) - to_chat(affected_mob, "Your body feels as if it's trying to rip itself apart!") - if(prob(50)) - affected_mob.gib() - else - return +/datum/disease/gbs + name = "GBS" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Synaptizine & Sulfur" + cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) + cure_chance = 15//higher chance to cure, since two reagents are required + agent = "Gravitokinetic Bipotential SADS+" + viable_mobtypes = list(/mob/living/carbon/human) + disease_flags = CAN_CARRY|CAN_RESIST|CURABLE + permeability_mod = 1 + severity = DISEASE_SEVERITY_BIOHAZARD + +/datum/disease/gbs/stage_act() + ..() + switch(stage) + if(2) + if(prob(5)) + affected_mob.emote("cough") + if(3) + if(prob(5)) + affected_mob.emote("gasp") + if(prob(10)) + to_chat(affected_mob, "Your body hurts all over!") + if(4) + to_chat(affected_mob, "Your body feels as if it's trying to rip itself apart!") + if(prob(50)) + affected_mob.gib() + else + return diff --git a/code/datums/diseases/magnitis.dm b/code/datums/diseases/magnitis.dm index 3a2e73f9691..a355a4bc019 100644 --- a/code/datums/diseases/magnitis.dm +++ b/code/datums/diseases/magnitis.dm @@ -1,68 +1,68 @@ -/datum/disease/magnitis - name = "Magnitis" - max_stages = 4 - spread_text = "Airborne" - cure_text = "Iron" - cures = list(/datum/reagent/iron) - agent = "Fukkos Miracos" - viable_mobtypes = list(/mob/living/carbon/human) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - permeability_mod = 0.75 - desc = "This disease disrupts the magnetic field of your body, making it act as if a powerful magnet. Injections of iron help stabilize the field." - severity = DISEASE_SEVERITY_MEDIUM - infectable_biotypes = MOB_ORGANIC|MOB_ROBOTIC - process_dead = TRUE - -/datum/disease/magnitis/stage_act() - ..() - switch(stage) - if(2) - if(prob(2)) - to_chat(affected_mob, "You feel a slight shock course through your body.") - if(prob(2)) - for(var/obj/M in orange(2,affected_mob)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - step_towards(M,affected_mob) - for(var/mob/living/silicon/S in orange(2,affected_mob)) - if(isAI(S)) - continue - step_towards(S,affected_mob) - if(3) - if(prob(2)) - to_chat(affected_mob, "You feel a strong shock course through your body.") - if(prob(2)) - to_chat(affected_mob, "You feel like clowning around.") - if(prob(4)) - for(var/obj/M in orange(4,affected_mob)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - var/i - var/iter = rand(1,2) - for(i=0,iYou feel a powerful shock course through your body.") - if(prob(2)) - to_chat(affected_mob, "You query upon the nature of miracles.") - if(prob(8)) - for(var/obj/M in orange(6,affected_mob)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - var/i - var/iter = rand(1,3) - for(i=0,iYou feel a slight shock course through your body.") + if(prob(2)) + for(var/obj/M in orange(2,affected_mob)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + step_towards(M,affected_mob) + for(var/mob/living/silicon/S in orange(2,affected_mob)) + if(isAI(S)) + continue + step_towards(S,affected_mob) + if(3) + if(prob(2)) + to_chat(affected_mob, "You feel a strong shock course through your body.") + if(prob(2)) + to_chat(affected_mob, "You feel like clowning around.") + if(prob(4)) + for(var/obj/M in orange(4,affected_mob)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + var/i + var/iter = rand(1,2) + for(i=0,iYou feel a powerful shock course through your body.") + if(prob(2)) + to_chat(affected_mob, "You query upon the nature of miracles.") + if(prob(8)) + for(var/obj/M in orange(6,affected_mob)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + var/i + var/iter = rand(1,3) + for(i=0,iYou feel a little silly.") - if(2) - if(prob(10)) - to_chat(affected_mob, "You start seeing rainbows.") - if(3) - if(prob(10)) - to_chat(affected_mob, "Your thoughts are interrupted by a loud HONK!") - if(4) - if(prob(5)) - affected_mob.say( pick( list("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk...") ) , forced = "pierrot's throat") - -/datum/disease/pierrot_throat/after_add() - RegisterSignal(affected_mob, COMSIG_MOB_SAY, .proc/handle_speech) - - -/datum/disease/pierrot_throat/proc/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - var/list/split_message = splittext(message, " ") //List each word in the message - var/applied = 0 - for (var/i in 1 to length(split_message)) - if(prob(3 * stage)) //Stage 1: 3% Stage 2: 6% Stage 3: 9% Stage 4: 12% - if(findtext(split_message[i], "*") || findtext(split_message[i], ";") || findtext(split_message[i], ":")) - continue - split_message[i] = "HONK" - if (applied++ > stage) - break - if (applied) - speech_args[SPEECH_SPANS] |= SPAN_CLOWN // a little bonus - message = jointext(split_message, " ") - speech_args[SPEECH_MESSAGE] = message - - -/datum/disease/pierrot_throat/Destroy() - UnregisterSignal(affected_mob, COMSIG_MOB_SAY) - return ..() - -/datum/disease/pierrot_throat/remove_disease() - UnregisterSignal(affected_mob, COMSIG_MOB_SAY) - return ..() +/datum/disease/pierrot_throat + name = "Pierrot's Throat" + max_stages = 4 + spread_text = "Airborne" + cure_text = "Banana products, especially banana bread." + cures = list(/datum/reagent/consumable/banana) + cure_chance = 75 + agent = "H0NI<42 Virus" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 0.75 + desc = "If left untreated the subject will probably drive others to insanity." + severity = DISEASE_SEVERITY_MEDIUM + +/datum/disease/pierrot_throat/stage_act() + ..() + switch(stage) + if(1) + if(prob(10)) + to_chat(affected_mob, "You feel a little silly.") + if(2) + if(prob(10)) + to_chat(affected_mob, "You start seeing rainbows.") + if(3) + if(prob(10)) + to_chat(affected_mob, "Your thoughts are interrupted by a loud HONK!") + if(4) + if(prob(5)) + affected_mob.say( pick( list("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk...") ) , forced = "pierrot's throat") + +/datum/disease/pierrot_throat/after_add() + RegisterSignal(affected_mob, COMSIG_MOB_SAY, .proc/handle_speech) + + +/datum/disease/pierrot_throat/proc/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + var/list/split_message = splittext(message, " ") //List each word in the message + var/applied = 0 + for (var/i in 1 to length(split_message)) + if(prob(3 * stage)) //Stage 1: 3% Stage 2: 6% Stage 3: 9% Stage 4: 12% + if(findtext(split_message[i], "*") || findtext(split_message[i], ";") || findtext(split_message[i], ":")) + continue + split_message[i] = "HONK" + if (applied++ > stage) + break + if (applied) + speech_args[SPEECH_SPANS] |= SPAN_CLOWN // a little bonus + message = jointext(split_message, " ") + speech_args[SPEECH_MESSAGE] = message + + +/datum/disease/pierrot_throat/Destroy() + UnregisterSignal(affected_mob, COMSIG_MOB_SAY) + return ..() + +/datum/disease/pierrot_throat/remove_disease() + UnregisterSignal(affected_mob, COMSIG_MOB_SAY) + return ..() diff --git a/code/datums/diseases/retrovirus.dm b/code/datums/diseases/retrovirus.dm index 1541cdd5ab3..311e0a29da6 100644 --- a/code/datums/diseases/retrovirus.dm +++ b/code/datums/diseases/retrovirus.dm @@ -1,84 +1,84 @@ -/datum/disease/dna_retrovirus - name = "Retrovirus" - max_stages = 4 - spread_text = "Contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Rest or an injection of mutadone" - cure_chance = 6 - agent = "" - viable_mobtypes = list(/mob/living/carbon/human) - desc = "A DNA-altering retrovirus that scrambles the structural and unique enzymes of a host constantly." - severity = DISEASE_SEVERITY_HARMFUL - permeability_mod = 0.4 - stage_prob = 2 - var/restcure = 0 - -/datum/disease/dna_retrovirus/New() - ..() - agent = "Virus class [pick("A","B","C","D","E","F")][pick("A","B","C","D","E","F")]-[rand(50,300)]" - if(prob(40)) - cures = list(/datum/reagent/medicine/mutadone) - else - restcure = 1 - -/datum/disease/dna_retrovirus/Copy() - var/datum/disease/dna_retrovirus/D = ..() - D.restcure = restcure - return D - -/datum/disease/dna_retrovirus/stage_act() - ..() - switch(stage) - if(1) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(30)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(8)) - to_chat(affected_mob, "Your head hurts.") - if (prob(9)) - to_chat(affected_mob, "You feel a tingling sensation in your chest.") - if (prob(9)) - to_chat(affected_mob, "You feel angry.") - if(2) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(8)) - to_chat(affected_mob, "Your skin feels loose.") - if (prob(10)) - to_chat(affected_mob, "You feel very strange.") - if (prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head!") - affected_mob.Unconscious(40) - if (prob(4)) - to_chat(affected_mob, "Your stomach churns.") - if(3) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(10)) - to_chat(affected_mob, "Your entire body vibrates.") - - if (prob(35)) - if(prob(50)) - scramble_dna(affected_mob, 1, 0, rand(15,45)) - else - scramble_dna(affected_mob, 0, 1, rand(15,45)) - - if(4) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(5)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(60)) - if(prob(50)) - scramble_dna(affected_mob, 1, 0, rand(50,75)) - else - scramble_dna(affected_mob, 0, 1, rand(50,75)) +/datum/disease/dna_retrovirus + name = "Retrovirus" + max_stages = 4 + spread_text = "Contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Rest or an injection of mutadone" + cure_chance = 6 + agent = "" + viable_mobtypes = list(/mob/living/carbon/human) + desc = "A DNA-altering retrovirus that scrambles the structural and unique enzymes of a host constantly." + severity = DISEASE_SEVERITY_HARMFUL + permeability_mod = 0.4 + stage_prob = 2 + var/restcure = 0 + +/datum/disease/dna_retrovirus/New() + ..() + agent = "Virus class [pick("A","B","C","D","E","F")][pick("A","B","C","D","E","F")]-[rand(50,300)]" + if(prob(40)) + cures = list(/datum/reagent/medicine/mutadone) + else + restcure = 1 + +/datum/disease/dna_retrovirus/Copy() + var/datum/disease/dna_retrovirus/D = ..() + D.restcure = restcure + return D + +/datum/disease/dna_retrovirus/stage_act() + ..() + switch(stage) + if(1) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(30)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(8)) + to_chat(affected_mob, "Your head hurts.") + if (prob(9)) + to_chat(affected_mob, "You feel a tingling sensation in your chest.") + if (prob(9)) + to_chat(affected_mob, "You feel angry.") + if(2) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(8)) + to_chat(affected_mob, "Your skin feels loose.") + if (prob(10)) + to_chat(affected_mob, "You feel very strange.") + if (prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head!") + affected_mob.Unconscious(40) + if (prob(4)) + to_chat(affected_mob, "Your stomach churns.") + if(3) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(10)) + to_chat(affected_mob, "Your entire body vibrates.") + + if (prob(35)) + if(prob(50)) + scramble_dna(affected_mob, 1, 0, rand(15,45)) + else + scramble_dna(affected_mob, 0, 1, rand(15,45)) + + if(4) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(5)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(60)) + if(prob(50)) + scramble_dna(affected_mob, 1, 0, rand(50,75)) + else + scramble_dna(affected_mob, 0, 1, rand(50,75)) diff --git a/code/datums/diseases/rhumba_beat.dm b/code/datums/diseases/rhumba_beat.dm index 52e9c2e19f7..de9b66f27e4 100644 --- a/code/datums/diseases/rhumba_beat.dm +++ b/code/datums/diseases/rhumba_beat.dm @@ -1,45 +1,45 @@ -/datum/disease/rhumba_beat - name = "The Rhumba Beat" - max_stages = 5 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Chick Chicky Boom!" - cures = list("plasma") - agent = "Unknown" - viable_mobtypes = list(/mob/living/carbon/human) - permeability_mod = 1 - severity = DISEASE_SEVERITY_BIOHAZARD - -/datum/disease/rhumba_beat/stage_act() - ..() - if(affected_mob.ckey == "rosham") - cure() - return - switch(stage) - if(2) - if(prob(45)) - affected_mob.adjustFireLoss(5) - affected_mob.updatehealth() - if(prob(1)) - to_chat(affected_mob, "You feel strange...") - if(3) - if(prob(5)) - to_chat(affected_mob, "You feel the urge to dance...") - else if(prob(5)) - affected_mob.emote("gasp") - else if(prob(10)) - to_chat(affected_mob, "You feel the need to chick chicky boom...") - if(4) - if(prob(20)) - if (prob(50)) - affected_mob.adjust_fire_stacks(2) - affected_mob.IgniteMob() - else - affected_mob.emote("gasp") - to_chat(affected_mob, "You feel a burning beat inside...") - if(5) - to_chat(affected_mob, "Your body is unable to contain the Rhumba Beat...") - if(prob(50)) - explosion(get_turf(affected_mob), -1, 0, 2, 3, 0, 2) // This is equivalent to a lvl 1 fireball - else - return +/datum/disease/rhumba_beat + name = "The Rhumba Beat" + max_stages = 5 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Chick Chicky Boom!" + cures = list("plasma") + agent = "Unknown" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 1 + severity = DISEASE_SEVERITY_BIOHAZARD + +/datum/disease/rhumba_beat/stage_act() + ..() + if(affected_mob.ckey == "rosham") + cure() + return + switch(stage) + if(2) + if(prob(45)) + affected_mob.adjustFireLoss(5) + affected_mob.updatehealth() + if(prob(1)) + to_chat(affected_mob, "You feel strange...") + if(3) + if(prob(5)) + to_chat(affected_mob, "You feel the urge to dance...") + else if(prob(5)) + affected_mob.emote("gasp") + else if(prob(10)) + to_chat(affected_mob, "You feel the need to chick chicky boom...") + if(4) + if(prob(20)) + if (prob(50)) + affected_mob.adjust_fire_stacks(2) + affected_mob.IgniteMob() + else + affected_mob.emote("gasp") + to_chat(affected_mob, "You feel a burning beat inside...") + if(5) + to_chat(affected_mob, "Your body is unable to contain the Rhumba Beat...") + if(prob(50)) + explosion(get_turf(affected_mob), -1, 0, 2, 3, 0, 2) // This is equivalent to a lvl 1 fireball + else + return diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index 5e00bc51092..9827914c1da 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -1,329 +1,329 @@ -/datum/disease/transformation - name = "Transformation" - max_stages = 5 - spread_text = "Acute" - spread_flags = DISEASE_SPREAD_SPECIAL - cure_text = "A coder's love (theoretical)." - agent = "Shenanigans" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey, /mob/living/carbon/alien) - severity = DISEASE_SEVERITY_BIOHAZARD - stage_prob = 10 - visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC - disease_flags = CURABLE - var/list/stage1 = list("You feel unremarkable.") - var/list/stage2 = list("You feel boring.") - var/list/stage3 = list("You feel utterly plain.") - var/list/stage4 = list("You feel white bread.") - var/list/stage5 = list("Oh the humanity!") - var/new_form = /mob/living/carbon/human - var/bantype - var/transformed_antag_datum //Do we add a specific antag datum once the transformation is complete? - -/datum/disease/transformation/Copy() - var/datum/disease/transformation/D = ..() - D.stage1 = stage1.Copy() - D.stage2 = stage2.Copy() - D.stage3 = stage3.Copy() - D.stage4 = stage4.Copy() - D.stage5 = stage5.Copy() - D.new_form = D.new_form - return D - -/datum/disease/transformation/stage_act() - ..() - switch(stage) - if(1) - if (prob(stage_prob) && stage1) - to_chat(affected_mob, pick(stage1)) - if(2) - if (prob(stage_prob) && stage2) - to_chat(affected_mob, pick(stage2)) - if(3) - if (prob(stage_prob*2) && stage3) - to_chat(affected_mob, pick(stage3)) - if(4) - if (prob(stage_prob*2) && stage4) - to_chat(affected_mob, pick(stage4)) - if(5) - do_disease_transformation(affected_mob) - -/datum/disease/transformation/proc/do_disease_transformation(mob/living/affected_mob) - if(istype(affected_mob, /mob/living/carbon) && affected_mob.stat != DEAD) - if(stage5) - to_chat(affected_mob, pick(stage5)) - if(QDELETED(affected_mob)) - return - if(affected_mob.notransform) - return - affected_mob.notransform = 1 - for(var/obj/item/W in affected_mob.get_equipped_items(TRUE)) - affected_mob.dropItemToGround(W) - for(var/obj/item/I in affected_mob.held_items) - affected_mob.dropItemToGround(I) - var/mob/living/new_mob = new new_form(affected_mob.loc) - if(istype(new_mob)) - if(bantype && is_banned_from(affected_mob.ckey, bantype)) - replace_banned_player(new_mob) - new_mob.a_intent = INTENT_HARM - if(affected_mob.mind) - affected_mob.mind.transfer_to(new_mob) - else - new_mob.key = affected_mob.key - if(transformed_antag_datum) - new_mob.mind.add_antag_datum(transformed_antag_datum) - new_mob.name = affected_mob.real_name - new_mob.real_name = new_mob.name - qdel(affected_mob) - -/datum/disease/transformation/proc/replace_banned_player(var/mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. - set waitfor = FALSE - - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [affected_mob.name]?", bantype, null, bantype, 50, affected_mob) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") - affected_mob.ghostize(0) - affected_mob.key = C.key - else - to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!") - new_mob.death() - if (!QDELETED(new_mob)) - new_mob.ghostize(can_reenter_corpse = FALSE) - new_mob.key = null - -/datum/disease/transformation/jungle_fever - name = "Jungle Fever" - cure_text = "Death." - cures = list(/datum/reagent/medicine/adminordrazine) - spread_text = "Monkey Bites" - spread_flags = DISEASE_SPREAD_SPECIAL - viable_mobtypes = list(/mob/living/carbon/monkey, /mob/living/carbon/human) - permeability_mod = 1 - cure_chance = 1 - disease_flags = CAN_CARRY|CAN_RESIST - desc = "Monkeys with this disease will bite humans, causing humans to mutate into a monkey." - severity = DISEASE_SEVERITY_BIOHAZARD - stage_prob = 4 - visibility_flags = 0 - agent = "Kongey Vibrion M-909" - new_form = /mob/living/carbon/monkey - bantype = ROLE_MONKEY - - - stage1 = list() - stage2 = list() - stage3 = list() - stage4 = list("Your back hurts.", "You breathe through your mouth.", - "You have a craving for bananas.", "Your mind feels clouded.") - stage5 = list("You feel like monkeying around.") - -/datum/disease/transformation/jungle_fever/do_disease_transformation(mob/living/carbon/affected_mob) - if(affected_mob.mind && !is_monkey(affected_mob.mind)) - add_monkey(affected_mob.mind) - if(ishuman(affected_mob)) - var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - M.ventcrawler = VENTCRAWLER_ALWAYS - -/datum/disease/transformation/jungle_fever/stage_act() - ..() - switch(stage) - if(2) - if(prob(2)) - to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") - if(3) - if(prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.confused += 10 - if(4) - if(prob(3)) - affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") - -/datum/disease/transformation/jungle_fever/cure() - remove_monkey(affected_mob.mind) - ..() - -/datum/disease/transformation/jungle_fever/monkeymode - visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC - disease_flags = CAN_CARRY //no vaccines! no cure! - -/datum/disease/transformation/jungle_fever/monkeymode/after_add() - if(affected_mob && !is_monkey_leader(affected_mob.mind)) - visibility_flags = NONE - - - -/datum/disease/transformation/robot - - name = "Robotic Transformation" - cure_text = "An injection of copper." - cures = list(/datum/reagent/copper) - cure_chance = 5 - agent = "R2D2 Nanomachines" - desc = "This disease, actually acute nanomachine infection, converts the victim into a cyborg." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list() - stage2 = list("Your joints feel stiff.", "Beep...boop..") - stage3 = list("Your joints feel very stiff.", "Your skin feels loose.", "You can feel something move...inside.") - stage4 = list("Your skin feels very loose.", "You can feel... something...inside you.") - stage5 = list("Your skin feels as if it's about to burst off!") - new_form = /mob/living/silicon/robot - infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC - bantype = "Cyborg" - -/datum/disease/transformation/robot/stage_act() - ..() - switch(stage) - if(3) - if (prob(8)) - affected_mob.say(pick("Beep, boop", "beep, beep!", "Boop...bop"), forced = "robotic transformation") - if (prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.Unconscious(40) - if(4) - if (prob(20)) - affected_mob.say(pick("beep, beep!", "Boop bop boop beep.", "kkkiiiill mmme", "I wwwaaannntt tttoo dddiiieeee..."), forced = "robotic transformation") - - -/datum/disease/transformation/xeno - - name = "Xenomorph Transformation" - cure_text = "Spaceacillin & Glycerol" - cures = list(/datum/reagent/medicine/spaceacillin, /datum/reagent/glycerol) - cure_chance = 5 - agent = "Rip-LEY Alien Microbes" - desc = "This disease changes the victim into a xenomorph." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list() - stage2 = list("Your throat feels scratchy.", "Kill...") - stage3 = list("Your throat feels very scratchy.", "Your skin feels tight.", "You can feel something move...inside.") - stage4 = list("Your skin feels very tight.", "Your blood boils!", "You can feel... something...inside you.") - stage5 = list("Your skin feels as if it's about to burst off!") - new_form = /mob/living/carbon/alien/humanoid/hunter - bantype = ROLE_ALIEN - -/datum/disease/transformation/xeno/stage_act() - ..() - switch(stage) - if(3) - if (prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.Unconscious(40) - if(4) - if (prob(20)) - affected_mob.say(pick("You look delicious.", "Going to... devour you...", "Hsssshhhhh!"), forced = "xenomorph transformation") - - -/datum/disease/transformation/slime - name = "Advanced Mutation Transformation" - cure_text = "frost oil" - cures = list(/datum/reagent/consumable/frostoil) - cure_chance = 80 - agent = "Advanced Mutation Toxin" - desc = "This highly concentrated extract converts anything into more of itself." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("You don't feel very well.") - stage2 = list("Your skin feels a little slimy.") - stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") - stage4 = list("You are turning into a slime.") - stage5 = list("You have become a slime.") - new_form = /mob/living/simple_animal/slime/random - -/datum/disease/transformation/slime/stage_act() - ..() - switch(stage) - if(1) - if(ishuman(affected_mob) && affected_mob.dna) - if(affected_mob.dna.species.id == "slime" || affected_mob.dna.species.id == "stargazer" || affected_mob.dna.species.id == "lum") - stage = 5 - if(3) - if(ishuman(affected_mob)) - var/mob/living/carbon/human/human = affected_mob - if(human.dna.species.id != "slime" && affected_mob.dna.species.id != "stargazer" && affected_mob.dna.species.id != "lum") - human.set_species(/datum/species/jelly/slime) - -/datum/disease/transformation/corgi - name = "The Barkening" - cure_text = "Death" - cures = list(/datum/reagent/medicine/adminordrazine) - agent = "Fell Doge Majicks" - desc = "This disease transforms the victim into a corgi." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("BARK.") - stage2 = list("You feel the need to wear silly hats.") - stage3 = list("Must... eat... chocolate....", "YAP") - stage4 = list("Visions of washing machines assail your mind!") - stage5 = list("AUUUUUU!!!") - new_form = /mob/living/simple_animal/pet/dog/corgi - -/datum/disease/transformation/corgi/stage_act() - ..() - switch(stage) - if(3) - if (prob(8)) - affected_mob.say(pick("YAP", "Woof!"), forced = "corgi transformation") - if(4) - if (prob(20)) - affected_mob.say(pick("Bark!", "AUUUUUU"), forced = "corgi transformation") - -/datum/disease/transformation/morph - name = "Gluttony's Blessing" - cure_text = /datum/reagent/consumable/nothing - cures = list(/datum/reagent/medicine/adminordrazine) - agent = "Gluttony's Blessing" - desc = "A 'gift' from somewhere terrible." - stage_prob = 20 - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("Your stomach rumbles.") - stage2 = list("Your skin feels saggy.") - stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") - stage4 = list("You're ravenous.") - stage5 = list("You have become a morph.") - new_form = /mob/living/simple_animal/hostile/morph - infectable_biotypes = MOB_ORGANIC|MOB_MINERAL|MOB_UNDEAD //magic! - transformed_antag_datum = /datum/antagonist/morph - -/datum/disease/transformation/gondola - name = "Gondola Transformation" - cure_text = "Condensed Capsaicin, ingested or injected." //getting pepper sprayed doesn't help - cures = list(/datum/reagent/consumable/condensedcapsaicin) //beats the hippie crap right out of your system - cure_chance = 80 - stage_prob = 5 - agent = "Tranquility" - desc = "Consuming the flesh of a Gondola comes at a terrible price." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("You seem a little lighter in your step.") - stage2 = list("You catch yourself smiling for no reason.") - stage3 = list("A cruel sense of calm overcomes you.", "You can't feel your arms!", "You let go of the urge to hurt clowns.") - stage4 = list("You can't feel your arms. It does not bother you anymore.", "You forgive the clown for hurting you.") - stage5 = list("You have become a Gondola.") - new_form = /mob/living/simple_animal/pet/gondola - -/datum/disease/transformation/gondola/stage_act() - ..() - switch(stage) - if(2) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if(3) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if(4) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if (prob(2)) - to_chat(affected_mob, "You let go of what you were holding.") - var/obj/item/I = affected_mob.get_active_held_item() - affected_mob.dropItemToGround(I) +/datum/disease/transformation + name = "Transformation" + max_stages = 5 + spread_text = "Acute" + spread_flags = DISEASE_SPREAD_SPECIAL + cure_text = "A coder's love (theoretical)." + agent = "Shenanigans" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey, /mob/living/carbon/alien) + severity = DISEASE_SEVERITY_BIOHAZARD + stage_prob = 10 + visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC + disease_flags = CURABLE + var/list/stage1 = list("You feel unremarkable.") + var/list/stage2 = list("You feel boring.") + var/list/stage3 = list("You feel utterly plain.") + var/list/stage4 = list("You feel white bread.") + var/list/stage5 = list("Oh the humanity!") + var/new_form = /mob/living/carbon/human + var/bantype + var/transformed_antag_datum //Do we add a specific antag datum once the transformation is complete? + +/datum/disease/transformation/Copy() + var/datum/disease/transformation/D = ..() + D.stage1 = stage1.Copy() + D.stage2 = stage2.Copy() + D.stage3 = stage3.Copy() + D.stage4 = stage4.Copy() + D.stage5 = stage5.Copy() + D.new_form = D.new_form + return D + +/datum/disease/transformation/stage_act() + ..() + switch(stage) + if(1) + if (prob(stage_prob) && stage1) + to_chat(affected_mob, pick(stage1)) + if(2) + if (prob(stage_prob) && stage2) + to_chat(affected_mob, pick(stage2)) + if(3) + if (prob(stage_prob*2) && stage3) + to_chat(affected_mob, pick(stage3)) + if(4) + if (prob(stage_prob*2) && stage4) + to_chat(affected_mob, pick(stage4)) + if(5) + do_disease_transformation(affected_mob) + +/datum/disease/transformation/proc/do_disease_transformation(mob/living/affected_mob) + if(istype(affected_mob, /mob/living/carbon) && affected_mob.stat != DEAD) + if(stage5) + to_chat(affected_mob, pick(stage5)) + if(QDELETED(affected_mob)) + return + if(affected_mob.notransform) + return + affected_mob.notransform = 1 + for(var/obj/item/W in affected_mob.get_equipped_items(TRUE)) + affected_mob.dropItemToGround(W) + for(var/obj/item/I in affected_mob.held_items) + affected_mob.dropItemToGround(I) + var/mob/living/new_mob = new new_form(affected_mob.loc) + if(istype(new_mob)) + if(bantype && is_banned_from(affected_mob.ckey, bantype)) + replace_banned_player(new_mob) + new_mob.a_intent = INTENT_HARM + if(affected_mob.mind) + affected_mob.mind.transfer_to(new_mob) + else + new_mob.key = affected_mob.key + if(transformed_antag_datum) + new_mob.mind.add_antag_datum(transformed_antag_datum) + new_mob.name = affected_mob.real_name + new_mob.real_name = new_mob.name + qdel(affected_mob) + +/datum/disease/transformation/proc/replace_banned_player(var/mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. + set waitfor = FALSE + + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [affected_mob.name]?", bantype, null, bantype, 50, affected_mob) + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") + message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") + affected_mob.ghostize(0) + affected_mob.key = C.key + else + to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!") + new_mob.death() + if (!QDELETED(new_mob)) + new_mob.ghostize(can_reenter_corpse = FALSE) + new_mob.key = null + +/datum/disease/transformation/jungle_fever + name = "Jungle Fever" + cure_text = "Death." + cures = list(/datum/reagent/medicine/adminordrazine) + spread_text = "Monkey Bites" + spread_flags = DISEASE_SPREAD_SPECIAL + viable_mobtypes = list(/mob/living/carbon/monkey, /mob/living/carbon/human) + permeability_mod = 1 + cure_chance = 1 + disease_flags = CAN_CARRY|CAN_RESIST + desc = "Monkeys with this disease will bite humans, causing humans to mutate into a monkey." + severity = DISEASE_SEVERITY_BIOHAZARD + stage_prob = 4 + visibility_flags = 0 + agent = "Kongey Vibrion M-909" + new_form = /mob/living/carbon/monkey + bantype = ROLE_MONKEY + + + stage1 = list() + stage2 = list() + stage3 = list() + stage4 = list("Your back hurts.", "You breathe through your mouth.", + "You have a craving for bananas.", "Your mind feels clouded.") + stage5 = list("You feel like monkeying around.") + +/datum/disease/transformation/jungle_fever/do_disease_transformation(mob/living/carbon/affected_mob) + if(affected_mob.mind && !is_monkey(affected_mob.mind)) + add_monkey(affected_mob.mind) + if(ishuman(affected_mob)) + var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + M.ventcrawler = VENTCRAWLER_ALWAYS + +/datum/disease/transformation/jungle_fever/stage_act() + ..() + switch(stage) + if(2) + if(prob(2)) + to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") + if(3) + if(prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head.") + affected_mob.confused += 10 + if(4) + if(prob(3)) + affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") + +/datum/disease/transformation/jungle_fever/cure() + remove_monkey(affected_mob.mind) + ..() + +/datum/disease/transformation/jungle_fever/monkeymode + visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC + disease_flags = CAN_CARRY //no vaccines! no cure! + +/datum/disease/transformation/jungle_fever/monkeymode/after_add() + if(affected_mob && !is_monkey_leader(affected_mob.mind)) + visibility_flags = NONE + + + +/datum/disease/transformation/robot + + name = "Robotic Transformation" + cure_text = "An injection of copper." + cures = list(/datum/reagent/copper) + cure_chance = 5 + agent = "R2D2 Nanomachines" + desc = "This disease, actually acute nanomachine infection, converts the victim into a cyborg." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list() + stage2 = list("Your joints feel stiff.", "Beep...boop..") + stage3 = list("Your joints feel very stiff.", "Your skin feels loose.", "You can feel something move...inside.") + stage4 = list("Your skin feels very loose.", "You can feel... something...inside you.") + stage5 = list("Your skin feels as if it's about to burst off!") + new_form = /mob/living/silicon/robot + infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC + bantype = "Cyborg" + +/datum/disease/transformation/robot/stage_act() + ..() + switch(stage) + if(3) + if (prob(8)) + affected_mob.say(pick("Beep, boop", "beep, beep!", "Boop...bop"), forced = "robotic transformation") + if (prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head.") + affected_mob.Unconscious(40) + if(4) + if (prob(20)) + affected_mob.say(pick("beep, beep!", "Boop bop boop beep.", "kkkiiiill mmme", "I wwwaaannntt tttoo dddiiieeee..."), forced = "robotic transformation") + + +/datum/disease/transformation/xeno + + name = "Xenomorph Transformation" + cure_text = "Spaceacillin & Glycerol" + cures = list(/datum/reagent/medicine/spaceacillin, /datum/reagent/glycerol) + cure_chance = 5 + agent = "Rip-LEY Alien Microbes" + desc = "This disease changes the victim into a xenomorph." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list() + stage2 = list("Your throat feels scratchy.", "Kill...") + stage3 = list("Your throat feels very scratchy.", "Your skin feels tight.", "You can feel something move...inside.") + stage4 = list("Your skin feels very tight.", "Your blood boils!", "You can feel... something...inside you.") + stage5 = list("Your skin feels as if it's about to burst off!") + new_form = /mob/living/carbon/alien/humanoid/hunter + bantype = ROLE_ALIEN + +/datum/disease/transformation/xeno/stage_act() + ..() + switch(stage) + if(3) + if (prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head.") + affected_mob.Unconscious(40) + if(4) + if (prob(20)) + affected_mob.say(pick("You look delicious.", "Going to... devour you...", "Hsssshhhhh!"), forced = "xenomorph transformation") + + +/datum/disease/transformation/slime + name = "Advanced Mutation Transformation" + cure_text = "frost oil" + cures = list(/datum/reagent/consumable/frostoil) + cure_chance = 80 + agent = "Advanced Mutation Toxin" + desc = "This highly concentrated extract converts anything into more of itself." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("You don't feel very well.") + stage2 = list("Your skin feels a little slimy.") + stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") + stage4 = list("You are turning into a slime.") + stage5 = list("You have become a slime.") + new_form = /mob/living/simple_animal/slime/random + +/datum/disease/transformation/slime/stage_act() + ..() + switch(stage) + if(1) + if(ishuman(affected_mob) && affected_mob.dna) + if(affected_mob.dna.species.id == "slime" || affected_mob.dna.species.id == "stargazer" || affected_mob.dna.species.id == "lum") + stage = 5 + if(3) + if(ishuman(affected_mob)) + var/mob/living/carbon/human/human = affected_mob + if(human.dna.species.id != "slime" && affected_mob.dna.species.id != "stargazer" && affected_mob.dna.species.id != "lum") + human.set_species(/datum/species/jelly/slime) + +/datum/disease/transformation/corgi + name = "The Barkening" + cure_text = "Death" + cures = list(/datum/reagent/medicine/adminordrazine) + agent = "Fell Doge Majicks" + desc = "This disease transforms the victim into a corgi." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("BARK.") + stage2 = list("You feel the need to wear silly hats.") + stage3 = list("Must... eat... chocolate....", "YAP") + stage4 = list("Visions of washing machines assail your mind!") + stage5 = list("AUUUUUU!!!") + new_form = /mob/living/simple_animal/pet/dog/corgi + +/datum/disease/transformation/corgi/stage_act() + ..() + switch(stage) + if(3) + if (prob(8)) + affected_mob.say(pick("YAP", "Woof!"), forced = "corgi transformation") + if(4) + if (prob(20)) + affected_mob.say(pick("Bark!", "AUUUUUU"), forced = "corgi transformation") + +/datum/disease/transformation/morph + name = "Gluttony's Blessing" + cure_text = /datum/reagent/consumable/nothing + cures = list(/datum/reagent/medicine/adminordrazine) + agent = "Gluttony's Blessing" + desc = "A 'gift' from somewhere terrible." + stage_prob = 20 + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("Your stomach rumbles.") + stage2 = list("Your skin feels saggy.") + stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") + stage4 = list("You're ravenous.") + stage5 = list("You have become a morph.") + new_form = /mob/living/simple_animal/hostile/morph + infectable_biotypes = MOB_ORGANIC|MOB_MINERAL|MOB_UNDEAD //magic! + transformed_antag_datum = /datum/antagonist/morph + +/datum/disease/transformation/gondola + name = "Gondola Transformation" + cure_text = "Condensed Capsaicin, ingested or injected." //getting pepper sprayed doesn't help + cures = list(/datum/reagent/consumable/condensedcapsaicin) //beats the hippie crap right out of your system + cure_chance = 80 + stage_prob = 5 + agent = "Tranquility" + desc = "Consuming the flesh of a Gondola comes at a terrible price." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("You seem a little lighter in your step.") + stage2 = list("You catch yourself smiling for no reason.") + stage3 = list("A cruel sense of calm overcomes you.", "You can't feel your arms!", "You let go of the urge to hurt clowns.") + stage4 = list("You can't feel your arms. It does not bother you anymore.", "You forgive the clown for hurting you.") + stage5 = list("You have become a Gondola.") + new_form = /mob/living/simple_animal/pet/gondola + +/datum/disease/transformation/gondola/stage_act() + ..() + switch(stage) + if(2) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if(3) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if(4) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if (prob(2)) + to_chat(affected_mob, "You let go of what you were holding.") + var/obj/item/I = affected_mob.get_active_held_item() + affected_mob.dropItemToGround(I) diff --git a/code/datums/diseases/wizarditis.dm b/code/datums/diseases/wizarditis.dm index b7a74615cb7..758775750c1 100644 --- a/code/datums/diseases/wizarditis.dm +++ b/code/datums/diseases/wizarditis.dm @@ -1,117 +1,117 @@ -/datum/disease/wizarditis - name = "Wizarditis" - max_stages = 4 - spread_text = "Airborne" - cure_text = "The Manly Dorf" - cures = list(/datum/reagent/consumable/ethanol/manly_dorf) - cure_chance = 100 - agent = "Rincewindus Vulgaris" - viable_mobtypes = list(/mob/living/carbon/human) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - permeability_mod = 0.75 - desc = "Some speculate that this virus is the cause of the Space Wizard Federation's existence. Subjects affected show the signs of brain damage, yelling obscure sentences or total gibberish. On late stages subjects sometime express the feelings of inner power, and, cite, 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike, condition." - severity = DISEASE_SEVERITY_HARMFUL - required_organs = list(/obj/item/bodypart/head) - -/* -BIRUZ BENNAR -SCYAR NILA - teleport -NEC CANTIO - dis techno -EI NATH - shocking grasp -AULIE OXIN FIERA - knock -TARCOL MINTI ZHERI - forcewall -STI KALY - blind -*/ - -/datum/disease/wizarditis/stage_act() - ..() - - switch(stage) - if(2) - if(prob(1)&&prob(50)) - affected_mob.say(pick("You shall not pass!", "Expeliarmus!", "By Merlins beard!", "Feel the power of the Dark Side!"), forced = "wizarditis") - if(prob(1)&&prob(50)) - to_chat(affected_mob, "You feel [pick("that you don't have enough mana", "that the winds of magic are gone", "an urge to summon familiar")].") - - - if(3) - if(prob(1)&&prob(50)) - affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!", "STI KALY!", "TARCOL MINTI ZHERI!"), forced = "wizarditis") - if(prob(1)&&prob(50)) - to_chat(affected_mob, "You feel [pick("the magic bubbling in your veins","that this location gives you a +1 to INT","an urge to summon familiar")].") - - if(4) - - if(prob(1)) - affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!","STI KALY!","EI NATH!"), forced = "wizarditis") - return - if(prob(1)&&prob(50)) - to_chat(affected_mob, "You feel [pick("the tidal wave of raw power building inside","that this location gives you a +2 to INT and +1 to WIS","an urge to teleport")].") - spawn_wizard_clothes(50) - if(prob(1)&&prob(1)) - teleport() - return - - - -/datum/disease/wizarditis/proc/spawn_wizard_clothes(chance = 0) - if(ishuman(affected_mob)) - var/mob/living/carbon/human/H = affected_mob - if(prob(chance)) - if(!istype(H.head, /obj/item/clothing/head/wizard)) - if(!H.dropItemToGround(H.head)) - qdel(H.head) - H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(H), ITEM_SLOT_HEAD) - return - if(prob(chance)) - if(!istype(H.wear_suit, /obj/item/clothing/suit/wizrobe)) - if(!H.dropItemToGround(H.wear_suit)) - qdel(H.wear_suit) - H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(H), ITEM_SLOT_OCLOTHING) - return - if(prob(chance)) - if(!istype(H.shoes, /obj/item/clothing/shoes/sandal/magic)) - if(!H.dropItemToGround(H.shoes)) - qdel(H.shoes) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(H), ITEM_SLOT_FEET) - return - else - var/mob/living/carbon/H = affected_mob - if(prob(chance)) - var/obj/item/staff/S = new(H) - if(!H.put_in_hands(S)) - qdel(S) - - -/datum/disease/wizarditis/proc/teleport() - var/list/theareas = get_areas_in_range(80, affected_mob) - for(var/area/space/S in theareas) - theareas -= S - - if(!theareas||!theareas.len) - return - - var/area/thearea = pick(theareas) - - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(T.z != affected_mob.z) - continue - if(T.name == "space") - continue - if(!T.density) - var/clear = 1 - for(var/obj/O in T) - if(O.density) - clear = 0 - break - if(clear) - L+=T - - if(!L) - return - - affected_mob.say("SCYAR NILA [uppertext(thearea.name)]!", forced = "wizarditis teleport") - affected_mob.forceMove(pick(L)) - - return +/datum/disease/wizarditis + name = "Wizarditis" + max_stages = 4 + spread_text = "Airborne" + cure_text = "The Manly Dorf" + cures = list(/datum/reagent/consumable/ethanol/manly_dorf) + cure_chance = 100 + agent = "Rincewindus Vulgaris" + viable_mobtypes = list(/mob/living/carbon/human) + disease_flags = CAN_CARRY|CAN_RESIST|CURABLE + permeability_mod = 0.75 + desc = "Some speculate that this virus is the cause of the Space Wizard Federation's existence. Subjects affected show the signs of brain damage, yelling obscure sentences or total gibberish. On late stages subjects sometime express the feelings of inner power, and, cite, 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike, condition." + severity = DISEASE_SEVERITY_HARMFUL + required_organs = list(/obj/item/bodypart/head) + +/* +BIRUZ BENNAR +SCYAR NILA - teleport +NEC CANTIO - dis techno +EI NATH - shocking grasp +AULIE OXIN FIERA - knock +TARCOL MINTI ZHERI - forcewall +STI KALY - blind +*/ + +/datum/disease/wizarditis/stage_act() + ..() + + switch(stage) + if(2) + if(prob(1)&&prob(50)) + affected_mob.say(pick("You shall not pass!", "Expeliarmus!", "By Merlins beard!", "Feel the power of the Dark Side!"), forced = "wizarditis") + if(prob(1)&&prob(50)) + to_chat(affected_mob, "You feel [pick("that you don't have enough mana", "that the winds of magic are gone", "an urge to summon familiar")].") + + + if(3) + if(prob(1)&&prob(50)) + affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!", "STI KALY!", "TARCOL MINTI ZHERI!"), forced = "wizarditis") + if(prob(1)&&prob(50)) + to_chat(affected_mob, "You feel [pick("the magic bubbling in your veins","that this location gives you a +1 to INT","an urge to summon familiar")].") + + if(4) + + if(prob(1)) + affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!","STI KALY!","EI NATH!"), forced = "wizarditis") + return + if(prob(1)&&prob(50)) + to_chat(affected_mob, "You feel [pick("the tidal wave of raw power building inside","that this location gives you a +2 to INT and +1 to WIS","an urge to teleport")].") + spawn_wizard_clothes(50) + if(prob(1)&&prob(1)) + teleport() + return + + + +/datum/disease/wizarditis/proc/spawn_wizard_clothes(chance = 0) + if(ishuman(affected_mob)) + var/mob/living/carbon/human/H = affected_mob + if(prob(chance)) + if(!istype(H.head, /obj/item/clothing/head/wizard)) + if(!H.dropItemToGround(H.head)) + qdel(H.head) + H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(H), ITEM_SLOT_HEAD) + return + if(prob(chance)) + if(!istype(H.wear_suit, /obj/item/clothing/suit/wizrobe)) + if(!H.dropItemToGround(H.wear_suit)) + qdel(H.wear_suit) + H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(H), ITEM_SLOT_OCLOTHING) + return + if(prob(chance)) + if(!istype(H.shoes, /obj/item/clothing/shoes/sandal/magic)) + if(!H.dropItemToGround(H.shoes)) + qdel(H.shoes) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(H), ITEM_SLOT_FEET) + return + else + var/mob/living/carbon/H = affected_mob + if(prob(chance)) + var/obj/item/staff/S = new(H) + if(!H.put_in_hands(S)) + qdel(S) + + +/datum/disease/wizarditis/proc/teleport() + var/list/theareas = get_areas_in_range(80, affected_mob) + for(var/area/space/S in theareas) + theareas -= S + + if(!theareas||!theareas.len) + return + + var/area/thearea = pick(theareas) + + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + if(T.z != affected_mob.z) + continue + if(T.name == "space") + continue + if(!T.density) + var/clear = 1 + for(var/obj/O in T) + if(O.density) + clear = 0 + break + if(clear) + L+=T + + if(!L) + return + + affected_mob.say("SCYAR NILA [uppertext(thearea.name)]!", forced = "wizarditis teleport") + affected_mob.forceMove(pick(L)) + + return diff --git a/code/datums/dna.dm b/code/datums/dna.dm index 0e025b60cd8..82833241d55 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -1,670 +1,670 @@ - -/////////////////////////// DNA DATUM -/datum/dna - var/unique_enzymes - var/uni_identity - var/blood_type - var/datum/species/species = new /datum/species/human //The type of mutant race the player is if applicable (i.e. potato-man) - var/list/features = list("FFF") //first value is mutant color - var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings, - var/list/mutations = list() //All mutations are from now on here - var/list/temporary_mutations = list() //Temporary changes to the UE - var/list/previous = list() //For temporary name/ui/ue/blood_type modifications - var/mob/living/holder - var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block - var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting - var/stability = 100 - var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers. - -/datum/dna/New(mob/living/new_holder) - if(istype(new_holder)) - holder = new_holder - -/datum/dna/Destroy() - if(iscarbon(holder)) - var/mob/living/carbon/cholder = holder - if(cholder.dna == src) - cholder.dna = null - holder = null - - QDEL_NULL(species) - - mutations.Cut() //This only references mutations, just dereference. - temporary_mutations.Cut() //^ - previous.Cut() //^ - - return ..() - -/datum/dna/proc/transfer_identity(mob/living/carbon/destination, transfer_SE = 0) - if(!istype(destination)) - return - destination.dna.unique_enzymes = unique_enzymes - destination.dna.uni_identity = uni_identity - destination.dna.blood_type = blood_type - destination.set_species(species.type, icon_update=0) - destination.dna.features = features.Copy() - destination.dna.real_name = real_name - destination.dna.temporary_mutations = temporary_mutations.Copy() - if(transfer_SE) - destination.dna.mutation_index = mutation_index - destination.dna.default_mutation_genes = default_mutation_genes - -/datum/dna/proc/copy_dna(datum/dna/new_dna) - new_dna.unique_enzymes = unique_enzymes - new_dna.mutation_index = mutation_index - new_dna.default_mutation_genes = default_mutation_genes - new_dna.uni_identity = uni_identity - new_dna.blood_type = blood_type - new_dna.features = features.Copy() - new_dna.species = new species.type - new_dna.real_name = real_name - new_dna.mutations = mutations.Copy() - -//See mutation.dm for what 'class' does. 'time' is time till it removes itself in decimals. 0 for no timer -/datum/dna/proc/add_mutation(mutation, class = MUT_OTHER, time) - var/mutation_type = mutation - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/HM = mutation - mutation_type = HM.type - if(get_mutation(mutation_type)) - return - return force_give(new mutation_type (class, time, copymut = mutation)) - -/datum/dna/proc/remove_mutation(mutation_type) - return force_lose(get_mutation(mutation_type)) - -/datum/dna/proc/check_mutation(mutation_type) - return get_mutation(mutation_type) - -/datum/dna/proc/remove_all_mutations(list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) - remove_mutation_group(mutations, classes, mutadone) - scrambled = FALSE - -/datum/dna/proc/remove_mutation_group(list/group, list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) - if(!group) - return - for(var/datum/mutation/human/HM in group) - if((HM.class in classes) && !(HM.mutadone_proof && mutadone)) - force_lose(HM) - -/datum/dna/proc/generate_uni_identity() - . = "" - var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) - - switch(holder.gender) - if(MALE) - L[DNA_GENDER_BLOCK] = construct_block(G_MALE, 3) - if(FEMALE) - L[DNA_GENDER_BLOCK] = construct_block(G_FEMALE, 3) - else - L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, 3) - if(ishuman(holder)) - var/mob/living/carbon/human/H = holder - if(!GLOB.hairstyles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair,GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) - L[DNA_HAIRSTYLE_BLOCK] = construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len) - L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color) - if(!GLOB.facial_hairstyles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) - L[DNA_FACIAL_HAIRSTYLE_BLOCK] = construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len) - L[DNA_FACIAL_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.facial_hair_color) - L[DNA_SKIN_TONE_BLOCK] = construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len) - L[DNA_EYE_COLOR_BLOCK] = sanitize_hexcolor(H.eye_color) - - for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) - if(L[i]) - . += L[i] - else - . += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters) - return . - -/datum/dna/proc/generate_dna_blocks() - var/bonus - if(species && species.inert_mutation) - bonus = GET_INITIALIZED_MUTATION(species.inert_mutation) - var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus - if(!LAZYLEN(mutations_temp)) - return - mutation_index.Cut() - default_mutation_genes.Cut() - shuffle_inplace(mutations_temp) - if(ismonkey(holder)) - mutations |= new RACEMUT(MUT_NORMAL) - mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT) - else - mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE) - default_mutation_genes[RACEMUT] = mutation_index[RACEMUT] - for(var/i in 2 to DNA_MUTATION_BLOCKS) - var/datum/mutation/human/M = mutations_temp[i] - mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty) - default_mutation_genes[M.type] = mutation_index[M.type] - shuffle_inplace(mutation_index) - -//Used to generate original gene sequences for every mutation -/proc/generate_gene_sequence(length=4) - var/static/list/active_sequences = list("AT","TA","GC","CG") - var/sequence - for(var/i in 1 to length*DNA_SEQUENCE_LENGTH) - sequence += pick(active_sequences) - return sequence - -//Used to create a chipped gene sequence -/proc/create_sequence(mutation, active, difficulty) - if(!difficulty) - var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(mutation) //leaves the possibility to change difficulty mid-round - if(!A) - return - difficulty = A.difficulty - difficulty += rand(-2,4) - var/sequence = GET_SEQUENCE(mutation) - if(active) - return sequence - while(difficulty) - var/randnum = rand(1, length_char(sequence)) - sequence = copytext_char(sequence, 1, randnum) + "X" + copytext_char(sequence, randnum + 1) - difficulty-- - return sequence - -/datum/dna/proc/generate_unique_enzymes() - . = "" - if(istype(holder)) - real_name = holder.real_name - . += md5(holder.real_name) - else - . += random_string(DNA_UNIQUE_ENZYMES_LEN, GLOB.hex_characters) - return . - -/datum/dna/proc/update_ui_block(blocknumber) - if(!blocknumber || !ishuman(holder)) - return - var/mob/living/carbon/human/H = holder - switch(blocknumber) - if(DNA_HAIR_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.hair_color)) - if(DNA_FACIAL_HAIR_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.facial_hair_color)) - if(DNA_SKIN_TONE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len)) - if(DNA_EYE_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.eye_color)) - if(DNA_GENDER_BLOCK) - switch(H.gender) - if(MALE) - setblock(uni_identity, blocknumber, construct_block(G_MALE, 3)) - if(FEMALE) - setblock(uni_identity, blocknumber, construct_block(G_FEMALE, 3)) - else - setblock(uni_identity, blocknumber, construct_block(G_PLURAL, 3)) - if(DNA_FACIAL_HAIRSTYLE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len)) - if(DNA_HAIRSTYLE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len)) - -//Please use add_mutation or activate_mutation instead -/datum/dna/proc/force_give(datum/mutation/human/HM) - if(holder && HM) - if(HM.class == MUT_NORMAL) - set_se(1, HM) - . = HM.on_acquiring(holder) - if(.) - qdel(HM) - update_instability() - -//Use remove_mutation instead -/datum/dna/proc/force_lose(datum/mutation/human/HM) - if(holder && (HM in mutations)) - set_se(0, HM) - . = HM.on_losing(holder) - update_instability(FALSE) - return - -/datum/dna/proc/is_same_as(datum/dna/D) - if(uni_identity == D.uni_identity && mutation_index == D.mutation_index && real_name == D.real_name) - if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) - return 1 - return 0 - -/datum/dna/proc/update_instability(alert=TRUE) - stability = 100 - for(var/datum/mutation/human/M in mutations) - if(M.class == MUT_EXTRA) - stability -= M.instability * GET_MUTATION_STABILIZER(M) - if(holder) - var/message - if(alert) - switch(stability) - if(70 to 90) - message = "You shiver." - if(60 to 69) - message = "You feel cold." - if(40 to 59) - message = "You feel sick." - if(20 to 39) - message = "It feels like your skin is moving." - if(1 to 19) - message = "You can feel your cells burning." - if(-INFINITY to 0) - message = "You can feel your DNA exploding, we need to do something fast!" - if(stability <= 0) - holder.apply_status_effect(STATUS_EFFECT_DNA_MELT) - if(message) - to_chat(holder, message) - -//used to update dna UI, UE, and dna.real_name. -/datum/dna/proc/update_dna_identity() - uni_identity = generate_uni_identity() - unique_enzymes = generate_unique_enzymes() - -/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) - if(newblood_type) - blood_type = newblood_type - unique_enzymes = generate_unique_enzymes() - uni_identity = generate_uni_identity() - if(!skip_index) //I hate this - generate_dna_blocks() - features = random_features() - - -/datum/dna/stored //subtype used by brain mob's stored_dna - -/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna. - return - -/datum/dna/stored/remove_mutation(mutation_name) - return - -/datum/dna/stored/check_mutation(mutation_name) - return - -/datum/dna/stored/remove_all_mutations(list/classes, mutadone = FALSE) - return - -/datum/dna/stored/remove_mutation_group(list/group) - return - -/////////////////////////// DNA MOB-PROCS ////////////////////// - -/mob/proc/set_species(datum/species/mrace, icon_update = 1) - return - -/mob/living/brain/set_species(datum/species/mrace, icon_update = 1) - if(mrace) - if(ispath(mrace)) - stored_dna.species = new mrace() - else - stored_dna.species = mrace //not calling any species update procs since we're a brain, not a monkey/human - - -/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) - if(mrace && has_dna()) - var/datum/species/new_race - if(ispath(mrace)) - new_race = new mrace - else if(istype(mrace)) - new_race = mrace - else - return - deathsound = new_race.deathsound - dna.species.on_species_loss(src, new_race, pref_load) - var/datum/species/old_species = dna.species - dna.species = new_race - dna.species.on_species_gain(src, old_species, pref_load) - if(ishuman(src)) - qdel(language_holder) - var/species_holder = initial(mrace.species_language_holder) - language_holder = new species_holder(src) - update_atom_languages() - -/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) - ..() - if(icon_update) - update_body() - update_hair() - update_body_parts() - update_mutations_overlay()// no lizard with human hulk overlay please. - - -/mob/proc/has_dna() - return - -/mob/living/carbon/has_dna() - return dna - - -/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations) -//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc) - if(newfeatures) - dna.features = newfeatures - - if(mrace) - var/datum/species/newrace = new mrace.type - newrace.copy_properties_from(mrace) - set_species(newrace, icon_update=0) - - if(newreal_name) - dna.real_name = newreal_name - dna.generate_unique_enzymes() - - if(newblood_type) - dna.blood_type = newblood_type - - if(ui) - dna.uni_identity = ui - updateappearance(icon_update=0) - - if(LAZYLEN(mutation_index)) - dna.mutation_index = mutation_index.Copy() - if(LAZYLEN(default_mutation_genes)) - dna.default_mutation_genes = default_mutation_genes.Copy() - else - dna.default_mutation_genes = mutation_index.Copy() - domutcheck() - - if(mrace || newfeatures || ui) - update_body() - update_hair() - update_body_parts() - update_mutations_overlay() - - if(LAZYLEN(mutations)) - for(var/M in mutations) - var/datum/mutation/human/HM = M - if(HM.allow_transfer || force_transfer_mutations) - dna.force_give(new HM.type(HM.class, copymut=HM)) //using force_give since it may include exotic mutations that otherwise won't be handled properly - -/mob/living/carbon/proc/create_dna() - dna = new /datum/dna(src) - if(!dna.species) - var/rando_race = pick(GLOB.roundstart_races) - dna.species = new rando_race() - -//proc used to update the mob's appearance after its dna UI has been changed -/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) - if(!has_dna()) - return - - switch(deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 3)) - if(G_MALE) - gender = MALE - if(G_FEMALE) - gender = FEMALE - else - gender = PLURAL - -/mob/living/carbon/human/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) - ..() - var/structure = dna.uni_identity - hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK)) - facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)) - skin_tone = GLOB.skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)] - eye_color = sanitize_hexcolor(getblock(structure, DNA_EYE_COLOR_BLOCK)) - facial_hairstyle = GLOB.facial_hairstyles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), GLOB.facial_hairstyles_list.len)] - hairstyle = GLOB.hairstyles_list[deconstruct_block(getblock(structure, DNA_HAIRSTYLE_BLOCK), GLOB.hairstyles_list.len)] - if(icon_update) - update_body() - update_hair() - if(mutcolor_update) - update_body_parts() - if(mutations_overlay_update) - update_mutations_overlay() - - -/mob/proc/domutcheck() - return - -/mob/living/carbon/domutcheck() - if(!has_dna()) - return - - for(var/mutation in dna.mutation_index) - if(ismob(dna.check_block(mutation))) - return //we got monkeyized/humanized, this mob will be deleted, no need to continue. - - update_mutations_overlay() - -/datum/dna/proc/check_block(mutation) - var/datum/mutation/human/HM = get_mutation(mutation) - if(check_block_string(mutation)) - if(!HM) - . = add_mutation(mutation, MUT_NORMAL) - return - return force_lose(HM) - -//Return the active mutation of a type if there is one -/datum/dna/proc/get_mutation(A) - for(var/datum/mutation/human/HM in mutations) - if(HM.type == A) - return HM - -/datum/dna/proc/check_block_string(mutation) - if((LAZYLEN(mutation_index) > DNA_MUTATION_BLOCKS) || !(mutation in mutation_index)) - return 0 - return is_gene_active(mutation) - -/datum/dna/proc/is_gene_active(mutation) - return (mutation_index[mutation] == GET_SEQUENCE(mutation)) - -/datum/dna/proc/set_se(on=TRUE, datum/mutation/human/HM) - if(!HM || !(HM.type in mutation_index) || (LAZYLEN(mutation_index) < DNA_MUTATION_BLOCKS)) - return - . = TRUE - if(on) - mutation_index[HM.type] = GET_SEQUENCE(HM.type) - default_mutation_genes[HM.type] = mutation_index[HM.type] - else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type]) - mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty) - default_mutation_genes[HM.type] = mutation_index[HM.type] - - -/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob - if(!mutation) - return FALSE - var/mutation_type = mutation - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/M = mutation - mutation_type = M.type - if(!mutation_in_sequence(mutation_type)) //cant activate what we dont have, use add_mutation - return FALSE - add_mutation(mutation, MUT_NORMAL) - return TRUE - -/////////////////////////// DNA HELPER-PROCS ////////////////////////////// - -/proc/getleftblocks(input,blocknumber,blocksize) - if(blocknumber > 1) - return copytext_char(input,1,((blocksize*blocknumber)-(blocksize-1))) - -/proc/getrightblocks(input,blocknumber,blocksize) - if(blocknumber < (length(input)/blocksize)) - return copytext_char(input,blocksize*blocknumber+1,length(input)+1) - -/proc/getblock(input, blocknumber, blocksize=DNA_BLOCK_SIZE) - return copytext_char(input, blocksize*(blocknumber-1)+1, (blocksize*blocknumber)+1) - -/proc/setblock(istring, blocknumber, replacement, blocksize=DNA_BLOCK_SIZE) - if(!istring || !blocknumber || !replacement || !blocksize) - return 0 - return getleftblocks(istring, blocknumber, blocksize) + replacement + getrightblocks(istring, blocknumber, blocksize) - -/datum/dna/proc/mutation_in_sequence(mutation) - if(!mutation) - return - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/HM = mutation - if(HM.type in mutation_index) - return TRUE - else if(mutation in mutation_index) - return TRUE - - -/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) - if(!has_dna()) - return - var/mutation = pick(candidates) - . = dna.add_mutation(mutation) - -/mob/living/carbon/proc/easy_randmut(quality = POSITIVE + NEGATIVE + MINOR_NEGATIVE, scrambled = TRUE, sequence = TRUE, exclude_monkey = TRUE, resilient = NONE) - if(!has_dna()) - return - var/list/mutations = list() - if(quality & POSITIVE) - mutations += GLOB.good_mutations - if(quality & NEGATIVE) - mutations += GLOB.bad_mutations - if(quality & MINOR_NEGATIVE) - mutations += GLOB.not_good_mutations - var/list/possible = list() - for(var/datum/mutation/human/A in mutations) - if((!sequence || dna.mutation_in_sequence(A.type)) && !dna.get_mutation(A.type)) - possible += A.type - if(exclude_monkey) - possible.Remove(RACEMUT) - if(LAZYLEN(possible)) - var/mutation = pick(possible) - . = dna.activate_mutation(mutation) - if(scrambled) - var/datum/mutation/human/HM = dna.get_mutation(mutation) - if(HM) - HM.scrambled = TRUE - if(HM.quality & resilient) - HM.mutadone_proof = TRUE - return TRUE - -/mob/living/carbon/proc/randmuti() - if(!has_dna()) - return - var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS) - var/newdna = setblock(dna.uni_identity, num, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) - dna.uni_identity = newdna - updateappearance(mutations_overlay_update=1) - -/mob/living/carbon/proc/clean_dna() - if(!has_dna()) - return - dna.remove_all_mutations() - -/mob/living/carbon/proc/clean_randmut(list/candidates, difficulty = 2) - clean_dna() - randmut(candidates, difficulty) - -/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, probability) - if(!M.has_dna()) - return 0 - if(se) - for(var/i=1, i<=DNA_MUTATION_BLOCKS, i++) - if(prob(probability)) - M.dna.generate_dna_blocks() - M.domutcheck() - if(ui) - for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) - if(prob(probability)) - M.dna.uni_identity = setblock(M.dna.uni_identity, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) - M.updateappearance(mutations_overlay_update=1) - return 1 - -//value in range 1 to values. values must be greater than 0 -//all arguments assumed to be positive integers -/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE) - var/width = round((16**blocksize)/values) - if(value < 1) - value = 1 - value = (value * width) - rand(1,width) - return num2hex(value, blocksize) - -//value is hex -/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE) - var/width = round((16**blocksize)/values) - value = round(hex2num(value) / width) + 1 - if(value > values) - value = values - return value - -/////////////////////////// DNA HELPER-PROCS - -/mob/living/carbon/human/proc/something_horrible(ignore_stability) - if(!has_dna()) //shouldn't ever happen anyway so it's just in really weird cases - return - if(!ignore_stability && (dna.stability > 0)) - return - var/instability = -dna.stability - dna.remove_all_mutations() - dna.stability = 100 - if(prob(max(70-instability,0))) - switch(rand(0,10)) //not complete and utter death - if(0) - monkeyize() - if(1) - gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) - new/obj/vehicle/ridden/wheelchair(get_turf(src)) //don't buckle, because I can't imagine to plethora of things to go through that could otherwise break - to_chat(src, "My flesh turned into a wheelchair and I can't feel my legs.") - if(2) - corgize() - if(3) - to_chat(src, "Oh, I actually feel quite alright!") - if(4) - to_chat(src, "Oh, I actually feel quite alright!") //you thought - physiology.damage_resistance = -20000 - if(5) - to_chat(src, "Oh, I actually feel quite alright!") - reagents.add_reagent(/datum/reagent/aslimetoxin, 10) - if(6) - apply_status_effect(STATUS_EFFECT_GO_AWAY) - if(7) - to_chat(src, "Oh, I actually feel quite alright!") - ForceContractDisease(new/datum/disease/decloning()) //slow acting, non-viral clone damage based GBS - if(8) - var/list/elligible_organs = list() - for(var/obj/item/organ/O in internal_organs) //make sure we dont get an implant or cavity item - elligible_organs += O - vomit(20, TRUE) - if(elligible_organs.len) - var/obj/item/organ/O = pick(elligible_organs) - O.Remove(src) - visible_message("[src] vomits up their [O.name]!", "You vomit up your [O.name]") //no "vomit up your heart" - O.forceMove(drop_location()) - if(prob(20)) - O.animate_atom_living() - if(9 to 10) - ForceContractDisease(new/datum/disease/gastrolosis()) - to_chat(src, "Oh, I actually feel quite alright!") - else - switch(rand(0,5)) - if(0) - gib() - if(1) - dust() - - if(2) - death() - petrify(INFINITY) - if(3) - if(prob(95)) - var/obj/item/bodypart/BP = get_bodypart(pick(BODY_ZONE_CHEST,BODY_ZONE_HEAD)) - if(BP) - BP.dismember() - else - gib() - else - set_species(/datum/species/dullahan) - if(4) - visible_message("[src]'s skin melts off!", "Your skin melts off!") - spawn_gibs() - set_species(/datum/species/skeleton) - if(prob(90)) - addtimer(CALLBACK(src, .proc/death), 30) - if(mind) - mind.hasSoul = FALSE - if(5) - to_chat(src, "LOOK UP!") - addtimer(CALLBACK(src, .proc/something_horrible_mindmelt), 30) - - -/mob/living/carbon/human/proc/something_horrible_mindmelt() - if(!is_blind()) - var/obj/item/organ/eyes/eyes = locate(/obj/item/organ/eyes) in internal_organs - if(!eyes) - return - eyes.Remove(src) - qdel(eyes) - visible_message("[src] looks up and their eyes melt away!", "='userdanger'>I understand now.") - addtimer(CALLBACK(src, .proc/adjustOrganLoss, ORGAN_SLOT_BRAIN, 200), 20) + +/////////////////////////// DNA DATUM +/datum/dna + var/unique_enzymes + var/uni_identity + var/blood_type + var/datum/species/species = new /datum/species/human //The type of mutant race the player is if applicable (i.e. potato-man) + var/list/features = list("FFF") //first value is mutant color + var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings, + var/list/mutations = list() //All mutations are from now on here + var/list/temporary_mutations = list() //Temporary changes to the UE + var/list/previous = list() //For temporary name/ui/ue/blood_type modifications + var/mob/living/holder + var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block + var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting + var/stability = 100 + var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers. + +/datum/dna/New(mob/living/new_holder) + if(istype(new_holder)) + holder = new_holder + +/datum/dna/Destroy() + if(iscarbon(holder)) + var/mob/living/carbon/cholder = holder + if(cholder.dna == src) + cholder.dna = null + holder = null + + QDEL_NULL(species) + + mutations.Cut() //This only references mutations, just dereference. + temporary_mutations.Cut() //^ + previous.Cut() //^ + + return ..() + +/datum/dna/proc/transfer_identity(mob/living/carbon/destination, transfer_SE = 0) + if(!istype(destination)) + return + destination.dna.unique_enzymes = unique_enzymes + destination.dna.uni_identity = uni_identity + destination.dna.blood_type = blood_type + destination.set_species(species.type, icon_update=0) + destination.dna.features = features.Copy() + destination.dna.real_name = real_name + destination.dna.temporary_mutations = temporary_mutations.Copy() + if(transfer_SE) + destination.dna.mutation_index = mutation_index + destination.dna.default_mutation_genes = default_mutation_genes + +/datum/dna/proc/copy_dna(datum/dna/new_dna) + new_dna.unique_enzymes = unique_enzymes + new_dna.mutation_index = mutation_index + new_dna.default_mutation_genes = default_mutation_genes + new_dna.uni_identity = uni_identity + new_dna.blood_type = blood_type + new_dna.features = features.Copy() + new_dna.species = new species.type + new_dna.real_name = real_name + new_dna.mutations = mutations.Copy() + +//See mutation.dm for what 'class' does. 'time' is time till it removes itself in decimals. 0 for no timer +/datum/dna/proc/add_mutation(mutation, class = MUT_OTHER, time) + var/mutation_type = mutation + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/HM = mutation + mutation_type = HM.type + if(get_mutation(mutation_type)) + return + return force_give(new mutation_type (class, time, copymut = mutation)) + +/datum/dna/proc/remove_mutation(mutation_type) + return force_lose(get_mutation(mutation_type)) + +/datum/dna/proc/check_mutation(mutation_type) + return get_mutation(mutation_type) + +/datum/dna/proc/remove_all_mutations(list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) + remove_mutation_group(mutations, classes, mutadone) + scrambled = FALSE + +/datum/dna/proc/remove_mutation_group(list/group, list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) + if(!group) + return + for(var/datum/mutation/human/HM in group) + if((HM.class in classes) && !(HM.mutadone_proof && mutadone)) + force_lose(HM) + +/datum/dna/proc/generate_uni_identity() + . = "" + var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) + + switch(holder.gender) + if(MALE) + L[DNA_GENDER_BLOCK] = construct_block(G_MALE, 3) + if(FEMALE) + L[DNA_GENDER_BLOCK] = construct_block(G_FEMALE, 3) + else + L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, 3) + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + if(!GLOB.hairstyles_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/hair,GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) + L[DNA_HAIRSTYLE_BLOCK] = construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len) + L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color) + if(!GLOB.facial_hairstyles_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) + L[DNA_FACIAL_HAIRSTYLE_BLOCK] = construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len) + L[DNA_FACIAL_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.facial_hair_color) + L[DNA_SKIN_TONE_BLOCK] = construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len) + L[DNA_EYE_COLOR_BLOCK] = sanitize_hexcolor(H.eye_color) + + for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) + if(L[i]) + . += L[i] + else + . += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters) + return . + +/datum/dna/proc/generate_dna_blocks() + var/bonus + if(species && species.inert_mutation) + bonus = GET_INITIALIZED_MUTATION(species.inert_mutation) + var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus + if(!LAZYLEN(mutations_temp)) + return + mutation_index.Cut() + default_mutation_genes.Cut() + shuffle_inplace(mutations_temp) + if(ismonkey(holder)) + mutations |= new RACEMUT(MUT_NORMAL) + mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT) + else + mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE) + default_mutation_genes[RACEMUT] = mutation_index[RACEMUT] + for(var/i in 2 to DNA_MUTATION_BLOCKS) + var/datum/mutation/human/M = mutations_temp[i] + mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty) + default_mutation_genes[M.type] = mutation_index[M.type] + shuffle_inplace(mutation_index) + +//Used to generate original gene sequences for every mutation +/proc/generate_gene_sequence(length=4) + var/static/list/active_sequences = list("AT","TA","GC","CG") + var/sequence + for(var/i in 1 to length*DNA_SEQUENCE_LENGTH) + sequence += pick(active_sequences) + return sequence + +//Used to create a chipped gene sequence +/proc/create_sequence(mutation, active, difficulty) + if(!difficulty) + var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(mutation) //leaves the possibility to change difficulty mid-round + if(!A) + return + difficulty = A.difficulty + difficulty += rand(-2,4) + var/sequence = GET_SEQUENCE(mutation) + if(active) + return sequence + while(difficulty) + var/randnum = rand(1, length_char(sequence)) + sequence = copytext_char(sequence, 1, randnum) + "X" + copytext_char(sequence, randnum + 1) + difficulty-- + return sequence + +/datum/dna/proc/generate_unique_enzymes() + . = "" + if(istype(holder)) + real_name = holder.real_name + . += md5(holder.real_name) + else + . += random_string(DNA_UNIQUE_ENZYMES_LEN, GLOB.hex_characters) + return . + +/datum/dna/proc/update_ui_block(blocknumber) + if(!blocknumber || !ishuman(holder)) + return + var/mob/living/carbon/human/H = holder + switch(blocknumber) + if(DNA_HAIR_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.hair_color)) + if(DNA_FACIAL_HAIR_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.facial_hair_color)) + if(DNA_SKIN_TONE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len)) + if(DNA_EYE_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.eye_color)) + if(DNA_GENDER_BLOCK) + switch(H.gender) + if(MALE) + setblock(uni_identity, blocknumber, construct_block(G_MALE, 3)) + if(FEMALE) + setblock(uni_identity, blocknumber, construct_block(G_FEMALE, 3)) + else + setblock(uni_identity, blocknumber, construct_block(G_PLURAL, 3)) + if(DNA_FACIAL_HAIRSTYLE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len)) + if(DNA_HAIRSTYLE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len)) + +//Please use add_mutation or activate_mutation instead +/datum/dna/proc/force_give(datum/mutation/human/HM) + if(holder && HM) + if(HM.class == MUT_NORMAL) + set_se(1, HM) + . = HM.on_acquiring(holder) + if(.) + qdel(HM) + update_instability() + +//Use remove_mutation instead +/datum/dna/proc/force_lose(datum/mutation/human/HM) + if(holder && (HM in mutations)) + set_se(0, HM) + . = HM.on_losing(holder) + update_instability(FALSE) + return + +/datum/dna/proc/is_same_as(datum/dna/D) + if(uni_identity == D.uni_identity && mutation_index == D.mutation_index && real_name == D.real_name) + if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) + return 1 + return 0 + +/datum/dna/proc/update_instability(alert=TRUE) + stability = 100 + for(var/datum/mutation/human/M in mutations) + if(M.class == MUT_EXTRA) + stability -= M.instability * GET_MUTATION_STABILIZER(M) + if(holder) + var/message + if(alert) + switch(stability) + if(70 to 90) + message = "You shiver." + if(60 to 69) + message = "You feel cold." + if(40 to 59) + message = "You feel sick." + if(20 to 39) + message = "It feels like your skin is moving." + if(1 to 19) + message = "You can feel your cells burning." + if(-INFINITY to 0) + message = "You can feel your DNA exploding, we need to do something fast!" + if(stability <= 0) + holder.apply_status_effect(STATUS_EFFECT_DNA_MELT) + if(message) + to_chat(holder, message) + +//used to update dna UI, UE, and dna.real_name. +/datum/dna/proc/update_dna_identity() + uni_identity = generate_uni_identity() + unique_enzymes = generate_unique_enzymes() + +/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) + if(newblood_type) + blood_type = newblood_type + unique_enzymes = generate_unique_enzymes() + uni_identity = generate_uni_identity() + if(!skip_index) //I hate this + generate_dna_blocks() + features = random_features() + + +/datum/dna/stored //subtype used by brain mob's stored_dna + +/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna. + return + +/datum/dna/stored/remove_mutation(mutation_name) + return + +/datum/dna/stored/check_mutation(mutation_name) + return + +/datum/dna/stored/remove_all_mutations(list/classes, mutadone = FALSE) + return + +/datum/dna/stored/remove_mutation_group(list/group) + return + +/////////////////////////// DNA MOB-PROCS ////////////////////// + +/mob/proc/set_species(datum/species/mrace, icon_update = 1) + return + +/mob/living/brain/set_species(datum/species/mrace, icon_update = 1) + if(mrace) + if(ispath(mrace)) + stored_dna.species = new mrace() + else + stored_dna.species = mrace //not calling any species update procs since we're a brain, not a monkey/human + + +/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + if(mrace && has_dna()) + var/datum/species/new_race + if(ispath(mrace)) + new_race = new mrace + else if(istype(mrace)) + new_race = mrace + else + return + deathsound = new_race.deathsound + dna.species.on_species_loss(src, new_race, pref_load) + var/datum/species/old_species = dna.species + dna.species = new_race + dna.species.on_species_gain(src, old_species, pref_load) + if(ishuman(src)) + qdel(language_holder) + var/species_holder = initial(mrace.species_language_holder) + language_holder = new species_holder(src) + update_atom_languages() + +/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + ..() + if(icon_update) + update_body() + update_hair() + update_body_parts() + update_mutations_overlay()// no lizard with human hulk overlay please. + + +/mob/proc/has_dna() + return + +/mob/living/carbon/has_dna() + return dna + + +/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations) +//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc) + if(newfeatures) + dna.features = newfeatures + + if(mrace) + var/datum/species/newrace = new mrace.type + newrace.copy_properties_from(mrace) + set_species(newrace, icon_update=0) + + if(newreal_name) + dna.real_name = newreal_name + dna.generate_unique_enzymes() + + if(newblood_type) + dna.blood_type = newblood_type + + if(ui) + dna.uni_identity = ui + updateappearance(icon_update=0) + + if(LAZYLEN(mutation_index)) + dna.mutation_index = mutation_index.Copy() + if(LAZYLEN(default_mutation_genes)) + dna.default_mutation_genes = default_mutation_genes.Copy() + else + dna.default_mutation_genes = mutation_index.Copy() + domutcheck() + + if(mrace || newfeatures || ui) + update_body() + update_hair() + update_body_parts() + update_mutations_overlay() + + if(LAZYLEN(mutations)) + for(var/M in mutations) + var/datum/mutation/human/HM = M + if(HM.allow_transfer || force_transfer_mutations) + dna.force_give(new HM.type(HM.class, copymut=HM)) //using force_give since it may include exotic mutations that otherwise won't be handled properly + +/mob/living/carbon/proc/create_dna() + dna = new /datum/dna(src) + if(!dna.species) + var/rando_race = pick(GLOB.roundstart_races) + dna.species = new rando_race() + +//proc used to update the mob's appearance after its dna UI has been changed +/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) + if(!has_dna()) + return + + switch(deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 3)) + if(G_MALE) + gender = MALE + if(G_FEMALE) + gender = FEMALE + else + gender = PLURAL + +/mob/living/carbon/human/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) + ..() + var/structure = dna.uni_identity + hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK)) + facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)) + skin_tone = GLOB.skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)] + eye_color = sanitize_hexcolor(getblock(structure, DNA_EYE_COLOR_BLOCK)) + facial_hairstyle = GLOB.facial_hairstyles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), GLOB.facial_hairstyles_list.len)] + hairstyle = GLOB.hairstyles_list[deconstruct_block(getblock(structure, DNA_HAIRSTYLE_BLOCK), GLOB.hairstyles_list.len)] + if(icon_update) + update_body() + update_hair() + if(mutcolor_update) + update_body_parts() + if(mutations_overlay_update) + update_mutations_overlay() + + +/mob/proc/domutcheck() + return + +/mob/living/carbon/domutcheck() + if(!has_dna()) + return + + for(var/mutation in dna.mutation_index) + if(ismob(dna.check_block(mutation))) + return //we got monkeyized/humanized, this mob will be deleted, no need to continue. + + update_mutations_overlay() + +/datum/dna/proc/check_block(mutation) + var/datum/mutation/human/HM = get_mutation(mutation) + if(check_block_string(mutation)) + if(!HM) + . = add_mutation(mutation, MUT_NORMAL) + return + return force_lose(HM) + +//Return the active mutation of a type if there is one +/datum/dna/proc/get_mutation(A) + for(var/datum/mutation/human/HM in mutations) + if(HM.type == A) + return HM + +/datum/dna/proc/check_block_string(mutation) + if((LAZYLEN(mutation_index) > DNA_MUTATION_BLOCKS) || !(mutation in mutation_index)) + return 0 + return is_gene_active(mutation) + +/datum/dna/proc/is_gene_active(mutation) + return (mutation_index[mutation] == GET_SEQUENCE(mutation)) + +/datum/dna/proc/set_se(on=TRUE, datum/mutation/human/HM) + if(!HM || !(HM.type in mutation_index) || (LAZYLEN(mutation_index) < DNA_MUTATION_BLOCKS)) + return + . = TRUE + if(on) + mutation_index[HM.type] = GET_SEQUENCE(HM.type) + default_mutation_genes[HM.type] = mutation_index[HM.type] + else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type]) + mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty) + default_mutation_genes[HM.type] = mutation_index[HM.type] + + +/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob + if(!mutation) + return FALSE + var/mutation_type = mutation + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/M = mutation + mutation_type = M.type + if(!mutation_in_sequence(mutation_type)) //cant activate what we dont have, use add_mutation + return FALSE + add_mutation(mutation, MUT_NORMAL) + return TRUE + +/////////////////////////// DNA HELPER-PROCS ////////////////////////////// + +/proc/getleftblocks(input,blocknumber,blocksize) + if(blocknumber > 1) + return copytext_char(input,1,((blocksize*blocknumber)-(blocksize-1))) + +/proc/getrightblocks(input,blocknumber,blocksize) + if(blocknumber < (length(input)/blocksize)) + return copytext_char(input,blocksize*blocknumber+1,length(input)+1) + +/proc/getblock(input, blocknumber, blocksize=DNA_BLOCK_SIZE) + return copytext_char(input, blocksize*(blocknumber-1)+1, (blocksize*blocknumber)+1) + +/proc/setblock(istring, blocknumber, replacement, blocksize=DNA_BLOCK_SIZE) + if(!istring || !blocknumber || !replacement || !blocksize) + return 0 + return getleftblocks(istring, blocknumber, blocksize) + replacement + getrightblocks(istring, blocknumber, blocksize) + +/datum/dna/proc/mutation_in_sequence(mutation) + if(!mutation) + return + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/HM = mutation + if(HM.type in mutation_index) + return TRUE + else if(mutation in mutation_index) + return TRUE + + +/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) + if(!has_dna()) + return + var/mutation = pick(candidates) + . = dna.add_mutation(mutation) + +/mob/living/carbon/proc/easy_randmut(quality = POSITIVE + NEGATIVE + MINOR_NEGATIVE, scrambled = TRUE, sequence = TRUE, exclude_monkey = TRUE, resilient = NONE) + if(!has_dna()) + return + var/list/mutations = list() + if(quality & POSITIVE) + mutations += GLOB.good_mutations + if(quality & NEGATIVE) + mutations += GLOB.bad_mutations + if(quality & MINOR_NEGATIVE) + mutations += GLOB.not_good_mutations + var/list/possible = list() + for(var/datum/mutation/human/A in mutations) + if((!sequence || dna.mutation_in_sequence(A.type)) && !dna.get_mutation(A.type)) + possible += A.type + if(exclude_monkey) + possible.Remove(RACEMUT) + if(LAZYLEN(possible)) + var/mutation = pick(possible) + . = dna.activate_mutation(mutation) + if(scrambled) + var/datum/mutation/human/HM = dna.get_mutation(mutation) + if(HM) + HM.scrambled = TRUE + if(HM.quality & resilient) + HM.mutadone_proof = TRUE + return TRUE + +/mob/living/carbon/proc/randmuti() + if(!has_dna()) + return + var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS) + var/newdna = setblock(dna.uni_identity, num, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) + dna.uni_identity = newdna + updateappearance(mutations_overlay_update=1) + +/mob/living/carbon/proc/clean_dna() + if(!has_dna()) + return + dna.remove_all_mutations() + +/mob/living/carbon/proc/clean_randmut(list/candidates, difficulty = 2) + clean_dna() + randmut(candidates, difficulty) + +/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, probability) + if(!M.has_dna()) + return 0 + if(se) + for(var/i=1, i<=DNA_MUTATION_BLOCKS, i++) + if(prob(probability)) + M.dna.generate_dna_blocks() + M.domutcheck() + if(ui) + for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) + if(prob(probability)) + M.dna.uni_identity = setblock(M.dna.uni_identity, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) + M.updateappearance(mutations_overlay_update=1) + return 1 + +//value in range 1 to values. values must be greater than 0 +//all arguments assumed to be positive integers +/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE) + var/width = round((16**blocksize)/values) + if(value < 1) + value = 1 + value = (value * width) - rand(1,width) + return num2hex(value, blocksize) + +//value is hex +/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE) + var/width = round((16**blocksize)/values) + value = round(hex2num(value) / width) + 1 + if(value > values) + value = values + return value + +/////////////////////////// DNA HELPER-PROCS + +/mob/living/carbon/human/proc/something_horrible(ignore_stability) + if(!has_dna()) //shouldn't ever happen anyway so it's just in really weird cases + return + if(!ignore_stability && (dna.stability > 0)) + return + var/instability = -dna.stability + dna.remove_all_mutations() + dna.stability = 100 + if(prob(max(70-instability,0))) + switch(rand(0,10)) //not complete and utter death + if(0) + monkeyize() + if(1) + gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) + new/obj/vehicle/ridden/wheelchair(get_turf(src)) //don't buckle, because I can't imagine to plethora of things to go through that could otherwise break + to_chat(src, "My flesh turned into a wheelchair and I can't feel my legs.") + if(2) + corgize() + if(3) + to_chat(src, "Oh, I actually feel quite alright!") + if(4) + to_chat(src, "Oh, I actually feel quite alright!") //you thought + physiology.damage_resistance = -20000 + if(5) + to_chat(src, "Oh, I actually feel quite alright!") + reagents.add_reagent(/datum/reagent/aslimetoxin, 10) + if(6) + apply_status_effect(STATUS_EFFECT_GO_AWAY) + if(7) + to_chat(src, "Oh, I actually feel quite alright!") + ForceContractDisease(new/datum/disease/decloning()) //slow acting, non-viral clone damage based GBS + if(8) + var/list/elligible_organs = list() + for(var/obj/item/organ/O in internal_organs) //make sure we dont get an implant or cavity item + elligible_organs += O + vomit(20, TRUE) + if(elligible_organs.len) + var/obj/item/organ/O = pick(elligible_organs) + O.Remove(src) + visible_message("[src] vomits up their [O.name]!", "You vomit up your [O.name]") //no "vomit up your heart" + O.forceMove(drop_location()) + if(prob(20)) + O.animate_atom_living() + if(9 to 10) + ForceContractDisease(new/datum/disease/gastrolosis()) + to_chat(src, "Oh, I actually feel quite alright!") + else + switch(rand(0,5)) + if(0) + gib() + if(1) + dust() + + if(2) + death() + petrify(INFINITY) + if(3) + if(prob(95)) + var/obj/item/bodypart/BP = get_bodypart(pick(BODY_ZONE_CHEST,BODY_ZONE_HEAD)) + if(BP) + BP.dismember() + else + gib() + else + set_species(/datum/species/dullahan) + if(4) + visible_message("[src]'s skin melts off!", "Your skin melts off!") + spawn_gibs() + set_species(/datum/species/skeleton) + if(prob(90)) + addtimer(CALLBACK(src, .proc/death), 30) + if(mind) + mind.hasSoul = FALSE + if(5) + to_chat(src, "LOOK UP!") + addtimer(CALLBACK(src, .proc/something_horrible_mindmelt), 30) + + +/mob/living/carbon/human/proc/something_horrible_mindmelt() + if(!is_blind()) + var/obj/item/organ/eyes/eyes = locate(/obj/item/organ/eyes) in internal_organs + if(!eyes) + return + eyes.Remove(src) + qdel(eyes) + visible_message("[src] looks up and their eyes melt away!", "='userdanger'>I understand now.") + addtimer(CALLBACK(src, .proc/adjustOrganLoss, ORGAN_SLOT_BRAIN, 200), 20) diff --git a/code/datums/forced_movement.dm b/code/datums/forced_movement.dm index 2c649be12b8..551699a0191 100644 --- a/code/datums/forced_movement.dm +++ b/code/datums/forced_movement.dm @@ -1,93 +1,93 @@ -//Just new and forget -/datum/forced_movement - var/atom/movable/victim - var/atom/target - var/last_processed - var/steps_per_tick - var/allow_climbing - var/datum/callback/on_step - var/moved_at_all = FALSE - //as fast as ssfastprocess -/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null) - victim = _victim - target = _target - steps_per_tick = _steps_per_tick - allow_climbing = _allow_climbing - on_step = _on_step - - . = ..() - - if(_victim && _target && _steps_per_tick && !_victim.force_moving) - last_processed = world.time - _victim.force_moving = src - START_PROCESSING(SSfastprocess, src) - else - qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this - -/datum/forced_movement/Destroy() - if(victim.force_moving == src) - victim.force_moving = null - if(moved_at_all) - victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice - STOP_PROCESSING(SSfastprocess, src) - victim = null - target = null - return ..() - -/datum/forced_movement/process() - if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc) - qdel(src) - return - var/steps_to_take = round(steps_per_tick * (world.time - last_processed)) - if(steps_to_take) - for(var/i in 1 to steps_to_take) - if(TryMove()) - moved_at_all = TRUE - if(on_step) - on_step.InvokeAsync() - else - qdel(src) - return - last_processed = world.time - -/datum/forced_movement/proc/TryMove(recursive = FALSE) - if(QDELETED(src)) //Our previous step caused deletion of this datum - return - - var/atom/movable/vic = victim //sanic - var/atom/tar = target - - if(!recursive) - . = step_towards(vic, tar) - - //shit way for getting around corners - if(!.) - if(tar.x > vic.x) - if(step(vic, EAST)) - . = TRUE - else if(tar.x < vic.x) - if(step(vic, WEST)) - . = TRUE - - if(!.) - if(tar.y > vic.y) - if(step(vic, NORTH)) - . = TRUE - else if(tar.y < vic.y) - if(step(vic, SOUTH)) - . = TRUE - - if(!.) - if(recursive) - return FALSE - else - . = TryMove(TRUE) - - . = . && (vic.loc != tar.loc) - -/mob/Bump(atom/A) - . = ..() - if(force_moving && force_moving.allow_climbing && isstructure(A)) - var/obj/structure/S = A - if(S.climbable) - S.do_climb(src) +//Just new and forget +/datum/forced_movement + var/atom/movable/victim + var/atom/target + var/last_processed + var/steps_per_tick + var/allow_climbing + var/datum/callback/on_step + var/moved_at_all = FALSE + //as fast as ssfastprocess +/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null) + victim = _victim + target = _target + steps_per_tick = _steps_per_tick + allow_climbing = _allow_climbing + on_step = _on_step + + . = ..() + + if(_victim && _target && _steps_per_tick && !_victim.force_moving) + last_processed = world.time + _victim.force_moving = src + START_PROCESSING(SSfastprocess, src) + else + qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this + +/datum/forced_movement/Destroy() + if(victim.force_moving == src) + victim.force_moving = null + if(moved_at_all) + victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice + STOP_PROCESSING(SSfastprocess, src) + victim = null + target = null + return ..() + +/datum/forced_movement/process() + if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc) + qdel(src) + return + var/steps_to_take = round(steps_per_tick * (world.time - last_processed)) + if(steps_to_take) + for(var/i in 1 to steps_to_take) + if(TryMove()) + moved_at_all = TRUE + if(on_step) + on_step.InvokeAsync() + else + qdel(src) + return + last_processed = world.time + +/datum/forced_movement/proc/TryMove(recursive = FALSE) + if(QDELETED(src)) //Our previous step caused deletion of this datum + return + + var/atom/movable/vic = victim //sanic + var/atom/tar = target + + if(!recursive) + . = step_towards(vic, tar) + + //shit way for getting around corners + if(!.) + if(tar.x > vic.x) + if(step(vic, EAST)) + . = TRUE + else if(tar.x < vic.x) + if(step(vic, WEST)) + . = TRUE + + if(!.) + if(tar.y > vic.y) + if(step(vic, NORTH)) + . = TRUE + else if(tar.y < vic.y) + if(step(vic, SOUTH)) + . = TRUE + + if(!.) + if(recursive) + return FALSE + else + . = TryMove(TRUE) + + . = . && (vic.loc != tar.loc) + +/mob/Bump(atom/A) + . = ..() + if(force_moving && force_moving.allow_climbing && isstructure(A)) + var/obj/structure/S = A + if(S.climbable) + S.do_climb(src) diff --git a/code/datums/helper_datums/events.dm b/code/datums/helper_datums/events.dm index c9bf2957b97..bbd1220048d 100644 --- a/code/datums/helper_datums/events.dm +++ b/code/datums/helper_datums/events.dm @@ -1,55 +1,55 @@ -/* - * WARRANTY VOID IF CODE USED - */ - - -/datum/events - var/list/events - -/datum/events/New() - ..() - events = new - -/datum/events/Destroy() - for(var/elist in events) - for(var/e in events[elist]) - qdel(e) - events = null - return ..() - -/datum/events/proc/addEventType(event_type as text) - if(!(event_type in events) || !islist(events[event_type])) - events[event_type] = list() - return TRUE - return FALSE - -// Arguments: event_type as text, proc_holder as datum, proc_name as text -// Returns: New event, null on error. -/datum/events/proc/addEvent(event_type as text, datum/callback/cb) - if(!event_type || !cb) - return - addEventType(event_type) - var/list/event = events[event_type] - event += cb - return cb - -// Arguments: event_type as text, any number of additional arguments to pass to event handler -// Returns: null -/datum/events/proc/fireEvent(eventName, ...) - - var/list/event = LAZYACCESS(events,eventName) - if(istype(event)) - for(var/E in event) - var/datum/callback/cb = E - cb.InvokeAsync(arglist(args.Copy(2))) - -// Arguments: event_type as text, E as /datum/event -// Returns: TRUE if event cleared, FALSE on error - -/datum/events/proc/clearEvent(event_type as text, datum/callback/cb) - if(!event_type || !cb) - return FALSE - var/list/event = LAZYACCESS(events,event_type) - event -= cb - qdel(cb) - return TRUE +/* + * WARRANTY VOID IF CODE USED + */ + + +/datum/events + var/list/events + +/datum/events/New() + ..() + events = new + +/datum/events/Destroy() + for(var/elist in events) + for(var/e in events[elist]) + qdel(e) + events = null + return ..() + +/datum/events/proc/addEventType(event_type as text) + if(!(event_type in events) || !islist(events[event_type])) + events[event_type] = list() + return TRUE + return FALSE + +// Arguments: event_type as text, proc_holder as datum, proc_name as text +// Returns: New event, null on error. +/datum/events/proc/addEvent(event_type as text, datum/callback/cb) + if(!event_type || !cb) + return + addEventType(event_type) + var/list/event = events[event_type] + event += cb + return cb + +// Arguments: event_type as text, any number of additional arguments to pass to event handler +// Returns: null +/datum/events/proc/fireEvent(eventName, ...) + + var/list/event = LAZYACCESS(events,eventName) + if(istype(event)) + for(var/E in event) + var/datum/callback/cb = E + cb.InvokeAsync(arglist(args.Copy(2))) + +// Arguments: event_type as text, E as /datum/event +// Returns: TRUE if event cleared, FALSE on error + +/datum/events/proc/clearEvent(event_type as text, datum/callback/cb) + if(!event_type || !cb) + return FALSE + var/list/event = LAZYACCESS(events,event_type) + event -= cb + qdel(cb) + return TRUE diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm index d5cbbec618f..91b8e325661 100644 --- a/code/datums/helper_datums/getrev.dm +++ b/code/datums/helper_datums/getrev.dm @@ -1,128 +1,128 @@ -/datum/getrev - var/commit // git rev-parse HEAD - var/date - var/originmastercommit // git rev-parse origin/master - var/list/testmerge = list() - -/datum/getrev/New() - commit = rustg_git_revparse("HEAD") - if(commit) - date = rustg_git_commit_date(commit) - originmastercommit = rustg_git_revparse("origin/master") - -/datum/getrev/proc/load_tgs_info() - testmerge = world.TgsTestMerges() - var/datum/tgs_revision_information/revinfo = world.TgsRevision() - if(revinfo) - commit = revinfo.commit - originmastercommit = revinfo.origin_commit - date = rustg_git_commit_date(commit) - - // goes to DD log and config_error.txt - log_world(get_log_message()) - -/datum/getrev/proc/get_log_message() - var/list/msg = list() - msg += "Running /tg/ revision: [date]" - if(originmastercommit) - msg += "origin/master: [originmastercommit]" - - for(var/line in testmerge) - var/datum/tgs_revision_information/test_merge/tm = line - msg += "Test merge active of PR #[tm.number] commit [tm.pull_request_commit]" - SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]")) - - if(commit && commit != originmastercommit) - msg += "HEAD: [commit]" - else if(!originmastercommit) - msg += "No commit information" - - return msg.Join("\n") - -/datum/getrev/proc/GetTestMergeInfo(header = TRUE) - if(!testmerge.len) - return "" - . = header ? "The following pull requests are currently test merged:
    " : "" - for(var/line in testmerge) - var/datum/tgs_revision_information/test_merge/tm = line - var/cm = tm.pull_request_commit - var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11)) - if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder)) - continue - . += "#[tm.number][details]
    " - -/client/verb/showrevinfo() - set category = "OOC" - set name = "Show Server Revision" - set desc = "Check the current server code revision" - - var/list/msg = list("") - // Round ID - if(GLOB.round_id) - msg += "Round ID: [GLOB.round_id]" - - msg += "BYOND Version: [world.byond_version].[world.byond_build]" - if(DM_VERSION != world.byond_version || DM_BUILD != world.byond_build) - msg += "Compiled with BYOND Version: [DM_VERSION].[DM_BUILD]" - - // Revision information - var/datum/getrev/revdata = GLOB.revdata - msg += "Server revision compiled on: [revdata.date]" - var/pc = revdata.originmastercommit - if(pc) - msg += "Master commit: [pc]" - if(revdata.testmerge.len) - msg += revdata.GetTestMergeInfo() - if(revdata.commit && revdata.commit != revdata.originmastercommit) - msg += "Local commit: [revdata.commit]" - else if(!pc) - msg += "No commit information" - if(world.TgsAvailable()) - var/datum/tgs_version/version = world.TgsVersion() - msg += "TGS version: [version.raw_parameter]" - var/datum/tgs_version/api_version = world.TgsApiVersion() - msg += "DMAPI version: [api_version.raw_parameter]" - - // Game mode odds - msg += "
    Current Informational Settings:" - msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]" - msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]" - msg += "Enforce Human Authority: [CONFIG_GET(flag/enforce_human_authority)]" - msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]" - msg += "Enforce Continuous Rounds: [length(CONFIG_GET(keyed_list/continuous))] of [config.modes.len] roundtypes" - msg += "Allow Midround Antagonists: [length(CONFIG_GET(keyed_list/midround_antag))] of [config.modes.len] roundtypes" - if(CONFIG_GET(flag/show_game_type_odds)) - var/list/probabilities = CONFIG_GET(keyed_list/probability) - if(SSticker.IsRoundInProgress()) - var/prob_sum = 0 - var/current_odds_differ = FALSE - var/list/probs = list() - var/list/modes = config.gamemode_cache - var/list/min_pop = CONFIG_GET(keyed_list/min_pop) - var/list/max_pop = CONFIG_GET(keyed_list/max_pop) - for(var/mode in modes) - var/datum/game_mode/M = mode - var/ctag = initial(M.config_tag) - if(!(ctag in probabilities)) - continue - if((min_pop[ctag] && (min_pop[ctag] > SSticker.totalPlayersReady)) || (max_pop[ctag] && (max_pop[ctag] < SSticker.totalPlayersReady)) || (initial(M.required_players) > SSticker.totalPlayersReady)) - current_odds_differ = TRUE - continue - probs[ctag] = 1 - prob_sum += probabilities[ctag] - if(current_odds_differ) - msg += "Game Mode Odds for current round:" - for(var/ctag in probs) - if(probabilities[ctag] > 0) - var/percentage = round(probabilities[ctag] / prob_sum * 100, 0.1) - msg += "[ctag] [percentage]%" - - msg += "All Game Mode Odds:" - var/sum = 0 - for(var/ctag in probabilities) - sum += probabilities[ctag] - for(var/ctag in probabilities) - if(probabilities[ctag] > 0) - var/percentage = round(probabilities[ctag] / sum * 100, 0.1) - msg += "[ctag] [percentage]%" - to_chat(src, msg.Join("
    ")) +/datum/getrev + var/commit // git rev-parse HEAD + var/date + var/originmastercommit // git rev-parse origin/master + var/list/testmerge = list() + +/datum/getrev/New() + commit = rustg_git_revparse("HEAD") + if(commit) + date = rustg_git_commit_date(commit) + originmastercommit = rustg_git_revparse("origin/master") + +/datum/getrev/proc/load_tgs_info() + testmerge = world.TgsTestMerges() + var/datum/tgs_revision_information/revinfo = world.TgsRevision() + if(revinfo) + commit = revinfo.commit + originmastercommit = revinfo.origin_commit + date = rustg_git_commit_date(commit) + + // goes to DD log and config_error.txt + log_world(get_log_message()) + +/datum/getrev/proc/get_log_message() + var/list/msg = list() + msg += "Running /tg/ revision: [date]" + if(originmastercommit) + msg += "origin/master: [originmastercommit]" + + for(var/line in testmerge) + var/datum/tgs_revision_information/test_merge/tm = line + msg += "Test merge active of PR #[tm.number] commit [tm.pull_request_commit]" + SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]")) + + if(commit && commit != originmastercommit) + msg += "HEAD: [commit]" + else if(!originmastercommit) + msg += "No commit information" + + return msg.Join("\n") + +/datum/getrev/proc/GetTestMergeInfo(header = TRUE) + if(!testmerge.len) + return "" + . = header ? "The following pull requests are currently test merged:
    " : "" + for(var/line in testmerge) + var/datum/tgs_revision_information/test_merge/tm = line + var/cm = tm.pull_request_commit + var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11)) + if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder)) + continue + . += "#[tm.number][details]
    " + +/client/verb/showrevinfo() + set category = "OOC" + set name = "Show Server Revision" + set desc = "Check the current server code revision" + + var/list/msg = list("") + // Round ID + if(GLOB.round_id) + msg += "Round ID: [GLOB.round_id]" + + msg += "BYOND Version: [world.byond_version].[world.byond_build]" + if(DM_VERSION != world.byond_version || DM_BUILD != world.byond_build) + msg += "Compiled with BYOND Version: [DM_VERSION].[DM_BUILD]" + + // Revision information + var/datum/getrev/revdata = GLOB.revdata + msg += "Server revision compiled on: [revdata.date]" + var/pc = revdata.originmastercommit + if(pc) + msg += "Master commit: [pc]" + if(revdata.testmerge.len) + msg += revdata.GetTestMergeInfo() + if(revdata.commit && revdata.commit != revdata.originmastercommit) + msg += "Local commit: [revdata.commit]" + else if(!pc) + msg += "No commit information" + if(world.TgsAvailable()) + var/datum/tgs_version/version = world.TgsVersion() + msg += "TGS version: [version.raw_parameter]" + var/datum/tgs_version/api_version = world.TgsApiVersion() + msg += "DMAPI version: [api_version.raw_parameter]" + + // Game mode odds + msg += "
    Current Informational Settings:" + msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]" + msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]" + msg += "Enforce Human Authority: [CONFIG_GET(flag/enforce_human_authority)]" + msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]" + msg += "Enforce Continuous Rounds: [length(CONFIG_GET(keyed_list/continuous))] of [config.modes.len] roundtypes" + msg += "Allow Midround Antagonists: [length(CONFIG_GET(keyed_list/midround_antag))] of [config.modes.len] roundtypes" + if(CONFIG_GET(flag/show_game_type_odds)) + var/list/probabilities = CONFIG_GET(keyed_list/probability) + if(SSticker.IsRoundInProgress()) + var/prob_sum = 0 + var/current_odds_differ = FALSE + var/list/probs = list() + var/list/modes = config.gamemode_cache + var/list/min_pop = CONFIG_GET(keyed_list/min_pop) + var/list/max_pop = CONFIG_GET(keyed_list/max_pop) + for(var/mode in modes) + var/datum/game_mode/M = mode + var/ctag = initial(M.config_tag) + if(!(ctag in probabilities)) + continue + if((min_pop[ctag] && (min_pop[ctag] > SSticker.totalPlayersReady)) || (max_pop[ctag] && (max_pop[ctag] < SSticker.totalPlayersReady)) || (initial(M.required_players) > SSticker.totalPlayersReady)) + current_odds_differ = TRUE + continue + probs[ctag] = 1 + prob_sum += probabilities[ctag] + if(current_odds_differ) + msg += "Game Mode Odds for current round:" + for(var/ctag in probs) + if(probabilities[ctag] > 0) + var/percentage = round(probabilities[ctag] / prob_sum * 100, 0.1) + msg += "[ctag] [percentage]%" + + msg += "All Game Mode Odds:" + var/sum = 0 + for(var/ctag in probabilities) + sum += probabilities[ctag] + for(var/ctag in probabilities) + if(probabilities[ctag] > 0) + var/percentage = round(probabilities[ctag] / sum * 100, 0.1) + msg += "[ctag] [percentage]%" + to_chat(src, msg.Join("
    ")) diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index f966ab1e8f9..aafa230b3fd 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -1,166 +1,166 @@ -// teleatom: atom to teleport -// destination: destination to teleport to -// precision: teleport precision (0 is most precise, the default) -// effectin: effect to show right before teleportation -// effectout: effect to show right after teleportation -// asoundin: soundfile to play before teleportation -// asoundout: soundfile to play after teleportation -// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) -// no_effects: disable the default effectin/effectout of sparks -// forced: whether or not to ignore no_teleport -/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE) - // teleporting most effects just deletes them - var/static/list/delete_atoms = typecacheof(list( - /obj/effect, - )) - typecacheof(list( - /obj/effect/dummy/chameleon, - /obj/effect/wisp, - /obj/effect/mob_spawn, - )) - if(delete_atoms[teleatom.type]) - qdel(teleatom) - return FALSE - - // argument handling - // if the precision is not specified, default to 0, but apply BoH penalties - if (isnull(precision)) - precision = 0 - - switch(channel) - if(TELEPORT_CHANNEL_BLUESPACE) - if(istype(teleatom, /obj/item/storage/backpack/holding)) - precision = rand(1,100) - - var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding) - var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache) - if(bagholding.len) - precision = max(rand(1,100)*bagholding.len,100) - if(isliving(teleatom)) - var/mob/living/MM = teleatom - to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") - - // if effects are not specified and not explicitly disabled, sparks - if ((!effectin || !effectout) && !no_effects) - var/datum/effect_system/spark_spread/sparks = new - sparks.set_up(5, 1, teleatom) - if (!effectin) - effectin = sparks - if (!effectout) - effectout = sparks - if(TELEPORT_CHANNEL_QUANTUM) - // if effects are not specified and not explicitly disabled, rainbow sparks - if ((!effectin || !effectout) && !no_effects) - var/datum/effect_system/spark_spread/quantum/sparks = new - sparks.set_up(5, 1, teleatom) - if (!effectin) - effectin = sparks - if (!effectout) - effectout = sparks - - // perform the teleport - var/turf/curturf = get_turf(teleatom) - var/turf/destturf = get_teleport_turf(get_turf(destination), precision) - - if(!destturf || !curturf || destturf.is_transition_turf()) - return FALSE - - var/area/A = get_area(curturf) - var/area/B = get_area(destturf) - if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport)) - return FALSE - - if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf)) - return FALSE - - tele_play_specials(teleatom, curturf, effectin, asoundin) - var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf) - if (success) - log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]") - tele_play_specials(teleatom, destturf, effectout, asoundout) - - if(ismob(teleatom)) - var/mob/M = teleatom - M.cancel_camera() - - return TRUE - -/proc/tele_play_specials(atom/movable/teleatom, atom/location, datum/effect_system/effect, sound) - if (location && !isobserver(teleatom)) - if (sound) - playsound(location, sound, 60, TRUE) - if (effect) - effect.attach(location) - effect.start() - -// Safe location finder -/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE) - if(!zlevels) - if (zlevel) - zlevels = list(zlevel) - else - zlevels = SSmapping.levels_by_trait(ZTRAIT_STATION) - var/cycles = 1000 - for(var/cycle in 1 to cycles) - // DRUNK DIALLING WOOOOOOOOO - var/x = rand(1, world.maxx) - var/y = rand(1, world.maxy) - var/z = pick(zlevels) - var/random_location = locate(x,y,z) - - if(!isfloorturf(random_location)) - continue - var/turf/open/floor/F = random_location - if(!F.air) - continue - - var/datum/gas_mixture/A = F.air - var/list/A_gases = A.gases - var/trace_gases - for(var/id in A_gases) - if(id in GLOB.hardcoded_gases) - continue - trace_gases = TRUE - break - - // Can most things breathe? - if(trace_gases) - continue - if(!(A_gases[/datum/gas/oxygen] && A_gases[/datum/gas/oxygen][MOLES] >= 16)) - continue - if(A_gases[/datum/gas/plasma]) - continue - if(A_gases[/datum/gas/carbon_dioxide] && A_gases[/datum/gas/carbon_dioxide][MOLES] >= 10) - continue - - // Aim for goldilocks temperatures and pressure - if((A.temperature <= 270) || (A.temperature >= 360)) - continue - var/pressure = A.return_pressure() - if((pressure <= 20) || (pressure >= 550)) - continue - - if(extended_safety_checks) - if(islava(F)) //chasms aren't /floor, and so are pre-filtered - var/turf/open/lava/L = F - if(!L.is_safe()) - continue - - // DING! You have passed the gauntlet, and are "probably" safe. - return F - -/proc/get_teleport_turfs(turf/center, precision = 0) - if(!precision) - return list(center) - var/list/posturfs = list() - for(var/turf/T in range(precision,center)) - if(T.is_transition_turf()) - continue // Avoid picking these. - var/area/A = T.loc - if(!A.noteleport) - posturfs.Add(T) - return posturfs - -/proc/get_teleport_turf(turf/center, precision = 0) - var/list/turfs = get_teleport_turfs(center, precision) - if (length(turfs)) - return pick(turfs) +// teleatom: atom to teleport +// destination: destination to teleport to +// precision: teleport precision (0 is most precise, the default) +// effectin: effect to show right before teleportation +// effectout: effect to show right after teleportation +// asoundin: soundfile to play before teleportation +// asoundout: soundfile to play after teleportation +// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) +// no_effects: disable the default effectin/effectout of sparks +// forced: whether or not to ignore no_teleport +/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE) + // teleporting most effects just deletes them + var/static/list/delete_atoms = typecacheof(list( + /obj/effect, + )) - typecacheof(list( + /obj/effect/dummy/chameleon, + /obj/effect/wisp, + /obj/effect/mob_spawn, + )) + if(delete_atoms[teleatom.type]) + qdel(teleatom) + return FALSE + + // argument handling + // if the precision is not specified, default to 0, but apply BoH penalties + if (isnull(precision)) + precision = 0 + + switch(channel) + if(TELEPORT_CHANNEL_BLUESPACE) + if(istype(teleatom, /obj/item/storage/backpack/holding)) + precision = rand(1,100) + + var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding) + var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache) + if(bagholding.len) + precision = max(rand(1,100)*bagholding.len,100) + if(isliving(teleatom)) + var/mob/living/MM = teleatom + to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") + + // if effects are not specified and not explicitly disabled, sparks + if ((!effectin || !effectout) && !no_effects) + var/datum/effect_system/spark_spread/sparks = new + sparks.set_up(5, 1, teleatom) + if (!effectin) + effectin = sparks + if (!effectout) + effectout = sparks + if(TELEPORT_CHANNEL_QUANTUM) + // if effects are not specified and not explicitly disabled, rainbow sparks + if ((!effectin || !effectout) && !no_effects) + var/datum/effect_system/spark_spread/quantum/sparks = new + sparks.set_up(5, 1, teleatom) + if (!effectin) + effectin = sparks + if (!effectout) + effectout = sparks + + // perform the teleport + var/turf/curturf = get_turf(teleatom) + var/turf/destturf = get_teleport_turf(get_turf(destination), precision) + + if(!destturf || !curturf || destturf.is_transition_turf()) + return FALSE + + var/area/A = get_area(curturf) + var/area/B = get_area(destturf) + if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport)) + return FALSE + + if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf)) + return FALSE + + tele_play_specials(teleatom, curturf, effectin, asoundin) + var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf) + if (success) + log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]") + tele_play_specials(teleatom, destturf, effectout, asoundout) + + if(ismob(teleatom)) + var/mob/M = teleatom + M.cancel_camera() + + return TRUE + +/proc/tele_play_specials(atom/movable/teleatom, atom/location, datum/effect_system/effect, sound) + if (location && !isobserver(teleatom)) + if (sound) + playsound(location, sound, 60, TRUE) + if (effect) + effect.attach(location) + effect.start() + +// Safe location finder +/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE) + if(!zlevels) + if (zlevel) + zlevels = list(zlevel) + else + zlevels = SSmapping.levels_by_trait(ZTRAIT_STATION) + var/cycles = 1000 + for(var/cycle in 1 to cycles) + // DRUNK DIALLING WOOOOOOOOO + var/x = rand(1, world.maxx) + var/y = rand(1, world.maxy) + var/z = pick(zlevels) + var/random_location = locate(x,y,z) + + if(!isfloorturf(random_location)) + continue + var/turf/open/floor/F = random_location + if(!F.air) + continue + + var/datum/gas_mixture/A = F.air + var/list/A_gases = A.gases + var/trace_gases + for(var/id in A_gases) + if(id in GLOB.hardcoded_gases) + continue + trace_gases = TRUE + break + + // Can most things breathe? + if(trace_gases) + continue + if(!(A_gases[/datum/gas/oxygen] && A_gases[/datum/gas/oxygen][MOLES] >= 16)) + continue + if(A_gases[/datum/gas/plasma]) + continue + if(A_gases[/datum/gas/carbon_dioxide] && A_gases[/datum/gas/carbon_dioxide][MOLES] >= 10) + continue + + // Aim for goldilocks temperatures and pressure + if((A.temperature <= 270) || (A.temperature >= 360)) + continue + var/pressure = A.return_pressure() + if((pressure <= 20) || (pressure >= 550)) + continue + + if(extended_safety_checks) + if(islava(F)) //chasms aren't /floor, and so are pre-filtered + var/turf/open/lava/L = F + if(!L.is_safe()) + continue + + // DING! You have passed the gauntlet, and are "probably" safe. + return F + +/proc/get_teleport_turfs(turf/center, precision = 0) + if(!precision) + return list(center) + var/list/posturfs = list() + for(var/turf/T in range(precision,center)) + if(T.is_transition_turf()) + continue // Avoid picking these. + var/area/A = T.loc + if(!A.noteleport) + posturfs.Add(T) + return posturfs + +/proc/get_teleport_turf(turf/center, precision = 0) + var/list/turfs = get_teleport_turfs(center, precision) + if (length(turfs)) + return pick(turfs) diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index bf9b250a22e..7ec84cca5d0 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -1,466 +1,466 @@ -#define HOLOPAD_MAX_DIAL_TIME 200 - -#define HOLORECORD_DELAY "delay" -#define HOLORECORD_SAY "say" -#define HOLORECORD_SOUND "sound" -#define HOLORECORD_LANGUAGE "lang" -#define HOLORECORD_PRESET "preset" -#define HOLORECORD_RENAME "rename" - -#define HOLORECORD_MAX_LENGTH 200 - -/mob/camera/ai_eye/remote/holo/setLoc() - . = ..() - var/obj/machinery/holopad/H = origin - H?.move_hologram(eye_user, loc) - -/obj/machinery/holopad/remove_eye_control(mob/living/user) - if(user.client) - user.reset_perspective(null) - user.remote_control = null - -//this datum manages it's own references - -/datum/holocall - var/mob/living/user //the one that called - var/obj/machinery/holopad/calling_holopad //the one that sent the call - var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null) - var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered - - var/mob/camera/ai_eye/remote/holo/eye //user's eye, once connected - var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected - var/datum/action/innate/end_holocall/hangup //hangup action - - var/call_start_time - var/head_call = FALSE //calls from a head of staff autoconnect, if the receiving pad is not secure. - -//creates a holocall made by `caller` from `calling_pad` to `callees` -/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE) - call_start_time = world.time - user = caller - calling_pad.outgoing_call = src - calling_holopad = calling_pad - head_call = elevated_access - dialed_holopads = list() - - for(var/I in callees) - var/obj/machinery/holopad/H = I - if(!QDELETED(H) && H.is_operational()) - dialed_holopads += H - if(head_call) - if(H.secure) - calling_pad.say("Auto-connection refused, falling back to call mode.") - H.say("Incoming call.") - else - H.say("Incoming connection.") - else - H.say("Incoming call.") - LAZYADD(H.holo_calls, src) - - if(!dialed_holopads.len) - calling_pad.say("Connection failure.") - qdel(src) - return - - testing("Holocall started") - -//cleans up ALL references :) -/datum/holocall/Destroy() - QDEL_NULL(hangup) - - if(!QDELETED(eye)) - QDEL_NULL(eye) - - if(connected_holopad && !QDELETED(hologram)) - hologram = null - connected_holopad.clear_holo(user) - - user = null - - //Hologram survived holopad destro - if(!QDELETED(hologram)) - hologram.HC = null - QDEL_NULL(hologram) - - for(var/I in dialed_holopads) - var/obj/machinery/holopad/H = I - LAZYREMOVE(H.holo_calls, src) - dialed_holopads.Cut() - - if(calling_holopad) - calling_holopad.calling = FALSE - calling_holopad.outgoing_call = null - calling_holopad.SetLightsAndPower() - calling_holopad = null - if(connected_holopad) - connected_holopad.SetLightsAndPower() - connected_holopad = null - - testing("Holocall destroyed") - - return ..() - -//Gracefully disconnects a holopad `H` from a call. Pads not in the call are ignored. Notifies participants of the disconnection -/datum/holocall/proc/Disconnect(obj/machinery/holopad/H) - testing("Holocall disconnect") - if(H == connected_holopad) - var/area/A = get_area(connected_holopad) - calling_holopad.say("[A] holopad disconnected.") - else if(H == calling_holopad && connected_holopad) - connected_holopad.say("[user] disconnected.") - - ConnectionFailure(H, TRUE) - -//Forcefully disconnects a holopad `H` from a call. Pads not in the call are ignored. -/datum/holocall/proc/ConnectionFailure(obj/machinery/holopad/H, graceful = FALSE) - testing("Holocall connection failure: graceful [graceful]") - if(H == connected_holopad || H == calling_holopad) - if(!graceful && H != calling_holopad) - calling_holopad.say("Connection failure.") - qdel(src) - return - - LAZYREMOVE(H.holo_calls, src) - dialed_holopads -= H - if(!dialed_holopads.len) - if(graceful) - calling_holopad.say("Call rejected.") - testing("No recipients, terminating") - qdel(src) - -//Answers a call made to a holopad `H` which cannot be the calling holopad. Pads not in the call are ignored -/datum/holocall/proc/Answer(obj/machinery/holopad/H) - testing("Holocall answer") - if(H == calling_holopad) - CRASH("How cute, a holopad tried to answer itself.") - - if(!(H in dialed_holopads)) - return - - if(connected_holopad) - CRASH("Multi-connection holocall") - - for(var/I in dialed_holopads) - if(I == H) - continue - Disconnect(I) - - for(var/I in H.holo_calls) - var/datum/holocall/HC = I - if(HC != src) - HC.Disconnect(H) - - connected_holopad = H - - if(!Check()) - return - - calling_holopad.calling = FALSE - hologram = H.activate_holo(user) - hologram.HC = src - - //eyeobj code is horrid, this is the best copypasta I could make - eye = new - eye.origin = H - eye.eye_initialized = TRUE - eye.eye_user = user - eye.name = "Camera Eye ([user.name])" - user.remote_control = eye - user.reset_perspective(eye) - eye.setLoc(H.loc) - - hangup = new(eye, src) - hangup.Grant(user) - playsound(H, 'sound/machines/ping.ogg', 100) - H.say("Connection established.") - -//Checks the validity of a holocall and qdels itself if it's not. Returns TRUE if valid, FALSE otherwise -/datum/holocall/proc/Check() - for(var/I in dialed_holopads) - var/obj/machinery/holopad/H = I - if(!H.is_operational()) - ConnectionFailure(H) - - if(QDELETED(src)) - return FALSE - - . = !QDELETED(user) && !user.incapacitated() && !QDELETED(calling_holopad) && calling_holopad.is_operational() && user.loc == calling_holopad.loc - - if(.) - if(!connected_holopad) - . = world.time < (call_start_time + HOLOPAD_MAX_DIAL_TIME) - if(!.) - calling_holopad.say("No answer received.") - - if(!.) - testing("Holocall Check fail") - qdel(src) - -/datum/action/innate/end_holocall - name = "End Holocall" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' - button_icon_state = "camera_off" - var/datum/holocall/hcall - -/datum/action/innate/end_holocall/New(Target, datum/holocall/HC) - ..() - hcall = HC - -/datum/action/innate/end_holocall/Activate() - hcall.Disconnect(hcall.calling_holopad) - - -//RECORDS -/datum/holorecord - var/caller_name = "Unknown" //Caller name - var/image/caller_image - var/list/entries = list() - var/language = /datum/language/common //Initial language, can be changed by HOLORECORD_LANGUAGE entries - -/datum/holorecord/proc/set_caller_image(mob/user) - var/olddir = user.dir - user.setDir(SOUTH) - caller_image = image(user) - user.setDir(olddir) - -/obj/item/disk/holodisk - name = "holorecord disk" - desc = "Stores recorder holocalls." - icon_state = "holodisk" - obj_flags = UNIQUE_RENAME - custom_materials = list(/datum/material/iron = 100, /datum/material/glass = 100) - var/datum/holorecord/record - //Preset variables - var/preset_image_type - var/preset_record_text - -/obj/item/disk/holodisk/Initialize(mapload) - . = ..() - if(preset_record_text) - build_record() - -/obj/item/disk/holodisk/Destroy() - QDEL_NULL(record) - return ..() - -/obj/item/disk/holodisk/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/disk/holodisk)) - var/obj/item/disk/holodisk/holodiskOriginal = W - if (holodiskOriginal.record) - if (!record) - record = new - record.caller_name = holodiskOriginal.record.caller_name - record.caller_image = holodiskOriginal.record.caller_image - record.entries = holodiskOriginal.record.entries.Copy() - record.language = holodiskOriginal.record.language - to_chat(user, "You copy the record from [holodiskOriginal] to [src] by connecting the ports!") - name = holodiskOriginal.name - else - to_chat(user, "[holodiskOriginal] has no record on it!") - ..() - -/obj/item/disk/holodisk/proc/build_record() - record = new - var/list/lines = splittext(preset_record_text,"\n") - for(var/line in lines) - var/prepared_line = trim(line) - if(!length(prepared_line)) - continue - var/splitpoint = findtext(prepared_line," ") - if(!splitpoint) - continue - var/command = copytext(prepared_line, 1, splitpoint) - var/value = copytext(prepared_line, splitpoint + length(prepared_line[splitpoint])) - switch(command) - if("DELAY") - var/delay_value = text2num(value) - if(!delay_value) - continue - record.entries += list(list(HOLORECORD_DELAY,delay_value)) - if("NAME") - if(!record.caller_name) - record.caller_name = value - else - record.entries += list(list(HOLORECORD_RENAME,value)) - if("SAY") - record.entries += list(list(HOLORECORD_SAY,value)) - if("SOUND") - record.entries += list(list(HOLORECORD_SOUND,value)) - if("LANGUAGE") - var/lang_type = text2path(value) - if(ispath(lang_type,/datum/language)) - record.entries += list(list(HOLORECORD_LANGUAGE,lang_type)) - if("PRESET") - var/preset_type = text2path(value) - if(ispath(preset_type,/datum/preset_holoimage)) - record.entries += list(list(HOLORECORD_PRESET,preset_type)) - if(!preset_image_type) - record.caller_image = image('icons/mob/animal.dmi',"old") - else - var/datum/preset_holoimage/H = new preset_image_type - record.caller_image = H.build_image() - -//These build caller image from outfit and some additional data, for use by mappers for ruin holorecords -/datum/preset_holoimage - var/nonhuman_mobtype //Fill this if you just want something nonhuman - var/outfit_type - var/species_type = /datum/species/human - -/datum/preset_holoimage/proc/build_image() - if(nonhuman_mobtype) - var/mob/living/L = nonhuman_mobtype - . = image(initial(L.icon),initial(L.icon_state)) - else - var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy("HOLODISK_PRESET") - if(species_type) - mannequin.set_species(species_type) - if(outfit_type) - mannequin.equipOutfit(outfit_type,TRUE) - mannequin.setDir(SOUTH) - COMPILE_OVERLAYS(mannequin) - . = image(mannequin) - unset_busy_human_dummy("HOLODISK_PRESET") - -/obj/item/disk/holodisk/example - preset_image_type = /datum/preset_holoimage/clown - preset_record_text = {" - NAME Clown - DELAY 10 - SAY Why did the chaplain cross the maint ? - DELAY 20 - SAY He wanted to get to the other side! - SOUND clownstep - DELAY 30 - LANGUAGE /datum/language/narsie - SAY Helped him get there! - DELAY 10 - SAY ALSO IM SECRETLY A GORILLA - DELAY 10 - PRESET /datum/preset_holoimage/gorilla - NAME Gorilla - LANGUAGE /datum/language/common - SAY OOGA - DELAY 20"} - -/datum/preset_holoimage/engineer - outfit_type = /datum/outfit/job/engineer - -/datum/preset_holoimage/engineer/rig - outfit_type = /datum/outfit/job/engineer/gloved/rig - -/datum/preset_holoimage/engineer/ce - outfit_type = /datum/outfit/job/ce - -/datum/preset_holoimage/engineer/ce/rig - outfit_type = /datum/outfit/job/engineer/gloved/rig - -/datum/preset_holoimage/engineer/atmos - outfit_type = /datum/outfit/job/atmos - -/datum/preset_holoimage/engineer/atmos/rig - outfit_type = /datum/outfit/job/engineer/gloved/rig - -/datum/preset_holoimage/researcher - outfit_type = /datum/outfit/job/scientist - -/datum/preset_holoimage/captain - outfit_type = /datum/outfit/job/captain - -/datum/preset_holoimage/nanotrasenprivatesecurity - outfit_type = /datum/outfit/nanotrasensoldiercorpse2 - -/datum/preset_holoimage/gorilla - nonhuman_mobtype = /mob/living/simple_animal/hostile/gorilla - -/datum/preset_holoimage/corgi - nonhuman_mobtype = /mob/living/simple_animal/pet/dog/corgi - -/datum/preset_holoimage/clown - outfit_type = /datum/outfit/job/clown - -/obj/item/disk/holodisk/donutstation/whiteship - name = "Blackbox Print-out #DS024" - desc = "A holodisk containing the last viable recording of DS024's blackbox." - preset_image_type = /datum/preset_holoimage/engineer/ce - preset_record_text = {" - NAME Geysr Shorthalt - SAY Engine renovations complete and the ships been loaded. We all ready? - DELAY 25 - PRESET /datum/preset_holoimage/engineer - NAME Jacob Ullman - SAY Lets blow this popsicle stand of a station. - DELAY 20 - PRESET /datum/preset_holoimage/engineer/atmos - NAME Lindsey Cuffler - SAY Uh, sir? Shouldn't we call for a secondary shuttle? The bluespace drive on this thing made an awfully weird noise when we jumped here.. - DELAY 30 - PRESET /datum/preset_holoimage/engineer/ce - NAME Geysr Shorthalt - SAY Pah! Ship techie at the dock said to give it a good few kicks if it started acting up, let me just.. - DELAY 25 - SOUND punch - SOUND sparks - DELAY 10 - SOUND punch - SOUND sparks - DELAY 10 - SOUND punch - SOUND sparks - SOUND warpspeed - DELAY 15 - PRESET /datum/preset_holoimage/engineer/atmos - NAME Lindsey Cuffler - SAY Uhh.. is it supposed to be doing that?? - DELAY 15 - PRESET /datum/preset_holoimage/engineer/ce - NAME Geysr Shorthalt - SAY See? Working as intended. Now, are we all ready? - DELAY 10 - PRESET /datum/preset_holoimage/engineer - NAME Jacob Ullman - SAY Is it supposed to be glowing like that? - DELAY 20 - SOUND explosion - - "} - -/obj/item/disk/holodisk/ruin/snowengieruin - name = "Blackbox Print-out #EB412" - desc = "A holodisk containing the last moments of EB412. There's a bloody fingerprint on it." - preset_image_type = /datum/preset_holoimage/engineer - preset_record_text = {" - NAME Dave Tundrale - SAY Maria, how's Build? - DELAY 10 - NAME Maria Dell - PRESET /datum/preset_holoimage/engineer/atmos - SAY It's fine, don't worry. I've got Plastic on it. And frankly, i'm kinda busy with, the, uhhm, incinerator. - DELAY 30 - NAME Dave Tundrale - PRESET /datum/preset_holoimage/engineer - SAY Aight, wonderful. The science mans been kinda shit though. No RCDs- - DELAY 20 - NAME Maria Dell - PRESET /datum/preset_holoimage/engineer/atmos - SAY Enough about your RCDs. They're not even that important, just bui- - DELAY 15 - SOUND explosion - DELAY 10 - SAY Oh, shit! - DELAY 10 - PRESET /datum/preset_holoimage/engineer/atmos/rig - LANGUAGE /datum/language/narsie - NAME Unknown - SAY RISE, MY LORD!! - DELAY 10 - LANGUAGE /datum/language/common - NAME Plastic - PRESET /datum/preset_holoimage/engineer/rig - SAY Fuck, fuck, fuck! - DELAY 20 - SAY It's loose! CALL THE FUCKING SHUTT- - DELAY 10 - PRESET /datum/preset_holoimage/corgi - NAME Blackbox Automated Message - SAY Connection lost. Dumping audio logs to disk. - DELAY 50"} +#define HOLOPAD_MAX_DIAL_TIME 200 + +#define HOLORECORD_DELAY "delay" +#define HOLORECORD_SAY "say" +#define HOLORECORD_SOUND "sound" +#define HOLORECORD_LANGUAGE "lang" +#define HOLORECORD_PRESET "preset" +#define HOLORECORD_RENAME "rename" + +#define HOLORECORD_MAX_LENGTH 200 + +/mob/camera/ai_eye/remote/holo/setLoc() + . = ..() + var/obj/machinery/holopad/H = origin + H?.move_hologram(eye_user, loc) + +/obj/machinery/holopad/remove_eye_control(mob/living/user) + if(user.client) + user.reset_perspective(null) + user.remote_control = null + +//this datum manages it's own references + +/datum/holocall + var/mob/living/user //the one that called + var/obj/machinery/holopad/calling_holopad //the one that sent the call + var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null) + var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered + + var/mob/camera/ai_eye/remote/holo/eye //user's eye, once connected + var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected + var/datum/action/innate/end_holocall/hangup //hangup action + + var/call_start_time + var/head_call = FALSE //calls from a head of staff autoconnect, if the receiving pad is not secure. + +//creates a holocall made by `caller` from `calling_pad` to `callees` +/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE) + call_start_time = world.time + user = caller + calling_pad.outgoing_call = src + calling_holopad = calling_pad + head_call = elevated_access + dialed_holopads = list() + + for(var/I in callees) + var/obj/machinery/holopad/H = I + if(!QDELETED(H) && H.is_operational()) + dialed_holopads += H + if(head_call) + if(H.secure) + calling_pad.say("Auto-connection refused, falling back to call mode.") + H.say("Incoming call.") + else + H.say("Incoming connection.") + else + H.say("Incoming call.") + LAZYADD(H.holo_calls, src) + + if(!dialed_holopads.len) + calling_pad.say("Connection failure.") + qdel(src) + return + + testing("Holocall started") + +//cleans up ALL references :) +/datum/holocall/Destroy() + QDEL_NULL(hangup) + + if(!QDELETED(eye)) + QDEL_NULL(eye) + + if(connected_holopad && !QDELETED(hologram)) + hologram = null + connected_holopad.clear_holo(user) + + user = null + + //Hologram survived holopad destro + if(!QDELETED(hologram)) + hologram.HC = null + QDEL_NULL(hologram) + + for(var/I in dialed_holopads) + var/obj/machinery/holopad/H = I + LAZYREMOVE(H.holo_calls, src) + dialed_holopads.Cut() + + if(calling_holopad) + calling_holopad.calling = FALSE + calling_holopad.outgoing_call = null + calling_holopad.SetLightsAndPower() + calling_holopad = null + if(connected_holopad) + connected_holopad.SetLightsAndPower() + connected_holopad = null + + testing("Holocall destroyed") + + return ..() + +//Gracefully disconnects a holopad `H` from a call. Pads not in the call are ignored. Notifies participants of the disconnection +/datum/holocall/proc/Disconnect(obj/machinery/holopad/H) + testing("Holocall disconnect") + if(H == connected_holopad) + var/area/A = get_area(connected_holopad) + calling_holopad.say("[A] holopad disconnected.") + else if(H == calling_holopad && connected_holopad) + connected_holopad.say("[user] disconnected.") + + ConnectionFailure(H, TRUE) + +//Forcefully disconnects a holopad `H` from a call. Pads not in the call are ignored. +/datum/holocall/proc/ConnectionFailure(obj/machinery/holopad/H, graceful = FALSE) + testing("Holocall connection failure: graceful [graceful]") + if(H == connected_holopad || H == calling_holopad) + if(!graceful && H != calling_holopad) + calling_holopad.say("Connection failure.") + qdel(src) + return + + LAZYREMOVE(H.holo_calls, src) + dialed_holopads -= H + if(!dialed_holopads.len) + if(graceful) + calling_holopad.say("Call rejected.") + testing("No recipients, terminating") + qdel(src) + +//Answers a call made to a holopad `H` which cannot be the calling holopad. Pads not in the call are ignored +/datum/holocall/proc/Answer(obj/machinery/holopad/H) + testing("Holocall answer") + if(H == calling_holopad) + CRASH("How cute, a holopad tried to answer itself.") + + if(!(H in dialed_holopads)) + return + + if(connected_holopad) + CRASH("Multi-connection holocall") + + for(var/I in dialed_holopads) + if(I == H) + continue + Disconnect(I) + + for(var/I in H.holo_calls) + var/datum/holocall/HC = I + if(HC != src) + HC.Disconnect(H) + + connected_holopad = H + + if(!Check()) + return + + calling_holopad.calling = FALSE + hologram = H.activate_holo(user) + hologram.HC = src + + //eyeobj code is horrid, this is the best copypasta I could make + eye = new + eye.origin = H + eye.eye_initialized = TRUE + eye.eye_user = user + eye.name = "Camera Eye ([user.name])" + user.remote_control = eye + user.reset_perspective(eye) + eye.setLoc(H.loc) + + hangup = new(eye, src) + hangup.Grant(user) + playsound(H, 'sound/machines/ping.ogg', 100) + H.say("Connection established.") + +//Checks the validity of a holocall and qdels itself if it's not. Returns TRUE if valid, FALSE otherwise +/datum/holocall/proc/Check() + for(var/I in dialed_holopads) + var/obj/machinery/holopad/H = I + if(!H.is_operational()) + ConnectionFailure(H) + + if(QDELETED(src)) + return FALSE + + . = !QDELETED(user) && !user.incapacitated() && !QDELETED(calling_holopad) && calling_holopad.is_operational() && user.loc == calling_holopad.loc + + if(.) + if(!connected_holopad) + . = world.time < (call_start_time + HOLOPAD_MAX_DIAL_TIME) + if(!.) + calling_holopad.say("No answer received.") + + if(!.) + testing("Holocall Check fail") + qdel(src) + +/datum/action/innate/end_holocall + name = "End Holocall" + icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon_state = "camera_off" + var/datum/holocall/hcall + +/datum/action/innate/end_holocall/New(Target, datum/holocall/HC) + ..() + hcall = HC + +/datum/action/innate/end_holocall/Activate() + hcall.Disconnect(hcall.calling_holopad) + + +//RECORDS +/datum/holorecord + var/caller_name = "Unknown" //Caller name + var/image/caller_image + var/list/entries = list() + var/language = /datum/language/common //Initial language, can be changed by HOLORECORD_LANGUAGE entries + +/datum/holorecord/proc/set_caller_image(mob/user) + var/olddir = user.dir + user.setDir(SOUTH) + caller_image = image(user) + user.setDir(olddir) + +/obj/item/disk/holodisk + name = "holorecord disk" + desc = "Stores recorder holocalls." + icon_state = "holodisk" + obj_flags = UNIQUE_RENAME + custom_materials = list(/datum/material/iron = 100, /datum/material/glass = 100) + var/datum/holorecord/record + //Preset variables + var/preset_image_type + var/preset_record_text + +/obj/item/disk/holodisk/Initialize(mapload) + . = ..() + if(preset_record_text) + build_record() + +/obj/item/disk/holodisk/Destroy() + QDEL_NULL(record) + return ..() + +/obj/item/disk/holodisk/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/disk/holodisk)) + var/obj/item/disk/holodisk/holodiskOriginal = W + if (holodiskOriginal.record) + if (!record) + record = new + record.caller_name = holodiskOriginal.record.caller_name + record.caller_image = holodiskOriginal.record.caller_image + record.entries = holodiskOriginal.record.entries.Copy() + record.language = holodiskOriginal.record.language + to_chat(user, "You copy the record from [holodiskOriginal] to [src] by connecting the ports!") + name = holodiskOriginal.name + else + to_chat(user, "[holodiskOriginal] has no record on it!") + ..() + +/obj/item/disk/holodisk/proc/build_record() + record = new + var/list/lines = splittext(preset_record_text,"\n") + for(var/line in lines) + var/prepared_line = trim(line) + if(!length(prepared_line)) + continue + var/splitpoint = findtext(prepared_line," ") + if(!splitpoint) + continue + var/command = copytext(prepared_line, 1, splitpoint) + var/value = copytext(prepared_line, splitpoint + length(prepared_line[splitpoint])) + switch(command) + if("DELAY") + var/delay_value = text2num(value) + if(!delay_value) + continue + record.entries += list(list(HOLORECORD_DELAY,delay_value)) + if("NAME") + if(!record.caller_name) + record.caller_name = value + else + record.entries += list(list(HOLORECORD_RENAME,value)) + if("SAY") + record.entries += list(list(HOLORECORD_SAY,value)) + if("SOUND") + record.entries += list(list(HOLORECORD_SOUND,value)) + if("LANGUAGE") + var/lang_type = text2path(value) + if(ispath(lang_type,/datum/language)) + record.entries += list(list(HOLORECORD_LANGUAGE,lang_type)) + if("PRESET") + var/preset_type = text2path(value) + if(ispath(preset_type,/datum/preset_holoimage)) + record.entries += list(list(HOLORECORD_PRESET,preset_type)) + if(!preset_image_type) + record.caller_image = image('icons/mob/animal.dmi',"old") + else + var/datum/preset_holoimage/H = new preset_image_type + record.caller_image = H.build_image() + +//These build caller image from outfit and some additional data, for use by mappers for ruin holorecords +/datum/preset_holoimage + var/nonhuman_mobtype //Fill this if you just want something nonhuman + var/outfit_type + var/species_type = /datum/species/human + +/datum/preset_holoimage/proc/build_image() + if(nonhuman_mobtype) + var/mob/living/L = nonhuman_mobtype + . = image(initial(L.icon),initial(L.icon_state)) + else + var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy("HOLODISK_PRESET") + if(species_type) + mannequin.set_species(species_type) + if(outfit_type) + mannequin.equipOutfit(outfit_type,TRUE) + mannequin.setDir(SOUTH) + COMPILE_OVERLAYS(mannequin) + . = image(mannequin) + unset_busy_human_dummy("HOLODISK_PRESET") + +/obj/item/disk/holodisk/example + preset_image_type = /datum/preset_holoimage/clown + preset_record_text = {" + NAME Clown + DELAY 10 + SAY Why did the chaplain cross the maint ? + DELAY 20 + SAY He wanted to get to the other side! + SOUND clownstep + DELAY 30 + LANGUAGE /datum/language/narsie + SAY Helped him get there! + DELAY 10 + SAY ALSO IM SECRETLY A GORILLA + DELAY 10 + PRESET /datum/preset_holoimage/gorilla + NAME Gorilla + LANGUAGE /datum/language/common + SAY OOGA + DELAY 20"} + +/datum/preset_holoimage/engineer + outfit_type = /datum/outfit/job/engineer + +/datum/preset_holoimage/engineer/rig + outfit_type = /datum/outfit/job/engineer/gloved/rig + +/datum/preset_holoimage/engineer/ce + outfit_type = /datum/outfit/job/ce + +/datum/preset_holoimage/engineer/ce/rig + outfit_type = /datum/outfit/job/engineer/gloved/rig + +/datum/preset_holoimage/engineer/atmos + outfit_type = /datum/outfit/job/atmos + +/datum/preset_holoimage/engineer/atmos/rig + outfit_type = /datum/outfit/job/engineer/gloved/rig + +/datum/preset_holoimage/researcher + outfit_type = /datum/outfit/job/scientist + +/datum/preset_holoimage/captain + outfit_type = /datum/outfit/job/captain + +/datum/preset_holoimage/nanotrasenprivatesecurity + outfit_type = /datum/outfit/nanotrasensoldiercorpse2 + +/datum/preset_holoimage/gorilla + nonhuman_mobtype = /mob/living/simple_animal/hostile/gorilla + +/datum/preset_holoimage/corgi + nonhuman_mobtype = /mob/living/simple_animal/pet/dog/corgi + +/datum/preset_holoimage/clown + outfit_type = /datum/outfit/job/clown + +/obj/item/disk/holodisk/donutstation/whiteship + name = "Blackbox Print-out #DS024" + desc = "A holodisk containing the last viable recording of DS024's blackbox." + preset_image_type = /datum/preset_holoimage/engineer/ce + preset_record_text = {" + NAME Geysr Shorthalt + SAY Engine renovations complete and the ships been loaded. We all ready? + DELAY 25 + PRESET /datum/preset_holoimage/engineer + NAME Jacob Ullman + SAY Lets blow this popsicle stand of a station. + DELAY 20 + PRESET /datum/preset_holoimage/engineer/atmos + NAME Lindsey Cuffler + SAY Uh, sir? Shouldn't we call for a secondary shuttle? The bluespace drive on this thing made an awfully weird noise when we jumped here.. + DELAY 30 + PRESET /datum/preset_holoimage/engineer/ce + NAME Geysr Shorthalt + SAY Pah! Ship techie at the dock said to give it a good few kicks if it started acting up, let me just.. + DELAY 25 + SOUND punch + SOUND sparks + DELAY 10 + SOUND punch + SOUND sparks + DELAY 10 + SOUND punch + SOUND sparks + SOUND warpspeed + DELAY 15 + PRESET /datum/preset_holoimage/engineer/atmos + NAME Lindsey Cuffler + SAY Uhh.. is it supposed to be doing that?? + DELAY 15 + PRESET /datum/preset_holoimage/engineer/ce + NAME Geysr Shorthalt + SAY See? Working as intended. Now, are we all ready? + DELAY 10 + PRESET /datum/preset_holoimage/engineer + NAME Jacob Ullman + SAY Is it supposed to be glowing like that? + DELAY 20 + SOUND explosion + + "} + +/obj/item/disk/holodisk/ruin/snowengieruin + name = "Blackbox Print-out #EB412" + desc = "A holodisk containing the last moments of EB412. There's a bloody fingerprint on it." + preset_image_type = /datum/preset_holoimage/engineer + preset_record_text = {" + NAME Dave Tundrale + SAY Maria, how's Build? + DELAY 10 + NAME Maria Dell + PRESET /datum/preset_holoimage/engineer/atmos + SAY It's fine, don't worry. I've got Plastic on it. And frankly, i'm kinda busy with, the, uhhm, incinerator. + DELAY 30 + NAME Dave Tundrale + PRESET /datum/preset_holoimage/engineer + SAY Aight, wonderful. The science mans been kinda shit though. No RCDs- + DELAY 20 + NAME Maria Dell + PRESET /datum/preset_holoimage/engineer/atmos + SAY Enough about your RCDs. They're not even that important, just bui- + DELAY 15 + SOUND explosion + DELAY 10 + SAY Oh, shit! + DELAY 10 + PRESET /datum/preset_holoimage/engineer/atmos/rig + LANGUAGE /datum/language/narsie + NAME Unknown + SAY RISE, MY LORD!! + DELAY 10 + LANGUAGE /datum/language/common + NAME Plastic + PRESET /datum/preset_holoimage/engineer/rig + SAY Fuck, fuck, fuck! + DELAY 20 + SAY It's loose! CALL THE FUCKING SHUTT- + DELAY 10 + PRESET /datum/preset_holoimage/corgi + NAME Blackbox Automated Message + SAY Connection lost. Dumping audio logs to disk. + DELAY 50"} diff --git a/code/datums/http.dm b/code/datums/http.dm index 8cac03d2bd0..2a9b53f131a 100644 --- a/code/datums/http.dm +++ b/code/datums/http.dm @@ -1,74 +1,74 @@ -/datum/http_request - var/id - var/in_progress = FALSE - - var/method - var/body - var/headers - var/url - - var/_raw_response - -/datum/http_request/proc/prepare(method, url, body = "", list/headers) - if (!length(headers)) - headers = "" - else - headers = json_encode(headers) - - src.method = method - src.url = url - src.body = body - src.headers = headers - -/datum/http_request/proc/execute_blocking() - _raw_response = rustg_http_request_blocking(method, url, body, headers) - -/datum/http_request/proc/begin_async() - if (in_progress) - CRASH("Attempted to re-use a request object.") - - id = rustg_http_request_async(method, url, body, headers) - - if (isnull(text2num(id))) - stack_trace("Proc error: [id]") - _raw_response = "Proc error: [id]" - else - in_progress = TRUE - -/datum/http_request/proc/is_complete() - if (isnull(id)) - return TRUE - - if (!in_progress) - return TRUE - - var/r = rustg_http_check_request(id) - - if (r == RUSTG_JOB_NO_RESULTS_YET) - return FALSE - else - _raw_response = r - in_progress = FALSE - return TRUE - -/datum/http_request/proc/into_response() - var/datum/http_response/R = new() - - try - var/list/L = json_decode(_raw_response) - R.status_code = L["status_code"] - R.headers = L["headers"] - R.body = L["body"] - catch - R.errored = TRUE - R.error = _raw_response - - return R - -/datum/http_response - var/status_code - var/body - var/list/headers - - var/errored = FALSE - var/error +/datum/http_request + var/id + var/in_progress = FALSE + + var/method + var/body + var/headers + var/url + + var/_raw_response + +/datum/http_request/proc/prepare(method, url, body = "", list/headers) + if (!length(headers)) + headers = "" + else + headers = json_encode(headers) + + src.method = method + src.url = url + src.body = body + src.headers = headers + +/datum/http_request/proc/execute_blocking() + _raw_response = rustg_http_request_blocking(method, url, body, headers) + +/datum/http_request/proc/begin_async() + if (in_progress) + CRASH("Attempted to re-use a request object.") + + id = rustg_http_request_async(method, url, body, headers) + + if (isnull(text2num(id))) + stack_trace("Proc error: [id]") + _raw_response = "Proc error: [id]" + else + in_progress = TRUE + +/datum/http_request/proc/is_complete() + if (isnull(id)) + return TRUE + + if (!in_progress) + return TRUE + + var/r = rustg_http_check_request(id) + + if (r == RUSTG_JOB_NO_RESULTS_YET) + return FALSE + else + _raw_response = r + in_progress = FALSE + return TRUE + +/datum/http_request/proc/into_response() + var/datum/http_response/R = new() + + try + var/list/L = json_decode(_raw_response) + R.status_code = L["status_code"] + R.headers = L["headers"] + R.body = L["body"] + catch + R.errored = TRUE + R.error = _raw_response + + return R + +/datum/http_response + var/status_code + var/body + var/list/headers + + var/errored = FALSE + var/error diff --git a/code/datums/hud.dm b/code/datums/hud.dm index 5c07d65c98a..9b3f6438e7d 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -1,148 +1,148 @@ -/* HUD DATUMS */ - -GLOBAL_LIST_EMPTY(all_huds) - -//GLOBAL HUD LIST -GLOBAL_LIST_INIT(huds, list( - DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(), - DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(), - DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(), - DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(), - DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(), - DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(), - DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(), - DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(), - DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(), - DATA_HUD_FAN = new/datum/atom_hud/data/human/fan_hud(), - ANTAG_HUD_CULT = new/datum/atom_hud/antag(), - ANTAG_HUD_REV = new/datum/atom_hud/antag(), - ANTAG_HUD_OPS = new/datum/atom_hud/antag(), - ANTAG_HUD_WIZ = new/datum/atom_hud/antag(), - ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(), - ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_DEVIL = new/datum/atom_hud/antag(), - ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(), - ANTAG_HUD_GANGSTER = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_SPACECOP = new/datum/atom_hud/antag(), - ANTAG_HUD_HERETIC = new/datum/atom_hud/antag/hidden() - )) - -/datum/atom_hud - var/list/atom/hudatoms = list() //list of all atoms which display this hud - var/list/hudusers = list() //list with all mobs who can see the hud - var/list/hud_icons = list() //these will be the indexes for the atom's hud_list - - var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them - var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet - var/hud_exceptions = list() // huduser = list(ofatomswiththeirhudhidden) - aka everyone hates targeted invisiblity - -/datum/atom_hud/New() - GLOB.all_huds += src - -/datum/atom_hud/Destroy() - for(var/v in hudusers) - remove_hud_from(v) - for(var/v in hudatoms) - remove_from_hud(v) - GLOB.all_huds -= src - return ..() - -/datum/atom_hud/proc/remove_hud_from(mob/M) - if(!M || !hudusers[M]) - return - if (!--hudusers[M]) - hudusers -= M - if(queued_to_see[M]) - queued_to_see -= M - else - for(var/atom/A in hudatoms) - remove_from_single_hud(M, A) - -/datum/atom_hud/proc/remove_from_hud(atom/A) - if(!A) - return FALSE - for(var/mob/M in hudusers) - remove_from_single_hud(M, A) - hudatoms -= A - return TRUE - -/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client - if(!M || !M.client || !A) - return - for(var/i in hud_icons) - M.client.images -= A.hud_list[i] - -/datum/atom_hud/proc/add_hud_to(mob/M) - if(!M) - return - if(!hudusers[M]) - hudusers[M] = 1 - if(next_time_allowed[M] > world.time) - if(!queued_to_see[M]) - addtimer(CALLBACK(src, .proc/show_hud_images_after_cooldown, M), next_time_allowed[M] - world.time) - queued_to_see[M] = TRUE - else - next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN - for(var/atom/A in hudatoms) - add_to_single_hud(M, A) - else - hudusers[M]++ - -/datum/atom_hud/proc/hide_single_atomhud_from(hud_user,hidden_atom) - if(hudusers[hud_user]) - remove_from_single_hud(hud_user,hidden_atom) - if(!hud_exceptions[hud_user]) - hud_exceptions[hud_user] = list(hidden_atom) - else - hud_exceptions[hud_user] += hidden_atom - -/datum/atom_hud/proc/unhide_single_atomhud_from(hud_user,hidden_atom) - hud_exceptions[hud_user] -= hidden_atom - if(hudusers[hud_user]) - add_to_single_hud(hud_user,hidden_atom) - -/datum/atom_hud/proc/show_hud_images_after_cooldown(M) - if(queued_to_see[M]) - queued_to_see -= M - next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN - for(var/atom/A in hudatoms) - add_to_single_hud(M, A) - -/datum/atom_hud/proc/add_to_hud(atom/A) - if(!A) - return FALSE - hudatoms |= A - for(var/mob/M in hudusers) - if(!queued_to_see[M]) - add_to_single_hud(M, A) - return TRUE - -/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client - if(!M || !M.client || !A) - return - for(var/i in hud_icons) - if(A.hud_list[i] && (!hud_exceptions[M] || !(A in hud_exceptions[M]))) - M.client.images |= A.hud_list[i] - -//MOB PROCS -/mob/proc/reload_huds() - for(var/datum/atom_hud/hud in GLOB.all_huds) - if(hud && hud.hudusers[src]) - for(var/atom/A in hud.hudatoms) - hud.add_to_single_hud(src, A) - -/mob/dead/new_player/reload_huds() - return - -/mob/proc/add_click_catcher() - client.screen += client.void - -/mob/dead/new_player/add_click_catcher() - return +/* HUD DATUMS */ + +GLOBAL_LIST_EMPTY(all_huds) + +//GLOBAL HUD LIST +GLOBAL_LIST_INIT(huds, list( + DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(), + DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(), + DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(), + DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(), + DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(), + DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(), + DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(), + DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(), + DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(), + DATA_HUD_FAN = new/datum/atom_hud/data/human/fan_hud(), + ANTAG_HUD_CULT = new/datum/atom_hud/antag(), + ANTAG_HUD_REV = new/datum/atom_hud/antag(), + ANTAG_HUD_OPS = new/datum/atom_hud/antag(), + ANTAG_HUD_WIZ = new/datum/atom_hud/antag(), + ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(), + ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_DEVIL = new/datum/atom_hud/antag(), + ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(), + ANTAG_HUD_GANGSTER = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_SPACECOP = new/datum/atom_hud/antag(), + ANTAG_HUD_HERETIC = new/datum/atom_hud/antag/hidden() + )) + +/datum/atom_hud + var/list/atom/hudatoms = list() //list of all atoms which display this hud + var/list/hudusers = list() //list with all mobs who can see the hud + var/list/hud_icons = list() //these will be the indexes for the atom's hud_list + + var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them + var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet + var/hud_exceptions = list() // huduser = list(ofatomswiththeirhudhidden) - aka everyone hates targeted invisiblity + +/datum/atom_hud/New() + GLOB.all_huds += src + +/datum/atom_hud/Destroy() + for(var/v in hudusers) + remove_hud_from(v) + for(var/v in hudatoms) + remove_from_hud(v) + GLOB.all_huds -= src + return ..() + +/datum/atom_hud/proc/remove_hud_from(mob/M) + if(!M || !hudusers[M]) + return + if (!--hudusers[M]) + hudusers -= M + if(queued_to_see[M]) + queued_to_see -= M + else + for(var/atom/A in hudatoms) + remove_from_single_hud(M, A) + +/datum/atom_hud/proc/remove_from_hud(atom/A) + if(!A) + return FALSE + for(var/mob/M in hudusers) + remove_from_single_hud(M, A) + hudatoms -= A + return TRUE + +/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client + if(!M || !M.client || !A) + return + for(var/i in hud_icons) + M.client.images -= A.hud_list[i] + +/datum/atom_hud/proc/add_hud_to(mob/M) + if(!M) + return + if(!hudusers[M]) + hudusers[M] = 1 + if(next_time_allowed[M] > world.time) + if(!queued_to_see[M]) + addtimer(CALLBACK(src, .proc/show_hud_images_after_cooldown, M), next_time_allowed[M] - world.time) + queued_to_see[M] = TRUE + else + next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN + for(var/atom/A in hudatoms) + add_to_single_hud(M, A) + else + hudusers[M]++ + +/datum/atom_hud/proc/hide_single_atomhud_from(hud_user,hidden_atom) + if(hudusers[hud_user]) + remove_from_single_hud(hud_user,hidden_atom) + if(!hud_exceptions[hud_user]) + hud_exceptions[hud_user] = list(hidden_atom) + else + hud_exceptions[hud_user] += hidden_atom + +/datum/atom_hud/proc/unhide_single_atomhud_from(hud_user,hidden_atom) + hud_exceptions[hud_user] -= hidden_atom + if(hudusers[hud_user]) + add_to_single_hud(hud_user,hidden_atom) + +/datum/atom_hud/proc/show_hud_images_after_cooldown(M) + if(queued_to_see[M]) + queued_to_see -= M + next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN + for(var/atom/A in hudatoms) + add_to_single_hud(M, A) + +/datum/atom_hud/proc/add_to_hud(atom/A) + if(!A) + return FALSE + hudatoms |= A + for(var/mob/M in hudusers) + if(!queued_to_see[M]) + add_to_single_hud(M, A) + return TRUE + +/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client + if(!M || !M.client || !A) + return + for(var/i in hud_icons) + if(A.hud_list[i] && (!hud_exceptions[M] || !(A in hud_exceptions[M]))) + M.client.images |= A.hud_list[i] + +//MOB PROCS +/mob/proc/reload_huds() + for(var/datum/atom_hud/hud in GLOB.all_huds) + if(hud && hud.hudusers[src]) + for(var/atom/A in hud.hudatoms) + hud.add_to_single_hud(src, A) + +/mob/dead/new_player/reload_huds() + return + +/mob/proc/add_click_catcher() + client.screen += client.void + +/mob/dead/new_player/add_click_catcher() + return diff --git a/code/datums/map_config.dm b/code/datums/map_config.dm index 1901557681b..9e6916054b7 100644 --- a/code/datums/map_config.dm +++ b/code/datums/map_config.dm @@ -1,143 +1,143 @@ -//used for holding information about unique properties of maps -//feed it json files that match the datum layout -//defaults to box -// -Cyberboss - -/datum/map_config - // Metadata - var/config_filename = "_maps/metastation.json" - var/defaulted = TRUE // set to FALSE by LoadConfig() succeeding - // Config from maps.txt - var/config_max_users = 0 - var/config_min_users = 0 - var/voteweight = 1 - var/votable = FALSE - - // Config actually from the JSON - should default to Meta - var/map_name = "Meta Station" - var/map_path = "map_files/MetaStation" - var/map_file = "MetaStation.dmm" - - var/traits = null - var/space_ruin_levels = 7 - var/space_empty_levels = 1 - - var/minetype = "lavaland" - - var/allow_custom_shuttles = TRUE - var/shuttles = list( - "cargo" = "cargo_box", - "ferry" = "ferry_fancy", - "whiteship" = "whiteship_box", - "emergency" = "emergency_box") - -/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE) - var/datum/map_config/config = new - if (default_to_box) - return config - if (!config.LoadConfig(filename, error_if_missing)) - qdel(config) - config = new /datum/map_config // Fall back to Box - if (delete_after) - fdel(filename) - return config - -#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; } -/datum/map_config/proc/LoadConfig(filename, error_if_missing) - if(!fexists(filename)) - if(error_if_missing) - log_world("map_config not found: [filename]") - return - - var/json = file(filename) - if(!json) - log_world("Could not open map_config: [filename]") - return - - json = file2text(json) - if(!json) - log_world("map_config is not text: [filename]") - return - - json = json_decode(json) - if(!json) - log_world("map_config is not json: [filename]") - return - - config_filename = filename - - CHECK_EXISTS("map_name") - map_name = json["map_name"] - CHECK_EXISTS("map_path") - map_path = json["map_path"] - - map_file = json["map_file"] - // "map_file": "MetaStation.dmm" - if (istext(map_file)) - if (!fexists("_maps/[map_path]/[map_file]")) - log_world("Map file ([map_path]/[map_file]) does not exist!") - return - // "map_file": ["Lower.dmm", "Upper.dmm"] - else if (islist(map_file)) - for (var/file in map_file) - if (!fexists("_maps/[map_path]/[file]")) - log_world("Map file ([map_path]/[file]) does not exist!") - return - else - log_world("map_file missing from json!") - return - - if (islist(json["shuttles"])) - var/list/L = json["shuttles"] - for(var/key in L) - var/value = L[key] - shuttles[key] = value - else if ("shuttles" in json) - log_world("map_config shuttles is not a list!") - return - - traits = json["traits"] - // "traits": [{"Linkage": "Cross"}, {"Space Ruins": true}] - if (islist(traits)) - // "Station" is set by default, but it's assumed if you're setting - // traits you want to customize which level is cross-linked - for (var/level in traits) - if (!(ZTRAIT_STATION in level)) - level[ZTRAIT_STATION] = TRUE - // "traits": null or absent -> default - else if (!isnull(traits)) - log_world("map_config traits is not a list!") - return - - var/temp = json["space_ruin_levels"] - if (isnum(temp)) - space_ruin_levels = temp - else if (!isnull(temp)) - log_world("map_config space_ruin_levels is not a number!") - return - - temp = json["space_empty_levels"] - if (isnum(temp)) - space_empty_levels = temp - else if (!isnull(temp)) - log_world("map_config space_empty_levels is not a number!") - return - - if ("minetype" in json) - minetype = json["minetype"] - - allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE - - defaulted = FALSE - return TRUE -#undef CHECK_EXISTS - -/datum/map_config/proc/GetFullMapPaths() - if (istext(map_file)) - return list("_maps/[map_path]/[map_file]") - . = list() - for (var/file in map_file) - . += "_maps/[map_path]/[file]" - -/datum/map_config/proc/MakeNextMap() - return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json") +//used for holding information about unique properties of maps +//feed it json files that match the datum layout +//defaults to box +// -Cyberboss + +/datum/map_config + // Metadata + var/config_filename = "_maps/metastation.json" + var/defaulted = TRUE // set to FALSE by LoadConfig() succeeding + // Config from maps.txt + var/config_max_users = 0 + var/config_min_users = 0 + var/voteweight = 1 + var/votable = FALSE + + // Config actually from the JSON - should default to Meta + var/map_name = "Meta Station" + var/map_path = "map_files/MetaStation" + var/map_file = "MetaStation.dmm" + + var/traits = null + var/space_ruin_levels = 7 + var/space_empty_levels = 1 + + var/minetype = "lavaland" + + var/allow_custom_shuttles = TRUE + var/shuttles = list( + "cargo" = "cargo_box", + "ferry" = "ferry_fancy", + "whiteship" = "whiteship_box", + "emergency" = "emergency_box") + +/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE) + var/datum/map_config/config = new + if (default_to_box) + return config + if (!config.LoadConfig(filename, error_if_missing)) + qdel(config) + config = new /datum/map_config // Fall back to Box + if (delete_after) + fdel(filename) + return config + +#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; } +/datum/map_config/proc/LoadConfig(filename, error_if_missing) + if(!fexists(filename)) + if(error_if_missing) + log_world("map_config not found: [filename]") + return + + var/json = file(filename) + if(!json) + log_world("Could not open map_config: [filename]") + return + + json = file2text(json) + if(!json) + log_world("map_config is not text: [filename]") + return + + json = json_decode(json) + if(!json) + log_world("map_config is not json: [filename]") + return + + config_filename = filename + + CHECK_EXISTS("map_name") + map_name = json["map_name"] + CHECK_EXISTS("map_path") + map_path = json["map_path"] + + map_file = json["map_file"] + // "map_file": "MetaStation.dmm" + if (istext(map_file)) + if (!fexists("_maps/[map_path]/[map_file]")) + log_world("Map file ([map_path]/[map_file]) does not exist!") + return + // "map_file": ["Lower.dmm", "Upper.dmm"] + else if (islist(map_file)) + for (var/file in map_file) + if (!fexists("_maps/[map_path]/[file]")) + log_world("Map file ([map_path]/[file]) does not exist!") + return + else + log_world("map_file missing from json!") + return + + if (islist(json["shuttles"])) + var/list/L = json["shuttles"] + for(var/key in L) + var/value = L[key] + shuttles[key] = value + else if ("shuttles" in json) + log_world("map_config shuttles is not a list!") + return + + traits = json["traits"] + // "traits": [{"Linkage": "Cross"}, {"Space Ruins": true}] + if (islist(traits)) + // "Station" is set by default, but it's assumed if you're setting + // traits you want to customize which level is cross-linked + for (var/level in traits) + if (!(ZTRAIT_STATION in level)) + level[ZTRAIT_STATION] = TRUE + // "traits": null or absent -> default + else if (!isnull(traits)) + log_world("map_config traits is not a list!") + return + + var/temp = json["space_ruin_levels"] + if (isnum(temp)) + space_ruin_levels = temp + else if (!isnull(temp)) + log_world("map_config space_ruin_levels is not a number!") + return + + temp = json["space_empty_levels"] + if (isnum(temp)) + space_empty_levels = temp + else if (!isnull(temp)) + log_world("map_config space_empty_levels is not a number!") + return + + if ("minetype" in json) + minetype = json["minetype"] + + allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE + + defaulted = FALSE + return TRUE +#undef CHECK_EXISTS + +/datum/map_config/proc/GetFullMapPaths() + if (istext(map_file)) + return list("_maps/[map_path]/[map_file]") + . = list() + for (var/file in map_file) + . += "_maps/[map_path]/[file]" + +/datum/map_config/proc/MakeNextMap() + return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json") diff --git a/code/datums/mind.dm b/code/datums/mind.dm index d0764ded1e4..343fd692756 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -1,825 +1,825 @@ -/* Note from Carnie: - The way datum/mind stuff works has been changed a lot. - Minds now represent IC characters rather than following a client around constantly. - - Guidelines for using minds properly: - - - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! - ghost.mind is however used as a reference to the ghost's corpse - - - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) - the existing mind of the old mob should be transfered to the new mob like so: - - mind.transfer_to(new_mob) - - - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. - By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing - the player. - - - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. - - - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting - a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. - - new_mob.key = key - - The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple! - However if you want that mind to have any special properties like being a traitor etc you will have to do that - yourself. - -*/ - -/datum/mind - var/key - var/name //replaces mob/var/original_name - var/ghostname //replaces name for observers name if set - var/mob/living/current - var/active = 0 - - var/memory - - var/assigned_role - var/special_role - var/list/restricted_roles = list() - - var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. - - var/linglink - var/datum/martial_art/martial_art - var/static/default_martial_art = new/datum/martial_art - var/miming = FALSE // Mime's vow of silence - var/list/antag_datums - var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state - var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD - var/damnation_type = 0 - var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src - var/hasSoul = TRUE // If false, renders the character unable to sell their soul. - var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this. - - var/mob/living/enslaved_to //If this mind's master is another mob (i.e. adamantine golems) - var/datum/language_holder/language_holder - var/unconvertable = FALSE - var/late_joiner = FALSE - - var/last_death = 0 - - var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator - - var/list/learned_recipes //List of learned recipe TYPES. - - ///List of skills the user has received a reward for. Should not be used to keep track of currently known skills. Lazy list because it shouldnt be filled often - var/list/skills_rewarded - ///Assoc list of skills. Use SKILL_LVL to access level, and SKILL_EXP to access skill's exp. - var/list/known_skills = list() - ///What character we spawned in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not - var/mob/original_character - ///Skill multiplier, adjusts how much xp you get/loose from adjust_xp. Dont override it directly, add your reason to experience_multiplier_reasons and use that as a key to put your value in there. - var/experience_multiplier = 1 - ///Skill multiplier list, just slap your multiplier change onto this with the type it is coming from as key. - var/list/experience_multiplier_reasons = list() - -/datum/mind/New(key) - src.key = key - soulOwner = src - martial_art = default_martial_art - init_known_skills() - -/datum/mind/Destroy() - SSticker.minds -= src - if(islist(antag_datums)) - QDEL_LIST(antag_datums) - current = null - soulOwner = null - return ..() - -/datum/mind/proc/get_language_holder() - if(!language_holder) - language_holder = new (src) - return language_holder - -/datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0) - original_character = null - if(current) // remove ourself from our old body's mind variable - current.mind = null - UnregisterSignal(current, COMSIG_MOB_DEATH) - SStgui.on_transfer(current, new_character) - - if(key) - if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours - new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless. - else - key = new_character.key - - if(new_character.mind) //disassociate any mind currently in our new body's mind variable - new_character.mind.current = null - - var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list - var/mob/living/old_current = current - if(current) - current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one - current = new_character //associate ourself with our new body - new_character.mind = src //and associate our new body with ourself - for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body - var/datum/antagonist/A = a - A.on_body_transfer(old_current, current) - if(iscarbon(new_character)) - var/mob/living/carbon/C = new_character - C.last_mind = src - transfer_antag_huds(hud_to_transfer) //inherit the antag HUD - transfer_actions(new_character) - transfer_martial_arts(new_character) - RegisterSignal(new_character, COMSIG_MOB_DEATH, .proc/set_death_time) - if(active || force_key_move) - new_character.key = key //now transfer the key to link the client to our new body - if(new_character.client) - LAZYCLEARLIST(new_character.client.recent_examines) - current.update_atom_languages() - -/datum/mind/proc/init_known_skills() - for (var/type in GLOB.skill_types) - known_skills[type] = list(SKILL_LEVEL_NONE, 0) - -///Return the amount of EXP needed to go to the next level. Returns 0 if max level -/datum/mind/proc/exp_needed_to_level_up(skill) - var/lvl = update_skill_level(skill) - if (lvl >= length(SKILL_EXP_LIST)) //If we're already past the last exp threshold - return 0 - return SKILL_EXP_LIST[lvl+1] - known_skills[skill][SKILL_EXP] - -///Adjust experience of a specific skill -/datum/mind/proc/adjust_experience(skill, amt, silent = FALSE, force_old_level = 0) - var/datum/skill/S = GetSkillRef(skill) - var/old_level = force_old_level ? force_old_level : known_skills[skill][SKILL_LVL] //Get current level of the S skill - experience_multiplier = initial(experience_multiplier) - for(var/key in experience_multiplier_reasons) - experience_multiplier += experience_multiplier_reasons[key] - known_skills[skill][SKILL_EXP] = max(0, known_skills[skill][SKILL_EXP] + amt*experience_multiplier) //Update exp. Prevent going below 0 - known_skills[skill][SKILL_LVL] = update_skill_level(skill)//Check what the current skill level is based on that skill's exp - if(silent) - return - if(known_skills[skill][SKILL_LVL] > old_level) - S.level_gained(src, known_skills[skill][SKILL_LVL], old_level) - else if(known_skills[skill][SKILL_LVL] < old_level) - S.level_lost(src, known_skills[skill][SKILL_LVL], old_level) - -///Set experience of a specific skill to a number -/datum/mind/proc/set_experience(skill, amt, silent = FALSE) - var/old_level = known_skills[skill][SKILL_EXP] - known_skills[skill][SKILL_EXP] = amt - adjust_experience(skill, 0, silent, old_level) //Make a call to adjust_experience to handle updating level - -///Set level of a specific skill -/datum/mind/proc/set_level(skill, newlevel, silent = FALSE) - var/oldlevel = get_skill_level(skill) - var/difference = SKILL_EXP_LIST[newlevel] - SKILL_EXP_LIST[oldlevel] - adjust_experience(skill, difference, silent) - -///Check what the current skill level is based on that skill's exp -/datum/mind/proc/update_skill_level(skill) - var/i = 0 - for (var/exp in SKILL_EXP_LIST) - i ++ - if (known_skills[skill][SKILL_EXP] >= SKILL_EXP_LIST[i]) - continue - return i - 1 //Return level based on the last exp requirement that we were greater than - return i //If we had greater EXP than even the last exp threshold, we return the last level - -///Gets the skill's singleton and returns the result of its get_skill_modifier -/datum/mind/proc/get_skill_modifier(skill, modifier) - var/datum/skill/S = GetSkillRef(skill) - return S.get_skill_modifier(modifier, known_skills[skill][SKILL_LVL]) - -///Gets the player's current level number from the relevant skill -/datum/mind/proc/get_skill_level(skill) - return known_skills[skill][SKILL_LVL] - -///Gets the player's current exp from the relevant skill -/datum/mind/proc/get_skill_exp(skill) - return known_skills[skill][SKILL_EXP] - -/datum/mind/proc/get_skill_level_name(skill) - var/level = get_skill_level(skill) - return SSskills.level_names[level] - -/datum/mind/proc/print_levels(user) - var/list/shown_skills = list() - for(var/i in known_skills) - if(known_skills[i][SKILL_LVL] > SKILL_LEVEL_NONE) //Do we actually have a level in this? - shown_skills += i - if(!length(shown_skills)) - to_chat(user, "You don't seem to have any particularly outstanding skills.") - return - var/msg = "*---------*\nYour skills\n" - for(var/i in shown_skills) - var/datum/skill/the_skill = i - msg += "[initial(the_skill.name)] - [get_skill_level_name(the_skill)]\n" - msg += "" - to_chat(user, msg) - -/datum/mind/proc/set_death_time() - last_death = world.time - -/datum/mind/proc/store_memory(new_text) - var/newlength = length_char(memory) + length_char(new_text) - if (newlength > MAX_MESSAGE_LEN * 100) - memory = copytext_char(memory, -newlength-MAX_MESSAGE_LEN * 100) - memory += "[new_text]
    " - -/datum/mind/proc/wipe_memory() - memory = null - -// Datum antag mind procs -/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) - if(!datum_type_or_instance) - return - var/datum/antagonist/A - if(!ispath(datum_type_or_instance)) - A = datum_type_or_instance - if(!istype(A)) - return - else - A = new datum_type_or_instance() - //Choose snowflake variation if antagonist handles it - var/datum/antagonist/S = A.specialization(src) - if(S && S != A) - qdel(A) - A = S - if(!A.can_be_owned(src)) - qdel(A) - return - A.owner = src - LAZYADD(antag_datums, A) - A.create_team(team) - var/datum/team/antag_team = A.get_team() - if(antag_team) - antag_team.add_member(src) - A.on_gain() - log_game("[key_name(src)] has gained antag datum [A.name]([A.type])") - return A - -/datum/mind/proc/remove_antag_datum(datum_type) - if(!datum_type) - return - var/datum/antagonist/A = has_antag_datum(datum_type) - if(A) - A.on_removal() - return TRUE - - -/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. - for(var/a in antag_datums) - var/datum/antagonist/A = a - A.on_removal() - -/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) - if(!datum_type) - return - . = FALSE - for(var/a in antag_datums) - var/datum/antagonist/A = a - if(check_subtypes && istype(A, datum_type)) - return A - else if(A.type == datum_type) - return A - -/* - Removes antag type's references from a mind. - objectives, uplinks, powers etc are all handled. -*/ - -/datum/mind/proc/remove_changeling() - var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) - if(C) - remove_antag_datum(/datum/antagonist/changeling) - special_role = null - -/datum/mind/proc/remove_traitor() - remove_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/remove_brother() - if(src in SSticker.mode.brothers) - remove_antag_datum(/datum/antagonist/brother) - -/datum/mind/proc/remove_nukeop() - var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE) - if(nuke) - remove_antag_datum(nuke.type) - special_role = null - -/datum/mind/proc/remove_wizard() - remove_antag_datum(/datum/antagonist/wizard) - special_role = null - -/datum/mind/proc/remove_cultist() - if(src in SSticker.mode.cult) - SSticker.mode.remove_cultist(src, 0, 0) - special_role = null - remove_antag_equip() - -/datum/mind/proc/remove_rev() - var/datum/antagonist/rev/rev = has_antag_datum(/datum/antagonist/rev) - if(rev) - remove_antag_datum(rev.type) - special_role = null - - -/datum/mind/proc/remove_antag_equip() - var/list/Mob_Contents = current.get_contents() - for(var/obj/item/I in Mob_Contents) - var/datum/component/uplink/O = I.GetComponent(/datum/component/uplink) //Todo make this reset signal - if(O) - O.unlock_code = null - -/datum/mind/proc/remove_all_antag() //For the Lazy amongst us. - remove_changeling() - remove_traitor() - remove_nukeop() - remove_wizard() - remove_cultist() - remove_rev() - -/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner) - if(!current) - return - var/mob/living/carbon/human/traitor_mob = current - if (!istype(traitor_mob)) - return - - var/list/all_contents = traitor_mob.GetAllContents() - var/obj/item/pda/PDA = locate() in all_contents - var/obj/item/radio/R = locate() in all_contents - var/obj/item/pen/P - - if (PDA) // Prioritize PDA pen, otherwise the pocket protector pens will be chosen, which causes numerous ahelps about missing uplink - P = locate() in PDA - if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way - P = locate() in all_contents - - var/obj/item/uplink_loc - - if(traitor_mob.client && traitor_mob.client.prefs) - switch(traitor_mob.client.prefs.uplink_spawn_loc) - if(UPLINK_PDA) - uplink_loc = PDA - if(!uplink_loc) - uplink_loc = R - if(!uplink_loc) - uplink_loc = P - if(UPLINK_RADIO) - uplink_loc = R - if(!uplink_loc) - uplink_loc = PDA - if(!uplink_loc) - uplink_loc = P - if(UPLINK_PEN) - uplink_loc = P - - if(!uplink_loc) // We've looked everywhere, let's just give you a pen - if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack! - P = new /obj/item/pen(traitor_mob.back) - else - P = new /obj/item/pen(traitor_mob.loc) - traitor_mob.put_in_hands(P) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused. - uplink_loc = P - - if (!uplink_loc) - if(!silent) - to_chat(traitor_mob, "Unfortunately, [employer] wasn't able to get you an Uplink.") - . = 0 - else - . = uplink_loc - var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key) - if(!U) - CRASH("Uplink creation failed.") - U.setup_unlock_code() - if(!silent) - if(uplink_loc == R) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.") - else if(uplink_loc == PDA) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.") - else if(uplink_loc == P) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [english_list(U.unlock_code)] from its starting position to unlock its hidden features.") - - if(uplink_owner) - uplink_owner.antag_memory += U.unlock_note + "
    " - else - traitor_mob.mind.store_memory(U.unlock_note) - - -//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. - -/datum/mind/proc/enslave_mind_to_creator(mob/living/creator) - if(iscultist(creator)) - SSticker.mode.add_cultist(src) - - else if(is_revolutionary(creator)) - var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE) - converter.add_revolutionary(src,FALSE) - - else if(is_nuclear_operative(creator)) - var/datum/antagonist/nukeop/converter = creator.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) - var/datum/antagonist/nukeop/N = new() - N.send_to_spawnpoint = FALSE - N.nukeop_outfit = null - add_antag_datum(N,converter.nuke_team) - - - enslaved_to = creator - - current.faction |= creator.faction - creator.faction |= current.faction - - if(creator.mind.special_role) - message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.") - to_chat(current, "Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.") - -/datum/mind/proc/show_memory(mob/recipient, window=1) - if(!recipient) - recipient = current - var/output = "[current.real_name]'s Memories:
    " - output += memory - - - var/list/all_objectives = list() - for(var/datum/antagonist/A in antag_datums) - output += A.antag_memory - all_objectives |= A.objectives - - if(all_objectives.len) - output += "Objectives:" - var/obj_count = 1 - for(var/datum/objective/objective in all_objectives) - output += "
    Objective #[obj_count++]: [objective.explanation_text]" - var/list/datum/mind/other_owners = objective.get_owners() - src - if(other_owners.len) - output += "
      " - for(var/datum/mind/M in other_owners) - output += "
    • Conspirator: [M.name]
    • " - output += "
    " - - if(window) - recipient << browse(output,"window=memory") - else if(all_objectives.len || memory) - to_chat(recipient, "[output]") - -/datum/mind/Topic(href, href_list) - if(!check_rights(R_ADMIN)) - return - - var/self_antagging = usr == current - - if(href_list["add_antag"]) - add_antag_wrapper(text2path(href_list["add_antag"]),usr) - if(href_list["remove_antag"]) - var/datum/antagonist/A = locate(href_list["remove_antag"]) in antag_datums - if(!istype(A)) - to_chat(usr,"Invalid antagonist ref to be removed.") - return - A.admin_remove(usr) - - if (href_list["role_edit"]) - var/new_role = input("Select new role", "Assigned role", assigned_role) as null|anything in sortList(get_all_jobs()) - if (!new_role) - return - assigned_role = new_role - - else if (href_list["memory_edit"]) - var/new_memo = stripped_multiline_input(usr, "Write new memory", "Memory", memory, MAX_MESSAGE_LEN) - if (isnull(new_memo)) - return - memory = new_memo - - else if (href_list["obj_edit"] || href_list["obj_add"]) - var/objective_pos //Edited objectives need to keep same order in antag objective list - var/def_value - var/datum/antagonist/target_antag - var/datum/objective/old_objective //The old objective we're replacing/editing - var/datum/objective/new_objective //New objective we're be adding - - if(href_list["obj_edit"]) - for(var/datum/antagonist/A in antag_datums) - old_objective = locate(href_list["obj_edit"]) in A.objectives - if(old_objective) - target_antag = A - objective_pos = A.objectives.Find(old_objective) - break - if(!old_objective) - to_chat(usr,"Invalid objective.") - return - else - if(href_list["target_antag"]) - var/datum/antagonist/X = locate(href_list["target_antag"]) in antag_datums - if(X) - target_antag = X - if(!target_antag) - switch(antag_datums.len) - if(0) - target_antag = add_antag_datum(/datum/antagonist/custom) - if(1) - target_antag = antag_datums[1] - else - var/datum/antagonist/target = input("Which antagonist gets the objective:", "Antagonist", "(new custom antag)") as null|anything in sortList(antag_datums) + "(new custom antag)" - if (QDELETED(target)) - return - else if(target == "(new custom antag)") - target_antag = add_antag_datum(/datum/antagonist/custom) - else - target_antag = target - - if(!GLOB.admin_objective_list) - generate_admin_objective_list() - - if(old_objective) - if(old_objective.name in GLOB.admin_objective_list) - def_value = old_objective.name - - var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list - selected_type = GLOB.admin_objective_list[selected_type] - if (!selected_type) - return - - if(!old_objective) - //Add new one - new_objective = new selected_type - new_objective.owner = src - new_objective.admin_edit(usr) - target_antag.objectives += new_objective - message_admins("[key_name_admin(usr)] added a new objective for [current]: [new_objective.explanation_text]") - log_admin("[key_name(usr)] added a new objective for [current]: [new_objective.explanation_text]") - else - if(old_objective.type == selected_type) - //Edit the old - old_objective.admin_edit(usr) - new_objective = old_objective - else - //Replace the old - new_objective = new selected_type - new_objective.owner = src - new_objective.admin_edit(usr) - target_antag.objectives -= old_objective - target_antag.objectives.Insert(objective_pos, new_objective) - message_admins("[key_name_admin(usr)] edited [current]'s objective to [new_objective.explanation_text]") - log_admin("[key_name(usr)] edited [current]'s objective to [new_objective.explanation_text]") - - else if (href_list["obj_delete"]) - var/datum/objective/objective - for(var/datum/antagonist/A in antag_datums) - objective = locate(href_list["obj_delete"]) in A.objectives - if(istype(objective)) - A.objectives -= objective - break - if(!objective) - to_chat(usr,"Invalid objective.") - return - //qdel(objective) Needs cleaning objective destroys - message_admins("[key_name_admin(usr)] removed an objective for [current]: [objective.explanation_text]") - log_admin("[key_name(usr)] removed an objective for [current]: [objective.explanation_text]") - - else if(href_list["obj_completed"]) - var/datum/objective/objective - for(var/datum/antagonist/A in antag_datums) - objective = locate(href_list["obj_completed"]) in A.objectives - if(istype(objective)) - objective = objective - break - if(!objective) - to_chat(usr,"Invalid objective.") - return - objective.completed = !objective.completed - log_admin("[key_name(usr)] toggled the win state for [current]'s objective: [objective.explanation_text]") - - else if (href_list["silicon"]) - switch(href_list["silicon"]) - if("unemag") - var/mob/living/silicon/robot/R = current - if (istype(R)) - R.SetEmagged(0) - message_admins("[key_name_admin(usr)] has unemag'ed [R].") - log_admin("[key_name(usr)] has unemag'ed [R].") - - if("unemagcyborgs") - if(isAI(current)) - var/mob/living/silicon/ai/ai = current - for (var/mob/living/silicon/robot/R in ai.connected_robots) - R.SetEmagged(0) - message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.") - log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.") - - else if (href_list["common"]) - switch(href_list["common"]) - if("undress") - for(var/obj/item/W in current) - current.dropItemToGround(W, TRUE) //The 1 forces all items to drop, since this is an admin undress. - if("takeuplink") - take_uplink() - memory = null//Remove any memory they may have had. - log_admin("[key_name(usr)] removed [current]'s uplink.") - if("crystals") - if(check_rights(R_FUN, 0)) - var/datum/component/uplink/U = find_syndicate_uplink() - if(U) - var/crystals = input("Amount of telecrystals for [key]","Syndicate uplink", U.telecrystals) as null | num - if(!isnull(crystals)) - U.telecrystals = crystals - message_admins("[key_name_admin(usr)] changed [current]'s telecrystal count to [crystals].") - log_admin("[key_name(usr)] changed [current]'s telecrystal count to [crystals].") - if("uplink") - if(!equip_traitor()) - to_chat(usr, "Equipping a syndicate failed!") - log_admin("[key_name(usr)] tried and failed to give [current] an uplink.") - else - log_admin("[key_name(usr)] gave [current] an uplink.") - - else if (href_list["obj_announce"]) - announce_objectives() - - //Something in here might have changed your mob - if(self_antagging && (!usr || !usr.client) && current.client) - usr = current - traitor_panel() - - -/datum/mind/proc/get_all_objectives() - var/list/all_objectives = list() - for(var/datum/antagonist/A in antag_datums) - all_objectives |= A.objectives - return all_objectives - -/datum/mind/proc/announce_objectives() - var/obj_count = 1 - to_chat(current, "Your current objectives:") - for(var/objective in get_all_objectives()) - var/datum/objective/O = objective - to_chat(current, "Objective #[obj_count]: [O.explanation_text]") - obj_count++ - -/datum/mind/proc/find_syndicate_uplink() - var/list/L = current.GetAllContents() - for (var/i in L) - var/atom/movable/I = i - . = I.GetComponent(/datum/component/uplink) - if(.) - break - -/datum/mind/proc/take_uplink() - qdel(find_syndicate_uplink()) - -/datum/mind/proc/make_Traitor() - if(!(has_antag_datum(/datum/antagonist/traitor))) - add_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/make_Contractor_Support() - if(!(has_antag_datum(/datum/antagonist/traitor/contractor_support))) - add_antag_datum(/datum/antagonist/traitor/contractor_support) - -/datum/mind/proc/make_Changeling() - var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) - if(!C) - C = add_antag_datum(/datum/antagonist/changeling) - special_role = ROLE_CHANGELING - return C - -/datum/mind/proc/make_Wizard() - if(!has_antag_datum(/datum/antagonist/wizard)) - special_role = ROLE_WIZARD - assigned_role = ROLE_WIZARD - add_antag_datum(/datum/antagonist/wizard) - - -/datum/mind/proc/make_Cultist() - if(!has_antag_datum(/datum/antagonist/cult,TRUE)) - SSticker.mode.add_cultist(src,FALSE,equip=TRUE) - special_role = ROLE_CULTIST - to_chat(current, "You catch a glimpse of the Realm of Nar'Sie, The Geometer of Blood. You now see how flimsy your world is, you see that it should be open to the knowledge of Nar'Sie.") - to_chat(current, "Assist your new brethren in their dark dealings. Their goal is yours, and yours is theirs. You serve the Dark One above all else. Bring It back.") - -/datum/mind/proc/make_Rev() - var/datum/antagonist/rev/head/head = new() - head.give_flash = TRUE - head.give_hud = TRUE - add_antag_datum(head) - special_role = ROLE_REV_HEAD - -/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) - spell_list += S - S.action.Grant(current) - -/datum/mind/proc/owns_soul() - return soulOwner == src - -//To remove a specific spell from a mind -/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) - if(!spell) - return - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - if(istype(S, spell)) - spell_list -= S - qdel(S) - -/datum/mind/proc/RemoveAllSpells() - for(var/obj/effect/proc_holder/S in spell_list) - RemoveSpell(S) - -/datum/mind/proc/transfer_martial_arts(mob/living/new_character) - if(!ishuman(new_character)) - return - if(martial_art) - if(martial_art.base) //Is the martial art temporary? - martial_art.remove(new_character) - else - martial_art.teach(new_character) - -/datum/mind/proc/transfer_actions(mob/living/new_character) - if(current && current.actions) - for(var/datum/action/A in current.actions) - A.Grant(new_character) - transfer_mindbound_actions(new_character) - -/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - S.action.Grant(new_character) - -/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - for(var/type in exceptions) - if(istype(S, type)) - continue - S.charge_counter = delay - S.updateButtonIcon() - INVOKE_ASYNC(S, /obj/effect/proc_holder/spell.proc/start_recharge) - -/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) - for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list)) - if(G.mind == src) - if(G.can_reenter_corpse || even_if_they_cant_reenter) - return G - break - -/datum/mind/proc/grab_ghost(force) - var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) - . = G - if(G) - G.reenter_corpse() - - -/datum/mind/proc/has_objective(objective_type) - for(var/datum/antagonist/A in antag_datums) - for(var/O in A.objectives) - if(istype(O,objective_type)) - return TRUE - -/mob/proc/sync_mind() - mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) - mind.active = 1 //indicates that the mind is currently synced with a client - -/datum/mind/proc/has_martialart(string) - if(martial_art && martial_art.id == string) - return martial_art - return FALSE - -/mob/dead/new_player/sync_mind() - return - -/mob/dead/observer/sync_mind() - return - -//Initialisation procs -/mob/proc/mind_initialize() - if(mind) - mind.key = key - - else - mind = new /datum/mind(key) - SSticker.minds += mind - if(!mind.name) - mind.name = real_name - mind.current = src - -/mob/living/carbon/mind_initialize() - ..() - last_mind = mind - -//HUMAN -/mob/living/carbon/human/mind_initialize() - ..() - if(!mind.assigned_role) - mind.assigned_role = "Unassigned" //default - -//AI -/mob/living/silicon/ai/mind_initialize() - ..() - mind.assigned_role = "AI" - -//BORG -/mob/living/silicon/robot/mind_initialize() - ..() - mind.assigned_role = "Cyborg" - -//PAI -/mob/living/silicon/pai/mind_initialize() - ..() - mind.assigned_role = ROLE_PAI - mind.special_role = "" +/* Note from Carnie: + The way datum/mind stuff works has been changed a lot. + Minds now represent IC characters rather than following a client around constantly. + + Guidelines for using minds properly: + + - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! + ghost.mind is however used as a reference to the ghost's corpse + + - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) + the existing mind of the old mob should be transfered to the new mob like so: + + mind.transfer_to(new_mob) + + - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. + By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing + the player. + + - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. + + - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting + a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. + + new_mob.key = key + + The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple! + However if you want that mind to have any special properties like being a traitor etc you will have to do that + yourself. + +*/ + +/datum/mind + var/key + var/name //replaces mob/var/original_name + var/ghostname //replaces name for observers name if set + var/mob/living/current + var/active = 0 + + var/memory + + var/assigned_role + var/special_role + var/list/restricted_roles = list() + + var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. + + var/linglink + var/datum/martial_art/martial_art + var/static/default_martial_art = new/datum/martial_art + var/miming = FALSE // Mime's vow of silence + var/list/antag_datums + var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state + var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD + var/damnation_type = 0 + var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src + var/hasSoul = TRUE // If false, renders the character unable to sell their soul. + var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this. + + var/mob/living/enslaved_to //If this mind's master is another mob (i.e. adamantine golems) + var/datum/language_holder/language_holder + var/unconvertable = FALSE + var/late_joiner = FALSE + + var/last_death = 0 + + var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator + + var/list/learned_recipes //List of learned recipe TYPES. + + ///List of skills the user has received a reward for. Should not be used to keep track of currently known skills. Lazy list because it shouldnt be filled often + var/list/skills_rewarded + ///Assoc list of skills. Use SKILL_LVL to access level, and SKILL_EXP to access skill's exp. + var/list/known_skills = list() + ///What character we spawned in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not + var/mob/original_character + ///Skill multiplier, adjusts how much xp you get/loose from adjust_xp. Dont override it directly, add your reason to experience_multiplier_reasons and use that as a key to put your value in there. + var/experience_multiplier = 1 + ///Skill multiplier list, just slap your multiplier change onto this with the type it is coming from as key. + var/list/experience_multiplier_reasons = list() + +/datum/mind/New(key) + src.key = key + soulOwner = src + martial_art = default_martial_art + init_known_skills() + +/datum/mind/Destroy() + SSticker.minds -= src + if(islist(antag_datums)) + QDEL_LIST(antag_datums) + current = null + soulOwner = null + return ..() + +/datum/mind/proc/get_language_holder() + if(!language_holder) + language_holder = new (src) + return language_holder + +/datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0) + original_character = null + if(current) // remove ourself from our old body's mind variable + current.mind = null + UnregisterSignal(current, COMSIG_MOB_DEATH) + SStgui.on_transfer(current, new_character) + + if(key) + if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours + new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless. + else + key = new_character.key + + if(new_character.mind) //disassociate any mind currently in our new body's mind variable + new_character.mind.current = null + + var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list + var/mob/living/old_current = current + if(current) + current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one + current = new_character //associate ourself with our new body + new_character.mind = src //and associate our new body with ourself + for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body + var/datum/antagonist/A = a + A.on_body_transfer(old_current, current) + if(iscarbon(new_character)) + var/mob/living/carbon/C = new_character + C.last_mind = src + transfer_antag_huds(hud_to_transfer) //inherit the antag HUD + transfer_actions(new_character) + transfer_martial_arts(new_character) + RegisterSignal(new_character, COMSIG_MOB_DEATH, .proc/set_death_time) + if(active || force_key_move) + new_character.key = key //now transfer the key to link the client to our new body + if(new_character.client) + LAZYCLEARLIST(new_character.client.recent_examines) + current.update_atom_languages() + +/datum/mind/proc/init_known_skills() + for (var/type in GLOB.skill_types) + known_skills[type] = list(SKILL_LEVEL_NONE, 0) + +///Return the amount of EXP needed to go to the next level. Returns 0 if max level +/datum/mind/proc/exp_needed_to_level_up(skill) + var/lvl = update_skill_level(skill) + if (lvl >= length(SKILL_EXP_LIST)) //If we're already past the last exp threshold + return 0 + return SKILL_EXP_LIST[lvl+1] - known_skills[skill][SKILL_EXP] + +///Adjust experience of a specific skill +/datum/mind/proc/adjust_experience(skill, amt, silent = FALSE, force_old_level = 0) + var/datum/skill/S = GetSkillRef(skill) + var/old_level = force_old_level ? force_old_level : known_skills[skill][SKILL_LVL] //Get current level of the S skill + experience_multiplier = initial(experience_multiplier) + for(var/key in experience_multiplier_reasons) + experience_multiplier += experience_multiplier_reasons[key] + known_skills[skill][SKILL_EXP] = max(0, known_skills[skill][SKILL_EXP] + amt*experience_multiplier) //Update exp. Prevent going below 0 + known_skills[skill][SKILL_LVL] = update_skill_level(skill)//Check what the current skill level is based on that skill's exp + if(silent) + return + if(known_skills[skill][SKILL_LVL] > old_level) + S.level_gained(src, known_skills[skill][SKILL_LVL], old_level) + else if(known_skills[skill][SKILL_LVL] < old_level) + S.level_lost(src, known_skills[skill][SKILL_LVL], old_level) + +///Set experience of a specific skill to a number +/datum/mind/proc/set_experience(skill, amt, silent = FALSE) + var/old_level = known_skills[skill][SKILL_EXP] + known_skills[skill][SKILL_EXP] = amt + adjust_experience(skill, 0, silent, old_level) //Make a call to adjust_experience to handle updating level + +///Set level of a specific skill +/datum/mind/proc/set_level(skill, newlevel, silent = FALSE) + var/oldlevel = get_skill_level(skill) + var/difference = SKILL_EXP_LIST[newlevel] - SKILL_EXP_LIST[oldlevel] + adjust_experience(skill, difference, silent) + +///Check what the current skill level is based on that skill's exp +/datum/mind/proc/update_skill_level(skill) + var/i = 0 + for (var/exp in SKILL_EXP_LIST) + i ++ + if (known_skills[skill][SKILL_EXP] >= SKILL_EXP_LIST[i]) + continue + return i - 1 //Return level based on the last exp requirement that we were greater than + return i //If we had greater EXP than even the last exp threshold, we return the last level + +///Gets the skill's singleton and returns the result of its get_skill_modifier +/datum/mind/proc/get_skill_modifier(skill, modifier) + var/datum/skill/S = GetSkillRef(skill) + return S.get_skill_modifier(modifier, known_skills[skill][SKILL_LVL]) + +///Gets the player's current level number from the relevant skill +/datum/mind/proc/get_skill_level(skill) + return known_skills[skill][SKILL_LVL] + +///Gets the player's current exp from the relevant skill +/datum/mind/proc/get_skill_exp(skill) + return known_skills[skill][SKILL_EXP] + +/datum/mind/proc/get_skill_level_name(skill) + var/level = get_skill_level(skill) + return SSskills.level_names[level] + +/datum/mind/proc/print_levels(user) + var/list/shown_skills = list() + for(var/i in known_skills) + if(known_skills[i][SKILL_LVL] > SKILL_LEVEL_NONE) //Do we actually have a level in this? + shown_skills += i + if(!length(shown_skills)) + to_chat(user, "You don't seem to have any particularly outstanding skills.") + return + var/msg = "*---------*\nYour skills\n" + for(var/i in shown_skills) + var/datum/skill/the_skill = i + msg += "[initial(the_skill.name)] - [get_skill_level_name(the_skill)]\n" + msg += "" + to_chat(user, msg) + +/datum/mind/proc/set_death_time() + last_death = world.time + +/datum/mind/proc/store_memory(new_text) + var/newlength = length_char(memory) + length_char(new_text) + if (newlength > MAX_MESSAGE_LEN * 100) + memory = copytext_char(memory, -newlength-MAX_MESSAGE_LEN * 100) + memory += "[new_text]
    " + +/datum/mind/proc/wipe_memory() + memory = null + +// Datum antag mind procs +/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) + if(!datum_type_or_instance) + return + var/datum/antagonist/A + if(!ispath(datum_type_or_instance)) + A = datum_type_or_instance + if(!istype(A)) + return + else + A = new datum_type_or_instance() + //Choose snowflake variation if antagonist handles it + var/datum/antagonist/S = A.specialization(src) + if(S && S != A) + qdel(A) + A = S + if(!A.can_be_owned(src)) + qdel(A) + return + A.owner = src + LAZYADD(antag_datums, A) + A.create_team(team) + var/datum/team/antag_team = A.get_team() + if(antag_team) + antag_team.add_member(src) + A.on_gain() + log_game("[key_name(src)] has gained antag datum [A.name]([A.type])") + return A + +/datum/mind/proc/remove_antag_datum(datum_type) + if(!datum_type) + return + var/datum/antagonist/A = has_antag_datum(datum_type) + if(A) + A.on_removal() + return TRUE + + +/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. + for(var/a in antag_datums) + var/datum/antagonist/A = a + A.on_removal() + +/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) + if(!datum_type) + return + . = FALSE + for(var/a in antag_datums) + var/datum/antagonist/A = a + if(check_subtypes && istype(A, datum_type)) + return A + else if(A.type == datum_type) + return A + +/* + Removes antag type's references from a mind. + objectives, uplinks, powers etc are all handled. +*/ + +/datum/mind/proc/remove_changeling() + var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) + if(C) + remove_antag_datum(/datum/antagonist/changeling) + special_role = null + +/datum/mind/proc/remove_traitor() + remove_antag_datum(/datum/antagonist/traitor) + +/datum/mind/proc/remove_brother() + if(src in SSticker.mode.brothers) + remove_antag_datum(/datum/antagonist/brother) + +/datum/mind/proc/remove_nukeop() + var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(nuke) + remove_antag_datum(nuke.type) + special_role = null + +/datum/mind/proc/remove_wizard() + remove_antag_datum(/datum/antagonist/wizard) + special_role = null + +/datum/mind/proc/remove_cultist() + if(src in SSticker.mode.cult) + SSticker.mode.remove_cultist(src, 0, 0) + special_role = null + remove_antag_equip() + +/datum/mind/proc/remove_rev() + var/datum/antagonist/rev/rev = has_antag_datum(/datum/antagonist/rev) + if(rev) + remove_antag_datum(rev.type) + special_role = null + + +/datum/mind/proc/remove_antag_equip() + var/list/Mob_Contents = current.get_contents() + for(var/obj/item/I in Mob_Contents) + var/datum/component/uplink/O = I.GetComponent(/datum/component/uplink) //Todo make this reset signal + if(O) + O.unlock_code = null + +/datum/mind/proc/remove_all_antag() //For the Lazy amongst us. + remove_changeling() + remove_traitor() + remove_nukeop() + remove_wizard() + remove_cultist() + remove_rev() + +/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner) + if(!current) + return + var/mob/living/carbon/human/traitor_mob = current + if (!istype(traitor_mob)) + return + + var/list/all_contents = traitor_mob.GetAllContents() + var/obj/item/pda/PDA = locate() in all_contents + var/obj/item/radio/R = locate() in all_contents + var/obj/item/pen/P + + if (PDA) // Prioritize PDA pen, otherwise the pocket protector pens will be chosen, which causes numerous ahelps about missing uplink + P = locate() in PDA + if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way + P = locate() in all_contents + + var/obj/item/uplink_loc + + if(traitor_mob.client && traitor_mob.client.prefs) + switch(traitor_mob.client.prefs.uplink_spawn_loc) + if(UPLINK_PDA) + uplink_loc = PDA + if(!uplink_loc) + uplink_loc = R + if(!uplink_loc) + uplink_loc = P + if(UPLINK_RADIO) + uplink_loc = R + if(!uplink_loc) + uplink_loc = PDA + if(!uplink_loc) + uplink_loc = P + if(UPLINK_PEN) + uplink_loc = P + + if(!uplink_loc) // We've looked everywhere, let's just give you a pen + if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack! + P = new /obj/item/pen(traitor_mob.back) + else + P = new /obj/item/pen(traitor_mob.loc) + traitor_mob.put_in_hands(P) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused. + uplink_loc = P + + if (!uplink_loc) + if(!silent) + to_chat(traitor_mob, "Unfortunately, [employer] wasn't able to get you an Uplink.") + . = 0 + else + . = uplink_loc + var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key) + if(!U) + CRASH("Uplink creation failed.") + U.setup_unlock_code() + if(!silent) + if(uplink_loc == R) + to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.") + else if(uplink_loc == PDA) + to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.") + else if(uplink_loc == P) + to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [english_list(U.unlock_code)] from its starting position to unlock its hidden features.") + + if(uplink_owner) + uplink_owner.antag_memory += U.unlock_note + "
    " + else + traitor_mob.mind.store_memory(U.unlock_note) + + +//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. + +/datum/mind/proc/enslave_mind_to_creator(mob/living/creator) + if(iscultist(creator)) + SSticker.mode.add_cultist(src) + + else if(is_revolutionary(creator)) + var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE) + converter.add_revolutionary(src,FALSE) + + else if(is_nuclear_operative(creator)) + var/datum/antagonist/nukeop/converter = creator.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) + var/datum/antagonist/nukeop/N = new() + N.send_to_spawnpoint = FALSE + N.nukeop_outfit = null + add_antag_datum(N,converter.nuke_team) + + + enslaved_to = creator + + current.faction |= creator.faction + creator.faction |= current.faction + + if(creator.mind.special_role) + message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.") + to_chat(current, "Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.") + +/datum/mind/proc/show_memory(mob/recipient, window=1) + if(!recipient) + recipient = current + var/output = "[current.real_name]'s Memories:
    " + output += memory + + + var/list/all_objectives = list() + for(var/datum/antagonist/A in antag_datums) + output += A.antag_memory + all_objectives |= A.objectives + + if(all_objectives.len) + output += "Objectives:" + var/obj_count = 1 + for(var/datum/objective/objective in all_objectives) + output += "
    Objective #[obj_count++]: [objective.explanation_text]" + var/list/datum/mind/other_owners = objective.get_owners() - src + if(other_owners.len) + output += "
      " + for(var/datum/mind/M in other_owners) + output += "
    • Conspirator: [M.name]
    • " + output += "
    " + + if(window) + recipient << browse(output,"window=memory") + else if(all_objectives.len || memory) + to_chat(recipient, "[output]") + +/datum/mind/Topic(href, href_list) + if(!check_rights(R_ADMIN)) + return + + var/self_antagging = usr == current + + if(href_list["add_antag"]) + add_antag_wrapper(text2path(href_list["add_antag"]),usr) + if(href_list["remove_antag"]) + var/datum/antagonist/A = locate(href_list["remove_antag"]) in antag_datums + if(!istype(A)) + to_chat(usr,"Invalid antagonist ref to be removed.") + return + A.admin_remove(usr) + + if (href_list["role_edit"]) + var/new_role = input("Select new role", "Assigned role", assigned_role) as null|anything in sortList(get_all_jobs()) + if (!new_role) + return + assigned_role = new_role + + else if (href_list["memory_edit"]) + var/new_memo = stripped_multiline_input(usr, "Write new memory", "Memory", memory, MAX_MESSAGE_LEN) + if (isnull(new_memo)) + return + memory = new_memo + + else if (href_list["obj_edit"] || href_list["obj_add"]) + var/objective_pos //Edited objectives need to keep same order in antag objective list + var/def_value + var/datum/antagonist/target_antag + var/datum/objective/old_objective //The old objective we're replacing/editing + var/datum/objective/new_objective //New objective we're be adding + + if(href_list["obj_edit"]) + for(var/datum/antagonist/A in antag_datums) + old_objective = locate(href_list["obj_edit"]) in A.objectives + if(old_objective) + target_antag = A + objective_pos = A.objectives.Find(old_objective) + break + if(!old_objective) + to_chat(usr,"Invalid objective.") + return + else + if(href_list["target_antag"]) + var/datum/antagonist/X = locate(href_list["target_antag"]) in antag_datums + if(X) + target_antag = X + if(!target_antag) + switch(antag_datums.len) + if(0) + target_antag = add_antag_datum(/datum/antagonist/custom) + if(1) + target_antag = antag_datums[1] + else + var/datum/antagonist/target = input("Which antagonist gets the objective:", "Antagonist", "(new custom antag)") as null|anything in sortList(antag_datums) + "(new custom antag)" + if (QDELETED(target)) + return + else if(target == "(new custom antag)") + target_antag = add_antag_datum(/datum/antagonist/custom) + else + target_antag = target + + if(!GLOB.admin_objective_list) + generate_admin_objective_list() + + if(old_objective) + if(old_objective.name in GLOB.admin_objective_list) + def_value = old_objective.name + + var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list + selected_type = GLOB.admin_objective_list[selected_type] + if (!selected_type) + return + + if(!old_objective) + //Add new one + new_objective = new selected_type + new_objective.owner = src + new_objective.admin_edit(usr) + target_antag.objectives += new_objective + message_admins("[key_name_admin(usr)] added a new objective for [current]: [new_objective.explanation_text]") + log_admin("[key_name(usr)] added a new objective for [current]: [new_objective.explanation_text]") + else + if(old_objective.type == selected_type) + //Edit the old + old_objective.admin_edit(usr) + new_objective = old_objective + else + //Replace the old + new_objective = new selected_type + new_objective.owner = src + new_objective.admin_edit(usr) + target_antag.objectives -= old_objective + target_antag.objectives.Insert(objective_pos, new_objective) + message_admins("[key_name_admin(usr)] edited [current]'s objective to [new_objective.explanation_text]") + log_admin("[key_name(usr)] edited [current]'s objective to [new_objective.explanation_text]") + + else if (href_list["obj_delete"]) + var/datum/objective/objective + for(var/datum/antagonist/A in antag_datums) + objective = locate(href_list["obj_delete"]) in A.objectives + if(istype(objective)) + A.objectives -= objective + break + if(!objective) + to_chat(usr,"Invalid objective.") + return + //qdel(objective) Needs cleaning objective destroys + message_admins("[key_name_admin(usr)] removed an objective for [current]: [objective.explanation_text]") + log_admin("[key_name(usr)] removed an objective for [current]: [objective.explanation_text]") + + else if(href_list["obj_completed"]) + var/datum/objective/objective + for(var/datum/antagonist/A in antag_datums) + objective = locate(href_list["obj_completed"]) in A.objectives + if(istype(objective)) + objective = objective + break + if(!objective) + to_chat(usr,"Invalid objective.") + return + objective.completed = !objective.completed + log_admin("[key_name(usr)] toggled the win state for [current]'s objective: [objective.explanation_text]") + + else if (href_list["silicon"]) + switch(href_list["silicon"]) + if("unemag") + var/mob/living/silicon/robot/R = current + if (istype(R)) + R.SetEmagged(0) + message_admins("[key_name_admin(usr)] has unemag'ed [R].") + log_admin("[key_name(usr)] has unemag'ed [R].") + + if("unemagcyborgs") + if(isAI(current)) + var/mob/living/silicon/ai/ai = current + for (var/mob/living/silicon/robot/R in ai.connected_robots) + R.SetEmagged(0) + message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.") + log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.") + + else if (href_list["common"]) + switch(href_list["common"]) + if("undress") + for(var/obj/item/W in current) + current.dropItemToGround(W, TRUE) //The 1 forces all items to drop, since this is an admin undress. + if("takeuplink") + take_uplink() + memory = null//Remove any memory they may have had. + log_admin("[key_name(usr)] removed [current]'s uplink.") + if("crystals") + if(check_rights(R_FUN, 0)) + var/datum/component/uplink/U = find_syndicate_uplink() + if(U) + var/crystals = input("Amount of telecrystals for [key]","Syndicate uplink", U.telecrystals) as null | num + if(!isnull(crystals)) + U.telecrystals = crystals + message_admins("[key_name_admin(usr)] changed [current]'s telecrystal count to [crystals].") + log_admin("[key_name(usr)] changed [current]'s telecrystal count to [crystals].") + if("uplink") + if(!equip_traitor()) + to_chat(usr, "Equipping a syndicate failed!") + log_admin("[key_name(usr)] tried and failed to give [current] an uplink.") + else + log_admin("[key_name(usr)] gave [current] an uplink.") + + else if (href_list["obj_announce"]) + announce_objectives() + + //Something in here might have changed your mob + if(self_antagging && (!usr || !usr.client) && current.client) + usr = current + traitor_panel() + + +/datum/mind/proc/get_all_objectives() + var/list/all_objectives = list() + for(var/datum/antagonist/A in antag_datums) + all_objectives |= A.objectives + return all_objectives + +/datum/mind/proc/announce_objectives() + var/obj_count = 1 + to_chat(current, "Your current objectives:") + for(var/objective in get_all_objectives()) + var/datum/objective/O = objective + to_chat(current, "Objective #[obj_count]: [O.explanation_text]") + obj_count++ + +/datum/mind/proc/find_syndicate_uplink() + var/list/L = current.GetAllContents() + for (var/i in L) + var/atom/movable/I = i + . = I.GetComponent(/datum/component/uplink) + if(.) + break + +/datum/mind/proc/take_uplink() + qdel(find_syndicate_uplink()) + +/datum/mind/proc/make_Traitor() + if(!(has_antag_datum(/datum/antagonist/traitor))) + add_antag_datum(/datum/antagonist/traitor) + +/datum/mind/proc/make_Contractor_Support() + if(!(has_antag_datum(/datum/antagonist/traitor/contractor_support))) + add_antag_datum(/datum/antagonist/traitor/contractor_support) + +/datum/mind/proc/make_Changeling() + var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) + if(!C) + C = add_antag_datum(/datum/antagonist/changeling) + special_role = ROLE_CHANGELING + return C + +/datum/mind/proc/make_Wizard() + if(!has_antag_datum(/datum/antagonist/wizard)) + special_role = ROLE_WIZARD + assigned_role = ROLE_WIZARD + add_antag_datum(/datum/antagonist/wizard) + + +/datum/mind/proc/make_Cultist() + if(!has_antag_datum(/datum/antagonist/cult,TRUE)) + SSticker.mode.add_cultist(src,FALSE,equip=TRUE) + special_role = ROLE_CULTIST + to_chat(current, "You catch a glimpse of the Realm of Nar'Sie, The Geometer of Blood. You now see how flimsy your world is, you see that it should be open to the knowledge of Nar'Sie.") + to_chat(current, "Assist your new brethren in their dark dealings. Their goal is yours, and yours is theirs. You serve the Dark One above all else. Bring It back.") + +/datum/mind/proc/make_Rev() + var/datum/antagonist/rev/head/head = new() + head.give_flash = TRUE + head.give_hud = TRUE + add_antag_datum(head) + special_role = ROLE_REV_HEAD + +/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) + spell_list += S + S.action.Grant(current) + +/datum/mind/proc/owns_soul() + return soulOwner == src + +//To remove a specific spell from a mind +/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) + if(!spell) + return + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + if(istype(S, spell)) + spell_list -= S + qdel(S) + +/datum/mind/proc/RemoveAllSpells() + for(var/obj/effect/proc_holder/S in spell_list) + RemoveSpell(S) + +/datum/mind/proc/transfer_martial_arts(mob/living/new_character) + if(!ishuman(new_character)) + return + if(martial_art) + if(martial_art.base) //Is the martial art temporary? + martial_art.remove(new_character) + else + martial_art.teach(new_character) + +/datum/mind/proc/transfer_actions(mob/living/new_character) + if(current && current.actions) + for(var/datum/action/A in current.actions) + A.Grant(new_character) + transfer_mindbound_actions(new_character) + +/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + S.action.Grant(new_character) + +/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + for(var/type in exceptions) + if(istype(S, type)) + continue + S.charge_counter = delay + S.updateButtonIcon() + INVOKE_ASYNC(S, /obj/effect/proc_holder/spell.proc/start_recharge) + +/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) + for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list)) + if(G.mind == src) + if(G.can_reenter_corpse || even_if_they_cant_reenter) + return G + break + +/datum/mind/proc/grab_ghost(force) + var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) + . = G + if(G) + G.reenter_corpse() + + +/datum/mind/proc/has_objective(objective_type) + for(var/datum/antagonist/A in antag_datums) + for(var/O in A.objectives) + if(istype(O,objective_type)) + return TRUE + +/mob/proc/sync_mind() + mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) + mind.active = 1 //indicates that the mind is currently synced with a client + +/datum/mind/proc/has_martialart(string) + if(martial_art && martial_art.id == string) + return martial_art + return FALSE + +/mob/dead/new_player/sync_mind() + return + +/mob/dead/observer/sync_mind() + return + +//Initialisation procs +/mob/proc/mind_initialize() + if(mind) + mind.key = key + + else + mind = new /datum/mind(key) + SSticker.minds += mind + if(!mind.name) + mind.name = real_name + mind.current = src + +/mob/living/carbon/mind_initialize() + ..() + last_mind = mind + +//HUMAN +/mob/living/carbon/human/mind_initialize() + ..() + if(!mind.assigned_role) + mind.assigned_role = "Unassigned" //default + +//AI +/mob/living/silicon/ai/mind_initialize() + ..() + mind.assigned_role = "AI" + +//BORG +/mob/living/silicon/robot/mind_initialize() + ..() + mind.assigned_role = "Cyborg" + +//PAI +/mob/living/silicon/pai/mind_initialize() + ..() + mind.assigned_role = ROLE_PAI + mind.special_role = "" diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index d2b773fb5a5..c1cb787fc59 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -1,25 +1,25 @@ -/datum/mood_event - var/description ///For descriptions, use the span classes bold nicegreen, nicegreen, none, warning and boldwarning in order from great to horrible. - var/mood_change = 0 - var/timeout = 0 - var/hidden = FALSE//Not shown on examine - var/category //string of what category this mood was added in as - var/special_screen_obj //if it isn't null, it will replace or add onto the mood icon with this (same file). see happiness drug for example - var/special_screen_replace = TRUE //if false, it will be an overlay instead - var/mob/owner - -/datum/mood_event/New(mob/M, ...) - owner = M - var/list/params = args.Copy(2) - add_effects(arglist(params)) - -/datum/mood_event/Destroy() - remove_effects() - owner = null - return ..() - -/datum/mood_event/proc/add_effects(param) - return - -/datum/mood_event/proc/remove_effects() - return +/datum/mood_event + var/description ///For descriptions, use the span classes bold nicegreen, nicegreen, none, warning and boldwarning in order from great to horrible. + var/mood_change = 0 + var/timeout = 0 + var/hidden = FALSE//Not shown on examine + var/category //string of what category this mood was added in as + var/special_screen_obj //if it isn't null, it will replace or add onto the mood icon with this (same file). see happiness drug for example + var/special_screen_replace = TRUE //if false, it will be an overlay instead + var/mob/owner + +/datum/mood_event/New(mob/M, ...) + owner = M + var/list/params = args.Copy(2) + add_effects(arglist(params)) + +/datum/mood_event/Destroy() + remove_effects() + owner = null + return ..() + +/datum/mood_event/proc/add_effects(param) + return + +/datum/mood_event/proc/remove_effects() + return diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm index bcb9bee20f9..0311e280b12 100644 --- a/code/datums/mood_events/drug_events.dm +++ b/code/datums/mood_events/drug_events.dm @@ -1,80 +1,80 @@ -/datum/mood_event/high - mood_change = 6 - description = "Woooow duudeeeeee...I'm tripping baaalls...\n" - -/datum/mood_event/smoked - description = "I have had a smoke recently.\n" - mood_change = 2 - timeout = 6 MINUTES - -/datum/mood_event/wrong_brand - description = "I hate that brand of cigarettes.\n" - mood_change = -2 - timeout = 6 MINUTES - -/datum/mood_event/overdose - mood_change = -8 - timeout = 5 MINUTES - -/datum/mood_event/overdose/add_effects(drug_name) - description = "I think I took a bit too much of that [drug_name]\n" - -/datum/mood_event/withdrawal_light - mood_change = -2 - -/datum/mood_event/withdrawal_light/add_effects(drug_name) - description = "I could use some [drug_name]\n" - -/datum/mood_event/withdrawal_medium - mood_change = -5 - -/datum/mood_event/withdrawal_medium/add_effects(drug_name) - description = "I really need [drug_name]\n" - -/datum/mood_event/withdrawal_severe - mood_change = -8 - -/datum/mood_event/withdrawal_severe/add_effects(drug_name) - description = "Oh god I need some of that [drug_name]\n" - -/datum/mood_event/withdrawal_critical - mood_change = -10 - -/datum/mood_event/withdrawal_critical/add_effects(drug_name) - description = "[drug_name]! [drug_name]! [drug_name]!\n" - -/datum/mood_event/happiness_drug - description = "Can't feel a thing...\n" - mood_change = 50 - -/datum/mood_event/happiness_drug_good_od - description = "YES! YES!! YES!!!\n" - mood_change = 100 - timeout = 30 SECONDS - special_screen_obj = "mood_happiness_good" - -/datum/mood_event/happiness_drug_bad_od - description = "NO! NO!! NO!!!\n" - mood_change = -100 - timeout = 30 SECONDS - special_screen_obj = "mood_happiness_bad" - -/datum/mood_event/narcotic_medium - description = "I feel comfortably numb.\n" - mood_change = 4 - timeout = 3 MINUTES - -/datum/mood_event/narcotic_heavy - description = "I feel like I'm wrapped up in cotton!\n" - mood_change = 9 - timeout = 3 MINUTES - -/datum/mood_event/stimulant_medium - description = "I have so much energy! I feel like I could do anything!\n" - mood_change = 4 - timeout = 3 MINUTES - -/datum/mood_event/stimulant_heavy - description = "Eh ah AAAAH! HA HA HA HA HAA! Uuuh.\n" - mood_change = 6 - timeout = 3 MINUTES +/datum/mood_event/high + mood_change = 6 + description = "Woooow duudeeeeee...I'm tripping baaalls...\n" + +/datum/mood_event/smoked + description = "I have had a smoke recently.\n" + mood_change = 2 + timeout = 6 MINUTES + +/datum/mood_event/wrong_brand + description = "I hate that brand of cigarettes.\n" + mood_change = -2 + timeout = 6 MINUTES + +/datum/mood_event/overdose + mood_change = -8 + timeout = 5 MINUTES + +/datum/mood_event/overdose/add_effects(drug_name) + description = "I think I took a bit too much of that [drug_name]\n" + +/datum/mood_event/withdrawal_light + mood_change = -2 + +/datum/mood_event/withdrawal_light/add_effects(drug_name) + description = "I could use some [drug_name]\n" + +/datum/mood_event/withdrawal_medium + mood_change = -5 + +/datum/mood_event/withdrawal_medium/add_effects(drug_name) + description = "I really need [drug_name]\n" + +/datum/mood_event/withdrawal_severe + mood_change = -8 + +/datum/mood_event/withdrawal_severe/add_effects(drug_name) + description = "Oh god I need some of that [drug_name]\n" + +/datum/mood_event/withdrawal_critical + mood_change = -10 + +/datum/mood_event/withdrawal_critical/add_effects(drug_name) + description = "[drug_name]! [drug_name]! [drug_name]!\n" + +/datum/mood_event/happiness_drug + description = "Can't feel a thing...\n" + mood_change = 50 + +/datum/mood_event/happiness_drug_good_od + description = "YES! YES!! YES!!!\n" + mood_change = 100 + timeout = 30 SECONDS + special_screen_obj = "mood_happiness_good" + +/datum/mood_event/happiness_drug_bad_od + description = "NO! NO!! NO!!!\n" + mood_change = -100 + timeout = 30 SECONDS + special_screen_obj = "mood_happiness_bad" + +/datum/mood_event/narcotic_medium + description = "I feel comfortably numb.\n" + mood_change = 4 + timeout = 3 MINUTES + +/datum/mood_event/narcotic_heavy + description = "I feel like I'm wrapped up in cotton!\n" + mood_change = 9 + timeout = 3 MINUTES + +/datum/mood_event/stimulant_medium + description = "I have so much energy! I feel like I could do anything!\n" + mood_change = 4 + timeout = 3 MINUTES + +/datum/mood_event/stimulant_heavy + description = "Eh ah AAAAH! HA HA HA HA HAA! Uuuh.\n" + mood_change = 6 + timeout = 3 MINUTES diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index e357a58b70f..e9ce0716532 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -1,93 +1,93 @@ -//nutrition -/datum/mood_event/fat - description = "I'm so fat...\n" //muh fatshaming - mood_change = -6 - -/datum/mood_event/wellfed - description = "I'm stuffed!\n" - mood_change = 8 - -/datum/mood_event/fed - description = "I have recently had some food.\n" - mood_change = 5 - -/datum/mood_event/hungry - description = "I'm getting a bit hungry.\n" - mood_change = -6 - -/datum/mood_event/starving - description = "I'm starving!\n" - mood_change = -10 - -//charge -/datum/mood_event/supercharged - description = "I can't possibly keep all this power inside, I need to release some quick!\n" - mood_change = -10 - -/datum/mood_event/overcharged - description = "I feel dangerously overcharged, perhaps I should release some power.\n" - mood_change = -4 - -/datum/mood_event/charged - description = "I feel the power in my veins!\n" - mood_change = 6 - -/datum/mood_event/lowpower - description = "My power is running low, I should go charge up somewhere.\n" - mood_change = -6 - -/datum/mood_event/decharged - description = "I'm in desperate need of some electricity!\n" - mood_change = -10 - -//Disgust -/datum/mood_event/gross - description = "I saw something gross.\n" - mood_change = -4 - -/datum/mood_event/verygross - description = "I think I'm going to puke...\n" - mood_change = -6 - -/datum/mood_event/disgusted - description = "Oh god that's disgusting...\n" - mood_change = -8 - -/datum/mood_event/disgust/bad_smell - description = "You smell something horribly decayed inside this room.\n" - mood_change = -6 - -/datum/mood_event/disgust/nauseating_stench - description = "The stench of rotting carcasses is unbearable!\n" - mood_change = -12 - -//Generic needs events -/datum/mood_event/favorite_food - description = "I really enjoyed eating that.\n" - mood_change = 5 - timeout = 4 MINUTES - -/datum/mood_event/gross_food - description = "I really didn't like that food.\n" - mood_change = -2 - timeout = 4 MINUTES - -/datum/mood_event/disgusting_food - description = "That food was disgusting!\n" - mood_change = -6 - timeout = 4 MINUTES - -/datum/mood_event/breakfast - description = "Nothing like a hearty breakfast to start the shift.\n" - mood_change = 2 - timeout = 10 MINUTES - -/datum/mood_event/nice_shower - description = "I have recently had a nice shower.\n" - mood_change = 4 - timeout = 5 MINUTES - -/datum/mood_event/fresh_laundry - description = "There's nothing like the feeling of a freshly laundered jumpsuit.\n" - mood_change = 2 - timeout = 10 MINUTES +//nutrition +/datum/mood_event/fat + description = "I'm so fat...\n" //muh fatshaming + mood_change = -6 + +/datum/mood_event/wellfed + description = "I'm stuffed!\n" + mood_change = 8 + +/datum/mood_event/fed + description = "I have recently had some food.\n" + mood_change = 5 + +/datum/mood_event/hungry + description = "I'm getting a bit hungry.\n" + mood_change = -6 + +/datum/mood_event/starving + description = "I'm starving!\n" + mood_change = -10 + +//charge +/datum/mood_event/supercharged + description = "I can't possibly keep all this power inside, I need to release some quick!\n" + mood_change = -10 + +/datum/mood_event/overcharged + description = "I feel dangerously overcharged, perhaps I should release some power.\n" + mood_change = -4 + +/datum/mood_event/charged + description = "I feel the power in my veins!\n" + mood_change = 6 + +/datum/mood_event/lowpower + description = "My power is running low, I should go charge up somewhere.\n" + mood_change = -6 + +/datum/mood_event/decharged + description = "I'm in desperate need of some electricity!\n" + mood_change = -10 + +//Disgust +/datum/mood_event/gross + description = "I saw something gross.\n" + mood_change = -4 + +/datum/mood_event/verygross + description = "I think I'm going to puke...\n" + mood_change = -6 + +/datum/mood_event/disgusted + description = "Oh god that's disgusting...\n" + mood_change = -8 + +/datum/mood_event/disgust/bad_smell + description = "You smell something horribly decayed inside this room.\n" + mood_change = -6 + +/datum/mood_event/disgust/nauseating_stench + description = "The stench of rotting carcasses is unbearable!\n" + mood_change = -12 + +//Generic needs events +/datum/mood_event/favorite_food + description = "I really enjoyed eating that.\n" + mood_change = 5 + timeout = 4 MINUTES + +/datum/mood_event/gross_food + description = "I really didn't like that food.\n" + mood_change = -2 + timeout = 4 MINUTES + +/datum/mood_event/disgusting_food + description = "That food was disgusting!\n" + mood_change = -6 + timeout = 4 MINUTES + +/datum/mood_event/breakfast + description = "Nothing like a hearty breakfast to start the shift.\n" + mood_change = 2 + timeout = 10 MINUTES + +/datum/mood_event/nice_shower + description = "I have recently had a nice shower.\n" + mood_change = 4 + timeout = 5 MINUTES + +/datum/mood_event/fresh_laundry + description = "There's nothing like the feeling of a freshly laundered jumpsuit.\n" + mood_change = 2 + timeout = 10 MINUTES diff --git a/code/datums/numbered_display.dm b/code/datums/numbered_display.dm index 84ab35a8def..9aa880aa75d 100644 --- a/code/datums/numbered_display.dm +++ b/code/datums/numbered_display.dm @@ -1,10 +1,10 @@ -//Used in storage. -/datum/numbered_display - var/obj/item/sample_object - var/number - -/datum/numbered_display/New(obj/item/sample, _number = 1) - if(!istype(sample)) - qdel(src) - sample_object = sample - number = _number +//Used in storage. +/datum/numbered_display + var/obj/item/sample_object + var/number + +/datum/numbered_display/New(obj/item/sample, _number = 1) + if(!istype(sample)) + qdel(src) + sample_object = sample + number = _number diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index 1709147b36d..07fe0ed7652 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -1,226 +1,226 @@ -//Designed for things that need precision trajectories like projectiles. -//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels. - -//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf. - -#define RETURN_PRECISE_POSITION(A) new /datum/position(A) -#define RETURN_PRECISE_POINT(A) new /datum/point(A) - -#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) -#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) - -/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below. - var/datum/point/P = new - P.x = a.x + (b.x - a.x) / 2 - P.y = a.y + (b.y - a.y) / 2 - P.z = a.z - return P - -/proc/pixel_length_between_points(datum/point/a, datum/point/b) - return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2)) - -/proc/angle_between_points(datum/point/a, datum/point/b) - return ATAN2((b.y - a.y), (b.x - a.x)) - -/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. - var/x = 0 - var/y = 0 - var/z = 0 - var/pixel_x = 0 - var/pixel_y = 0 - -/datum/position/proc/valid() - return x && y && z && !isnull(pixel_x) && !isnull(pixel_y) - -/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point. - if(istype(_x, /datum/point)) - var/datum/point/P = _x - var/turf/T = P.return_turf() - _x = T.x - _y = T.y - _z = T.z - _pixel_x = P.return_px() - _pixel_y = P.return_py() - else if(isatom(_x)) - var/atom/A = _x - _x = A.x - _y = A.y - _z = A.z - _pixel_x = A.pixel_x - _pixel_y = A.pixel_y - x = _x - y = _y - z = _z - pixel_x = _pixel_x - pixel_y = _pixel_y - -/datum/position/proc/return_turf() - return locate(x, y, z) - -/datum/position/proc/return_px() - return pixel_x - -/datum/position/proc/return_py() - return pixel_y - -/datum/position/proc/return_point() - return new /datum/point(src) - -/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP! - var/x = 0 - var/y = 0 - var/z = 0 - -/datum/point/proc/valid() - return x && y && z - -/datum/point/proc/copy_to(datum/point/p = new) - p.x = x - p.y = y - p.z = z - return p - -/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom. - if(istype(_x, /datum/position)) - var/datum/position/P = _x - _x = P.x - _y = P.y - _z = P.z - _pixel_x = P.pixel_x - _pixel_y = P.pixel_y - else if(istype(_x, /atom)) - var/atom/A = _x - _x = A.x - _y = A.y - _z = A.z - _pixel_x = A.pixel_x - _pixel_y = A.pixel_y - initialize_location(_x, _y, _z, _pixel_x, _pixel_y) - -/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) - if(!isnull(tile_x)) - x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x + 1 - if(!isnull(tile_y)) - y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2 + p_y + 1 - if(!isnull(tile_z)) - z = tile_z - -/datum/point/proc/debug_out() - var/turf/T = return_turf() - return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" - -/datum/point/proc/move_atom_to_src(atom/movable/AM) - AM.forceMove(return_turf()) - AM.pixel_x = return_px() - AM.pixel_y = return_py() - -/datum/point/proc/return_turf() - return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) - -/datum/point/proc/return_coordinates() //[turf_x, turf_y, z] - return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) - -/datum/point/proc/return_position() - return new /datum/position(src) - -/datum/point/proc/return_px() - return MODULUS(x, world.icon_size) - 16 - 1 - -/datum/point/proc/return_py() - return MODULUS(y, world.icon_size) - 16 - 1 - -/datum/point/vector - var/speed = 32 //pixels per iteration - var/iteration = 0 - var/angle = 0 - var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step. - var/mpy = 0 - var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location(). - var/starting_y = 0 - var/starting_z = 0 - -/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0) - ..() - initialize_trajectory(_speed, _angle) - if(initial_increment) - increment(initial_increment) - -/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) - . = ..() - starting_x = x - starting_y = y - starting_z = z - -/datum/point/vector/copy_to(datum/point/vector/v = new) - ..(v) - v.speed = speed - v.iteration = iteration - v.angle = angle - v.mpx = mpx - v.mpy = mpy - v.starting_x = starting_x - v.starting_y = starting_y - v.starting_z = starting_z - return v - -/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle) - if(!isnull(pixel_speed)) - speed = pixel_speed - set_angle(new_angle) - -/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270. - if(isnull(angle)) - return - angle = new_angle - update_offsets() - -/datum/point/vector/proc/update_offsets() - mpx = sin(angle) * speed - mpy = cos(angle) * speed - -/datum/point/vector/proc/set_speed(new_speed) - if(isnull(new_speed) || speed == new_speed) - return - speed = new_speed - update_offsets() - -/datum/point/vector/proc/increment(multiplier = 1) - iteration++ - x += mpx * (multiplier) - y += mpy * (multiplier) - -/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE) - var/datum/point/vector/v = copy_to() - if(force_simulate) - for(var/i in 1 to amount) - v.increment(multiplier) - else - v.increment(multiplier * amount) - return v - -/datum/point/vector/proc/on_z_change() - return - -/datum/point/vector/processed //pixel_speed is per decisecond. - var/last_process = 0 - var/last_move = 0 - var/paused = FALSE - -/datum/point/vector/processed/Destroy() - STOP_PROCESSING(SSprojectiles, src) - return ..() - -/datum/point/vector/processed/proc/start() - last_process = world.time - last_move = world.time - START_PROCESSING(SSprojectiles, src) - -/datum/point/vector/processed/process() - if(paused) - last_move += world.time - last_process - last_process = world.time - return - var/needed_time = world.time - last_move - last_process = world.time - last_move = world.time - increment(needed_time / SSprojectiles.wait) +//Designed for things that need precision trajectories like projectiles. +//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels. + +//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf. + +#define RETURN_PRECISE_POSITION(A) new /datum/position(A) +#define RETURN_PRECISE_POINT(A) new /datum/point(A) + +#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) +#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) + +/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below. + var/datum/point/P = new + P.x = a.x + (b.x - a.x) / 2 + P.y = a.y + (b.y - a.y) / 2 + P.z = a.z + return P + +/proc/pixel_length_between_points(datum/point/a, datum/point/b) + return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2)) + +/proc/angle_between_points(datum/point/a, datum/point/b) + return ATAN2((b.y - a.y), (b.x - a.x)) + +/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. + var/x = 0 + var/y = 0 + var/z = 0 + var/pixel_x = 0 + var/pixel_y = 0 + +/datum/position/proc/valid() + return x && y && z && !isnull(pixel_x) && !isnull(pixel_y) + +/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point. + if(istype(_x, /datum/point)) + var/datum/point/P = _x + var/turf/T = P.return_turf() + _x = T.x + _y = T.y + _z = T.z + _pixel_x = P.return_px() + _pixel_y = P.return_py() + else if(isatom(_x)) + var/atom/A = _x + _x = A.x + _y = A.y + _z = A.z + _pixel_x = A.pixel_x + _pixel_y = A.pixel_y + x = _x + y = _y + z = _z + pixel_x = _pixel_x + pixel_y = _pixel_y + +/datum/position/proc/return_turf() + return locate(x, y, z) + +/datum/position/proc/return_px() + return pixel_x + +/datum/position/proc/return_py() + return pixel_y + +/datum/position/proc/return_point() + return new /datum/point(src) + +/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP! + var/x = 0 + var/y = 0 + var/z = 0 + +/datum/point/proc/valid() + return x && y && z + +/datum/point/proc/copy_to(datum/point/p = new) + p.x = x + p.y = y + p.z = z + return p + +/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom. + if(istype(_x, /datum/position)) + var/datum/position/P = _x + _x = P.x + _y = P.y + _z = P.z + _pixel_x = P.pixel_x + _pixel_y = P.pixel_y + else if(istype(_x, /atom)) + var/atom/A = _x + _x = A.x + _y = A.y + _z = A.z + _pixel_x = A.pixel_x + _pixel_y = A.pixel_y + initialize_location(_x, _y, _z, _pixel_x, _pixel_y) + +/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) + if(!isnull(tile_x)) + x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x + 1 + if(!isnull(tile_y)) + y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2 + p_y + 1 + if(!isnull(tile_z)) + z = tile_z + +/datum/point/proc/debug_out() + var/turf/T = return_turf() + return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" + +/datum/point/proc/move_atom_to_src(atom/movable/AM) + AM.forceMove(return_turf()) + AM.pixel_x = return_px() + AM.pixel_y = return_py() + +/datum/point/proc/return_turf() + return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) + +/datum/point/proc/return_coordinates() //[turf_x, turf_y, z] + return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) + +/datum/point/proc/return_position() + return new /datum/position(src) + +/datum/point/proc/return_px() + return MODULUS(x, world.icon_size) - 16 - 1 + +/datum/point/proc/return_py() + return MODULUS(y, world.icon_size) - 16 - 1 + +/datum/point/vector + var/speed = 32 //pixels per iteration + var/iteration = 0 + var/angle = 0 + var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step. + var/mpy = 0 + var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location(). + var/starting_y = 0 + var/starting_z = 0 + +/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0) + ..() + initialize_trajectory(_speed, _angle) + if(initial_increment) + increment(initial_increment) + +/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) + . = ..() + starting_x = x + starting_y = y + starting_z = z + +/datum/point/vector/copy_to(datum/point/vector/v = new) + ..(v) + v.speed = speed + v.iteration = iteration + v.angle = angle + v.mpx = mpx + v.mpy = mpy + v.starting_x = starting_x + v.starting_y = starting_y + v.starting_z = starting_z + return v + +/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle) + if(!isnull(pixel_speed)) + speed = pixel_speed + set_angle(new_angle) + +/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270. + if(isnull(angle)) + return + angle = new_angle + update_offsets() + +/datum/point/vector/proc/update_offsets() + mpx = sin(angle) * speed + mpy = cos(angle) * speed + +/datum/point/vector/proc/set_speed(new_speed) + if(isnull(new_speed) || speed == new_speed) + return + speed = new_speed + update_offsets() + +/datum/point/vector/proc/increment(multiplier = 1) + iteration++ + x += mpx * (multiplier) + y += mpy * (multiplier) + +/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE) + var/datum/point/vector/v = copy_to() + if(force_simulate) + for(var/i in 1 to amount) + v.increment(multiplier) + else + v.increment(multiplier * amount) + return v + +/datum/point/vector/proc/on_z_change() + return + +/datum/point/vector/processed //pixel_speed is per decisecond. + var/last_process = 0 + var/last_move = 0 + var/paused = FALSE + +/datum/point/vector/processed/Destroy() + STOP_PROCESSING(SSprojectiles, src) + return ..() + +/datum/point/vector/processed/proc/start() + last_process = world.time + last_move = world.time + START_PROCESSING(SSprojectiles, src) + +/datum/point/vector/processed/process() + if(paused) + last_move += world.time - last_process + last_process = world.time + return + var/needed_time = world.time - last_move + last_process = world.time + last_move = world.time + increment(needed_time / SSprojectiles.wait) diff --git a/code/datums/recipe.dm b/code/datums/recipe.dm index 0cd636bfeb8..11141e3452e 100644 --- a/code/datums/recipe.dm +++ b/code/datums/recipe.dm @@ -1,120 +1,120 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * - * /datum/recipe by rastaf0 13 apr 2011 * - * * * * * * * * * * * * * * * * * * * * * * * * * * - * This is powerful and flexible recipe system. - * It exists not only for food. - * supports both reagents and objects as prerequisites. - * In order to use this system you have to define a deriative from /datum/recipe - * * reagents are reagents. Acid, milc, booze, etc. - * * items are objects. Fruits, tools, circuit boards. - * * result is type to create as new object - * * time is optional parameter, you shall use in in your machine, - default /datum/recipe/ procs does not rely on this parameter. - * - * Functions you need: - * /datum/recipe/proc/make(var/obj/container as obj) - * Creates result inside container, - * deletes prerequisite reagents, - * transfers reagents from prerequisite objects, - * deletes all prerequisite objects (even not needed for recipe at the moment). - * - * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) - * Wonderful function that select suitable recipe for you. - * obj is a machine (or magik hat) with prerequisites, - * exact = 0 forces algorithm to ignore superfluous stuff. - * - * - * Functions you do not need to call directly but could: - * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * /datum/recipe/proc/check_items(var/obj/container as obj) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * */ - -/datum/recipe - var/list/reagents_list // example: = list(/datum/reagent/consumable/berryjuice = 5) // do not list same reagent twice - var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo - var/result //example: = /obj/item/reagent_containers/food/snacks/donut - var/time = 100 // 1/10 part of second - - -/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous - . = 1 - for (var/r_r in reagents_list) - var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) - if (!(abs(aval_r_amnt - reagents_list[r_r])<0.5)) //if NOT equals - if (aval_r_amnt>reagents_list[r_r]) - . = -1 - else - return 0 - if ((reagents_list?(reagents_list.len):(0)) < avail_reagents.reagent_list.len) - return -1 - return . - -/datum/recipe/proc/check_items(obj/container) //1=precisely, 0=insufficiently, -1=superfluous - if (!items) - if (locate(/obj/) in container) - return -1 - else - return 1 - . = 1 - var/list/checklist = items.Copy() - for (var/obj/O in container) - var/found = 0 - for (var/type in checklist) - if (istype(O,type)) - checklist-=type - found = 1 - break - if (!found) - . = -1 - if (checklist.len) - return 0 - return . - -//general version -/datum/recipe/proc/make(obj/container) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -// food-related -/datum/recipe/proc/make_food(obj/container) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - if (O.reagents) - O.reagents.del_reagent(/datum/reagent/consumable/nutriment) - O.reagents.update_total() - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -/proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj, exact = 1 as num) - if (!exact) - exact = -1 - var/list/datum/recipe/possible_recipes = new - for (var/datum/recipe/recipe in avaiable_recipes) - if (recipe.check_reagents(obj.reagents)==exact && recipe.check_items(obj)==exact) - possible_recipes+=recipe - if (possible_recipes.len==0) - return null - else if (possible_recipes.len==1) - return possible_recipes[1] - else //okay, let's select the most complicated recipe - var/r_count = 0 - var/i_count = 0 - . = possible_recipes[1] - for (var/datum/recipe/recipe in possible_recipes) - var/N_i = (recipe.items)?(recipe.items.len):0 - var/N_r = (recipe.reagents_list)?(recipe.reagents_list.len):0 - if (N_i > i_count || (N_i== i_count && N_r > r_count )) - r_count = N_r - i_count = N_i - . = recipe - return . +/* * * * * * * * * * * * * * * * * * * * * * * * * * + * /datum/recipe by rastaf0 13 apr 2011 * + * * * * * * * * * * * * * * * * * * * * * * * * * * + * This is powerful and flexible recipe system. + * It exists not only for food. + * supports both reagents and objects as prerequisites. + * In order to use this system you have to define a deriative from /datum/recipe + * * reagents are reagents. Acid, milc, booze, etc. + * * items are objects. Fruits, tools, circuit boards. + * * result is type to create as new object + * * time is optional parameter, you shall use in in your machine, + default /datum/recipe/ procs does not rely on this parameter. + * + * Functions you need: + * /datum/recipe/proc/make(var/obj/container as obj) + * Creates result inside container, + * deletes prerequisite reagents, + * transfers reagents from prerequisite objects, + * deletes all prerequisite objects (even not needed for recipe at the moment). + * + * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) + * Wonderful function that select suitable recipe for you. + * obj is a machine (or magik hat) with prerequisites, + * exact = 0 forces algorithm to ignore superfluous stuff. + * + * + * Functions you do not need to call directly but could: + * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * /datum/recipe/proc/check_items(var/obj/container as obj) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * */ + +/datum/recipe + var/list/reagents_list // example: = list(/datum/reagent/consumable/berryjuice = 5) // do not list same reagent twice + var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo + var/result //example: = /obj/item/reagent_containers/food/snacks/donut + var/time = 100 // 1/10 part of second + + +/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous + . = 1 + for (var/r_r in reagents_list) + var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) + if (!(abs(aval_r_amnt - reagents_list[r_r])<0.5)) //if NOT equals + if (aval_r_amnt>reagents_list[r_r]) + . = -1 + else + return 0 + if ((reagents_list?(reagents_list.len):(0)) < avail_reagents.reagent_list.len) + return -1 + return . + +/datum/recipe/proc/check_items(obj/container) //1=precisely, 0=insufficiently, -1=superfluous + if (!items) + if (locate(/obj/) in container) + return -1 + else + return 1 + . = 1 + var/list/checklist = items.Copy() + for (var/obj/O in container) + var/found = 0 + for (var/type in checklist) + if (istype(O,type)) + checklist-=type + found = 1 + break + if (!found) + . = -1 + if (checklist.len) + return 0 + return . + +//general version +/datum/recipe/proc/make(obj/container) + var/obj/result_obj = new result(container) + for (var/obj/O in (container.contents-result_obj)) + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +// food-related +/datum/recipe/proc/make_food(obj/container) + var/obj/result_obj = new result(container) + for (var/obj/O in (container.contents-result_obj)) + if (O.reagents) + O.reagents.del_reagent(/datum/reagent/consumable/nutriment) + O.reagents.update_total() + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +/proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj, exact = 1 as num) + if (!exact) + exact = -1 + var/list/datum/recipe/possible_recipes = new + for (var/datum/recipe/recipe in avaiable_recipes) + if (recipe.check_reagents(obj.reagents)==exact && recipe.check_items(obj)==exact) + possible_recipes+=recipe + if (possible_recipes.len==0) + return null + else if (possible_recipes.len==1) + return possible_recipes[1] + else //okay, let's select the most complicated recipe + var/r_count = 0 + var/i_count = 0 + . = possible_recipes[1] + for (var/datum/recipe/recipe in possible_recipes) + var/N_i = (recipe.items)?(recipe.items.len):0 + var/N_r = (recipe.reagents_list)?(recipe.reagents_list.len):0 + if (N_i > i_count || (N_i== i_count && N_r > r_count )) + r_count = N_r + i_count = N_i + . = recipe + return . diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm index a6f49e9c323..88ac3ef1382 100644 --- a/code/datums/spawners_menu.dm +++ b/code/datums/spawners_menu.dm @@ -1,62 +1,62 @@ -/datum/spawners_menu - var/mob/dead/observer/owner - -/datum/spawners_menu/New(mob/dead/observer/new_owner) - if(!istype(new_owner)) - qdel(src) - owner = new_owner - -/datum/spawners_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "SpawnersMenu", "Spawners Menu", 700, 600, master_ui, state) - ui.open() - -/datum/spawners_menu/ui_data(mob/user) - var/list/data = list() - data["spawners"] = list() - for(var/spawner in GLOB.mob_spawners) - var/list/this = list() - this["name"] = spawner - this["short_desc"] = "" - this["flavor_text"] = "" - this["important_warning"] = "" - this["refs"] = list() - for(var/spawner_obj in GLOB.mob_spawners[spawner]) - this["refs"] += "[REF(spawner_obj)]" - if(!this["desc"]) - if(istype(spawner_obj, /obj/effect/mob_spawn)) - var/obj/effect/mob_spawn/MS = spawner_obj - this["short_desc"] = MS.short_desc - this["flavor_text"] = MS.flavour_text - this["important_info"] = MS.important_info - else - var/obj/O = spawner_obj - this["desc"] = O.desc - this["amount_left"] = LAZYLEN(GLOB.mob_spawners[spawner]) - data["spawners"] += list(this) - - return data - -/datum/spawners_menu/ui_act(action, params) - if(..()) - return - - var/group_name = params["name"] - if(!group_name || !(group_name in GLOB.mob_spawners)) - return - var/list/spawnerlist = GLOB.mob_spawners[group_name] - if(!spawnerlist.len) - return - var/obj/effect/mob_spawn/MS = pick(spawnerlist) - if(!istype(MS) || !(MS in GLOB.poi_list)) - return - switch(action) - if("jump") - if(MS) - owner.forceMove(get_turf(MS)) - . = TRUE - if("spawn") - if(MS) - MS.attack_ghost(owner) - . = TRUE +/datum/spawners_menu + var/mob/dead/observer/owner + +/datum/spawners_menu/New(mob/dead/observer/new_owner) + if(!istype(new_owner)) + qdel(src) + owner = new_owner + +/datum/spawners_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "SpawnersMenu", "Spawners Menu", 700, 600, master_ui, state) + ui.open() + +/datum/spawners_menu/ui_data(mob/user) + var/list/data = list() + data["spawners"] = list() + for(var/spawner in GLOB.mob_spawners) + var/list/this = list() + this["name"] = spawner + this["short_desc"] = "" + this["flavor_text"] = "" + this["important_warning"] = "" + this["refs"] = list() + for(var/spawner_obj in GLOB.mob_spawners[spawner]) + this["refs"] += "[REF(spawner_obj)]" + if(!this["desc"]) + if(istype(spawner_obj, /obj/effect/mob_spawn)) + var/obj/effect/mob_spawn/MS = spawner_obj + this["short_desc"] = MS.short_desc + this["flavor_text"] = MS.flavour_text + this["important_info"] = MS.important_info + else + var/obj/O = spawner_obj + this["desc"] = O.desc + this["amount_left"] = LAZYLEN(GLOB.mob_spawners[spawner]) + data["spawners"] += list(this) + + return data + +/datum/spawners_menu/ui_act(action, params) + if(..()) + return + + var/group_name = params["name"] + if(!group_name || !(group_name in GLOB.mob_spawners)) + return + var/list/spawnerlist = GLOB.mob_spawners[group_name] + if(!spawnerlist.len) + return + var/obj/effect/mob_spawn/MS = pick(spawnerlist) + if(!istype(MS) || !(MS in GLOB.poi_list)) + return + switch(action) + if("jump") + if(MS) + owner.forceMove(get_turf(MS)) + . = TRUE + if("spawn") + if(MS) + MS.attack_ghost(owner) + . = TRUE diff --git a/code/datums/verbs.dm b/code/datums/verbs.dm index 8301c5fe384..39afde14d03 100644 --- a/code/datums/verbs.dm +++ b/code/datums/verbs.dm @@ -1,102 +1,102 @@ -/datum/verbs - var/name - var/list/children - var/datum/verbs/parent - var/list/verblist - var/abstract = FALSE - -//returns the master list for verbs of a type -/datum/verbs/proc/GetList() - CRASH("Abstract verblist for [type]") - -//do things for each entry in Generate_list -//return value sets Generate_list[verbpath] -/datum/verbs/proc/HandleVerb(list/entry, procpath/verbpath, ...) - return entry - -/datum/verbs/New() - var/mainlist = GetList() - var/ourentry = mainlist[type] - children = list() - verblist = list() - if (ourentry) - if (!islist(ourentry)) //some of our childern already loaded - qdel(src) - CRASH("Verb double load: [type]") - Add_children(ourentry) - - mainlist[type] = src - - Load_verbs(type, typesof("[type]/verb")) - - var/datum/verbs/parent = mainlist[parent_type] - if (!parent) - mainlist[parent_type] = list(src) - else if (islist(parent)) - parent += src - else - parent.Add_children(list(src)) - -/datum/verbs/proc/Set_parent(datum/verbs/_parent) - parent = _parent - if (abstract) - parent.Add_children(children) - var/list/verblistoftypes = list() - for(var/thing in verblist) - LAZYADD(verblistoftypes[verblist[thing]], thing) - - for(var/verbparenttype in verblistoftypes) - parent.Load_verbs(verbparenttype, verblistoftypes[verbparenttype]) - -/datum/verbs/proc/Add_children(list/kids) - if (abstract && parent) - parent.Add_children(kids) - return - - for(var/thing in kids) - var/datum/verbs/item = thing - item.Set_parent(src) - if (!item.abstract) - children += item - -/datum/verbs/proc/Load_verbs(verb_parent_type, list/verbs) - if (abstract && parent) - parent.Load_verbs(verb_parent_type, verbs) - return - - for (var/verbpath in verbs) - verblist[verbpath] = verb_parent_type - -/datum/verbs/proc/Generate_list(...) - . = list() - if (length(children)) - for (var/thing in children) - var/datum/verbs/child = thing - var/list/childlist = child.Generate_list(arglist(args)) - if (childlist) - var/childname = "[child]" - if (childname == "[child.type]") - var/list/tree = splittext(childname, "/") - childname = tree[tree.len] - .[child.type] = "parent=[url_encode(type)];name=[childname]" - . += childlist - - for (var/thing in verblist) - var/procpath/verbpath = thing - if (!verbpath) - stack_trace("Bad VERB in [type] verblist: [english_list(verblist)]") - var/list/entry = list() - entry["parent"] = "[type]" - entry["name"] = verbpath.desc - if (verbpath.name[1] == "@") - entry["command"] = copytext(verbpath.name, length(verbpath.name[1]) + 1) - else - entry["command"] = replacetext(verbpath.name, " ", "-") - - .[verbpath] = HandleVerb(arglist(list(entry, verbpath) + args)) - -/world/proc/LoadVerbs(verb_type) - if(!ispath(verb_type, /datum/verbs) || verb_type == /datum/verbs) - CRASH("Invalid verb_type: [verb_type]") - for (var/typepath in subtypesof(verb_type)) - new typepath() +/datum/verbs + var/name + var/list/children + var/datum/verbs/parent + var/list/verblist + var/abstract = FALSE + +//returns the master list for verbs of a type +/datum/verbs/proc/GetList() + CRASH("Abstract verblist for [type]") + +//do things for each entry in Generate_list +//return value sets Generate_list[verbpath] +/datum/verbs/proc/HandleVerb(list/entry, procpath/verbpath, ...) + return entry + +/datum/verbs/New() + var/mainlist = GetList() + var/ourentry = mainlist[type] + children = list() + verblist = list() + if (ourentry) + if (!islist(ourentry)) //some of our childern already loaded + qdel(src) + CRASH("Verb double load: [type]") + Add_children(ourentry) + + mainlist[type] = src + + Load_verbs(type, typesof("[type]/verb")) + + var/datum/verbs/parent = mainlist[parent_type] + if (!parent) + mainlist[parent_type] = list(src) + else if (islist(parent)) + parent += src + else + parent.Add_children(list(src)) + +/datum/verbs/proc/Set_parent(datum/verbs/_parent) + parent = _parent + if (abstract) + parent.Add_children(children) + var/list/verblistoftypes = list() + for(var/thing in verblist) + LAZYADD(verblistoftypes[verblist[thing]], thing) + + for(var/verbparenttype in verblistoftypes) + parent.Load_verbs(verbparenttype, verblistoftypes[verbparenttype]) + +/datum/verbs/proc/Add_children(list/kids) + if (abstract && parent) + parent.Add_children(kids) + return + + for(var/thing in kids) + var/datum/verbs/item = thing + item.Set_parent(src) + if (!item.abstract) + children += item + +/datum/verbs/proc/Load_verbs(verb_parent_type, list/verbs) + if (abstract && parent) + parent.Load_verbs(verb_parent_type, verbs) + return + + for (var/verbpath in verbs) + verblist[verbpath] = verb_parent_type + +/datum/verbs/proc/Generate_list(...) + . = list() + if (length(children)) + for (var/thing in children) + var/datum/verbs/child = thing + var/list/childlist = child.Generate_list(arglist(args)) + if (childlist) + var/childname = "[child]" + if (childname == "[child.type]") + var/list/tree = splittext(childname, "/") + childname = tree[tree.len] + .[child.type] = "parent=[url_encode(type)];name=[childname]" + . += childlist + + for (var/thing in verblist) + var/procpath/verbpath = thing + if (!verbpath) + stack_trace("Bad VERB in [type] verblist: [english_list(verblist)]") + var/list/entry = list() + entry["parent"] = "[type]" + entry["name"] = verbpath.desc + if (verbpath.name[1] == "@") + entry["command"] = copytext(verbpath.name, length(verbpath.name[1]) + 1) + else + entry["command"] = replacetext(verbpath.name, " ", "-") + + .[verbpath] = HandleVerb(arglist(list(entry, verbpath) + args)) + +/world/proc/LoadVerbs(verb_type) + if(!ispath(verb_type, /datum/verbs) || verb_type == /datum/verbs) + CRASH("Invalid verb_type: [verb_type]") + for (var/typepath in subtypesof(verb_type)) + new typepath() diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm index 46d1a7261f6..942c2ca4035 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -1,297 +1,297 @@ -#define MAXIMUM_EMP_WIRES 3 - -/proc/is_wire_tool(obj/item/I) - if(!I) - return - - if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) - return TRUE - if(istype(I, /obj/item/assembly)) - var/obj/item/assembly/A = I - if(A.attachable) - return TRUE - -/atom/proc/attempt_wire_interaction(mob/user) - if(!wires) - return WIRE_INTERACTION_FAIL - if(!user.CanReach(src)) - return WIRE_INTERACTION_FAIL - wires.interact(user) - return WIRE_INTERACTION_BLOCK - -/datum/wires - var/atom/holder = null // The holder (atom that contains these wires). - var/holder_type = null // The holder's typepath (used to make wire colors common to all holders). - var/proper_name = "Unknown" // The display name for the wire set shown in station blueprints. Not used if randomize is true or it's an item NT wouldn't know about (Explosives/Nuke) - - var/list/wires = list() // List of wires. - var/list/cut_wires = list() // List of wires that have been cut. - var/list/colors = list() // Dictionary of colors to wire. - var/list/assemblies = list() // List of attached assemblies. - var/randomize = 0 // If every instance of these wires should be random. - // Prevents wires from showing up in station blueprints - -/datum/wires/New(atom/holder) - ..() - if(!istype(holder, holder_type)) - CRASH("Wire holder is not of the expected type!") - - src.holder = holder - if(randomize) - randomize() - else - if(!GLOB.wire_color_directory[holder_type]) - randomize() - GLOB.wire_color_directory[holder_type] = colors - GLOB.wire_name_directory[holder_type] = proper_name - else - colors = GLOB.wire_color_directory[holder_type] - -/datum/wires/Destroy() - holder = null - assemblies = list() - return ..() - -/datum/wires/proc/add_duds(duds) - while(duds) - var/dud = WIRE_DUD_PREFIX + "[--duds]" - if(dud in wires) - continue - wires += dud - -/datum/wires/proc/randomize() - var/static/list/possible_colors = list( - "blue", - "brown", - "crimson", - "cyan", - "gold", - "grey", - "green", - "magenta", - "orange", - "pink", - "purple", - "red", - "silver", - "violet", - "white", - "yellow" - ) - - var/list/my_possible_colors = possible_colors.Copy() - - for(var/wire in shuffle(wires)) - colors[pick_n_take(my_possible_colors)] = wire - -/datum/wires/proc/shuffle_wires() - colors.Cut() - randomize() - -/datum/wires/proc/repair() - cut_wires.Cut() - -/datum/wires/proc/get_wire(color) - return colors[color] - -/datum/wires/proc/get_color_of_wire(wire_type) - for(var/color in colors) - var/other_type = colors[color] - if(wire_type == other_type) - return color - -/datum/wires/proc/get_attached(color) - if(assemblies[color]) - return assemblies[color] - return null - -/datum/wires/proc/is_attached(color) - if(assemblies[color]) - return TRUE - -/datum/wires/proc/is_cut(wire) - return (wire in cut_wires) - -/datum/wires/proc/is_color_cut(color) - return is_cut(get_wire(color)) - -/datum/wires/proc/is_all_cut() - if(cut_wires.len == wires.len) - return TRUE - -/datum/wires/proc/is_dud(wire) - return findtext(wire, WIRE_DUD_PREFIX, 1, length(WIRE_DUD_PREFIX) + 1) - -/datum/wires/proc/is_dud_color(color) - return is_dud(get_wire(color)) - -/datum/wires/proc/cut(wire) - if(is_cut(wire)) - cut_wires -= wire - on_cut(wire, mend = TRUE) - else - cut_wires += wire - on_cut(wire, mend = FALSE) - -/datum/wires/proc/cut_color(color) - cut(get_wire(color)) - -/datum/wires/proc/cut_random() - cut(wires[rand(1, wires.len)]) - -/datum/wires/proc/cut_all() - for(var/wire in wires) - cut(wire) - -/datum/wires/proc/pulse(wire, user) - if(is_cut(wire)) - return - on_pulse(wire, user) - -/datum/wires/proc/pulse_color(color, mob/living/user) - pulse(get_wire(color), user) - -/datum/wires/proc/pulse_assembly(obj/item/assembly/S) - for(var/color in assemblies) - if(S == assemblies[color]) - pulse_color(color) - return TRUE - -/datum/wires/proc/attach_assembly(color, obj/item/assembly/S) - if(S && istype(S) && S.attachable && !is_attached(color)) - assemblies[color] = S - S.forceMove(holder) - S.connected = src - return S - -/datum/wires/proc/detach_assembly(color) - var/obj/item/assembly/S = get_attached(color) - if(S && istype(S)) - assemblies -= color - S.connected = null - S.forceMove(holder.drop_location()) - return S - -/// Called from [/atom/proc/emp_act] -/datum/wires/proc/emp_pulse() - var/list/possible_wires = shuffle(wires) - var/remaining_pulses = MAXIMUM_EMP_WIRES - - for(var/wire in possible_wires) - if(prob(33)) - pulse(wire) - remaining_pulses-- - if(!remaining_pulses) - break - -// Overridable Procs -/datum/wires/proc/interactable(mob/user) - return TRUE - -/datum/wires/proc/get_status() - return list() - -/datum/wires/proc/on_cut(wire, mend = FALSE) - return - -/datum/wires/proc/on_pulse(wire, user) - return -// End Overridable Procs - -/datum/wires/proc/interact(mob/user) - if(!interactable(user)) - return - ui_interact(user) - for(var/A in assemblies) - var/obj/item/I = assemblies[A] - if(istype(I) && I.on_found(user)) - return - -/datum/wires/ui_host() - return holder - -/datum/wires/ui_status(mob/user) - if(interactable(user)) - return ..() - return UI_CLOSE - -/datum/wires/ui_interact(mob/user, ui_key = "wires", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if (!ui) - ui = new(user, src, ui_key, "Wires", "[holder.name] Wires", 350, 150 + wires.len * 30, master_ui, state) - ui.open() - -/datum/wires/ui_data(mob/user) - var/list/data = list() - var/list/payload = list() - var/reveal_wires = FALSE - - // Admin ghost can see a purpose of each wire. - if(IsAdminGhost(user)) - reveal_wires = TRUE - - // Same for anyone with an abductor multitool. - else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) - reveal_wires = TRUE - - // Station blueprints do that too, but only if the wires are not randomized. - else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize) - reveal_wires = TRUE - - for(var/color in colors) - payload.Add(list(list( - "color" = color, - "wire" = ((reveal_wires && !is_dud_color(color)) ? get_wire(color) : null), - "cut" = is_color_cut(color), - "attached" = is_attached(color) - ))) - data["wires"] = payload - data["status"] = get_status() - return data - -/datum/wires/ui_act(action, params) - if(..() || !interactable(usr)) - return - var/target_wire = params["wire"] - var/mob/living/L = usr - var/obj/item/I - switch(action) - if("cut") - I = L.is_holding_tool_quality(TOOL_WIRECUTTER) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) - cut_color(target_wire) - . = TRUE - else - to_chat(L, "You need wirecutters!") - if("pulse") - I = L.is_holding_tool_quality(TOOL_MULTITOOL) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) - pulse_color(target_wire, L) - . = TRUE - else - to_chat(L, "You need a multitool!") - if("attach") - if(is_attached(target_wire)) - I = detach_assembly(target_wire) - if(I) - L.put_in_hands(I) - . = TRUE - else - I = L.get_active_held_item() - if(istype(I, /obj/item/assembly)) - var/obj/item/assembly/A = I - if(A.attachable) - if(!L.temporarilyRemoveItemFromInventory(A)) - return - if(!attach_assembly(target_wire, A)) - A.forceMove(L.drop_location()) - . = TRUE - else - to_chat(L, "You need an attachable assembly!") - -#undef MAXIMUM_EMP_WIRES +#define MAXIMUM_EMP_WIRES 3 + +/proc/is_wire_tool(obj/item/I) + if(!I) + return + + if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) + return TRUE + if(istype(I, /obj/item/assembly)) + var/obj/item/assembly/A = I + if(A.attachable) + return TRUE + +/atom/proc/attempt_wire_interaction(mob/user) + if(!wires) + return WIRE_INTERACTION_FAIL + if(!user.CanReach(src)) + return WIRE_INTERACTION_FAIL + wires.interact(user) + return WIRE_INTERACTION_BLOCK + +/datum/wires + var/atom/holder = null // The holder (atom that contains these wires). + var/holder_type = null // The holder's typepath (used to make wire colors common to all holders). + var/proper_name = "Unknown" // The display name for the wire set shown in station blueprints. Not used if randomize is true or it's an item NT wouldn't know about (Explosives/Nuke) + + var/list/wires = list() // List of wires. + var/list/cut_wires = list() // List of wires that have been cut. + var/list/colors = list() // Dictionary of colors to wire. + var/list/assemblies = list() // List of attached assemblies. + var/randomize = 0 // If every instance of these wires should be random. + // Prevents wires from showing up in station blueprints + +/datum/wires/New(atom/holder) + ..() + if(!istype(holder, holder_type)) + CRASH("Wire holder is not of the expected type!") + + src.holder = holder + if(randomize) + randomize() + else + if(!GLOB.wire_color_directory[holder_type]) + randomize() + GLOB.wire_color_directory[holder_type] = colors + GLOB.wire_name_directory[holder_type] = proper_name + else + colors = GLOB.wire_color_directory[holder_type] + +/datum/wires/Destroy() + holder = null + assemblies = list() + return ..() + +/datum/wires/proc/add_duds(duds) + while(duds) + var/dud = WIRE_DUD_PREFIX + "[--duds]" + if(dud in wires) + continue + wires += dud + +/datum/wires/proc/randomize() + var/static/list/possible_colors = list( + "blue", + "brown", + "crimson", + "cyan", + "gold", + "grey", + "green", + "magenta", + "orange", + "pink", + "purple", + "red", + "silver", + "violet", + "white", + "yellow" + ) + + var/list/my_possible_colors = possible_colors.Copy() + + for(var/wire in shuffle(wires)) + colors[pick_n_take(my_possible_colors)] = wire + +/datum/wires/proc/shuffle_wires() + colors.Cut() + randomize() + +/datum/wires/proc/repair() + cut_wires.Cut() + +/datum/wires/proc/get_wire(color) + return colors[color] + +/datum/wires/proc/get_color_of_wire(wire_type) + for(var/color in colors) + var/other_type = colors[color] + if(wire_type == other_type) + return color + +/datum/wires/proc/get_attached(color) + if(assemblies[color]) + return assemblies[color] + return null + +/datum/wires/proc/is_attached(color) + if(assemblies[color]) + return TRUE + +/datum/wires/proc/is_cut(wire) + return (wire in cut_wires) + +/datum/wires/proc/is_color_cut(color) + return is_cut(get_wire(color)) + +/datum/wires/proc/is_all_cut() + if(cut_wires.len == wires.len) + return TRUE + +/datum/wires/proc/is_dud(wire) + return findtext(wire, WIRE_DUD_PREFIX, 1, length(WIRE_DUD_PREFIX) + 1) + +/datum/wires/proc/is_dud_color(color) + return is_dud(get_wire(color)) + +/datum/wires/proc/cut(wire) + if(is_cut(wire)) + cut_wires -= wire + on_cut(wire, mend = TRUE) + else + cut_wires += wire + on_cut(wire, mend = FALSE) + +/datum/wires/proc/cut_color(color) + cut(get_wire(color)) + +/datum/wires/proc/cut_random() + cut(wires[rand(1, wires.len)]) + +/datum/wires/proc/cut_all() + for(var/wire in wires) + cut(wire) + +/datum/wires/proc/pulse(wire, user) + if(is_cut(wire)) + return + on_pulse(wire, user) + +/datum/wires/proc/pulse_color(color, mob/living/user) + pulse(get_wire(color), user) + +/datum/wires/proc/pulse_assembly(obj/item/assembly/S) + for(var/color in assemblies) + if(S == assemblies[color]) + pulse_color(color) + return TRUE + +/datum/wires/proc/attach_assembly(color, obj/item/assembly/S) + if(S && istype(S) && S.attachable && !is_attached(color)) + assemblies[color] = S + S.forceMove(holder) + S.connected = src + return S + +/datum/wires/proc/detach_assembly(color) + var/obj/item/assembly/S = get_attached(color) + if(S && istype(S)) + assemblies -= color + S.connected = null + S.forceMove(holder.drop_location()) + return S + +/// Called from [/atom/proc/emp_act] +/datum/wires/proc/emp_pulse() + var/list/possible_wires = shuffle(wires) + var/remaining_pulses = MAXIMUM_EMP_WIRES + + for(var/wire in possible_wires) + if(prob(33)) + pulse(wire) + remaining_pulses-- + if(!remaining_pulses) + break + +// Overridable Procs +/datum/wires/proc/interactable(mob/user) + return TRUE + +/datum/wires/proc/get_status() + return list() + +/datum/wires/proc/on_cut(wire, mend = FALSE) + return + +/datum/wires/proc/on_pulse(wire, user) + return +// End Overridable Procs + +/datum/wires/proc/interact(mob/user) + if(!interactable(user)) + return + ui_interact(user) + for(var/A in assemblies) + var/obj/item/I = assemblies[A] + if(istype(I) && I.on_found(user)) + return + +/datum/wires/ui_host() + return holder + +/datum/wires/ui_status(mob/user) + if(interactable(user)) + return ..() + return UI_CLOSE + +/datum/wires/ui_interact(mob/user, ui_key = "wires", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + ui = new(user, src, ui_key, "Wires", "[holder.name] Wires", 350, 150 + wires.len * 30, master_ui, state) + ui.open() + +/datum/wires/ui_data(mob/user) + var/list/data = list() + var/list/payload = list() + var/reveal_wires = FALSE + + // Admin ghost can see a purpose of each wire. + if(IsAdminGhost(user)) + reveal_wires = TRUE + + // Same for anyone with an abductor multitool. + else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) + reveal_wires = TRUE + + // Station blueprints do that too, but only if the wires are not randomized. + else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize) + reveal_wires = TRUE + + for(var/color in colors) + payload.Add(list(list( + "color" = color, + "wire" = ((reveal_wires && !is_dud_color(color)) ? get_wire(color) : null), + "cut" = is_color_cut(color), + "attached" = is_attached(color) + ))) + data["wires"] = payload + data["status"] = get_status() + return data + +/datum/wires/ui_act(action, params) + if(..() || !interactable(usr)) + return + var/target_wire = params["wire"] + var/mob/living/L = usr + var/obj/item/I + switch(action) + if("cut") + I = L.is_holding_tool_quality(TOOL_WIRECUTTER) + if(I || IsAdminGhost(usr)) + if(I && holder) + I.play_tool_sound(holder, 20) + cut_color(target_wire) + . = TRUE + else + to_chat(L, "You need wirecutters!") + if("pulse") + I = L.is_holding_tool_quality(TOOL_MULTITOOL) + if(I || IsAdminGhost(usr)) + if(I && holder) + I.play_tool_sound(holder, 20) + pulse_color(target_wire, L) + . = TRUE + else + to_chat(L, "You need a multitool!") + if("attach") + if(is_attached(target_wire)) + I = detach_assembly(target_wire) + if(I) + L.put_in_hands(I) + . = TRUE + else + I = L.get_active_held_item() + if(istype(I, /obj/item/assembly)) + var/obj/item/assembly/A = I + if(A.attachable) + if(!L.temporarilyRemoveItemFromInventory(A)) + return + if(!attach_assembly(target_wire, A)) + A.forceMove(L.drop_location()) + . = TRUE + else + to_chat(L, "You need an attachable assembly!") + +#undef MAXIMUM_EMP_WIRES diff --git a/code/datums/wires/airalarm.dm b/code/datums/wires/airalarm.dm index 237d36f51bd..c46e06a4adf 100644 --- a/code/datums/wires/airalarm.dm +++ b/code/datums/wires/airalarm.dm @@ -1,74 +1,74 @@ -/datum/wires/airalarm - holder_type = /obj/machinery/airalarm - proper_name = "Air Alarm" - -/datum/wires/airalarm/New(atom/holder) - wires = list( - WIRE_POWER, - WIRE_IDSCAN, WIRE_AI, - WIRE_PANIC, WIRE_ALARM - ) - add_duds(3) - ..() - -/datum/wires/airalarm/interactable(mob/user) - var/obj/machinery/airalarm/A = holder - if(A.panel_open && A.buildstage == 2) - return TRUE - -/datum/wires/airalarm/get_status() - var/obj/machinery/airalarm/A = holder - var/list/status = list() - status += "The interface light is [A.locked ? "red" : "green"]." - status += "The short indicator is [A.shorted ? "lit" : "off"]." - status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." - return status - -/datum/wires/airalarm/on_pulse(wire) - var/obj/machinery/airalarm/A = holder - switch(wire) - if(WIRE_POWER) // Short out for a long time. - if(!A.shorted) - A.shorted = TRUE - A.update_icon() - addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 1200) - if(WIRE_IDSCAN) // Toggle lock. - A.locked = !A.locked - if(WIRE_AI) // Disable AI control for a while. - if(!A.aidisabled) - A.aidisabled = TRUE - addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 100) - if(WIRE_PANIC) // Toggle panic siphon. - if(!A.shorted) - if(A.mode == 1) // AALARM_MODE_SCRUB - A.mode = 3 // AALARM_MODE_PANIC - else - A.mode = 1 // AALARM_MODE_SCRUB - A.apply_mode(usr) - if(WIRE_ALARM) // Clear alarms. - var/area/AA = get_area(A) - if(AA.atmosalert(0, holder)) - A.post_alert(0) - A.update_icon() - -/datum/wires/airalarm/on_cut(wire, mend) - var/obj/machinery/airalarm/A = holder - switch(wire) - if(WIRE_POWER) // Short out forever. - A.shock(usr, 50) - A.shorted = !mend - A.update_icon() - if(WIRE_IDSCAN) - if(!mend) - A.locked = TRUE - if(WIRE_AI) - A.aidisabled = mend // Enable/disable AI control. - if(WIRE_PANIC) // Force panic syphon on. - if(!mend && !A.shorted) - A.mode = 3 // AALARM_MODE_PANIC - A.apply_mode(usr) - if(WIRE_ALARM) // Post alarm. - var/area/AA = get_area(A) - if(AA.atmosalert(2, holder)) - A.post_alert(2) - A.update_icon() +/datum/wires/airalarm + holder_type = /obj/machinery/airalarm + proper_name = "Air Alarm" + +/datum/wires/airalarm/New(atom/holder) + wires = list( + WIRE_POWER, + WIRE_IDSCAN, WIRE_AI, + WIRE_PANIC, WIRE_ALARM + ) + add_duds(3) + ..() + +/datum/wires/airalarm/interactable(mob/user) + var/obj/machinery/airalarm/A = holder + if(A.panel_open && A.buildstage == 2) + return TRUE + +/datum/wires/airalarm/get_status() + var/obj/machinery/airalarm/A = holder + var/list/status = list() + status += "The interface light is [A.locked ? "red" : "green"]." + status += "The short indicator is [A.shorted ? "lit" : "off"]." + status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." + return status + +/datum/wires/airalarm/on_pulse(wire) + var/obj/machinery/airalarm/A = holder + switch(wire) + if(WIRE_POWER) // Short out for a long time. + if(!A.shorted) + A.shorted = TRUE + A.update_icon() + addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 1200) + if(WIRE_IDSCAN) // Toggle lock. + A.locked = !A.locked + if(WIRE_AI) // Disable AI control for a while. + if(!A.aidisabled) + A.aidisabled = TRUE + addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 100) + if(WIRE_PANIC) // Toggle panic siphon. + if(!A.shorted) + if(A.mode == 1) // AALARM_MODE_SCRUB + A.mode = 3 // AALARM_MODE_PANIC + else + A.mode = 1 // AALARM_MODE_SCRUB + A.apply_mode(usr) + if(WIRE_ALARM) // Clear alarms. + var/area/AA = get_area(A) + if(AA.atmosalert(0, holder)) + A.post_alert(0) + A.update_icon() + +/datum/wires/airalarm/on_cut(wire, mend) + var/obj/machinery/airalarm/A = holder + switch(wire) + if(WIRE_POWER) // Short out forever. + A.shock(usr, 50) + A.shorted = !mend + A.update_icon() + if(WIRE_IDSCAN) + if(!mend) + A.locked = TRUE + if(WIRE_AI) + A.aidisabled = mend // Enable/disable AI control. + if(WIRE_PANIC) // Force panic syphon on. + if(!mend && !A.shorted) + A.mode = 3 // AALARM_MODE_PANIC + A.apply_mode(usr) + if(WIRE_ALARM) // Post alarm. + var/area/AA = get_area(A) + if(AA.atmosalert(2, holder)) + A.post_alert(2) + A.update_icon() diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm index 13e5c0aa495..f06bba64867 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -1,140 +1,140 @@ -/datum/wires/airlock - holder_type = /obj/machinery/door/airlock - proper_name = "Airlock" - -/datum/wires/airlock/secure - randomize = TRUE - -/datum/wires/airlock/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_BACKUP1, WIRE_BACKUP2, - WIRE_OPEN, WIRE_BOLTS, WIRE_IDSCAN, WIRE_AI, - WIRE_SHOCK, WIRE_SAFETY, WIRE_TIMING, WIRE_LIGHT, - WIRE_ZAP1, WIRE_ZAP2 - ) - add_duds(2) - ..() - -/datum/wires/airlock/interactable(mob/user) - var/obj/machinery/door/airlock/A = holder - if(!issilicon(user) && A.isElectrified() && A.shock(user, 100)) - return FALSE - if(A.panel_open) - return TRUE - -/datum/wires/airlock/get_status() - var/obj/machinery/door/airlock/A = holder - var/list/status = list() - status += "The door bolts [A.locked ? "have fallen!" : "look up."]" - status += "The test light is [A.hasPower() ? "on" : "off"]." - status += "The AI connection light is [A.aiControlDisabled || (A.obj_flags & EMAGGED) ? "off" : "on"]." - status += "The check wiring light is [A.safe ? "off" : "on"]." - status += "The timer is powered [A.autoclose ? "on" : "off"]." - status += "The speed light is [A.normalspeed ? "on" : "off"]." - status += "The emergency light is [A.emergency ? "on" : "off"]." - return status - -/datum/wires/airlock/on_pulse(wire) - set waitfor = FALSE - var/obj/machinery/door/airlock/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Pulse to loose power. - A.loseMainPower() - if(WIRE_BACKUP1, WIRE_BACKUP2) // Pulse to loose backup power. - A.loseBackupPower() - if(WIRE_OPEN) // Pulse to open door (only works not emagged and ID wire is cut or no access is required). - if(A.obj_flags & EMAGGED) - return - if(!A.requiresID() || A.check_access(null)) - if(A.density) - INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/open) - else - INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/close) - if(WIRE_BOLTS) // Pulse to toggle bolts (but only raise if power is on). - if(!A.locked) - A.bolt() - else - if(A.hasPower()) - A.unbolt() - A.update_icon() - if(WIRE_IDSCAN) // Pulse to disable emergency access and flash red lights. - if(A.hasPower() && A.density) - A.do_animate("deny") - if(A.emergency) - A.emergency = FALSE - A.update_icon() - if(WIRE_AI) // Pulse to disable WIRE_AI control for 10 ticks (follows same rules as cutting). - if(A.aiControlDisabled == 0) - A.aiControlDisabled = 1 - else if(A.aiControlDisabled == -1) - A.aiControlDisabled = 2 - sleep(10) - if(A) - if(A.aiControlDisabled == 1) - A.aiControlDisabled = 0 - else if(A.aiControlDisabled == 2) - A.aiControlDisabled = -1 - if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks. - if(!A.secondsElectrified) - A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr) - if(WIRE_SAFETY) - A.safe = !A.safe - if(!A.density) - A.close() - if(WIRE_TIMING) - A.normalspeed = !A.normalspeed - if(WIRE_LIGHT) - A.lights = !A.lights - A.update_icon() - -/datum/wires/airlock/on_cut(wire, mend) - var/obj/machinery/door/airlock/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Cut to loose power, repair all to gain power. - if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) - A.regainMainPower() - else - A.loseMainPower() - if(isliving(usr)) - A.shock(usr, 50) - if(WIRE_BACKUP1, WIRE_BACKUP2) // Cut to loose backup power, repair all to gain backup power. - if(mend && !is_cut(WIRE_BACKUP1) && !is_cut(WIRE_BACKUP2)) - A.regainBackupPower() - else - A.loseBackupPower() - if(isliving(usr)) - A.shock(usr, 50) - if(WIRE_BOLTS) // Cut to drop bolts, mend does nothing. - if(!mend) - A.bolt() - if(WIRE_AI) // Cut to disable WIRE_AI control, mend to re-enable. - if(mend) - if(A.aiControlDisabled == 1) // 0 = normal, 1 = locked out, 2 = overridden by WIRE_AI, -1 = previously overridden by WIRE_AI - A.aiControlDisabled = 0 - else if(A.aiControlDisabled == 2) - A.aiControlDisabled = -1 - else - if(A.aiControlDisabled == 0) - A.aiControlDisabled = 1 - else if(A.aiControlDisabled == -1) - A.aiControlDisabled = 2 - if(WIRE_SHOCK) // Cut to shock the door, mend to unshock. - if(mend) - if(A.secondsElectrified) - A.set_electrified(MACHINE_NOT_ELECTRIFIED, usr) - else - if(A.secondsElectrified != MACHINE_ELECTRIFIED_PERMANENT) - A.set_electrified(MACHINE_ELECTRIFIED_PERMANENT, usr) - if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable. - A.safe = mend - if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable. - A.autoclose = mend - if(A.autoclose && !A.density) - A.close() - if(WIRE_LIGHT) // Cut to disable lights, mend to re-enable. - A.lights = mend - A.update_icon() - if(WIRE_ZAP1, WIRE_ZAP2) // Ouch. - if(isliving(usr)) - A.shock(usr, 50) +/datum/wires/airlock + holder_type = /obj/machinery/door/airlock + proper_name = "Airlock" + +/datum/wires/airlock/secure + randomize = TRUE + +/datum/wires/airlock/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_BACKUP1, WIRE_BACKUP2, + WIRE_OPEN, WIRE_BOLTS, WIRE_IDSCAN, WIRE_AI, + WIRE_SHOCK, WIRE_SAFETY, WIRE_TIMING, WIRE_LIGHT, + WIRE_ZAP1, WIRE_ZAP2 + ) + add_duds(2) + ..() + +/datum/wires/airlock/interactable(mob/user) + var/obj/machinery/door/airlock/A = holder + if(!issilicon(user) && A.isElectrified() && A.shock(user, 100)) + return FALSE + if(A.panel_open) + return TRUE + +/datum/wires/airlock/get_status() + var/obj/machinery/door/airlock/A = holder + var/list/status = list() + status += "The door bolts [A.locked ? "have fallen!" : "look up."]" + status += "The test light is [A.hasPower() ? "on" : "off"]." + status += "The AI connection light is [A.aiControlDisabled || (A.obj_flags & EMAGGED) ? "off" : "on"]." + status += "The check wiring light is [A.safe ? "off" : "on"]." + status += "The timer is powered [A.autoclose ? "on" : "off"]." + status += "The speed light is [A.normalspeed ? "on" : "off"]." + status += "The emergency light is [A.emergency ? "on" : "off"]." + return status + +/datum/wires/airlock/on_pulse(wire) + set waitfor = FALSE + var/obj/machinery/door/airlock/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Pulse to loose power. + A.loseMainPower() + if(WIRE_BACKUP1, WIRE_BACKUP2) // Pulse to loose backup power. + A.loseBackupPower() + if(WIRE_OPEN) // Pulse to open door (only works not emagged and ID wire is cut or no access is required). + if(A.obj_flags & EMAGGED) + return + if(!A.requiresID() || A.check_access(null)) + if(A.density) + INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/open) + else + INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/close) + if(WIRE_BOLTS) // Pulse to toggle bolts (but only raise if power is on). + if(!A.locked) + A.bolt() + else + if(A.hasPower()) + A.unbolt() + A.update_icon() + if(WIRE_IDSCAN) // Pulse to disable emergency access and flash red lights. + if(A.hasPower() && A.density) + A.do_animate("deny") + if(A.emergency) + A.emergency = FALSE + A.update_icon() + if(WIRE_AI) // Pulse to disable WIRE_AI control for 10 ticks (follows same rules as cutting). + if(A.aiControlDisabled == 0) + A.aiControlDisabled = 1 + else if(A.aiControlDisabled == -1) + A.aiControlDisabled = 2 + sleep(10) + if(A) + if(A.aiControlDisabled == 1) + A.aiControlDisabled = 0 + else if(A.aiControlDisabled == 2) + A.aiControlDisabled = -1 + if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks. + if(!A.secondsElectrified) + A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr) + if(WIRE_SAFETY) + A.safe = !A.safe + if(!A.density) + A.close() + if(WIRE_TIMING) + A.normalspeed = !A.normalspeed + if(WIRE_LIGHT) + A.lights = !A.lights + A.update_icon() + +/datum/wires/airlock/on_cut(wire, mend) + var/obj/machinery/door/airlock/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Cut to loose power, repair all to gain power. + if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) + A.regainMainPower() + else + A.loseMainPower() + if(isliving(usr)) + A.shock(usr, 50) + if(WIRE_BACKUP1, WIRE_BACKUP2) // Cut to loose backup power, repair all to gain backup power. + if(mend && !is_cut(WIRE_BACKUP1) && !is_cut(WIRE_BACKUP2)) + A.regainBackupPower() + else + A.loseBackupPower() + if(isliving(usr)) + A.shock(usr, 50) + if(WIRE_BOLTS) // Cut to drop bolts, mend does nothing. + if(!mend) + A.bolt() + if(WIRE_AI) // Cut to disable WIRE_AI control, mend to re-enable. + if(mend) + if(A.aiControlDisabled == 1) // 0 = normal, 1 = locked out, 2 = overridden by WIRE_AI, -1 = previously overridden by WIRE_AI + A.aiControlDisabled = 0 + else if(A.aiControlDisabled == 2) + A.aiControlDisabled = -1 + else + if(A.aiControlDisabled == 0) + A.aiControlDisabled = 1 + else if(A.aiControlDisabled == -1) + A.aiControlDisabled = 2 + if(WIRE_SHOCK) // Cut to shock the door, mend to unshock. + if(mend) + if(A.secondsElectrified) + A.set_electrified(MACHINE_NOT_ELECTRIFIED, usr) + else + if(A.secondsElectrified != MACHINE_ELECTRIFIED_PERMANENT) + A.set_electrified(MACHINE_ELECTRIFIED_PERMANENT, usr) + if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable. + A.safe = mend + if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable. + A.autoclose = mend + if(A.autoclose && !A.density) + A.close() + if(WIRE_LIGHT) // Cut to disable lights, mend to re-enable. + A.lights = mend + A.update_icon() + if(WIRE_ZAP1, WIRE_ZAP2) // Ouch. + if(isliving(usr)) + A.shock(usr, 50) diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm index 9fa90221786..e97c1f3654e 100644 --- a/code/datums/wires/apc.dm +++ b/code/datums/wires/apc.dm @@ -1,55 +1,55 @@ -/datum/wires/apc - holder_type = /obj/machinery/power/apc - proper_name = "APC" - -/datum/wires/apc/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_IDSCAN, WIRE_AI - ) - add_duds(6) - ..() - -/datum/wires/apc/interactable(mob/user) - var/obj/machinery/power/apc/A = holder - if(A.panel_open && !A.opened) - return TRUE - -/datum/wires/apc/get_status() - var/obj/machinery/power/apc/A = holder - var/list/status = list() - status += "The interface light is [A.locked ? "red" : "green"]." - status += "The short indicator is [A.shorted ? "lit" : "off"]." - status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." - return status - -/datum/wires/apc/on_pulse(wire) - var/obj/machinery/power/apc/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Short for a long while. - if(!A.shorted) - A.shorted = TRUE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 1200) - if(WIRE_IDSCAN) // Unlock for a little while. - A.locked = FALSE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 300) - if(WIRE_AI) // Disable AI control for a very short time. - if(!A.aidisabled) - A.aidisabled = TRUE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 10) - -/datum/wires/apc/on_cut(index, mend) - var/obj/machinery/power/apc/A = holder - switch(index) - if(WIRE_POWER1, WIRE_POWER2) // Short out. - if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) - A.shorted = FALSE - A.shock(usr, 50) - else - A.shorted = TRUE - A.shock(usr, 50) - if(WIRE_AI) // Disable AI control. - if(mend) - A.aidisabled = FALSE - else - A.aidisabled = TRUE +/datum/wires/apc + holder_type = /obj/machinery/power/apc + proper_name = "APC" + +/datum/wires/apc/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_IDSCAN, WIRE_AI + ) + add_duds(6) + ..() + +/datum/wires/apc/interactable(mob/user) + var/obj/machinery/power/apc/A = holder + if(A.panel_open && !A.opened) + return TRUE + +/datum/wires/apc/get_status() + var/obj/machinery/power/apc/A = holder + var/list/status = list() + status += "The interface light is [A.locked ? "red" : "green"]." + status += "The short indicator is [A.shorted ? "lit" : "off"]." + status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." + return status + +/datum/wires/apc/on_pulse(wire) + var/obj/machinery/power/apc/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Short for a long while. + if(!A.shorted) + A.shorted = TRUE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 1200) + if(WIRE_IDSCAN) // Unlock for a little while. + A.locked = FALSE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 300) + if(WIRE_AI) // Disable AI control for a very short time. + if(!A.aidisabled) + A.aidisabled = TRUE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 10) + +/datum/wires/apc/on_cut(index, mend) + var/obj/machinery/power/apc/A = holder + switch(index) + if(WIRE_POWER1, WIRE_POWER2) // Short out. + if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) + A.shorted = FALSE + A.shock(usr, 50) + else + A.shorted = TRUE + A.shock(usr, 50) + if(WIRE_AI) // Disable AI control. + if(mend) + A.aidisabled = FALSE + else + A.aidisabled = TRUE diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm index 242a460c829..7f3519f3947 100644 --- a/code/datums/wires/autolathe.dm +++ b/code/datums/wires/autolathe.dm @@ -1,48 +1,48 @@ -/datum/wires/autolathe - holder_type = /obj/machinery/autolathe - proper_name = "Autolathe" - -/datum/wires/autolathe/New(atom/holder) - wires = list( - WIRE_HACK, WIRE_DISABLE, - WIRE_SHOCK, WIRE_ZAP - ) - add_duds(6) - ..() - -/datum/wires/autolathe/interactable(mob/user) - var/obj/machinery/autolathe/A = holder - if(A.panel_open) - return TRUE - -/datum/wires/autolathe/get_status() - var/obj/machinery/autolathe/A = holder - var/list/status = list() - status += "The red light is [A.disabled ? "on" : "off"]." - status += "The blue light is [A.hacked ? "on" : "off"]." - return status - -/datum/wires/autolathe/on_pulse(wire) - var/obj/machinery/autolathe/A = holder - switch(wire) - if(WIRE_HACK) - A.adjust_hacked(!A.hacked) - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - if(WIRE_SHOCK) - A.shocked = !A.shocked - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - if(WIRE_DISABLE) - A.disabled = !A.disabled - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - -/datum/wires/autolathe/on_cut(wire, mend) - var/obj/machinery/autolathe/A = holder - switch(wire) - if(WIRE_HACK) - A.adjust_hacked(!mend) - if(WIRE_HACK) - A.shocked = !mend - if(WIRE_DISABLE) - A.disabled = !mend - if(WIRE_ZAP) - A.shock(usr, 50) +/datum/wires/autolathe + holder_type = /obj/machinery/autolathe + proper_name = "Autolathe" + +/datum/wires/autolathe/New(atom/holder) + wires = list( + WIRE_HACK, WIRE_DISABLE, + WIRE_SHOCK, WIRE_ZAP + ) + add_duds(6) + ..() + +/datum/wires/autolathe/interactable(mob/user) + var/obj/machinery/autolathe/A = holder + if(A.panel_open) + return TRUE + +/datum/wires/autolathe/get_status() + var/obj/machinery/autolathe/A = holder + var/list/status = list() + status += "The red light is [A.disabled ? "on" : "off"]." + status += "The blue light is [A.hacked ? "on" : "off"]." + return status + +/datum/wires/autolathe/on_pulse(wire) + var/obj/machinery/autolathe/A = holder + switch(wire) + if(WIRE_HACK) + A.adjust_hacked(!A.hacked) + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + if(WIRE_SHOCK) + A.shocked = !A.shocked + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + if(WIRE_DISABLE) + A.disabled = !A.disabled + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + +/datum/wires/autolathe/on_cut(wire, mend) + var/obj/machinery/autolathe/A = holder + switch(wire) + if(WIRE_HACK) + A.adjust_hacked(!mend) + if(WIRE_HACK) + A.shocked = !mend + if(WIRE_DISABLE) + A.disabled = !mend + if(WIRE_ZAP) + A.shock(usr, 50) diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm index 4a8f5d2453d..e3f73d287b7 100644 --- a/code/datums/wires/explosive.dm +++ b/code/datums/wires/explosive.dm @@ -1,120 +1,120 @@ -/datum/wires/explosive - var/duds_number = 2 // All "dud" wires cause an explosion when cut or pulsed - randomize = TRUE // Prevents wires from showing up on blueprints - -/datum/wires/explosive/New(atom/holder) - add_duds(duds_number) // Duds also explode here. - ..() - -/datum/wires/explosive/on_pulse(index) - explode() - -/datum/wires/explosive/on_cut(index, mend) - explode() - -/datum/wires/explosive/proc/explode() - return - -/datum/wires/explosive/chem_grenade - duds_number = 1 - holder_type = /obj/item/grenade/chem_grenade - var/fingerprint - -/datum/wires/explosive/chem_grenade/interactable(mob/user) - var/obj/item/grenade/chem_grenade/G = holder - if(G.stage == GRENADE_WIRED) - return TRUE - -/datum/wires/explosive/chem_grenade/attach_assembly(color, obj/item/assembly/S) - if(istype(S,/obj/item/assembly/timer)) - var/obj/item/grenade/chem_grenade/G = holder - var/obj/item/assembly/timer/T = S - G.det_time = T.saved_time*10 - else if(istype(S,/obj/item/assembly/prox_sensor)) - var/obj/item/grenade/chem_grenade/G = holder - G.landminemode = S - S.proximity_monitor.wire = TRUE - fingerprint = S.fingerprintslast - return ..() - -/datum/wires/explosive/chem_grenade/explode() - var/obj/item/grenade/chem_grenade/G = holder - var/obj/item/assembly/assembly = get_attached(get_wire(1)) - message_admins("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") - log_game("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") - var/mob/M = get_mob_by_ckey(fingerprint) - var/turf/T = get_turf(M) - G.log_grenade(M, T) - G.prime() - -/datum/wires/explosive/chem_grenade/detach_assembly(color) - var/obj/item/assembly/S = get_attached(color) - if(S && istype(S)) - assemblies -= color - S.connected = null - S.forceMove(holder.drop_location()) - var/obj/item/grenade/chem_grenade/G = holder - G.landminemode = null - return S - -/datum/wires/explosive/c4 // Also includes X4 - holder_type = /obj/item/grenade/c4 - -/datum/wires/explosive/c4/interactable(mob/user) // No need to unscrew wire panels on plastic explosives - return TRUE - -/datum/wires/explosive/c4/explode() - var/obj/item/grenade/c4/P = holder - P.prime() - -/datum/wires/explosive/pizza - holder_type = /obj/item/pizzabox - -/datum/wires/explosive/pizza/New(atom/holder) - wires = list( - WIRE_DISARM - ) - add_duds(3) // Duds also explode here. - ..() - -/datum/wires/explosive/pizza/interactable(mob/user) - var/obj/item/pizzabox/P = holder - if(P.open && P.bomb) - return TRUE - -/datum/wires/explosive/pizza/get_status() - var/obj/item/pizzabox/P = holder - var/list/status = list() - status += "The red light is [P.bomb_active ? "on" : "off"]." - status += "The green light is [P.bomb_defused ? "on": "off"]." - return status - -/datum/wires/explosive/pizza/on_pulse(wire) - var/obj/item/pizzabox/P = holder - switch(wire) - if(WIRE_DISARM) // Pulse to toggle - P.bomb_defused = !P.bomb_defused - else // Boom - explode() - -/datum/wires/explosive/pizza/on_cut(wire, mend) - var/obj/item/pizzabox/P = holder - switch(wire) - if(WIRE_DISARM) // Disarm and untrap the box. - if(!mend) - P.bomb_defused = TRUE - else - if(!mend && !P.bomb_defused) - explode() - -/datum/wires/explosive/pizza/explode() - var/obj/item/pizzabox/P = holder - P.bomb.detonate() - - -/datum/wires/explosive/gibtonite - holder_type = /obj/item/gibtonite - -/datum/wires/explosive/gibtonite/explode() - var/obj/item/gibtonite/P = holder - P.GibtoniteReaction(null, 2) +/datum/wires/explosive + var/duds_number = 2 // All "dud" wires cause an explosion when cut or pulsed + randomize = TRUE // Prevents wires from showing up on blueprints + +/datum/wires/explosive/New(atom/holder) + add_duds(duds_number) // Duds also explode here. + ..() + +/datum/wires/explosive/on_pulse(index) + explode() + +/datum/wires/explosive/on_cut(index, mend) + explode() + +/datum/wires/explosive/proc/explode() + return + +/datum/wires/explosive/chem_grenade + duds_number = 1 + holder_type = /obj/item/grenade/chem_grenade + var/fingerprint + +/datum/wires/explosive/chem_grenade/interactable(mob/user) + var/obj/item/grenade/chem_grenade/G = holder + if(G.stage == GRENADE_WIRED) + return TRUE + +/datum/wires/explosive/chem_grenade/attach_assembly(color, obj/item/assembly/S) + if(istype(S,/obj/item/assembly/timer)) + var/obj/item/grenade/chem_grenade/G = holder + var/obj/item/assembly/timer/T = S + G.det_time = T.saved_time*10 + else if(istype(S,/obj/item/assembly/prox_sensor)) + var/obj/item/grenade/chem_grenade/G = holder + G.landminemode = S + S.proximity_monitor.wire = TRUE + fingerprint = S.fingerprintslast + return ..() + +/datum/wires/explosive/chem_grenade/explode() + var/obj/item/grenade/chem_grenade/G = holder + var/obj/item/assembly/assembly = get_attached(get_wire(1)) + message_admins("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") + log_game("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") + var/mob/M = get_mob_by_ckey(fingerprint) + var/turf/T = get_turf(M) + G.log_grenade(M, T) + G.prime() + +/datum/wires/explosive/chem_grenade/detach_assembly(color) + var/obj/item/assembly/S = get_attached(color) + if(S && istype(S)) + assemblies -= color + S.connected = null + S.forceMove(holder.drop_location()) + var/obj/item/grenade/chem_grenade/G = holder + G.landminemode = null + return S + +/datum/wires/explosive/c4 // Also includes X4 + holder_type = /obj/item/grenade/c4 + +/datum/wires/explosive/c4/interactable(mob/user) // No need to unscrew wire panels on plastic explosives + return TRUE + +/datum/wires/explosive/c4/explode() + var/obj/item/grenade/c4/P = holder + P.prime() + +/datum/wires/explosive/pizza + holder_type = /obj/item/pizzabox + +/datum/wires/explosive/pizza/New(atom/holder) + wires = list( + WIRE_DISARM + ) + add_duds(3) // Duds also explode here. + ..() + +/datum/wires/explosive/pizza/interactable(mob/user) + var/obj/item/pizzabox/P = holder + if(P.open && P.bomb) + return TRUE + +/datum/wires/explosive/pizza/get_status() + var/obj/item/pizzabox/P = holder + var/list/status = list() + status += "The red light is [P.bomb_active ? "on" : "off"]." + status += "The green light is [P.bomb_defused ? "on": "off"]." + return status + +/datum/wires/explosive/pizza/on_pulse(wire) + var/obj/item/pizzabox/P = holder + switch(wire) + if(WIRE_DISARM) // Pulse to toggle + P.bomb_defused = !P.bomb_defused + else // Boom + explode() + +/datum/wires/explosive/pizza/on_cut(wire, mend) + var/obj/item/pizzabox/P = holder + switch(wire) + if(WIRE_DISARM) // Disarm and untrap the box. + if(!mend) + P.bomb_defused = TRUE + else + if(!mend && !P.bomb_defused) + explode() + +/datum/wires/explosive/pizza/explode() + var/obj/item/pizzabox/P = holder + P.bomb.detonate() + + +/datum/wires/explosive/gibtonite + holder_type = /obj/item/gibtonite + +/datum/wires/explosive/gibtonite/explode() + var/obj/item/gibtonite/P = holder + P.GibtoniteReaction(null, 2) diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index 9c71c9568f2..08a10afbfca 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -1,34 +1,34 @@ -/datum/wires/mulebot - holder_type = /mob/living/simple_animal/bot/mulebot - randomize = TRUE - -/datum/wires/mulebot/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_AVOIDANCE, WIRE_LOADCHECK, - WIRE_MOTOR1, WIRE_MOTOR2, - WIRE_RX, WIRE_TX, WIRE_BEACON - ) - ..() - -/datum/wires/mulebot/interactable(mob/user) - var/mob/living/simple_animal/bot/mulebot/M = holder - if(M.open) - return TRUE - -/datum/wires/mulebot/on_pulse(wire) - var/mob/living/simple_animal/bot/mulebot/M = holder - if(!M.has_power(TRUE)) - return //logically mulebots can't flash and beep if they don't have power. - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) - holder.visible_message("[icon2html(M, viewers(holder))] The charge light flickers.") - if(WIRE_AVOIDANCE) - holder.visible_message("[icon2html(M, viewers(holder))] The external warning lights flash briefly.") - flick("[M.base_icon]1", M) - if(WIRE_LOADCHECK) - holder.visible_message("[icon2html(M, viewers(holder))] The load platform clunks.") - if(WIRE_MOTOR1, WIRE_MOTOR2) - holder.visible_message("[icon2html(M, viewers(holder))] The drive motor whines briefly.") - else - holder.visible_message("[icon2html(M, viewers(holder))] You hear a radio crackle.") +/datum/wires/mulebot + holder_type = /mob/living/simple_animal/bot/mulebot + randomize = TRUE + +/datum/wires/mulebot/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_AVOIDANCE, WIRE_LOADCHECK, + WIRE_MOTOR1, WIRE_MOTOR2, + WIRE_RX, WIRE_TX, WIRE_BEACON + ) + ..() + +/datum/wires/mulebot/interactable(mob/user) + var/mob/living/simple_animal/bot/mulebot/M = holder + if(M.open) + return TRUE + +/datum/wires/mulebot/on_pulse(wire) + var/mob/living/simple_animal/bot/mulebot/M = holder + if(!M.has_power(TRUE)) + return //logically mulebots can't flash and beep if they don't have power. + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) + holder.visible_message("[icon2html(M, viewers(holder))] The charge light flickers.") + if(WIRE_AVOIDANCE) + holder.visible_message("[icon2html(M, viewers(holder))] The external warning lights flash briefly.") + flick("[M.base_icon]1", M) + if(WIRE_LOADCHECK) + holder.visible_message("[icon2html(M, viewers(holder))] The load platform clunks.") + if(WIRE_MOTOR1, WIRE_MOTOR2) + holder.visible_message("[icon2html(M, viewers(holder))] The drive motor whines briefly.") + else + holder.visible_message("[icon2html(M, viewers(holder))] You hear a radio crackle.") diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm index 630a506b777..e58dba4631f 100644 --- a/code/datums/wires/particle_accelerator.dm +++ b/code/datums/wires/particle_accelerator.dm @@ -1,48 +1,48 @@ -/datum/wires/particle_accelerator/control_box - holder_type = /obj/machinery/particle_accelerator/control_box - proper_name = "Particle Accelerator" - -/datum/wires/particle_accelerator/control_box/New(atom/holder) - wires = list( - WIRE_POWER, WIRE_STRENGTH, WIRE_LIMIT, - WIRE_INTERFACE - ) - add_duds(2) - ..() - -/datum/wires/particle_accelerator/control_box/interactable(mob/user) - var/obj/machinery/particle_accelerator/control_box/C = holder - if(C.construction_state == 2) - return TRUE - -/datum/wires/particle_accelerator/control_box/on_pulse(wire) - var/obj/machinery/particle_accelerator/control_box/C = holder - switch(wire) - if(WIRE_POWER) - C.toggle_power() - if(WIRE_STRENGTH) - C.add_strength() - if(WIRE_INTERFACE) - C.interface_control = !C.interface_control - if(WIRE_LIMIT) - C.visible_message("[icon2html(C, viewers(holder))][C] makes a large whirring noise.") - -/datum/wires/particle_accelerator/control_box/on_cut(wire, mend) - var/obj/machinery/particle_accelerator/control_box/C = holder - switch(wire) - if(WIRE_POWER) - if(C.active == !mend) - C.toggle_power() - if(WIRE_STRENGTH) - for(var/i = 1; i < 3; i++) - C.remove_strength() - if(WIRE_INTERFACE) - if(!mend) - C.interface_control = FALSE - if(WIRE_LIMIT) - C.strength_upper_limit = (mend ? 2 : 3) - if(C.strength_upper_limit < C.strength) - C.remove_strength() - -/datum/wires/particle_accelerator/control_box/emp_pulse() // to prevent singulo from pulsing wires - return +/datum/wires/particle_accelerator/control_box + holder_type = /obj/machinery/particle_accelerator/control_box + proper_name = "Particle Accelerator" + +/datum/wires/particle_accelerator/control_box/New(atom/holder) + wires = list( + WIRE_POWER, WIRE_STRENGTH, WIRE_LIMIT, + WIRE_INTERFACE + ) + add_duds(2) + ..() + +/datum/wires/particle_accelerator/control_box/interactable(mob/user) + var/obj/machinery/particle_accelerator/control_box/C = holder + if(C.construction_state == 2) + return TRUE + +/datum/wires/particle_accelerator/control_box/on_pulse(wire) + var/obj/machinery/particle_accelerator/control_box/C = holder + switch(wire) + if(WIRE_POWER) + C.toggle_power() + if(WIRE_STRENGTH) + C.add_strength() + if(WIRE_INTERFACE) + C.interface_control = !C.interface_control + if(WIRE_LIMIT) + C.visible_message("[icon2html(C, viewers(holder))][C] makes a large whirring noise.") + +/datum/wires/particle_accelerator/control_box/on_cut(wire, mend) + var/obj/machinery/particle_accelerator/control_box/C = holder + switch(wire) + if(WIRE_POWER) + if(C.active == !mend) + C.toggle_power() + if(WIRE_STRENGTH) + for(var/i = 1; i < 3; i++) + C.remove_strength() + if(WIRE_INTERFACE) + if(!mend) + C.interface_control = FALSE + if(WIRE_LIMIT) + C.strength_upper_limit = (mend ? 2 : 3) + if(C.strength_upper_limit < C.strength) + C.remove_strength() + +/datum/wires/particle_accelerator/control_box/emp_pulse() // to prevent singulo from pulsing wires + return diff --git a/code/datums/wires/radio.dm b/code/datums/wires/radio.dm index f950d7a7ce9..a1118da6d73 100644 --- a/code/datums/wires/radio.dm +++ b/code/datums/wires/radio.dm @@ -1,25 +1,25 @@ -/datum/wires/radio - holder_type = /obj/item/radio - proper_name = "Radio" - -/datum/wires/radio/New(atom/holder) - wires = list( - WIRE_SIGNAL, - WIRE_RX, WIRE_TX - ) - ..() - -/datum/wires/radio/interactable(mob/user) - var/obj/item/radio/R = holder - return R.unscrewed - -/datum/wires/radio/on_pulse(index) - var/obj/item/radio/R = holder - switch(index) - if(WIRE_SIGNAL) - R.listening = !R.listening - R.broadcasting = R.listening - if(WIRE_RX) - R.listening = !R.listening - if(WIRE_TX) - R.broadcasting = !R.broadcasting +/datum/wires/radio + holder_type = /obj/item/radio + proper_name = "Radio" + +/datum/wires/radio/New(atom/holder) + wires = list( + WIRE_SIGNAL, + WIRE_RX, WIRE_TX + ) + ..() + +/datum/wires/radio/interactable(mob/user) + var/obj/item/radio/R = holder + return R.unscrewed + +/datum/wires/radio/on_pulse(index) + var/obj/item/radio/R = holder + switch(index) + if(WIRE_SIGNAL) + R.listening = !R.listening + R.broadcasting = R.listening + if(WIRE_RX) + R.listening = !R.listening + if(WIRE_TX) + R.broadcasting = !R.broadcasting diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index c24e40150b3..febb3d0ce2e 100644 --- a/code/datums/wires/robot.dm +++ b/code/datums/wires/robot.dm @@ -1,86 +1,86 @@ -/datum/wires/robot - holder_type = /mob/living/silicon/robot - randomize = TRUE - -/datum/wires/robot/New(atom/holder) - wires = list( - WIRE_AI, WIRE_CAMERA, - WIRE_LAWSYNC, WIRE_LOCKDOWN, - WIRE_RESET_MODULE - ) - add_duds(2) - ..() - -/datum/wires/robot/interactable(mob/user) - var/mob/living/silicon/robot/R = holder - if(R.wiresexposed) - return TRUE - -/datum/wires/robot/get_status() - var/mob/living/silicon/robot/R = holder - var/list/status = list() - status += "The law sync module is [R.lawupdate ? "on" : "off"]." - status += "The intelligence link display shows [R.connected_ai ? R.connected_ai.name : "NULL"]." - status += "The camera light is [!isnull(R.builtInCamera) && R.builtInCamera.status ? "on" : "off"]." - status += "The lockdown indicator is [R.lockcharge ? "on" : "off"]." - status += "There is a star symbol above the [get_color_of_wire(WIRE_RESET_MODULE)] wire." - return status - -/datum/wires/robot/on_pulse(wire, user) - var/mob/living/silicon/robot/R = holder - switch(wire) - if(WIRE_AI) // Pulse to pick a new AI. - if(!R.emagged) - var/new_ai - if(user) - new_ai = select_active_ai(user, R.z) - else - new_ai = select_active_ai(R, R.z) - R.notify_ai(DISCONNECT) - if(new_ai && (new_ai != R.connected_ai)) - R.connected_ai = new_ai - if(R.shell) - R.undeploy() //If this borg is an AI shell, disconnect the controlling AI and assign ti to a new AI - R.notify_ai(AI_SHELL) - else - R.notify_ai(TRUE) - if(WIRE_CAMERA) // Pulse to disable the camera. - if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) - R.builtInCamera.toggle_cam(usr, 0) - R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") - if(WIRE_LAWSYNC) // Forces a law update if possible. - if(R.lawupdate) - R.visible_message("[R] gently chimes.", "LawSync protocol engaged.") - R.lawsync() - R.show_laws() - if(WIRE_LOCKDOWN) - R.SetLockdown(!R.lockcharge) // Toggle - if(WIRE_RESET_MODULE) - if(R.has_module()) - R.visible_message("[R]'s module servos twitch.", "Your module display flickers.") - -/datum/wires/robot/on_cut(wire, mend) - var/mob/living/silicon/robot/R = holder - switch(wire) - if(WIRE_AI) // Cut the AI wire to reset AI control. - if(!mend) - R.notify_ai(DISCONNECT) - if(R.shell) - R.undeploy() - R.connected_ai = null - if(WIRE_LAWSYNC) // Cut the law wire, and the borg will no longer receive law updates from its AI. Repair and it will re-sync. - if(mend) - if(!R.emagged) - R.lawupdate = TRUE - else if(!R.deployed) //AI shells must always have the same laws as the AI - R.lawupdate = FALSE - if (WIRE_CAMERA) // Disable the camera. - if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) - R.builtInCamera.status = mend - R.builtInCamera.toggle_cam(usr, 0) - R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") - if(WIRE_LOCKDOWN) // Simple lockdown. - R.SetLockdown(!mend) - if(WIRE_RESET_MODULE) - if(R.has_module() && !mend) - R.ResetModule() +/datum/wires/robot + holder_type = /mob/living/silicon/robot + randomize = TRUE + +/datum/wires/robot/New(atom/holder) + wires = list( + WIRE_AI, WIRE_CAMERA, + WIRE_LAWSYNC, WIRE_LOCKDOWN, + WIRE_RESET_MODULE + ) + add_duds(2) + ..() + +/datum/wires/robot/interactable(mob/user) + var/mob/living/silicon/robot/R = holder + if(R.wiresexposed) + return TRUE + +/datum/wires/robot/get_status() + var/mob/living/silicon/robot/R = holder + var/list/status = list() + status += "The law sync module is [R.lawupdate ? "on" : "off"]." + status += "The intelligence link display shows [R.connected_ai ? R.connected_ai.name : "NULL"]." + status += "The camera light is [!isnull(R.builtInCamera) && R.builtInCamera.status ? "on" : "off"]." + status += "The lockdown indicator is [R.lockcharge ? "on" : "off"]." + status += "There is a star symbol above the [get_color_of_wire(WIRE_RESET_MODULE)] wire." + return status + +/datum/wires/robot/on_pulse(wire, user) + var/mob/living/silicon/robot/R = holder + switch(wire) + if(WIRE_AI) // Pulse to pick a new AI. + if(!R.emagged) + var/new_ai + if(user) + new_ai = select_active_ai(user, R.z) + else + new_ai = select_active_ai(R, R.z) + R.notify_ai(DISCONNECT) + if(new_ai && (new_ai != R.connected_ai)) + R.connected_ai = new_ai + if(R.shell) + R.undeploy() //If this borg is an AI shell, disconnect the controlling AI and assign ti to a new AI + R.notify_ai(AI_SHELL) + else + R.notify_ai(TRUE) + if(WIRE_CAMERA) // Pulse to disable the camera. + if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) + R.builtInCamera.toggle_cam(usr, 0) + R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") + if(WIRE_LAWSYNC) // Forces a law update if possible. + if(R.lawupdate) + R.visible_message("[R] gently chimes.", "LawSync protocol engaged.") + R.lawsync() + R.show_laws() + if(WIRE_LOCKDOWN) + R.SetLockdown(!R.lockcharge) // Toggle + if(WIRE_RESET_MODULE) + if(R.has_module()) + R.visible_message("[R]'s module servos twitch.", "Your module display flickers.") + +/datum/wires/robot/on_cut(wire, mend) + var/mob/living/silicon/robot/R = holder + switch(wire) + if(WIRE_AI) // Cut the AI wire to reset AI control. + if(!mend) + R.notify_ai(DISCONNECT) + if(R.shell) + R.undeploy() + R.connected_ai = null + if(WIRE_LAWSYNC) // Cut the law wire, and the borg will no longer receive law updates from its AI. Repair and it will re-sync. + if(mend) + if(!R.emagged) + R.lawupdate = TRUE + else if(!R.deployed) //AI shells must always have the same laws as the AI + R.lawupdate = FALSE + if (WIRE_CAMERA) // Disable the camera. + if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) + R.builtInCamera.status = mend + R.builtInCamera.toggle_cam(usr, 0) + R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") + if(WIRE_LOCKDOWN) // Simple lockdown. + R.SetLockdown(!mend) + if(WIRE_RESET_MODULE) + if(R.has_module() && !mend) + R.ResetModule() diff --git a/code/datums/wires/suit_storage_unit.dm b/code/datums/wires/suit_storage_unit.dm index 4813ad8d207..eb7781203b2 100644 --- a/code/datums/wires/suit_storage_unit.dm +++ b/code/datums/wires/suit_storage_unit.dm @@ -1,45 +1,45 @@ -/datum/wires/suit_storage_unit - holder_type = /obj/machinery/suit_storage_unit - proper_name = "Suit Storage Unit" - -/datum/wires/suit_storage_unit/New(atom/holder) - wires = list( - WIRE_HACK, WIRE_SAFETY, - WIRE_ZAP - ) - add_duds(2) - ..() - -/datum/wires/suit_storage_unit/interactable(mob/user) - var/obj/machinery/suit_storage_unit/SSU = holder - if(SSU.panel_open) - return TRUE - -/datum/wires/suit_storage_unit/get_status() - var/obj/machinery/suit_storage_unit/SSU = holder - var/list/status = list() - status += "The UV bulb is [SSU.uv_super ? "glowing" : "dim"]." - status += "The service light is [SSU.safeties ? "off" : "on"]." - return status - -/datum/wires/suit_storage_unit/on_pulse(wire) - var/obj/machinery/suit_storage_unit/SSU = holder - switch(wire) - if(WIRE_HACK) - SSU.uv_super = !SSU.uv_super - if(WIRE_SAFETY) - SSU.safeties = !SSU.safeties - if(WIRE_ZAP) - if(usr) - SSU.shock(usr) - -/datum/wires/suit_storage_unit/on_cut(wire, mend) - var/obj/machinery/suit_storage_unit/SSU = holder - switch(wire) - if(WIRE_HACK) - SSU.uv_super = !mend - if(WIRE_SAFETY) - SSU.safeties = mend - if(WIRE_ZAP) - if(usr) - SSU.shock(usr) +/datum/wires/suit_storage_unit + holder_type = /obj/machinery/suit_storage_unit + proper_name = "Suit Storage Unit" + +/datum/wires/suit_storage_unit/New(atom/holder) + wires = list( + WIRE_HACK, WIRE_SAFETY, + WIRE_ZAP + ) + add_duds(2) + ..() + +/datum/wires/suit_storage_unit/interactable(mob/user) + var/obj/machinery/suit_storage_unit/SSU = holder + if(SSU.panel_open) + return TRUE + +/datum/wires/suit_storage_unit/get_status() + var/obj/machinery/suit_storage_unit/SSU = holder + var/list/status = list() + status += "The UV bulb is [SSU.uv_super ? "glowing" : "dim"]." + status += "The service light is [SSU.safeties ? "off" : "on"]." + return status + +/datum/wires/suit_storage_unit/on_pulse(wire) + var/obj/machinery/suit_storage_unit/SSU = holder + switch(wire) + if(WIRE_HACK) + SSU.uv_super = !SSU.uv_super + if(WIRE_SAFETY) + SSU.safeties = !SSU.safeties + if(WIRE_ZAP) + if(usr) + SSU.shock(usr) + +/datum/wires/suit_storage_unit/on_cut(wire, mend) + var/obj/machinery/suit_storage_unit/SSU = holder + switch(wire) + if(WIRE_HACK) + SSU.uv_super = !mend + if(WIRE_SAFETY) + SSU.safeties = mend + if(WIRE_ZAP) + if(usr) + SSU.shock(usr) diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm index 1ebcf704539..fa777eefcba 100644 --- a/code/datums/wires/syndicatebomb.dm +++ b/code/datums/wires/syndicatebomb.dm @@ -1,91 +1,91 @@ -/datum/wires/syndicatebomb - holder_type = /obj/machinery/syndicatebomb - randomize = TRUE - -/datum/wires/syndicatebomb/New(atom/holder) - wires = list( - WIRE_BOOM, WIRE_UNBOLT, - WIRE_ACTIVATE, WIRE_DELAY, WIRE_PROCEED - ) - ..() - -/datum/wires/syndicatebomb/interactable(mob/user) - var/obj/machinery/syndicatebomb/P = holder - if(P.open_panel) - return TRUE - -/datum/wires/syndicatebomb/on_pulse(wire) - var/obj/machinery/syndicatebomb/B = holder - switch(wire) - if(WIRE_BOOM) - if(B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - else - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - if(WIRE_UNBOLT) - holder.visible_message("[icon2html(B, viewers(holder))] The bolts spin in place for a moment.") - if(WIRE_DELAY) - if(B.delayedbig) - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - else - holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") - playsound(B, 'sound/machines/chime.ogg', 30, TRUE) - B.detonation_timer += 300 - if(B.active) - B.delayedbig = TRUE - if(WIRE_PROCEED) - holder.visible_message("[icon2html(B, viewers(holder))] The bomb buzzes ominously!") - playsound(B, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - var/seconds = B.seconds_remaining() - if(seconds >= 61) // Long fuse bombs can suddenly become more dangerous if you tinker with them. - B.detonation_timer = world.time + 600 - else if(seconds >= 21) - B.detonation_timer -= 100 - else if(seconds >= 11) // Both to prevent negative timers and to have a little mercy. - B.detonation_timer = world.time + 100 - if(WIRE_ACTIVATE) - if(!B.active) - holder.visible_message("[icon2html(B, viewers(holder))] You hear the bomb start ticking!") - B.activate() - B.update_icon() - else if(B.delayedlittle) - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - else - holder.visible_message("[icon2html(B, viewers(holder))] The bomb seems to hesitate for a moment.") - B.detonation_timer += 100 - B.delayedlittle = TRUE - -/datum/wires/syndicatebomb/on_cut(wire, mend) - var/obj/machinery/syndicatebomb/B = holder - switch(wire) - if(WIRE_BOOM) - if(!mend && B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - if(WIRE_UNBOLT) - if(!mend && B.anchored) - holder.visible_message("[icon2html(B, viewers(holder))] The bolts lift out of the ground!") - playsound(B, 'sound/effects/stealthoff.ogg', 30, TRUE) - B.anchored = FALSE - if(WIRE_PROCEED) - if(!mend && B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - if(WIRE_ACTIVATE) - if(!mend && B.active) - holder.visible_message("[icon2html(B, viewers(holder))] The timer stops! The bomb has been defused!") - B.active = FALSE - B.delayedlittle = FALSE - B.delayedbig = FALSE - B.update_icon() - -/datum/wires/syndicatebomb/proc/tell_admins(obj/machinery/syndicatebomb/B) - if(istype(B, /obj/machinery/syndicatebomb/training)) - return - var/turf/T = get_turf(B) - log_game("\A [B] was detonated via boom wire at [AREACOORD(T)].") - message_admins("A [B.name] was detonated via boom wire at [ADMIN_VERBOSEJMP(T)].") +/datum/wires/syndicatebomb + holder_type = /obj/machinery/syndicatebomb + randomize = TRUE + +/datum/wires/syndicatebomb/New(atom/holder) + wires = list( + WIRE_BOOM, WIRE_UNBOLT, + WIRE_ACTIVATE, WIRE_DELAY, WIRE_PROCEED + ) + ..() + +/datum/wires/syndicatebomb/interactable(mob/user) + var/obj/machinery/syndicatebomb/P = holder + if(P.open_panel) + return TRUE + +/datum/wires/syndicatebomb/on_pulse(wire) + var/obj/machinery/syndicatebomb/B = holder + switch(wire) + if(WIRE_BOOM) + if(B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + else + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + if(WIRE_UNBOLT) + holder.visible_message("[icon2html(B, viewers(holder))] The bolts spin in place for a moment.") + if(WIRE_DELAY) + if(B.delayedbig) + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + else + holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") + playsound(B, 'sound/machines/chime.ogg', 30, TRUE) + B.detonation_timer += 300 + if(B.active) + B.delayedbig = TRUE + if(WIRE_PROCEED) + holder.visible_message("[icon2html(B, viewers(holder))] The bomb buzzes ominously!") + playsound(B, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + var/seconds = B.seconds_remaining() + if(seconds >= 61) // Long fuse bombs can suddenly become more dangerous if you tinker with them. + B.detonation_timer = world.time + 600 + else if(seconds >= 21) + B.detonation_timer -= 100 + else if(seconds >= 11) // Both to prevent negative timers and to have a little mercy. + B.detonation_timer = world.time + 100 + if(WIRE_ACTIVATE) + if(!B.active) + holder.visible_message("[icon2html(B, viewers(holder))] You hear the bomb start ticking!") + B.activate() + B.update_icon() + else if(B.delayedlittle) + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + else + holder.visible_message("[icon2html(B, viewers(holder))] The bomb seems to hesitate for a moment.") + B.detonation_timer += 100 + B.delayedlittle = TRUE + +/datum/wires/syndicatebomb/on_cut(wire, mend) + var/obj/machinery/syndicatebomb/B = holder + switch(wire) + if(WIRE_BOOM) + if(!mend && B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + if(WIRE_UNBOLT) + if(!mend && B.anchored) + holder.visible_message("[icon2html(B, viewers(holder))] The bolts lift out of the ground!") + playsound(B, 'sound/effects/stealthoff.ogg', 30, TRUE) + B.anchored = FALSE + if(WIRE_PROCEED) + if(!mend && B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + if(WIRE_ACTIVATE) + if(!mend && B.active) + holder.visible_message("[icon2html(B, viewers(holder))] The timer stops! The bomb has been defused!") + B.active = FALSE + B.delayedlittle = FALSE + B.delayedbig = FALSE + B.update_icon() + +/datum/wires/syndicatebomb/proc/tell_admins(obj/machinery/syndicatebomb/B) + if(istype(B, /obj/machinery/syndicatebomb/training)) + return + var/turf/T = get_turf(B) + log_game("\A [B] was detonated via boom wire at [AREACOORD(T)].") + message_admins("A [B.name] was detonated via boom wire at [ADMIN_VERBOSEJMP(T)].") diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 46217d33428..6113eaaf307 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -1,62 +1,62 @@ -/datum/wires/vending - holder_type = /obj/machinery/vending - proper_name = "Vending Unit" - -/datum/wires/vending/New(atom/holder) - wires = list( - WIRE_THROW, WIRE_SHOCK, WIRE_SPEAKER, - WIRE_CONTRABAND, WIRE_IDSCAN - ) - add_duds(1) - ..() - -/datum/wires/vending/interactable(mob/user) - var/obj/machinery/vending/V = holder - if(!issilicon(user) && V.seconds_electrified && V.shock(user, 100)) - return FALSE - if(V.panel_open) - return TRUE - -/datum/wires/vending/get_status() - var/obj/machinery/vending/V = holder - var/list/status = list() - status += "The orange light is [V.seconds_electrified ? "on" : "off"]." - status += "The red light is [V.shoot_inventory ? "off" : "blinking"]." - status += "The green light is [V.extended_inventory ? "on" : "off"]." - status += "A [V.scan_id ? "purple" : "yellow"] light is on." - status += "A white light is [V.age_restrictions ? "on" : "off"]." - status += "The speaker light is [V.shut_up ? "off" : "on"]." - return status - -/datum/wires/vending/on_pulse(wire) - var/obj/machinery/vending/V = holder - switch(wire) - if(WIRE_THROW) - V.shoot_inventory = !V.shoot_inventory - if(WIRE_CONTRABAND) - V.extended_inventory = !V.extended_inventory - if(WIRE_SHOCK) - V.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME - if(WIRE_IDSCAN) - V.scan_id = !V.scan_id - if(WIRE_SPEAKER) - V.shut_up = !V.shut_up - if(WIRE_AGELIMIT) - V.age_restrictions = !V.age_restrictions - -/datum/wires/vending/on_cut(wire, mend) - var/obj/machinery/vending/V = holder - switch(wire) - if(WIRE_THROW) - V.shoot_inventory = !mend - if(WIRE_CONTRABAND) - V.extended_inventory = FALSE - if(WIRE_SHOCK) - if(mend) - V.seconds_electrified = MACHINE_NOT_ELECTRIFIED - else - V.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT - if(WIRE_IDSCAN) - V.scan_id = mend - if(WIRE_SPEAKER) - V.shut_up = mend +/datum/wires/vending + holder_type = /obj/machinery/vending + proper_name = "Vending Unit" + +/datum/wires/vending/New(atom/holder) + wires = list( + WIRE_THROW, WIRE_SHOCK, WIRE_SPEAKER, + WIRE_CONTRABAND, WIRE_IDSCAN + ) + add_duds(1) + ..() + +/datum/wires/vending/interactable(mob/user) + var/obj/machinery/vending/V = holder + if(!issilicon(user) && V.seconds_electrified && V.shock(user, 100)) + return FALSE + if(V.panel_open) + return TRUE + +/datum/wires/vending/get_status() + var/obj/machinery/vending/V = holder + var/list/status = list() + status += "The orange light is [V.seconds_electrified ? "on" : "off"]." + status += "The red light is [V.shoot_inventory ? "off" : "blinking"]." + status += "The green light is [V.extended_inventory ? "on" : "off"]." + status += "A [V.scan_id ? "purple" : "yellow"] light is on." + status += "A white light is [V.age_restrictions ? "on" : "off"]." + status += "The speaker light is [V.shut_up ? "off" : "on"]." + return status + +/datum/wires/vending/on_pulse(wire) + var/obj/machinery/vending/V = holder + switch(wire) + if(WIRE_THROW) + V.shoot_inventory = !V.shoot_inventory + if(WIRE_CONTRABAND) + V.extended_inventory = !V.extended_inventory + if(WIRE_SHOCK) + V.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME + if(WIRE_IDSCAN) + V.scan_id = !V.scan_id + if(WIRE_SPEAKER) + V.shut_up = !V.shut_up + if(WIRE_AGELIMIT) + V.age_restrictions = !V.age_restrictions + +/datum/wires/vending/on_cut(wire, mend) + var/obj/machinery/vending/V = holder + switch(wire) + if(WIRE_THROW) + V.shoot_inventory = !mend + if(WIRE_CONTRABAND) + V.extended_inventory = FALSE + if(WIRE_SHOCK) + if(mend) + V.seconds_electrified = MACHINE_NOT_ELECTRIFIED + else + V.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT + if(WIRE_IDSCAN) + V.scan_id = mend + if(WIRE_SPEAKER) + V.shut_up = mend diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index c152496faef..64f7ddd0f9e 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -1,185 +1,185 @@ -// SETUP - -/proc/TopicHandlers() - . = list() - var/list/all_handlers = subtypesof(/datum/world_topic) - for(var/I in all_handlers) - var/datum/world_topic/WT = I - var/keyword = initial(WT.keyword) - if(!keyword) - warning("[WT] has no keyword! Ignoring...") - continue - var/existing_path = .[keyword] - if(existing_path) - warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") - else if(keyword == "key") - warning("[WT] has keyword 'key'! Ignoring...") - else - .[keyword] = WT - -// DATUM - -/datum/world_topic - var/keyword - var/log = TRUE - var/key_valid - var/require_comms_key = FALSE - -/datum/world_topic/proc/TryRun(list/input) - key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) - input -= "key" - if(require_comms_key && !key_valid) - . = "Bad Key" - if (input["format"] == "json") - . = list("error" = .) - else - . = Run(input) - if (input["format"] == "json") - . = json_encode(.) - else if(islist(.)) - . = list2params(.) - -/datum/world_topic/proc/Run(list/input) - CRASH("Run() not implemented for [type]!") - -// TOPICS - -/datum/world_topic/ping - keyword = "ping" - log = FALSE - -/datum/world_topic/ping/Run(list/input) - . = 0 - for (var/client/C in GLOB.clients) - ++. - -/datum/world_topic/playing - keyword = "playing" - log = FALSE - -/datum/world_topic/playing/Run(list/input) - return GLOB.player_list.len - -/datum/world_topic/pr_announce - keyword = "announce" - require_comms_key = TRUE - var/static/list/PRcounts = list() //PR id -> number of times announced this round - -/datum/world_topic/pr_announce/Run(list/input) - var/list/payload = json_decode(input["payload"]) - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [input[keyword]]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/datum/world_topic/ahelp_relay - keyword = "Ahelp" - require_comms_key = TRUE - -/datum/world_topic/ahelp_relay/Run(list/input) - relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") - -/datum/world_topic/comms_console - keyword = "Comms_Console" - require_comms_key = TRUE - -/datum/world_topic/comms_console/Run(list/input) - minor_announce(input["message"], "Incoming message from [input["message_sender"]]") - for(var/obj/machinery/computer/communications/CM in GLOB.machines) - CM.overrideCooldown() - -/datum/world_topic/news_report - keyword = "News_Report" - require_comms_key = TRUE - -/datum/world_topic/news_report/Run(list/input) - minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") - -/datum/world_topic/adminmsg - keyword = "adminmsg" - require_comms_key = TRUE - -/datum/world_topic/adminmsg/Run(list/input) - return TgsPm(input[keyword], input["msg"], input["sender"]) - -/datum/world_topic/namecheck - keyword = "namecheck" - require_comms_key = TRUE - -/datum/world_topic/namecheck/Run(list/input) - //Oh this is a hack, someone refactor the functionality out of the chat command PLS - var/datum/tgs_chat_command/namecheck/NC = new - var/datum/tgs_chat_user/user = new - user.friendly_name = input["sender"] - user.mention = user.friendly_name - return NC.Run(user, input["namecheck"]) - -/datum/world_topic/adminwho - keyword = "adminwho" - require_comms_key = TRUE - -/datum/world_topic/adminwho/Run(list/input) - return tgsadminwho() - -/datum/world_topic/status - keyword = "status" - -/datum/world_topic/status/Run(list/input) - . = list() - .["version"] = GLOB.game_version - .["mode"] = GLOB.master_mode - .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE - .["enter"] = GLOB.enter_allowed - .["vote"] = CONFIG_GET(flag/allow_vote_mode) - .["ai"] = CONFIG_GET(flag/allow_ai) - .["host"] = world.host ? world.host : null - .["round_id"] = GLOB.round_id - .["players"] = GLOB.clients.len - .["revision"] = GLOB.revdata.commit - .["revision_date"] = GLOB.revdata.date - .["hub"] = GLOB.hub_visibility - - - var/list/adm = get_admin_counts() - var/list/presentmins = adm["present"] - var/list/afkmins = adm["afk"] - .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho - .["gamestate"] = SSticker.current_state - - .["map_name"] = SSmapping.config?.map_name || "Loading..." - - if(key_valid) - .["active_players"] = get_active_player_count() - if(SSticker.HasRoundStarted()) - .["real_mode"] = SSticker.mode.name - // Key-authed callers may know the truth behind the "secret" - - .["security_level"] = get_security_level() - .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 - // Amount of world's ticks in seconds, useful for calculating round duration - - //Time dilation stats. - .["time_dilation_current"] = SStime_track.time_dilation_current - .["time_dilation_avg"] = SStime_track.time_dilation_avg - .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow - .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast - - //pop cap stats - .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0 - .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0 - .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0 - .["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases - .["bunkered"] = CONFIG_GET(flag/panic_bunker) || FALSE - if(SSshuttle && SSshuttle.emergency) - .["shuttle_mode"] = SSshuttle.emergency.mode - // Shuttle status, see /__DEFINES/stat.dm - .["shuttle_timer"] = SSshuttle.emergency.timeLeft() - // Shuttle timer, in seconds - +// SETUP + +/proc/TopicHandlers() + . = list() + var/list/all_handlers = subtypesof(/datum/world_topic) + for(var/I in all_handlers) + var/datum/world_topic/WT = I + var/keyword = initial(WT.keyword) + if(!keyword) + warning("[WT] has no keyword! Ignoring...") + continue + var/existing_path = .[keyword] + if(existing_path) + warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") + else if(keyword == "key") + warning("[WT] has keyword 'key'! Ignoring...") + else + .[keyword] = WT + +// DATUM + +/datum/world_topic + var/keyword + var/log = TRUE + var/key_valid + var/require_comms_key = FALSE + +/datum/world_topic/proc/TryRun(list/input) + key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) + input -= "key" + if(require_comms_key && !key_valid) + . = "Bad Key" + if (input["format"] == "json") + . = list("error" = .) + else + . = Run(input) + if (input["format"] == "json") + . = json_encode(.) + else if(islist(.)) + . = list2params(.) + +/datum/world_topic/proc/Run(list/input) + CRASH("Run() not implemented for [type]!") + +// TOPICS + +/datum/world_topic/ping + keyword = "ping" + log = FALSE + +/datum/world_topic/ping/Run(list/input) + . = 0 + for (var/client/C in GLOB.clients) + ++. + +/datum/world_topic/playing + keyword = "playing" + log = FALSE + +/datum/world_topic/playing/Run(list/input) + return GLOB.player_list.len + +/datum/world_topic/pr_announce + keyword = "announce" + require_comms_key = TRUE + var/static/list/PRcounts = list() //PR id -> number of times announced this round + +/datum/world_topic/pr_announce/Run(list/input) + var/list/payload = json_decode(input["payload"]) + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [input[keyword]]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/datum/world_topic/ahelp_relay + keyword = "Ahelp" + require_comms_key = TRUE + +/datum/world_topic/ahelp_relay/Run(list/input) + relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") + +/datum/world_topic/comms_console + keyword = "Comms_Console" + require_comms_key = TRUE + +/datum/world_topic/comms_console/Run(list/input) + minor_announce(input["message"], "Incoming message from [input["message_sender"]]") + for(var/obj/machinery/computer/communications/CM in GLOB.machines) + CM.overrideCooldown() + +/datum/world_topic/news_report + keyword = "News_Report" + require_comms_key = TRUE + +/datum/world_topic/news_report/Run(list/input) + minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") + +/datum/world_topic/adminmsg + keyword = "adminmsg" + require_comms_key = TRUE + +/datum/world_topic/adminmsg/Run(list/input) + return TgsPm(input[keyword], input["msg"], input["sender"]) + +/datum/world_topic/namecheck + keyword = "namecheck" + require_comms_key = TRUE + +/datum/world_topic/namecheck/Run(list/input) + //Oh this is a hack, someone refactor the functionality out of the chat command PLS + var/datum/tgs_chat_command/namecheck/NC = new + var/datum/tgs_chat_user/user = new + user.friendly_name = input["sender"] + user.mention = user.friendly_name + return NC.Run(user, input["namecheck"]) + +/datum/world_topic/adminwho + keyword = "adminwho" + require_comms_key = TRUE + +/datum/world_topic/adminwho/Run(list/input) + return tgsadminwho() + +/datum/world_topic/status + keyword = "status" + +/datum/world_topic/status/Run(list/input) + . = list() + .["version"] = GLOB.game_version + .["mode"] = GLOB.master_mode + .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE + .["enter"] = GLOB.enter_allowed + .["vote"] = CONFIG_GET(flag/allow_vote_mode) + .["ai"] = CONFIG_GET(flag/allow_ai) + .["host"] = world.host ? world.host : null + .["round_id"] = GLOB.round_id + .["players"] = GLOB.clients.len + .["revision"] = GLOB.revdata.commit + .["revision_date"] = GLOB.revdata.date + .["hub"] = GLOB.hub_visibility + + + var/list/adm = get_admin_counts() + var/list/presentmins = adm["present"] + var/list/afkmins = adm["afk"] + .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho + .["gamestate"] = SSticker.current_state + + .["map_name"] = SSmapping.config?.map_name || "Loading..." + + if(key_valid) + .["active_players"] = get_active_player_count() + if(SSticker.HasRoundStarted()) + .["real_mode"] = SSticker.mode.name + // Key-authed callers may know the truth behind the "secret" + + .["security_level"] = get_security_level() + .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 + // Amount of world's ticks in seconds, useful for calculating round duration + + //Time dilation stats. + .["time_dilation_current"] = SStime_track.time_dilation_current + .["time_dilation_avg"] = SStime_track.time_dilation_avg + .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow + .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast + + //pop cap stats + .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0 + .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0 + .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0 + .["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases + .["bunkered"] = CONFIG_GET(flag/panic_bunker) || FALSE + if(SSshuttle && SSshuttle.emergency) + .["shuttle_mode"] = SSshuttle.emergency.mode + // Shuttle status, see /__DEFINES/stat.dm + .["shuttle_timer"] = SSshuttle.emergency.timeLeft() + // Shuttle timer, in seconds + diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm index 745c65c02f7..b3291d3a76c 100644 --- a/code/game/area/Space_Station_13_areas.dm +++ b/code/game/area/Space_Station_13_areas.dm @@ -1,1262 +1,1262 @@ -/* - -### This file contains a list of all the areas in your station. Format is as follows: - -/area/CATEGORY/OR/DESCRIPTOR/NAME (you can make as many subdivisions as you want) - name = "NICE NAME" (not required but makes things really nice) - icon = 'ICON FILENAME' (defaults to 'icons/turf/areas.dmi') - icon_state = "NAME OF ICON" (defaults to "unknown" (blank)) - requires_power = FALSE (defaults to true) - ambientsounds = list() (defaults to GENERIC from sound.dm. override it as "ambientsounds = list('sound/ambience/signal.ogg')" or using another define. - -NOTE: there are two lists of areas in the end of this file: centcom and station itself. Please maintain these lists valid. --rastaf0 - -*/ - - -/*-----------------------------------------------------------------------------*/ - -/area/ai_monitored //stub defined ai_monitored.dm - -/area/ai_monitored/turret_protected - -/area/space - icon_state = "space" - requires_power = TRUE - always_unpowered = TRUE - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - power_light = FALSE - power_equip = FALSE - power_environ = FALSE - valid_territory = FALSE - outdoors = TRUE - ambientsounds = SPACE - blob_allowed = FALSE //Eating up space doesn't count for victory as a blob. - flags_1 = CAN_BE_DIRTY_1 - -/area/space/nearstation - icon_state = "space_near" - dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT - -/area/start - name = "start area" - icon_state = "start" - requires_power = FALSE - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - has_gravity = STANDARD_GRAVITY - - -/area/testroom - requires_power = FALSE - name = "Test Room" - icon_state = "storage" - -//EXTRA - -/area/asteroid - name = "Asteroid" - icon_state = "asteroid" - requires_power = FALSE - has_gravity = STANDARD_GRAVITY - blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station. - valid_territory = FALSE - ambientsounds = MINING - flags_1 = CAN_BE_DIRTY_1 - -/area/asteroid/nearstation - dynamic_lighting = DYNAMIC_LIGHTING_FORCED - ambientsounds = RUINS - always_unpowered = FALSE - requires_power = TRUE - blob_allowed = TRUE - -/area/asteroid/nearstation/bomb_site - name = "Bomb Testing Asteroid" - -//STATION13 - -//Maintenance - -/area/maintenance - ambientsounds = MAINTENANCE - valid_territory = FALSE - - -//Departments - -/area/maintenance/department/chapel - name = "Chapel Maintenance" - icon_state = "maint_chapel" - -/area/maintenance/department/chapel/monastery - name = "Monastery Maintenance" - icon_state = "maint_monastery" - -/area/maintenance/department/crew_quarters/bar - name = "Bar Maintenance" - icon_state = "maint_bar" - -/area/maintenance/department/crew_quarters/dorms - name = "Dormitory Maintenance" - icon_state = "maint_dorms" - -/area/maintenance/department/eva - name = "EVA Maintenance" - icon_state = "maint_eva" - -/area/maintenance/department/electrical - name = "Electrical Maintenance" - icon_state = "maint_electrical" - -/area/maintenance/department/engine/atmos - name = "Atmospherics Maintenance" - icon_state = "maint_atmos" - -/area/maintenance/department/security - name = "Security Maintenance" - icon_state = "maint_sec" - -/area/maintenance/department/security/upper - name = "Upper Security Maintenance" - -/area/maintenance/department/security/brig - name = "Brig Maintenance" - icon_state = "maint_brig" - -/area/maintenance/department/medical - name = "Medbay Maintenance" - icon_state = "medbay_maint" - -/area/maintenance/department/medical/central - name = "Central Medbay Maintenance" - icon_state = "medbay_maint_central" - -/area/maintenance/department/medical/morgue - name = "Morgue Maintenance" - icon_state = "morgue_maint" - -/area/maintenance/department/science - name = "Science Maintenance" - icon_state = "maint_sci" - -/area/maintenance/department/science/central - name = "Central Science Maintenance" - icon_state = "maint_sci_central" - -/area/maintenance/department/cargo - name = "Cargo Maintenance" - icon_state = "maint_cargo" - -/area/maintenance/department/bridge - name = "Bridge Maintenance" - icon_state = "maint_bridge" - -/area/maintenance/department/engine - name = "Engineering Maintenance" - icon_state = "maint_engi" - -/area/maintenance/department/science/xenobiology - name = "Xenobiology Maintenance" - icon_state = "xenomaint" - xenobiology_compatible = TRUE - - -//Maintenance - Generic - -/area/maintenance/aft - name = "Aft Maintenance" - icon_state = "amaint" - -/area/maintenance/aft/upper - name = "Upper Aft Maintenance" - -/area/maintenance/aft/secondary - name = "Aft Maintenance" - icon_state = "amaint_2" - -/area/maintenance/central - name = "Central Maintenance" - icon_state = "maintcentral" - -/area/maintenance/central/secondary - name = "Central Maintenance" - icon_state = "maintcentral" - -/area/maintenance/fore - name = "Fore Maintenance" - icon_state = "fmaint" - -/area/maintenance/fore/upper - name = "Upper Fore Maintenance" - -/area/maintenance/fore/secondary - name = "Fore Maintenance" - icon_state = "fmaint_2" - -/area/maintenance/starboard - name = "Starboard Maintenance" - icon_state = "smaint" - -/area/maintenance/starboard/upper - name = "Upper Starboard Maintenance" - -/area/maintenance/starboard/central - name = "Central Starboard Maintenance" - icon_state = "smaint" - -/area/maintenance/starboard/secondary - name = "Secondary Starboard Maintenance" - icon_state = "smaint_2" - -/area/maintenance/starboard/aft - name = "Starboard Quarter Maintenance" - icon_state = "asmaint" - -/area/maintenance/starboard/aft/secondary - name = "Secondary Starboard Quarter Maintenance" - icon_state = "asmaint_2" - -/area/maintenance/starboard/fore - name = "Starboard Bow Maintenance" - icon_state = "fsmaint" - -/area/maintenance/port - name = "Port Maintenance" - icon_state = "pmaint" - -/area/maintenance/port/central - name = "Central Port Maintenance" - icon_state = "maintcentral" - -/area/maintenance/port/aft - name = "Port Quarter Maintenance" - icon_state = "apmaint" - -/area/maintenance/port/fore - name = "Port Bow Maintenance" - icon_state = "fpmaint" - -/area/maintenance/disposal - name = "Waste Disposal" - icon_state = "disposal" - -/area/maintenance/disposal/incinerator - name = "Incinerator" - icon_state = "incinerator" - - -//Hallway - -/area/hallway/primary/aft - name = "Aft Primary Hallway" - icon_state = "hallA" - -/area/hallway/primary/fore - name = "Fore Primary Hallway" - icon_state = "hallF" - -/area/hallway/primary/starboard - name = "Starboard Primary Hallway" - icon_state = "hallS" - -/area/hallway/primary/port - name = "Port Primary Hallway" - icon_state = "hallP" - -/area/hallway/primary/central - name = "Central Primary Hallway" - icon_state = "hallC" - -/area/hallway/primary/upper - name = "Upper Central Primary Hallway" - icon_state = "hallC" - - -/area/hallway/secondary/command - name = "Command Hallway" - icon_state = "bridge_hallway" - -/area/hallway/secondary/construction - name = "Construction Area" - icon_state = "construction" - -/area/hallway/secondary/exit - name = "Escape Shuttle Hallway" - icon_state = "escape" - -/area/hallway/secondary/exit/departure_lounge - name = "Departure Lounge" - icon_state = "escape_lounge" - -/area/hallway/secondary/entry - name = "Arrival Shuttle Hallway" - icon_state = "entry" - -/area/hallway/secondary/service - name = "Service Hallway" - icon_state = "hall_service" - -//Command - -/area/bridge - name = "Bridge" - icon_state = "bridge" - ambientsounds = list('sound/ambience/signal.ogg') - -/area/bridge/meeting_room - name = "Heads of Staff Meeting Room" - icon_state = "meeting" - -/area/bridge/meeting_room/council - name = "Council Chamber" - icon_state = "meeting" - -/area/bridge/showroom/corporate - name = "Corporate Showroom" - icon_state = "showroom" - -/area/crew_quarters/heads/captain - name = "Captain's Office" - icon_state = "captain" - -/area/crew_quarters/heads/captain/private - name = "Captain's Quarters" - icon_state = "captain_private" - -/area/crew_quarters/heads/chief - name = "Chief Engineer's Office" - icon_state = "ce_office" - -/area/crew_quarters/heads/cmo - name = "Chief Medical Officer's Office" - icon_state = "cmo_office" - -/area/crew_quarters/heads/hop - name = "Head of Personnel's Office" - icon_state = "hop_office" - -/area/crew_quarters/heads/hos - name = "Head of Security's Office" - icon_state = "hos_office" - -/area/crew_quarters/heads/hor - name = "Research Director's Office" - icon_state = "rd_office" - -/area/comms - name = "Communications Relay" - icon_state = "tcomsatcham" - -/area/server - name = "Messaging Server Room" - icon_state = "server" - -//Crew - -/area/crew_quarters/dorms - name = "Dormitories" - icon_state = "dorms" - safe = TRUE - -/area/crew_quarters/dorms/barracks - name = "Sleep Barracks" - -/area/crew_quarters/dorms/barracks/male - name = "Male Sleep Barracks" - icon_state = "dorms_male" - -/area/crew_quarters/dorms/barracks/female - name = "Female Sleep Barracks" - icon_state = "dorms_female" - -/area/crew_quarters/toilet - name = "Dormitory Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/auxiliary - name = "Auxiliary Restrooms" - icon_state = "toilet" - -/area/crew_quarters/toilet/locker - name = "Locker Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/restrooms - name = "Restrooms" - icon_state = "toilet" - -/area/crew_quarters/locker - name = "Locker Room" - icon_state = "locker" - -/area/crew_quarters/lounge - name = "Lounge" - icon_state = "lounge" - -/area/crew_quarters/fitness - name = "Fitness Room" - icon_state = "fitness" - -/area/crew_quarters/fitness/locker_room - name = "Unisex Locker Room" - icon_state = "locker" - -/area/crew_quarters/fitness/locker_room/male - name = "Male Locker Room" - icon_state = "locker_male" - -/area/crew_quarters/fitness/locker_room/female - name = "Female Locker Room" - icon_state = "locker_female" - - -/area/crew_quarters/fitness/recreation - name = "Recreation Area" - icon_state = "rec" - -/area/crew_quarters/cafeteria - name = "Cafeteria" - icon_state = "cafeteria" - -/area/crew_quarters/kitchen - name = "Kitchen" - icon_state = "kitchen" - -/area/crew_quarters/kitchen/coldroom - name = "Kitchen Cold Room" - icon_state = "kitchen_cold" - -/area/crew_quarters/bar - name = "Bar" - icon_state = "bar" - mood_bonus = 5 - mood_message = "I love being in the bar!\n" - -/area/crew_quarters/bar/atrium - name = "Atrium" - icon_state = "bar" - -/area/crew_quarters/electronic_marketing_den - name = "Electronic Marketing Den" - icon_state = "abandoned_m_den" - -/area/crew_quarters/abandoned_gambling_den - name = "Abandoned Gambling Den" - icon_state = "abandoned_g_den" - -/area/crew_quarters/abandoned_gambling_den/secondary - icon_state = "abandoned_g_den_2" - -/area/crew_quarters/theatre - name = "Theatre" - icon_state = "theatre" - -/area/crew_quarters/theatre/abandoned - name = "Abandoned Theatre" - icon_state = "abandoned_theatre" - -/area/library - name = "Library" - icon_state = "library" - flags_1 = CULT_PERMITTED_1 - -/area/library/lounge - name = "Library Lounge" - icon_state = "library_lounge" - -/area/library/artgallery - name = " Art Gallery" - icon_state = "library_gallery" - -/area/library/private - name = "Library Private Study" - icon_state = "library_gallery_private" - -/area/library/upper - name = "Library Upper Floor" - icon_state = "library" - -/area/library/printer - name = "Library Printer Room" - icon_state = "library" - -/area/library/abandoned - name = "Abandoned Library" - icon_state = "abandoned_library" - flags_1 = CULT_PERMITTED_1 - -/area/chapel - icon_state = "chapel" - ambientsounds = HOLY - flags_1 = NONE - -/area/chapel/main - name = "Chapel" - -/area/chapel/main/monastery - name = "Monastery" - -/area/chapel/office - name = "Chapel Office" - icon_state = "chapeloffice" - -/area/chapel/asteroid - name = "Chapel Asteroid" - icon_state = "explored" - -/area/chapel/asteroid/monastery - name = "Monastery Asteroid" - -/area/chapel/dock - name = "Chapel Dock" - icon_state = "construction" - -/area/lawoffice - name = "Law Office" - icon_state = "law" - - -//Engineering - -/area/engine - ambientsounds = ENGINEERING - -/area/engine/engine_smes - name = "Engineering SMES" - icon_state = "engine_smes" - -/area/engine/engineering - name = "Engineering" - icon_state = "engine" - -/area/engine/atmos - name = "Atmospherics" - icon_state = "atmos" - flags_1 = CULT_PERMITTED_1 - -/area/engine/atmos/upper - name = "Upper Atmospherics" - -/area/engine/atmospherics_engine - name = "Atmospherics Engine" - icon_state = "atmos_engine" - valid_territory = FALSE - -/area/engine/engine_room //donut station specific - name = "Engine Room" - icon_state = "atmos_engine" - -/area/engine/lobby - name = "Engineering Lobby" - icon_state = "engi_lobby" - -/area/engine/engine_room/external - name = "Supermatter External Access" - icon_state = "engine_foyer" - -/area/engine/supermatter - name = "Supermatter Engine" - icon_state = "engine_sm" - valid_territory = FALSE - -/area/engine/break_room - name = "Engineering Foyer" - icon_state = "engine_break" - -/area/engine/gravity_generator - name = "Gravity Generator Room" - icon_state = "grav_gen" - -/area/engine/storage - name = "Engineering Storage" - icon_state = "engi_storage" - -/area/engine/storage_shared - name = "Shared Engineering Storage" - icon_state = "engi_storage" - -/area/engine/transit_tube - name = "Transit Tube" - icon_state = "transit_tube" - - -//Solars - -/area/solar - requires_power = FALSE - dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT - valid_territory = FALSE - blob_allowed = FALSE - flags_1 = NONE - ambientsounds = ENGINEERING - -/area/solar/fore - name = "Fore Solar Array" - icon_state = "yellow" - -/area/solar/aft - name = "Aft Solar Array" - icon_state = "yellow" - -/area/solar/aux/port - name = "Port Bow Auxiliary Solar Array" - icon_state = "panelsA" - -/area/solar/aux/starboard - name = "Starboard Bow Auxiliary Solar Array" - icon_state = "panelsA" - -/area/solar/starboard - name = "Starboard Solar Array" - icon_state = "panelsS" - -/area/solar/starboard/aft - name = "Starboard Quarter Solar Array" - icon_state = "panelsAS" - -/area/solar/starboard/fore - name = "Starboard Bow Solar Array" - icon_state = "panelsFS" - -/area/solar/port - name = "Port Solar Array" - icon_state = "panelsP" - -/area/solar/port/aft - name = "Port Quarter Solar Array" - icon_state = "panelsAP" - -/area/solar/port/fore - name = "Port Bow Solar Array" - icon_state = "panelsFP" - -/area/solar/aisat - name = "AI Satellite Solars" - icon_state = "yellow" - -//Solar Maint - -/area/maintenance/solars - name = "Solar Maintenance" - icon_state = "yellow" - -/area/maintenance/solars/port - name = "Port Solar Maintenance" - icon_state = "SolarcontrolP" - -/area/maintenance/solars/port/aft - name = "Port Quarter Solar Maintenance" - icon_state = "SolarcontrolAP" - -/area/maintenance/solars/port/fore - name = "Port Bow Solar Maintenance" - icon_state = "SolarcontrolFP" - -/area/maintenance/solars/starboard - name = "Starboard Solar Maintenance" - icon_state = "SolarcontrolS" - -/area/maintenance/solars/starboard/aft - name = "Starboard Quarter Solar Maintenance" - icon_state = "SolarcontrolAS" - -/area/maintenance/solars/starboard/fore - name = "Starboard Bow Solar Maintenance" - icon_state = "SolarcontrolFS" - -//Teleporter - -/area/teleporter - name = "Teleporter Room" - icon_state = "teleporter" - ambientsounds = ENGINEERING - -/area/gateway - name = "Gateway" - icon_state = "gateway" - ambientsounds = ENGINEERING - -//MedBay - -/area/medical - name = "Medical" - icon_state = "medbay1" - ambientsounds = MEDICAL - -/area/medical/abandoned - name = "Abandoned Medbay" - icon_state = "abandoned_medbay" - ambientsounds = list('sound/ambience/signal.ogg') - -/area/medical/medbay/central - name = "Medbay Central" - icon_state = "med_central" - -/area/medical/medbay/lobby - name = "Medbay Lobby" - icon_state = "med_lobby" - - //Medbay is a large area, these additional areas help level out APC load. - -/area/medical/medbay/zone2 - name = "Medbay" - icon_state = "medbay2" - -/area/medical/medbay/aft - name = "Medbay Aft" - icon_state = "med_aft" - -/area/medical/storage - name = "Medbay Storage" - icon_state = "med_storage" - -/area/medical/paramedic - name = "Paramedic Dispatch" - icon_state = "paramedic" - -/area/medical/office - name = "Medical Office" - icon_state = "med_office" - -/area/medical/surgery/room_c - name = "Surgery C" - icon_state = "surgery" - -/area/medical/surgery/room_d - name = "Surgery D" - icon_state = "surgery" - -/area/medical/break_room - name = "Medical Break Room" - icon_state = "med_break" - -/area/medical/coldroom - name = "Medical Cold Room" - icon_state = "kitchen_cold" - -/area/medical/patients_rooms - name = "Patients' Rooms" - icon_state = "patients" - -/area/medical/patients_rooms/room_a - name = "Patient Room A" - icon_state = "patients" - -/area/medical/patients_rooms/room_b - name = "Patient Room B" - icon_state = "patients" - -/area/medical/virology - name = "Virology" - icon_state = "virology" - flags_1 = CULT_PERMITTED_1 - -/area/medical/morgue - name = "Morgue" - icon_state = "morgue" - ambientsounds = SPOOKY - -/area/medical/chemistry - name = "Chemistry" - icon_state = "chem" - -/area/medical/pharmacy - name = "Pharmacy" - icon_state = "pharmacy" - -/area/medical/surgery - name = "Surgery" - icon_state = "surgery" - -/area/medical/surgery/room_b - name = "Surgery B" - icon_state = "surgery" - -/area/medical/cryo - name = "Cryogenics" - icon_state = "cryo" - -/area/medical/exam_room - name = "Exam Room" - icon_state = "exam_room" - -/area/medical/genetics - name = "Genetics Lab" - icon_state = "genetics" - -/area/medical/sleeper - name = "Medbay Treatment Center" - icon_state = "exam_room" - -/area/medical/psychology - name = "Psychology Office" - icon_state = "psychology" - mood_bonus = 3 - mood_message = "I feel at ease here.\n" - ambientsounds = list('sound/ambience/aurora_caelus_short.ogg') - -//Security - -/area/security - name = "Security" - icon_state = "security" - ambientsounds = HIGHSEC - -/area/security/main - name = "Security Office" - icon_state = "security" - -/area/security/brig - name = "Brig" - icon_state = "brig" - -/area/security/brig/upper - name = "Brig Overlook" - -/area/security/courtroom - name = "Courtroom" - icon_state = "courtroom" - -/area/security/prison - name = "Prison Wing" - icon_state = "sec_prison" - -/area/security/prison/toilet //radproof - name = "Prison Toilet" - icon_state = "sec_prison_safe" - -/area/security/prison/safe //radproof - name = "Prison Wing Cells" - icon_state = "sec_prison_safe" - -/area/security/prison/upper - name = "Upper Prison Wing" - icon_state = "prison_upper" - -/area/security/prison/visit - name = "Prison Visitation Area" - icon_state = "prison_visit" - -/area/security/prison/rec - name = "Prison Rec Room" - icon_state = "prison_rec" - -/area/security/prison/mess - name = "Prison Mess Hall" - icon_state = "prison_mess" - -/area/security/prison/work - name = "Prison Work Room" - icon_state = "prison_work" - -/area/security/prison/shower - name = "Prison Shower" - icon_state = "prison_shower" - -/area/security/prison/workout - name = "Prison Gym" - icon_state = "prison_workout" - -/area/security/prison/garden - name = "Prison Garden" - icon_state = "prison_garden" - -/area/security/processing - name = "Labor Shuttle Dock" - icon_state = "sec_processing" - -/area/security/processing/cremation - name = "Security Crematorium" - icon_state = "sec_cremation" - -/area/security/warden - name = "Brig Control" - icon_state = "warden" - -/area/security/detectives_office - name = "Detective's Office" - icon_state = "detective" - ambientsounds = list('sound/ambience/ambidet1.ogg','sound/ambience/ambidet2.ogg') - -/area/security/detectives_office/private_investigators_office - name = "Private Investigator's Office" - icon_state = "investigate_office" - -/area/security/range - name = "Firing Range" - icon_state = "firingrange" - -/area/security/execution - icon_state = "execution_room" - -/area/security/execution/transfer - name = "Transfer Centre" - icon_state = "sec_processing" - -/area/security/execution/education - name = "Prisoner Education Chamber" - -/area/security/nuke_storage - name = "Vault" - icon_state = "nuke_storage" - -/area/ai_monitored/nuke_storage - name = "Vault" - icon_state = "nuke_storage" - -/area/security/checkpoint - name = "Security Checkpoint" - icon_state = "checkpoint1" - -/area/security/checkpoint/auxiliary - icon_state = "checkpoint_aux" - -/area/security/checkpoint/escape - icon_state = "checkpoint_esc" - -/area/security/checkpoint/supply - name = "Security Post - Cargo Bay" - icon_state = "checkpoint_supp" - -/area/security/checkpoint/engineering - name = "Security Post - Engineering" - icon_state = "checkpoint_engi" - -/area/security/checkpoint/medical - name = "Security Post - Medbay" - icon_state = "checkpoint_med" - -/area/security/checkpoint/science - name = "Security Post - Science" - icon_state = "checkpoint_sci" - -/area/security/checkpoint/science/research - name = "Security Post - Research Division" - icon_state = "checkpoint_res" - -/area/security/checkpoint/customs - name = "Customs" - icon_state = "customs_point" - -/area/security/checkpoint/customs/auxiliary - icon_state = "customs_point_aux" - - -//Service - -/area/quartermaster - name = "Quartermasters" - icon_state = "quart" - -/area/quartermaster/sorting - name = "Delivery Office" - icon_state = "cargo_delivery" - -/area/quartermaster/warehouse - name = "Warehouse" - icon_state = "cargo_warehouse" - -/area/quartermaster/warehouse/upper - name = "Upper Warehouse" - -/area/quartermaster/office - name = "Cargo Office" - icon_state = "cargo_office" - -/area/quartermaster/storage - name = "Cargo Bay" - icon_state = "cargo_bay" - -/area/quartermaster/qm - name = "Quartermaster's Office" - icon_state = "quart_office" - -/area/quartermaster/qm/perch - name = "Quartermaster's Perch" - icon_state = "quart_perch" - -/area/quartermaster/miningdock - name = "Mining Dock" - icon_state = "mining" - -/area/quartermaster/miningoffice - name = "Mining Office" - icon_state = "mining" - -/area/janitor - name = "Custodial Closet" - icon_state = "janitor" - flags_1 = CULT_PERMITTED_1 - -/area/hydroponics - name = "Hydroponics" - icon_state = "hydro" - -/area/hydroponics/upper - name = "Upper Hydroponics" - icon_state = "hydro" - -/area/hydroponics/garden - name = "Garden" - icon_state = "garden" - -/area/hydroponics/garden/abandoned - name = "Abandoned Garden" - icon_state = "abandoned_garden" - -/area/hydroponics/garden/monastery - name = "Monastery Garden" - icon_state = "hydro" - - -//Science - -/area/science - name = "Science Division" - icon_state = "science" - -/area/science/lab - name = "Research and Development" - icon_state = "research" - -/area/science/xenobiology - name = "Xenobiology Lab" - icon_state = "xenobio" - -/area/science/storage - name = "Toxins Storage" - icon_state = "tox_storage" - -/area/science/test_area - valid_territory = FALSE - name = "Toxins Test Area" - icon_state = "tox_test" - -/area/science/mixing - name = "Toxins Mixing Lab" - icon_state = "tox_mix" - -/area/science/mixing/chamber - name = "Toxins Mixing Chamber" - icon_state = "tox_mix_chamber" - valid_territory = FALSE - -/area/science/genetics - name = "Genetics Lab" - icon_state = "geneticssci" - -/area/science/misc_lab - name = "Testing Lab" - icon_state = "tox_misc" - -/area/science/misc_lab/range - name = "Research Testing Range" - icon_state = "tox_range" - -/area/science/server - name = "Research Division Server Room" - icon_state = "server" - -/area/science/explab - name = "Experimentation Lab" - icon_state = "exp_lab" - -/area/science/robotics - name = "Robotics" - icon_state = "robotics" - -/area/science/robotics/mechbay - name = "Mech Bay" - icon_state = "mechbay" - -/area/science/robotics/lab - name = "Robotics Lab" - icon_state = "ass_line" - -/area/science/research - name = "Research Division" - icon_state = "science" - -/area/science/research/abandoned - name = "Abandoned Research Lab" - icon_state = "abandoned_sci" - -/area/science/nanite - name = "Nanite Lab" - icon_state = "nanite" - -//Storage - -/area/storage/tools - name = "Auxiliary Tool Storage" - icon_state = "tool_storage" - -/area/storage/primary - name = "Primary Tool Storage" - icon_state = "primary_storage" - -/area/storage/art - name = "Art Supply Storage" - icon_state = "art_storage" - -/area/storage/tcom - name = "Telecomms Storage" - icon_state = "tcom" - valid_territory = FALSE - -/area/storage/eva - name = "EVA Storage" - icon_state = "eva" - -/area/storage/emergency/starboard - name = "Starboard Emergency Storage" - icon_state = "emergency_storage" - -/area/storage/emergency/port - name = "Port Emergency Storage" - icon_state = "emergency_storage" - -/area/storage/tech - name = "Technical Storage" - icon_state = "aux_storage" - -/area/storage/mining - name = "Public Mining Storage" - icon_state = "mining" - -//Construction - -/area/construction - name = "Construction Area" - icon_state = "construction" - ambientsounds = ENGINEERING - -/area/construction/mining/aux_base - name = "Auxiliary Base Construction" - icon_state = "aux_base_construction" - -/area/construction/storage_wing - name = "Storage Wing" - icon_state = "storage_wing" - -// Vacant Rooms -/area/vacant_room - name = "Vacant Room" - icon_state = "vacant_room" - ambientsounds = MAINTENANCE - -/area/vacant_room/office - name = "Vacant Office" - icon_state = "vacant_office" - -/area/vacant_room/commissary - name = "Vacant Commissary" - icon_state = "vacant_commissary" - -//AI - -/area/ai_monitored/security/armory - name = "Armory" - icon_state = "armory" - ambientsounds = HIGHSEC - -/area/ai_monitored/security/armory/upper - name = "Upper Armory" - -/area/ai_monitored/storage/eva - name = "EVA Storage" - icon_state = "eva" - ambientsounds = HIGHSEC - -/area/ai_monitored/storage/eva/upper - name = "Upper EVA Storage" - -/area/ai_monitored/storage/satellite - name = "AI Satellite Maint" - icon_state = "ai_storage" - ambientsounds = HIGHSEC - - //Turret_protected - -/area/ai_monitored/turret_protected - ambientsounds = list('sound/ambience/ambimalf.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg') - -/area/ai_monitored/turret_protected/ai_upload - name = "AI Upload Chamber" - icon_state = "ai_upload" - -/area/ai_monitored/turret_protected/ai_upload_foyer - name = "AI Upload Access" - icon_state = "ai_upload_foyer" - -/area/ai_monitored/turret_protected/ai - name = "AI Chamber" - icon_state = "ai_chamber" - -/area/ai_monitored/turret_protected/aisat - name = "AI Satellite" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat/atmos - name = "AI Satellite Atmos" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat/foyer - name = "AI Satellite Foyer" - icon_state = "ai_foyer" - -/area/ai_monitored/turret_protected/aisat/service - name = "AI Satellite Service" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat/hallway - name = "AI Satellite Hallway" - icon_state = "ai" - -/area/aisat - name = "AI Satellite Exterior" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat_interior - name = "AI Satellite Antechamber" - icon_state = "ai_interior" - -/area/ai_monitored/turret_protected/ai_sat_ext_as - name = "AI Sat Ext" - icon_state = "ai_sat_east" - -/area/ai_monitored/turret_protected/ai_sat_ext_ap - name = "AI Sat Ext" - icon_state = "ai_sat_west" - - -// Telecommunications Satellite - -/area/tcommsat - ambientsounds = list('sound/ambience/ambisin2.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/ambigen10.ogg', 'sound/ambience/ambitech.ogg',\ - 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg', 'sound/ambience/ambimystery.ogg') - -/area/tcommsat/computer - name = "Telecomms Control Room" - icon_state = "tcomsatcomp" - -/area/tcommsat/server - name = "Telecomms Server Room" - icon_state = "tcomsatcham" - -/area/tcommsat/server/upper - name = "Upper Telecomms Server Room" - -//External Hull Access -/area/maintenance/external - name = "External Hull Access" - icon_state = "amaint" - -/area/maintenance/external/aft - name = "Aft External Hull Access" - -/area/maintenance/external/port - name = "Port External Hull Access" - -/area/maintenance/external/port/bow - name = "Port Bow External Hull Access" +/* + +### This file contains a list of all the areas in your station. Format is as follows: + +/area/CATEGORY/OR/DESCRIPTOR/NAME (you can make as many subdivisions as you want) + name = "NICE NAME" (not required but makes things really nice) + icon = 'ICON FILENAME' (defaults to 'icons/turf/areas.dmi') + icon_state = "NAME OF ICON" (defaults to "unknown" (blank)) + requires_power = FALSE (defaults to true) + ambientsounds = list() (defaults to GENERIC from sound.dm. override it as "ambientsounds = list('sound/ambience/signal.ogg')" or using another define. + +NOTE: there are two lists of areas in the end of this file: centcom and station itself. Please maintain these lists valid. --rastaf0 + +*/ + + +/*-----------------------------------------------------------------------------*/ + +/area/ai_monitored //stub defined ai_monitored.dm + +/area/ai_monitored/turret_protected + +/area/space + icon_state = "space" + requires_power = TRUE + always_unpowered = TRUE + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + power_light = FALSE + power_equip = FALSE + power_environ = FALSE + valid_territory = FALSE + outdoors = TRUE + ambientsounds = SPACE + blob_allowed = FALSE //Eating up space doesn't count for victory as a blob. + flags_1 = CAN_BE_DIRTY_1 + +/area/space/nearstation + icon_state = "space_near" + dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT + +/area/start + name = "start area" + icon_state = "start" + requires_power = FALSE + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + has_gravity = STANDARD_GRAVITY + + +/area/testroom + requires_power = FALSE + name = "Test Room" + icon_state = "storage" + +//EXTRA + +/area/asteroid + name = "Asteroid" + icon_state = "asteroid" + requires_power = FALSE + has_gravity = STANDARD_GRAVITY + blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station. + valid_territory = FALSE + ambientsounds = MINING + flags_1 = CAN_BE_DIRTY_1 + +/area/asteroid/nearstation + dynamic_lighting = DYNAMIC_LIGHTING_FORCED + ambientsounds = RUINS + always_unpowered = FALSE + requires_power = TRUE + blob_allowed = TRUE + +/area/asteroid/nearstation/bomb_site + name = "Bomb Testing Asteroid" + +//STATION13 + +//Maintenance + +/area/maintenance + ambientsounds = MAINTENANCE + valid_territory = FALSE + + +//Departments + +/area/maintenance/department/chapel + name = "Chapel Maintenance" + icon_state = "maint_chapel" + +/area/maintenance/department/chapel/monastery + name = "Monastery Maintenance" + icon_state = "maint_monastery" + +/area/maintenance/department/crew_quarters/bar + name = "Bar Maintenance" + icon_state = "maint_bar" + +/area/maintenance/department/crew_quarters/dorms + name = "Dormitory Maintenance" + icon_state = "maint_dorms" + +/area/maintenance/department/eva + name = "EVA Maintenance" + icon_state = "maint_eva" + +/area/maintenance/department/electrical + name = "Electrical Maintenance" + icon_state = "maint_electrical" + +/area/maintenance/department/engine/atmos + name = "Atmospherics Maintenance" + icon_state = "maint_atmos" + +/area/maintenance/department/security + name = "Security Maintenance" + icon_state = "maint_sec" + +/area/maintenance/department/security/upper + name = "Upper Security Maintenance" + +/area/maintenance/department/security/brig + name = "Brig Maintenance" + icon_state = "maint_brig" + +/area/maintenance/department/medical + name = "Medbay Maintenance" + icon_state = "medbay_maint" + +/area/maintenance/department/medical/central + name = "Central Medbay Maintenance" + icon_state = "medbay_maint_central" + +/area/maintenance/department/medical/morgue + name = "Morgue Maintenance" + icon_state = "morgue_maint" + +/area/maintenance/department/science + name = "Science Maintenance" + icon_state = "maint_sci" + +/area/maintenance/department/science/central + name = "Central Science Maintenance" + icon_state = "maint_sci_central" + +/area/maintenance/department/cargo + name = "Cargo Maintenance" + icon_state = "maint_cargo" + +/area/maintenance/department/bridge + name = "Bridge Maintenance" + icon_state = "maint_bridge" + +/area/maintenance/department/engine + name = "Engineering Maintenance" + icon_state = "maint_engi" + +/area/maintenance/department/science/xenobiology + name = "Xenobiology Maintenance" + icon_state = "xenomaint" + xenobiology_compatible = TRUE + + +//Maintenance - Generic + +/area/maintenance/aft + name = "Aft Maintenance" + icon_state = "amaint" + +/area/maintenance/aft/upper + name = "Upper Aft Maintenance" + +/area/maintenance/aft/secondary + name = "Aft Maintenance" + icon_state = "amaint_2" + +/area/maintenance/central + name = "Central Maintenance" + icon_state = "maintcentral" + +/area/maintenance/central/secondary + name = "Central Maintenance" + icon_state = "maintcentral" + +/area/maintenance/fore + name = "Fore Maintenance" + icon_state = "fmaint" + +/area/maintenance/fore/upper + name = "Upper Fore Maintenance" + +/area/maintenance/fore/secondary + name = "Fore Maintenance" + icon_state = "fmaint_2" + +/area/maintenance/starboard + name = "Starboard Maintenance" + icon_state = "smaint" + +/area/maintenance/starboard/upper + name = "Upper Starboard Maintenance" + +/area/maintenance/starboard/central + name = "Central Starboard Maintenance" + icon_state = "smaint" + +/area/maintenance/starboard/secondary + name = "Secondary Starboard Maintenance" + icon_state = "smaint_2" + +/area/maintenance/starboard/aft + name = "Starboard Quarter Maintenance" + icon_state = "asmaint" + +/area/maintenance/starboard/aft/secondary + name = "Secondary Starboard Quarter Maintenance" + icon_state = "asmaint_2" + +/area/maintenance/starboard/fore + name = "Starboard Bow Maintenance" + icon_state = "fsmaint" + +/area/maintenance/port + name = "Port Maintenance" + icon_state = "pmaint" + +/area/maintenance/port/central + name = "Central Port Maintenance" + icon_state = "maintcentral" + +/area/maintenance/port/aft + name = "Port Quarter Maintenance" + icon_state = "apmaint" + +/area/maintenance/port/fore + name = "Port Bow Maintenance" + icon_state = "fpmaint" + +/area/maintenance/disposal + name = "Waste Disposal" + icon_state = "disposal" + +/area/maintenance/disposal/incinerator + name = "Incinerator" + icon_state = "incinerator" + + +//Hallway + +/area/hallway/primary/aft + name = "Aft Primary Hallway" + icon_state = "hallA" + +/area/hallway/primary/fore + name = "Fore Primary Hallway" + icon_state = "hallF" + +/area/hallway/primary/starboard + name = "Starboard Primary Hallway" + icon_state = "hallS" + +/area/hallway/primary/port + name = "Port Primary Hallway" + icon_state = "hallP" + +/area/hallway/primary/central + name = "Central Primary Hallway" + icon_state = "hallC" + +/area/hallway/primary/upper + name = "Upper Central Primary Hallway" + icon_state = "hallC" + + +/area/hallway/secondary/command + name = "Command Hallway" + icon_state = "bridge_hallway" + +/area/hallway/secondary/construction + name = "Construction Area" + icon_state = "construction" + +/area/hallway/secondary/exit + name = "Escape Shuttle Hallway" + icon_state = "escape" + +/area/hallway/secondary/exit/departure_lounge + name = "Departure Lounge" + icon_state = "escape_lounge" + +/area/hallway/secondary/entry + name = "Arrival Shuttle Hallway" + icon_state = "entry" + +/area/hallway/secondary/service + name = "Service Hallway" + icon_state = "hall_service" + +//Command + +/area/bridge + name = "Bridge" + icon_state = "bridge" + ambientsounds = list('sound/ambience/signal.ogg') + +/area/bridge/meeting_room + name = "Heads of Staff Meeting Room" + icon_state = "meeting" + +/area/bridge/meeting_room/council + name = "Council Chamber" + icon_state = "meeting" + +/area/bridge/showroom/corporate + name = "Corporate Showroom" + icon_state = "showroom" + +/area/crew_quarters/heads/captain + name = "Captain's Office" + icon_state = "captain" + +/area/crew_quarters/heads/captain/private + name = "Captain's Quarters" + icon_state = "captain_private" + +/area/crew_quarters/heads/chief + name = "Chief Engineer's Office" + icon_state = "ce_office" + +/area/crew_quarters/heads/cmo + name = "Chief Medical Officer's Office" + icon_state = "cmo_office" + +/area/crew_quarters/heads/hop + name = "Head of Personnel's Office" + icon_state = "hop_office" + +/area/crew_quarters/heads/hos + name = "Head of Security's Office" + icon_state = "hos_office" + +/area/crew_quarters/heads/hor + name = "Research Director's Office" + icon_state = "rd_office" + +/area/comms + name = "Communications Relay" + icon_state = "tcomsatcham" + +/area/server + name = "Messaging Server Room" + icon_state = "server" + +//Crew + +/area/crew_quarters/dorms + name = "Dormitories" + icon_state = "dorms" + safe = TRUE + +/area/crew_quarters/dorms/barracks + name = "Sleep Barracks" + +/area/crew_quarters/dorms/barracks/male + name = "Male Sleep Barracks" + icon_state = "dorms_male" + +/area/crew_quarters/dorms/barracks/female + name = "Female Sleep Barracks" + icon_state = "dorms_female" + +/area/crew_quarters/toilet + name = "Dormitory Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/auxiliary + name = "Auxiliary Restrooms" + icon_state = "toilet" + +/area/crew_quarters/toilet/locker + name = "Locker Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/restrooms + name = "Restrooms" + icon_state = "toilet" + +/area/crew_quarters/locker + name = "Locker Room" + icon_state = "locker" + +/area/crew_quarters/lounge + name = "Lounge" + icon_state = "lounge" + +/area/crew_quarters/fitness + name = "Fitness Room" + icon_state = "fitness" + +/area/crew_quarters/fitness/locker_room + name = "Unisex Locker Room" + icon_state = "locker" + +/area/crew_quarters/fitness/locker_room/male + name = "Male Locker Room" + icon_state = "locker_male" + +/area/crew_quarters/fitness/locker_room/female + name = "Female Locker Room" + icon_state = "locker_female" + + +/area/crew_quarters/fitness/recreation + name = "Recreation Area" + icon_state = "rec" + +/area/crew_quarters/cafeteria + name = "Cafeteria" + icon_state = "cafeteria" + +/area/crew_quarters/kitchen + name = "Kitchen" + icon_state = "kitchen" + +/area/crew_quarters/kitchen/coldroom + name = "Kitchen Cold Room" + icon_state = "kitchen_cold" + +/area/crew_quarters/bar + name = "Bar" + icon_state = "bar" + mood_bonus = 5 + mood_message = "I love being in the bar!\n" + +/area/crew_quarters/bar/atrium + name = "Atrium" + icon_state = "bar" + +/area/crew_quarters/electronic_marketing_den + name = "Electronic Marketing Den" + icon_state = "abandoned_m_den" + +/area/crew_quarters/abandoned_gambling_den + name = "Abandoned Gambling Den" + icon_state = "abandoned_g_den" + +/area/crew_quarters/abandoned_gambling_den/secondary + icon_state = "abandoned_g_den_2" + +/area/crew_quarters/theatre + name = "Theatre" + icon_state = "theatre" + +/area/crew_quarters/theatre/abandoned + name = "Abandoned Theatre" + icon_state = "abandoned_theatre" + +/area/library + name = "Library" + icon_state = "library" + flags_1 = CULT_PERMITTED_1 + +/area/library/lounge + name = "Library Lounge" + icon_state = "library_lounge" + +/area/library/artgallery + name = " Art Gallery" + icon_state = "library_gallery" + +/area/library/private + name = "Library Private Study" + icon_state = "library_gallery_private" + +/area/library/upper + name = "Library Upper Floor" + icon_state = "library" + +/area/library/printer + name = "Library Printer Room" + icon_state = "library" + +/area/library/abandoned + name = "Abandoned Library" + icon_state = "abandoned_library" + flags_1 = CULT_PERMITTED_1 + +/area/chapel + icon_state = "chapel" + ambientsounds = HOLY + flags_1 = NONE + +/area/chapel/main + name = "Chapel" + +/area/chapel/main/monastery + name = "Monastery" + +/area/chapel/office + name = "Chapel Office" + icon_state = "chapeloffice" + +/area/chapel/asteroid + name = "Chapel Asteroid" + icon_state = "explored" + +/area/chapel/asteroid/monastery + name = "Monastery Asteroid" + +/area/chapel/dock + name = "Chapel Dock" + icon_state = "construction" + +/area/lawoffice + name = "Law Office" + icon_state = "law" + + +//Engineering + +/area/engine + ambientsounds = ENGINEERING + +/area/engine/engine_smes + name = "Engineering SMES" + icon_state = "engine_smes" + +/area/engine/engineering + name = "Engineering" + icon_state = "engine" + +/area/engine/atmos + name = "Atmospherics" + icon_state = "atmos" + flags_1 = CULT_PERMITTED_1 + +/area/engine/atmos/upper + name = "Upper Atmospherics" + +/area/engine/atmospherics_engine + name = "Atmospherics Engine" + icon_state = "atmos_engine" + valid_territory = FALSE + +/area/engine/engine_room //donut station specific + name = "Engine Room" + icon_state = "atmos_engine" + +/area/engine/lobby + name = "Engineering Lobby" + icon_state = "engi_lobby" + +/area/engine/engine_room/external + name = "Supermatter External Access" + icon_state = "engine_foyer" + +/area/engine/supermatter + name = "Supermatter Engine" + icon_state = "engine_sm" + valid_territory = FALSE + +/area/engine/break_room + name = "Engineering Foyer" + icon_state = "engine_break" + +/area/engine/gravity_generator + name = "Gravity Generator Room" + icon_state = "grav_gen" + +/area/engine/storage + name = "Engineering Storage" + icon_state = "engi_storage" + +/area/engine/storage_shared + name = "Shared Engineering Storage" + icon_state = "engi_storage" + +/area/engine/transit_tube + name = "Transit Tube" + icon_state = "transit_tube" + + +//Solars + +/area/solar + requires_power = FALSE + dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT + valid_territory = FALSE + blob_allowed = FALSE + flags_1 = NONE + ambientsounds = ENGINEERING + +/area/solar/fore + name = "Fore Solar Array" + icon_state = "yellow" + +/area/solar/aft + name = "Aft Solar Array" + icon_state = "yellow" + +/area/solar/aux/port + name = "Port Bow Auxiliary Solar Array" + icon_state = "panelsA" + +/area/solar/aux/starboard + name = "Starboard Bow Auxiliary Solar Array" + icon_state = "panelsA" + +/area/solar/starboard + name = "Starboard Solar Array" + icon_state = "panelsS" + +/area/solar/starboard/aft + name = "Starboard Quarter Solar Array" + icon_state = "panelsAS" + +/area/solar/starboard/fore + name = "Starboard Bow Solar Array" + icon_state = "panelsFS" + +/area/solar/port + name = "Port Solar Array" + icon_state = "panelsP" + +/area/solar/port/aft + name = "Port Quarter Solar Array" + icon_state = "panelsAP" + +/area/solar/port/fore + name = "Port Bow Solar Array" + icon_state = "panelsFP" + +/area/solar/aisat + name = "AI Satellite Solars" + icon_state = "yellow" + +//Solar Maint + +/area/maintenance/solars + name = "Solar Maintenance" + icon_state = "yellow" + +/area/maintenance/solars/port + name = "Port Solar Maintenance" + icon_state = "SolarcontrolP" + +/area/maintenance/solars/port/aft + name = "Port Quarter Solar Maintenance" + icon_state = "SolarcontrolAP" + +/area/maintenance/solars/port/fore + name = "Port Bow Solar Maintenance" + icon_state = "SolarcontrolFP" + +/area/maintenance/solars/starboard + name = "Starboard Solar Maintenance" + icon_state = "SolarcontrolS" + +/area/maintenance/solars/starboard/aft + name = "Starboard Quarter Solar Maintenance" + icon_state = "SolarcontrolAS" + +/area/maintenance/solars/starboard/fore + name = "Starboard Bow Solar Maintenance" + icon_state = "SolarcontrolFS" + +//Teleporter + +/area/teleporter + name = "Teleporter Room" + icon_state = "teleporter" + ambientsounds = ENGINEERING + +/area/gateway + name = "Gateway" + icon_state = "gateway" + ambientsounds = ENGINEERING + +//MedBay + +/area/medical + name = "Medical" + icon_state = "medbay1" + ambientsounds = MEDICAL + +/area/medical/abandoned + name = "Abandoned Medbay" + icon_state = "abandoned_medbay" + ambientsounds = list('sound/ambience/signal.ogg') + +/area/medical/medbay/central + name = "Medbay Central" + icon_state = "med_central" + +/area/medical/medbay/lobby + name = "Medbay Lobby" + icon_state = "med_lobby" + + //Medbay is a large area, these additional areas help level out APC load. + +/area/medical/medbay/zone2 + name = "Medbay" + icon_state = "medbay2" + +/area/medical/medbay/aft + name = "Medbay Aft" + icon_state = "med_aft" + +/area/medical/storage + name = "Medbay Storage" + icon_state = "med_storage" + +/area/medical/paramedic + name = "Paramedic Dispatch" + icon_state = "paramedic" + +/area/medical/office + name = "Medical Office" + icon_state = "med_office" + +/area/medical/surgery/room_c + name = "Surgery C" + icon_state = "surgery" + +/area/medical/surgery/room_d + name = "Surgery D" + icon_state = "surgery" + +/area/medical/break_room + name = "Medical Break Room" + icon_state = "med_break" + +/area/medical/coldroom + name = "Medical Cold Room" + icon_state = "kitchen_cold" + +/area/medical/patients_rooms + name = "Patients' Rooms" + icon_state = "patients" + +/area/medical/patients_rooms/room_a + name = "Patient Room A" + icon_state = "patients" + +/area/medical/patients_rooms/room_b + name = "Patient Room B" + icon_state = "patients" + +/area/medical/virology + name = "Virology" + icon_state = "virology" + flags_1 = CULT_PERMITTED_1 + +/area/medical/morgue + name = "Morgue" + icon_state = "morgue" + ambientsounds = SPOOKY + +/area/medical/chemistry + name = "Chemistry" + icon_state = "chem" + +/area/medical/pharmacy + name = "Pharmacy" + icon_state = "pharmacy" + +/area/medical/surgery + name = "Surgery" + icon_state = "surgery" + +/area/medical/surgery/room_b + name = "Surgery B" + icon_state = "surgery" + +/area/medical/cryo + name = "Cryogenics" + icon_state = "cryo" + +/area/medical/exam_room + name = "Exam Room" + icon_state = "exam_room" + +/area/medical/genetics + name = "Genetics Lab" + icon_state = "genetics" + +/area/medical/sleeper + name = "Medbay Treatment Center" + icon_state = "exam_room" + +/area/medical/psychology + name = "Psychology Office" + icon_state = "psychology" + mood_bonus = 3 + mood_message = "I feel at ease here.\n" + ambientsounds = list('sound/ambience/aurora_caelus_short.ogg') + +//Security + +/area/security + name = "Security" + icon_state = "security" + ambientsounds = HIGHSEC + +/area/security/main + name = "Security Office" + icon_state = "security" + +/area/security/brig + name = "Brig" + icon_state = "brig" + +/area/security/brig/upper + name = "Brig Overlook" + +/area/security/courtroom + name = "Courtroom" + icon_state = "courtroom" + +/area/security/prison + name = "Prison Wing" + icon_state = "sec_prison" + +/area/security/prison/toilet //radproof + name = "Prison Toilet" + icon_state = "sec_prison_safe" + +/area/security/prison/safe //radproof + name = "Prison Wing Cells" + icon_state = "sec_prison_safe" + +/area/security/prison/upper + name = "Upper Prison Wing" + icon_state = "prison_upper" + +/area/security/prison/visit + name = "Prison Visitation Area" + icon_state = "prison_visit" + +/area/security/prison/rec + name = "Prison Rec Room" + icon_state = "prison_rec" + +/area/security/prison/mess + name = "Prison Mess Hall" + icon_state = "prison_mess" + +/area/security/prison/work + name = "Prison Work Room" + icon_state = "prison_work" + +/area/security/prison/shower + name = "Prison Shower" + icon_state = "prison_shower" + +/area/security/prison/workout + name = "Prison Gym" + icon_state = "prison_workout" + +/area/security/prison/garden + name = "Prison Garden" + icon_state = "prison_garden" + +/area/security/processing + name = "Labor Shuttle Dock" + icon_state = "sec_processing" + +/area/security/processing/cremation + name = "Security Crematorium" + icon_state = "sec_cremation" + +/area/security/warden + name = "Brig Control" + icon_state = "warden" + +/area/security/detectives_office + name = "Detective's Office" + icon_state = "detective" + ambientsounds = list('sound/ambience/ambidet1.ogg','sound/ambience/ambidet2.ogg') + +/area/security/detectives_office/private_investigators_office + name = "Private Investigator's Office" + icon_state = "investigate_office" + +/area/security/range + name = "Firing Range" + icon_state = "firingrange" + +/area/security/execution + icon_state = "execution_room" + +/area/security/execution/transfer + name = "Transfer Centre" + icon_state = "sec_processing" + +/area/security/execution/education + name = "Prisoner Education Chamber" + +/area/security/nuke_storage + name = "Vault" + icon_state = "nuke_storage" + +/area/ai_monitored/nuke_storage + name = "Vault" + icon_state = "nuke_storage" + +/area/security/checkpoint + name = "Security Checkpoint" + icon_state = "checkpoint1" + +/area/security/checkpoint/auxiliary + icon_state = "checkpoint_aux" + +/area/security/checkpoint/escape + icon_state = "checkpoint_esc" + +/area/security/checkpoint/supply + name = "Security Post - Cargo Bay" + icon_state = "checkpoint_supp" + +/area/security/checkpoint/engineering + name = "Security Post - Engineering" + icon_state = "checkpoint_engi" + +/area/security/checkpoint/medical + name = "Security Post - Medbay" + icon_state = "checkpoint_med" + +/area/security/checkpoint/science + name = "Security Post - Science" + icon_state = "checkpoint_sci" + +/area/security/checkpoint/science/research + name = "Security Post - Research Division" + icon_state = "checkpoint_res" + +/area/security/checkpoint/customs + name = "Customs" + icon_state = "customs_point" + +/area/security/checkpoint/customs/auxiliary + icon_state = "customs_point_aux" + + +//Service + +/area/quartermaster + name = "Quartermasters" + icon_state = "quart" + +/area/quartermaster/sorting + name = "Delivery Office" + icon_state = "cargo_delivery" + +/area/quartermaster/warehouse + name = "Warehouse" + icon_state = "cargo_warehouse" + +/area/quartermaster/warehouse/upper + name = "Upper Warehouse" + +/area/quartermaster/office + name = "Cargo Office" + icon_state = "cargo_office" + +/area/quartermaster/storage + name = "Cargo Bay" + icon_state = "cargo_bay" + +/area/quartermaster/qm + name = "Quartermaster's Office" + icon_state = "quart_office" + +/area/quartermaster/qm/perch + name = "Quartermaster's Perch" + icon_state = "quart_perch" + +/area/quartermaster/miningdock + name = "Mining Dock" + icon_state = "mining" + +/area/quartermaster/miningoffice + name = "Mining Office" + icon_state = "mining" + +/area/janitor + name = "Custodial Closet" + icon_state = "janitor" + flags_1 = CULT_PERMITTED_1 + +/area/hydroponics + name = "Hydroponics" + icon_state = "hydro" + +/area/hydroponics/upper + name = "Upper Hydroponics" + icon_state = "hydro" + +/area/hydroponics/garden + name = "Garden" + icon_state = "garden" + +/area/hydroponics/garden/abandoned + name = "Abandoned Garden" + icon_state = "abandoned_garden" + +/area/hydroponics/garden/monastery + name = "Monastery Garden" + icon_state = "hydro" + + +//Science + +/area/science + name = "Science Division" + icon_state = "science" + +/area/science/lab + name = "Research and Development" + icon_state = "research" + +/area/science/xenobiology + name = "Xenobiology Lab" + icon_state = "xenobio" + +/area/science/storage + name = "Toxins Storage" + icon_state = "tox_storage" + +/area/science/test_area + valid_territory = FALSE + name = "Toxins Test Area" + icon_state = "tox_test" + +/area/science/mixing + name = "Toxins Mixing Lab" + icon_state = "tox_mix" + +/area/science/mixing/chamber + name = "Toxins Mixing Chamber" + icon_state = "tox_mix_chamber" + valid_territory = FALSE + +/area/science/genetics + name = "Genetics Lab" + icon_state = "geneticssci" + +/area/science/misc_lab + name = "Testing Lab" + icon_state = "tox_misc" + +/area/science/misc_lab/range + name = "Research Testing Range" + icon_state = "tox_range" + +/area/science/server + name = "Research Division Server Room" + icon_state = "server" + +/area/science/explab + name = "Experimentation Lab" + icon_state = "exp_lab" + +/area/science/robotics + name = "Robotics" + icon_state = "robotics" + +/area/science/robotics/mechbay + name = "Mech Bay" + icon_state = "mechbay" + +/area/science/robotics/lab + name = "Robotics Lab" + icon_state = "ass_line" + +/area/science/research + name = "Research Division" + icon_state = "science" + +/area/science/research/abandoned + name = "Abandoned Research Lab" + icon_state = "abandoned_sci" + +/area/science/nanite + name = "Nanite Lab" + icon_state = "nanite" + +//Storage + +/area/storage/tools + name = "Auxiliary Tool Storage" + icon_state = "tool_storage" + +/area/storage/primary + name = "Primary Tool Storage" + icon_state = "primary_storage" + +/area/storage/art + name = "Art Supply Storage" + icon_state = "art_storage" + +/area/storage/tcom + name = "Telecomms Storage" + icon_state = "tcom" + valid_territory = FALSE + +/area/storage/eva + name = "EVA Storage" + icon_state = "eva" + +/area/storage/emergency/starboard + name = "Starboard Emergency Storage" + icon_state = "emergency_storage" + +/area/storage/emergency/port + name = "Port Emergency Storage" + icon_state = "emergency_storage" + +/area/storage/tech + name = "Technical Storage" + icon_state = "aux_storage" + +/area/storage/mining + name = "Public Mining Storage" + icon_state = "mining" + +//Construction + +/area/construction + name = "Construction Area" + icon_state = "construction" + ambientsounds = ENGINEERING + +/area/construction/mining/aux_base + name = "Auxiliary Base Construction" + icon_state = "aux_base_construction" + +/area/construction/storage_wing + name = "Storage Wing" + icon_state = "storage_wing" + +// Vacant Rooms +/area/vacant_room + name = "Vacant Room" + icon_state = "vacant_room" + ambientsounds = MAINTENANCE + +/area/vacant_room/office + name = "Vacant Office" + icon_state = "vacant_office" + +/area/vacant_room/commissary + name = "Vacant Commissary" + icon_state = "vacant_commissary" + +//AI + +/area/ai_monitored/security/armory + name = "Armory" + icon_state = "armory" + ambientsounds = HIGHSEC + +/area/ai_monitored/security/armory/upper + name = "Upper Armory" + +/area/ai_monitored/storage/eva + name = "EVA Storage" + icon_state = "eva" + ambientsounds = HIGHSEC + +/area/ai_monitored/storage/eva/upper + name = "Upper EVA Storage" + +/area/ai_monitored/storage/satellite + name = "AI Satellite Maint" + icon_state = "ai_storage" + ambientsounds = HIGHSEC + + //Turret_protected + +/area/ai_monitored/turret_protected + ambientsounds = list('sound/ambience/ambimalf.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg') + +/area/ai_monitored/turret_protected/ai_upload + name = "AI Upload Chamber" + icon_state = "ai_upload" + +/area/ai_monitored/turret_protected/ai_upload_foyer + name = "AI Upload Access" + icon_state = "ai_upload_foyer" + +/area/ai_monitored/turret_protected/ai + name = "AI Chamber" + icon_state = "ai_chamber" + +/area/ai_monitored/turret_protected/aisat + name = "AI Satellite" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat/atmos + name = "AI Satellite Atmos" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat/foyer + name = "AI Satellite Foyer" + icon_state = "ai_foyer" + +/area/ai_monitored/turret_protected/aisat/service + name = "AI Satellite Service" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat/hallway + name = "AI Satellite Hallway" + icon_state = "ai" + +/area/aisat + name = "AI Satellite Exterior" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat_interior + name = "AI Satellite Antechamber" + icon_state = "ai_interior" + +/area/ai_monitored/turret_protected/ai_sat_ext_as + name = "AI Sat Ext" + icon_state = "ai_sat_east" + +/area/ai_monitored/turret_protected/ai_sat_ext_ap + name = "AI Sat Ext" + icon_state = "ai_sat_west" + + +// Telecommunications Satellite + +/area/tcommsat + ambientsounds = list('sound/ambience/ambisin2.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/ambigen10.ogg', 'sound/ambience/ambitech.ogg',\ + 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg', 'sound/ambience/ambimystery.ogg') + +/area/tcommsat/computer + name = "Telecomms Control Room" + icon_state = "tcomsatcomp" + +/area/tcommsat/server + name = "Telecomms Server Room" + icon_state = "tcomsatcham" + +/area/tcommsat/server/upper + name = "Upper Telecomms Server Room" + +//External Hull Access +/area/maintenance/external + name = "External Hull Access" + icon_state = "amaint" + +/area/maintenance/external/aft + name = "Aft External Hull Access" + +/area/maintenance/external/port + name = "Port External Hull Access" + +/area/maintenance/external/port/bow + name = "Port Bow External Hull Access" diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index a4ff676dcf7..35eaa235263 100644 --- a/code/game/area/ai_monitored.dm +++ b/code/game/area/ai_monitored.dm @@ -1,30 +1,30 @@ -/area/ai_monitored - name = "AI Monitored Area" - var/list/obj/machinery/camera/motioncameras = list() - var/list/datum/weakref/motionTargets = list() - -/area/ai_monitored/Initialize(mapload) - . = ..() - if(mapload) - for (var/obj/machinery/camera/M in src) - if(M.isMotion()) - motioncameras.Add(M) - M.set_area_motion(src) - -//Only need to use one camera - -/area/ai_monitored/Entered(atom/movable/O) - ..() - if (ismob(O) && motioncameras.len) - for(var/X in motioncameras) - var/obj/machinery/camera/cam = X - cam.newTarget(O) - return - -/area/ai_monitored/Exited(atom/movable/O) - ..() - if (ismob(O) && motioncameras.len) - for(var/X in motioncameras) - var/obj/machinery/camera/cam = X - cam.lostTargetRef(WEAKREF(O)) - return +/area/ai_monitored + name = "AI Monitored Area" + var/list/obj/machinery/camera/motioncameras = list() + var/list/datum/weakref/motionTargets = list() + +/area/ai_monitored/Initialize(mapload) + . = ..() + if(mapload) + for (var/obj/machinery/camera/M in src) + if(M.isMotion()) + motioncameras.Add(M) + M.set_area_motion(src) + +//Only need to use one camera + +/area/ai_monitored/Entered(atom/movable/O) + ..() + if (ismob(O) && motioncameras.len) + for(var/X in motioncameras) + var/obj/machinery/camera/cam = X + cam.newTarget(O) + return + +/area/ai_monitored/Exited(atom/movable/O) + ..() + if (ismob(O) && motioncameras.len) + for(var/X in motioncameras) + var/obj/machinery/camera/cam = X + cam.lostTargetRef(WEAKREF(O)) + return diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 71ee3a4e67b..03486f0e995 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1,1433 +1,1433 @@ -/** - * The base type for nearly all physical objects in SS13 - - * Lots and lots of functionality lives here, although in general we are striving to move - * as much as possible to the components/elements system - */ -/atom - layer = TURF_LAYER - plane = GAME_PLANE - - ///If non-null, overrides a/an/some in all cases - var/article - - ///First atom flags var - var/flags_1 = NONE - ///Intearaction flags - var/interaction_flags_atom = NONE - - var/flags_ricochet = NONE - - ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this - var/ricochet_chance_mod = 1 - ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom - var/ricochet_damage_mod = 0.33 - - ///Reagents holder - var/datum/reagents/reagents = null - - ///This atom's HUD (med/sec, etc) images. Associative list. - var/list/image/hud_list = null - ///HUD images that this atom can provide. - var/list/hud_possible - - ///Value used to increment ex_act() if reactionary_explosions is on - var/explosion_block = 0 - - /** - * used to store the different colors on an atom - * - * its inherent color, the colored paint applied on it, special color effect etc... - */ - var/list/atom_colours - - - /// a very temporary list of overlays to remove - var/list/remove_overlays - /// a very temporary list of overlays to add - var/list/add_overlays - - ///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays - var/list/managed_vis_overlays - ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc - var/list/managed_overlays - - ///Proximity monitor associated with this atom - var/datum/proximity_monitor/proximity_monitor - ///Cooldown tick timer for buckle messages - var/buckle_message_cooldown = 0 - ///Last fingerprints to touch this atom - var/fingerprintslast - - var/list/filter_data //For handling persistent filters - - ///Economy cost of item - var/custom_price - ///Economy cost of item in premium vendor - var/custom_premium_price - ///Whether spessmen with an ID with an age below AGE_MINOR (20 by default) can buy this item - var/age_restricted = FALSE - - //List of datums orbiting this atom - var/datum/component/orbiter/orbiters - - /// Radiation insulation types - var/rad_insulation = RAD_NO_INSULATION - - ///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.) - ///The list referenced by this var can be shared by multiple objects and should not be directly modified. Instead, use [set_custom_materials][/atom/proc/set_custom_materials]. - var/list/custom_materials - ///Bitfield for how the atom handles materials. - var/material_flags = NONE - ///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines. - var/material_modifier = 1 - - var/datum/wires/wires = null - - var/list/alternate_appearances - - ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy() - var/list/targeted_by - - /// Last appearance of the atom for demo saving purposes - var/image/demo_last_appearance - - /// Last name used to calculate a color for the chatmessage overlays - var/chat_color_name - /// Last color calculated for the the chatmessage overlays - var/chat_color - /// A luminescence-shifted value of the last color calculated for chatmessage overlays - var/chat_color_darkened - -/** - * Called when an atom is created in byond (built in engine proc) - * - * Not a lot happens here in SS13 code, as we offload most of the work to the - * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader - * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate - * result is that the Intialize proc is called. - * - * We also generate a tag here if the DF_USE_TAG flag is set on the atom - */ -/atom/New(loc, ...) - //atom creation method that preloads variables at creation - if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() - world.preloader_load(src) - - if(datum_flags & DF_USE_TAG) - GenerateTag() - - var/do_initialize = SSatoms.initialized - if(do_initialize != INITIALIZATION_INSSATOMS) - args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD - if(SSatoms.InitAtom(src, args)) - //we were deleted - return - -/** - * The primary method that objects are setup in SS13 with - * - * we don't use New as we have better control over when this is called and we can choose - * to delay calls or hook other logic in and so forth - * - * During roundstart map parsing, atoms are queued for intialization in the base atom/New(), - * After the map has loaded, then Initalize is called on all atoms one by one. NB: this - * is also true for loading map templates as well, so they don't Initalize until all objects - * in the map file are parsed and present in the world - * - * If you're creating an object at any point after SSInit has run then this proc will be - * immediately be called from New. - * - * mapload: This parameter is true if the atom being loaded is either being intialized during - * the Atom subsystem intialization, or if the atom is being loaded from the map template. - * If the item is being created at runtime any time after the Atom subsystem is intialized then - * it's false. - * - * You must always call the parent of this proc, otherwise failures will occur as the item - * will not be seen as initalized (this can lead to all sorts of strange behaviour, like - * the item being completely unclickable) - * - * You must not sleep in this proc, or any subprocs - * - * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map - * there are no other arguments - * - * Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur. - * - * Note: the following functions don't call the base for optimization and must copypasta handling: - * * [/turf/Initialize] - * * [/turf/open/space/Initialize] - */ -/atom/proc/Initialize(mapload, ...) - //SHOULD_NOT_SLEEP(TRUE) - SHOULD_CALL_PARENT(TRUE) - if(flags_1 & INITIALIZED_1) - stack_trace("Warning: [src]([type]) initialized multiple times!") - flags_1 |= INITIALIZED_1 - - if(loc) - SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc` - - //atom color stuff - if(color) - add_atom_colour(color, FIXED_COLOUR_PRIORITY) - - if (light_power && light_range) - update_light() - - if (opacity && isturf(loc)) - var/turf/T = loc - T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways. - - if (canSmoothWith) - canSmoothWith = typelist("canSmoothWith", canSmoothWith) - - // apply materials properly from the default custom_materials value - set_custom_materials(custom_materials) - - ComponentInitialize() - - return INITIALIZE_HINT_NORMAL - -/** - * Late Intialization, for code that should run after all atoms have run Intialization - * - * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize] - * proc must return the hint - * [INITIALIZE_HINT_LATELOAD] otherwise you will never be called. - * - * useful for doing things like finding other machines on GLOB.machines because you can guarantee - * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization - * code has been run - */ -/atom/proc/LateInitialize() - set waitfor = FALSE - -/// Put your [AddComponent] calls here -/atom/proc/ComponentInitialize() - return - -/** - * Top level of the destroy chain for most atoms - * - * Cleans up the following: - * * Removes alternate apperances from huds that see them - * * qdels the reagent holder from atoms if it exists - * * clears the orbiters list - * * clears overlays and priority overlays - * * clears the light object - */ -/atom/Destroy() - if(alternate_appearances) - for(var/K in alternate_appearances) - var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K] - AA.remove_from_hud(src) - - if(reagents) - qdel(reagents) - - orbiters = null // The component is attached to us normaly and will be deleted elsewhere - - LAZYCLEARLIST(overlays) - - for(var/i in targeted_by) - var/mob/M = i - LAZYREMOVE(M.do_afters, src) - - targeted_by = null - QDEL_NULL(light) - - return ..() - -/atom/proc/handle_ricochet(obj/projectile/P) - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - var/a_incidence_s = abs(incidence_s) - if(a_incidence_s > 90 && a_incidence_s < 270) - return FALSE - if((P.flag in list("bullet", "bomb")) && P.ricochet_incidence_leeway) - if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway)) - return - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.setAngle(new_angle_s) - return TRUE - -///Can the mover object pass this atom, while heading for the target turf -/atom/proc/CanPass(atom/movable/mover, turf/target) - SHOULD_CALL_PARENT(TRUE) - SHOULD_BE_PURE(TRUE) - if(mover.movement_type & UNSTOPPABLE) - return TRUE - . = CanAllowThrough(mover, target) - // This is cheaper than calling the proc every time since most things dont override CanPassThrough - if(!mover.generic_canpass) - return mover.CanPassThrough(src, target, .) - -/// Returns true or false to allow the mover to move through src -/atom/proc/CanAllowThrough(atom/movable/mover, turf/target) - SHOULD_CALL_PARENT(TRUE) - //SHOULD_BE_PURE(TRUE) - return !density - -/** - * Is this atom currently located on centcom - * - * Specifically, is it on the z level and within the centcom areas - * - * You can also be in a shuttleshuttle during endgame transit - * - * Used in gamemode to identify mobs who have escaped and for some other areas of the code - * who don't want atoms where they shouldn't be - */ -/atom/proc/onCentCom() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(is_reserved_level(T.z)) - for(var/A in SSshuttle.mobile) - var/obj/docking_port/mobile/M = A - if(M.launch_status == ENDGAME_TRANSIT) - for(var/place in M.shuttle_areas) - var/area/shuttle/shuttle_area = place - if(T in shuttle_area) - return TRUE - - if(!is_centcom_level(T.z))//if not, don't bother - return FALSE - - //Check for centcom itself - if(istype(T.loc, /area/centcom)) - return TRUE - - //Check for centcom shuttles - for(var/A in SSshuttle.mobile) - var/obj/docking_port/mobile/M = A - if(M.launch_status == ENDGAME_LAUNCHED) - for(var/place in M.shuttle_areas) - var/area/shuttle/shuttle_area = place - if(T in shuttle_area) - return TRUE - -/** - * Is the atom in any of the centcom syndicate areas - * - * Either in the syndie base on centcom, or any of their shuttles - * - * Also used in gamemode code for win conditions - */ -/atom/proc/onSyndieBase() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(!is_centcom_level(T.z))//if not, don't bother - return FALSE - - if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod)) - return TRUE - - return FALSE - -/** - * Is the atom in an away mission - * - * Must be in the away mission z-level to return TRUE - * - * Also used in gamemode code for win conditions - */ -/atom/proc/onAwayMission() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(is_away_level(T.z)) - return TRUE - - return FALSE - - - -///This atom has been hit by a hulkified mob in hulk mode (user) -/atom/proc/attack_hulk(mob/living/carbon/human/user) - SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) - -/** - * Ensure a list of atoms/reagents exists inside this atom - * - * Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder - * creating the reagent holder if it exists. - * - * If the part is a moveable atom and the previous location of the item was a mob/living, - * it calls the inventory handler transferItemToLoc for that mob/living and transfers the part - * to this atom - * - * Otherwise it simply forceMoves the atom into this atom - */ -/atom/proc/CheckParts(list/parts_list) - if(parts_list) - for(var/A in parts_list) - if(istype(A, /datum/reagent)) - if(!reagents) - reagents = new() - reagents.reagent_list.Add(A) - reagents.conditional_update() - else if(ismovable(A)) - var/atom/movable/M = A - if(isliving(M.loc)) - var/mob/living/L = M.loc - L.transferItemToLoc(M, src) - else - M.forceMove(src) - parts_list.Cut() - -///Hook for multiz??? -/atom/proc/update_multiz(prune_on_fail = FALSE) - return FALSE - -///Take air from the passed in gas mixture datum -/atom/proc/assume_air(datum/gas_mixture/giver) - qdel(giver) - return null - -///Remove air from this atom -/atom/proc/remove_air(amount) - return null - -///Return the current air environment in this atom -/atom/proc/return_air() - if(loc) - return loc.return_air() - else - return null - -///Return the air if we can analyze it -/atom/proc/return_analyzable_air() - return null - -///Check if this atoms eye is still alive (probably) -/atom/proc/check_eye(mob/user) - return - -/atom/proc/Bumped(atom/movable/AM) - set waitfor = FALSE - SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, AM) - -/// Convenience proc to see if a container is open for chemistry handling -/atom/proc/is_open_container() - return is_refillable() && is_drainable() - -/// Is this atom injectable into other atoms -/atom/proc/is_injectable(mob/user, allowmobs = TRUE) - return reagents && (reagents.flags & (INJECTABLE | REFILLABLE)) - -/// Can we draw from this atom with an injectable atom -/atom/proc/is_drawable(mob/user, allowmobs = TRUE) - return reagents && (reagents.flags & (DRAWABLE | DRAINABLE)) - -/// Can this atoms reagents be refilled -/atom/proc/is_refillable() - return reagents && (reagents.flags & REFILLABLE) - -/// Is this atom drainable of reagents -/atom/proc/is_drainable() - return reagents && (reagents.flags & DRAINABLE) - -/** Handles exposing this atom to a list of reagents. - * - * Sends COMSIG_ATOM_EXPOSE_REAGENTS - * Calls expose_atom() for every reagent in the reagent list. - * - * Arguments: - * - [reagents][/list]: The list of reagents the atom is being exposed to. - * - [source][/datum/reagents]: The reagent holder the reagents are being sourced from. - * - method: How the atom is being exposed to the reagents. - * - volume_modifier: Volume multiplier. - * - show_message: Whether to display anything to mobs when they are exposed. - */ -/atom/proc/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) - if((. = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, method, volume_modifier, show_message)) & COMPONENT_NO_EXPOSE_REAGENTS) - return - - for(var/reagent in reagents) - var/datum/reagent/R = reagent - . |= R.expose_atom(src, reagents[R]) - -/// Are you allowed to drop this atom -/atom/proc/AllowDrop() - return FALSE - -/atom/proc/CheckExit() - return 1 - -///Is this atom within 1 tile of another atom -/atom/proc/HasProximity(atom/movable/AM as mob|obj) - return - -/** - * React to an EMP of the given severity - * - * Default behaviour is to send the [COMSIG_ATOM_EMP_ACT] signal - * - * If the signal does not return protection, and there are attached wires then we call - * [emp_pulse][/datum/wires/proc/emp_pulse] on the wires - * - * We then return the protection value - */ -/atom/proc/emp_act(severity) - var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) - if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) - wires.emp_pulse() - return protection // Pass the protection value collected here upwards - -/** - * React to a hit by a projectile object - * - * Default behaviour is to send the [COMSIG_ATOM_BULLET_ACT] and then call [on_hit][/obj/projectile/proc/on_hit] on the projectile - */ -/atom/proc/bullet_act(obj/projectile/P, def_zone) - SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) - . = P.on_hit(src, 0, def_zone) - -///Return true if we're inside the passed in atom -/atom/proc/in_contents_of(container)//can take class or object instance as argument - if(ispath(container)) - if(istype(src.loc, container)) - return TRUE - else if(src in container) - return TRUE - return FALSE - -/** - * Get the name of this object for examine - * - * You can override what is returned from this proc by registering to listen for the - * [COMSIG_ATOM_GET_EXAMINE_NAME] signal - */ -/atom/proc/get_examine_name(mob/user) - . = "\a [src]" - var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]") - if(article) - . = "[article] [src]" - override[EXAMINE_POSITION_ARTICLE] = article - if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) - . = override.Join("") - -///Generate the full examine string of this atom (including icon for goonchat) -/atom/proc/get_examine_string(mob/user, thats = FALSE) - return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]" - -/** - * Called when a mob examines (shift click or verb) this atom - * - * Default behaviour is to get the name and icon of the object and it's reagents where - * the [TRANSPARENT] flag is set on the reagents holder - * - * Produces a signal [COMSIG_PARENT_EXAMINE] - */ -/atom/proc/examine(mob/user) - . = list("[get_examine_string(user, TRUE)].") - - if(desc) - . += desc - - if(custom_materials) - var/list/materials_list = list() - for(var/i in custom_materials) - var/datum/material/M = i - materials_list += "[M.name]" - . += "It is made out of [english_list(materials_list)]." - if(reagents) - if(reagents.flags & TRANSPARENT) - . += "It contains:" - if(length(reagents.reagent_list)) - if(user.can_see_reagents()) //Show each individual reagent - for(var/datum/reagent/R in reagents.reagent_list) - . += "[R.volume] units of [R.name]" - else //Otherwise, just show the total volume - var/total_volume = 0 - for(var/datum/reagent/R in reagents.reagent_list) - total_volume += R.volume - . += "[total_volume] units of various reagents" - else - . += "Nothing." - else if(reagents.flags & AMOUNT_VISIBLE) - if(reagents.total_volume) - . += "It has [reagents.total_volume] unit\s left." - else - . += "It's empty." - - SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) -/** - * Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_TIME (default 1.5 seconds) - * - * This is where you can put extra information on something that may be superfluous or not important in critical gameplay - * moments, while allowing people to manually double-examine to take a closer look - * - * Produces a signal [COMSIG_PARENT_EXAMINE_MORE] - */ -/atom/proc/examine_more(mob/user) - . = list() - SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .) - if(!LAZYLEN(.)) // lol ..length - return list("You examine [src] closer, but find nothing of interest...") - -/// Updates the icon of the atom -/atom/proc/update_icon() - var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) - . = FALSE - - if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) - update_icon_state() - . = TRUE - - if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS)) - var/list/new_overlays = update_overlays() - if(managed_overlays) - cut_overlay(managed_overlays) - managed_overlays = null - if(length(new_overlays)) - managed_overlays = new_overlays - add_overlay(new_overlays) - . = TRUE - - SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, signalOut, .) - -/// Updates the icon state of the atom -/atom/proc/update_icon_state() - -/// Updates the overlays of the atom -/atom/proc/update_overlays() - SHOULD_CALL_PARENT(1) - . = list() - SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .) - -/** - * An atom we are buckled or is contained within us has tried to move - * - * Default behaviour is to send a warning that the user can't move while buckled as long - * as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks) - */ -/atom/proc/relaymove(mob/user) - if(buckle_message_cooldown <= world.time) - buckle_message_cooldown = world.time + 50 - to_chat(user, "You can't move while buckled to [src]!") - return - -/// Handle what happens when your contents are exploded by a bomb -/atom/proc/contents_explosion(severity, target) - return //For handling the effects of explosions on contents that would not normally be effected - -/** - * React to being hit by an explosion - * - * Default behaviour is to call [contents_explosion][/atom/proc/contents_explosion] and send the [COMSIG_ATOM_EX_ACT] signal - */ -/atom/proc/ex_act(severity, target) - set waitfor = FALSE - contents_explosion(severity, target) - SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) - -/** - * React to a hit by a blob objecd - * - * default behaviour is to send the [COMSIG_ATOM_BLOB_ACT] signal - */ -/atom/proc/blob_act(obj/structure/blob/B) - SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) - return - -/atom/proc/fire_act(exposed_temperature, exposed_volume) - SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) - return - -/** - * React to being hit by a thrown object - * - * Default behaviour is to call [hitby_react][/atom/proc/hitby_react] on ourselves after 2 seconds if we are dense - * and under normal gravity. - * - * Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is - * deleted shortly after hitting something (during explosions or other massive events that - * throw lots of items around - singularity being a notable example) - */ -/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). - addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) - -/** - * We have have actually hit the passed in atom - * - * Default behaviour is to move back from the item that hit us - */ -/atom/proc/hitby_react(atom/movable/AM) - if(AM && isturf(AM.loc)) - step(AM, turn(AM.dir, 180)) - -///Handle the atom being slipped over -/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube, paralyze, force_drop) - return - -///returns the mob's dna info as a list, to be inserted in an object's blood_DNA list -/mob/living/proc/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - return list("ANIMAL DNA" = "Y-") - -///Get the mobs dna list -/mob/living/carbon/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - var/list/blood_dna = list() - if(dna) - blood_dna[dna.unique_enzymes] = dna.blood_type - else - blood_dna["UNKNOWN DNA"] = "X*" - return blood_dna - -/mob/living/carbon/alien/get_blood_dna_list() - return list("UNKNOWN DNA" = "X*") - -/mob/living/silicon/get_blood_dna_list() - return list("MOTOR OIL" = "SAE 5W-30") //just a little flavor text. - -///to add a mob's dna info into an object's blood_dna list. -/atom/proc/transfer_mob_blood_dna(mob/living/L) - // Returns 0 if we have that blood already - var/new_blood_dna = L.get_blood_dna_list() - if(!new_blood_dna) - return FALSE - var/old_length = blood_DNA_length() - add_blood_DNA(new_blood_dna) - if(blood_DNA_length() == old_length) - return FALSE - return TRUE - -///to add blood from a mob onto something, and transfer their dna info -/atom/proc/add_mob_blood(mob/living/M) - var/list/blood_dna = M.get_blood_dna_list() - if(!blood_dna) - return FALSE - return add_blood_DNA(blood_dna) - -///Is this atom in space -/atom/proc/isinspace() - if(isspaceturf(get_turf(src))) - return TRUE - else - return FALSE - -///Used for making a sound when a mob involuntarily falls into the ground. -/atom/proc/handle_fall(mob/faller) - return - -///Respond to the singularity eating this atom -/atom/proc/singularity_act() - return - -/** - * Respond to the singularity pulling on us - * - * Default behaviour is to send [COMSIG_ATOM_SING_PULL] and return - */ -/atom/proc/singularity_pull(obj/singularity/S, current_size) - SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) - - -/** - * Respond to acid being used on our atom - * - * Default behaviour is to send [COMSIG_ATOM_ACID_ACT] and return - */ -/atom/proc/acid_act(acidpwr, acid_volume) - SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) - -/** - * Respond to an emag being used on our atom - * - * Default behaviour is to send [COMSIG_ATOM_EMAG_ACT] and return - */ -/atom/proc/emag_act(mob/user, obj/item/card/emag/E) - SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user, E) - -/** - * Respond to a radioactive wave hitting this atom - * - * Default behaviour is to send [COMSIG_ATOM_RAD_ACT] and return - */ -/atom/proc/rad_act(strength) - SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) - -/** - * Respond to narsie eating our atom - * - * Default behaviour is to send [COMSIG_ATOM_NARSIE_ACT] and return - */ -/atom/proc/narsie_act() - SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) - - -///Return the values you get when an RCD eats you? -/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - return FALSE - - -/** - * Respond to an RCD acting on our item - * - * Default behaviour is to send [COMSIG_ATOM_RCD_ACT] and return FALSE - */ -/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode) - return FALSE - -/** - * Respond to an electric bolt action on our item - * - * Default behaviour is to return, we define here to allow for cleaner code later on - */ -/atom/proc/zap_act(power, zap_flags, shocked_targets) - return - -/** - * Implement the behaviour for when a user click drags a storage object to your atom - * - * This behaviour is usually to mass transfer, but this is no longer a used proc as it just - * calls the underyling /datum/component/storage dump act if a component exists - * - * TODO these should be purely component items that intercept the atom clicks higher in the - * call chain - */ -/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user) - if(GetComponent(/datum/component/storage)) - return component_storage_contents_dump_act(src_object, user) - return FALSE - -/** - * Implement the behaviour for when a user click drags another storage item to you - * - * In this case we get as many of the tiems from the target items compoent storage and then - * put everything into ourselves (or our storage component) - * - * TODO these should be purely component items that intercept the atom clicks higher in the - * call chain - */ -/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user) - var/list/things = src_object.contents() - var/datum/progressbar/progress = new(user, things.len, src) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress))) - stoplag(1) - progress.end_progress() - to_chat(user, "You dump as much of [src_object.parent]'s contents [STR.insert_preposition]to [src] as you can.") - STR.orient2hud(user) - src_object.orient2hud(user) - if(user.active_storage) //refresh the HUD to show the transfered contents - user.active_storage.close(user) - user.active_storage.show_to(user) - return TRUE - -///Get the best place to dump the items contained in the source storage item? -/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) - return null - -/** - * This proc is called when an atom in our contents has it's [Destroy][/atom/Destroy] called - * - * Default behaviour is to simply send [COMSIG_ATOM_CONTENTS_DEL] - */ -/atom/proc/handle_atom_del(atom/A) - SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A) - -/** - * called when the turf the atom resides on is ChangeTurfed - * - * Default behaviour is to loop through atom contents and call their HandleTurfChange() proc - */ -/atom/proc/HandleTurfChange(turf/T) - for(var/a in src) - var/atom/A = a - A.HandleTurfChange(T) - -/** - * the vision impairment to give to the mob whose perspective is set to that atom - * - * (e.g. an unfocused camera giving you an impaired vision when looking through it) - */ -/atom/proc/get_remote_view_fullscreens(mob/user) - return - -/** - * the sight changes to give to the mob whose perspective is set to that atom - * - * (e.g. A mob with nightvision loses its nightvision while looking through a normal camera) - */ -/atom/proc/update_remote_sight(mob/living/user) - return - - -/** - * Hook for running code when a dir change occurs - * - * Not recommended to use, listen for the [COMSIG_ATOM_DIR_CHANGE] signal instead (sent by this proc) - */ -/atom/proc/setDir(newdir) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) - dir = newdir - -///Handle melee attack by a mech -/atom/proc/mech_melee_attack(obj/mecha/M) - return - -/** - * Called when the atom log's in or out - * - * Default behaviour is to call on_log on the location this atom is in - */ -/atom/proc/on_log(login) - if(loc) - loc.on_log(login) - - -/* - Atom Colour Priority System - A System that gives finer control over which atom colour to colour the atom with. - The "highest priority" one is always displayed as opposed to the default of - "whichever was set last is displayed" -*/ - - -///Adds an instance of colour_type to the atom's atom_colours list -/atom/proc/add_atom_colour(coloration, colour_priority) - if(!atom_colours || !atom_colours.len) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - if(!coloration) - return - if(colour_priority > atom_colours.len) - return - atom_colours[colour_priority] = coloration - update_atom_colour() - - -///Removes an instance of colour_type from the atom's atom_colours list -/atom/proc/remove_atom_colour(colour_priority, coloration) - if(!atom_colours) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - if(colour_priority > atom_colours.len) - return - if(coloration && atom_colours[colour_priority] != coloration) - return //if we don't have the expected color (for a specific priority) to remove, do nothing - atom_colours[colour_priority] = null - update_atom_colour() - - -///Resets the atom's color to null, and then sets it to the highest priority colour available -/atom/proc/update_atom_colour() - if(!atom_colours) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - color = null - for(var/C in atom_colours) - if(islist(C)) - var/list/L = C - if(L.len) - color = L - return - else if(C) - color = C - return - - -///Proc for being washed by a shower -/atom/proc/washed(var/atom/washer, wash_strength = CLEAN_WEAK) - SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, wash_strength) - remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - - var/datum/component/radioactive/healthy_green_glow = GetComponent(/datum/component/radioactive) - if(!QDELETED(healthy_green_glow)) - healthy_green_glow.strength -= max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * 2))) - if(healthy_green_glow.strength <= RAD_BACKGROUND_RADIATION) - qdel(healthy_green_glow) - - /// we got to return true because of mob code - /// and not all code that uses COMSIG_COMPONENT_CLEAN_ACT returns true half the time - return TRUE - -/** - * call back when a var is edited on this atom - * - * Can be used to implement special handling of vars - * - * At the atom level, if you edit a var named "color" it will add the atom colour with - * admin level priority to the atom colours list - * - * Also, if GLOB.Debug2 is FALSE, it sets the [ADMIN_SPAWNED_1] flag on [flags_1][/atom/var/flags_1], which signifies - * the object has been admin edited - */ -/atom/vv_edit_var(var_name, var_value) - if(!GLOB.Debug2) - flags_1 |= ADMIN_SPAWNED_1 - . = ..() - switch(var_name) - if(NAMEOF(src, color)) - add_atom_colour(color, ADMIN_COLOUR_PRIORITY) - -/** - * Return the markup to for the dropdown list for the VV panel for this atom - * - * Override in subtypes to add custom VV handling in the VV panel - */ -/atom/vv_get_dropdown() - . = ..() - VV_DROPDOWN_OPTION("", "---------") - if(!ismovable(src)) - var/turf/curturf = get_turf(src) - if(curturf) - . += "" - VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform") - VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent") - VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse") - VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion") - -/atom/vv_do_topic(list/href_list) - . = ..() - if(href_list[VV_HK_ADD_REAGENT] && check_rights(R_VAREDIT)) - if(!reagents) - var/amount = input(usr, "Specify the reagent size of [src]", "Set Reagent Size", 50) as num|null - if(amount) - create_reagents(amount) - - if(reagents) - var/chosen_id - switch(alert(usr, "Choose a method.", "Add Reagents", "Search", "Choose from a list", "I'm feeling lucky")) - if("Search") - var/valid_id - while(!valid_id) - chosen_id = input(usr, "Enter the ID of the reagent you want to add.", "Search reagents") as null|text - if(isnull(chosen_id)) //Get me out of here! - break - if (!ispath(text2path(chosen_id))) - chosen_id = pick_closest_path(chosen_id, make_types_fancy(subtypesof(/datum/reagent))) - if (ispath(chosen_id)) - valid_id = TRUE - else - valid_id = TRUE - if(!valid_id) - to_chat(usr, "A reagent with that ID doesn't exist!") - if("Choose from a list") - chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in sortList(subtypesof(/datum/reagent), /proc/cmp_typepaths_asc) - if("I'm feeling lucky") - chosen_id = pick(subtypesof(/datum/reagent)) - if(chosen_id) - var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", reagents.maximum_volume) as num|null - if(amount) - reagents.add_reagent(chosen_id, amount) - log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") - message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") - if(href_list[VV_HK_TRIGGER_EXPLOSION] && check_rights(R_FUN)) - usr.client.cmd_admin_explosion(src) - if(href_list[VV_HK_TRIGGER_EMP] && check_rights(R_FUN)) - usr.client.cmd_admin_emp(src) - if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT)) - var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") - var/matrix/M = transform - switch(result) - if("Scale") - var/x = input(usr, "Choose x mod","Transform Mod") as null|num - var/y = input(usr, "Choose y mod","Transform Mod") as null|num - if(!isnull(x) && !isnull(y)) - transform = M.Scale(x,y) - if("Translate") - var/x = input(usr, "Choose x mod","Transform Mod") as null|num - var/y = input(usr, "Choose y mod","Transform Mod") as null|num - if(!isnull(x) && !isnull(y)) - transform = M.Translate(x,y) - if("Rotate") - var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num - if(!isnull(angle)) - transform = M.Turn(angle) - if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT)) - var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text - if(newname) - vv_auto_rename(newname) - -/atom/vv_get_header() - . = ..() - var/refid = REF(src) - . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]" - . += "
    << [dir2text(dir) || dir] >>" - -///Where atoms should drop if taken from this atom -/atom/proc/drop_location() - var/atom/L = loc - if(!L) - return null - return L.AllowDrop() ? L : L.drop_location() - -/atom/proc/vv_auto_rename(newname) - name = newname - -/** - * An atom has entered this atom's contents - * - * Default behaviour is to send the [COMSIG_ATOM_ENTERED] - */ -/atom/Entered(atom/movable/AM, atom/oldLoc) - SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) - -/** - * An atom is attempting to exit this atom's contents - * - * Default behaviour is to send the [COMSIG_ATOM_EXIT] - * - * Return value should be set to FALSE if the moving atom is unable to leave, - * otherwise leave value the result of the parent call - */ -/atom/Exit(atom/movable/AM, atom/newLoc) - . = ..() - if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) - return FALSE - -/** - * An atom has exited this atom's contents - * - * Default behaviour is to send the [COMSIG_ATOM_EXITED] - */ -/atom/Exited(atom/movable/AM, atom/newLoc) - SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) - -///Return atom temperature -/atom/proc/return_temperature() - return - -/** - *Tool behavior procedure. Redirects to tool-specific procs by default. - * - * You can override it to catch all tool interactions, for use in complex deconstruction procs. - * - * Must return parent proc ..() in the end if overridden - */ -/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) - switch(tool_type) - if(TOOL_CROWBAR) - . |= crowbar_act(user, I) - if(TOOL_MULTITOOL) - . |= multitool_act(user, I) - if(TOOL_SCREWDRIVER) - . |= screwdriver_act(user, I) - if(TOOL_WRENCH) - . |= wrench_act(user, I) - if(TOOL_WIRECUTTER) - . |= wirecutter_act(user, I) - if(TOOL_WELDER) - . |= welder_act(user, I) - if(TOOL_ANALYZER) - . |= analyzer_act(user, I) - if(. & COMPONENT_BLOCK_TOOL_ATTACK) - return TRUE - -//! Tool-specific behavior procs. They send signals, so try to call ..() -/// - -///Crowbar act -/atom/proc/crowbar_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_CROWBAR_ACT, user, I) - -///Multitool act -/atom/proc/multitool_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_MULTITOOL_ACT, user, I) - -///Check if the multitool has an item in it's data buffer -/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE) - if(!istype(I, /obj/item/multitool)) - if(user && !silent) - to_chat(user, "[I] has no data buffer!") - return FALSE - return TRUE - -///Screwdriver act -/atom/proc/screwdriver_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) - -///Wrench act -/atom/proc/wrench_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_WRENCH_ACT, user, I) - -///Wirecutter act -/atom/proc/wirecutter_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_WIRECUTTER_ACT, user, I) - -///Welder act -/atom/proc/welder_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_WELDER_ACT, user, I) - -///Analyzer act -/atom/proc/analyzer_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_ANALYSER_ACT, user, I) - -///Generate a tag for this atom -/atom/proc/GenerateTag() - return - -///Connect this atom to a shuttle -/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - return - -/// Generic logging helper -/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) - if(!log_globally) - return - - var/log_text = "[key_name(src)] [message] [loc_name(src)]" - switch(message_type) - if(LOG_ATTACK) - log_attack(log_text) - if(LOG_SAY) - log_say(log_text) - if(LOG_WHISPER) - log_whisper(log_text) - if(LOG_EMOTE) - log_emote(log_text) - if(LOG_DSAY) - log_dsay(log_text) - if(LOG_PDA) - log_pda(log_text) - if(LOG_CHAT) - log_chat(log_text) - if(LOG_COMMENT) - log_comment(log_text) - if(LOG_TELECOMMS) - log_telecomms(log_text) - if(LOG_ECON) - log_econ(log_text) - if(LOG_OOC) - log_ooc(log_text) - if(LOG_ADMIN) - log_admin(log_text) - if(LOG_ADMIN_PRIVATE) - log_admin_private(log_text) - if(LOG_ASAY) - log_adminsay(log_text) - if(LOG_OWNERSHIP) - log_game(log_text) - if(LOG_GAME) - log_game(log_text) - if(LOG_MECHA) - log_mecha(log_text) - if(LOG_SHUTTLE) - log_shuttle(log_text) - else - stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).") - log_game(log_text) - -/// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements) -/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null) - var/prefix = tag ? "([tag]) " : "" - var/suffix = forced_by ? " FORCED by [forced_by]" : "" - log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally) - -/// Helper for logging of messages with only one sender and receiver -/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag) - if(!tag) - stack_trace("Unspecified tag for private message") - tag = "UNKNOWN" - - source.log_talk(message, message_type, tag="[tag] to [key_name(target)]") - if(source != target) - target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE) - -/** - * Log a combat message in the attack log - * - * Arguments: - * * atom/user - argument is the actor performing the action - * * atom/target - argument is the target of the action - * * what_done - is a verb describing the action (e.g. punched, throwed, kicked, etc.) - * * atom/object - is a tool with which the action was made (usually an item) - * * addition - is any additional text, which will be appended to the rest of the log line - */ -/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null) - var/ssource = key_name(user) - var/starget = key_name(target) - - var/mob/living/living_target = target - var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : "" - - var/sobject = "" - if(object) - sobject = " with [object]" - var/saddition = "" - if(addition) - saddition = " [addition]" - - var/postfix = "[sobject][saddition][hp]" - - var/message = "has [what_done] [starget][postfix]" - user.log_message(message, LOG_ATTACK, color="red") - - if(user != target) - var/reverse_message = "has been [what_done] by [ssource][postfix]" - target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE) - -/** - * log_wound() is for when someone is *attacked* and suffers a wound. Note that this only captures wounds from damage, so smites/forced wounds aren't logged, as well as demotions like cuts scabbing over - * - * Note that this has no info on the attack that dealt the wound: information about where damage came from isn't passed to the bodypart's damaged proc. When in doubt, check the attack log for attacks at that same time - * TODO later: Add logging for healed wounds, though that will require some rewriting of healing code to prevent admin heals from spamming the logs. Not high priority - * - * Arguments: - * * victim- The guy who got wounded - * * suffered_wound- The wound, already applied, that we're logging. It has to already be attached so we can get the limb from it - * * dealt_damage- How much damage is associated with the attack that dealt with this wound. - * * dealt_wound_bonus- The wound_bonus, if one was specified, of the wounding attack - * * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present - * * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods - */ -/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll) - if(QDELETED(victim) || !suffered_wound) - return - var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote? - - if(dealt_damage) - message += " | Damage: [dealt_damage]" - // The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut - if(base_roll) - message += " (rolled [base_roll]/[dealt_damage ** WOUND_DAMAGE_EXPONENT])" - - if(dealt_wound_bonus) - message += " | WB: [dealt_wound_bonus]" - - if(dealt_bare_wound_bonus) - message += " | BWB: [dealt_bare_wound_bonus]" - - victim.log_message(message, LOG_ATTACK, color="blue") - -/atom/movable/proc/add_filter(name,priority,list/params) - LAZYINITLIST(filter_data) - var/list/p = params.Copy() - p["priority"] = priority - filter_data[name] = p - update_filters() - -/atom/movable/proc/update_filters() - filters = null - filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE) - for(var/f in filter_data) - var/list/data = filter_data[f] - var/list/arguments = data.Copy() - arguments -= "priority" - filters += filter(arglist(arguments)) - -/obj/item/update_filters() - . = ..() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/atom/movable/proc/get_filter(name) - if(filter_data && filter_data[name]) - return filters[filter_data.Find(name)] - -/atom/movable/proc/remove_filter(name) - if(filter_data && filter_data[name]) - filter_data -= name - update_filters() - -/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) - . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels) - -///Sets the custom materials for an item. -/atom/proc/set_custom_materials(list/materials, multiplier = 1) - if(custom_materials) //Only runs if custom materials existed at first. Should usually be the case but check anyways - for(var/i in custom_materials) - var/datum/material/custom_material = SSmaterials.GetMaterialRef(i) - custom_material.on_removed(src, material_flags) //Remove the current materials - - if(!length(materials)) - custom_materials = null - return - - if(!(material_flags & MATERIAL_NO_EFFECTS)) - for(var/x in materials) - var/datum/material/custom_material = SSmaterials.GetMaterialRef(x) - custom_material.on_applied(src, materials[x] * multiplier * material_modifier, material_flags) - - custom_materials = SSmaterials.FindOrCreateMaterialCombo(materials, multiplier) - -/** - * Returns true if this atom has gravity for the passed in turf - * - * Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with - * the forced gravity var - * - * Gravity situations: - * * No gravity if you're not in a turf - * * No gravity if this atom is in is a space turf - * * Gravity if the area it's in always has gravity - * * Gravity if there's a gravity generator on the z level - * * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY - * * otherwise no gravity - */ -/atom/proc/has_gravity(turf/T) - if(!T || !isturf(T)) - T = get_turf(src) - - if(!T) - return 0 - - var/list/forced_gravity = list() - SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, T, forced_gravity) - if(!forced_gravity.len) - SEND_SIGNAL(T, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) - if(forced_gravity.len) - var/max_grav - for(var/i in forced_gravity) - max_grav = max(max_grav, i) - return max_grav - - if(isspaceturf(T)) // Turf never has gravity - return FALSE - if(istype(T, /turf/open/transparent/openspace)) //openspace in a space area doesn't get gravity - if(istype(get_area(T), /area/space)) - return FALSE - - var/area/A = get_area(T) - if(A.has_gravity) // Areas which always has gravity - return A.has_gravity - else - // There's a gravity generator on our z level - if(GLOB.gravity_generators["[T.z]"]) - var/max_grav = 0 - for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[T.z]"]) - max_grav = max(G.setting,max_grav) - return max_grav - return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY) - -/** - * Causes effects when the atom gets hit by a rust effect from heretics - * - * Override this if you want custom behaviour in whatever gets hit by the rust - */ -/atom/proc/rust_heretic_act() - return - -/** - * Used to set something as 'open' if it's being used as a supplypod - * - * Override this if you want an atom to be usable as a supplypod. - */ -/atom/proc/setOpened() - return - -/** - * Used to set something as 'closed' if it's being used as a supplypod - * - * Override this if you want an atom to be usable as a supplypod. - */ -/atom/proc/setClosed() - return +/** + * The base type for nearly all physical objects in SS13 + + * Lots and lots of functionality lives here, although in general we are striving to move + * as much as possible to the components/elements system + */ +/atom + layer = TURF_LAYER + plane = GAME_PLANE + + ///If non-null, overrides a/an/some in all cases + var/article + + ///First atom flags var + var/flags_1 = NONE + ///Intearaction flags + var/interaction_flags_atom = NONE + + var/flags_ricochet = NONE + + ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this + var/ricochet_chance_mod = 1 + ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom + var/ricochet_damage_mod = 0.33 + + ///Reagents holder + var/datum/reagents/reagents = null + + ///This atom's HUD (med/sec, etc) images. Associative list. + var/list/image/hud_list = null + ///HUD images that this atom can provide. + var/list/hud_possible + + ///Value used to increment ex_act() if reactionary_explosions is on + var/explosion_block = 0 + + /** + * used to store the different colors on an atom + * + * its inherent color, the colored paint applied on it, special color effect etc... + */ + var/list/atom_colours + + + /// a very temporary list of overlays to remove + var/list/remove_overlays + /// a very temporary list of overlays to add + var/list/add_overlays + + ///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays + var/list/managed_vis_overlays + ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc + var/list/managed_overlays + + ///Proximity monitor associated with this atom + var/datum/proximity_monitor/proximity_monitor + ///Cooldown tick timer for buckle messages + var/buckle_message_cooldown = 0 + ///Last fingerprints to touch this atom + var/fingerprintslast + + var/list/filter_data //For handling persistent filters + + ///Economy cost of item + var/custom_price + ///Economy cost of item in premium vendor + var/custom_premium_price + ///Whether spessmen with an ID with an age below AGE_MINOR (20 by default) can buy this item + var/age_restricted = FALSE + + //List of datums orbiting this atom + var/datum/component/orbiter/orbiters + + /// Radiation insulation types + var/rad_insulation = RAD_NO_INSULATION + + ///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.) + ///The list referenced by this var can be shared by multiple objects and should not be directly modified. Instead, use [set_custom_materials][/atom/proc/set_custom_materials]. + var/list/custom_materials + ///Bitfield for how the atom handles materials. + var/material_flags = NONE + ///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines. + var/material_modifier = 1 + + var/datum/wires/wires = null + + var/list/alternate_appearances + + ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy() + var/list/targeted_by + + /// Last appearance of the atom for demo saving purposes + var/image/demo_last_appearance + + /// Last name used to calculate a color for the chatmessage overlays + var/chat_color_name + /// Last color calculated for the the chatmessage overlays + var/chat_color + /// A luminescence-shifted value of the last color calculated for chatmessage overlays + var/chat_color_darkened + +/** + * Called when an atom is created in byond (built in engine proc) + * + * Not a lot happens here in SS13 code, as we offload most of the work to the + * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader + * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate + * result is that the Intialize proc is called. + * + * We also generate a tag here if the DF_USE_TAG flag is set on the atom + */ +/atom/New(loc, ...) + //atom creation method that preloads variables at creation + if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() + world.preloader_load(src) + + if(datum_flags & DF_USE_TAG) + GenerateTag() + + var/do_initialize = SSatoms.initialized + if(do_initialize != INITIALIZATION_INSSATOMS) + args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD + if(SSatoms.InitAtom(src, args)) + //we were deleted + return + +/** + * The primary method that objects are setup in SS13 with + * + * we don't use New as we have better control over when this is called and we can choose + * to delay calls or hook other logic in and so forth + * + * During roundstart map parsing, atoms are queued for intialization in the base atom/New(), + * After the map has loaded, then Initalize is called on all atoms one by one. NB: this + * is also true for loading map templates as well, so they don't Initalize until all objects + * in the map file are parsed and present in the world + * + * If you're creating an object at any point after SSInit has run then this proc will be + * immediately be called from New. + * + * mapload: This parameter is true if the atom being loaded is either being intialized during + * the Atom subsystem intialization, or if the atom is being loaded from the map template. + * If the item is being created at runtime any time after the Atom subsystem is intialized then + * it's false. + * + * You must always call the parent of this proc, otherwise failures will occur as the item + * will not be seen as initalized (this can lead to all sorts of strange behaviour, like + * the item being completely unclickable) + * + * You must not sleep in this proc, or any subprocs + * + * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map + * there are no other arguments + * + * Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur. + * + * Note: the following functions don't call the base for optimization and must copypasta handling: + * * [/turf/Initialize] + * * [/turf/open/space/Initialize] + */ +/atom/proc/Initialize(mapload, ...) + //SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + + if(loc) + SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc` + + //atom color stuff + if(color) + add_atom_colour(color, FIXED_COLOUR_PRIORITY) + + if (light_power && light_range) + update_light() + + if (opacity && isturf(loc)) + var/turf/T = loc + T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways. + + if (canSmoothWith) + canSmoothWith = typelist("canSmoothWith", canSmoothWith) + + // apply materials properly from the default custom_materials value + set_custom_materials(custom_materials) + + ComponentInitialize() + + return INITIALIZE_HINT_NORMAL + +/** + * Late Intialization, for code that should run after all atoms have run Intialization + * + * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize] + * proc must return the hint + * [INITIALIZE_HINT_LATELOAD] otherwise you will never be called. + * + * useful for doing things like finding other machines on GLOB.machines because you can guarantee + * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization + * code has been run + */ +/atom/proc/LateInitialize() + set waitfor = FALSE + +/// Put your [AddComponent] calls here +/atom/proc/ComponentInitialize() + return + +/** + * Top level of the destroy chain for most atoms + * + * Cleans up the following: + * * Removes alternate apperances from huds that see them + * * qdels the reagent holder from atoms if it exists + * * clears the orbiters list + * * clears overlays and priority overlays + * * clears the light object + */ +/atom/Destroy() + if(alternate_appearances) + for(var/K in alternate_appearances) + var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K] + AA.remove_from_hud(src) + + if(reagents) + qdel(reagents) + + orbiters = null // The component is attached to us normaly and will be deleted elsewhere + + LAZYCLEARLIST(overlays) + + for(var/i in targeted_by) + var/mob/M = i + LAZYREMOVE(M.do_afters, src) + + targeted_by = null + QDEL_NULL(light) + + return ..() + +/atom/proc/handle_ricochet(obj/projectile/P) + var/turf/p_turf = get_turf(P) + var/face_direction = get_dir(src, p_turf) + var/face_angle = dir2angle(face_direction) + var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) + var/a_incidence_s = abs(incidence_s) + if(a_incidence_s > 90 && a_incidence_s < 270) + return FALSE + if((P.flag in list("bullet", "bomb")) && P.ricochet_incidence_leeway) + if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway)) + return + var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) + P.setAngle(new_angle_s) + return TRUE + +///Can the mover object pass this atom, while heading for the target turf +/atom/proc/CanPass(atom/movable/mover, turf/target) + SHOULD_CALL_PARENT(TRUE) + SHOULD_BE_PURE(TRUE) + if(mover.movement_type & UNSTOPPABLE) + return TRUE + . = CanAllowThrough(mover, target) + // This is cheaper than calling the proc every time since most things dont override CanPassThrough + if(!mover.generic_canpass) + return mover.CanPassThrough(src, target, .) + +/// Returns true or false to allow the mover to move through src +/atom/proc/CanAllowThrough(atom/movable/mover, turf/target) + SHOULD_CALL_PARENT(TRUE) + //SHOULD_BE_PURE(TRUE) + return !density + +/** + * Is this atom currently located on centcom + * + * Specifically, is it on the z level and within the centcom areas + * + * You can also be in a shuttleshuttle during endgame transit + * + * Used in gamemode to identify mobs who have escaped and for some other areas of the code + * who don't want atoms where they shouldn't be + */ +/atom/proc/onCentCom() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(is_reserved_level(T.z)) + for(var/A in SSshuttle.mobile) + var/obj/docking_port/mobile/M = A + if(M.launch_status == ENDGAME_TRANSIT) + for(var/place in M.shuttle_areas) + var/area/shuttle/shuttle_area = place + if(T in shuttle_area) + return TRUE + + if(!is_centcom_level(T.z))//if not, don't bother + return FALSE + + //Check for centcom itself + if(istype(T.loc, /area/centcom)) + return TRUE + + //Check for centcom shuttles + for(var/A in SSshuttle.mobile) + var/obj/docking_port/mobile/M = A + if(M.launch_status == ENDGAME_LAUNCHED) + for(var/place in M.shuttle_areas) + var/area/shuttle/shuttle_area = place + if(T in shuttle_area) + return TRUE + +/** + * Is the atom in any of the centcom syndicate areas + * + * Either in the syndie base on centcom, or any of their shuttles + * + * Also used in gamemode code for win conditions + */ +/atom/proc/onSyndieBase() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(!is_centcom_level(T.z))//if not, don't bother + return FALSE + + if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod)) + return TRUE + + return FALSE + +/** + * Is the atom in an away mission + * + * Must be in the away mission z-level to return TRUE + * + * Also used in gamemode code for win conditions + */ +/atom/proc/onAwayMission() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(is_away_level(T.z)) + return TRUE + + return FALSE + + + +///This atom has been hit by a hulkified mob in hulk mode (user) +/atom/proc/attack_hulk(mob/living/carbon/human/user) + SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) + +/** + * Ensure a list of atoms/reagents exists inside this atom + * + * Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder + * creating the reagent holder if it exists. + * + * If the part is a moveable atom and the previous location of the item was a mob/living, + * it calls the inventory handler transferItemToLoc for that mob/living and transfers the part + * to this atom + * + * Otherwise it simply forceMoves the atom into this atom + */ +/atom/proc/CheckParts(list/parts_list) + if(parts_list) + for(var/A in parts_list) + if(istype(A, /datum/reagent)) + if(!reagents) + reagents = new() + reagents.reagent_list.Add(A) + reagents.conditional_update() + else if(ismovable(A)) + var/atom/movable/M = A + if(isliving(M.loc)) + var/mob/living/L = M.loc + L.transferItemToLoc(M, src) + else + M.forceMove(src) + parts_list.Cut() + +///Hook for multiz??? +/atom/proc/update_multiz(prune_on_fail = FALSE) + return FALSE + +///Take air from the passed in gas mixture datum +/atom/proc/assume_air(datum/gas_mixture/giver) + qdel(giver) + return null + +///Remove air from this atom +/atom/proc/remove_air(amount) + return null + +///Return the current air environment in this atom +/atom/proc/return_air() + if(loc) + return loc.return_air() + else + return null + +///Return the air if we can analyze it +/atom/proc/return_analyzable_air() + return null + +///Check if this atoms eye is still alive (probably) +/atom/proc/check_eye(mob/user) + return + +/atom/proc/Bumped(atom/movable/AM) + set waitfor = FALSE + SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, AM) + +/// Convenience proc to see if a container is open for chemistry handling +/atom/proc/is_open_container() + return is_refillable() && is_drainable() + +/// Is this atom injectable into other atoms +/atom/proc/is_injectable(mob/user, allowmobs = TRUE) + return reagents && (reagents.flags & (INJECTABLE | REFILLABLE)) + +/// Can we draw from this atom with an injectable atom +/atom/proc/is_drawable(mob/user, allowmobs = TRUE) + return reagents && (reagents.flags & (DRAWABLE | DRAINABLE)) + +/// Can this atoms reagents be refilled +/atom/proc/is_refillable() + return reagents && (reagents.flags & REFILLABLE) + +/// Is this atom drainable of reagents +/atom/proc/is_drainable() + return reagents && (reagents.flags & DRAINABLE) + +/** Handles exposing this atom to a list of reagents. + * + * Sends COMSIG_ATOM_EXPOSE_REAGENTS + * Calls expose_atom() for every reagent in the reagent list. + * + * Arguments: + * - [reagents][/list]: The list of reagents the atom is being exposed to. + * - [source][/datum/reagents]: The reagent holder the reagents are being sourced from. + * - method: How the atom is being exposed to the reagents. + * - volume_modifier: Volume multiplier. + * - show_message: Whether to display anything to mobs when they are exposed. + */ +/atom/proc/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) + if((. = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, method, volume_modifier, show_message)) & COMPONENT_NO_EXPOSE_REAGENTS) + return + + for(var/reagent in reagents) + var/datum/reagent/R = reagent + . |= R.expose_atom(src, reagents[R]) + +/// Are you allowed to drop this atom +/atom/proc/AllowDrop() + return FALSE + +/atom/proc/CheckExit() + return 1 + +///Is this atom within 1 tile of another atom +/atom/proc/HasProximity(atom/movable/AM as mob|obj) + return + +/** + * React to an EMP of the given severity + * + * Default behaviour is to send the [COMSIG_ATOM_EMP_ACT] signal + * + * If the signal does not return protection, and there are attached wires then we call + * [emp_pulse][/datum/wires/proc/emp_pulse] on the wires + * + * We then return the protection value + */ +/atom/proc/emp_act(severity) + var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) + if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) + wires.emp_pulse() + return protection // Pass the protection value collected here upwards + +/** + * React to a hit by a projectile object + * + * Default behaviour is to send the [COMSIG_ATOM_BULLET_ACT] and then call [on_hit][/obj/projectile/proc/on_hit] on the projectile + */ +/atom/proc/bullet_act(obj/projectile/P, def_zone) + SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) + . = P.on_hit(src, 0, def_zone) + +///Return true if we're inside the passed in atom +/atom/proc/in_contents_of(container)//can take class or object instance as argument + if(ispath(container)) + if(istype(src.loc, container)) + return TRUE + else if(src in container) + return TRUE + return FALSE + +/** + * Get the name of this object for examine + * + * You can override what is returned from this proc by registering to listen for the + * [COMSIG_ATOM_GET_EXAMINE_NAME] signal + */ +/atom/proc/get_examine_name(mob/user) + . = "\a [src]" + var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]") + if(article) + . = "[article] [src]" + override[EXAMINE_POSITION_ARTICLE] = article + if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) + . = override.Join("") + +///Generate the full examine string of this atom (including icon for goonchat) +/atom/proc/get_examine_string(mob/user, thats = FALSE) + return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]" + +/** + * Called when a mob examines (shift click or verb) this atom + * + * Default behaviour is to get the name and icon of the object and it's reagents where + * the [TRANSPARENT] flag is set on the reagents holder + * + * Produces a signal [COMSIG_PARENT_EXAMINE] + */ +/atom/proc/examine(mob/user) + . = list("[get_examine_string(user, TRUE)].") + + if(desc) + . += desc + + if(custom_materials) + var/list/materials_list = list() + for(var/i in custom_materials) + var/datum/material/M = i + materials_list += "[M.name]" + . += "It is made out of [english_list(materials_list)]." + if(reagents) + if(reagents.flags & TRANSPARENT) + . += "It contains:" + if(length(reagents.reagent_list)) + if(user.can_see_reagents()) //Show each individual reagent + for(var/datum/reagent/R in reagents.reagent_list) + . += "[R.volume] units of [R.name]" + else //Otherwise, just show the total volume + var/total_volume = 0 + for(var/datum/reagent/R in reagents.reagent_list) + total_volume += R.volume + . += "[total_volume] units of various reagents" + else + . += "Nothing." + else if(reagents.flags & AMOUNT_VISIBLE) + if(reagents.total_volume) + . += "It has [reagents.total_volume] unit\s left." + else + . += "It's empty." + + SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) +/** + * Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_TIME (default 1.5 seconds) + * + * This is where you can put extra information on something that may be superfluous or not important in critical gameplay + * moments, while allowing people to manually double-examine to take a closer look + * + * Produces a signal [COMSIG_PARENT_EXAMINE_MORE] + */ +/atom/proc/examine_more(mob/user) + . = list() + SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .) + if(!LAZYLEN(.)) // lol ..length + return list("You examine [src] closer, but find nothing of interest...") + +/// Updates the icon of the atom +/atom/proc/update_icon() + var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) + . = FALSE + + if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) + update_icon_state() + . = TRUE + + if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS)) + var/list/new_overlays = update_overlays() + if(managed_overlays) + cut_overlay(managed_overlays) + managed_overlays = null + if(length(new_overlays)) + managed_overlays = new_overlays + add_overlay(new_overlays) + . = TRUE + + SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, signalOut, .) + +/// Updates the icon state of the atom +/atom/proc/update_icon_state() + +/// Updates the overlays of the atom +/atom/proc/update_overlays() + SHOULD_CALL_PARENT(1) + . = list() + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .) + +/** + * An atom we are buckled or is contained within us has tried to move + * + * Default behaviour is to send a warning that the user can't move while buckled as long + * as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks) + */ +/atom/proc/relaymove(mob/user) + if(buckle_message_cooldown <= world.time) + buckle_message_cooldown = world.time + 50 + to_chat(user, "You can't move while buckled to [src]!") + return + +/// Handle what happens when your contents are exploded by a bomb +/atom/proc/contents_explosion(severity, target) + return //For handling the effects of explosions on contents that would not normally be effected + +/** + * React to being hit by an explosion + * + * Default behaviour is to call [contents_explosion][/atom/proc/contents_explosion] and send the [COMSIG_ATOM_EX_ACT] signal + */ +/atom/proc/ex_act(severity, target) + set waitfor = FALSE + contents_explosion(severity, target) + SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) + +/** + * React to a hit by a blob objecd + * + * default behaviour is to send the [COMSIG_ATOM_BLOB_ACT] signal + */ +/atom/proc/blob_act(obj/structure/blob/B) + SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) + return + +/atom/proc/fire_act(exposed_temperature, exposed_volume) + SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) + return + +/** + * React to being hit by a thrown object + * + * Default behaviour is to call [hitby_react][/atom/proc/hitby_react] on ourselves after 2 seconds if we are dense + * and under normal gravity. + * + * Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is + * deleted shortly after hitting something (during explosions or other massive events that + * throw lots of items around - singularity being a notable example) + */ +/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). + addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) + +/** + * We have have actually hit the passed in atom + * + * Default behaviour is to move back from the item that hit us + */ +/atom/proc/hitby_react(atom/movable/AM) + if(AM && isturf(AM.loc)) + step(AM, turn(AM.dir, 180)) + +///Handle the atom being slipped over +/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube, paralyze, force_drop) + return + +///returns the mob's dna info as a list, to be inserted in an object's blood_DNA list +/mob/living/proc/get_blood_dna_list() + if(get_blood_id() != /datum/reagent/blood) + return + return list("ANIMAL DNA" = "Y-") + +///Get the mobs dna list +/mob/living/carbon/get_blood_dna_list() + if(get_blood_id() != /datum/reagent/blood) + return + var/list/blood_dna = list() + if(dna) + blood_dna[dna.unique_enzymes] = dna.blood_type + else + blood_dna["UNKNOWN DNA"] = "X*" + return blood_dna + +/mob/living/carbon/alien/get_blood_dna_list() + return list("UNKNOWN DNA" = "X*") + +/mob/living/silicon/get_blood_dna_list() + return list("MOTOR OIL" = "SAE 5W-30") //just a little flavor text. + +///to add a mob's dna info into an object's blood_dna list. +/atom/proc/transfer_mob_blood_dna(mob/living/L) + // Returns 0 if we have that blood already + var/new_blood_dna = L.get_blood_dna_list() + if(!new_blood_dna) + return FALSE + var/old_length = blood_DNA_length() + add_blood_DNA(new_blood_dna) + if(blood_DNA_length() == old_length) + return FALSE + return TRUE + +///to add blood from a mob onto something, and transfer their dna info +/atom/proc/add_mob_blood(mob/living/M) + var/list/blood_dna = M.get_blood_dna_list() + if(!blood_dna) + return FALSE + return add_blood_DNA(blood_dna) + +///Is this atom in space +/atom/proc/isinspace() + if(isspaceturf(get_turf(src))) + return TRUE + else + return FALSE + +///Used for making a sound when a mob involuntarily falls into the ground. +/atom/proc/handle_fall(mob/faller) + return + +///Respond to the singularity eating this atom +/atom/proc/singularity_act() + return + +/** + * Respond to the singularity pulling on us + * + * Default behaviour is to send [COMSIG_ATOM_SING_PULL] and return + */ +/atom/proc/singularity_pull(obj/singularity/S, current_size) + SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) + + +/** + * Respond to acid being used on our atom + * + * Default behaviour is to send [COMSIG_ATOM_ACID_ACT] and return + */ +/atom/proc/acid_act(acidpwr, acid_volume) + SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) + +/** + * Respond to an emag being used on our atom + * + * Default behaviour is to send [COMSIG_ATOM_EMAG_ACT] and return + */ +/atom/proc/emag_act(mob/user, obj/item/card/emag/E) + SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user, E) + +/** + * Respond to a radioactive wave hitting this atom + * + * Default behaviour is to send [COMSIG_ATOM_RAD_ACT] and return + */ +/atom/proc/rad_act(strength) + SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) + +/** + * Respond to narsie eating our atom + * + * Default behaviour is to send [COMSIG_ATOM_NARSIE_ACT] and return + */ +/atom/proc/narsie_act() + SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) + + +///Return the values you get when an RCD eats you? +/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + return FALSE + + +/** + * Respond to an RCD acting on our item + * + * Default behaviour is to send [COMSIG_ATOM_RCD_ACT] and return FALSE + */ +/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode) + return FALSE + +/** + * Respond to an electric bolt action on our item + * + * Default behaviour is to return, we define here to allow for cleaner code later on + */ +/atom/proc/zap_act(power, zap_flags, shocked_targets) + return + +/** + * Implement the behaviour for when a user click drags a storage object to your atom + * + * This behaviour is usually to mass transfer, but this is no longer a used proc as it just + * calls the underyling /datum/component/storage dump act if a component exists + * + * TODO these should be purely component items that intercept the atom clicks higher in the + * call chain + */ +/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user) + if(GetComponent(/datum/component/storage)) + return component_storage_contents_dump_act(src_object, user) + return FALSE + +/** + * Implement the behaviour for when a user click drags another storage item to you + * + * In this case we get as many of the tiems from the target items compoent storage and then + * put everything into ourselves (or our storage component) + * + * TODO these should be purely component items that intercept the atom clicks higher in the + * call chain + */ +/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user) + var/list/things = src_object.contents() + var/datum/progressbar/progress = new(user, things.len, src) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress))) + stoplag(1) + progress.end_progress() + to_chat(user, "You dump as much of [src_object.parent]'s contents [STR.insert_preposition]to [src] as you can.") + STR.orient2hud(user) + src_object.orient2hud(user) + if(user.active_storage) //refresh the HUD to show the transfered contents + user.active_storage.close(user) + user.active_storage.show_to(user) + return TRUE + +///Get the best place to dump the items contained in the source storage item? +/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) + return null + +/** + * This proc is called when an atom in our contents has it's [Destroy][/atom/Destroy] called + * + * Default behaviour is to simply send [COMSIG_ATOM_CONTENTS_DEL] + */ +/atom/proc/handle_atom_del(atom/A) + SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A) + +/** + * called when the turf the atom resides on is ChangeTurfed + * + * Default behaviour is to loop through atom contents and call their HandleTurfChange() proc + */ +/atom/proc/HandleTurfChange(turf/T) + for(var/a in src) + var/atom/A = a + A.HandleTurfChange(T) + +/** + * the vision impairment to give to the mob whose perspective is set to that atom + * + * (e.g. an unfocused camera giving you an impaired vision when looking through it) + */ +/atom/proc/get_remote_view_fullscreens(mob/user) + return + +/** + * the sight changes to give to the mob whose perspective is set to that atom + * + * (e.g. A mob with nightvision loses its nightvision while looking through a normal camera) + */ +/atom/proc/update_remote_sight(mob/living/user) + return + + +/** + * Hook for running code when a dir change occurs + * + * Not recommended to use, listen for the [COMSIG_ATOM_DIR_CHANGE] signal instead (sent by this proc) + */ +/atom/proc/setDir(newdir) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) + dir = newdir + +///Handle melee attack by a mech +/atom/proc/mech_melee_attack(obj/mecha/M) + return + +/** + * Called when the atom log's in or out + * + * Default behaviour is to call on_log on the location this atom is in + */ +/atom/proc/on_log(login) + if(loc) + loc.on_log(login) + + +/* + Atom Colour Priority System + A System that gives finer control over which atom colour to colour the atom with. + The "highest priority" one is always displayed as opposed to the default of + "whichever was set last is displayed" +*/ + + +///Adds an instance of colour_type to the atom's atom_colours list +/atom/proc/add_atom_colour(coloration, colour_priority) + if(!atom_colours || !atom_colours.len) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(!coloration) + return + if(colour_priority > atom_colours.len) + return + atom_colours[colour_priority] = coloration + update_atom_colour() + + +///Removes an instance of colour_type from the atom's atom_colours list +/atom/proc/remove_atom_colour(colour_priority, coloration) + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(colour_priority > atom_colours.len) + return + if(coloration && atom_colours[colour_priority] != coloration) + return //if we don't have the expected color (for a specific priority) to remove, do nothing + atom_colours[colour_priority] = null + update_atom_colour() + + +///Resets the atom's color to null, and then sets it to the highest priority colour available +/atom/proc/update_atom_colour() + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + color = null + for(var/C in atom_colours) + if(islist(C)) + var/list/L = C + if(L.len) + color = L + return + else if(C) + color = C + return + + +///Proc for being washed by a shower +/atom/proc/washed(var/atom/washer, wash_strength = CLEAN_WEAK) + SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, wash_strength) + remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + + var/datum/component/radioactive/healthy_green_glow = GetComponent(/datum/component/radioactive) + if(!QDELETED(healthy_green_glow)) + healthy_green_glow.strength -= max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * 2))) + if(healthy_green_glow.strength <= RAD_BACKGROUND_RADIATION) + qdel(healthy_green_glow) + + /// we got to return true because of mob code + /// and not all code that uses COMSIG_COMPONENT_CLEAN_ACT returns true half the time + return TRUE + +/** + * call back when a var is edited on this atom + * + * Can be used to implement special handling of vars + * + * At the atom level, if you edit a var named "color" it will add the atom colour with + * admin level priority to the atom colours list + * + * Also, if GLOB.Debug2 is FALSE, it sets the [ADMIN_SPAWNED_1] flag on [flags_1][/atom/var/flags_1], which signifies + * the object has been admin edited + */ +/atom/vv_edit_var(var_name, var_value) + if(!GLOB.Debug2) + flags_1 |= ADMIN_SPAWNED_1 + . = ..() + switch(var_name) + if(NAMEOF(src, color)) + add_atom_colour(color, ADMIN_COLOUR_PRIORITY) + +/** + * Return the markup to for the dropdown list for the VV panel for this atom + * + * Override in subtypes to add custom VV handling in the VV panel + */ +/atom/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION("", "---------") + if(!ismovable(src)) + var/turf/curturf = get_turf(src) + if(curturf) + . += "" + VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform") + VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent") + VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse") + VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion") + +/atom/vv_do_topic(list/href_list) + . = ..() + if(href_list[VV_HK_ADD_REAGENT] && check_rights(R_VAREDIT)) + if(!reagents) + var/amount = input(usr, "Specify the reagent size of [src]", "Set Reagent Size", 50) as num|null + if(amount) + create_reagents(amount) + + if(reagents) + var/chosen_id + switch(alert(usr, "Choose a method.", "Add Reagents", "Search", "Choose from a list", "I'm feeling lucky")) + if("Search") + var/valid_id + while(!valid_id) + chosen_id = input(usr, "Enter the ID of the reagent you want to add.", "Search reagents") as null|text + if(isnull(chosen_id)) //Get me out of here! + break + if (!ispath(text2path(chosen_id))) + chosen_id = pick_closest_path(chosen_id, make_types_fancy(subtypesof(/datum/reagent))) + if (ispath(chosen_id)) + valid_id = TRUE + else + valid_id = TRUE + if(!valid_id) + to_chat(usr, "A reagent with that ID doesn't exist!") + if("Choose from a list") + chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in sortList(subtypesof(/datum/reagent), /proc/cmp_typepaths_asc) + if("I'm feeling lucky") + chosen_id = pick(subtypesof(/datum/reagent)) + if(chosen_id) + var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", reagents.maximum_volume) as num|null + if(amount) + reagents.add_reagent(chosen_id, amount) + log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") + message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") + if(href_list[VV_HK_TRIGGER_EXPLOSION] && check_rights(R_FUN)) + usr.client.cmd_admin_explosion(src) + if(href_list[VV_HK_TRIGGER_EMP] && check_rights(R_FUN)) + usr.client.cmd_admin_emp(src) + if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT)) + var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") + var/matrix/M = transform + switch(result) + if("Scale") + var/x = input(usr, "Choose x mod","Transform Mod") as null|num + var/y = input(usr, "Choose y mod","Transform Mod") as null|num + if(!isnull(x) && !isnull(y)) + transform = M.Scale(x,y) + if("Translate") + var/x = input(usr, "Choose x mod","Transform Mod") as null|num + var/y = input(usr, "Choose y mod","Transform Mod") as null|num + if(!isnull(x) && !isnull(y)) + transform = M.Translate(x,y) + if("Rotate") + var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num + if(!isnull(angle)) + transform = M.Turn(angle) + if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT)) + var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text + if(newname) + vv_auto_rename(newname) + +/atom/vv_get_header() + . = ..() + var/refid = REF(src) + . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]" + . += "
    << [dir2text(dir) || dir] >>" + +///Where atoms should drop if taken from this atom +/atom/proc/drop_location() + var/atom/L = loc + if(!L) + return null + return L.AllowDrop() ? L : L.drop_location() + +/atom/proc/vv_auto_rename(newname) + name = newname + +/** + * An atom has entered this atom's contents + * + * Default behaviour is to send the [COMSIG_ATOM_ENTERED] + */ +/atom/Entered(atom/movable/AM, atom/oldLoc) + SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) + +/** + * An atom is attempting to exit this atom's contents + * + * Default behaviour is to send the [COMSIG_ATOM_EXIT] + * + * Return value should be set to FALSE if the moving atom is unable to leave, + * otherwise leave value the result of the parent call + */ +/atom/Exit(atom/movable/AM, atom/newLoc) + . = ..() + if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) + return FALSE + +/** + * An atom has exited this atom's contents + * + * Default behaviour is to send the [COMSIG_ATOM_EXITED] + */ +/atom/Exited(atom/movable/AM, atom/newLoc) + SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) + +///Return atom temperature +/atom/proc/return_temperature() + return + +/** + *Tool behavior procedure. Redirects to tool-specific procs by default. + * + * You can override it to catch all tool interactions, for use in complex deconstruction procs. + * + * Must return parent proc ..() in the end if overridden + */ +/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) + switch(tool_type) + if(TOOL_CROWBAR) + . |= crowbar_act(user, I) + if(TOOL_MULTITOOL) + . |= multitool_act(user, I) + if(TOOL_SCREWDRIVER) + . |= screwdriver_act(user, I) + if(TOOL_WRENCH) + . |= wrench_act(user, I) + if(TOOL_WIRECUTTER) + . |= wirecutter_act(user, I) + if(TOOL_WELDER) + . |= welder_act(user, I) + if(TOOL_ANALYZER) + . |= analyzer_act(user, I) + if(. & COMPONENT_BLOCK_TOOL_ATTACK) + return TRUE + +//! Tool-specific behavior procs. They send signals, so try to call ..() +/// + +///Crowbar act +/atom/proc/crowbar_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_CROWBAR_ACT, user, I) + +///Multitool act +/atom/proc/multitool_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_MULTITOOL_ACT, user, I) + +///Check if the multitool has an item in it's data buffer +/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE) + if(!istype(I, /obj/item/multitool)) + if(user && !silent) + to_chat(user, "[I] has no data buffer!") + return FALSE + return TRUE + +///Screwdriver act +/atom/proc/screwdriver_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) + +///Wrench act +/atom/proc/wrench_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_WRENCH_ACT, user, I) + +///Wirecutter act +/atom/proc/wirecutter_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_WIRECUTTER_ACT, user, I) + +///Welder act +/atom/proc/welder_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_WELDER_ACT, user, I) + +///Analyzer act +/atom/proc/analyzer_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_ANALYSER_ACT, user, I) + +///Generate a tag for this atom +/atom/proc/GenerateTag() + return + +///Connect this atom to a shuttle +/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + return + +/// Generic logging helper +/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) + if(!log_globally) + return + + var/log_text = "[key_name(src)] [message] [loc_name(src)]" + switch(message_type) + if(LOG_ATTACK) + log_attack(log_text) + if(LOG_SAY) + log_say(log_text) + if(LOG_WHISPER) + log_whisper(log_text) + if(LOG_EMOTE) + log_emote(log_text) + if(LOG_DSAY) + log_dsay(log_text) + if(LOG_PDA) + log_pda(log_text) + if(LOG_CHAT) + log_chat(log_text) + if(LOG_COMMENT) + log_comment(log_text) + if(LOG_TELECOMMS) + log_telecomms(log_text) + if(LOG_ECON) + log_econ(log_text) + if(LOG_OOC) + log_ooc(log_text) + if(LOG_ADMIN) + log_admin(log_text) + if(LOG_ADMIN_PRIVATE) + log_admin_private(log_text) + if(LOG_ASAY) + log_adminsay(log_text) + if(LOG_OWNERSHIP) + log_game(log_text) + if(LOG_GAME) + log_game(log_text) + if(LOG_MECHA) + log_mecha(log_text) + if(LOG_SHUTTLE) + log_shuttle(log_text) + else + stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).") + log_game(log_text) + +/// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements) +/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null) + var/prefix = tag ? "([tag]) " : "" + var/suffix = forced_by ? " FORCED by [forced_by]" : "" + log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally) + +/// Helper for logging of messages with only one sender and receiver +/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag) + if(!tag) + stack_trace("Unspecified tag for private message") + tag = "UNKNOWN" + + source.log_talk(message, message_type, tag="[tag] to [key_name(target)]") + if(source != target) + target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE) + +/** + * Log a combat message in the attack log + * + * Arguments: + * * atom/user - argument is the actor performing the action + * * atom/target - argument is the target of the action + * * what_done - is a verb describing the action (e.g. punched, throwed, kicked, etc.) + * * atom/object - is a tool with which the action was made (usually an item) + * * addition - is any additional text, which will be appended to the rest of the log line + */ +/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null) + var/ssource = key_name(user) + var/starget = key_name(target) + + var/mob/living/living_target = target + var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : "" + + var/sobject = "" + if(object) + sobject = " with [object]" + var/saddition = "" + if(addition) + saddition = " [addition]" + + var/postfix = "[sobject][saddition][hp]" + + var/message = "has [what_done] [starget][postfix]" + user.log_message(message, LOG_ATTACK, color="red") + + if(user != target) + var/reverse_message = "has been [what_done] by [ssource][postfix]" + target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE) + +/** + * log_wound() is for when someone is *attacked* and suffers a wound. Note that this only captures wounds from damage, so smites/forced wounds aren't logged, as well as demotions like cuts scabbing over + * + * Note that this has no info on the attack that dealt the wound: information about where damage came from isn't passed to the bodypart's damaged proc. When in doubt, check the attack log for attacks at that same time + * TODO later: Add logging for healed wounds, though that will require some rewriting of healing code to prevent admin heals from spamming the logs. Not high priority + * + * Arguments: + * * victim- The guy who got wounded + * * suffered_wound- The wound, already applied, that we're logging. It has to already be attached so we can get the limb from it + * * dealt_damage- How much damage is associated with the attack that dealt with this wound. + * * dealt_wound_bonus- The wound_bonus, if one was specified, of the wounding attack + * * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present + * * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods + */ +/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll) + if(QDELETED(victim) || !suffered_wound) + return + var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote? + + if(dealt_damage) + message += " | Damage: [dealt_damage]" + // The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut + if(base_roll) + message += " (rolled [base_roll]/[dealt_damage ** WOUND_DAMAGE_EXPONENT])" + + if(dealt_wound_bonus) + message += " | WB: [dealt_wound_bonus]" + + if(dealt_bare_wound_bonus) + message += " | BWB: [dealt_bare_wound_bonus]" + + victim.log_message(message, LOG_ATTACK, color="blue") + +/atom/movable/proc/add_filter(name,priority,list/params) + LAZYINITLIST(filter_data) + var/list/p = params.Copy() + p["priority"] = priority + filter_data[name] = p + update_filters() + +/atom/movable/proc/update_filters() + filters = null + filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE) + for(var/f in filter_data) + var/list/data = filter_data[f] + var/list/arguments = data.Copy() + arguments -= "priority" + filters += filter(arglist(arguments)) + +/obj/item/update_filters() + . = ..() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/atom/movable/proc/get_filter(name) + if(filter_data && filter_data[name]) + return filters[filter_data.Find(name)] + +/atom/movable/proc/remove_filter(name) + if(filter_data && filter_data[name]) + filter_data -= name + update_filters() + +/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) + . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels) + +///Sets the custom materials for an item. +/atom/proc/set_custom_materials(list/materials, multiplier = 1) + if(custom_materials) //Only runs if custom materials existed at first. Should usually be the case but check anyways + for(var/i in custom_materials) + var/datum/material/custom_material = SSmaterials.GetMaterialRef(i) + custom_material.on_removed(src, material_flags) //Remove the current materials + + if(!length(materials)) + custom_materials = null + return + + if(!(material_flags & MATERIAL_NO_EFFECTS)) + for(var/x in materials) + var/datum/material/custom_material = SSmaterials.GetMaterialRef(x) + custom_material.on_applied(src, materials[x] * multiplier * material_modifier, material_flags) + + custom_materials = SSmaterials.FindOrCreateMaterialCombo(materials, multiplier) + +/** + * Returns true if this atom has gravity for the passed in turf + * + * Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with + * the forced gravity var + * + * Gravity situations: + * * No gravity if you're not in a turf + * * No gravity if this atom is in is a space turf + * * Gravity if the area it's in always has gravity + * * Gravity if there's a gravity generator on the z level + * * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY + * * otherwise no gravity + */ +/atom/proc/has_gravity(turf/T) + if(!T || !isturf(T)) + T = get_turf(src) + + if(!T) + return 0 + + var/list/forced_gravity = list() + SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, T, forced_gravity) + if(!forced_gravity.len) + SEND_SIGNAL(T, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) + if(forced_gravity.len) + var/max_grav + for(var/i in forced_gravity) + max_grav = max(max_grav, i) + return max_grav + + if(isspaceturf(T)) // Turf never has gravity + return FALSE + if(istype(T, /turf/open/transparent/openspace)) //openspace in a space area doesn't get gravity + if(istype(get_area(T), /area/space)) + return FALSE + + var/area/A = get_area(T) + if(A.has_gravity) // Areas which always has gravity + return A.has_gravity + else + // There's a gravity generator on our z level + if(GLOB.gravity_generators["[T.z]"]) + var/max_grav = 0 + for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[T.z]"]) + max_grav = max(G.setting,max_grav) + return max_grav + return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY) + +/** + * Causes effects when the atom gets hit by a rust effect from heretics + * + * Override this if you want custom behaviour in whatever gets hit by the rust + */ +/atom/proc/rust_heretic_act() + return + +/** + * Used to set something as 'open' if it's being used as a supplypod + * + * Override this if you want an atom to be usable as a supplypod. + */ +/atom/proc/setOpened() + return + +/** + * Used to set something as 'closed' if it's being used as a supplypod + * + * Override this if you want an atom to be usable as a supplypod. + */ +/atom/proc/setClosed() + return diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index ea40d92df91..c3df27252ea 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1,979 +1,979 @@ -/atom/movable - layer = OBJ_LAYER - var/last_move = null - var/last_move_time = 0 - var/anchored = FALSE - var/move_resist = MOVE_RESIST_DEFAULT - var/move_force = MOVE_FORCE_DEFAULT - var/pull_force = PULL_FORCE_DEFAULT - var/datum/thrownthing/throwing = null - var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported - var/throw_range = 7 - var/mob/pulledby = null - var/initial_language_holder = /datum/language_holder - var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. - var/verb_say = "says" - var/verb_ask = "asks" - var/verb_exclaim = "exclaims" - var/verb_whisper = "whispers" - var/verb_sing = "sings" - var/verb_yell = "yells" - var/speech_span - var/inertia_dir = 0 - var/atom/inertia_last_loc - var/inertia_moving = 0 - var/inertia_next_move = 0 - var/inertia_move_delay = 5 - var/pass_flags = NONE - /// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour - var/generic_canpass = TRUE - var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move - var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before. - var/list/client_mobs_in_contents // This contains all the client mobs within this container - var/list/acted_explosions //for explosion dodging - glide_size = 8 - appearance_flags = TILE_BOUND|PIXEL_SCALE - var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm - var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc. - var/atom/movable/pulling - var/grab_state = 0 - var/throwforce = 0 - var/datum/component/orbiter/orbiting - var/can_be_z_moved = TRUE - - var/zfalling = FALSE - - ///Last location of the atom for demo recording purposes - var/atom/demo_last_loc - - /// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE] - var/blocks_emissive = FALSE - ///Internal holder for emissive blocker object, do not use directly use blocks_emissive - var/atom/movable/emissive_blocker/em_block - - -/atom/movable/Initialize(mapload) - . = ..() - switch(blocks_emissive) - if(EMISSIVE_BLOCK_GENERIC) - update_emissive_block() - if(EMISSIVE_BLOCK_UNIQUE) - render_target = ref(src) - em_block = new(src, render_target) - vis_contents += em_block - -/atom/movable/Destroy() - QDEL_NULL(em_block) - return ..() - -/atom/movable/proc/update_emissive_block() - if(blocks_emissive != EMISSIVE_BLOCK_GENERIC) - return - if(length(managed_vis_overlays)) - for(var/a in managed_vis_overlays) - var/obj/effect/overlay/vis/vs - if(vs.plane == EMISSIVE_BLOCKER_PLANE) - SSvis_overlays.remove_vis_overlay(src, list(vs)) - break - SSvis_overlays.add_vis_overlay(src, icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE, dir) - -/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction) - if(!direction) - direction = DOWN - if(!source) - source = get_turf(src) - if(!source) - return FALSE - if(!target) - target = get_step_multiz(source, direction) - if(!target) - return FALSE - return !(movement_type & FLYING) && has_gravity(source) && !throwing - -/atom/movable/proc/onZImpact(turf/T, levels) - var/atom/highest = T - for(var/i in T.contents) - var/atom/A = i - if(!A.density) - continue - if(isobj(A) || ismob(A)) - if(A.layer > highest.layer) - highest = A - INVOKE_ASYNC(src, .proc/SpinAnimation, 5, 2) - return TRUE - -//For physical constraints to travelling up/down. -/atom/movable/proc/can_zTravel(turf/destination, direction) - var/turf/T = get_turf(src) - if(!T) - return FALSE - if(!direction) - if(!destination) - return FALSE - direction = get_dir(T, destination) - if(direction != UP && direction != DOWN) - return FALSE - if(!destination) - destination = get_step_multiz(src, direction) - if(!destination) - return FALSE - return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T) - -/atom/movable/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list("step_x", "step_y", "step_size", "bounds") - var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height") - if(var_name in banned_edits) - return FALSE //PLEASE no. - if((var_name in careful_edits) && (var_value % world.icon_size) != 0) - return FALSE - switch(var_name) - if(NAMEOF(src, x)) - var/turf/T = locate(var_value, y, z) - if(T) - forceMove(T) - return TRUE - return FALSE - if(NAMEOF(src, y)) - var/turf/T = locate(x, var_value, z) - if(T) - forceMove(T) - return TRUE - return FALSE - if(NAMEOF(src, z)) - var/turf/T = locate(x, y, var_value) - if(T) - forceMove(T) - return TRUE - return FALSE - if(NAMEOF(src, loc)) - if(istype(var_value, /atom)) - forceMove(var_value) - return TRUE - else if(isnull(var_value)) - moveToNullspace() - return TRUE - return FALSE - return ..() - -/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - if(QDELETED(AM)) - return FALSE - if(!(AM.can_be_pulled(src, state, force))) - return FALSE - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - if(state == 0) - stop_pulling() - return FALSE - // Are we trying to pull something we are already pulling? Then enter grab cycle and end. - if(AM == pulling) - setGrabState(state) - if(istype(AM,/mob/living)) - var/mob/living/AMob = AM - AMob.grabbedby(src) - return TRUE - stop_pulling() - - SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force) - - if(AM.pulledby) - log_combat(AM, AM.pulledby, "pulled from", src) - AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. - pulling = AM - AM.pulledby = src - setGrabState(state) - if(ismob(AM)) - var/mob/M = AM - log_combat(src, M, "grabbed", addition="passive grab") - if(!supress_message) - M.visible_message("[src] grabs [M] passively.", \ - "[src] grabs you passively.") - return TRUE - -/atom/movable/proc/stop_pulling() - if(pulling) - pulling.pulledby = null - var/mob/living/ex_pulled = pulling - pulling = null - setGrabState(0) - if(isliving(ex_pulled)) - var/mob/living/L = ex_pulled - L.update_mobility()// mob gets up if it was lyng down in a chokehold - -/atom/movable/proc/Move_Pulled(atom/A) - if(!pulling) - return - if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) - stop_pulling() - return - if(isliving(pulling)) - var/mob/living/L = pulling - if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it - stop_pulling() - return - if(A == loc && pulling.density) - return - if(!Process_Spacemove(get_dir(pulling.loc, A))) - return - step(pulling, get_dir(pulling.loc, A)) - return TRUE - -/mob/living/Move_Pulled(atom/A) - . = ..() - if(!. || !isliving(A)) - return - var/mob/living/L = A - set_pull_offsets(L, grab_state) - -/atom/movable/proc/check_pulling() - if(pulling) - var/atom/movable/pullee = pulling - if(pullee && get_dist(src, pullee) > 1) - stop_pulling() - return - if(!isturf(loc)) - stop_pulling() - return - if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") - stop_pulling() - return - if(pulling.anchored || pulling.move_resist > move_force) - stop_pulling() - return - if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. - pulledby.stop_pulling() - -//////////////////////////////////////// -// Here's where we rewrite how byond handles movement except slightly different -// To be removed on step_ conversion -// All this work to prevent a second bump -/atom/movable/Move(atom/newloc, direct=0) - . = FALSE - if(!newloc || newloc == loc) - return - - if(!direct) - direct = get_dir(src, newloc) - setDir(direct) - - if(!loc.Exit(src, newloc)) - return - - if(!newloc.Enter(src, src.loc)) - return - - if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE) - return - - // Past this is the point of no return - var/atom/oldloc = loc - var/area/oldarea = get_area(oldloc) - var/area/newarea = get_area(newloc) - loc = newloc - . = TRUE - oldloc.Exited(src, newloc) - if(oldarea != newarea) - oldarea.Exited(src, newloc) - - for(var/i in oldloc) - if(i == src) // Multi tile objects - continue - var/atom/movable/thing = i - thing.Uncrossed(src) - - newloc.Entered(src, oldloc) - if(oldarea != newarea) - newarea.Entered(src, oldloc) - - for(var/i in loc) - if(i == src) // Multi tile objects - continue - var/atom/movable/thing = i - thing.Crossed(src) - -//////////////////////////////////////// - -/atom/movable/Move(atom/newloc, direct) - var/atom/movable/pullee = pulling - var/turf/T = loc - if(!moving_from_pull) - check_pulling() - if(!loc || !newloc) - return FALSE - var/atom/oldloc = loc - - if(loc != newloc) - if (!(direct & (direct - 1))) //Cardinal move - . = ..() - else //Diagonal move, split it into cardinal moves - moving_diagonally = FIRST_DIAG_STEP - var/first_step_dir - // The `&& moving_diagonally` checks are so that a forceMove taking - // place due to a Crossed, Bumped, etc. call will interrupt - // the second half of the diagonal movement, or the second attempt - // at a first half if step() fails because we hit something. - if (direct & NORTH) - if (direct & EAST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & WEST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & SOUTH) - if (direct & EAST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - else if (direct & WEST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - if(moving_diagonally == SECOND_DIAG_STEP) - if(!.) - setDir(first_step_dir) - else if (!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(direct) - moving_diagonally = 0 - return - - if(!loc || (loc == oldloc && oldloc != newloc)) - last_move = 0 - return - - if(.) - Moved(oldloc, direct) - if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. - if(pulling.anchored) - stop_pulling() - else - var/pull_dir = get_dir(src, pulling) - //puller and pullee more than one tile away or in diagonal position - if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir))) - pulling.moving_from_pull = src - pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position - pulling.moving_from_pull = null - check_pulling() - - last_move = direct - setDir(direct) - if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s) - return FALSE - -//Called after a successful Move(). By this point, we've already moved -/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced) - if (!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(Dir) - if (length(client_mobs_in_contents)) - update_parallax_contents() - - return TRUE - -/atom/movable/Destroy(force) - QDEL_NULL(proximity_monitor) - QDEL_NULL(language_holder) - - unbuckle_all_mobs(force=1) - - . = ..() - if(loc) - //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary) - if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc)) - CanAtmosPass = ATMOS_PASS_YES - air_update_turf(TRUE) - loc.handle_atom_del(src) - for(var/atom/movable/AM in contents) - qdel(AM) - LAZYCLEARLIST(client_mobs_in_contents) - moveToNullspace() - invisibility = INVISIBILITY_ABSTRACT - if(pulledby) - pulledby.stop_pulling() - - if(orbiting) - orbiting.end_orbit(src) - orbiting = null - -// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly. -// You probably want CanPass() -/atom/movable/Cross(atom/movable/AM) - . = TRUE - SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, AM) - return CanPass(AM, AM.loc, TRUE) - -//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called! -/atom/movable/Crossed(atom/movable/AM, oldloc) - SHOULD_CALL_PARENT(TRUE) - . = ..() - SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) - -/atom/movable/Uncross(atom/movable/AM, atom/newloc) - . = ..() - if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS) - return FALSE - if(isturf(newloc) && !CheckExit(AM, newloc)) - return FALSE - -/atom/movable/Uncrossed(atom/movable/AM) - SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM) - -/atom/movable/Bump(atom/A) - if(!A) - CRASH("Bump was called with no argument.") - SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A) - . = ..() - if(!QDELETED(throwing)) - throwing.hit_atom(A) - . = TRUE - if(QDELETED(A)) - return - A.Bumped(src) - -/atom/movable/proc/forceMove(atom/destination) - . = FALSE - if(destination) - . = doMove(destination) - else - CRASH("No valid destination passed into forceMove") - -/atom/movable/proc/moveToNullspace() - return doMove(null) - -/atom/movable/proc/doMove(atom/destination) - . = FALSE - if(destination) - if(pulledby) - pulledby.stop_pulling() - var/atom/oldloc = loc - var/same_loc = oldloc == destination - var/area/old_area = get_area(oldloc) - var/area/destarea = get_area(destination) - - loc = destination - moving_diagonally = 0 - - if(!same_loc) - if(oldloc) - oldloc.Exited(src, destination) - if(old_area && old_area != destarea) - old_area.Exited(src, destination) - for(var/atom/movable/AM in oldloc) - AM.Uncrossed(src) - var/turf/oldturf = get_turf(oldloc) - var/turf/destturf = get_turf(destination) - var/old_z = (oldturf ? oldturf.z : null) - var/dest_z = (destturf ? destturf.z : null) - if (old_z != dest_z) - onTransitZ(old_z, dest_z) - destination.Entered(src, oldloc) - if(destarea && old_area != destarea) - destarea.Entered(src, oldloc) - - for(var/atom/movable/AM in destination) - if(AM == src) - continue - AM.Crossed(src, oldloc) - - Moved(oldloc, NONE, TRUE) - . = TRUE - - //If no destination, move the atom into nullspace (don't do this unless you know what you're doing) - else - . = TRUE - if (loc) - var/atom/oldloc = loc - var/area/old_area = get_area(oldloc) - oldloc.Exited(src, null) - if(old_area) - old_area.Exited(src, null) - loc = null - -/atom/movable/proc/onTransitZ(old_z,new_z) - SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z) - for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care. - var/atom/movable/AM = item - AM.onTransitZ(old_z,new_z) - -/atom/movable/proc/setMovetype(newval) - movement_type = newval - -/** - * Called whenever an object moves and by mobs when they attempt to move themselves through space - * And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move] - * - * Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting - * - * Mobs should return 1 if they should be able to move of their own volition, see [/client/Move] - * - * Arguments: - * * movement_dir - 0 when stopping or any dir when trying to move - */ -/atom/movable/proc/Process_Spacemove(movement_dir = 0) - if(has_gravity(src)) - return 1 - - if(pulledby) - return 1 - - if(throwing) - return 1 - - if(!isturf(loc)) - return 1 - - if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier - return 1 - - return 0 - - -/// Only moves the object if it's under no gravity -/atom/movable/proc/newtonian_move(direction) - if(!loc || Process_Spacemove(0)) - inertia_dir = 0 - return 0 - - inertia_dir = direction - if(!direction) - return 1 - inertia_last_loc = loc - SSspacedrift.processing[src] = src - return 1 - -/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - set waitfor = 0 - var/hitpush = TRUE - var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH) - hitpush = FALSE // hacky, tie this to something else or a proper workaround later - - if(!(impact_signal && (impact_signal & COMPONENT_MOVABLE_IMPACT_NEVERMIND))) // in case a signal interceptor broke or deleted the thing before we could process our hit - return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) - -/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) - if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) - step(src, AM.dir) - ..() - -/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) - if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) - return - return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle) - -///If this returns FALSE then callback will not be called. -/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE) - . = FALSE - if (!target || speed <= 0) - return - - if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW) - return - - if (pulledby) - pulledby.stop_pulling() - - //They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? - if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2) - var/user_momentum = thrower.cached_multiplicative_slowdown - if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead. - user_momentum = world.tick_lag - - user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses. - - if (get_dir(thrower, target) & last_move) - user_momentum = user_momentum //basically a noop, but needed - else if (get_dir(target, thrower) & last_move) - user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly - else - user_momentum = 0 - - - if (user_momentum) - //first lets add that momentum to range. - range *= (user_momentum / speed) + 1 - //then lets add it to speed - speed += user_momentum - if (speed <= 0) - return//no throw speed, the user was moving too fast. - - . = TRUE // No failure conditions past this point. - - var/datum/thrownthing/TT = new() - TT.thrownthing = src - TT.target = target - TT.target_turf = get_turf(target) - TT.init_dir = get_dir(src, target) - TT.maxrange = range - TT.speed = speed - TT.thrower = thrower - TT.diagonals_first = diagonals_first - TT.force = force - TT.gentle = gentle - TT.callback = callback - if(!QDELETED(thrower)) - TT.target_zone = thrower.zone_selected - - var/dist_x = abs(target.x - src.x) - var/dist_y = abs(target.y - src.y) - var/dx = (target.x > src.x) ? EAST : WEST - var/dy = (target.y > src.y) ? NORTH : SOUTH - - if (dist_x == dist_y) - TT.pure_diagonal = 1 - - else if(dist_x <= dist_y) - var/olddist_x = dist_x - var/olddx = dx - dist_x = dist_y - dist_y = olddist_x - dx = dy - dy = olddx - TT.dist_x = dist_x - TT.dist_y = dist_y - TT.dx = dx - TT.dy = dy - TT.diagonal_error = dist_x/2 - dist_y - TT.start_time = world.time - - if(pulledby) - pulledby.stop_pulling() - - throwing = TT - if(spin) - SpinAnimation(5, 1) - - SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin) - SSthrowing.processing[src] = TT - if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun)) - SSthrowing.currentrun[src] = TT - if (quickstart) - TT.tick() - -/atom/movable/proc/handle_buckled_mob_movement(newloc,direct) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - if(!buckled_mob.Move(newloc, direct)) - forceMove(buckled_mob.loc) - last_move = buckled_mob.last_move - inertia_dir = last_move - buckled_mob.inertia_dir = last_move - return 0 - return 1 - -/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.force_pushed(src, force, direction) - if(!silent && .) - visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") - -/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.move_crushed(src, force, direction) - if(!silent && .) - visible_message("[src] crushes past [AM]!", "You crush [AM]!") - -/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(mover in buckled_mobs) - return TRUE - -/// Returns true or false to allow src to move through the blocker, mover has final say -/atom/movable/proc/CanPassThrough(atom/blocker, turf/target, blocker_opinion) - SHOULD_CALL_PARENT(TRUE) - SHOULD_BE_PURE(TRUE) - return blocker_opinion - -/// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called. -/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S) - return - -/// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item. -/atom/movable/proc/on_enter_storage(datum/component/storage/concrete/S) - return - -/atom/movable/proc/get_spacemove_backup() - var/atom/movable/dense_object_backup - for(var/A in orange(1, get_turf(src))) - if(isarea(A)) - continue - else if(isturf(A)) - var/turf/turf = A - if(!turf.density) - continue - return turf - else - var/atom/movable/AM = A - if(!AM.CanPass(src) || AM.density) - if(AM.anchored) - return AM - dense_object_backup = AM - break - . = dense_object_backup - -///called when a mob resists while inside a container that is itself inside something. -/atom/movable/proc/relay_container_resist(mob/living/user, obj/O) - return - - -/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) - if(!no_effect && (visual_effect_icon || used_item)) - do_item_attack_animation(A, visual_effect_icon, used_item) - - if(A == src) - return //don't do an animation if attacking self - var/pixel_x_diff = 0 - var/pixel_y_diff = 0 - - var/direction = get_dir(src, A) - if(direction & NORTH) - pixel_y_diff = 8 - else if(direction & SOUTH) - pixel_y_diff = -8 - - if(direction & EAST) - pixel_x_diff = 8 - else if(direction & WEST) - pixel_x_diff = -8 - - animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) - animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2) - -/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) - var/image/I - if(visual_effect_icon) - I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) - else if(used_item) - I = image(icon = used_item, loc = A, layer = A.layer + 0.1) - I.plane = GAME_PLANE - - // Scale the icon. - I.transform *= 0.75 - // The icon should not rotate. - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - - // Set the direction of the icon animation. - var/direction = get_dir(src, A) - if(direction & NORTH) - I.pixel_y = -16 - else if(direction & SOUTH) - I.pixel_y = 16 - - if(direction & EAST) - I.pixel_x = -16 - else if(direction & WEST) - I.pixel_x = 16 - - if(!direction) // Attacked self?! - I.pixel_z = 16 - - if(!I) - return - - flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second - - // And animate the attack! - animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3) - -/atom/movable/vv_get_dropdown() - . = ..() - . += "" - . += "" - -/atom/movable/proc/ex_check(ex_id) - if(!ex_id) - return TRUE - LAZYINITLIST(acted_explosions) - if(ex_id in acted_explosions) - return FALSE - acted_explosions += ex_id - return TRUE - -//TODO: Better floating -/atom/movable/proc/float(on) - if(throwing) - return - if(on && !(movement_type & FLOATING)) - animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1) - sleep(10) - animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1) - setMovetype(movement_type | FLOATING) - else if (!on && (movement_type & FLOATING)) - animate(src, pixel_y = initial(pixel_y), time = 10) - setMovetype(movement_type & ~FLOATING) - - -/* Language procs -* Unless you are doing something very specific, these are the ones you want to use. -*/ - -/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one. -/atom/movable/proc/get_language_holder(get_minds = TRUE) - if(!language_holder) - language_holder = new initial_language_holder(src) - return language_holder - -/// Grants the supplied language and sets omnitongue true. -/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.grant_language(language, understood, spoken, source) - -/// Grants every language. -/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND) - var/datum/language_holder/LH = get_language_holder() - return LH.grant_all_languages(understood, spoken, grant_omnitongue, source) - -/// Removes a single language. -/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_language(language, understood, spoken, source) - -/// Removes every language and sets omnitongue false. -/atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_all_languages(source, remove_omnitongue) - -/// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later. -/atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.add_blocked_language(language, source) - -/// Removes a language from the blocked language list. -/atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_blocked_language(language, source) - -/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language. -/atom/movable/proc/has_language(language, spoken = FALSE) - var/datum/language_holder/LH = get_language_holder() - return LH.has_language(language, spoken) - -/// Checks if atom can speak the language. -/atom/movable/proc/can_speak_language(language) - var/datum/language_holder/LH = get_language_holder() - return LH.can_speak_language(language) - -/// Returns the result of tongue specific limitations on spoken languages. -/atom/movable/proc/could_speak_language(language) - return TRUE - -/// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible. -/atom/movable/proc/get_selected_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_selected_language() - -/// Gets a random understood language, useful for hallucinations and such. -/atom/movable/proc/get_random_understood_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_random_understood_language() - -/// Gets a random spoken language, useful for forced speech and such. -/atom/movable/proc/get_random_spoken_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_random_spoken_language() - -/// Copies all languages into the supplied atom/language holder. Source should be overridden when you -/// do not want the language overwritten by later atom updates or want to avoid blocked languages. -/atom/movable/proc/copy_languages(from_holder, source_override) - if(isatom(from_holder)) - var/atom/movable/thing = from_holder - from_holder = thing.get_language_holder() - var/datum/language_holder/LH = get_language_holder() - return LH.copy_languages(from_holder, source_override) - -/// Empties out the atom specific languages and updates them according to the current atoms language holder. -/// As a side effect, it also creates missing language holders in the process. -/atom/movable/proc/update_atom_languages() - var/datum/language_holder/LH = get_language_holder() - return LH.update_atom_languages(src) - -/* End language procs */ - - -/atom/movable/proc/ConveyorMove(movedir) - set waitfor = FALSE - if(!anchored && has_gravity()) - step(src, movedir) - -//Returns an atom's power cell, if it has one. Overload for individual items. -/atom/movable/proc/get_cell() - return - -/atom/movable/proc/can_be_pulled(user, grab_state, force) - if(src == user || !isturf(loc)) - return FALSE - if(anchored || throwing) - return FALSE - if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) - return FALSE - return TRUE - -/** - * Updates the grab state of the movable - * - * This exists to act as a hook for behaviour - */ -/atom/movable/proc/setGrabState(newstate) - grab_state = newstate - -/obj/item/proc/do_pickup_animation(atom/target) - set waitfor = FALSE - if(!istype(loc, /turf)) - return - var/image/I = image(icon = src, loc = loc, layer = layer + 0.1) - I.plane = GAME_PLANE - I.transform *= 0.75 - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - var/turf/T = get_turf(src) - var/direction - var/to_x = 0 - var/to_y = 0 - - if(!QDELETED(T) && !QDELETED(target)) - direction = get_dir(T, target) - if(direction & NORTH) - to_y = 32 - else if(direction & SOUTH) - to_y = -32 - if(direction & EAST) - to_x = 32 - else if(direction & WEST) - to_x = -32 - if(!direction) - to_y = 16 - flick_overlay(I, GLOB.clients, 6) - var/matrix/M = new - M.Turn(pick(-30, 30)) - animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING) - sleep(1) - animate(I, alpha = 0, transform = matrix(), time = 1) +/atom/movable + layer = OBJ_LAYER + var/last_move = null + var/last_move_time = 0 + var/anchored = FALSE + var/move_resist = MOVE_RESIST_DEFAULT + var/move_force = MOVE_FORCE_DEFAULT + var/pull_force = PULL_FORCE_DEFAULT + var/datum/thrownthing/throwing = null + var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported + var/throw_range = 7 + var/mob/pulledby = null + var/initial_language_holder = /datum/language_holder + var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. + var/verb_say = "says" + var/verb_ask = "asks" + var/verb_exclaim = "exclaims" + var/verb_whisper = "whispers" + var/verb_sing = "sings" + var/verb_yell = "yells" + var/speech_span + var/inertia_dir = 0 + var/atom/inertia_last_loc + var/inertia_moving = 0 + var/inertia_next_move = 0 + var/inertia_move_delay = 5 + var/pass_flags = NONE + /// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour + var/generic_canpass = TRUE + var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move + var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before. + var/list/client_mobs_in_contents // This contains all the client mobs within this container + var/list/acted_explosions //for explosion dodging + glide_size = 8 + appearance_flags = TILE_BOUND|PIXEL_SCALE + var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm + var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc. + var/atom/movable/pulling + var/grab_state = 0 + var/throwforce = 0 + var/datum/component/orbiter/orbiting + var/can_be_z_moved = TRUE + + var/zfalling = FALSE + + ///Last location of the atom for demo recording purposes + var/atom/demo_last_loc + + /// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE] + var/blocks_emissive = FALSE + ///Internal holder for emissive blocker object, do not use directly use blocks_emissive + var/atom/movable/emissive_blocker/em_block + + +/atom/movable/Initialize(mapload) + . = ..() + switch(blocks_emissive) + if(EMISSIVE_BLOCK_GENERIC) + update_emissive_block() + if(EMISSIVE_BLOCK_UNIQUE) + render_target = ref(src) + em_block = new(src, render_target) + vis_contents += em_block + +/atom/movable/Destroy() + QDEL_NULL(em_block) + return ..() + +/atom/movable/proc/update_emissive_block() + if(blocks_emissive != EMISSIVE_BLOCK_GENERIC) + return + if(length(managed_vis_overlays)) + for(var/a in managed_vis_overlays) + var/obj/effect/overlay/vis/vs + if(vs.plane == EMISSIVE_BLOCKER_PLANE) + SSvis_overlays.remove_vis_overlay(src, list(vs)) + break + SSvis_overlays.add_vis_overlay(src, icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE, dir) + +/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction) + if(!direction) + direction = DOWN + if(!source) + source = get_turf(src) + if(!source) + return FALSE + if(!target) + target = get_step_multiz(source, direction) + if(!target) + return FALSE + return !(movement_type & FLYING) && has_gravity(source) && !throwing + +/atom/movable/proc/onZImpact(turf/T, levels) + var/atom/highest = T + for(var/i in T.contents) + var/atom/A = i + if(!A.density) + continue + if(isobj(A) || ismob(A)) + if(A.layer > highest.layer) + highest = A + INVOKE_ASYNC(src, .proc/SpinAnimation, 5, 2) + return TRUE + +//For physical constraints to travelling up/down. +/atom/movable/proc/can_zTravel(turf/destination, direction) + var/turf/T = get_turf(src) + if(!T) + return FALSE + if(!direction) + if(!destination) + return FALSE + direction = get_dir(T, destination) + if(direction != UP && direction != DOWN) + return FALSE + if(!destination) + destination = get_step_multiz(src, direction) + if(!destination) + return FALSE + return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T) + +/atom/movable/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list("step_x", "step_y", "step_size", "bounds") + var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height") + if(var_name in banned_edits) + return FALSE //PLEASE no. + if((var_name in careful_edits) && (var_value % world.icon_size) != 0) + return FALSE + switch(var_name) + if(NAMEOF(src, x)) + var/turf/T = locate(var_value, y, z) + if(T) + forceMove(T) + return TRUE + return FALSE + if(NAMEOF(src, y)) + var/turf/T = locate(x, var_value, z) + if(T) + forceMove(T) + return TRUE + return FALSE + if(NAMEOF(src, z)) + var/turf/T = locate(x, y, var_value) + if(T) + forceMove(T) + return TRUE + return FALSE + if(NAMEOF(src, loc)) + if(istype(var_value, /atom)) + forceMove(var_value) + return TRUE + else if(isnull(var_value)) + moveToNullspace() + return TRUE + return FALSE + return ..() + +/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) + if(QDELETED(AM)) + return FALSE + if(!(AM.can_be_pulled(src, state, force))) + return FALSE + + // If we're pulling something then drop what we're currently pulling and pull this instead. + if(pulling) + if(state == 0) + stop_pulling() + return FALSE + // Are we trying to pull something we are already pulling? Then enter grab cycle and end. + if(AM == pulling) + setGrabState(state) + if(istype(AM,/mob/living)) + var/mob/living/AMob = AM + AMob.grabbedby(src) + return TRUE + stop_pulling() + + SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force) + + if(AM.pulledby) + log_combat(AM, AM.pulledby, "pulled from", src) + AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. + pulling = AM + AM.pulledby = src + setGrabState(state) + if(ismob(AM)) + var/mob/M = AM + log_combat(src, M, "grabbed", addition="passive grab") + if(!supress_message) + M.visible_message("[src] grabs [M] passively.", \ + "[src] grabs you passively.") + return TRUE + +/atom/movable/proc/stop_pulling() + if(pulling) + pulling.pulledby = null + var/mob/living/ex_pulled = pulling + pulling = null + setGrabState(0) + if(isliving(ex_pulled)) + var/mob/living/L = ex_pulled + L.update_mobility()// mob gets up if it was lyng down in a chokehold + +/atom/movable/proc/Move_Pulled(atom/A) + if(!pulling) + return + if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) + stop_pulling() + return + if(isliving(pulling)) + var/mob/living/L = pulling + if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it + stop_pulling() + return + if(A == loc && pulling.density) + return + if(!Process_Spacemove(get_dir(pulling.loc, A))) + return + step(pulling, get_dir(pulling.loc, A)) + return TRUE + +/mob/living/Move_Pulled(atom/A) + . = ..() + if(!. || !isliving(A)) + return + var/mob/living/L = A + set_pull_offsets(L, grab_state) + +/atom/movable/proc/check_pulling() + if(pulling) + var/atom/movable/pullee = pulling + if(pullee && get_dist(src, pullee) > 1) + stop_pulling() + return + if(!isturf(loc)) + stop_pulling() + return + if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). + log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") + stop_pulling() + return + if(pulling.anchored || pulling.move_resist > move_force) + stop_pulling() + return + if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. + pulledby.stop_pulling() + +//////////////////////////////////////// +// Here's where we rewrite how byond handles movement except slightly different +// To be removed on step_ conversion +// All this work to prevent a second bump +/atom/movable/Move(atom/newloc, direct=0) + . = FALSE + if(!newloc || newloc == loc) + return + + if(!direct) + direct = get_dir(src, newloc) + setDir(direct) + + if(!loc.Exit(src, newloc)) + return + + if(!newloc.Enter(src, src.loc)) + return + + if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE) + return + + // Past this is the point of no return + var/atom/oldloc = loc + var/area/oldarea = get_area(oldloc) + var/area/newarea = get_area(newloc) + loc = newloc + . = TRUE + oldloc.Exited(src, newloc) + if(oldarea != newarea) + oldarea.Exited(src, newloc) + + for(var/i in oldloc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Uncrossed(src) + + newloc.Entered(src, oldloc) + if(oldarea != newarea) + newarea.Entered(src, oldloc) + + for(var/i in loc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Crossed(src) + +//////////////////////////////////////// + +/atom/movable/Move(atom/newloc, direct) + var/atom/movable/pullee = pulling + var/turf/T = loc + if(!moving_from_pull) + check_pulling() + if(!loc || !newloc) + return FALSE + var/atom/oldloc = loc + + if(loc != newloc) + if (!(direct & (direct - 1))) //Cardinal move + . = ..() + else //Diagonal move, split it into cardinal moves + moving_diagonally = FIRST_DIAG_STEP + var/first_step_dir + // The `&& moving_diagonally` checks are so that a forceMove taking + // place due to a Crossed, Bumped, etc. call will interrupt + // the second half of the diagonal movement, or the second attempt + // at a first half if step() fails because we hit something. + if (direct & NORTH) + if (direct & EAST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & WEST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & SOUTH) + if (direct & EAST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + else if (direct & WEST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + if(moving_diagonally == SECOND_DIAG_STEP) + if(!.) + setDir(first_step_dir) + else if (!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(direct) + moving_diagonally = 0 + return + + if(!loc || (loc == oldloc && oldloc != newloc)) + last_move = 0 + return + + if(.) + Moved(oldloc, direct) + if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. + if(pulling.anchored) + stop_pulling() + else + var/pull_dir = get_dir(src, pulling) + //puller and pullee more than one tile away or in diagonal position + if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir))) + pulling.moving_from_pull = src + pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position + pulling.moving_from_pull = null + check_pulling() + + last_move = direct + setDir(direct) + if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s) + return FALSE + +//Called after a successful Move(). By this point, we've already moved +/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced) + if (!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(Dir) + if (length(client_mobs_in_contents)) + update_parallax_contents() + + return TRUE + +/atom/movable/Destroy(force) + QDEL_NULL(proximity_monitor) + QDEL_NULL(language_holder) + + unbuckle_all_mobs(force=1) + + . = ..() + if(loc) + //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary) + if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc)) + CanAtmosPass = ATMOS_PASS_YES + air_update_turf(TRUE) + loc.handle_atom_del(src) + for(var/atom/movable/AM in contents) + qdel(AM) + LAZYCLEARLIST(client_mobs_in_contents) + moveToNullspace() + invisibility = INVISIBILITY_ABSTRACT + if(pulledby) + pulledby.stop_pulling() + + if(orbiting) + orbiting.end_orbit(src) + orbiting = null + +// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly. +// You probably want CanPass() +/atom/movable/Cross(atom/movable/AM) + . = TRUE + SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, AM) + return CanPass(AM, AM.loc, TRUE) + +//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called! +/atom/movable/Crossed(atom/movable/AM, oldloc) + SHOULD_CALL_PARENT(TRUE) + . = ..() + SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) + +/atom/movable/Uncross(atom/movable/AM, atom/newloc) + . = ..() + if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS) + return FALSE + if(isturf(newloc) && !CheckExit(AM, newloc)) + return FALSE + +/atom/movable/Uncrossed(atom/movable/AM) + SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM) + +/atom/movable/Bump(atom/A) + if(!A) + CRASH("Bump was called with no argument.") + SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A) + . = ..() + if(!QDELETED(throwing)) + throwing.hit_atom(A) + . = TRUE + if(QDELETED(A)) + return + A.Bumped(src) + +/atom/movable/proc/forceMove(atom/destination) + . = FALSE + if(destination) + . = doMove(destination) + else + CRASH("No valid destination passed into forceMove") + +/atom/movable/proc/moveToNullspace() + return doMove(null) + +/atom/movable/proc/doMove(atom/destination) + . = FALSE + if(destination) + if(pulledby) + pulledby.stop_pulling() + var/atom/oldloc = loc + var/same_loc = oldloc == destination + var/area/old_area = get_area(oldloc) + var/area/destarea = get_area(destination) + + loc = destination + moving_diagonally = 0 + + if(!same_loc) + if(oldloc) + oldloc.Exited(src, destination) + if(old_area && old_area != destarea) + old_area.Exited(src, destination) + for(var/atom/movable/AM in oldloc) + AM.Uncrossed(src) + var/turf/oldturf = get_turf(oldloc) + var/turf/destturf = get_turf(destination) + var/old_z = (oldturf ? oldturf.z : null) + var/dest_z = (destturf ? destturf.z : null) + if (old_z != dest_z) + onTransitZ(old_z, dest_z) + destination.Entered(src, oldloc) + if(destarea && old_area != destarea) + destarea.Entered(src, oldloc) + + for(var/atom/movable/AM in destination) + if(AM == src) + continue + AM.Crossed(src, oldloc) + + Moved(oldloc, NONE, TRUE) + . = TRUE + + //If no destination, move the atom into nullspace (don't do this unless you know what you're doing) + else + . = TRUE + if (loc) + var/atom/oldloc = loc + var/area/old_area = get_area(oldloc) + oldloc.Exited(src, null) + if(old_area) + old_area.Exited(src, null) + loc = null + +/atom/movable/proc/onTransitZ(old_z,new_z) + SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z) + for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care. + var/atom/movable/AM = item + AM.onTransitZ(old_z,new_z) + +/atom/movable/proc/setMovetype(newval) + movement_type = newval + +/** + * Called whenever an object moves and by mobs when they attempt to move themselves through space + * And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move] + * + * Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting + * + * Mobs should return 1 if they should be able to move of their own volition, see [/client/Move] + * + * Arguments: + * * movement_dir - 0 when stopping or any dir when trying to move + */ +/atom/movable/proc/Process_Spacemove(movement_dir = 0) + if(has_gravity(src)) + return 1 + + if(pulledby) + return 1 + + if(throwing) + return 1 + + if(!isturf(loc)) + return 1 + + if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier + return 1 + + return 0 + + +/// Only moves the object if it's under no gravity +/atom/movable/proc/newtonian_move(direction) + if(!loc || Process_Spacemove(0)) + inertia_dir = 0 + return 0 + + inertia_dir = direction + if(!direction) + return 1 + inertia_last_loc = loc + SSspacedrift.processing[src] = src + return 1 + +/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + set waitfor = 0 + var/hitpush = TRUE + var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) + if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH) + hitpush = FALSE // hacky, tie this to something else or a proper workaround later + + if(!(impact_signal && (impact_signal & COMPONENT_MOVABLE_IMPACT_NEVERMIND))) // in case a signal interceptor broke or deleted the thing before we could process our hit + return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) + +/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) + if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) + step(src, AM.dir) + ..() + +/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) + if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) + return + return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle) + +///If this returns FALSE then callback will not be called. +/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE) + . = FALSE + if (!target || speed <= 0) + return + + if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW) + return + + if (pulledby) + pulledby.stop_pulling() + + //They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? + if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2) + var/user_momentum = thrower.cached_multiplicative_slowdown + if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead. + user_momentum = world.tick_lag + + user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses. + + if (get_dir(thrower, target) & last_move) + user_momentum = user_momentum //basically a noop, but needed + else if (get_dir(target, thrower) & last_move) + user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly + else + user_momentum = 0 + + + if (user_momentum) + //first lets add that momentum to range. + range *= (user_momentum / speed) + 1 + //then lets add it to speed + speed += user_momentum + if (speed <= 0) + return//no throw speed, the user was moving too fast. + + . = TRUE // No failure conditions past this point. + + var/datum/thrownthing/TT = new() + TT.thrownthing = src + TT.target = target + TT.target_turf = get_turf(target) + TT.init_dir = get_dir(src, target) + TT.maxrange = range + TT.speed = speed + TT.thrower = thrower + TT.diagonals_first = diagonals_first + TT.force = force + TT.gentle = gentle + TT.callback = callback + if(!QDELETED(thrower)) + TT.target_zone = thrower.zone_selected + + var/dist_x = abs(target.x - src.x) + var/dist_y = abs(target.y - src.y) + var/dx = (target.x > src.x) ? EAST : WEST + var/dy = (target.y > src.y) ? NORTH : SOUTH + + if (dist_x == dist_y) + TT.pure_diagonal = 1 + + else if(dist_x <= dist_y) + var/olddist_x = dist_x + var/olddx = dx + dist_x = dist_y + dist_y = olddist_x + dx = dy + dy = olddx + TT.dist_x = dist_x + TT.dist_y = dist_y + TT.dx = dx + TT.dy = dy + TT.diagonal_error = dist_x/2 - dist_y + TT.start_time = world.time + + if(pulledby) + pulledby.stop_pulling() + + throwing = TT + if(spin) + SpinAnimation(5, 1) + + SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin) + SSthrowing.processing[src] = TT + if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun)) + SSthrowing.currentrun[src] = TT + if (quickstart) + TT.tick() + +/atom/movable/proc/handle_buckled_mob_movement(newloc,direct) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + if(!buckled_mob.Move(newloc, direct)) + forceMove(buckled_mob.loc) + last_move = buckled_mob.last_move + inertia_dir = last_move + buckled_mob.inertia_dir = last_move + return 0 + return 1 + +/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.force_pushed(src, force, direction) + if(!silent && .) + visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") + +/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.move_crushed(src, force, direction) + if(!silent && .) + visible_message("[src] crushes past [AM]!", "You crush [AM]!") + +/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(mover in buckled_mobs) + return TRUE + +/// Returns true or false to allow src to move through the blocker, mover has final say +/atom/movable/proc/CanPassThrough(atom/blocker, turf/target, blocker_opinion) + SHOULD_CALL_PARENT(TRUE) + SHOULD_BE_PURE(TRUE) + return blocker_opinion + +/// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called. +/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S) + return + +/// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item. +/atom/movable/proc/on_enter_storage(datum/component/storage/concrete/S) + return + +/atom/movable/proc/get_spacemove_backup() + var/atom/movable/dense_object_backup + for(var/A in orange(1, get_turf(src))) + if(isarea(A)) + continue + else if(isturf(A)) + var/turf/turf = A + if(!turf.density) + continue + return turf + else + var/atom/movable/AM = A + if(!AM.CanPass(src) || AM.density) + if(AM.anchored) + return AM + dense_object_backup = AM + break + . = dense_object_backup + +///called when a mob resists while inside a container that is itself inside something. +/atom/movable/proc/relay_container_resist(mob/living/user, obj/O) + return + + +/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) + if(!no_effect && (visual_effect_icon || used_item)) + do_item_attack_animation(A, visual_effect_icon, used_item) + + if(A == src) + return //don't do an animation if attacking self + var/pixel_x_diff = 0 + var/pixel_y_diff = 0 + + var/direction = get_dir(src, A) + if(direction & NORTH) + pixel_y_diff = 8 + else if(direction & SOUTH) + pixel_y_diff = -8 + + if(direction & EAST) + pixel_x_diff = 8 + else if(direction & WEST) + pixel_x_diff = -8 + + animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) + animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2) + +/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) + var/image/I + if(visual_effect_icon) + I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) + else if(used_item) + I = image(icon = used_item, loc = A, layer = A.layer + 0.1) + I.plane = GAME_PLANE + + // Scale the icon. + I.transform *= 0.75 + // The icon should not rotate. + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + + // Set the direction of the icon animation. + var/direction = get_dir(src, A) + if(direction & NORTH) + I.pixel_y = -16 + else if(direction & SOUTH) + I.pixel_y = 16 + + if(direction & EAST) + I.pixel_x = -16 + else if(direction & WEST) + I.pixel_x = 16 + + if(!direction) // Attacked self?! + I.pixel_z = 16 + + if(!I) + return + + flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second + + // And animate the attack! + animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3) + +/atom/movable/vv_get_dropdown() + . = ..() + . += "" + . += "" + +/atom/movable/proc/ex_check(ex_id) + if(!ex_id) + return TRUE + LAZYINITLIST(acted_explosions) + if(ex_id in acted_explosions) + return FALSE + acted_explosions += ex_id + return TRUE + +//TODO: Better floating +/atom/movable/proc/float(on) + if(throwing) + return + if(on && !(movement_type & FLOATING)) + animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1) + sleep(10) + animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1) + setMovetype(movement_type | FLOATING) + else if (!on && (movement_type & FLOATING)) + animate(src, pixel_y = initial(pixel_y), time = 10) + setMovetype(movement_type & ~FLOATING) + + +/* Language procs +* Unless you are doing something very specific, these are the ones you want to use. +*/ + +/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one. +/atom/movable/proc/get_language_holder(get_minds = TRUE) + if(!language_holder) + language_holder = new initial_language_holder(src) + return language_holder + +/// Grants the supplied language and sets omnitongue true. +/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM) + var/datum/language_holder/LH = get_language_holder() + return LH.grant_language(language, understood, spoken, source) + +/// Grants every language. +/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND) + var/datum/language_holder/LH = get_language_holder() + return LH.grant_all_languages(understood, spoken, grant_omnitongue, source) + +/// Removes a single language. +/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL) + var/datum/language_holder/LH = get_language_holder() + return LH.remove_language(language, understood, spoken, source) + +/// Removes every language and sets omnitongue false. +/atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE) + var/datum/language_holder/LH = get_language_holder() + return LH.remove_all_languages(source, remove_omnitongue) + +/// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later. +/atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM) + var/datum/language_holder/LH = get_language_holder() + return LH.add_blocked_language(language, source) + +/// Removes a language from the blocked language list. +/atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM) + var/datum/language_holder/LH = get_language_holder() + return LH.remove_blocked_language(language, source) + +/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language. +/atom/movable/proc/has_language(language, spoken = FALSE) + var/datum/language_holder/LH = get_language_holder() + return LH.has_language(language, spoken) + +/// Checks if atom can speak the language. +/atom/movable/proc/can_speak_language(language) + var/datum/language_holder/LH = get_language_holder() + return LH.can_speak_language(language) + +/// Returns the result of tongue specific limitations on spoken languages. +/atom/movable/proc/could_speak_language(language) + return TRUE + +/// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible. +/atom/movable/proc/get_selected_language() + var/datum/language_holder/LH = get_language_holder() + return LH.get_selected_language() + +/// Gets a random understood language, useful for hallucinations and such. +/atom/movable/proc/get_random_understood_language() + var/datum/language_holder/LH = get_language_holder() + return LH.get_random_understood_language() + +/// Gets a random spoken language, useful for forced speech and such. +/atom/movable/proc/get_random_spoken_language() + var/datum/language_holder/LH = get_language_holder() + return LH.get_random_spoken_language() + +/// Copies all languages into the supplied atom/language holder. Source should be overridden when you +/// do not want the language overwritten by later atom updates or want to avoid blocked languages. +/atom/movable/proc/copy_languages(from_holder, source_override) + if(isatom(from_holder)) + var/atom/movable/thing = from_holder + from_holder = thing.get_language_holder() + var/datum/language_holder/LH = get_language_holder() + return LH.copy_languages(from_holder, source_override) + +/// Empties out the atom specific languages and updates them according to the current atoms language holder. +/// As a side effect, it also creates missing language holders in the process. +/atom/movable/proc/update_atom_languages() + var/datum/language_holder/LH = get_language_holder() + return LH.update_atom_languages(src) + +/* End language procs */ + + +/atom/movable/proc/ConveyorMove(movedir) + set waitfor = FALSE + if(!anchored && has_gravity()) + step(src, movedir) + +//Returns an atom's power cell, if it has one. Overload for individual items. +/atom/movable/proc/get_cell() + return + +/atom/movable/proc/can_be_pulled(user, grab_state, force) + if(src == user || !isturf(loc)) + return FALSE + if(anchored || throwing) + return FALSE + if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) + return FALSE + return TRUE + +/** + * Updates the grab state of the movable + * + * This exists to act as a hook for behaviour + */ +/atom/movable/proc/setGrabState(newstate) + grab_state = newstate + +/obj/item/proc/do_pickup_animation(atom/target) + set waitfor = FALSE + if(!istype(loc, /turf)) + return + var/image/I = image(icon = src, loc = loc, layer = layer + 0.1) + I.plane = GAME_PLANE + I.transform *= 0.75 + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + var/turf/T = get_turf(src) + var/direction + var/to_x = 0 + var/to_y = 0 + + if(!QDELETED(T) && !QDELETED(target)) + direction = get_dir(T, target) + if(direction & NORTH) + to_y = 32 + else if(direction & SOUTH) + to_y = -32 + if(direction & EAST) + to_x = 32 + else if(direction & WEST) + to_x = -32 + if(!direction) + to_y = 16 + flick_overlay(I, GLOB.clients, 6) + var/matrix/M = new + M.Turn(pick(-30, 30)) + animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING) + sleep(1) + animate(I, alpha = 0, transform = matrix(), time = 1) diff --git a/code/game/communications.dm b/code/game/communications.dm index ed470473d95..696b942434f 100644 --- a/code/game/communications.dm +++ b/code/game/communications.dm @@ -1,199 +1,199 @@ -/* - HOW IT WORKS - - The SSradio is a global object maintaining all radio transmissions, think about it as about "ether". - Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way. - procs: - - add_object(obj/device as obj, var/new_frequency as num, var/filter as text|null = null) - Adds listening object. - parameters: - device - device receiving signals, must have proc receive_signal (see description below). - one device may listen several frequencies, but not same frequency twice. - new_frequency - see possibly frequencies below; - filter - thing for optimization. Optional, but recommended. - All filters should be consolidated in this file, see defines later. - Device without listening filter will receive all signals (on specified frequency). - Device with filter will receive any signals sent without filter. - Device with filter will not receive any signals sent with different filter. - returns: - Reference to frequency object. - - remove_object (obj/device, old_frequency) - Obliviously, after calling this proc, device will not receive any signals on old_frequency. - Other frequencies will left unaffected. - - return_frequency(var/frequency as num) - returns: - Reference to frequency object. Use it if you need to send and do not need to listen. - - radio_frequency is a global object maintaining list of devices that listening specific frequency. - procs: - - post_signal(obj/source as obj|null, datum/signal/signal, var/filter as text|null = null, var/range as num|null = null) - Sends signal to all devices that wants such signal. - parameters: - source - object, emitted signal. Usually, devices will not receive their own signals. - signal - see description below. - filter - described above. - range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels. - - obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param) - Handler from received signals. By default does nothing. Define your own for your object. - Avoid of sending signals directly from this proc, use spawn(0). Do not use sleep() here please. - parameters: - signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return! - receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO. - TRANSMISSION_WIRE is currently unused. - receive_param - for TRANSMISSION_RADIO here comes frequency. - - datum/signal - vars: - source - an object that emitted signal. Used for debug and bearing. - data - list with transmitting data. Usual use pattern: - data["msg"] = "hello world" - encryption - Some number symbolizing "encryption key". - Note that game actually do not use any cryptography here. - If receiving object don't know right key, it must ignore encrypted signal in its receive_signal. - -*/ -/* the radio controller is a confusing piece of shit and didnt work - so i made radios not use the radio controller. -*/ -GLOBAL_LIST_EMPTY(all_radios) -/proc/add_radio(obj/item/radio, freq) - if(!freq || !radio) - return - if(!GLOB.all_radios["[freq]"]) - GLOB.all_radios["[freq]"] = list(radio) - return freq - - GLOB.all_radios["[freq]"] |= radio - return freq - -/proc/remove_radio(obj/item/radio, freq) - if(!freq || !radio) - return - if(!GLOB.all_radios["[freq]"]) - return - - GLOB.all_radios["[freq]"] -= radio - -/proc/remove_radio_all(obj/item/radio) - for(var/freq in GLOB.all_radios) - GLOB.all_radios["[freq]"] -= radio - -// For information on what objects or departments use what frequencies, -// see __DEFINES/radio.dm. Mappers may also select additional frequencies for -// use in maps, such as in intercoms. - -GLOBAL_LIST_INIT(radiochannels, list( - RADIO_CHANNEL_COMMON = FREQ_COMMON, - RADIO_CHANNEL_SCIENCE = FREQ_SCIENCE, - RADIO_CHANNEL_COMMAND = FREQ_COMMAND, - RADIO_CHANNEL_MEDICAL = FREQ_MEDICAL, - RADIO_CHANNEL_ENGINEERING = FREQ_ENGINEERING, - RADIO_CHANNEL_SECURITY = FREQ_SECURITY, - RADIO_CHANNEL_CENTCOM = FREQ_CENTCOM, - RADIO_CHANNEL_SYNDICATE = FREQ_SYNDICATE, - RADIO_CHANNEL_SUPPLY = FREQ_SUPPLY, - RADIO_CHANNEL_SERVICE = FREQ_SERVICE, - RADIO_CHANNEL_AI_PRIVATE = FREQ_AI_PRIVATE, - RADIO_CHANNEL_CTF_RED = FREQ_CTF_RED, - RADIO_CHANNEL_CTF_BLUE = FREQ_CTF_BLUE -)) - -GLOBAL_LIST_INIT(reverseradiochannels, list( - "[FREQ_COMMON]" = RADIO_CHANNEL_COMMON, - "[FREQ_SCIENCE]" = RADIO_CHANNEL_SCIENCE, - "[FREQ_COMMAND]" = RADIO_CHANNEL_COMMAND, - "[FREQ_MEDICAL]" = RADIO_CHANNEL_MEDICAL, - "[FREQ_ENGINEERING]" = RADIO_CHANNEL_ENGINEERING, - "[FREQ_SECURITY]" = RADIO_CHANNEL_SECURITY, - "[FREQ_CENTCOM]" = RADIO_CHANNEL_CENTCOM, - "[FREQ_SYNDICATE]" = RADIO_CHANNEL_SYNDICATE, - "[FREQ_SUPPLY]" = RADIO_CHANNEL_SUPPLY, - "[FREQ_SERVICE]" = RADIO_CHANNEL_SERVICE, - "[FREQ_AI_PRIVATE]" = RADIO_CHANNEL_AI_PRIVATE, - "[FREQ_CTF_RED]" = RADIO_CHANNEL_CTF_RED, - "[FREQ_CTF_BLUE]" = RADIO_CHANNEL_CTF_BLUE -)) - -/datum/radio_frequency - var/frequency as num - var/list/list/obj/devices = list() - -/datum/radio_frequency/New(freq) - frequency = freq - -//If range > 0, only post to devices on the same z_level and within range -//Use range = -1, to restrain to the same z_level without limiting range -/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, filter = null as text|null, range = null as num|null) - // Ensure the signal's data is fully filled - signal.source = source - signal.frequency = frequency - - //Apply filter to the signal. If none supply, broadcast to every devices - //_default channel is always checked - var/list/filter_list - - if(filter) - filter_list = list(filter,"_default") - else - filter_list = devices - - //If checking range, find the source turf - var/turf/start_point - if(range) - start_point = get_turf(source) - if(!start_point) - return 0 - - //Send the data - for(var/current_filter in filter_list) - for(var/obj/device in devices[current_filter]) - if(device == source) - continue - if(range) - var/turf/end_point = get_turf(device) - if(!end_point) - continue - if(start_point.z != end_point.z || (range > 0 && get_dist(start_point, end_point) > range)) - continue - device.receive_signal(signal) - -/datum/radio_frequency/proc/add_listener(obj/device, filter as text|null) - if (!filter) - filter = "_default" - - var/list/devices_line = devices[filter] - if(!devices_line) - devices[filter] = devices_line = list() - devices_line += device - - -/datum/radio_frequency/proc/remove_listener(obj/device) - for(var/devices_filter in devices) - var/list/devices_line = devices[devices_filter] - if(!devices_line) - devices -= devices_filter - devices_line -= device - if(!devices_line.len) - devices -= devices_filter - - -/obj/proc/receive_signal(datum/signal/signal) - return - -/datum/signal - var/obj/source - var/frequency = 0 - var/transmission_method - var/list/data - -/datum/signal/New(data, transmission_method = TRANSMISSION_RADIO) - src.data = data || list() - src.transmission_method = transmission_method +/* + HOW IT WORKS + + The SSradio is a global object maintaining all radio transmissions, think about it as about "ether". + Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way. + procs: + + add_object(obj/device as obj, var/new_frequency as num, var/filter as text|null = null) + Adds listening object. + parameters: + device - device receiving signals, must have proc receive_signal (see description below). + one device may listen several frequencies, but not same frequency twice. + new_frequency - see possibly frequencies below; + filter - thing for optimization. Optional, but recommended. + All filters should be consolidated in this file, see defines later. + Device without listening filter will receive all signals (on specified frequency). + Device with filter will receive any signals sent without filter. + Device with filter will not receive any signals sent with different filter. + returns: + Reference to frequency object. + + remove_object (obj/device, old_frequency) + Obliviously, after calling this proc, device will not receive any signals on old_frequency. + Other frequencies will left unaffected. + + return_frequency(var/frequency as num) + returns: + Reference to frequency object. Use it if you need to send and do not need to listen. + + radio_frequency is a global object maintaining list of devices that listening specific frequency. + procs: + + post_signal(obj/source as obj|null, datum/signal/signal, var/filter as text|null = null, var/range as num|null = null) + Sends signal to all devices that wants such signal. + parameters: + source - object, emitted signal. Usually, devices will not receive their own signals. + signal - see description below. + filter - described above. + range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels. + + obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param) + Handler from received signals. By default does nothing. Define your own for your object. + Avoid of sending signals directly from this proc, use spawn(0). Do not use sleep() here please. + parameters: + signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return! + receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO. + TRANSMISSION_WIRE is currently unused. + receive_param - for TRANSMISSION_RADIO here comes frequency. + + datum/signal + vars: + source + an object that emitted signal. Used for debug and bearing. + data + list with transmitting data. Usual use pattern: + data["msg"] = "hello world" + encryption + Some number symbolizing "encryption key". + Note that game actually do not use any cryptography here. + If receiving object don't know right key, it must ignore encrypted signal in its receive_signal. + +*/ +/* the radio controller is a confusing piece of shit and didnt work + so i made radios not use the radio controller. +*/ +GLOBAL_LIST_EMPTY(all_radios) +/proc/add_radio(obj/item/radio, freq) + if(!freq || !radio) + return + if(!GLOB.all_radios["[freq]"]) + GLOB.all_radios["[freq]"] = list(radio) + return freq + + GLOB.all_radios["[freq]"] |= radio + return freq + +/proc/remove_radio(obj/item/radio, freq) + if(!freq || !radio) + return + if(!GLOB.all_radios["[freq]"]) + return + + GLOB.all_radios["[freq]"] -= radio + +/proc/remove_radio_all(obj/item/radio) + for(var/freq in GLOB.all_radios) + GLOB.all_radios["[freq]"] -= radio + +// For information on what objects or departments use what frequencies, +// see __DEFINES/radio.dm. Mappers may also select additional frequencies for +// use in maps, such as in intercoms. + +GLOBAL_LIST_INIT(radiochannels, list( + RADIO_CHANNEL_COMMON = FREQ_COMMON, + RADIO_CHANNEL_SCIENCE = FREQ_SCIENCE, + RADIO_CHANNEL_COMMAND = FREQ_COMMAND, + RADIO_CHANNEL_MEDICAL = FREQ_MEDICAL, + RADIO_CHANNEL_ENGINEERING = FREQ_ENGINEERING, + RADIO_CHANNEL_SECURITY = FREQ_SECURITY, + RADIO_CHANNEL_CENTCOM = FREQ_CENTCOM, + RADIO_CHANNEL_SYNDICATE = FREQ_SYNDICATE, + RADIO_CHANNEL_SUPPLY = FREQ_SUPPLY, + RADIO_CHANNEL_SERVICE = FREQ_SERVICE, + RADIO_CHANNEL_AI_PRIVATE = FREQ_AI_PRIVATE, + RADIO_CHANNEL_CTF_RED = FREQ_CTF_RED, + RADIO_CHANNEL_CTF_BLUE = FREQ_CTF_BLUE +)) + +GLOBAL_LIST_INIT(reverseradiochannels, list( + "[FREQ_COMMON]" = RADIO_CHANNEL_COMMON, + "[FREQ_SCIENCE]" = RADIO_CHANNEL_SCIENCE, + "[FREQ_COMMAND]" = RADIO_CHANNEL_COMMAND, + "[FREQ_MEDICAL]" = RADIO_CHANNEL_MEDICAL, + "[FREQ_ENGINEERING]" = RADIO_CHANNEL_ENGINEERING, + "[FREQ_SECURITY]" = RADIO_CHANNEL_SECURITY, + "[FREQ_CENTCOM]" = RADIO_CHANNEL_CENTCOM, + "[FREQ_SYNDICATE]" = RADIO_CHANNEL_SYNDICATE, + "[FREQ_SUPPLY]" = RADIO_CHANNEL_SUPPLY, + "[FREQ_SERVICE]" = RADIO_CHANNEL_SERVICE, + "[FREQ_AI_PRIVATE]" = RADIO_CHANNEL_AI_PRIVATE, + "[FREQ_CTF_RED]" = RADIO_CHANNEL_CTF_RED, + "[FREQ_CTF_BLUE]" = RADIO_CHANNEL_CTF_BLUE +)) + +/datum/radio_frequency + var/frequency as num + var/list/list/obj/devices = list() + +/datum/radio_frequency/New(freq) + frequency = freq + +//If range > 0, only post to devices on the same z_level and within range +//Use range = -1, to restrain to the same z_level without limiting range +/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, filter = null as text|null, range = null as num|null) + // Ensure the signal's data is fully filled + signal.source = source + signal.frequency = frequency + + //Apply filter to the signal. If none supply, broadcast to every devices + //_default channel is always checked + var/list/filter_list + + if(filter) + filter_list = list(filter,"_default") + else + filter_list = devices + + //If checking range, find the source turf + var/turf/start_point + if(range) + start_point = get_turf(source) + if(!start_point) + return 0 + + //Send the data + for(var/current_filter in filter_list) + for(var/obj/device in devices[current_filter]) + if(device == source) + continue + if(range) + var/turf/end_point = get_turf(device) + if(!end_point) + continue + if(start_point.z != end_point.z || (range > 0 && get_dist(start_point, end_point) > range)) + continue + device.receive_signal(signal) + +/datum/radio_frequency/proc/add_listener(obj/device, filter as text|null) + if (!filter) + filter = "_default" + + var/list/devices_line = devices[filter] + if(!devices_line) + devices[filter] = devices_line = list() + devices_line += device + + +/datum/radio_frequency/proc/remove_listener(obj/device) + for(var/devices_filter in devices) + var/list/devices_line = devices[devices_filter] + if(!devices_line) + devices -= devices_filter + devices_line -= device + if(!devices_line.len) + devices -= devices_filter + + +/obj/proc/receive_signal(datum/signal/signal) + return + +/datum/signal + var/obj/source + var/frequency = 0 + var/transmission_method + var/list/data + +/datum/signal/New(data, transmission_method = TRANSMISSION_RADIO) + src.data = data || list() + src.transmission_method = transmission_method diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm index babddde0161..4b9499be778 100644 --- a/code/game/gamemodes/brother/traitor_bro.dm +++ b/code/game/gamemodes/brother/traitor_bro.dm @@ -1,67 +1,67 @@ -/datum/game_mode - var/list/datum/mind/brothers = list() - var/list/datum/team/brother_team/brother_teams = list() - -/datum/game_mode/traitor/bros - name = "traitor+brothers" - config_tag = "traitorbro" - restricted_jobs = list("Prisoner","AI", "Cyborg") - - announce_span = "danger" - announce_text = "There are Syndicate agents and Blood Brothers on the station!\n\ - Traitors: Accomplish your objectives.\n\ - Blood Brothers: Accomplish your objectives.\n\ - Crew: Do not let the traitors or brothers succeed!" - - var/list/datum/team/brother_team/pre_brother_teams = list() - var/const/team_amount = 2 //hard limit on brother teams if scaling is turned off - var/const/min_team_size = 2 - traitors_required = FALSE //Only teams are possible - -/datum/game_mode/traitor/bros/pre_setup() - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/list/datum/mind/possible_brothers = get_players_for_role(ROLE_BROTHER) - - var/num_teams = team_amount - var/bsc = CONFIG_GET(number/brother_scaling_coeff) - if(bsc) - num_teams = max(1, round(num_players() / bsc)) - - for(var/j = 1 to num_teams) - if(possible_brothers.len < min_team_size || antag_candidates.len <= required_enemies) - break - var/datum/team/brother_team/team = new - var/team_size = prob(10) ? min(3, possible_brothers.len) : 2 - for(var/k = 1 to team_size) - var/datum/mind/bro = antag_pick(possible_brothers) - possible_brothers -= bro - antag_candidates -= bro - team.add_member(bro) - bro.special_role = "brother" - bro.restricted_roles = restricted_jobs - log_game("[key_name(bro)] has been selected as a Brother") - pre_brother_teams += team - . = ..() - if(.) //To ensure the game mode is going ahead - for(var/teams in pre_brother_teams) - for(var/antag in teams) - GLOB.pre_setup_antags += antag - return - -/datum/game_mode/traitor/bros/post_setup() - for(var/datum/team/brother_team/team in pre_brother_teams) - team.pick_meeting_area() - team.forge_brother_objectives() - for(var/datum/mind/M in team.members) - M.add_antag_datum(/datum/antagonist/brother, team) - GLOB.pre_setup_antags -= M - team.update_name() - brother_teams += pre_brother_teams - return ..() - -/datum/game_mode/traitor/bros/generate_report() - return "It's Syndicate recruiting season. Be alert for potential Syndicate infiltrators, but also watch out for disgruntled employees trying to defect. Unlike Nanotrasen, the Syndicate prides itself in teamwork and will only recruit pairs that share a brotherly trust." +/datum/game_mode + var/list/datum/mind/brothers = list() + var/list/datum/team/brother_team/brother_teams = list() + +/datum/game_mode/traitor/bros + name = "traitor+brothers" + config_tag = "traitorbro" + restricted_jobs = list("Prisoner","AI", "Cyborg") + + announce_span = "danger" + announce_text = "There are Syndicate agents and Blood Brothers on the station!\n\ + Traitors: Accomplish your objectives.\n\ + Blood Brothers: Accomplish your objectives.\n\ + Crew: Do not let the traitors or brothers succeed!" + + var/list/datum/team/brother_team/pre_brother_teams = list() + var/const/team_amount = 2 //hard limit on brother teams if scaling is turned off + var/const/min_team_size = 2 + traitors_required = FALSE //Only teams are possible + +/datum/game_mode/traitor/bros/pre_setup() + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/list/datum/mind/possible_brothers = get_players_for_role(ROLE_BROTHER) + + var/num_teams = team_amount + var/bsc = CONFIG_GET(number/brother_scaling_coeff) + if(bsc) + num_teams = max(1, round(num_players() / bsc)) + + for(var/j = 1 to num_teams) + if(possible_brothers.len < min_team_size || antag_candidates.len <= required_enemies) + break + var/datum/team/brother_team/team = new + var/team_size = prob(10) ? min(3, possible_brothers.len) : 2 + for(var/k = 1 to team_size) + var/datum/mind/bro = antag_pick(possible_brothers) + possible_brothers -= bro + antag_candidates -= bro + team.add_member(bro) + bro.special_role = "brother" + bro.restricted_roles = restricted_jobs + log_game("[key_name(bro)] has been selected as a Brother") + pre_brother_teams += team + . = ..() + if(.) //To ensure the game mode is going ahead + for(var/teams in pre_brother_teams) + for(var/antag in teams) + GLOB.pre_setup_antags += antag + return + +/datum/game_mode/traitor/bros/post_setup() + for(var/datum/team/brother_team/team in pre_brother_teams) + team.pick_meeting_area() + team.forge_brother_objectives() + for(var/datum/mind/M in team.members) + M.add_antag_datum(/datum/antagonist/brother, team) + GLOB.pre_setup_antags -= M + team.update_name() + brother_teams += pre_brother_teams + return ..() + +/datum/game_mode/traitor/bros/generate_report() + return "It's Syndicate recruiting season. Be alert for potential Syndicate infiltrators, but also watch out for disgruntled employees trying to defect. Unlike Nanotrasen, the Syndicate prides itself in teamwork and will only recruit pairs that share a brotherly trust." diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index 4f71ed70442..ce04b21e77b 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -1,143 +1,143 @@ -GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega")) -GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store")) -GLOBAL_LIST_INIT(slot2slot, list("head" = ITEM_SLOT_HEAD, "wear_mask" = ITEM_SLOT_MASK, "neck" = ITEM_SLOT_NECK, "back" = ITEM_SLOT_BACK, "wear_suit" = ITEM_SLOT_OCLOTHING, "w_uniform" = ITEM_SLOT_ICLOTHING, "shoes" = ITEM_SLOT_FEET, "belt" = ITEM_SLOT_BELT, "gloves" = ITEM_SLOT_GLOVES, "glasses" = ITEM_SLOT_EYES, "ears" = ITEM_SLOT_EARS, "wear_id" = ITEM_SLOT_ID, "s_store" = ITEM_SLOT_SUITSTORE)) -GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling)) -GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our this objective to all lings - - -/datum/game_mode/changeling - name = "changeling" - config_tag = "changeling" - report_type = "changeling" - antag_flag = ROLE_CHANGELING - false_report_weight = 10 - restricted_jobs = list("AI", "Cyborg") - protected_jobs = list("Prisoner", "Security Officer", "Warden", "Detective", "Head of Security", "Captain") - required_players = 15 - required_enemies = 1 - recommended_enemies = 4 - reroll_friendly = 1 - - announce_span = "green" - announce_text = "Alien changelings have infiltrated the crew!\n\ - Changelings: Accomplish the objectives assigned to you.\n\ - Crew: Root out and eliminate the changeling menace." - - var/const/changeling_amount = 4 //hard limit on changelings if scaling is turned off - var/list/changelings = list() - -/datum/game_mode/changeling/pre_setup() - - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/num_changelings = 1 - - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - if(csc) - num_changelings = max(1, min(round(num_players() / (csc * 2)) + 2, round(num_players() / csc))) - else - num_changelings = max(1, min(num_players(), changeling_amount)) - - if(antag_candidates.len>0) - for(var/i = 0, i < num_changelings, i++) - if(!antag_candidates.len) - break - var/datum/mind/changeling = antag_pick(antag_candidates) - antag_candidates -= changeling - changelings += changeling - changeling.special_role = ROLE_CHANGELING - changeling.restricted_roles = restricted_jobs - GLOB.pre_setup_antags += changeling - return TRUE - else - setup_error = "Not enough changeling candidates" - return FALSE - -/datum/game_mode/changeling/post_setup() - //Decide if it's ok for the lings to have a team objective - //And then set it up to be handed out in forge_changeling_objectives - var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) - var/list/possible_team_objectives = list() - for(var/T in team_objectives) - var/datum/objective/changeling_team_objective/CTO = T - - if(changelings.len >= initial(CTO.min_lings)) - possible_team_objectives += T - - if(possible_team_objectives.len && prob(20*changelings.len)) - GLOB.changeling_team_objective_type = pick(possible_team_objectives) - - for(var/datum/mind/changeling in changelings) - log_game("[key_name(changeling)] has been selected as a changeling") - var/datum/antagonist/changeling/new_antag = new() - new_antag.team_mode = TRUE - changeling.add_antag_datum(new_antag) - GLOB.pre_setup_antags -= changeling - ..() - -/datum/game_mode/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - var/changelingcap = min(round(GLOB.joined_player_list.len / (csc * 2)) + 2, round(GLOB.joined_player_list.len / csc)) - if(changelings.len >= changelingcap) //Caps number of latejoin antagonists - return - if(changelings.len <= (changelingcap - 2) || prob(100 - (csc * 2))) - if(ROLE_CHANGELING in character.client.prefs.be_special) - if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changeling() - changelings += character.mind - -/datum/game_mode/changeling/generate_report() - return "The Gorlex Marauders have announced the successful raid and destruction of Central Command containment ship #S-[rand(1111, 9999)]. This ship housed only a single prisoner - \ - codenamed \"Thing\", and it was highly adaptive and extremely dangerous. We have reason to believe that the Thing has allied with the Syndicate, and you should note that likelihood \ - of the Thing being sent to a station in this sector is highly likely. It may be in the guise of any crew member. Trust nobody - suspect everybody. Do not announce this to the crew, \ - as paranoia may spread and inhibit workplace efficiency." - -/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) - var/datum/dna/chosen_dna = chosen_prof.dna - user.real_name = chosen_prof.name - user.underwear = chosen_prof.underwear - user.undershirt = chosen_prof.undershirt - user.socks = chosen_prof.socks - - chosen_dna.transfer_identity(user, 1) - user.updateappearance(mutcolor_update=1) - user.update_body() - user.domutcheck() - - //vars hackery. not pretty, but better than the alternative. - for(var/slot in GLOB.slots) - if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items - qdel(user.vars[slot]) - continue - - if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) - continue - - var/obj/item/C - var/equip = 0 - if(!user.vars[slot]) - var/thetype = GLOB.slot2type[slot] - equip = 1 - C = new thetype(user) - - else if(istype(user.vars[slot], GLOB.slot2type[slot])) - C = user.vars[slot] - - C.appearance = chosen_prof.appearance_list[slot] - C.name = chosen_prof.name_list[slot] - C.flags_cover = chosen_prof.flags_cover_list[slot] - C.lefthand_file = chosen_prof.lefthand_file_list[slot] - C.righthand_file = chosen_prof.righthand_file_list[slot] - C.inhand_icon_state = chosen_prof.inhand_icon_state_list[slot] - C.worn_icon = chosen_prof.worn_icon_list[slot] - C.worn_icon_state = chosen_prof.worn_icon_state_list[slot] - if(equip) - user.equip_to_slot_or_del(C, GLOB.slot2slot[slot]) - - user.regenerate_icons() +GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega")) +GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store")) +GLOBAL_LIST_INIT(slot2slot, list("head" = ITEM_SLOT_HEAD, "wear_mask" = ITEM_SLOT_MASK, "neck" = ITEM_SLOT_NECK, "back" = ITEM_SLOT_BACK, "wear_suit" = ITEM_SLOT_OCLOTHING, "w_uniform" = ITEM_SLOT_ICLOTHING, "shoes" = ITEM_SLOT_FEET, "belt" = ITEM_SLOT_BELT, "gloves" = ITEM_SLOT_GLOVES, "glasses" = ITEM_SLOT_EYES, "ears" = ITEM_SLOT_EARS, "wear_id" = ITEM_SLOT_ID, "s_store" = ITEM_SLOT_SUITSTORE)) +GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling)) +GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our this objective to all lings + + +/datum/game_mode/changeling + name = "changeling" + config_tag = "changeling" + report_type = "changeling" + antag_flag = ROLE_CHANGELING + false_report_weight = 10 + restricted_jobs = list("AI", "Cyborg") + protected_jobs = list("Prisoner", "Security Officer", "Warden", "Detective", "Head of Security", "Captain") + required_players = 15 + required_enemies = 1 + recommended_enemies = 4 + reroll_friendly = 1 + + announce_span = "green" + announce_text = "Alien changelings have infiltrated the crew!\n\ + Changelings: Accomplish the objectives assigned to you.\n\ + Crew: Root out and eliminate the changeling menace." + + var/const/changeling_amount = 4 //hard limit on changelings if scaling is turned off + var/list/changelings = list() + +/datum/game_mode/changeling/pre_setup() + + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/num_changelings = 1 + + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + if(csc) + num_changelings = max(1, min(round(num_players() / (csc * 2)) + 2, round(num_players() / csc))) + else + num_changelings = max(1, min(num_players(), changeling_amount)) + + if(antag_candidates.len>0) + for(var/i = 0, i < num_changelings, i++) + if(!antag_candidates.len) + break + var/datum/mind/changeling = antag_pick(antag_candidates) + antag_candidates -= changeling + changelings += changeling + changeling.special_role = ROLE_CHANGELING + changeling.restricted_roles = restricted_jobs + GLOB.pre_setup_antags += changeling + return TRUE + else + setup_error = "Not enough changeling candidates" + return FALSE + +/datum/game_mode/changeling/post_setup() + //Decide if it's ok for the lings to have a team objective + //And then set it up to be handed out in forge_changeling_objectives + var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) + var/list/possible_team_objectives = list() + for(var/T in team_objectives) + var/datum/objective/changeling_team_objective/CTO = T + + if(changelings.len >= initial(CTO.min_lings)) + possible_team_objectives += T + + if(possible_team_objectives.len && prob(20*changelings.len)) + GLOB.changeling_team_objective_type = pick(possible_team_objectives) + + for(var/datum/mind/changeling in changelings) + log_game("[key_name(changeling)] has been selected as a changeling") + var/datum/antagonist/changeling/new_antag = new() + new_antag.team_mode = TRUE + changeling.add_antag_datum(new_antag) + GLOB.pre_setup_antags -= changeling + ..() + +/datum/game_mode/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + var/changelingcap = min(round(GLOB.joined_player_list.len / (csc * 2)) + 2, round(GLOB.joined_player_list.len / csc)) + if(changelings.len >= changelingcap) //Caps number of latejoin antagonists + return + if(changelings.len <= (changelingcap - 2) || prob(100 - (csc * 2))) + if(ROLE_CHANGELING in character.client.prefs.be_special) + if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changeling() + changelings += character.mind + +/datum/game_mode/changeling/generate_report() + return "The Gorlex Marauders have announced the successful raid and destruction of Central Command containment ship #S-[rand(1111, 9999)]. This ship housed only a single prisoner - \ + codenamed \"Thing\", and it was highly adaptive and extremely dangerous. We have reason to believe that the Thing has allied with the Syndicate, and you should note that likelihood \ + of the Thing being sent to a station in this sector is highly likely. It may be in the guise of any crew member. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." + +/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) + var/datum/dna/chosen_dna = chosen_prof.dna + user.real_name = chosen_prof.name + user.underwear = chosen_prof.underwear + user.undershirt = chosen_prof.undershirt + user.socks = chosen_prof.socks + + chosen_dna.transfer_identity(user, 1) + user.updateappearance(mutcolor_update=1) + user.update_body() + user.domutcheck() + + //vars hackery. not pretty, but better than the alternative. + for(var/slot in GLOB.slots) + if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items + qdel(user.vars[slot]) + continue + + if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) + continue + + var/obj/item/C + var/equip = 0 + if(!user.vars[slot]) + var/thetype = GLOB.slot2type[slot] + equip = 1 + C = new thetype(user) + + else if(istype(user.vars[slot], GLOB.slot2type[slot])) + C = user.vars[slot] + + C.appearance = chosen_prof.appearance_list[slot] + C.name = chosen_prof.name_list[slot] + C.flags_cover = chosen_prof.flags_cover_list[slot] + C.lefthand_file = chosen_prof.lefthand_file_list[slot] + C.righthand_file = chosen_prof.righthand_file_list[slot] + C.inhand_icon_state = chosen_prof.inhand_icon_state_list[slot] + C.worn_icon = chosen_prof.worn_icon_list[slot] + C.worn_icon_state = chosen_prof.worn_icon_state_list[slot] + if(equip) + user.equip_to_slot_or_del(C, GLOB.slot2slot[slot]) + + user.regenerate_icons() diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm index 745883428b3..e722505c2d3 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -1,88 +1,88 @@ -/datum/game_mode/traitor/changeling - name = "traitor+changeling" - config_tag = "traitorchan" - report_type = "traitorchan" - false_report_weight = 10 - traitors_possible = 3 //hard limit on traitors if scaling is turned off - restricted_jobs = list("Prisoner","AI", "Cyborg") - required_players = 25 - required_enemies = 1 // how many of each type are required - recommended_enemies = 3 - reroll_friendly = 1 - announce_span = "Traitors and Changelings" - announce_text = "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!" - - var/list/possible_changelings = list() - var/list/changelings = list() - var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off - -/datum/game_mode/traitor/changeling/can_start() - if(!..()) - return 0 - possible_changelings = get_players_for_role(ROLE_CHANGELING) - if(possible_changelings.len < required_enemies) - return 0 - return 1 - -/datum/game_mode/traitor/changeling/pre_setup() - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) - - var/num_changelings = 1 - - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - if(csc) - num_changelings = max(1, min(round(num_players() / (csc * 4)) + 2, round(num_players() / (csc * 2)))) - else - num_changelings = max(1, min(num_players(), changeling_amount/2)) - - if(possible_changelings.len>0) - for(var/j = 0, j < num_changelings, j++) - if(!possible_changelings.len) - break - var/datum/mind/changeling = antag_pick(possible_changelings) - antag_candidates -= changeling - possible_changelings -= changeling - changeling.special_role = ROLE_CHANGELING - changelings += changeling - changeling.restricted_roles = restricted_jobs - . = ..() - if(.) //To ensure the game mode is going ahead - for(var/antag in changelings) - GLOB.pre_setup_antags += antag - return - else - return FALSE - -/datum/game_mode/traitor/changeling/post_setup() - for(var/datum/mind/changeling in changelings) - changeling.add_antag_datum(/datum/antagonist/changeling) - GLOB.pre_setup_antags -= changeling - return ..() - -/datum/game_mode/traitor/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - var/changelingcap = min( round(GLOB.joined_player_list.len / (csc * 4)) + 2, round(GLOB.joined_player_list.len / (csc * 2))) - if(changelings.len >= changelingcap) //Caps number of latejoin antagonists - ..() - return - if(changelings.len <= (changelingcap - 2) || prob(100 / (csc * 4))) - if(ROLE_CHANGELING in character.client.prefs.be_special) - if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changeling() - changelings += character.mind - if(QDELETED(character)) - return - ..() - -/datum/game_mode/traitor/changeling/generate_report() - return "The Syndicate has started some experimental research regarding humanoid shapeshifting. There are rumors that this technology will be field tested on a Nanotrasen station \ - for infiltration purposes. Be advised that support personel may also be deployed to defend these shapeshifters. Trust nobody - suspect everybody. Do not announce this to the crew, \ - as paranoia may spread and inhibit workplace efficiency." +/datum/game_mode/traitor/changeling + name = "traitor+changeling" + config_tag = "traitorchan" + report_type = "traitorchan" + false_report_weight = 10 + traitors_possible = 3 //hard limit on traitors if scaling is turned off + restricted_jobs = list("Prisoner","AI", "Cyborg") + required_players = 25 + required_enemies = 1 // how many of each type are required + recommended_enemies = 3 + reroll_friendly = 1 + announce_span = "Traitors and Changelings" + announce_text = "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!" + + var/list/possible_changelings = list() + var/list/changelings = list() + var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off + +/datum/game_mode/traitor/changeling/can_start() + if(!..()) + return 0 + possible_changelings = get_players_for_role(ROLE_CHANGELING) + if(possible_changelings.len < required_enemies) + return 0 + return 1 + +/datum/game_mode/traitor/changeling/pre_setup() + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) + + var/num_changelings = 1 + + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + if(csc) + num_changelings = max(1, min(round(num_players() / (csc * 4)) + 2, round(num_players() / (csc * 2)))) + else + num_changelings = max(1, min(num_players(), changeling_amount/2)) + + if(possible_changelings.len>0) + for(var/j = 0, j < num_changelings, j++) + if(!possible_changelings.len) + break + var/datum/mind/changeling = antag_pick(possible_changelings) + antag_candidates -= changeling + possible_changelings -= changeling + changeling.special_role = ROLE_CHANGELING + changelings += changeling + changeling.restricted_roles = restricted_jobs + . = ..() + if(.) //To ensure the game mode is going ahead + for(var/antag in changelings) + GLOB.pre_setup_antags += antag + return + else + return FALSE + +/datum/game_mode/traitor/changeling/post_setup() + for(var/datum/mind/changeling in changelings) + changeling.add_antag_datum(/datum/antagonist/changeling) + GLOB.pre_setup_antags -= changeling + return ..() + +/datum/game_mode/traitor/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + var/changelingcap = min( round(GLOB.joined_player_list.len / (csc * 4)) + 2, round(GLOB.joined_player_list.len / (csc * 2))) + if(changelings.len >= changelingcap) //Caps number of latejoin antagonists + ..() + return + if(changelings.len <= (changelingcap - 2) || prob(100 / (csc * 4))) + if(ROLE_CHANGELING in character.client.prefs.be_special) + if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changeling() + changelings += character.mind + if(QDELETED(character)) + return + ..() + +/datum/game_mode/traitor/changeling/generate_report() + return "The Syndicate has started some experimental research regarding humanoid shapeshifting. There are rumors that this technology will be field tested on a Nanotrasen station \ + for infiltration purposes. Be advised that support personel may also be deployed to defend these shapeshifters. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." diff --git a/code/game/gamemodes/events.dm b/code/game/gamemodes/events.dm index ef781c5ee36..8a7181aaa3d 100644 --- a/code/game/gamemodes/events.dm +++ b/code/game/gamemodes/events.dm @@ -1,66 +1,66 @@ -/proc/power_failure() - priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg') - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(istype(get_area(S), /area/ai_monitored/turret_protected) || !is_station_level(S.z)) - continue - S.charge = 0 - S.output_level = 0 - S.output_attempt = FALSE - S.update_icon() - S.power_change() - - for(var/area/A in GLOB.the_station_areas) - if(!A.requires_power || A.always_unpowered ) - continue - if(GLOB.typecache_powerfailure_safe_areas[A.type]) - continue - - A.power_light = FALSE - A.power_equip = FALSE - A.power_environ = FALSE - A.power_change() - - for(var/obj/machinery/power/apc/C in GLOB.apcs_list) - if(C.cell && is_station_level(C.z)) - var/area/A = C.area - if(GLOB.typecache_powerfailure_safe_areas[A.type]) - continue - - C.cell.charge = 0 - -/proc/power_restore() - - priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') - for(var/obj/machinery/power/apc/C in GLOB.machines) - if(C.cell && is_station_level(C.z)) - C.cell.charge = C.cell.maxcharge - C.failure_timer = 0 - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(!is_station_level(S.z)) - continue - S.charge = S.capacity - S.output_level = S.output_level_max - S.output_attempt = TRUE - S.update_icon() - S.power_change() - for(var/area/A in GLOB.the_station_areas) - if(!A.requires_power || A.always_unpowered) - continue - if(!istype(A, /area/shuttle)) - A.power_light = TRUE - A.power_equip = TRUE - A.power_environ = TRUE - A.power_change() - -/proc/power_restore_quick() - - priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(!is_station_level(S.z)) - continue - S.charge = S.capacity - S.output_level = S.output_level_max - S.output_attempt = TRUE - S.update_icon() - S.power_change() - +/proc/power_failure() + priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg') + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(istype(get_area(S), /area/ai_monitored/turret_protected) || !is_station_level(S.z)) + continue + S.charge = 0 + S.output_level = 0 + S.output_attempt = FALSE + S.update_icon() + S.power_change() + + for(var/area/A in GLOB.the_station_areas) + if(!A.requires_power || A.always_unpowered ) + continue + if(GLOB.typecache_powerfailure_safe_areas[A.type]) + continue + + A.power_light = FALSE + A.power_equip = FALSE + A.power_environ = FALSE + A.power_change() + + for(var/obj/machinery/power/apc/C in GLOB.apcs_list) + if(C.cell && is_station_level(C.z)) + var/area/A = C.area + if(GLOB.typecache_powerfailure_safe_areas[A.type]) + continue + + C.cell.charge = 0 + +/proc/power_restore() + + priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') + for(var/obj/machinery/power/apc/C in GLOB.machines) + if(C.cell && is_station_level(C.z)) + C.cell.charge = C.cell.maxcharge + C.failure_timer = 0 + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(!is_station_level(S.z)) + continue + S.charge = S.capacity + S.output_level = S.output_level_max + S.output_attempt = TRUE + S.update_icon() + S.power_change() + for(var/area/A in GLOB.the_station_areas) + if(!A.requires_power || A.always_unpowered) + continue + if(!istype(A, /area/shuttle)) + A.power_light = TRUE + A.power_equip = TRUE + A.power_environ = TRUE + A.power_change() + +/proc/power_restore_quick() + + priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(!is_station_level(S.z)) + continue + S.charge = S.capacity + S.output_level = S.output_level_max + S.output_attempt = TRUE + S.update_icon() + S.power_change() + diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm index 61a91c79bd0..d1b90b50b54 100644 --- a/code/game/gamemodes/extended/extended.dm +++ b/code/game/gamemodes/extended/extended.dm @@ -1,29 +1,29 @@ -/datum/game_mode/extended - name = "secret extended" - config_tag = "secret_extended" - report_type = "extended" - false_report_weight = 5 - required_players = 0 - - announce_span = "notice" - announce_text = "Just have fun and enjoy the game!" - -/datum/game_mode/extended/pre_setup() - return 1 - -/datum/game_mode/extended/generate_report() - return "The transmission mostly failed to mention your sector. It is possible that there is nothing in the Syndicate that could threaten your station during this shift." - -/datum/game_mode/extended/announced - name = "extended" - config_tag = "extended" - false_report_weight = 0 - -/datum/game_mode/extended/announced/generate_station_goals() - for(var/T in subtypesof(/datum/station_goal)) - var/datum/station_goal/G = new T - station_goals += G - G.on_report() - -/datum/game_mode/extended/announced/send_intercept(report = 0) - priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", 'sound/ai/commandreport.ogg') +/datum/game_mode/extended + name = "secret extended" + config_tag = "secret_extended" + report_type = "extended" + false_report_weight = 5 + required_players = 0 + + announce_span = "notice" + announce_text = "Just have fun and enjoy the game!" + +/datum/game_mode/extended/pre_setup() + return 1 + +/datum/game_mode/extended/generate_report() + return "The transmission mostly failed to mention your sector. It is possible that there is nothing in the Syndicate that could threaten your station during this shift." + +/datum/game_mode/extended/announced + name = "extended" + config_tag = "extended" + false_report_weight = 0 + +/datum/game_mode/extended/announced/generate_station_goals() + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = new T + station_goals += G + G.on_report() + +/datum/game_mode/extended/announced/send_intercept(report = 0) + priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", 'sound/ai/commandreport.ogg') diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index dd8ef04546a..e0852a7e9c8 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -1,602 +1,602 @@ - - -/* - * GAMEMODES (by Rastaf0) - * - * In the new mode system all special roles are fully supported. - * You can have proper wizards/traitors/changelings/cultists during any mode. - * Only two things really depends on gamemode: - * 1. Starting roles, equipment and preparations - * 2. Conditions of finishing the round. - * - */ - - -/datum/game_mode - var/name = "invalid" - var/config_tag = null - var/votable = 1 - var/probability = 0 - var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report? - var/report_type = "invalid" //gamemodes with the same report type will not show up in the command report together. - var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm - var/nuke_off_station = 0 //Used for tracking where the nuke hit - var/round_ends_with_antag_death = 0 //flags the "one verse the station" antags as such - var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here - var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist - var/list/protected_jobs = list() // Jobs that can't be traitors because - var/list/required_jobs = list() // alternative required job groups eg list(list(cap=1),list(hos=1,sec=2)) translates to one captain OR one hos and two secmans - var/required_players = 0 - var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations - var/required_enemies = 0 - var/recommended_enemies = 0 - var/antag_flag = null //preferences flag such as BE_WIZARD that need to be turned on for players to be antag - var/mob/living/living_antag_player = null - var/datum/game_mode/replacementmode = null - var/round_converted = 0 //0: round not converted, 1: round going to convert, 2: round converted - var/reroll_friendly //During mode conversion only these are in the running - var/continuous_sanity_checked //Catches some cases where config options could be used to suggest that modes without antagonists should end when all antagonists die - var/enemy_minimum_age = 7 //How many days must players have been playing before they can play this antagonist - - var/announce_span = "warning" //The gamemode's name will be in this span during announcement. - var/announce_text = "This gamemode forgot to set a descriptive text! Uh oh!" //Used to describe a gamemode when it's announced. - - var/const/waittime_l = 600 - var/const/waittime_h = 1800 // started at 1800 - - var/list/datum/station_goal/station_goals = list() - - var/allow_persistence_save = TRUE - - var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions. - var/setup_error //What stopepd setting up the mode. - - /// Associative list of current players, in order: living players, living antagonists, dead players and observers. - var/list/list/current_players = list(CURRENT_LIVING_PLAYERS = list(), CURRENT_LIVING_ANTAGS = list(), CURRENT_DEAD_PLAYERS = list(), CURRENT_OBSERVERS = list()) - - -/datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description. - to_chat(world, "The gamemode is: [name]!") - to_chat(world, "[announce_text]") - - -///Checks to see if the game can be setup and ran with the current number of players or whatnot. -/datum/game_mode/proc/can_start() - var/playerC = 0 - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY) - playerC++ - if(!GLOB.Debug2) - if(playerC < required_players || (maximum_players >= 0 && playerC > maximum_players)) - return 0 - antag_candidates = get_players_for_role(antag_flag) - if(!GLOB.Debug2) - if(antag_candidates.len < required_enemies) - return 0 - return 1 - else - message_admins("DEBUG: GAME STARTING WITHOUT PLAYER NUMBER CHECKS, THIS WILL PROBABLY BREAK SHIT.") - return 1 - - -///Attempts to select players for special roles the mode might have. -/datum/game_mode/proc/pre_setup() - return 1 - -///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things -/datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report. - if(!report) - report = !CONFIG_GET(flag/no_intercept_report) - addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) - - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) - var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) - if(delay) - delay = (delay SECONDS) - else - delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. - addtimer(CALLBACK(GLOBAL_PROC, .proc/reopen_roundstart_suicide_roles), delay) - - if(SSdbcore.Connect()) - var/list/to_set = list() - var/arguments = list() - if(SSticker.mode) - to_set += "game_mode = :game_mode" - arguments["game_mode"] = SSticker.mode - if(GLOB.revdata.originmastercommit) - to_set += "commit_hash = :commit_hash" - arguments["commit_hash"] = GLOB.revdata.originmastercommit - if(to_set.len) - arguments["round_id"] = GLOB.round_id - var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id", - arguments - ) - query_round_game_mode.Execute() - qdel(query_round_game_mode) - if(report) - addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h)) - generate_station_goals() - gamemode_ready = TRUE - return 1 - - -///Handles late-join antag assignments -/datum/game_mode/proc/make_antag_chance(mob/living/carbon/human/character) - if(replacementmode && round_converted == 2) - replacementmode.make_antag_chance(character) - return - - -///Allows rounds to basically be "rerolled" should the initial premise fall through. Also known as mulligan antags. -/datum/game_mode/proc/convert_roundtype() - set waitfor = FALSE - var/list/living_crew = list() - - for(var/i in GLOB.player_list) - var/mob/Player = i - if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player)) - living_crew += Player - var/malc = CONFIG_GET(number/midround_antag_life_check) - if(living_crew.len / GLOB.joined_player_list.len <= malc) //If a lot of the player base died, we start fresh - message_admins("Convert_roundtype failed due to too many dead people. Limit is [malc * 100]% living crew") - return null - - var/list/datum/game_mode/runnable_modes = config.get_runnable_midround_modes(living_crew.len) - var/list/datum/game_mode/usable_modes = list() - for(var/datum/game_mode/G in runnable_modes) - if(G.reroll_friendly && living_crew.len >= G.required_players) - usable_modes += G - else - qdel(G) - - if(!usable_modes.len) - message_admins("Convert_roundtype failed due to no valid modes to convert to. Please report this error to the Coders.") - return null - - replacementmode = pickweight(usable_modes) - - switch(SSshuttle.emergency.mode) //Rounds on the verge of ending don't get new antags, they just run out - if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE) - return 1 - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergencyCallTime)*0.5) - return 1 - - var/matc = CONFIG_GET(number/midround_antag_time_check) - if(world.time >= (matc * 600)) - message_admins("Convert_roundtype failed due to round length. Limit is [matc] minutes.") - return null - - var/list/antag_candidates = list() - - for(var/mob/living/carbon/human/H in living_crew) - if(H.client && H.client.prefs.allow_midround_antag && !is_centcom_level(H.z)) - antag_candidates += H - - if(!antag_candidates) - message_admins("Convert_roundtype failed due to no antag candidates.") - return null - - antag_candidates = shuffle(antag_candidates) - - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - replacementmode.restricted_jobs += replacementmode.protected_jobs - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - replacementmode.restricted_jobs += "Assistant" - - message_admins("The roundtype will be converted. If you have other plans for the station or feel the station is too messed up to inhabit stop the creation of antags or end the round now.") - log_game("Roundtype converted to [replacementmode.name]") - - . = 1 - - sleep(rand(600,1800)) - if(!SSticker.IsRoundInProgress()) - message_admins("Roundtype conversion cancelled, the game appears to have finished!") - round_converted = 0 - return - //somewhere between 1 and 3 minutes from now - if(!CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag]) - round_converted = 0 - return 1 - for(var/mob/living/carbon/human/H in antag_candidates) - if(H.client) - replacementmode.make_antag_chance(H) - replacementmode.gamemode_ready = TRUE //Awful but we're not doing standard setup here. - round_converted = 2 - message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --") - - -///Called by the gameSSticker -/datum/game_mode/process() - return 0 - -//For things that do not die easily -/datum/game_mode/proc/are_special_antags_dead() - return TRUE - - -/datum/game_mode/proc/check_finished(force_ending) //to be called by SSticker - if(!SSticker.setup_done || !gamemode_ready) - return FALSE - if(replacementmode && round_converted == 2) - return replacementmode.check_finished() - if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)) - return TRUE - if(station_was_nuked) - return TRUE - var/list/continuous = CONFIG_GET(keyed_list/continuous) - var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) - if(!round_converted && (!continuous[config_tag] || (continuous[config_tag] && midround_antag[config_tag]))) //Non-continuous or continous with replacement antags - if(!continuous_sanity_checked) //make sure we have antags to be checking in the first place - for(var/mob/Player in GLOB.mob_list) - if(Player.mind) - if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) - continuous_sanity_checked = 1 - return 0 - if(!continuous_sanity_checked) - message_admins("The roundtype ([config_tag]) has no antagonists, continuous round has been defaulted to on and midround_antag has been defaulted to off.") - continuous[config_tag] = TRUE - midround_antag[config_tag] = FALSE - SSshuttle.clearHostileEnvironment(src) - return 0 - - - if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player) && (living_antag_player.mind.special_role || LAZYLEN(living_antag_player.mind.antag_datums))) - return 0 //A resource saver: once we find someone who has to die for all antags to be dead, we can just keep checking them, cycling over everyone only when we lose our mark. - - for(var/mob/Player in GLOB.alive_mob_list) - if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) &&!isbrain(Player) && Player.client && (Player.mind.special_role || LAZYLEN(Player.mind.antag_datums))) //Someone's still antagging but is their antagonist datum important enough to skip mulligan? - for(var/datum/antagonist/antag_types in Player.mind.antag_datums) - if(antag_types.prevent_roundtype_conversion) - living_antag_player = Player //they were an important antag, they're our new mark - return 0 - - if(!are_special_antags_dead()) - return FALSE - - if(!continuous[config_tag] || force_ending) - return 1 - - else - round_converted = convert_roundtype() - if(!round_converted) - if(round_ends_with_antag_death) - return 1 - else - midround_antag[config_tag] = 0 - return 0 - - return 0 - - -/datum/game_mode/proc/send_intercept() - var/intercepttext = "Central Command Status Summary
    " - intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \ - likely threats to appear in your sector." - var/list/report_weights = config.mode_false_report_weight.Copy() - report_weights[report_type] = 0 //Prevent the current mode from being falsely selected. - var/list/reports = list() - var/Count = 0 //To compensate for missing correct report - if(prob(65)) // 65% chance the actual mode will appear on the list - reports += config.mode_reports[report_type] - Count++ - for(var/i in Count to rand(3,5)) //Between three and five wrong entries on the list. - var/false_report_type = pickweightAllowZero(report_weights) - report_weights[false_report_type] = 0 //Make it so the same false report won't be selected twice - reports += config.mode_reports[false_report_type] - - reports = shuffle(reports) //Randomize the order, so the real one is at a random position. - - for(var/report in reports) - intercepttext += "
    " - intercepttext += report - - if(station_goals.len) - intercepttext += "
    Special Orders for [station_name()]:" - for(var/datum/station_goal/G in station_goals) - G.on_report() - intercepttext += G.get_report() - - print_command_report(intercepttext, "Central Command Status Summary", announce=FALSE) - priority_announce("A summary has been copied and printed to all communications consoles.", "Enemy communication intercepted. Security level elevated.", 'sound/ai/intercept.ogg') - if(GLOB.security_level < SEC_LEVEL_BLUE) - set_security_level(SEC_LEVEL_BLUE) - - -// This is a frequency selection system. You may imagine it like a raffle where each player can have some number of tickets. The more tickets you have the more likely you are to -// "win". The default is 100 tickets. If no players use any extra tickets (earned with the antagonist rep system) calling this function should be equivalent to calling the normal -// pick() function. By default you may use up to 100 extra tickets per roll, meaning at maximum a player may double their chances compared to a player who has no extra tickets. -// -// The odds of being picked are simply (your_tickets / total_tickets). Suppose you have one player using fifty (50) extra tickets, and one who uses no extra: -// Player A: 150 tickets -// Player B: 100 tickets -// Total: 250 tickets -// -// The odds become: -// Player A: 150 / 250 = 0.6 = 60% -// Player B: 100 / 250 = 0.4 = 40% -/datum/game_mode/proc/antag_pick(list/datum/candidates) - if(!CONFIG_GET(flag/use_antag_rep)) // || candidates.len <= 1) - return pick(candidates) - - // Tickets start at 100 - var/DEFAULT_ANTAG_TICKETS = CONFIG_GET(number/default_antag_tickets) - - // You may use up to 100 extra tickets (double your odds) - var/MAX_TICKETS_PER_ROLL = CONFIG_GET(number/max_tickets_per_roll) - - - var/total_tickets = 0 - - MAX_TICKETS_PER_ROLL += DEFAULT_ANTAG_TICKETS - - var/p_ckey - var/p_rep - - for(var/datum/mind/mind in candidates) - p_ckey = ckey(mind.key) - total_tickets += min(SSpersistence.antag_rep[p_ckey] + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) - - var/antag_select = rand(1,total_tickets) - var/current = 1 - - for(var/datum/mind/mind in candidates) - p_ckey = ckey(mind.key) - p_rep = SSpersistence.antag_rep[p_ckey] - - var/previous = current - var/spend = min(p_rep + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) - current += spend - - if(antag_select >= previous && antag_select <= (current-1)) - SSpersistence.antag_rep_change[p_ckey] = -(spend - DEFAULT_ANTAG_TICKETS) - -// WARNING("AR_DEBUG: Player [mind.key] won spending [spend] tickets from starting value [SSpersistence.antag_rep[p_ckey]]") - - return mind - - WARNING("Something has gone terribly wrong. /datum/game_mode/proc/antag_pick failed to select a candidate. Falling back to pick()") - return pick(candidates) - -/datum/game_mode/proc/get_players_for_role(role) - var/list/players = list() - var/list/candidates = list() - var/list/drafted = list() - var/datum/mind/applicant = null - - // Ultimate randomizing code right here - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences()) - players += player - - // Shuffling, the players list is now ping-independent!!! - // Goodbye antag dante - players = shuffle(players) - - for(var/mob/dead/new_player/player in players) - if(player.client && player.ready == PLAYER_READY_TO_PLAY) - if(role in player.client.prefs.be_special) - if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) - if(age_check(player.client)) //Must be older than the minimum age - candidates += player.mind // Get a list of all the people who want to be the antagonist for this round - - if(restricted_jobs) - for(var/datum/mind/player in candidates) - for(var/job in restricted_jobs) // Remove people who want to be antagonist but have a job already that precludes it - if(player.assigned_role == job) - candidates -= player - - if(candidates.len < recommended_enemies) - for(var/mob/dead/new_player/player in players) - if(player.client && player.ready == PLAYER_READY_TO_PLAY) - if(!(role in player.client.prefs.be_special)) // We don't have enough people who want to be antagonist, make a separate list of people who don't want to be one - if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) - drafted += player.mind - - if(restricted_jobs) - for(var/datum/mind/player in drafted) // Remove people who can't be an antagonist - for(var/job in restricted_jobs) - if(player.assigned_role == job) - drafted -= player - - drafted = shuffle(drafted) // Will hopefully increase randomness, Donkie - - while(candidates.len < recommended_enemies) // Pick randomlly just the number of people we need and add them to our list of candidates - if(drafted.len > 0) - applicant = pick(drafted) - if(applicant) - candidates += applicant - drafted.Remove(applicant) - - else // Not enough scrubs, ABORT ABORT ABORT - break - - return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies - // recommended_enemies if the number of people with that role set to yes is less than recomended_enemies, - // Less if there are not enough valid players in the game entirely to make recommended_enemies. - - - -/datum/game_mode/proc/num_players() - . = 0 - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/P = i - if(P.ready == PLAYER_READY_TO_PLAY) - . ++ - -/proc/reopen_roundstart_suicide_roles() - var/list/valid_positions = list() - valid_positions += GLOB.engineering_positions - valid_positions += GLOB.medical_positions - valid_positions += GLOB.science_positions - valid_positions += GLOB.supply_positions - valid_positions += GLOB.service_positions - valid_positions += GLOB.security_positions - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions)) - valid_positions += GLOB.command_positions //add any remaining command positions - else - valid_positions -= GLOB.command_positions //remove all command positions that were added from their respective department positions lists. - - var/list/reopened_jobs = list() - for(var/X in GLOB.suicided_mob_list) - if(!isliving(X)) - continue - var/mob/living/L = X - if(L.job in valid_positions) - var/datum/job/J = SSjob.GetJob(L.job) - if(!J) - continue - J.current_positions = max(J.current_positions-1, 0) - reopened_jobs += L.job - - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report)) - if(reopened_jobs.len) - var/reopened_job_report_positions - for(var/dead_dudes_job in reopened_jobs) - reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]" - - var/suicide_command_report = "Central Command Human Resources Board
    \ - Notice of Personnel Change

    \ - To personnel management staff aboard [station_name()]:

    \ - Our medical staff have detected a series of anomalies in the vital sensors \ - of some of the staff aboard your station.

    \ - Further investigation into the situation on our end resulted in us discovering \ - a series of rather... unforturnate decisions that were made on the part of said staff.

    \ - As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members \ - who have decided not to partake in our research. We will be forwarding their cases to our employment review board \ - to determine their eligibility for continued service with the company (and of course the \ - continued storage of cloning records within the central medical backup server.)

    \ - The following positions have been reopened on our behalf:

    \ - [reopened_job_report_positions]
    " - - print_command_report(suicide_command_report, "Central Command Personnel Update") - -////////////////////////// -//Reports player logouts// -////////////////////////// -/proc/display_roundstart_logout_report() - var/list/msg = list("Roundstart logout report\n\n") - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - var/mob/living/carbon/C = L - if (istype(C) && !C.last_mind) - continue // never had a client - - if(L.ckey && !GLOB.directory[L.ckey]) - msg += "[L.name] ([L.key]), the [L.job] (Disconnected)\n" - - - if(L.ckey && L.client) - var/failed = FALSE - if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) - msg += "[L.name] ([L.key]), the [L.job] (Connected, Inactive)\n" - failed = TRUE //AFK client - if(!failed && L.stat) - if(L.suiciding) //Suicider - msg += "[L.name] ([L.key]), the [L.job] (Suicide)\n" - failed = TRUE //Disconnected client - if(!failed && L.stat == UNCONSCIOUS) - msg += "[L.name] ([L.key]), the [L.job] (Dying)\n" - failed = TRUE //Unconscious - if(!failed && L.stat == DEAD) - msg += "[L.name] ([L.key]), the [L.job] (Dead)\n" - failed = TRUE //Dead - - var/p_ckey = L.client.ckey -// WARNING("AR_DEBUG: [p_ckey]: failed - [failed], antag_rep_change: [SSpersistence.antag_rep_change[p_ckey]]") - - // people who died or left should not gain any reputation - // people who rolled antagonist still lose it - if(failed && SSpersistence.antag_rep_change[p_ckey] > 0) -// WARNING("AR_DEBUG: Zeroed [p_ckey]'s antag_rep_change") - SSpersistence.antag_rep_change[p_ckey] = 0 - - continue //Happy connected client - for(var/mob/dead/observer/D in GLOB.dead_mob_list) - if(D.mind && D.mind.current == L) - if(L.stat == DEAD) - if(L.suiciding) //Suicider - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Suicide)\n" - continue //Disconnected client - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" - continue //Dead mob, ghost abandoned - else - if(D.can_reenter_corpse) - continue //Adminghost, or cult/wizard ghost - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Ghosted)\n" - continue //Ghosted while alive - - - for (var/C in GLOB.admins) - to_chat(C, msg.Join()) - -//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 -/datum/game_mode/proc/age_check(client/C) - if(get_remaining_days(C) == 0) - return 1 //Available in 0 days = available right now = player is old enough to play. - return 0 - - -/datum/game_mode/proc/get_remaining_days(client/C) - if(!C) - return 0 - if(!CONFIG_GET(flag/use_age_restriction_for_jobs)) - return 0 - if(!isnum(C.player_age)) - return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced - if(!isnum(enemy_minimum_age)) - return 0 - - return max(0, enemy_minimum_age - C.player_age) - -/datum/game_mode/proc/remove_antag_for_borging(datum/mind/newborgie) - SSticker.mode.remove_cultist(newborgie, 0, 0) - var/datum/antagonist/rev/rev = newborgie.has_antag_datum(/datum/antagonist/rev) - if(rev) - rev.remove_revolutionary(TRUE) - -/datum/game_mode/proc/generate_station_goals() - var/list/possible = list() - for(var/T in subtypesof(/datum/station_goal)) - var/datum/station_goal/G = T - if(config_tag in initial(G.gamemode_blacklist)) - continue - possible += T - var/goal_weights = 0 - while(possible.len && goal_weights < STATION_GOAL_BUDGET) - var/datum/station_goal/picked = pick_n_take(possible) - goal_weights += initial(picked.weight) - station_goals += new picked - - -/datum/game_mode/proc/generate_report() //Generates a small text blurb for the gamemode in centcom report - return "Gamemode report for [name] not set. Contact a coder." - -//By default nuke just ends the round -/datum/game_mode/proc/OnNukeExplosion(off_station) - nuke_off_station = off_station - if(off_station < 2) - station_was_nuked = TRUE //Will end the round on next check. - -//Additional report section in roundend report -/datum/game_mode/proc/special_report() - return - -//Set result and news report here -/datum/game_mode/proc/set_round_result() - SSticker.mode_result = "undefined" - if(station_was_nuked) - SSticker.news_report = STATION_DESTROYED_NUKE - if(EMERGENCY_ESCAPED_OR_ENDGAMED) - SSticker.news_report = STATION_EVACUATED - if(SSshuttle.emergency.is_hijacked()) - SSticker.news_report = SHUTTLE_HIJACK - -/// Mode specific admin panel. -/datum/game_mode/proc/admin_panel() - return + + +/* + * GAMEMODES (by Rastaf0) + * + * In the new mode system all special roles are fully supported. + * You can have proper wizards/traitors/changelings/cultists during any mode. + * Only two things really depends on gamemode: + * 1. Starting roles, equipment and preparations + * 2. Conditions of finishing the round. + * + */ + + +/datum/game_mode + var/name = "invalid" + var/config_tag = null + var/votable = 1 + var/probability = 0 + var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report? + var/report_type = "invalid" //gamemodes with the same report type will not show up in the command report together. + var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm + var/nuke_off_station = 0 //Used for tracking where the nuke hit + var/round_ends_with_antag_death = 0 //flags the "one verse the station" antags as such + var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here + var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist + var/list/protected_jobs = list() // Jobs that can't be traitors because + var/list/required_jobs = list() // alternative required job groups eg list(list(cap=1),list(hos=1,sec=2)) translates to one captain OR one hos and two secmans + var/required_players = 0 + var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations + var/required_enemies = 0 + var/recommended_enemies = 0 + var/antag_flag = null //preferences flag such as BE_WIZARD that need to be turned on for players to be antag + var/mob/living/living_antag_player = null + var/datum/game_mode/replacementmode = null + var/round_converted = 0 //0: round not converted, 1: round going to convert, 2: round converted + var/reroll_friendly //During mode conversion only these are in the running + var/continuous_sanity_checked //Catches some cases where config options could be used to suggest that modes without antagonists should end when all antagonists die + var/enemy_minimum_age = 7 //How many days must players have been playing before they can play this antagonist + + var/announce_span = "warning" //The gamemode's name will be in this span during announcement. + var/announce_text = "This gamemode forgot to set a descriptive text! Uh oh!" //Used to describe a gamemode when it's announced. + + var/const/waittime_l = 600 + var/const/waittime_h = 1800 // started at 1800 + + var/list/datum/station_goal/station_goals = list() + + var/allow_persistence_save = TRUE + + var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions. + var/setup_error //What stopepd setting up the mode. + + /// Associative list of current players, in order: living players, living antagonists, dead players and observers. + var/list/list/current_players = list(CURRENT_LIVING_PLAYERS = list(), CURRENT_LIVING_ANTAGS = list(), CURRENT_DEAD_PLAYERS = list(), CURRENT_OBSERVERS = list()) + + +/datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description. + to_chat(world, "The gamemode is: [name]!") + to_chat(world, "[announce_text]") + + +///Checks to see if the game can be setup and ran with the current number of players or whatnot. +/datum/game_mode/proc/can_start() + var/playerC = 0 + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/player = i + if(player.ready == PLAYER_READY_TO_PLAY) + playerC++ + if(!GLOB.Debug2) + if(playerC < required_players || (maximum_players >= 0 && playerC > maximum_players)) + return 0 + antag_candidates = get_players_for_role(antag_flag) + if(!GLOB.Debug2) + if(antag_candidates.len < required_enemies) + return 0 + return 1 + else + message_admins("DEBUG: GAME STARTING WITHOUT PLAYER NUMBER CHECKS, THIS WILL PROBABLY BREAK SHIT.") + return 1 + + +///Attempts to select players for special roles the mode might have. +/datum/game_mode/proc/pre_setup() + return 1 + +///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things +/datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report. + if(!report) + report = !CONFIG_GET(flag/no_intercept_report) + addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) + + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) + var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) + if(delay) + delay = (delay SECONDS) + else + delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. + addtimer(CALLBACK(GLOBAL_PROC, .proc/reopen_roundstart_suicide_roles), delay) + + if(SSdbcore.Connect()) + var/list/to_set = list() + var/arguments = list() + if(SSticker.mode) + to_set += "game_mode = :game_mode" + arguments["game_mode"] = SSticker.mode + if(GLOB.revdata.originmastercommit) + to_set += "commit_hash = :commit_hash" + arguments["commit_hash"] = GLOB.revdata.originmastercommit + if(to_set.len) + arguments["round_id"] = GLOB.round_id + var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id", + arguments + ) + query_round_game_mode.Execute() + qdel(query_round_game_mode) + if(report) + addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h)) + generate_station_goals() + gamemode_ready = TRUE + return 1 + + +///Handles late-join antag assignments +/datum/game_mode/proc/make_antag_chance(mob/living/carbon/human/character) + if(replacementmode && round_converted == 2) + replacementmode.make_antag_chance(character) + return + + +///Allows rounds to basically be "rerolled" should the initial premise fall through. Also known as mulligan antags. +/datum/game_mode/proc/convert_roundtype() + set waitfor = FALSE + var/list/living_crew = list() + + for(var/i in GLOB.player_list) + var/mob/Player = i + if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player)) + living_crew += Player + var/malc = CONFIG_GET(number/midround_antag_life_check) + if(living_crew.len / GLOB.joined_player_list.len <= malc) //If a lot of the player base died, we start fresh + message_admins("Convert_roundtype failed due to too many dead people. Limit is [malc * 100]% living crew") + return null + + var/list/datum/game_mode/runnable_modes = config.get_runnable_midround_modes(living_crew.len) + var/list/datum/game_mode/usable_modes = list() + for(var/datum/game_mode/G in runnable_modes) + if(G.reroll_friendly && living_crew.len >= G.required_players) + usable_modes += G + else + qdel(G) + + if(!usable_modes.len) + message_admins("Convert_roundtype failed due to no valid modes to convert to. Please report this error to the Coders.") + return null + + replacementmode = pickweight(usable_modes) + + switch(SSshuttle.emergency.mode) //Rounds on the verge of ending don't get new antags, they just run out + if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE) + return 1 + if(SHUTTLE_CALL) + if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergencyCallTime)*0.5) + return 1 + + var/matc = CONFIG_GET(number/midround_antag_time_check) + if(world.time >= (matc * 600)) + message_admins("Convert_roundtype failed due to round length. Limit is [matc] minutes.") + return null + + var/list/antag_candidates = list() + + for(var/mob/living/carbon/human/H in living_crew) + if(H.client && H.client.prefs.allow_midround_antag && !is_centcom_level(H.z)) + antag_candidates += H + + if(!antag_candidates) + message_admins("Convert_roundtype failed due to no antag candidates.") + return null + + antag_candidates = shuffle(antag_candidates) + + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + replacementmode.restricted_jobs += replacementmode.protected_jobs + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + replacementmode.restricted_jobs += "Assistant" + + message_admins("The roundtype will be converted. If you have other plans for the station or feel the station is too messed up to inhabit stop the creation of antags or end the round now.") + log_game("Roundtype converted to [replacementmode.name]") + + . = 1 + + sleep(rand(600,1800)) + if(!SSticker.IsRoundInProgress()) + message_admins("Roundtype conversion cancelled, the game appears to have finished!") + round_converted = 0 + return + //somewhere between 1 and 3 minutes from now + if(!CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag]) + round_converted = 0 + return 1 + for(var/mob/living/carbon/human/H in antag_candidates) + if(H.client) + replacementmode.make_antag_chance(H) + replacementmode.gamemode_ready = TRUE //Awful but we're not doing standard setup here. + round_converted = 2 + message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --") + + +///Called by the gameSSticker +/datum/game_mode/process() + return 0 + +//For things that do not die easily +/datum/game_mode/proc/are_special_antags_dead() + return TRUE + + +/datum/game_mode/proc/check_finished(force_ending) //to be called by SSticker + if(!SSticker.setup_done || !gamemode_ready) + return FALSE + if(replacementmode && round_converted == 2) + return replacementmode.check_finished() + if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)) + return TRUE + if(station_was_nuked) + return TRUE + var/list/continuous = CONFIG_GET(keyed_list/continuous) + var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) + if(!round_converted && (!continuous[config_tag] || (continuous[config_tag] && midround_antag[config_tag]))) //Non-continuous or continous with replacement antags + if(!continuous_sanity_checked) //make sure we have antags to be checking in the first place + for(var/mob/Player in GLOB.mob_list) + if(Player.mind) + if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) + continuous_sanity_checked = 1 + return 0 + if(!continuous_sanity_checked) + message_admins("The roundtype ([config_tag]) has no antagonists, continuous round has been defaulted to on and midround_antag has been defaulted to off.") + continuous[config_tag] = TRUE + midround_antag[config_tag] = FALSE + SSshuttle.clearHostileEnvironment(src) + return 0 + + + if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player) && (living_antag_player.mind.special_role || LAZYLEN(living_antag_player.mind.antag_datums))) + return 0 //A resource saver: once we find someone who has to die for all antags to be dead, we can just keep checking them, cycling over everyone only when we lose our mark. + + for(var/mob/Player in GLOB.alive_mob_list) + if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) &&!isbrain(Player) && Player.client && (Player.mind.special_role || LAZYLEN(Player.mind.antag_datums))) //Someone's still antagging but is their antagonist datum important enough to skip mulligan? + for(var/datum/antagonist/antag_types in Player.mind.antag_datums) + if(antag_types.prevent_roundtype_conversion) + living_antag_player = Player //they were an important antag, they're our new mark + return 0 + + if(!are_special_antags_dead()) + return FALSE + + if(!continuous[config_tag] || force_ending) + return 1 + + else + round_converted = convert_roundtype() + if(!round_converted) + if(round_ends_with_antag_death) + return 1 + else + midround_antag[config_tag] = 0 + return 0 + + return 0 + + +/datum/game_mode/proc/send_intercept() + var/intercepttext = "Central Command Status Summary
    " + intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \ + likely threats to appear in your sector." + var/list/report_weights = config.mode_false_report_weight.Copy() + report_weights[report_type] = 0 //Prevent the current mode from being falsely selected. + var/list/reports = list() + var/Count = 0 //To compensate for missing correct report + if(prob(65)) // 65% chance the actual mode will appear on the list + reports += config.mode_reports[report_type] + Count++ + for(var/i in Count to rand(3,5)) //Between three and five wrong entries on the list. + var/false_report_type = pickweightAllowZero(report_weights) + report_weights[false_report_type] = 0 //Make it so the same false report won't be selected twice + reports += config.mode_reports[false_report_type] + + reports = shuffle(reports) //Randomize the order, so the real one is at a random position. + + for(var/report in reports) + intercepttext += "
    " + intercepttext += report + + if(station_goals.len) + intercepttext += "
    Special Orders for [station_name()]:" + for(var/datum/station_goal/G in station_goals) + G.on_report() + intercepttext += G.get_report() + + print_command_report(intercepttext, "Central Command Status Summary", announce=FALSE) + priority_announce("A summary has been copied and printed to all communications consoles.", "Enemy communication intercepted. Security level elevated.", 'sound/ai/intercept.ogg') + if(GLOB.security_level < SEC_LEVEL_BLUE) + set_security_level(SEC_LEVEL_BLUE) + + +// This is a frequency selection system. You may imagine it like a raffle where each player can have some number of tickets. The more tickets you have the more likely you are to +// "win". The default is 100 tickets. If no players use any extra tickets (earned with the antagonist rep system) calling this function should be equivalent to calling the normal +// pick() function. By default you may use up to 100 extra tickets per roll, meaning at maximum a player may double their chances compared to a player who has no extra tickets. +// +// The odds of being picked are simply (your_tickets / total_tickets). Suppose you have one player using fifty (50) extra tickets, and one who uses no extra: +// Player A: 150 tickets +// Player B: 100 tickets +// Total: 250 tickets +// +// The odds become: +// Player A: 150 / 250 = 0.6 = 60% +// Player B: 100 / 250 = 0.4 = 40% +/datum/game_mode/proc/antag_pick(list/datum/candidates) + if(!CONFIG_GET(flag/use_antag_rep)) // || candidates.len <= 1) + return pick(candidates) + + // Tickets start at 100 + var/DEFAULT_ANTAG_TICKETS = CONFIG_GET(number/default_antag_tickets) + + // You may use up to 100 extra tickets (double your odds) + var/MAX_TICKETS_PER_ROLL = CONFIG_GET(number/max_tickets_per_roll) + + + var/total_tickets = 0 + + MAX_TICKETS_PER_ROLL += DEFAULT_ANTAG_TICKETS + + var/p_ckey + var/p_rep + + for(var/datum/mind/mind in candidates) + p_ckey = ckey(mind.key) + total_tickets += min(SSpersistence.antag_rep[p_ckey] + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) + + var/antag_select = rand(1,total_tickets) + var/current = 1 + + for(var/datum/mind/mind in candidates) + p_ckey = ckey(mind.key) + p_rep = SSpersistence.antag_rep[p_ckey] + + var/previous = current + var/spend = min(p_rep + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) + current += spend + + if(antag_select >= previous && antag_select <= (current-1)) + SSpersistence.antag_rep_change[p_ckey] = -(spend - DEFAULT_ANTAG_TICKETS) + +// WARNING("AR_DEBUG: Player [mind.key] won spending [spend] tickets from starting value [SSpersistence.antag_rep[p_ckey]]") + + return mind + + WARNING("Something has gone terribly wrong. /datum/game_mode/proc/antag_pick failed to select a candidate. Falling back to pick()") + return pick(candidates) + +/datum/game_mode/proc/get_players_for_role(role) + var/list/players = list() + var/list/candidates = list() + var/list/drafted = list() + var/datum/mind/applicant = null + + // Ultimate randomizing code right here + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/player = i + if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences()) + players += player + + // Shuffling, the players list is now ping-independent!!! + // Goodbye antag dante + players = shuffle(players) + + for(var/mob/dead/new_player/player in players) + if(player.client && player.ready == PLAYER_READY_TO_PLAY) + if(role in player.client.prefs.be_special) + if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) + if(age_check(player.client)) //Must be older than the minimum age + candidates += player.mind // Get a list of all the people who want to be the antagonist for this round + + if(restricted_jobs) + for(var/datum/mind/player in candidates) + for(var/job in restricted_jobs) // Remove people who want to be antagonist but have a job already that precludes it + if(player.assigned_role == job) + candidates -= player + + if(candidates.len < recommended_enemies) + for(var/mob/dead/new_player/player in players) + if(player.client && player.ready == PLAYER_READY_TO_PLAY) + if(!(role in player.client.prefs.be_special)) // We don't have enough people who want to be antagonist, make a separate list of people who don't want to be one + if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) + drafted += player.mind + + if(restricted_jobs) + for(var/datum/mind/player in drafted) // Remove people who can't be an antagonist + for(var/job in restricted_jobs) + if(player.assigned_role == job) + drafted -= player + + drafted = shuffle(drafted) // Will hopefully increase randomness, Donkie + + while(candidates.len < recommended_enemies) // Pick randomlly just the number of people we need and add them to our list of candidates + if(drafted.len > 0) + applicant = pick(drafted) + if(applicant) + candidates += applicant + drafted.Remove(applicant) + + else // Not enough scrubs, ABORT ABORT ABORT + break + + return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies + // recommended_enemies if the number of people with that role set to yes is less than recomended_enemies, + // Less if there are not enough valid players in the game entirely to make recommended_enemies. + + + +/datum/game_mode/proc/num_players() + . = 0 + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/P = i + if(P.ready == PLAYER_READY_TO_PLAY) + . ++ + +/proc/reopen_roundstart_suicide_roles() + var/list/valid_positions = list() + valid_positions += GLOB.engineering_positions + valid_positions += GLOB.medical_positions + valid_positions += GLOB.science_positions + valid_positions += GLOB.supply_positions + valid_positions += GLOB.service_positions + valid_positions += GLOB.security_positions + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions)) + valid_positions += GLOB.command_positions //add any remaining command positions + else + valid_positions -= GLOB.command_positions //remove all command positions that were added from their respective department positions lists. + + var/list/reopened_jobs = list() + for(var/X in GLOB.suicided_mob_list) + if(!isliving(X)) + continue + var/mob/living/L = X + if(L.job in valid_positions) + var/datum/job/J = SSjob.GetJob(L.job) + if(!J) + continue + J.current_positions = max(J.current_positions-1, 0) + reopened_jobs += L.job + + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report)) + if(reopened_jobs.len) + var/reopened_job_report_positions + for(var/dead_dudes_job in reopened_jobs) + reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]" + + var/suicide_command_report = "Central Command Human Resources Board
    \ + Notice of Personnel Change

    \ + To personnel management staff aboard [station_name()]:

    \ + Our medical staff have detected a series of anomalies in the vital sensors \ + of some of the staff aboard your station.

    \ + Further investigation into the situation on our end resulted in us discovering \ + a series of rather... unforturnate decisions that were made on the part of said staff.

    \ + As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members \ + who have decided not to partake in our research. We will be forwarding their cases to our employment review board \ + to determine their eligibility for continued service with the company (and of course the \ + continued storage of cloning records within the central medical backup server.)

    \ + The following positions have been reopened on our behalf:

    \ + [reopened_job_report_positions]
    " + + print_command_report(suicide_command_report, "Central Command Personnel Update") + +////////////////////////// +//Reports player logouts// +////////////////////////// +/proc/display_roundstart_logout_report() + var/list/msg = list("Roundstart logout report\n\n") + for(var/i in GLOB.mob_living_list) + var/mob/living/L = i + var/mob/living/carbon/C = L + if (istype(C) && !C.last_mind) + continue // never had a client + + if(L.ckey && !GLOB.directory[L.ckey]) + msg += "[L.name] ([L.key]), the [L.job] (Disconnected)\n" + + + if(L.ckey && L.client) + var/failed = FALSE + if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) + msg += "[L.name] ([L.key]), the [L.job] (Connected, Inactive)\n" + failed = TRUE //AFK client + if(!failed && L.stat) + if(L.suiciding) //Suicider + msg += "[L.name] ([L.key]), the [L.job] (Suicide)\n" + failed = TRUE //Disconnected client + if(!failed && L.stat == UNCONSCIOUS) + msg += "[L.name] ([L.key]), the [L.job] (Dying)\n" + failed = TRUE //Unconscious + if(!failed && L.stat == DEAD) + msg += "[L.name] ([L.key]), the [L.job] (Dead)\n" + failed = TRUE //Dead + + var/p_ckey = L.client.ckey +// WARNING("AR_DEBUG: [p_ckey]: failed - [failed], antag_rep_change: [SSpersistence.antag_rep_change[p_ckey]]") + + // people who died or left should not gain any reputation + // people who rolled antagonist still lose it + if(failed && SSpersistence.antag_rep_change[p_ckey] > 0) +// WARNING("AR_DEBUG: Zeroed [p_ckey]'s antag_rep_change") + SSpersistence.antag_rep_change[p_ckey] = 0 + + continue //Happy connected client + for(var/mob/dead/observer/D in GLOB.dead_mob_list) + if(D.mind && D.mind.current == L) + if(L.stat == DEAD) + if(L.suiciding) //Suicider + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Suicide)\n" + continue //Disconnected client + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" + continue //Dead mob, ghost abandoned + else + if(D.can_reenter_corpse) + continue //Adminghost, or cult/wizard ghost + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Ghosted)\n" + continue //Ghosted while alive + + + for (var/C in GLOB.admins) + to_chat(C, msg.Join()) + +//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 +/datum/game_mode/proc/age_check(client/C) + if(get_remaining_days(C) == 0) + return 1 //Available in 0 days = available right now = player is old enough to play. + return 0 + + +/datum/game_mode/proc/get_remaining_days(client/C) + if(!C) + return 0 + if(!CONFIG_GET(flag/use_age_restriction_for_jobs)) + return 0 + if(!isnum(C.player_age)) + return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced + if(!isnum(enemy_minimum_age)) + return 0 + + return max(0, enemy_minimum_age - C.player_age) + +/datum/game_mode/proc/remove_antag_for_borging(datum/mind/newborgie) + SSticker.mode.remove_cultist(newborgie, 0, 0) + var/datum/antagonist/rev/rev = newborgie.has_antag_datum(/datum/antagonist/rev) + if(rev) + rev.remove_revolutionary(TRUE) + +/datum/game_mode/proc/generate_station_goals() + var/list/possible = list() + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = T + if(config_tag in initial(G.gamemode_blacklist)) + continue + possible += T + var/goal_weights = 0 + while(possible.len && goal_weights < STATION_GOAL_BUDGET) + var/datum/station_goal/picked = pick_n_take(possible) + goal_weights += initial(picked.weight) + station_goals += new picked + + +/datum/game_mode/proc/generate_report() //Generates a small text blurb for the gamemode in centcom report + return "Gamemode report for [name] not set. Contact a coder." + +//By default nuke just ends the round +/datum/game_mode/proc/OnNukeExplosion(off_station) + nuke_off_station = off_station + if(off_station < 2) + station_was_nuked = TRUE //Will end the round on next check. + +//Additional report section in roundend report +/datum/game_mode/proc/special_report() + return + +//Set result and news report here +/datum/game_mode/proc/set_round_result() + SSticker.mode_result = "undefined" + if(station_was_nuked) + SSticker.news_report = STATION_DESTROYED_NUKE + if(EMERGENCY_ESCAPED_OR_ENDGAMED) + SSticker.news_report = STATION_EVACUATED + if(SSshuttle.emergency.is_hijacked()) + SSticker.news_report = SHUTTLE_HIJACK + +/// Mode specific admin panel. +/datum/game_mode/proc/admin_panel() + return diff --git a/code/game/gamemodes/gang/gang_handler.dm b/code/game/gamemodes/gang/gang_handler.dm index d47cec4f93d..c47f127f298 100644 --- a/code/game/gamemodes/gang/gang_handler.dm +++ b/code/game/gamemodes/gang/gang_handler.dm @@ -1,616 +1,616 @@ -#define LOWPOP_FAMILIES_COUNT 50 - -#define TWO_STARS_HIGHPOP 11 -#define THREE_STARS_HIGHPOP 16 -#define FOUR_STARS_HIGHPOP 21 -#define FIVE_STARS_HIGHPOP 31 - -#define TWO_STARS_LOW 6 -#define THREE_STARS_LOW 9 -#define FOUR_STARS_LOW 12 -#define FIVE_STARS_LOW 15 - -#define CREW_SIZE_MIN 4 -#define CREW_SIZE_MAX 8 - - -GLOBAL_VAR_INIT(deaths_during_shift, 0) - -/** - * # Families gamemode / dynamic ruleset handler - * - * A special datum used by the families gamemode and dynamic rulesets to centralize code. "Family" and "gang" used interchangeably in code. - * - * This datum centralizes code used for the families gamemode / dynamic rulesets. Families incorporates a significant - * amount of unique processing; without this datum, that could would be duplicated. To ensure the maintainability - * of the families gamemode / rulesets, the code was moved to this datum. The gamemode / rulesets instance this - * datum, pass it lists (lists are passed by reference; removing candidates here removes candidates in the gamemode), - * and call its procs. Additionally, the families antagonist datum and families induction package also - * contain vars that reference this datum, allowing for new families / family members to add themselves - * to this datum's lists thereof (primarily used for point calculation). Despite this, the basic team mechanics - * themselves should function regardless of this datum's instantiation, should a player have the gang or cop - * antagonist datum added to them through methods external to the families gamemode / rulesets. - * - */ -/datum/gang_handler - /// A counter used to minimize the overhead of computationally intensive, periodic family point gain checks. Used and set internally. - var/check_counter = 0 - /// The time, in deciseconds, that the datum's pre_setup() occured at. Used in end_time. Used and set internally. - var/start_time = null - /// The time, in deciseconds, that the space cops will arrive at. Calculated based on wanted level and start_time. Used and set internally. - var/end_time = null - /// Whether the gamemode-announcing announcement has been sent. Used and set internally. - var/sent_announcement = FALSE - /// Whether the "5 minute warning" announcement has been sent. Used and set internally. - var/sent_second_announcement = FALSE - /// Whether the space cops have arrived. Set internally; used internally, and for updating the wanted HUD. - var/cops_arrived = FALSE - /// The current wanted level. Set internally; used internally, and for updating the wanted HUD. - var/wanted_level - /// List of all /datum/team/gang. Used internally; added to externally by /datum/antagonist/gang when it generates a new /datum/team/gang. - var/list/gangs = list() - /// List of all family member minds. Used internally; added to internally, and externally by /obj/item/gang_induction_package when used to induct a new family member. - var/list/gangbangers = list() - /// List of all undercover cop minds. Used and set internally. - var/list/undercover_cops = list() - /// The number of families (and 1:1 corresponding undercover cops) that should be generated. Can be set externally; used internally. - var/gangs_to_generate = 3 - /// The number of family members more that a family may have over other active families. Can be set externally; used internally. - var/gang_balance_cap = 5 - /// Whether the handler corresponds to a ruleset that does not trigger at round start. Should be set externally only if applicable; used internally. - var/midround_ruleset = FALSE - /// Whether we want to use the 30 to 15 minute timer instead of the 60 to 30 minute timer, for Dynamic. - var/use_dynamic_timing = FALSE - /// Keeps track of the amount of deaths since the calling of pre_setup_analogue() if this is a midround handler. Used to prevent a high wanted level due to a large amount of deaths during the shift prior to the activation of this handler / the midround ruleset. - var/deaths_during_shift_at_beginning = 0 - - /// List of all eligible starting family members / undercover cops. Set externally (passed by reference) by gamemode / ruleset; used internally. Note that dynamic uses a list of mobs to handle candidates while game_modes use lists of minds! Don't be fooled! - var/list/antag_candidates = list() - /// List of jobs not eligible for starting family member / undercover cop. Set externally (passed by reference) by gamemode / ruleset; used internally. - var/list/restricted_jobs - -/** - * Sets antag_candidates and restricted_jobs. - * - * Sets the antag_candidates and restricted_jobs lists to the equivalent - * lists of its instantiating game_mode / dynamic_ruleset datum. As lists - * are passed by reference, the variable set in this datum and the passed list - * list used to set it are literally the same; changes to one affect the other. - * Like all New() procs, called when the datum is first instantiated. - * There's an annoying caveat here, though -- dynamic rulesets don't have - * lists of minds for candidates, they have lists of mobs. Ghost mobs, before - * the round has started. But we still want to preserve the structure of the candidates - * list by not duplicating it and making sure to remove the candidates as we use them. - * So there's a little bit of boilerplate throughout to preserve the sanctity of this reference. - * Arguments: - * * given_candidates - The antag_candidates list or equivalent of the datum instantiating this one. - * * revised_restricted - The restricted_jobs list or equivalent of the datum instantiating this one. - */ -/datum/gang_handler/New(list/given_candidates, list/revised_restricted) - antag_candidates = given_candidates - restricted_jobs = revised_restricted - -/** - * pre_setup() or pre_execute() equivalent. - * - * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. - * This is done during the pre_setup() or pre_execute() phase, after first instantiation - * and the modification of gangs_to_generate, gang_balance_cap, and midround_ruleset. - * It is intended to take the place of the code that would normally occupy the pre_setup() - * or pre_execute() proc, were the code localized to the game_mode or dynamic_ruleset datum respectively - * as opposed to this handler. As such, it picks players to be chosen for starting familiy members - * or undercover cops prior to assignment to jobs. Sets start_time, default end_time, - * and the current value of deaths_during_shift, to ensure the wanted level only cares about - * the deaths since this proc has been called. - * Takes no arguments. - */ -/datum/gang_handler/proc/pre_setup_analogue() - for(var/j = 0, j < gangs_to_generate, j++) - if (!antag_candidates.len) - break - var/taken = pick_n_take(antag_candidates) // original used antag_pick, but that's local to game_mode and rulesets use pick_n_take so this is fine maybe - var/datum/mind/gangbanger - if(istype(taken, /mob)) - var/mob/T = taken - gangbanger = T.mind - else - gangbanger = taken - gangbangers += gangbanger - gangbanger.restricted_roles = restricted_jobs - log_game("[key_name(gangbanger)] has been selected as a starting gangster!") - if(!midround_ruleset) - GLOB.pre_setup_antags += gangbanger - for(var/j = 0, j < gangs_to_generate, j++) - if(!antag_candidates.len) - break - var/taken = pick_n_take(antag_candidates) - var/datum/mind/undercover_cop - if(istype(taken, /mob)) - var/mob/T = taken - undercover_cop = T.mind - else - undercover_cop = taken - undercover_cops += undercover_cop - undercover_cop.restricted_roles = restricted_jobs - log_game("[key_name(undercover_cop)] has been selected as a starting undercover cop!") - if(!midround_ruleset) - GLOB.pre_setup_antags += undercover_cop - deaths_during_shift_at_beginning = GLOB.deaths_during_shift // don't want to mix up pre-families and post-families deaths - start_time = world.time - end_time = start_time + ((60 MINUTES) / (midround_ruleset ? 2 : 1)) // midround families rounds end quicker - return TRUE - -/** - * post_setup() or execute() equivalent. - * - * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. - * This is done during the post_setup() or execute() phase, after the pre_setup() / pre_execute() phase. - * It is intended to take the place of the code that would normally occupy the pre_setup() - * or pre_execute() proc. As such, it ensures that all prospective starting family members / - * undercover cops are eligible, and picks replacements if there were ineligible cops / family members. - * It then assigns gear to the finalized family members and undercover cops, adding them to its lists, - * and sets the families announcement proc (that does the announcing) to trigger in five minutes. - * Additionally, if given the argument TRUE, it will return FALSE if there are no eligible starting family members. - * This is only to be done if the instantiating datum is a dynamic_ruleset, as these require returns - * while a game_mode is not expected to return early during this phase. - * Arguments: - * * return_if_no_gangs - Boolean that determines if the proc should return FALSE should it find no eligible family members. Should be used for dynamic only. - */ -/datum/gang_handler/proc/post_setup_analogue(return_if_no_gangs = FALSE) - var/replacement_gangsters = 0 - var/replacement_cops = 0 - for(var/datum/mind/gangbanger in gangbangers) - if(!ishuman(gangbanger.current)) - if(!midround_ruleset) - GLOB.pre_setup_antags -= gangbanger - gangbangers.Remove(gangbanger) - log_game("[gangbanger] was not a human, and thus has lost their gangster role.") - replacement_gangsters++ - if(replacement_gangsters) - for(var/j = 0, j < replacement_gangsters, j++) - if(!antag_candidates.len) - log_game("Unable to find more replacement gangsters. Not all of the gangs will spawn.") - break - var/taken = pick_n_take(antag_candidates) - var/datum/mind/gangbanger - if(istype(taken, /mob)) // boilerplate needed because antag_candidates might not contain minds - var/mob/T = taken - gangbanger = T.mind - else - gangbanger = taken - gangbangers += gangbanger - log_game("[key_name(gangbanger)] has been selected as a replacement gangster!") - for(var/datum/mind/undercover_cop in undercover_cops) - if(!ishuman(undercover_cop.current)) - undercover_cops.Remove(undercover_cop) - if(!midround_ruleset) - GLOB.pre_setup_antags -= undercover_cop - log_game("[undercover_cop] was not a human, and thus has lost their undercover cop role.") - replacement_cops++ - if(replacement_cops) - for(var/j = 0, j < replacement_cops, j++) - if(!antag_candidates.len) - log_game("Unable to find more replacement undercover cops. Not all of the cops will spawn.") - break - var/taken = pick_n_take(antag_candidates) - var/datum/mind/undercover_cop - if(istype(taken, /mob)) - var/mob/T = taken - undercover_cop = T.mind - else - undercover_cop = taken - undercover_cops += undercover_cop - log_game("[key_name(undercover_cop)] has been selected as a replacement undercover cop!") - - if(!gangbangers.len) - if(return_if_no_gangs) - return FALSE // ending early is bad if we're not in dynamic - - for(var/datum/mind/undercover_cop in undercover_cops) - var/datum/antagonist/ert/families/undercover_cop/one_eight_seven_on_an_undercover_cop = new() - undercover_cop.add_antag_datum(one_eight_seven_on_an_undercover_cop) - - var/list/gangs_to_use = subtypesof(/datum/antagonist/gang) - for(var/datum/mind/gangbanger in gangbangers) - var/gang_to_use = pick_n_take(gangs_to_use) - var/datum/antagonist/gang/new_gangster = new gang_to_use() - new_gangster.handler = src - new_gangster.starter_gangster = TRUE - gangbanger.add_antag_datum(new_gangster) - // see /datum/antagonist/gang/create_team() for how the gang team datum gets instantiated and added to our gangs list - - addtimer(CALLBACK(src, .proc/announce_gang_locations), 5 MINUTES) - SSshuttle.registerHostileEnvironment(src) - return TRUE - -/** - * process() or rule_process() equivalent. - * - * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. - * This is done during the process() or rule_process() phase, after post_setup() or - * execute() and at regular intervals thereafter. process() and rule_process() are optional - * for a game_mode / dynamic_ruleset, but are important for this gamemode. It is of central - * importance to the gamemode's flow, calculating wanted level updates, family point gain, - * and announcing + executing the arrival of the space cops, achieved through calling internal procs. - * Takes no arguments. - */ -/datum/gang_handler/proc/process_analogue() - check_wanted_level() - check_counter++ - if(check_counter >= 5) - if(world.time > (end_time - 5 MINUTES) && !sent_second_announcement) - five_minute_warning() - addtimer(CALLBACK(src, .proc/send_in_the_fuzz), 5 MINUTES) - - check_counter = 0 - - check_tagged_turfs() - check_gang_clothes() - check_rollin_with_crews() - -/** - * set_round_result() or round_result() equivalent. - * - * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. - * This is done by the set_round_result() or round_result() procs, at roundend. - * Sets the ticker subsystem to the correct result based off of the relative populations - * of space cops and family members. - * Takes no arguments. - */ -/datum/gang_handler/proc/set_round_result_analogue() - var/alive_gangsters = 0 - var/alive_cops = 0 - for(var/datum/mind/gangbanger in gangbangers) - if(!ishuman(gangbanger.current)) - continue - var/mob/living/carbon/human/H = gangbanger.current - if(H.stat) - continue - alive_gangsters++ - for(var/datum/mind/bacon in get_antag_minds(/datum/antagonist/ert/families)) - if(!ishuman(bacon.current)) // always returns false - continue - var/mob/living/carbon/human/H = bacon.current - if(H.stat) - continue - alive_cops++ - if(alive_gangsters > alive_cops) - SSticker.mode_result = "win - gangs survived" - SSticker.news_report = GANG_OPERATING - return TRUE - SSticker.mode_result = "loss - police destroyed the gangs" - SSticker.news_report = GANG_DESTROYED - return FALSE - -/// Internal. Announces the presence of families to the entire station and sets sent_announcement to true to allow other checks to occur. -/datum/gang_handler/proc/announce_gang_locations() - var/list/readable_gang_names = list() - for(var/GG in gangs) - var/datum/team/gang/G = GG - readable_gang_names += "[G.name]" - var/finalized_gang_names = english_list(readable_gang_names) - priority_announce("Julio G coming to you live from Radio Los Spess! We've been hearing reports of gang activity on [station_name()], with the [finalized_gang_names] duking it out, looking for fresh territory and drugs to sling! Stay safe out there for the [use_dynamic_timing ? "half-hour" : "hour"] 'till the space cops get there, and keep it cool, yeah?\n\n The local jump gates are shut down for about an hour due to some maintenance troubles, so if you wanna split from the area you're gonna have to wait [use_dynamic_timing ? "thirty minutes" : "an hour"]. \n Play music, not gunshots, I say. Peace out!", "Radio Los Spess", 'sound/voice/beepsky/radio.ogg') - sent_announcement = TRUE - check_wanted_level() // i like it when the wanted level updates at the same time as the announcement - -/// Internal. Announces that space cops will arrive in 5 minutes and sets sent_second_announcement to true to freeze -/datum/gang_handler/proc/five_minute_warning() - priority_announce("Julio G coming to you live from Radio Los Spess! The space cops are closing in on [station_name()] and will arrive in about 5 minutes! Better clear on out of there if you don't want to get hurt!", "Radio Los Spess", 'sound/voice/beepsky/radio.ogg') - sent_second_announcement = TRUE - -/// Internal. Checks if our wanted level has changed; calls update_wanted_level. Only updates wanted level post the initial announcement and until the cops show up. After that, it's locked. -/datum/gang_handler/proc/check_wanted_level() - if(cops_arrived) - update_wanted_level(wanted_level) // at this point, we still want to update people's star huds, even though they're mostly locked, because not everyone is around for the last update before the rest of this proc gets shut off forever, and that's when the wanted bar switches from gold stars to red / blue to signify the arrival of the space cops - return - if(!sent_announcement) - return - var/new_wanted_level - if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT) - switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning) // if this is a midround ruleset, we only care about the deaths since the families were activated, not since shiftstart - if(0 to TWO_STARS_HIGHPOP-1) - new_wanted_level = 1 - if(TWO_STARS_HIGHPOP to THREE_STARS_HIGHPOP-1) - new_wanted_level = 2 - if(THREE_STARS_HIGHPOP to FOUR_STARS_HIGHPOP-1) - new_wanted_level = 3 - if(FOUR_STARS_HIGHPOP to FIVE_STARS_HIGHPOP-1) - new_wanted_level = 4 - if(FIVE_STARS_HIGHPOP to INFINITY) - new_wanted_level = 5 - else - switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning) - if(0 to TWO_STARS_LOW-1) - new_wanted_level = 1 - if(TWO_STARS_LOW to THREE_STARS_LOW-1) - new_wanted_level = 2 - if(THREE_STARS_LOW to FOUR_STARS_LOW-1) - new_wanted_level = 3 - if(FOUR_STARS_LOW to FIVE_STARS_LOW-1) - new_wanted_level = 4 - if(FIVE_STARS_LOW to INFINITY) - new_wanted_level = 5 - update_wanted_level(new_wanted_level) - -/// Internal. Updates the icon states for everyone, and calls procs that send out announcements / change the end_time if the wanted level has changed. -/datum/gang_handler/proc/update_wanted_level(newlevel) - if(newlevel > wanted_level) - on_gain_wanted_level(newlevel) - else if (newlevel < wanted_level) - on_lower_wanted_level(newlevel) - wanted_level = newlevel - for(var/i in GLOB.player_list) - var/mob/M = i - if(!M.hud_used?.wanted_lvl) - continue - var/datum/hud/H = M.hud_used - H.wanted_lvl.level = newlevel - H.wanted_lvl.cops_arrived = cops_arrived - H.wanted_lvl.update_icon() - -/// Internal. Updates the end_time and sends out an announcement if the wanted level has increased. Called by update_wanted_level(). -/datum/gang_handler/proc/on_gain_wanted_level(newlevel) - var/announcement_message - switch(newlevel) - if(2) - if(!sent_second_announcement) // when you hear that they're "arriving in 5 minutes," that's a goddamn guarantee - end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "Small amount of police vehicles have been spotted en route towards [station_name()]." - if(3) - if(!sent_second_announcement) - end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "A large detachment police vehicles have been spotted en route towards [station_name()]." - if(4) - if(!sent_second_announcement) - end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "A detachment of top-trained agents has been spotted on their way to [station_name()]." - if(5) - if(!sent_second_announcement) - end_time = start_time + ((30 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "The fleet enroute to [station_name()] now consists of national guard personnel." - if(!midround_ruleset) // stops midround rulesets from announcing janky ass times - announcement_message += " They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark." - if(newlevel == 1) // specific exception to stop the announcement from triggering right after the families themselves are announced because aesthetics - return - priority_announce(announcement_message, "Station Spaceship Detection Systems") - -/// Internal. Updates the end_time and sends out an announcement if the wanted level has decreased. Called by update_wanted_level(). -/datum/gang_handler/proc/on_lower_wanted_level(newlevel) - var/announcement_message - switch(newlevel) - if(1) - if(!sent_second_announcement) - end_time = start_time + ((60 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "There are now only a few police vehicle headed towards [station_name()]." - if(2) - if(!sent_second_announcement) - end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "There seem to be fewer police vehicles headed towards [station_name()]." - if(3) - if(!sent_second_announcement) - end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "There are no longer top-trained agents in the fleet headed towards [station_name()]." - if(4) - if(!sent_second_announcement) - end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1)) - announcement_message = "The convoy enroute to [station_name()] seems to no longer consist of national guard personnel." - if(!midround_ruleset) - announcement_message += " They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark." - priority_announce(announcement_message, "Station Spaceship Detection Systems") - -/// Internal. Polls ghosts and sends in a team of space cops according to the wanted level, accompanied by an announcement. Will let the shuttle leave 10 minutes after sending. Freezes the wanted level. -/datum/gang_handler/proc/send_in_the_fuzz() - var/team_size - var/cops_to_send - var/announcement_message = "PUNK ASS BALLA BITCH" - var/announcer = "Spinward Stellar Coalition" - if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT) - switch(wanted_level) - if(1) - team_size = 8 - cops_to_send = /datum/antagonist/ert/families/beatcop - announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!" - announcer = "Spinward Stellar Coalition Police Department" - if(2) - team_size = 9 - cops_to_send = /datum/antagonist/ert/families/beatcop/armored - announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day." - announcer = "Spinward Stellar Coalition Police Department" - if(3) - team_size = 10 - cops_to_send = /datum/antagonist/ert/families/beatcop/swat - announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day." - announcer = "Spinward Stellar Coalition Police Department" - if(4) - team_size = 11 - cops_to_send = /datum/antagonist/ert/families/beatcop/fbi - announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions." - announcer = "Federal Bureau of Investigation" - if(5) - team_size = 12 - cops_to_send = /datum/antagonist/ert/families/beatcop/military - announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians." - announcer = "Spinward Stellar Coalition National Guard" - else - switch(wanted_level) - if(1) - team_size = 5 - cops_to_send = /datum/antagonist/ert/families/beatcop - announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!" - announcer = "Spinward Stellar Coalition Police Department" - if(2) - team_size = 6 - cops_to_send = /datum/antagonist/ert/families/beatcop/armored - announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day." - announcer = "Spinward Stellar Coalition Police Department" - if(3) - team_size = 7 - cops_to_send = /datum/antagonist/ert/families/beatcop/swat - announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day." - announcer = "Spinward Stellar Coalition Police Department" - if(4) - team_size = 8 - cops_to_send = /datum/antagonist/ert/families/beatcop/fbi - announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions." - announcer = "Federal Bureau of Investigation" - if(5) - team_size = 10 - cops_to_send = /datum/antagonist/ert/families/beatcop/military - announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians." - announcer = "Spinward Stellar Coalition National Guard" - - priority_announce(announcement_message, announcer, 'sound/effects/families_police.ogg') - var/list/candidates = pollGhostCandidates("Do you want to help clean up crime on this station?", "deathsquad", null) - - - if(candidates.len) - //Pick the (un)lucky players - var/numagents = min(team_size,candidates.len) - - var/list/spawnpoints = GLOB.emergencyresponseteamspawn - var/index = 0 - while(numagents && candidates.len) - var/spawnloc = spawnpoints[index+1] - //loop through spawnpoints one at a time - index = (index + 1) % spawnpoints.len - var/mob/dead/observer/chosen_candidate = pick(candidates) - candidates -= chosen_candidate - if(!chosen_candidate.key) - continue - - //Spawn the body - var/mob/living/carbon/human/cop = new(spawnloc) - chosen_candidate.client.prefs.copy_to(cop) - cop.key = chosen_candidate.key - - //Give antag datum - var/datum/antagonist/ert/families/ert_antag = new cops_to_send - - cop.mind.add_antag_datum(ert_antag) - cop.mind.assigned_role = ert_antag.name - SSjob.SendToLateJoin(cop) - - //Logging and cleanup - log_game("[key_name(cop)] has been selected as an [ert_antag.name]") - numagents-- - cops_arrived = TRUE - update_wanted_level(wanted_level) // gotta make sure everyone's wanted level display looks nice - addtimer(CALLBACK(src, .proc/end_hostile_sit), 10 MINUTES) - return TRUE - -/// Internal. Clears the hostile environment, letting the shuttle leave. -/datum/gang_handler/proc/end_hostile_sit() - SSshuttle.clearHostileEnvironment(src) - -/// Internal. Assigns points to families according to gang tags. -/datum/gang_handler/proc/check_tagged_turfs() - for(var/T in GLOB.gang_tags) - var/obj/effect/decal/cleanable/crayon/gang/tag = T - if(tag.my_gang) - tag.my_gang.adjust_points(50) - CHECK_TICK - -/// Internal. Assigns points to families according to clothing of all currently living humans. -/datum/gang_handler/proc/check_gang_clothes() // TODO: make this grab the sprite itself, average out what the primary color would be, then compare how close it is to the gang color so I don't have to manually fill shit out for 5 years for every gang type - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(!H.mind || !H.client) - continue - var/datum/antagonist/gang/is_gangster = H.mind.has_antag_datum(/datum/antagonist/gang) - for(var/clothing in list(H.head, H.wear_mask, H.wear_suit, H.w_uniform, H.back, H.gloves, H.shoes, H.belt, H.s_store, H.glasses, H.ears, H.wear_id)) - if(is_gangster) - if(is_type_in_list(clothing, is_gangster.acceptable_clothes)) - is_gangster.add_gang_points(10) - else - for(var/G in gangs) - var/datum/team/gang/gang_clothes = G - if(is_type_in_list(clothing, gang_clothes.acceptable_clothes)) - gang_clothes.adjust_points(5) - - CHECK_TICK - -/// Internal. Assigns points to families according to groups of nearby family members. -/datum/gang_handler/proc/check_rollin_with_crews() - var/list/areas_to_check = list() - for(var/G in gangbangers) - var/datum/mind/gangster = G - areas_to_check += get_area(gangster.current) - for(var/AA in areas_to_check) - var/area/A = AA - var/list/gang_members = list() - for(var/mob/living/carbon/human/H in A) - if(H.stat || !H.mind || !H.client) - continue - var/datum/antagonist/gang/is_gangster = H.mind.has_antag_datum(/datum/antagonist/gang) - if(is_gangster) - gang_members[is_gangster.my_gang]++ - CHECK_TICK - if(gang_members.len) - for(var/datum/team/gang/gangsters in gang_members) - if(gang_members[gangsters] >= CREW_SIZE_MIN) - if(gang_members[gangsters] >= CREW_SIZE_MAX) - gangsters.adjust_points(5) // Discourage larger clumps, spread ur people out - else - gangsters.adjust_points(10) - - -/// Hijacks the space cops' roundend results to say if cops / a gang won the round. Included in the same file as the gang_handler as it's far more related to the gamemode than it is to the beat cop datum; it's kind of hacky. -/datum/antagonist/ert/families/beatcop/roundend_report_footer() - var/list/all_gangs = list() - for(var/datum/team/gang/G in GLOB.antagonist_teams) - all_gangs += G - if(!all_gangs.len) - return ..() - var/list/all_gangsters = get_antag_minds(/datum/antagonist/gang) - var/list/all_cops = get_antag_minds(/datum/antagonist/ert/families) - var/report - var/highest_point_value = 0 - var/highest_gang = "Leet Like Jeff K" - var/objective_failures = TRUE - - for(var/G in all_gangs) - var/datum/team/gang/GG = G - if(GG.my_gang_datum.check_gang_objective()) - objective_failures = FALSE - break - for(var/G in all_gangs) - var/datum/team/gang/GG = G - if(!objective_failures) - if(GG.points >= highest_point_value && GG.members.len && GG.my_gang_datum.check_gang_objective()) - highest_point_value = GG.points - highest_gang = GG.name - else - if(GG.points >= highest_point_value && GG.members.len) - highest_point_value = GG.points - highest_gang = GG.name - var/alive_gangsters = 0 - var/alive_cops = 0 - for(var/M in all_gangsters) - var/datum/mind/gangbanger = M - if(gangbanger.current) - if(!ishuman(gangbanger.current)) - continue - var/mob/living/carbon/human/H = gangbanger.current - if(H.stat) - continue - alive_gangsters++ - for(var/M in all_cops) - var/datum/mind/bacon = M - if(bacon.current) - if(!ishuman(bacon.current)) // always returns false - continue - var/mob/living/carbon/human/H = bacon.current - if(H.stat) - continue - alive_cops++ - - if(alive_gangsters > alive_cops) - if(!objective_failures) - report = "[highest_gang] won the round by completing their objective and having the most points!" - else - report = "[highest_gang] won the round by having the most points!" - else if(alive_gangsters == alive_cops) - report = "Legend has it the police and the families are still duking it out to this day!" - else - report = "The police put the boots to the families, medium style!" - - return "
    [report]" //
    at the front not the back because this proc is intended for normal text not a whole new panel +#define LOWPOP_FAMILIES_COUNT 50 + +#define TWO_STARS_HIGHPOP 11 +#define THREE_STARS_HIGHPOP 16 +#define FOUR_STARS_HIGHPOP 21 +#define FIVE_STARS_HIGHPOP 31 + +#define TWO_STARS_LOW 6 +#define THREE_STARS_LOW 9 +#define FOUR_STARS_LOW 12 +#define FIVE_STARS_LOW 15 + +#define CREW_SIZE_MIN 4 +#define CREW_SIZE_MAX 8 + + +GLOBAL_VAR_INIT(deaths_during_shift, 0) + +/** + * # Families gamemode / dynamic ruleset handler + * + * A special datum used by the families gamemode and dynamic rulesets to centralize code. "Family" and "gang" used interchangeably in code. + * + * This datum centralizes code used for the families gamemode / dynamic rulesets. Families incorporates a significant + * amount of unique processing; without this datum, that could would be duplicated. To ensure the maintainability + * of the families gamemode / rulesets, the code was moved to this datum. The gamemode / rulesets instance this + * datum, pass it lists (lists are passed by reference; removing candidates here removes candidates in the gamemode), + * and call its procs. Additionally, the families antagonist datum and families induction package also + * contain vars that reference this datum, allowing for new families / family members to add themselves + * to this datum's lists thereof (primarily used for point calculation). Despite this, the basic team mechanics + * themselves should function regardless of this datum's instantiation, should a player have the gang or cop + * antagonist datum added to them through methods external to the families gamemode / rulesets. + * + */ +/datum/gang_handler + /// A counter used to minimize the overhead of computationally intensive, periodic family point gain checks. Used and set internally. + var/check_counter = 0 + /// The time, in deciseconds, that the datum's pre_setup() occured at. Used in end_time. Used and set internally. + var/start_time = null + /// The time, in deciseconds, that the space cops will arrive at. Calculated based on wanted level and start_time. Used and set internally. + var/end_time = null + /// Whether the gamemode-announcing announcement has been sent. Used and set internally. + var/sent_announcement = FALSE + /// Whether the "5 minute warning" announcement has been sent. Used and set internally. + var/sent_second_announcement = FALSE + /// Whether the space cops have arrived. Set internally; used internally, and for updating the wanted HUD. + var/cops_arrived = FALSE + /// The current wanted level. Set internally; used internally, and for updating the wanted HUD. + var/wanted_level + /// List of all /datum/team/gang. Used internally; added to externally by /datum/antagonist/gang when it generates a new /datum/team/gang. + var/list/gangs = list() + /// List of all family member minds. Used internally; added to internally, and externally by /obj/item/gang_induction_package when used to induct a new family member. + var/list/gangbangers = list() + /// List of all undercover cop minds. Used and set internally. + var/list/undercover_cops = list() + /// The number of families (and 1:1 corresponding undercover cops) that should be generated. Can be set externally; used internally. + var/gangs_to_generate = 3 + /// The number of family members more that a family may have over other active families. Can be set externally; used internally. + var/gang_balance_cap = 5 + /// Whether the handler corresponds to a ruleset that does not trigger at round start. Should be set externally only if applicable; used internally. + var/midround_ruleset = FALSE + /// Whether we want to use the 30 to 15 minute timer instead of the 60 to 30 minute timer, for Dynamic. + var/use_dynamic_timing = FALSE + /// Keeps track of the amount of deaths since the calling of pre_setup_analogue() if this is a midround handler. Used to prevent a high wanted level due to a large amount of deaths during the shift prior to the activation of this handler / the midround ruleset. + var/deaths_during_shift_at_beginning = 0 + + /// List of all eligible starting family members / undercover cops. Set externally (passed by reference) by gamemode / ruleset; used internally. Note that dynamic uses a list of mobs to handle candidates while game_modes use lists of minds! Don't be fooled! + var/list/antag_candidates = list() + /// List of jobs not eligible for starting family member / undercover cop. Set externally (passed by reference) by gamemode / ruleset; used internally. + var/list/restricted_jobs + +/** + * Sets antag_candidates and restricted_jobs. + * + * Sets the antag_candidates and restricted_jobs lists to the equivalent + * lists of its instantiating game_mode / dynamic_ruleset datum. As lists + * are passed by reference, the variable set in this datum and the passed list + * list used to set it are literally the same; changes to one affect the other. + * Like all New() procs, called when the datum is first instantiated. + * There's an annoying caveat here, though -- dynamic rulesets don't have + * lists of minds for candidates, they have lists of mobs. Ghost mobs, before + * the round has started. But we still want to preserve the structure of the candidates + * list by not duplicating it and making sure to remove the candidates as we use them. + * So there's a little bit of boilerplate throughout to preserve the sanctity of this reference. + * Arguments: + * * given_candidates - The antag_candidates list or equivalent of the datum instantiating this one. + * * revised_restricted - The restricted_jobs list or equivalent of the datum instantiating this one. + */ +/datum/gang_handler/New(list/given_candidates, list/revised_restricted) + antag_candidates = given_candidates + restricted_jobs = revised_restricted + +/** + * pre_setup() or pre_execute() equivalent. + * + * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. + * This is done during the pre_setup() or pre_execute() phase, after first instantiation + * and the modification of gangs_to_generate, gang_balance_cap, and midround_ruleset. + * It is intended to take the place of the code that would normally occupy the pre_setup() + * or pre_execute() proc, were the code localized to the game_mode or dynamic_ruleset datum respectively + * as opposed to this handler. As such, it picks players to be chosen for starting familiy members + * or undercover cops prior to assignment to jobs. Sets start_time, default end_time, + * and the current value of deaths_during_shift, to ensure the wanted level only cares about + * the deaths since this proc has been called. + * Takes no arguments. + */ +/datum/gang_handler/proc/pre_setup_analogue() + for(var/j = 0, j < gangs_to_generate, j++) + if (!antag_candidates.len) + break + var/taken = pick_n_take(antag_candidates) // original used antag_pick, but that's local to game_mode and rulesets use pick_n_take so this is fine maybe + var/datum/mind/gangbanger + if(istype(taken, /mob)) + var/mob/T = taken + gangbanger = T.mind + else + gangbanger = taken + gangbangers += gangbanger + gangbanger.restricted_roles = restricted_jobs + log_game("[key_name(gangbanger)] has been selected as a starting gangster!") + if(!midround_ruleset) + GLOB.pre_setup_antags += gangbanger + for(var/j = 0, j < gangs_to_generate, j++) + if(!antag_candidates.len) + break + var/taken = pick_n_take(antag_candidates) + var/datum/mind/undercover_cop + if(istype(taken, /mob)) + var/mob/T = taken + undercover_cop = T.mind + else + undercover_cop = taken + undercover_cops += undercover_cop + undercover_cop.restricted_roles = restricted_jobs + log_game("[key_name(undercover_cop)] has been selected as a starting undercover cop!") + if(!midround_ruleset) + GLOB.pre_setup_antags += undercover_cop + deaths_during_shift_at_beginning = GLOB.deaths_during_shift // don't want to mix up pre-families and post-families deaths + start_time = world.time + end_time = start_time + ((60 MINUTES) / (midround_ruleset ? 2 : 1)) // midround families rounds end quicker + return TRUE + +/** + * post_setup() or execute() equivalent. + * + * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. + * This is done during the post_setup() or execute() phase, after the pre_setup() / pre_execute() phase. + * It is intended to take the place of the code that would normally occupy the pre_setup() + * or pre_execute() proc. As such, it ensures that all prospective starting family members / + * undercover cops are eligible, and picks replacements if there were ineligible cops / family members. + * It then assigns gear to the finalized family members and undercover cops, adding them to its lists, + * and sets the families announcement proc (that does the announcing) to trigger in five minutes. + * Additionally, if given the argument TRUE, it will return FALSE if there are no eligible starting family members. + * This is only to be done if the instantiating datum is a dynamic_ruleset, as these require returns + * while a game_mode is not expected to return early during this phase. + * Arguments: + * * return_if_no_gangs - Boolean that determines if the proc should return FALSE should it find no eligible family members. Should be used for dynamic only. + */ +/datum/gang_handler/proc/post_setup_analogue(return_if_no_gangs = FALSE) + var/replacement_gangsters = 0 + var/replacement_cops = 0 + for(var/datum/mind/gangbanger in gangbangers) + if(!ishuman(gangbanger.current)) + if(!midround_ruleset) + GLOB.pre_setup_antags -= gangbanger + gangbangers.Remove(gangbanger) + log_game("[gangbanger] was not a human, and thus has lost their gangster role.") + replacement_gangsters++ + if(replacement_gangsters) + for(var/j = 0, j < replacement_gangsters, j++) + if(!antag_candidates.len) + log_game("Unable to find more replacement gangsters. Not all of the gangs will spawn.") + break + var/taken = pick_n_take(antag_candidates) + var/datum/mind/gangbanger + if(istype(taken, /mob)) // boilerplate needed because antag_candidates might not contain minds + var/mob/T = taken + gangbanger = T.mind + else + gangbanger = taken + gangbangers += gangbanger + log_game("[key_name(gangbanger)] has been selected as a replacement gangster!") + for(var/datum/mind/undercover_cop in undercover_cops) + if(!ishuman(undercover_cop.current)) + undercover_cops.Remove(undercover_cop) + if(!midround_ruleset) + GLOB.pre_setup_antags -= undercover_cop + log_game("[undercover_cop] was not a human, and thus has lost their undercover cop role.") + replacement_cops++ + if(replacement_cops) + for(var/j = 0, j < replacement_cops, j++) + if(!antag_candidates.len) + log_game("Unable to find more replacement undercover cops. Not all of the cops will spawn.") + break + var/taken = pick_n_take(antag_candidates) + var/datum/mind/undercover_cop + if(istype(taken, /mob)) + var/mob/T = taken + undercover_cop = T.mind + else + undercover_cop = taken + undercover_cops += undercover_cop + log_game("[key_name(undercover_cop)] has been selected as a replacement undercover cop!") + + if(!gangbangers.len) + if(return_if_no_gangs) + return FALSE // ending early is bad if we're not in dynamic + + for(var/datum/mind/undercover_cop in undercover_cops) + var/datum/antagonist/ert/families/undercover_cop/one_eight_seven_on_an_undercover_cop = new() + undercover_cop.add_antag_datum(one_eight_seven_on_an_undercover_cop) + + var/list/gangs_to_use = subtypesof(/datum/antagonist/gang) + for(var/datum/mind/gangbanger in gangbangers) + var/gang_to_use = pick_n_take(gangs_to_use) + var/datum/antagonist/gang/new_gangster = new gang_to_use() + new_gangster.handler = src + new_gangster.starter_gangster = TRUE + gangbanger.add_antag_datum(new_gangster) + // see /datum/antagonist/gang/create_team() for how the gang team datum gets instantiated and added to our gangs list + + addtimer(CALLBACK(src, .proc/announce_gang_locations), 5 MINUTES) + SSshuttle.registerHostileEnvironment(src) + return TRUE + +/** + * process() or rule_process() equivalent. + * + * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. + * This is done during the process() or rule_process() phase, after post_setup() or + * execute() and at regular intervals thereafter. process() and rule_process() are optional + * for a game_mode / dynamic_ruleset, but are important for this gamemode. It is of central + * importance to the gamemode's flow, calculating wanted level updates, family point gain, + * and announcing + executing the arrival of the space cops, achieved through calling internal procs. + * Takes no arguments. + */ +/datum/gang_handler/proc/process_analogue() + check_wanted_level() + check_counter++ + if(check_counter >= 5) + if(world.time > (end_time - 5 MINUTES) && !sent_second_announcement) + five_minute_warning() + addtimer(CALLBACK(src, .proc/send_in_the_fuzz), 5 MINUTES) + + check_counter = 0 + + check_tagged_turfs() + check_gang_clothes() + check_rollin_with_crews() + +/** + * set_round_result() or round_result() equivalent. + * + * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset. + * This is done by the set_round_result() or round_result() procs, at roundend. + * Sets the ticker subsystem to the correct result based off of the relative populations + * of space cops and family members. + * Takes no arguments. + */ +/datum/gang_handler/proc/set_round_result_analogue() + var/alive_gangsters = 0 + var/alive_cops = 0 + for(var/datum/mind/gangbanger in gangbangers) + if(!ishuman(gangbanger.current)) + continue + var/mob/living/carbon/human/H = gangbanger.current + if(H.stat) + continue + alive_gangsters++ + for(var/datum/mind/bacon in get_antag_minds(/datum/antagonist/ert/families)) + if(!ishuman(bacon.current)) // always returns false + continue + var/mob/living/carbon/human/H = bacon.current + if(H.stat) + continue + alive_cops++ + if(alive_gangsters > alive_cops) + SSticker.mode_result = "win - gangs survived" + SSticker.news_report = GANG_OPERATING + return TRUE + SSticker.mode_result = "loss - police destroyed the gangs" + SSticker.news_report = GANG_DESTROYED + return FALSE + +/// Internal. Announces the presence of families to the entire station and sets sent_announcement to true to allow other checks to occur. +/datum/gang_handler/proc/announce_gang_locations() + var/list/readable_gang_names = list() + for(var/GG in gangs) + var/datum/team/gang/G = GG + readable_gang_names += "[G.name]" + var/finalized_gang_names = english_list(readable_gang_names) + priority_announce("Julio G coming to you live from Radio Los Spess! We've been hearing reports of gang activity on [station_name()], with the [finalized_gang_names] duking it out, looking for fresh territory and drugs to sling! Stay safe out there for the [use_dynamic_timing ? "half-hour" : "hour"] 'till the space cops get there, and keep it cool, yeah?\n\n The local jump gates are shut down for about an hour due to some maintenance troubles, so if you wanna split from the area you're gonna have to wait [use_dynamic_timing ? "thirty minutes" : "an hour"]. \n Play music, not gunshots, I say. Peace out!", "Radio Los Spess", 'sound/voice/beepsky/radio.ogg') + sent_announcement = TRUE + check_wanted_level() // i like it when the wanted level updates at the same time as the announcement + +/// Internal. Announces that space cops will arrive in 5 minutes and sets sent_second_announcement to true to freeze +/datum/gang_handler/proc/five_minute_warning() + priority_announce("Julio G coming to you live from Radio Los Spess! The space cops are closing in on [station_name()] and will arrive in about 5 minutes! Better clear on out of there if you don't want to get hurt!", "Radio Los Spess", 'sound/voice/beepsky/radio.ogg') + sent_second_announcement = TRUE + +/// Internal. Checks if our wanted level has changed; calls update_wanted_level. Only updates wanted level post the initial announcement and until the cops show up. After that, it's locked. +/datum/gang_handler/proc/check_wanted_level() + if(cops_arrived) + update_wanted_level(wanted_level) // at this point, we still want to update people's star huds, even though they're mostly locked, because not everyone is around for the last update before the rest of this proc gets shut off forever, and that's when the wanted bar switches from gold stars to red / blue to signify the arrival of the space cops + return + if(!sent_announcement) + return + var/new_wanted_level + if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT) + switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning) // if this is a midround ruleset, we only care about the deaths since the families were activated, not since shiftstart + if(0 to TWO_STARS_HIGHPOP-1) + new_wanted_level = 1 + if(TWO_STARS_HIGHPOP to THREE_STARS_HIGHPOP-1) + new_wanted_level = 2 + if(THREE_STARS_HIGHPOP to FOUR_STARS_HIGHPOP-1) + new_wanted_level = 3 + if(FOUR_STARS_HIGHPOP to FIVE_STARS_HIGHPOP-1) + new_wanted_level = 4 + if(FIVE_STARS_HIGHPOP to INFINITY) + new_wanted_level = 5 + else + switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning) + if(0 to TWO_STARS_LOW-1) + new_wanted_level = 1 + if(TWO_STARS_LOW to THREE_STARS_LOW-1) + new_wanted_level = 2 + if(THREE_STARS_LOW to FOUR_STARS_LOW-1) + new_wanted_level = 3 + if(FOUR_STARS_LOW to FIVE_STARS_LOW-1) + new_wanted_level = 4 + if(FIVE_STARS_LOW to INFINITY) + new_wanted_level = 5 + update_wanted_level(new_wanted_level) + +/// Internal. Updates the icon states for everyone, and calls procs that send out announcements / change the end_time if the wanted level has changed. +/datum/gang_handler/proc/update_wanted_level(newlevel) + if(newlevel > wanted_level) + on_gain_wanted_level(newlevel) + else if (newlevel < wanted_level) + on_lower_wanted_level(newlevel) + wanted_level = newlevel + for(var/i in GLOB.player_list) + var/mob/M = i + if(!M.hud_used?.wanted_lvl) + continue + var/datum/hud/H = M.hud_used + H.wanted_lvl.level = newlevel + H.wanted_lvl.cops_arrived = cops_arrived + H.wanted_lvl.update_icon() + +/// Internal. Updates the end_time and sends out an announcement if the wanted level has increased. Called by update_wanted_level(). +/datum/gang_handler/proc/on_gain_wanted_level(newlevel) + var/announcement_message + switch(newlevel) + if(2) + if(!sent_second_announcement) // when you hear that they're "arriving in 5 minutes," that's a goddamn guarantee + end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "Small amount of police vehicles have been spotted en route towards [station_name()]." + if(3) + if(!sent_second_announcement) + end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "A large detachment police vehicles have been spotted en route towards [station_name()]." + if(4) + if(!sent_second_announcement) + end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "A detachment of top-trained agents has been spotted on their way to [station_name()]." + if(5) + if(!sent_second_announcement) + end_time = start_time + ((30 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "The fleet enroute to [station_name()] now consists of national guard personnel." + if(!midround_ruleset) // stops midround rulesets from announcing janky ass times + announcement_message += " They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark." + if(newlevel == 1) // specific exception to stop the announcement from triggering right after the families themselves are announced because aesthetics + return + priority_announce(announcement_message, "Station Spaceship Detection Systems") + +/// Internal. Updates the end_time and sends out an announcement if the wanted level has decreased. Called by update_wanted_level(). +/datum/gang_handler/proc/on_lower_wanted_level(newlevel) + var/announcement_message + switch(newlevel) + if(1) + if(!sent_second_announcement) + end_time = start_time + ((60 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "There are now only a few police vehicle headed towards [station_name()]." + if(2) + if(!sent_second_announcement) + end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "There seem to be fewer police vehicles headed towards [station_name()]." + if(3) + if(!sent_second_announcement) + end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "There are no longer top-trained agents in the fleet headed towards [station_name()]." + if(4) + if(!sent_second_announcement) + end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1)) + announcement_message = "The convoy enroute to [station_name()] seems to no longer consist of national guard personnel." + if(!midround_ruleset) + announcement_message += " They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark." + priority_announce(announcement_message, "Station Spaceship Detection Systems") + +/// Internal. Polls ghosts and sends in a team of space cops according to the wanted level, accompanied by an announcement. Will let the shuttle leave 10 minutes after sending. Freezes the wanted level. +/datum/gang_handler/proc/send_in_the_fuzz() + var/team_size + var/cops_to_send + var/announcement_message = "PUNK ASS BALLA BITCH" + var/announcer = "Spinward Stellar Coalition" + if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT) + switch(wanted_level) + if(1) + team_size = 8 + cops_to_send = /datum/antagonist/ert/families/beatcop + announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!" + announcer = "Spinward Stellar Coalition Police Department" + if(2) + team_size = 9 + cops_to_send = /datum/antagonist/ert/families/beatcop/armored + announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day." + announcer = "Spinward Stellar Coalition Police Department" + if(3) + team_size = 10 + cops_to_send = /datum/antagonist/ert/families/beatcop/swat + announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day." + announcer = "Spinward Stellar Coalition Police Department" + if(4) + team_size = 11 + cops_to_send = /datum/antagonist/ert/families/beatcop/fbi + announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions." + announcer = "Federal Bureau of Investigation" + if(5) + team_size = 12 + cops_to_send = /datum/antagonist/ert/families/beatcop/military + announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians." + announcer = "Spinward Stellar Coalition National Guard" + else + switch(wanted_level) + if(1) + team_size = 5 + cops_to_send = /datum/antagonist/ert/families/beatcop + announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!" + announcer = "Spinward Stellar Coalition Police Department" + if(2) + team_size = 6 + cops_to_send = /datum/antagonist/ert/families/beatcop/armored + announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day." + announcer = "Spinward Stellar Coalition Police Department" + if(3) + team_size = 7 + cops_to_send = /datum/antagonist/ert/families/beatcop/swat + announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day." + announcer = "Spinward Stellar Coalition Police Department" + if(4) + team_size = 8 + cops_to_send = /datum/antagonist/ert/families/beatcop/fbi + announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions." + announcer = "Federal Bureau of Investigation" + if(5) + team_size = 10 + cops_to_send = /datum/antagonist/ert/families/beatcop/military + announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians." + announcer = "Spinward Stellar Coalition National Guard" + + priority_announce(announcement_message, announcer, 'sound/effects/families_police.ogg') + var/list/candidates = pollGhostCandidates("Do you want to help clean up crime on this station?", "deathsquad", null) + + + if(candidates.len) + //Pick the (un)lucky players + var/numagents = min(team_size,candidates.len) + + var/list/spawnpoints = GLOB.emergencyresponseteamspawn + var/index = 0 + while(numagents && candidates.len) + var/spawnloc = spawnpoints[index+1] + //loop through spawnpoints one at a time + index = (index + 1) % spawnpoints.len + var/mob/dead/observer/chosen_candidate = pick(candidates) + candidates -= chosen_candidate + if(!chosen_candidate.key) + continue + + //Spawn the body + var/mob/living/carbon/human/cop = new(spawnloc) + chosen_candidate.client.prefs.copy_to(cop) + cop.key = chosen_candidate.key + + //Give antag datum + var/datum/antagonist/ert/families/ert_antag = new cops_to_send + + cop.mind.add_antag_datum(ert_antag) + cop.mind.assigned_role = ert_antag.name + SSjob.SendToLateJoin(cop) + + //Logging and cleanup + log_game("[key_name(cop)] has been selected as an [ert_antag.name]") + numagents-- + cops_arrived = TRUE + update_wanted_level(wanted_level) // gotta make sure everyone's wanted level display looks nice + addtimer(CALLBACK(src, .proc/end_hostile_sit), 10 MINUTES) + return TRUE + +/// Internal. Clears the hostile environment, letting the shuttle leave. +/datum/gang_handler/proc/end_hostile_sit() + SSshuttle.clearHostileEnvironment(src) + +/// Internal. Assigns points to families according to gang tags. +/datum/gang_handler/proc/check_tagged_turfs() + for(var/T in GLOB.gang_tags) + var/obj/effect/decal/cleanable/crayon/gang/tag = T + if(tag.my_gang) + tag.my_gang.adjust_points(50) + CHECK_TICK + +/// Internal. Assigns points to families according to clothing of all currently living humans. +/datum/gang_handler/proc/check_gang_clothes() // TODO: make this grab the sprite itself, average out what the primary color would be, then compare how close it is to the gang color so I don't have to manually fill shit out for 5 years for every gang type + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(!H.mind || !H.client) + continue + var/datum/antagonist/gang/is_gangster = H.mind.has_antag_datum(/datum/antagonist/gang) + for(var/clothing in list(H.head, H.wear_mask, H.wear_suit, H.w_uniform, H.back, H.gloves, H.shoes, H.belt, H.s_store, H.glasses, H.ears, H.wear_id)) + if(is_gangster) + if(is_type_in_list(clothing, is_gangster.acceptable_clothes)) + is_gangster.add_gang_points(10) + else + for(var/G in gangs) + var/datum/team/gang/gang_clothes = G + if(is_type_in_list(clothing, gang_clothes.acceptable_clothes)) + gang_clothes.adjust_points(5) + + CHECK_TICK + +/// Internal. Assigns points to families according to groups of nearby family members. +/datum/gang_handler/proc/check_rollin_with_crews() + var/list/areas_to_check = list() + for(var/G in gangbangers) + var/datum/mind/gangster = G + areas_to_check += get_area(gangster.current) + for(var/AA in areas_to_check) + var/area/A = AA + var/list/gang_members = list() + for(var/mob/living/carbon/human/H in A) + if(H.stat || !H.mind || !H.client) + continue + var/datum/antagonist/gang/is_gangster = H.mind.has_antag_datum(/datum/antagonist/gang) + if(is_gangster) + gang_members[is_gangster.my_gang]++ + CHECK_TICK + if(gang_members.len) + for(var/datum/team/gang/gangsters in gang_members) + if(gang_members[gangsters] >= CREW_SIZE_MIN) + if(gang_members[gangsters] >= CREW_SIZE_MAX) + gangsters.adjust_points(5) // Discourage larger clumps, spread ur people out + else + gangsters.adjust_points(10) + + +/// Hijacks the space cops' roundend results to say if cops / a gang won the round. Included in the same file as the gang_handler as it's far more related to the gamemode than it is to the beat cop datum; it's kind of hacky. +/datum/antagonist/ert/families/beatcop/roundend_report_footer() + var/list/all_gangs = list() + for(var/datum/team/gang/G in GLOB.antagonist_teams) + all_gangs += G + if(!all_gangs.len) + return ..() + var/list/all_gangsters = get_antag_minds(/datum/antagonist/gang) + var/list/all_cops = get_antag_minds(/datum/antagonist/ert/families) + var/report + var/highest_point_value = 0 + var/highest_gang = "Leet Like Jeff K" + var/objective_failures = TRUE + + for(var/G in all_gangs) + var/datum/team/gang/GG = G + if(GG.my_gang_datum.check_gang_objective()) + objective_failures = FALSE + break + for(var/G in all_gangs) + var/datum/team/gang/GG = G + if(!objective_failures) + if(GG.points >= highest_point_value && GG.members.len && GG.my_gang_datum.check_gang_objective()) + highest_point_value = GG.points + highest_gang = GG.name + else + if(GG.points >= highest_point_value && GG.members.len) + highest_point_value = GG.points + highest_gang = GG.name + var/alive_gangsters = 0 + var/alive_cops = 0 + for(var/M in all_gangsters) + var/datum/mind/gangbanger = M + if(gangbanger.current) + if(!ishuman(gangbanger.current)) + continue + var/mob/living/carbon/human/H = gangbanger.current + if(H.stat) + continue + alive_gangsters++ + for(var/M in all_cops) + var/datum/mind/bacon = M + if(bacon.current) + if(!ishuman(bacon.current)) // always returns false + continue + var/mob/living/carbon/human/H = bacon.current + if(H.stat) + continue + alive_cops++ + + if(alive_gangsters > alive_cops) + if(!objective_failures) + report = "[highest_gang] won the round by completing their objective and having the most points!" + else + report = "[highest_gang] won the round by having the most points!" + else if(alive_gangsters == alive_cops) + report = "Legend has it the police and the families are still duking it out to this day!" + else + report = "The police put the boots to the families, medium style!" + + return "
    [report]" //
    at the front not the back because this proc is intended for normal text not a whole new panel diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index 3b88abfb83b..eac455425ad 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -1,60 +1,60 @@ -/datum/game_mode/meteor - name = "meteor" - config_tag = "meteor" - report_type = "meteor" - false_report_weight = 1 - var/meteordelay = 2000 - var/nometeors = 0 - var/rampupdelta = 5 - required_players = 0 - - announce_span = "danger" - announce_text = "A major meteor shower is bombarding the station! The crew needs to evacuate or survive the onslaught." - - -/datum/game_mode/meteor/process() - if(nometeors || meteordelay > world.time - SSticker.round_start_time) - return - - var/list/wavetype = GLOB.meteors_normal - var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 - - - if (prob(meteorminutes)) - wavetype = GLOB.meteors_threatening - - if (prob(meteorminutes/2)) - wavetype = GLOB.meteors_catastrophic - - var/ramp_up_final = clamp(round(meteorminutes/rampupdelta), 1, 10) - - spawn_meteors(ramp_up_final, wavetype) - - -/datum/game_mode/meteor/special_report() - var/survivors = 0 - var/list/survivor_list = list() - - for(var/mob/living/player in GLOB.player_list) - if(player.stat != DEAD) - ++survivors - - if(player.onCentCom()) - survivor_list += "[player.real_name] escaped to the safety of CentCom." - else if(player.onSyndieBase()) - survivor_list += "[player.real_name] escaped to the (relative) safety of Syndicate Space." - else - survivor_list += "[player.real_name] survived but is stranded without any hope of rescue." - - if(survivors) - return "
    The following survived the meteor storm:
    [survivor_list.Join("
    ")]
    " - else - return "
    Nobody survived the meteor storm!
    " - -/datum/game_mode/meteor/set_round_result() - ..() - SSticker.mode_result = "end - evacuation" - -/datum/game_mode/meteor/generate_report() - return "[pick("Asteroids have", "Meteors have", "Large rocks have", "Stellar minerals have", "Space hail has", "Debris has")] been detected near your station, and a collision is possible, \ - though unlikely. Be prepared for largescale impacts and destruction. Please note that the debris will prevent the escape shuttle from arriving quickly." +/datum/game_mode/meteor + name = "meteor" + config_tag = "meteor" + report_type = "meteor" + false_report_weight = 1 + var/meteordelay = 2000 + var/nometeors = 0 + var/rampupdelta = 5 + required_players = 0 + + announce_span = "danger" + announce_text = "A major meteor shower is bombarding the station! The crew needs to evacuate or survive the onslaught." + + +/datum/game_mode/meteor/process() + if(nometeors || meteordelay > world.time - SSticker.round_start_time) + return + + var/list/wavetype = GLOB.meteors_normal + var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 + + + if (prob(meteorminutes)) + wavetype = GLOB.meteors_threatening + + if (prob(meteorminutes/2)) + wavetype = GLOB.meteors_catastrophic + + var/ramp_up_final = clamp(round(meteorminutes/rampupdelta), 1, 10) + + spawn_meteors(ramp_up_final, wavetype) + + +/datum/game_mode/meteor/special_report() + var/survivors = 0 + var/list/survivor_list = list() + + for(var/mob/living/player in GLOB.player_list) + if(player.stat != DEAD) + ++survivors + + if(player.onCentCom()) + survivor_list += "[player.real_name] escaped to the safety of CentCom." + else if(player.onSyndieBase()) + survivor_list += "[player.real_name] escaped to the (relative) safety of Syndicate Space." + else + survivor_list += "[player.real_name] survived but is stranded without any hope of rescue." + + if(survivors) + return "
    The following survived the meteor storm:
    [survivor_list.Join("
    ")]
    " + else + return "
    Nobody survived the meteor storm!
    " + +/datum/game_mode/meteor/set_round_result() + ..() + SSticker.mode_result = "end - evacuation" + +/datum/game_mode/meteor/generate_report() + return "[pick("Asteroids have", "Meteors have", "Large rocks have", "Stellar minerals have", "Space hail has", "Debris has")] been detected near your station, and a collision is possible, \ + though unlikely. Be prepared for largescale impacts and destruction. Please note that the debris will prevent the escape shuttle from arriving quickly." diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index 9f88e56b049..3f05dee1360 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -1,271 +1,271 @@ -//Contains the target item datums for Steal objectives. - -/datum/objective_item - var/name = "A silly bike horn! Honk!" - var/targetitem = /obj/item/bikehorn //typepath of the objective item - var/difficulty = 9001 //vaguely how hard it is to do this objective - var/list/excludefromjob = list() //If you don't want a job to get a certain objective (no captain stealing his own medal, etcetc) - var/list/altitems = list() //Items which can serve as an alternative to the objective (darn you blueprints) - var/list/special_equipment = list() - -/datum/objective_item/proc/check_special_completion() //for objectives with special checks (is that slime extract unused? does that intellicard have an ai in it? etcetc) - return 1 - -/datum/objective_item/proc/TargetExists() - return TRUE - -/datum/objective_item/steal/New() - ..() - if(TargetExists()) - GLOB.possible_items += src - else - qdel(src) - -/datum/objective_item/steal/Destroy() - GLOB.possible_items -= src - return ..() - -/datum/objective_item/steal/caplaser - name = "the captain's antique laser gun." - targetitem = /obj/item/gun/energy/laser/captain - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/hoslaser - name = "the head of security's personal laser gun." - targetitem = /obj/item/gun/energy/e_gun/hos - difficulty = 10 - excludefromjob = list("Head Of Security") - -/datum/objective_item/steal/handtele - name = "a hand teleporter." - targetitem = /obj/item/hand_tele - difficulty = 5 - excludefromjob = list("Captain", "Research Director") - -/datum/objective_item/steal/jetpack - name = "the Captain's jetpack." - targetitem = /obj/item/tank/jetpack/oxygen/captain - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/magboots - name = "the chief engineer's advanced magnetic boots." - targetitem = /obj/item/clothing/shoes/magboots/advance - difficulty = 5 - excludefromjob = list("Chief Engineer") - -/datum/objective_item/steal/capmedal - name = "the medal of captaincy." - targetitem = /obj/item/clothing/accessory/medal/gold/captain - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/hypo - name = "the hypospray." - targetitem = /obj/item/reagent_containers/hypospray/cmo - difficulty = 5 - excludefromjob = list("Chief Medical Officer") - -/datum/objective_item/steal/nukedisc - name = "the nuclear authentication disk." - targetitem = /obj/item/disk/nuclear - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/nukedisc/check_special_completion(obj/item/disk/nuclear/N) - return !N.fake - -/datum/objective_item/steal/reflector - name = "a reflector trenchcoat." - targetitem = /obj/item/clothing/suit/hooded/ablative - difficulty = 3 - excludefromjob = list("Head of Security", "Warden") - -/datum/objective_item/steal/reactive - name = "the reactive teleport armor." - targetitem = /obj/item/clothing/suit/armor/reactive/teleport - difficulty = 5 - excludefromjob = list("Research Director") - -/datum/objective_item/steal/documents - name = "any set of secret documents of any organization." - targetitem = /obj/item/documents //Any set of secret documents. Doesn't have to be NT's - difficulty = 5 - -/datum/objective_item/steal/nuke_core - name = "the heavily radioactive plutonium core from the onboard self-destruct. Take care to wear the proper safety equipment when extracting the core!" - targetitem = /obj/item/nuke_core - difficulty = 15 - -/datum/objective_item/steal/nuke_core/New() - special_equipment += /obj/item/storage/box/syndie_kit/nuke - ..() - -/datum/objective_item/steal/supermatter - name = "a sliver of a supermatter crystal. Be sure to use the proper safety equipment when extracting the sliver!" - targetitem = /obj/item/nuke_core/supermatter_sliver - difficulty = 15 - -/datum/objective_item/steal/supermatter/New() - special_equipment += /obj/item/storage/box/syndie_kit/supermatter - ..() - -/datum/objective_item/steal/supermatter/TargetExists() - return GLOB.main_supermatter_engine != null - -//Items with special checks! -/datum/objective_item/steal/plasma - name = "28 moles of plasma (full tank)." - targetitem = /obj/item/tank - difficulty = 3 - excludefromjob = list("Chief Engineer","Research Director","Station Engineer","Scientist","Atmospheric Technician") - -/datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T) - var/target_amount = text2num(name) - var/found_amount = 0 - found_amount += T.air_contents.gases[/datum/gas/plasma] ? T.air_contents.gases[/datum/gas/plasma][MOLES] : 0 - return found_amount>=target_amount - - -/datum/objective_item/steal/functionalai - name = "a functional AI." - targetitem = /obj/item/aicard - difficulty = 20 //beyond the impossible - -/datum/objective_item/steal/functionalai/check_special_completion(obj/item/aicard/C) - for(var/mob/living/silicon/ai/A in C) - if(isAI(A) && A.stat != DEAD) //See if any AI's are alive inside that card. - return 1 - return 0 - -/datum/objective_item/steal/blueprints - name = "the station blueprints." - targetitem = /obj/item/areaeditor/blueprints - difficulty = 10 - excludefromjob = list("Chief Engineer") - altitems = list(/obj/item/photo) - -/datum/objective_item/steal/blueprints/check_special_completion(obj/item/I) - if(istype(I, /obj/item/areaeditor/blueprints)) - return TRUE - if(istype(I, /obj/item/photo)) - var/obj/item/photo/P = I - if(P.picture.has_blueprints) //if the blueprints are in frame - return TRUE - return FALSE - -/datum/objective_item/steal/slime - name = "an unused sample of slime extract." - targetitem = /obj/item/slime_extract - difficulty = 3 - excludefromjob = list("Research Director","Scientist") - -/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) - if(E.Uses > 0) - return 1 - return 0 - -/datum/objective_item/steal/blackbox - name = "The Blackbox." - targetitem = /obj/item/blackbox - difficulty = 10 - excludefromjob = list("Chief Engineer","Station Engineer","Atmospheric Technician") - -//Unique Objectives -/datum/objective_item/unique/docs_red - name = "the \"Red\" secret documents." - targetitem = /obj/item/documents/syndicate/red - difficulty = 10 - -/datum/objective_item/unique/docs_blue - name = "the \"Blue\" secret documents." - targetitem = /obj/item/documents/syndicate/blue - difficulty = 10 - -/datum/objective_item/special/New() - ..() - if(TargetExists()) - GLOB.possible_items_special += src - else - qdel(src) - -/datum/objective_item/special/Destroy() - GLOB.possible_items_special -= src - return ..() - -//Old ninja objectives. -/datum/objective_item/special/pinpointer/nuke - name = "the captain's pinpointer." - targetitem = /obj/item/pinpointer - difficulty = 10 - -/datum/objective_item/special/aegun - name = "an advanced energy gun." - targetitem = /obj/item/gun/energy/e_gun/nuclear - difficulty = 10 - -/datum/objective_item/special/ddrill - name = "a diamond drill." - targetitem = /obj/item/pickaxe/drill/diamonddrill - difficulty = 10 - -/datum/objective_item/special/boh - name = "a bag of holding." - targetitem = /obj/item/storage/backpack/holding - difficulty = 10 - -/datum/objective_item/special/hypercell - name = "a hyper-capacity power cell." - targetitem = /obj/item/stock_parts/cell/hyper - difficulty = 5 - -/datum/objective_item/special/laserpointer - name = "a laser pointer." - targetitem = /obj/item/laser_pointer - difficulty = 5 - -/datum/objective_item/special/corgimeat - name = "a piece of corgi meat." - targetitem = /obj/item/reagent_containers/food/snacks/meat/slab/corgi - difficulty = 5 - -/datum/objective_item/stack/New() - ..() - if(TargetExists()) - GLOB.possible_items_special += src - else - qdel(src) - -/datum/objective_item/stack/Destroy() - GLOB.possible_items_special -= src - return ..() - -//Stack objectives get their own subtype -/datum/objective_item/stack - name = "5 cardboard." - targetitem = /obj/item/stack/sheet/cardboard - difficulty = 9001 - -/datum/objective_item/stack/check_special_completion(obj/item/stack/S) - var/target_amount = text2num(name) - var/found_amount = 0 - - if(istype(S, targetitem)) - found_amount = S.amount - return found_amount>=target_amount - -/datum/objective_item/stack/diamond - name = "10 diamonds." - targetitem = /obj/item/stack/sheet/mineral/diamond - difficulty = 10 - -/datum/objective_item/stack/gold - name = "50 gold bars." - targetitem = /obj/item/stack/sheet/mineral/gold - difficulty = 15 - -/datum/objective_item/stack/uranium - name = "25 refined uranium bars." - targetitem = /obj/item/stack/sheet/mineral/uranium - difficulty = 10 +//Contains the target item datums for Steal objectives. + +/datum/objective_item + var/name = "A silly bike horn! Honk!" + var/targetitem = /obj/item/bikehorn //typepath of the objective item + var/difficulty = 9001 //vaguely how hard it is to do this objective + var/list/excludefromjob = list() //If you don't want a job to get a certain objective (no captain stealing his own medal, etcetc) + var/list/altitems = list() //Items which can serve as an alternative to the objective (darn you blueprints) + var/list/special_equipment = list() + +/datum/objective_item/proc/check_special_completion() //for objectives with special checks (is that slime extract unused? does that intellicard have an ai in it? etcetc) + return 1 + +/datum/objective_item/proc/TargetExists() + return TRUE + +/datum/objective_item/steal/New() + ..() + if(TargetExists()) + GLOB.possible_items += src + else + qdel(src) + +/datum/objective_item/steal/Destroy() + GLOB.possible_items -= src + return ..() + +/datum/objective_item/steal/caplaser + name = "the captain's antique laser gun." + targetitem = /obj/item/gun/energy/laser/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/hoslaser + name = "the head of security's personal laser gun." + targetitem = /obj/item/gun/energy/e_gun/hos + difficulty = 10 + excludefromjob = list("Head Of Security") + +/datum/objective_item/steal/handtele + name = "a hand teleporter." + targetitem = /obj/item/hand_tele + difficulty = 5 + excludefromjob = list("Captain", "Research Director") + +/datum/objective_item/steal/jetpack + name = "the Captain's jetpack." + targetitem = /obj/item/tank/jetpack/oxygen/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/magboots + name = "the chief engineer's advanced magnetic boots." + targetitem = /obj/item/clothing/shoes/magboots/advance + difficulty = 5 + excludefromjob = list("Chief Engineer") + +/datum/objective_item/steal/capmedal + name = "the medal of captaincy." + targetitem = /obj/item/clothing/accessory/medal/gold/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/hypo + name = "the hypospray." + targetitem = /obj/item/reagent_containers/hypospray/cmo + difficulty = 5 + excludefromjob = list("Chief Medical Officer") + +/datum/objective_item/steal/nukedisc + name = "the nuclear authentication disk." + targetitem = /obj/item/disk/nuclear + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/nukedisc/check_special_completion(obj/item/disk/nuclear/N) + return !N.fake + +/datum/objective_item/steal/reflector + name = "a reflector trenchcoat." + targetitem = /obj/item/clothing/suit/hooded/ablative + difficulty = 3 + excludefromjob = list("Head of Security", "Warden") + +/datum/objective_item/steal/reactive + name = "the reactive teleport armor." + targetitem = /obj/item/clothing/suit/armor/reactive/teleport + difficulty = 5 + excludefromjob = list("Research Director") + +/datum/objective_item/steal/documents + name = "any set of secret documents of any organization." + targetitem = /obj/item/documents //Any set of secret documents. Doesn't have to be NT's + difficulty = 5 + +/datum/objective_item/steal/nuke_core + name = "the heavily radioactive plutonium core from the onboard self-destruct. Take care to wear the proper safety equipment when extracting the core!" + targetitem = /obj/item/nuke_core + difficulty = 15 + +/datum/objective_item/steal/nuke_core/New() + special_equipment += /obj/item/storage/box/syndie_kit/nuke + ..() + +/datum/objective_item/steal/supermatter + name = "a sliver of a supermatter crystal. Be sure to use the proper safety equipment when extracting the sliver!" + targetitem = /obj/item/nuke_core/supermatter_sliver + difficulty = 15 + +/datum/objective_item/steal/supermatter/New() + special_equipment += /obj/item/storage/box/syndie_kit/supermatter + ..() + +/datum/objective_item/steal/supermatter/TargetExists() + return GLOB.main_supermatter_engine != null + +//Items with special checks! +/datum/objective_item/steal/plasma + name = "28 moles of plasma (full tank)." + targetitem = /obj/item/tank + difficulty = 3 + excludefromjob = list("Chief Engineer","Research Director","Station Engineer","Scientist","Atmospheric Technician") + +/datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T) + var/target_amount = text2num(name) + var/found_amount = 0 + found_amount += T.air_contents.gases[/datum/gas/plasma] ? T.air_contents.gases[/datum/gas/plasma][MOLES] : 0 + return found_amount>=target_amount + + +/datum/objective_item/steal/functionalai + name = "a functional AI." + targetitem = /obj/item/aicard + difficulty = 20 //beyond the impossible + +/datum/objective_item/steal/functionalai/check_special_completion(obj/item/aicard/C) + for(var/mob/living/silicon/ai/A in C) + if(isAI(A) && A.stat != DEAD) //See if any AI's are alive inside that card. + return 1 + return 0 + +/datum/objective_item/steal/blueprints + name = "the station blueprints." + targetitem = /obj/item/areaeditor/blueprints + difficulty = 10 + excludefromjob = list("Chief Engineer") + altitems = list(/obj/item/photo) + +/datum/objective_item/steal/blueprints/check_special_completion(obj/item/I) + if(istype(I, /obj/item/areaeditor/blueprints)) + return TRUE + if(istype(I, /obj/item/photo)) + var/obj/item/photo/P = I + if(P.picture.has_blueprints) //if the blueprints are in frame + return TRUE + return FALSE + +/datum/objective_item/steal/slime + name = "an unused sample of slime extract." + targetitem = /obj/item/slime_extract + difficulty = 3 + excludefromjob = list("Research Director","Scientist") + +/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) + if(E.Uses > 0) + return 1 + return 0 + +/datum/objective_item/steal/blackbox + name = "The Blackbox." + targetitem = /obj/item/blackbox + difficulty = 10 + excludefromjob = list("Chief Engineer","Station Engineer","Atmospheric Technician") + +//Unique Objectives +/datum/objective_item/unique/docs_red + name = "the \"Red\" secret documents." + targetitem = /obj/item/documents/syndicate/red + difficulty = 10 + +/datum/objective_item/unique/docs_blue + name = "the \"Blue\" secret documents." + targetitem = /obj/item/documents/syndicate/blue + difficulty = 10 + +/datum/objective_item/special/New() + ..() + if(TargetExists()) + GLOB.possible_items_special += src + else + qdel(src) + +/datum/objective_item/special/Destroy() + GLOB.possible_items_special -= src + return ..() + +//Old ninja objectives. +/datum/objective_item/special/pinpointer/nuke + name = "the captain's pinpointer." + targetitem = /obj/item/pinpointer + difficulty = 10 + +/datum/objective_item/special/aegun + name = "an advanced energy gun." + targetitem = /obj/item/gun/energy/e_gun/nuclear + difficulty = 10 + +/datum/objective_item/special/ddrill + name = "a diamond drill." + targetitem = /obj/item/pickaxe/drill/diamonddrill + difficulty = 10 + +/datum/objective_item/special/boh + name = "a bag of holding." + targetitem = /obj/item/storage/backpack/holding + difficulty = 10 + +/datum/objective_item/special/hypercell + name = "a hyper-capacity power cell." + targetitem = /obj/item/stock_parts/cell/hyper + difficulty = 5 + +/datum/objective_item/special/laserpointer + name = "a laser pointer." + targetitem = /obj/item/laser_pointer + difficulty = 5 + +/datum/objective_item/special/corgimeat + name = "a piece of corgi meat." + targetitem = /obj/item/reagent_containers/food/snacks/meat/slab/corgi + difficulty = 5 + +/datum/objective_item/stack/New() + ..() + if(TargetExists()) + GLOB.possible_items_special += src + else + qdel(src) + +/datum/objective_item/stack/Destroy() + GLOB.possible_items_special -= src + return ..() + +//Stack objectives get their own subtype +/datum/objective_item/stack + name = "5 cardboard." + targetitem = /obj/item/stack/sheet/cardboard + difficulty = 9001 + +/datum/objective_item/stack/check_special_completion(obj/item/stack/S) + var/target_amount = text2num(name) + var/found_amount = 0 + + if(istype(S, targetitem)) + found_amount = S.amount + return found_amount>=target_amount + +/datum/objective_item/stack/diamond + name = "10 diamonds." + targetitem = /obj/item/stack/sheet/mineral/diamond + difficulty = 10 + +/datum/objective_item/stack/gold + name = "50 gold bars." + targetitem = /obj/item/stack/sheet/mineral/gold + difficulty = 15 + +/datum/objective_item/stack/uranium + name = "25 refined uranium bars." + targetitem = /obj/item/stack/sheet/mineral/uranium + difficulty = 10 diff --git a/code/game/gamemodes/sandbox/airlock_maker.dm b/code/game/gamemodes/sandbox/airlock_maker.dm index 6b988fd0772..ddb622ab08c 100644 --- a/code/game/gamemodes/sandbox/airlock_maker.dm +++ b/code/game/gamemodes/sandbox/airlock_maker.dm @@ -1,141 +1,141 @@ -/* - This is for the sandbox for now, - maybe useful later for an actual thing? - -Sayu -*/ - -/obj/structure/door_assembly - var/datum/airlock_maker/maker = null - -/obj/structure/door_assembly/attack_hand() - . = ..() - if(.) - return - if(maker) - maker.interact() - -/datum/airlock_maker - var/obj/structure/door_assembly/linked = null - - var/list/access_used = null - var/require_all = 1 - - var/paintjob = "none" - var/glassdoor = 0 - - var/doorname = "airlock" - -/datum/airlock_maker/New(var/atom/target_loc) - linked = new(target_loc) - linked.maker = src - linked.anchored = FALSE - access_used = list() - - interact() - -/datum/airlock_maker/proc/linkpretty(href,desc,active) - if(!desc) - var/static/list/defaults = list("No","Yes") - desc = defaults[active+1] - if(active) - return "[desc]" - return "[desc]" - -/datum/airlock_maker/proc/interact() - var/list/leftcolumn = list() - var/list/rightcolumn = list() - leftcolumn += "Required Access" - for(var/access in get_all_accesses()) - leftcolumn += linkpretty("access=[access]",get_access_desc(access),access in access_used) - leftcolumn += "Require all listed accesses: [linkpretty("reqall",null,require_all)]" - - rightcolumn += "Paintjob" - for(var/option in list("none","engineering","atmos","security","command","medical","research","mining","maintenance","external","highsecurity")) - rightcolumn += linkpretty("paint=[option]",option,option == paintjob) - rightcolumn += "Glass door: " + linkpretty("glass",null,glassdoor) + "

    " - var/length = max(leftcolumn.len,rightcolumn.len) - - var/dat = "You may move the model airlock around. A new airlock will be built in its space when you click done, below.

    " - dat += "Door name: \"[doorname]\"" - dat += "" - for(var/i=1; i<=length; i++) - dat += "" - - dat += "
    " - if(i<=leftcolumn.len) - dat += leftcolumn[i] - dat += "" - if(i<=rightcolumn.len) - dat += rightcolumn[i] - dat += "

    Finalize Airlock Construction | Cancel and Destroy Airlock" - usr << browse(dat,"window=airlockmaker") - -/datum/airlock_maker/Topic(var/href,var/list/href_list) - if(!usr) - return - if(!src || !linked || !linked.loc) - usr << browse(null,"window=airlockmaker") - return - - if("rename" in href_list) - var/newname = stripped_input(usr,"New airlock name:","Name the airlock",doorname) - if(newname) - doorname = newname - if("access" in href_list) - var/value = text2num(href_list["access"]) - access_used ^= value - if("reqall" in href_list) - require_all = !require_all - if("paint" in href_list) - paintjob = href_list["paint"] - if("glass" in href_list) - glassdoor = !glassdoor - - if("cancel" in href_list) - usr << browse(null,"window=airlockmaker") - qdel(linked) - qdel(src) - return - - if("done" in href_list) - usr << browse(null,"window=airlockmaker") - var/turf/t_loc = linked.loc - qdel(linked) - if(!istype(t_loc)) - return - - var/target_type = "/obj/machinery/door/airlock" - if(glassdoor) - if(paintjob != "none") - if(paintjob in list("external","highsecurity","maintenance")) // no glass version - target_type += "/[paintjob]" - else - target_type += "/glass_[paintjob]" - else - target_type += "/glass" - else if(paintjob != "none") - target_type += "/[paintjob]" - var/final = target_type - target_type = text2path(final) - if(!target_type) - to_chat(usr, "Didn't work, contact Sayu with this: [final]") - usr << browse(null,"window=airlockmaker") - return - - var/obj/machinery/door/D = new target_type(t_loc) - - D.name = doorname - - if(access_used.len == 0) - D.req_access = null - D.req_one_access = null - else if(require_all) - D.req_access = access_used.Copy() - D.req_one_access = null - else - D.req_access = null - D.req_one_access = access_used.Copy() - - return - - interact() +/* + This is for the sandbox for now, + maybe useful later for an actual thing? + -Sayu +*/ + +/obj/structure/door_assembly + var/datum/airlock_maker/maker = null + +/obj/structure/door_assembly/attack_hand() + . = ..() + if(.) + return + if(maker) + maker.interact() + +/datum/airlock_maker + var/obj/structure/door_assembly/linked = null + + var/list/access_used = null + var/require_all = 1 + + var/paintjob = "none" + var/glassdoor = 0 + + var/doorname = "airlock" + +/datum/airlock_maker/New(var/atom/target_loc) + linked = new(target_loc) + linked.maker = src + linked.anchored = FALSE + access_used = list() + + interact() + +/datum/airlock_maker/proc/linkpretty(href,desc,active) + if(!desc) + var/static/list/defaults = list("No","Yes") + desc = defaults[active+1] + if(active) + return "[desc]" + return "[desc]" + +/datum/airlock_maker/proc/interact() + var/list/leftcolumn = list() + var/list/rightcolumn = list() + leftcolumn += "Required Access" + for(var/access in get_all_accesses()) + leftcolumn += linkpretty("access=[access]",get_access_desc(access),access in access_used) + leftcolumn += "Require all listed accesses: [linkpretty("reqall",null,require_all)]" + + rightcolumn += "Paintjob" + for(var/option in list("none","engineering","atmos","security","command","medical","research","mining","maintenance","external","highsecurity")) + rightcolumn += linkpretty("paint=[option]",option,option == paintjob) + rightcolumn += "Glass door: " + linkpretty("glass",null,glassdoor) + "

    " + var/length = max(leftcolumn.len,rightcolumn.len) + + var/dat = "You may move the model airlock around. A new airlock will be built in its space when you click done, below.

    " + dat += "Door name: \"[doorname]\"" + dat += "" + for(var/i=1; i<=length; i++) + dat += "" + + dat += "
    " + if(i<=leftcolumn.len) + dat += leftcolumn[i] + dat += "" + if(i<=rightcolumn.len) + dat += rightcolumn[i] + dat += "

    Finalize Airlock Construction | Cancel and Destroy Airlock" + usr << browse(dat,"window=airlockmaker") + +/datum/airlock_maker/Topic(var/href,var/list/href_list) + if(!usr) + return + if(!src || !linked || !linked.loc) + usr << browse(null,"window=airlockmaker") + return + + if("rename" in href_list) + var/newname = stripped_input(usr,"New airlock name:","Name the airlock",doorname) + if(newname) + doorname = newname + if("access" in href_list) + var/value = text2num(href_list["access"]) + access_used ^= value + if("reqall" in href_list) + require_all = !require_all + if("paint" in href_list) + paintjob = href_list["paint"] + if("glass" in href_list) + glassdoor = !glassdoor + + if("cancel" in href_list) + usr << browse(null,"window=airlockmaker") + qdel(linked) + qdel(src) + return + + if("done" in href_list) + usr << browse(null,"window=airlockmaker") + var/turf/t_loc = linked.loc + qdel(linked) + if(!istype(t_loc)) + return + + var/target_type = "/obj/machinery/door/airlock" + if(glassdoor) + if(paintjob != "none") + if(paintjob in list("external","highsecurity","maintenance")) // no glass version + target_type += "/[paintjob]" + else + target_type += "/glass_[paintjob]" + else + target_type += "/glass" + else if(paintjob != "none") + target_type += "/[paintjob]" + var/final = target_type + target_type = text2path(final) + if(!target_type) + to_chat(usr, "Didn't work, contact Sayu with this: [final]") + usr << browse(null,"window=airlockmaker") + return + + var/obj/machinery/door/D = new target_type(t_loc) + + D.name = doorname + + if(access_used.len == 0) + D.req_access = null + D.req_one_access = null + else if(require_all) + D.req_access = access_used.Copy() + D.req_one_access = null + else + D.req_access = null + D.req_one_access = access_used.Copy() + + return + + interact() diff --git a/code/game/gamemodes/sandbox/h_sandbox.dm b/code/game/gamemodes/sandbox/h_sandbox.dm index 5d88e64fab7..9d41e451adf 100644 --- a/code/game/gamemodes/sandbox/h_sandbox.dm +++ b/code/game/gamemodes/sandbox/h_sandbox.dm @@ -1,302 +1,302 @@ -GLOBAL_VAR_INIT(hsboxspawn, TRUE) - -/mob/proc/CanBuild() - sandbox = new/datum/h_sandbox - sandbox.owner = src.ckey - if(src.client.holder) - sandbox.admin = 1 - verbs += new/mob/proc/sandbox_panel -/mob/proc/sandbox_panel() - set name = "Sandbox Panel" - if(sandbox) - sandbox.update() - -/datum/h_sandbox - var/owner = null - var/admin = 0 - - var/static/clothinfo = null - var/static/reaginfo = null - var/static/objinfo = null - var/canisterinfo = null - var/hsbinfo = null - //items that shouldn't spawn on the floor because they would bug or act weird - var/static/list/spawn_forbidden = list( - /obj/item/tk_grab, /obj/item/implant, // not implanter, the actual thing that is inside you - /obj/item/assembly, /obj/item/onetankbomb, /obj/item/pda/ai, - /obj/item/small_delivery, /obj/projectile, - /obj/item/borg/sight, /obj/item/borg/stun, /obj/item/robot_module) - -/datum/h_sandbox/proc/update() - var/static/list/hrefs = list( - "Space Gear", - "Suit Up (Space Travel Gear)" = "hsbsuit", - "Spawn Gas Mask" = "hsbspawn&path=[/obj/item/clothing/mask/gas]", - "Spawn Emergency Air Tank" = "hsbspawn&path=[/obj/item/tank/internals/emergency_oxygen/double]", - - "Standard Tools", - "Spawn Flashlight" = "hsbspawn&path=[/obj/item/flashlight]", - "Spawn Toolbox" = "hsbspawn&path=[/obj/item/storage/toolbox/mechanical]", - "Spawn Experimental Welding tool" = "hsbspawn&path=[/obj/item/weldingtool/experimental]", - "Spawn Light Replacer" = "hsbspawn&path=[/obj/item/lightreplacer]", - "Spawn Medical Kit" = "hsbspawn&path=[/obj/item/storage/firstaid/regular]", - "Spawn All-Access ID" = "hsbaaid", - - "Building Supplies", - "Spawn 50 Wood" = "hsbwood", - "Spawn 50 Metal" = "hsbmetal", - "Spawn 50 Plasteel" = "hsbplasteel", - "Spawn 50 Reinforced Glass" = "hsbrglass", - "Spawn 50 Glass" = "hsbglass", - "Spawn Box of Materials" = "hsbspawn&path=[/obj/item/storage/box/material]", - "Spawn Full Cable Coil" = "hsbspawn&path=[/obj/item/stack/cable_coil]", - "Spawn Hyper Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/hyper]", - "Spawn Inf. Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/infinite]", - "Spawn Rapid Construction Device" = "hsbrcd", - "Spawn RCD Ammo" = "hsb_safespawn&path=[/obj/item/rcd_ammo]", - "Spawn Airlock" = "hsbairlock", - - "Miscellaneous", - "Spawn Air Scrubber" = "hsbscrubber", - "Spawn CentCom Technology Disk" = "hsbspawn&path=[/obj/item/disk/tech_disk/debug]", - "Spawn Adminordrazine" = "hsbspawn&path=[/obj/item/reagent_containers/pill/adminordrazine]", - "Spawn Water Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/watertank]", - - "Bots", - "Spawn Cleanbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/cleanbot]", - "Spawn Floorbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/floorbot]", - "Spawn Medbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/medbot]", - - "Canisters", - "Spawn O2 Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/oxygen]", - "Spawn Air Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/air]") - - - if(!hsbinfo) - hsbinfo = "
    Sandbox Panel

    " - if(admin) - hsbinfo += "Administration
    " - hsbinfo += "- Toggle Object Spawning
    " - hsbinfo += "- Toggle Item Spawn Panel Auto-close
    " - hsbinfo += "Canister Spawning
    " - hsbinfo += "- Spawn Plasma Canister
    " - hsbinfo += "- Spawn CO2 Canister
    " - hsbinfo += "- Spawn Nitrogen Canister
    " - hsbinfo += "- Spawn N2O Canister
    " - else - hsbinfo += "Some item spawning may be disabled by the administrators.
    " - hsbinfo += "Only administrators may spawn dangerous canisters.
    " - for(var/T in hrefs) - var/href = hrefs[T] - if(href) - hsbinfo += "- [T]
    " - else - hsbinfo += "
    [T]
    " - hsbinfo += "
    " - hsbinfo += "- Spawn Clothing...
    " - hsbinfo += "- Spawn Reagent Container...
    " - hsbinfo += "- Spawn Other Item...

    " - - usr << browse(hsbinfo, "window=hsbpanel") - -/datum/h_sandbox/Topic(href, href_list) - if(!usr || !src || !(src.owner == usr.ckey)) - if(usr) - usr << browse(null,"window=sandbox") - return - - if(href_list["hsb"]) - switch(href_list["hsb"]) - // - // Admin: toggle spawning - // - if("hsbtobj") - if(!admin) return - if(GLOB.hsboxspawn) - to_chat(world, "Sandbox: \black[usr.key] has disabled object spawning!") - GLOB.hsboxspawn = FALSE - return - else - to_chat(world, "Sandbox: \black[usr.key] has enabled object spawning!") - GLOB.hsboxspawn = TRUE - return - // - // Admin: Toggle auto-close - // - if("hsbtac") - if(!admin) return - var/sbac = CONFIG_GET(flag/sandbox_autoclose) - if(sbac) - to_chat(world, "Sandbox: \black [usr.key] has removed the object spawn limiter.") - else - to_chat(world, "Sandbox: \black [usr.key] has added a limiter to object spawning. The window will now auto-close after use.") - CONFIG_SET(flag/sandbox_autoclose, !sbac) - return - // - // Spacesuit with full air jetpack set as internals - // - if("hsbsuit") - var/mob/living/carbon/human/P = usr - if(!istype(P)) return - if(P.wear_suit) - P.wear_suit.forceMove(P.drop_location()) - P.wear_suit.layer = initial(P.wear_suit.layer) - P.wear_suit.plane = initial(P.wear_suit.plane) - P.wear_suit = null - P.wear_suit = new/obj/item/clothing/suit/space(P) - P.wear_suit.layer = ABOVE_HUD_LAYER - P.wear_suit.plane = ABOVE_HUD_PLANE - P.update_inv_wear_suit() - if(P.head) - P.head.forceMove(P.drop_location()) - P.head.layer = initial(P.head.layer) - P.head.plane = initial(P.head.plane) - P.head = null - P.head = new/obj/item/clothing/head/helmet/space(P) - P.head.layer = ABOVE_HUD_LAYER - P.head.plane = ABOVE_HUD_PLANE - P.update_inv_head() - if(P.wear_mask) - P.wear_mask.forceMove(P.drop_location()) - P.wear_mask.layer = initial(P.wear_mask.layer) - P.wear_mask.plane = initial(P.wear_mask.plane) - P.wear_mask = null - P.wear_mask = new/obj/item/clothing/mask/gas(P) - P.wear_mask.layer = ABOVE_HUD_LAYER - P.wear_mask.plane = ABOVE_HUD_PLANE - P.update_inv_wear_mask() - if(P.back) - P.back.forceMove(P.drop_location()) - P.back.layer = initial(P.back.layer) - P.back.plane = initial(P.back.plane) - P.back = null - P.back = new/obj/item/tank/jetpack/oxygen(P) - P.back.layer = ABOVE_HUD_LAYER - P.back.plane = ABOVE_HUD_PLANE - P.update_inv_back() - P.internal = P.back - P.update_internals_hud_icon(1) - - if("hsbscrubber") // This is beyond its normal capability but this is sandbox and you spawned one, I assume you need it - var/obj/hsb = new/obj/machinery/portable_atmospherics/scrubber{volume_rate=50*ONE_ATMOSPHERE;on=1}(usr.loc) - hsb.update_icon() // hackish but it wasn't meant to be spawned I guess? - - // - // Stacked Materials - // - - if("hsbrglass") - new/obj/item/stack/sheet/rglass{amount=50}(usr.loc) - - if("hsbmetal") - new/obj/item/stack/sheet/metal{amount=50}(usr.loc) - - if("hsbplasteel") - new/obj/item/stack/sheet/plasteel{amount=50}(usr.loc) - - if("hsbglass") - new/obj/item/stack/sheet/glass{amount=50}(usr.loc) - - if("hsbwood") - new/obj/item/stack/sheet/mineral/wood{amount=50}(usr.loc) - - // - // All access ID - // - if("hsbaaid") - var/obj/item/card/id/gold/ID = new(usr.loc) - ID.registered_name = usr.real_name - ID.assignment = "Sandbox" - ID.access = get_all_accesses() - ID.update_label() - - // - // RCD - starts with full clip - // Spawn check due to grief potential (destroying floors, walls, etc) - // - if("hsbrcd") - if(!GLOB.hsboxspawn) return - - new/obj/item/construction/rcd/combat(usr.loc) - - // - // New sandbox airlock maker - // - if("hsbairlock") - new /datum/airlock_maker(usr.loc) - - // - // Object spawn window - // - - // Clothing - if("hsbcloth") - if(!GLOB.hsboxspawn) return - - if(!clothinfo) - clothinfo = "Clothing (Reagent Containers) (Other Items)

    " - var/list/all_items = subtypesof(/obj/item/clothing) - for(var/typekey in spawn_forbidden) - all_items -= typesof(typekey) - for(var/O in reverseRange(all_items)) - clothinfo += "[O]
    " - - usr << browse(clothinfo,"window=sandbox") - - // Reagent containers - if("hsbreag") - if(!GLOB.hsboxspawn) return - - if(!reaginfo) - reaginfo = "Reagent Containers (Clothing) (Other Items)

    " - var/list/all_items = subtypesof(/obj/item/reagent_containers) - for(var/typekey in spawn_forbidden) - all_items -= typesof(typekey) - for(var/O in reverseRange(all_items)) - reaginfo += "[O]
    " - - usr << browse(reaginfo,"window=sandbox") - - // Other items - if("hsbobj") - if(!GLOB.hsboxspawn) return - - if(!objinfo) - objinfo = "Other Items (Clothing) (Reagent Containers)

    " - var/list/all_items = subtypesof(/obj/item/) - typesof(/obj/item/clothing) - typesof(/obj/item/reagent_containers) - for(var/typekey in spawn_forbidden) - all_items -= typesof(typekey) - - for(var/O in reverseRange(all_items)) - objinfo += "[O]
    " - - usr << browse(objinfo,"window=sandbox") - - // - // Safespawn checks to see if spawning is disabled. - // - if("hsb_safespawn") - if(!GLOB.hsboxspawn) - usr << browse(null,"window=sandbox") - return - - var/typepath = text2path(href_list["path"]) - if(!typepath) - to_chat(usr, "Bad path: \"[href_list["path"]]\"") - return - new typepath(usr.loc) - - if(CONFIG_GET(flag/sandbox_autoclose)) - usr << browse(null,"window=sandbox") - // - // For everything else in the href list - // - if("hsbspawn") - var/typepath = text2path(href_list["path"]) - if(!typepath) - to_chat(usr, "Bad path: \"[href_list["path"]]\"") - return - new typepath(usr.loc) - - if(CONFIG_GET(flag/sandbox_autoclose)) - usr << browse(null,"window=sandbox") +GLOBAL_VAR_INIT(hsboxspawn, TRUE) + +/mob/proc/CanBuild() + sandbox = new/datum/h_sandbox + sandbox.owner = src.ckey + if(src.client.holder) + sandbox.admin = 1 + verbs += new/mob/proc/sandbox_panel +/mob/proc/sandbox_panel() + set name = "Sandbox Panel" + if(sandbox) + sandbox.update() + +/datum/h_sandbox + var/owner = null + var/admin = 0 + + var/static/clothinfo = null + var/static/reaginfo = null + var/static/objinfo = null + var/canisterinfo = null + var/hsbinfo = null + //items that shouldn't spawn on the floor because they would bug or act weird + var/static/list/spawn_forbidden = list( + /obj/item/tk_grab, /obj/item/implant, // not implanter, the actual thing that is inside you + /obj/item/assembly, /obj/item/onetankbomb, /obj/item/pda/ai, + /obj/item/small_delivery, /obj/projectile, + /obj/item/borg/sight, /obj/item/borg/stun, /obj/item/robot_module) + +/datum/h_sandbox/proc/update() + var/static/list/hrefs = list( + "Space Gear", + "Suit Up (Space Travel Gear)" = "hsbsuit", + "Spawn Gas Mask" = "hsbspawn&path=[/obj/item/clothing/mask/gas]", + "Spawn Emergency Air Tank" = "hsbspawn&path=[/obj/item/tank/internals/emergency_oxygen/double]", + + "Standard Tools", + "Spawn Flashlight" = "hsbspawn&path=[/obj/item/flashlight]", + "Spawn Toolbox" = "hsbspawn&path=[/obj/item/storage/toolbox/mechanical]", + "Spawn Experimental Welding tool" = "hsbspawn&path=[/obj/item/weldingtool/experimental]", + "Spawn Light Replacer" = "hsbspawn&path=[/obj/item/lightreplacer]", + "Spawn Medical Kit" = "hsbspawn&path=[/obj/item/storage/firstaid/regular]", + "Spawn All-Access ID" = "hsbaaid", + + "Building Supplies", + "Spawn 50 Wood" = "hsbwood", + "Spawn 50 Metal" = "hsbmetal", + "Spawn 50 Plasteel" = "hsbplasteel", + "Spawn 50 Reinforced Glass" = "hsbrglass", + "Spawn 50 Glass" = "hsbglass", + "Spawn Box of Materials" = "hsbspawn&path=[/obj/item/storage/box/material]", + "Spawn Full Cable Coil" = "hsbspawn&path=[/obj/item/stack/cable_coil]", + "Spawn Hyper Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/hyper]", + "Spawn Inf. Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/infinite]", + "Spawn Rapid Construction Device" = "hsbrcd", + "Spawn RCD Ammo" = "hsb_safespawn&path=[/obj/item/rcd_ammo]", + "Spawn Airlock" = "hsbairlock", + + "Miscellaneous", + "Spawn Air Scrubber" = "hsbscrubber", + "Spawn CentCom Technology Disk" = "hsbspawn&path=[/obj/item/disk/tech_disk/debug]", + "Spawn Adminordrazine" = "hsbspawn&path=[/obj/item/reagent_containers/pill/adminordrazine]", + "Spawn Water Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/watertank]", + + "Bots", + "Spawn Cleanbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/cleanbot]", + "Spawn Floorbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/floorbot]", + "Spawn Medbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/medbot]", + + "Canisters", + "Spawn O2 Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/oxygen]", + "Spawn Air Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/air]") + + + if(!hsbinfo) + hsbinfo = "
    Sandbox Panel

    " + if(admin) + hsbinfo += "Administration
    " + hsbinfo += "- Toggle Object Spawning
    " + hsbinfo += "- Toggle Item Spawn Panel Auto-close
    " + hsbinfo += "Canister Spawning
    " + hsbinfo += "- Spawn Plasma Canister
    " + hsbinfo += "- Spawn CO2 Canister
    " + hsbinfo += "- Spawn Nitrogen Canister
    " + hsbinfo += "- Spawn N2O Canister
    " + else + hsbinfo += "Some item spawning may be disabled by the administrators.
    " + hsbinfo += "Only administrators may spawn dangerous canisters.
    " + for(var/T in hrefs) + var/href = hrefs[T] + if(href) + hsbinfo += "- [T]
    " + else + hsbinfo += "
    [T]
    " + hsbinfo += "
    " + hsbinfo += "- Spawn Clothing...
    " + hsbinfo += "- Spawn Reagent Container...
    " + hsbinfo += "- Spawn Other Item...

    " + + usr << browse(hsbinfo, "window=hsbpanel") + +/datum/h_sandbox/Topic(href, href_list) + if(!usr || !src || !(src.owner == usr.ckey)) + if(usr) + usr << browse(null,"window=sandbox") + return + + if(href_list["hsb"]) + switch(href_list["hsb"]) + // + // Admin: toggle spawning + // + if("hsbtobj") + if(!admin) return + if(GLOB.hsboxspawn) + to_chat(world, "Sandbox: \black[usr.key] has disabled object spawning!") + GLOB.hsboxspawn = FALSE + return + else + to_chat(world, "Sandbox: \black[usr.key] has enabled object spawning!") + GLOB.hsboxspawn = TRUE + return + // + // Admin: Toggle auto-close + // + if("hsbtac") + if(!admin) return + var/sbac = CONFIG_GET(flag/sandbox_autoclose) + if(sbac) + to_chat(world, "Sandbox: \black [usr.key] has removed the object spawn limiter.") + else + to_chat(world, "Sandbox: \black [usr.key] has added a limiter to object spawning. The window will now auto-close after use.") + CONFIG_SET(flag/sandbox_autoclose, !sbac) + return + // + // Spacesuit with full air jetpack set as internals + // + if("hsbsuit") + var/mob/living/carbon/human/P = usr + if(!istype(P)) return + if(P.wear_suit) + P.wear_suit.forceMove(P.drop_location()) + P.wear_suit.layer = initial(P.wear_suit.layer) + P.wear_suit.plane = initial(P.wear_suit.plane) + P.wear_suit = null + P.wear_suit = new/obj/item/clothing/suit/space(P) + P.wear_suit.layer = ABOVE_HUD_LAYER + P.wear_suit.plane = ABOVE_HUD_PLANE + P.update_inv_wear_suit() + if(P.head) + P.head.forceMove(P.drop_location()) + P.head.layer = initial(P.head.layer) + P.head.plane = initial(P.head.plane) + P.head = null + P.head = new/obj/item/clothing/head/helmet/space(P) + P.head.layer = ABOVE_HUD_LAYER + P.head.plane = ABOVE_HUD_PLANE + P.update_inv_head() + if(P.wear_mask) + P.wear_mask.forceMove(P.drop_location()) + P.wear_mask.layer = initial(P.wear_mask.layer) + P.wear_mask.plane = initial(P.wear_mask.plane) + P.wear_mask = null + P.wear_mask = new/obj/item/clothing/mask/gas(P) + P.wear_mask.layer = ABOVE_HUD_LAYER + P.wear_mask.plane = ABOVE_HUD_PLANE + P.update_inv_wear_mask() + if(P.back) + P.back.forceMove(P.drop_location()) + P.back.layer = initial(P.back.layer) + P.back.plane = initial(P.back.plane) + P.back = null + P.back = new/obj/item/tank/jetpack/oxygen(P) + P.back.layer = ABOVE_HUD_LAYER + P.back.plane = ABOVE_HUD_PLANE + P.update_inv_back() + P.internal = P.back + P.update_internals_hud_icon(1) + + if("hsbscrubber") // This is beyond its normal capability but this is sandbox and you spawned one, I assume you need it + var/obj/hsb = new/obj/machinery/portable_atmospherics/scrubber{volume_rate=50*ONE_ATMOSPHERE;on=1}(usr.loc) + hsb.update_icon() // hackish but it wasn't meant to be spawned I guess? + + // + // Stacked Materials + // + + if("hsbrglass") + new/obj/item/stack/sheet/rglass{amount=50}(usr.loc) + + if("hsbmetal") + new/obj/item/stack/sheet/metal{amount=50}(usr.loc) + + if("hsbplasteel") + new/obj/item/stack/sheet/plasteel{amount=50}(usr.loc) + + if("hsbglass") + new/obj/item/stack/sheet/glass{amount=50}(usr.loc) + + if("hsbwood") + new/obj/item/stack/sheet/mineral/wood{amount=50}(usr.loc) + + // + // All access ID + // + if("hsbaaid") + var/obj/item/card/id/gold/ID = new(usr.loc) + ID.registered_name = usr.real_name + ID.assignment = "Sandbox" + ID.access = get_all_accesses() + ID.update_label() + + // + // RCD - starts with full clip + // Spawn check due to grief potential (destroying floors, walls, etc) + // + if("hsbrcd") + if(!GLOB.hsboxspawn) return + + new/obj/item/construction/rcd/combat(usr.loc) + + // + // New sandbox airlock maker + // + if("hsbairlock") + new /datum/airlock_maker(usr.loc) + + // + // Object spawn window + // + + // Clothing + if("hsbcloth") + if(!GLOB.hsboxspawn) return + + if(!clothinfo) + clothinfo = "Clothing (Reagent Containers) (Other Items)

    " + var/list/all_items = subtypesof(/obj/item/clothing) + for(var/typekey in spawn_forbidden) + all_items -= typesof(typekey) + for(var/O in reverseRange(all_items)) + clothinfo += "[O]
    " + + usr << browse(clothinfo,"window=sandbox") + + // Reagent containers + if("hsbreag") + if(!GLOB.hsboxspawn) return + + if(!reaginfo) + reaginfo = "Reagent Containers (Clothing) (Other Items)

    " + var/list/all_items = subtypesof(/obj/item/reagent_containers) + for(var/typekey in spawn_forbidden) + all_items -= typesof(typekey) + for(var/O in reverseRange(all_items)) + reaginfo += "[O]
    " + + usr << browse(reaginfo,"window=sandbox") + + // Other items + if("hsbobj") + if(!GLOB.hsboxspawn) return + + if(!objinfo) + objinfo = "Other Items (Clothing) (Reagent Containers)

    " + var/list/all_items = subtypesof(/obj/item/) - typesof(/obj/item/clothing) - typesof(/obj/item/reagent_containers) + for(var/typekey in spawn_forbidden) + all_items -= typesof(typekey) + + for(var/O in reverseRange(all_items)) + objinfo += "[O]
    " + + usr << browse(objinfo,"window=sandbox") + + // + // Safespawn checks to see if spawning is disabled. + // + if("hsb_safespawn") + if(!GLOB.hsboxspawn) + usr << browse(null,"window=sandbox") + return + + var/typepath = text2path(href_list["path"]) + if(!typepath) + to_chat(usr, "Bad path: \"[href_list["path"]]\"") + return + new typepath(usr.loc) + + if(CONFIG_GET(flag/sandbox_autoclose)) + usr << browse(null,"window=sandbox") + // + // For everything else in the href list + // + if("hsbspawn") + var/typepath = text2path(href_list["path"]) + if(!typepath) + to_chat(usr, "Bad path: \"[href_list["path"]]\"") + return + new typepath(usr.loc) + + if(CONFIG_GET(flag/sandbox_autoclose)) + usr << browse(null,"window=sandbox") diff --git a/code/game/gamemodes/sandbox/sandbox.dm b/code/game/gamemodes/sandbox/sandbox.dm index a41a6a185ed..8d4846d579e 100644 --- a/code/game/gamemodes/sandbox/sandbox.dm +++ b/code/game/gamemodes/sandbox/sandbox.dm @@ -1,22 +1,22 @@ -/datum/game_mode/sandbox - name = "sandbox" - config_tag = "sandbox" - report_type = "sandbox" - required_players = 0 - - announce_span = "info" - announce_text = "Build your own station... or just shoot each other!" - - allow_persistence_save = FALSE - -/datum/game_mode/sandbox/pre_setup() - for(var/mob/M in GLOB.player_list) - M.CanBuild() - return 1 - -/datum/game_mode/sandbox/post_setup() - ..() - SSshuttle.registerHostileEnvironment(src) - -/datum/game_mode/sandbox/generate_report() - return "Sensors indicate that crewmembers have been all given psychic powers from which they can manifest various objects.

    This can only end poorly." +/datum/game_mode/sandbox + name = "sandbox" + config_tag = "sandbox" + report_type = "sandbox" + required_players = 0 + + announce_span = "info" + announce_text = "Build your own station... or just shoot each other!" + + allow_persistence_save = FALSE + +/datum/game_mode/sandbox/pre_setup() + for(var/mob/M in GLOB.player_list) + M.CanBuild() + return 1 + +/datum/game_mode/sandbox/post_setup() + ..() + SSshuttle.registerHostileEnvironment(src) + +/datum/game_mode/sandbox/generate_report() + return "Sensors indicate that crewmembers have been all given psychic powers from which they can manifest various objects.

    This can only end poorly." diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm index 9e8b9985cdb..7e3db6e86c4 100644 --- a/code/game/gamemodes/traitor/double_agents.dm +++ b/code/game/gamemodes/traitor/double_agents.dm @@ -1,83 +1,83 @@ -/datum/game_mode - var/list/target_list = list() - var/list/late_joining_list = list() - -/datum/game_mode/traitor/internal_affairs - name = "Internal Affairs" - config_tag = "internal_affairs" - report_type = "internal_affairs" - false_report_weight = 10 - required_players = 25 - required_enemies = 5 - recommended_enemies = 8 - reroll_friendly = 0 - traitor_name = "Nanotrasen Internal Affairs Agent" - antag_flag = ROLE_INTERNAL_AFFAIRS - - traitors_possible = 10 //hard limit on traitors if scaling is turned off - num_modifier = 4 // Four additional traitors - antag_datum = /datum/antagonist/traitor/internal_affairs - - announce_text = "There are Nanotrasen Internal Affairs Agents trying to kill each other!\n\ - IAA: Eliminate your targets and protect yourself!\n\ - Crew: Stop the IAA agents before they can cause too much mayhem." - - - -/datum/game_mode/traitor/internal_affairs/post_setup() - var/i = 0 - for(var/datum/mind/traitor in pre_traitors) - i++ - if(i + 1 > pre_traitors.len) - i = 0 - target_list[traitor] = pre_traitors[i+1] - ..() - - -/datum/game_mode/traitor/internal_affairs/add_latejoin_traitor(datum/mind/character) - - check_potential_agents() - - // As soon as we get 3 or 4 extra latejoin traitors, make them traitors and kill each other. - if(late_joining_list.len >= rand(3, 4)) - // True randomness - shuffle_inplace(late_joining_list) - // Reset the target_list, it'll be used again in force_traitor_objectives - target_list = list() - - // Basically setting the target_list for who is killing who - var/i = 0 - for(var/datum/mind/traitor in late_joining_list) - i++ - if(i + 1 > late_joining_list.len) - i = 0 - target_list[traitor] = late_joining_list[i + 1] - traitor.special_role = traitor_name - - // Now, give them their targets - for(var/datum/mind/traitor in target_list) - ..(traitor) - - late_joining_list = list() - else - late_joining_list += character - return - -/datum/game_mode/traitor/internal_affairs/proc/check_potential_agents() - - for(var/M in late_joining_list) - if(istype(M, /datum/mind)) - var/datum/mind/agent_mind = M - if(ishuman(agent_mind.current)) - var/mob/living/carbon/human/H = agent_mind.current - if(H.stat != DEAD) - if(H.client) - continue // It all checks out. - - // If any check fails, remove them from our list - late_joining_list -= M - - -/datum/game_mode/traitor/internal_affairs/generate_report() - return "Nanotrasen denies any accusations of placing internal affairs agents onboard your station to eliminate inconvenient employees. Any further accusations against CentCom for such \ - actions will be met with a conversation with an official internal affairs agent." +/datum/game_mode + var/list/target_list = list() + var/list/late_joining_list = list() + +/datum/game_mode/traitor/internal_affairs + name = "Internal Affairs" + config_tag = "internal_affairs" + report_type = "internal_affairs" + false_report_weight = 10 + required_players = 25 + required_enemies = 5 + recommended_enemies = 8 + reroll_friendly = 0 + traitor_name = "Nanotrasen Internal Affairs Agent" + antag_flag = ROLE_INTERNAL_AFFAIRS + + traitors_possible = 10 //hard limit on traitors if scaling is turned off + num_modifier = 4 // Four additional traitors + antag_datum = /datum/antagonist/traitor/internal_affairs + + announce_text = "There are Nanotrasen Internal Affairs Agents trying to kill each other!\n\ + IAA: Eliminate your targets and protect yourself!\n\ + Crew: Stop the IAA agents before they can cause too much mayhem." + + + +/datum/game_mode/traitor/internal_affairs/post_setup() + var/i = 0 + for(var/datum/mind/traitor in pre_traitors) + i++ + if(i + 1 > pre_traitors.len) + i = 0 + target_list[traitor] = pre_traitors[i+1] + ..() + + +/datum/game_mode/traitor/internal_affairs/add_latejoin_traitor(datum/mind/character) + + check_potential_agents() + + // As soon as we get 3 or 4 extra latejoin traitors, make them traitors and kill each other. + if(late_joining_list.len >= rand(3, 4)) + // True randomness + shuffle_inplace(late_joining_list) + // Reset the target_list, it'll be used again in force_traitor_objectives + target_list = list() + + // Basically setting the target_list for who is killing who + var/i = 0 + for(var/datum/mind/traitor in late_joining_list) + i++ + if(i + 1 > late_joining_list.len) + i = 0 + target_list[traitor] = late_joining_list[i + 1] + traitor.special_role = traitor_name + + // Now, give them their targets + for(var/datum/mind/traitor in target_list) + ..(traitor) + + late_joining_list = list() + else + late_joining_list += character + return + +/datum/game_mode/traitor/internal_affairs/proc/check_potential_agents() + + for(var/M in late_joining_list) + if(istype(M, /datum/mind)) + var/datum/mind/agent_mind = M + if(ishuman(agent_mind.current)) + var/mob/living/carbon/human/H = agent_mind.current + if(H.stat != DEAD) + if(H.client) + continue // It all checks out. + + // If any check fails, remove them from our list + late_joining_list -= M + + +/datum/game_mode/traitor/internal_affairs/generate_report() + return "Nanotrasen denies any accusations of placing internal affairs agents onboard your station to eliminate inconvenient employees. Any further accusations against CentCom for such \ + actions will be met with a conversation with an official internal affairs agent." diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index b6f9342be55..92c8f02509a 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -1,103 +1,103 @@ -/datum/game_mode - var/traitor_name = "traitor" - var/list/datum/mind/traitors = list() - - var/datum/mind/exchange_red - var/datum/mind/exchange_blue - -/datum/game_mode/traitor - name = "traitor" - config_tag = "traitor" - report_type = "traitor" - antag_flag = ROLE_TRAITOR - false_report_weight = 20 //Reports of traitors are pretty common. - restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances - protected_jobs = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain") - required_players = 0 - required_enemies = 1 - recommended_enemies = 4 - reroll_friendly = 1 - enemy_minimum_age = 0 - - announce_span = "danger" - announce_text = "There are Syndicate agents on the station!\n\ - Traitors: Accomplish your objectives.\n\ - Crew: Do not let the traitors succeed!" - - var/list/datum/mind/pre_traitors = list() - var/traitors_possible = 4 //hard limit on traitors if scaling is turned off - var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual. - var/antag_datum = /datum/antagonist/traitor //what type of antag to create - var/traitors_required = TRUE //Will allow no traitors - - -/datum/game_mode/traitor/pre_setup() - - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/num_traitors = 1 - - var/tsc = CONFIG_GET(number/traitor_scaling_coeff) - if(tsc) - num_traitors = max(1, min(round(num_players() / (tsc * 2)) + 2 + num_modifier, round(num_players() / tsc) + num_modifier)) - else - num_traitors = max(1, min(num_players(), traitors_possible)) - - for(var/j = 0, j < num_traitors, j++) - if (!antag_candidates.len) - break - var/datum/mind/traitor = antag_pick(antag_candidates) - pre_traitors += traitor - traitor.special_role = traitor_name - traitor.restricted_roles = restricted_jobs - log_game("[key_name(traitor)] has been selected as a [traitor_name]") - antag_candidates.Remove(traitor) - - var/enough_tators = !traitors_required || pre_traitors.len > 0 - - if(!enough_tators) - setup_error = "Not enough traitor candidates" - return FALSE - else - for(var/antag in pre_traitors) - GLOB.pre_setup_antags += antag - return TRUE - - -/datum/game_mode/traitor/post_setup() - for(var/datum/mind/traitor in pre_traitors) - var/datum/antagonist/traitor/new_antag = new antag_datum() - addtimer(CALLBACK(traitor, /datum/mind.proc/add_antag_datum, new_antag), rand(10,100)) - GLOB.pre_setup_antags -= traitor - if(!exchange_blue) - exchange_blue = -1 //Block latejoiners from getting exchange objectives - ..() - - //We're not actually ready until all traitors are assigned. - gamemode_ready = FALSE - addtimer(VARSET_CALLBACK(src, gamemode_ready, TRUE), 101) - return TRUE - -/datum/game_mode/traitor/make_antag_chance(mob/living/carbon/human/character) //Assigns traitor to latejoiners - var/tsc = CONFIG_GET(number/traitor_scaling_coeff) - var/traitorcap = min(round(GLOB.joined_player_list.len / (tsc * 2)) + 2 + num_modifier, round(GLOB.joined_player_list.len / tsc) + num_modifier) - if((SSticker.mode.traitors.len + pre_traitors.len) >= traitorcap) //Upper cap for number of latejoin antagonists - return - if((SSticker.mode.traitors.len + pre_traitors.len) <= (traitorcap - 2) || prob(100 / (tsc * 2))) - if(antag_flag in character.client.prefs.be_special) - if(!is_banned_from(character.ckey, list(ROLE_TRAITOR, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - add_latejoin_traitor(character.mind) - -/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) - var/datum/antagonist/traitor/new_antag = new antag_datum() - character.add_antag_datum(new_antag) - -/datum/game_mode/traitor/generate_report() - return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \ - Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions." +/datum/game_mode + var/traitor_name = "traitor" + var/list/datum/mind/traitors = list() + + var/datum/mind/exchange_red + var/datum/mind/exchange_blue + +/datum/game_mode/traitor + name = "traitor" + config_tag = "traitor" + report_type = "traitor" + antag_flag = ROLE_TRAITOR + false_report_weight = 20 //Reports of traitors are pretty common. + restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances + protected_jobs = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain") + required_players = 0 + required_enemies = 1 + recommended_enemies = 4 + reroll_friendly = 1 + enemy_minimum_age = 0 + + announce_span = "danger" + announce_text = "There are Syndicate agents on the station!\n\ + Traitors: Accomplish your objectives.\n\ + Crew: Do not let the traitors succeed!" + + var/list/datum/mind/pre_traitors = list() + var/traitors_possible = 4 //hard limit on traitors if scaling is turned off + var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual. + var/antag_datum = /datum/antagonist/traitor //what type of antag to create + var/traitors_required = TRUE //Will allow no traitors + + +/datum/game_mode/traitor/pre_setup() + + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/num_traitors = 1 + + var/tsc = CONFIG_GET(number/traitor_scaling_coeff) + if(tsc) + num_traitors = max(1, min(round(num_players() / (tsc * 2)) + 2 + num_modifier, round(num_players() / tsc) + num_modifier)) + else + num_traitors = max(1, min(num_players(), traitors_possible)) + + for(var/j = 0, j < num_traitors, j++) + if (!antag_candidates.len) + break + var/datum/mind/traitor = antag_pick(antag_candidates) + pre_traitors += traitor + traitor.special_role = traitor_name + traitor.restricted_roles = restricted_jobs + log_game("[key_name(traitor)] has been selected as a [traitor_name]") + antag_candidates.Remove(traitor) + + var/enough_tators = !traitors_required || pre_traitors.len > 0 + + if(!enough_tators) + setup_error = "Not enough traitor candidates" + return FALSE + else + for(var/antag in pre_traitors) + GLOB.pre_setup_antags += antag + return TRUE + + +/datum/game_mode/traitor/post_setup() + for(var/datum/mind/traitor in pre_traitors) + var/datum/antagonist/traitor/new_antag = new antag_datum() + addtimer(CALLBACK(traitor, /datum/mind.proc/add_antag_datum, new_antag), rand(10,100)) + GLOB.pre_setup_antags -= traitor + if(!exchange_blue) + exchange_blue = -1 //Block latejoiners from getting exchange objectives + ..() + + //We're not actually ready until all traitors are assigned. + gamemode_ready = FALSE + addtimer(VARSET_CALLBACK(src, gamemode_ready, TRUE), 101) + return TRUE + +/datum/game_mode/traitor/make_antag_chance(mob/living/carbon/human/character) //Assigns traitor to latejoiners + var/tsc = CONFIG_GET(number/traitor_scaling_coeff) + var/traitorcap = min(round(GLOB.joined_player_list.len / (tsc * 2)) + 2 + num_modifier, round(GLOB.joined_player_list.len / tsc) + num_modifier) + if((SSticker.mode.traitors.len + pre_traitors.len) >= traitorcap) //Upper cap for number of latejoin antagonists + return + if((SSticker.mode.traitors.len + pre_traitors.len) <= (traitorcap - 2) || prob(100 / (tsc * 2))) + if(antag_flag in character.client.prefs.be_special) + if(!is_banned_from(character.ckey, list(ROLE_TRAITOR, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + add_latejoin_traitor(character.mind) + +/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) + var/datum/antagonist/traitor/new_antag = new antag_datum() + character.add_antag_datum(new_antag) + +/datum/game_mode/traitor/generate_report() + return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \ + Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions." diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index bf540a9d63d..809d107e40d 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -1,82 +1,82 @@ -/datum/game_mode - var/list/datum/mind/wizards = list() - var/list/datum/mind/apprentices = list() - -/datum/game_mode/wizard - name = "wizard" - config_tag = "wizard" - report_type = "wizard" - antag_flag = ROLE_WIZARD - false_report_weight = 10 - required_players = 20 - required_enemies = 1 - recommended_enemies = 1 - enemy_minimum_age = 14 - round_ends_with_antag_death = 1 - announce_span = "danger" - announce_text = "There is a space wizard attacking the station!\n\ - Wizard: Accomplish your objectives and cause mayhem on the station.\n\ - Crew: Eliminate the wizard before they can succeed!" - var/finished = 0 - -/datum/game_mode/wizard/pre_setup() - var/datum/mind/wizard = antag_pick(antag_candidates) - wizards += wizard - wizard.assigned_role = ROLE_WIZARD - wizard.special_role = ROLE_WIZARD - log_game("[key_name(wizard)] has been selected as a Wizard") //TODO: Move these to base antag datum - if(GLOB.wizardstart.len == 0) - setup_error = "No wizard starting location found" - return FALSE - for(var/datum/mind/wiz in wizards) - wiz.current.forceMove(pick(GLOB.wizardstart)) - return TRUE - - -/datum/game_mode/wizard/post_setup() - for(var/datum/mind/wizard in wizards) - wizard.add_antag_datum(/datum/antagonist/wizard) - return ..() - -/datum/game_mode/wizard/generate_report() - return "A dangerous Wizards' Federation individual by the name of [pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)] has recently escaped confinement from an unlisted prison facility. This \ - man is a dangerous mutant with the ability to alter himself and the world around him by what he and his leaders believe to be magic. If this man attempts an attack on your station, \ - his execution is highly encouraged, as is the preservation of his body for later study." - - -/datum/game_mode/wizard/are_special_antags_dead() - for(var/datum/mind/wizard in wizards | apprentices) - if(isliving(wizard.current) && wizard.current.stat!=DEAD) - return FALSE - - for(var/obj/item/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead() - if(P.mind && P.mind.has_antag_datum(/datum/antagonist/wizard)) - return FALSE - - if(SSevents.wizardmode) //If summon events was active, turn it off - SSevents.toggleWizardmode() - SSevents.resetFrequency() - - return TRUE - -/datum/game_mode/wizard/check_finished() - . = ..() - if(.) - finished = TRUE - else if(gamemode_ready && are_special_antags_dead() && !CONFIG_GET(keyed_list/continuous)[config_tag]) - finished = TRUE - . = TRUE - -/datum/game_mode/wizard/set_round_result() - ..() - if(finished) - SSticker.mode_result = "loss - wizard killed" - SSticker.news_report = WIZARD_KILLED - -/datum/game_mode/wizard/special_report() - if(finished) - return "
    The wizard[(wizards.len>1)?"s":""] has been killed by the crew! The Space Wizards Federation has been taught a lesson they will not soon forget!
    " - -//returns whether the mob is a wizard (or apprentice) -/proc/iswizard(mob/living/M) - return M.mind && M.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) +/datum/game_mode + var/list/datum/mind/wizards = list() + var/list/datum/mind/apprentices = list() + +/datum/game_mode/wizard + name = "wizard" + config_tag = "wizard" + report_type = "wizard" + antag_flag = ROLE_WIZARD + false_report_weight = 10 + required_players = 20 + required_enemies = 1 + recommended_enemies = 1 + enemy_minimum_age = 14 + round_ends_with_antag_death = 1 + announce_span = "danger" + announce_text = "There is a space wizard attacking the station!\n\ + Wizard: Accomplish your objectives and cause mayhem on the station.\n\ + Crew: Eliminate the wizard before they can succeed!" + var/finished = 0 + +/datum/game_mode/wizard/pre_setup() + var/datum/mind/wizard = antag_pick(antag_candidates) + wizards += wizard + wizard.assigned_role = ROLE_WIZARD + wizard.special_role = ROLE_WIZARD + log_game("[key_name(wizard)] has been selected as a Wizard") //TODO: Move these to base antag datum + if(GLOB.wizardstart.len == 0) + setup_error = "No wizard starting location found" + return FALSE + for(var/datum/mind/wiz in wizards) + wiz.current.forceMove(pick(GLOB.wizardstart)) + return TRUE + + +/datum/game_mode/wizard/post_setup() + for(var/datum/mind/wizard in wizards) + wizard.add_antag_datum(/datum/antagonist/wizard) + return ..() + +/datum/game_mode/wizard/generate_report() + return "A dangerous Wizards' Federation individual by the name of [pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)] has recently escaped confinement from an unlisted prison facility. This \ + man is a dangerous mutant with the ability to alter himself and the world around him by what he and his leaders believe to be magic. If this man attempts an attack on your station, \ + his execution is highly encouraged, as is the preservation of his body for later study." + + +/datum/game_mode/wizard/are_special_antags_dead() + for(var/datum/mind/wizard in wizards | apprentices) + if(isliving(wizard.current) && wizard.current.stat!=DEAD) + return FALSE + + for(var/obj/item/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead() + if(P.mind && P.mind.has_antag_datum(/datum/antagonist/wizard)) + return FALSE + + if(SSevents.wizardmode) //If summon events was active, turn it off + SSevents.toggleWizardmode() + SSevents.resetFrequency() + + return TRUE + +/datum/game_mode/wizard/check_finished() + . = ..() + if(.) + finished = TRUE + else if(gamemode_ready && are_special_antags_dead() && !CONFIG_GET(keyed_list/continuous)[config_tag]) + finished = TRUE + . = TRUE + +/datum/game_mode/wizard/set_round_result() + ..() + if(finished) + SSticker.mode_result = "loss - wizard killed" + SSticker.news_report = WIZARD_KILLED + +/datum/game_mode/wizard/special_report() + if(finished) + return "
    The wizard[(wizards.len>1)?"s":""] has been killed by the crew! The Space Wizards Federation has been taught a lesson they will not soon forget!
    " + +//returns whether the mob is a wizard (or apprentice) +/proc/iswizard(mob/living/M) + return M.mind && M.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) diff --git a/code/game/machinery/Beacon.dm b/code/game/machinery/Beacon.dm index 7593e392fc8..fd918d1cb2d 100644 --- a/code/game/machinery/Beacon.dm +++ b/code/game/machinery/Beacon.dm @@ -1,30 +1,30 @@ -/obj/machinery/bluespace_beacon - - icon = 'icons/obj/objects.dmi' - icon_state = "floor_beaconf" - name = "bluespace gigabeacon" - desc = "A device that draws power from bluespace and creates a permanent tracking beacon." - layer = LOW_OBJ_LAYER - use_power = IDLE_POWER_USE - idle_power_usage = 0 - var/obj/item/beacon/Beacon - -/obj/machinery/bluespace_beacon/Initialize() - . = ..() - var/turf/T = loc - Beacon = new(T) - Beacon.invisibility = INVISIBILITY_MAXIMUM - - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) - -/obj/machinery/bluespace_beacon/Destroy() - QDEL_NULL(Beacon) - return ..() - -/obj/machinery/bluespace_beacon/process() - if(!Beacon) - var/turf/T = loc - Beacon = new(T) - Beacon.invisibility = INVISIBILITY_MAXIMUM - else if (Beacon.loc != loc) - Beacon.forceMove(loc) +/obj/machinery/bluespace_beacon + + icon = 'icons/obj/objects.dmi' + icon_state = "floor_beaconf" + name = "bluespace gigabeacon" + desc = "A device that draws power from bluespace and creates a permanent tracking beacon." + layer = LOW_OBJ_LAYER + use_power = IDLE_POWER_USE + idle_power_usage = 0 + var/obj/item/beacon/Beacon + +/obj/machinery/bluespace_beacon/Initialize() + . = ..() + var/turf/T = loc + Beacon = new(T) + Beacon.invisibility = INVISIBILITY_MAXIMUM + + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) + +/obj/machinery/bluespace_beacon/Destroy() + QDEL_NULL(Beacon) + return ..() + +/obj/machinery/bluespace_beacon/process() + if(!Beacon) + var/turf/T = loc + Beacon = new(T) + Beacon.invisibility = INVISIBILITY_MAXIMUM + else if (Beacon.loc != loc) + Beacon.forceMove(loc) diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm index 00ba4b2eabf..3aa18d3d889 100644 --- a/code/game/machinery/PDApainter.dm +++ b/code/game/machinery/PDApainter.dm @@ -1,145 +1,145 @@ -/obj/machinery/pdapainter - name = "\improper PDA painter" - desc = "A PDA painting machine. To use, simply insert your PDA and choose the desired preset paint scheme." - icon = 'icons/obj/pda.dmi' - icon_state = "pdapainter" - density = TRUE - max_integrity = 200 - var/obj/item/pda/storedpda = null - var/list/colorlist = list() - -/obj/machinery/pdapainter/update_icon_state() - if(machine_stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - return - - if(powered()) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]-off" - -/obj/machinery/pdapainter/update_overlays() - . = ..() - - if(machine_stat & BROKEN) - return - - if(storedpda) - . += "[initial(icon_state)]-closed" - -/obj/machinery/pdapainter/Initialize() - . = ..() - var/list/blocked = list( - /obj/item/pda/ai/pai, - /obj/item/pda/ai, - /obj/item/pda/heads, - /obj/item/pda/clear, - /obj/item/pda/syndicate, - /obj/item/pda/chameleon, - /obj/item/pda/chameleon/broken) - - for(var/P in typesof(/obj/item/pda) - blocked) - var/obj/item/pda/D = new P - - //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" - D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. - - src.colorlist += D - -/obj/machinery/pdapainter/Destroy() - QDEL_NULL(storedpda) - return ..() - -/obj/machinery/pdapainter/on_deconstruction() - if(storedpda) - storedpda.forceMove(loc) - storedpda = null - -/obj/machinery/pdapainter/contents_explosion(severity, target) - if(storedpda) - storedpda.ex_act(severity, target) - -/obj/machinery/pdapainter/handle_atom_del(atom/A) - if(A == storedpda) - storedpda = null - update_icon() - -/obj/machinery/pdapainter/attackby(obj/item/O, mob/user, params) - if(machine_stat & BROKEN) - if(O.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) - if(!O.tool_start_check(user, amount=0)) - return - user.visible_message("[user] is repairing [src].", \ - "You begin repairing [src]...", \ - "You hear welding.") - if(O.use_tool(src, user, 40, volume=50)) - if(!(machine_stat & BROKEN)) - return - to_chat(user, "You repair [src].") - machine_stat &= ~BROKEN - obj_integrity = max_integrity - update_icon() - - else - return ..() - - else if(default_unfasten_wrench(user, O)) - power_change() - return - - else if(istype(O, /obj/item/pda)) - if(storedpda) - to_chat(user, "There is already a PDA inside!") - return - else if(!user.transferItemToLoc(O, src)) - return - storedpda = O - O.add_fingerprint(user) - update_icon() - - else - return ..() - -/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) - obj_break() - -/obj/machinery/pdapainter/attack_hand(mob/user) - . = ..() - if(.) - return - - if(storedpda) - if(machine_stat & BROKEN) //otherwise the PDA is stuck until repaired - ejectpda() - to_chat(user, "You manage to eject the loaded PDA.") - else - var/obj/item/pda/P - P = input(user, "Select your color!", "PDA Painting") as null|anything in sortNames(colorlist) - if(!P) - return - if(!in_range(src, user)) - return - if(!storedpda)//is the pda still there? - return - storedpda.icon_state = P.icon_state - storedpda.desc = P.desc - ejectpda() - - else - to_chat(user, "[src] is empty!") - - -/obj/machinery/pdapainter/verb/ejectpda() - set name = "Eject PDA" - set category = "Object" - set src in oview(1) - - if(usr.stat || usr.restrained()) - return - - if(storedpda) - storedpda.forceMove(drop_location()) - storedpda = null - update_icon() - else - to_chat(usr, "[src] is empty!") +/obj/machinery/pdapainter + name = "\improper PDA painter" + desc = "A PDA painting machine. To use, simply insert your PDA and choose the desired preset paint scheme." + icon = 'icons/obj/pda.dmi' + icon_state = "pdapainter" + density = TRUE + max_integrity = 200 + var/obj/item/pda/storedpda = null + var/list/colorlist = list() + +/obj/machinery/pdapainter/update_icon_state() + if(machine_stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + return + + if(powered()) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]-off" + +/obj/machinery/pdapainter/update_overlays() + . = ..() + + if(machine_stat & BROKEN) + return + + if(storedpda) + . += "[initial(icon_state)]-closed" + +/obj/machinery/pdapainter/Initialize() + . = ..() + var/list/blocked = list( + /obj/item/pda/ai/pai, + /obj/item/pda/ai, + /obj/item/pda/heads, + /obj/item/pda/clear, + /obj/item/pda/syndicate, + /obj/item/pda/chameleon, + /obj/item/pda/chameleon/broken) + + for(var/P in typesof(/obj/item/pda) - blocked) + var/obj/item/pda/D = new P + + //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" + D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. + + src.colorlist += D + +/obj/machinery/pdapainter/Destroy() + QDEL_NULL(storedpda) + return ..() + +/obj/machinery/pdapainter/on_deconstruction() + if(storedpda) + storedpda.forceMove(loc) + storedpda = null + +/obj/machinery/pdapainter/contents_explosion(severity, target) + if(storedpda) + storedpda.ex_act(severity, target) + +/obj/machinery/pdapainter/handle_atom_del(atom/A) + if(A == storedpda) + storedpda = null + update_icon() + +/obj/machinery/pdapainter/attackby(obj/item/O, mob/user, params) + if(machine_stat & BROKEN) + if(O.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) + if(!O.tool_start_check(user, amount=0)) + return + user.visible_message("[user] is repairing [src].", \ + "You begin repairing [src]...", \ + "You hear welding.") + if(O.use_tool(src, user, 40, volume=50)) + if(!(machine_stat & BROKEN)) + return + to_chat(user, "You repair [src].") + machine_stat &= ~BROKEN + obj_integrity = max_integrity + update_icon() + + else + return ..() + + else if(default_unfasten_wrench(user, O)) + power_change() + return + + else if(istype(O, /obj/item/pda)) + if(storedpda) + to_chat(user, "There is already a PDA inside!") + return + else if(!user.transferItemToLoc(O, src)) + return + storedpda = O + O.add_fingerprint(user) + update_icon() + + else + return ..() + +/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) + obj_break() + +/obj/machinery/pdapainter/attack_hand(mob/user) + . = ..() + if(.) + return + + if(storedpda) + if(machine_stat & BROKEN) //otherwise the PDA is stuck until repaired + ejectpda() + to_chat(user, "You manage to eject the loaded PDA.") + else + var/obj/item/pda/P + P = input(user, "Select your color!", "PDA Painting") as null|anything in sortNames(colorlist) + if(!P) + return + if(!in_range(src, user)) + return + if(!storedpda)//is the pda still there? + return + storedpda.icon_state = P.icon_state + storedpda.desc = P.desc + ejectpda() + + else + to_chat(user, "[src] is empty!") + + +/obj/machinery/pdapainter/verb/ejectpda() + set name = "Eject PDA" + set category = "Object" + set src in oview(1) + + if(usr.stat || usr.restrained()) + return + + if(storedpda) + storedpda.forceMove(drop_location()) + storedpda = null + update_icon() + else + to_chat(usr, "[src] is empty!") diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index ba72905a4c7..9bdc44bea4e 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -1,323 +1,323 @@ -/obj/machinery/sleep_console - name = "sleeper console" - icon = 'icons/obj/machines/sleeper.dmi' - icon_state = "console" - density = FALSE - -/obj/machinery/sleeper - name = "sleeper" - desc = "An enclosed machine used to stabilize and heal patients." - icon = 'icons/obj/machines/sleeper.dmi' - icon_state = "sleeper" - density = FALSE - state_open = TRUE - circuit = /obj/item/circuitboard/machine/sleeper - ui_x = 310 - ui_y = 465 - - var/efficiency = 1 - var/min_health = -25 - var/list/available_chems - var/controls_inside = FALSE - var/list/possible_chems = list( - list(/datum/reagent/medicine/epinephrine, /datum/reagent/medicine/morphine, /datum/reagent/medicine/c2/convermol, /datum/reagent/medicine/c2/libital, /datum/reagent/medicine/c2/aiuri), - list(/datum/reagent/medicine/oculine,/datum/reagent/medicine/inacusiate), - list(/datum/reagent/medicine/c2/multiver, /datum/reagent/medicine/mutadone, /datum/reagent/medicine/mannitol, /datum/reagent/medicine/salbutamol, /datum/reagent/medicine/pen_acid), - list(/datum/reagent/medicine/omnizine) - ) - var/list/chem_buttons //Used when emagged to scramble which chem is used, eg: mutadone -> morphine - var/scrambled_chems = FALSE //Are chem buttons scrambled? used as a warning - var/enter_message = "You feel cool air surround you. You go numb as your senses turn inward." - payment_department = ACCOUNT_MED - fair_market_price = 5 - -/obj/machinery/sleeper/Initialize(mapload) - . = ..() - if(mapload) - component_parts -= circuit - QDEL_NULL(circuit) - occupant_typecache = GLOB.typecache_living - update_icon() - reset_chem_buttons() - -/obj/machinery/sleeper/RefreshParts() - var/E - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - E += B.rating - var/I - for(var/obj/item/stock_parts/manipulator/M in component_parts) - I += M.rating - - efficiency = initial(efficiency)* E - min_health = initial(min_health) * E - available_chems = list() - for(var/i in 1 to I) - available_chems |= possible_chems[i] - reset_chem_buttons() - -/obj/machinery/sleeper/update_icon_state() - if(state_open) - icon_state = "[initial(icon_state)]-open" - else - icon_state = initial(icon_state) - -/obj/machinery/sleeper/container_resist(mob/living/user) - visible_message("[occupant] emerges from [src]!", - "You climb out of [src]!") - open_machine() - -/obj/machinery/sleeper/Exited(atom/movable/user) - if (!state_open && user == occupant) - container_resist(user) - -/obj/machinery/sleeper/relaymove(mob/user) - if (!state_open) - container_resist(user) - -/obj/machinery/sleeper/open_machine() - if(!state_open && !panel_open) - flick("[initial(icon_state)]-anim", src) - ..() - -/obj/machinery/sleeper/close_machine(mob/user) - if((isnull(user) || istype(user)) && state_open && !panel_open) - flick("[initial(icon_state)]-anim", src) - ..(user) - var/mob/living/mob_occupant = occupant - if(mob_occupant && mob_occupant.stat != DEAD) - to_chat(occupant, "[enter_message]") - -/obj/machinery/sleeper/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(is_operational() && occupant) - open_machine() - -/obj/machinery/sleeper/MouseDrop_T(mob/target, mob/user) - if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - close_machine(target) - -/obj/machinery/sleeper/screwdriver_act(mob/living/user, obj/item/I) - . = TRUE - if(..()) - return - if(occupant) - to_chat(user, "[src] is currently occupied!") - return - if(state_open) - to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!") - return - if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I)) - return - return FALSE - -/obj/machinery/sleeper/wrench_act(mob/living/user, obj/item/I) - . = ..() - if(default_change_direction_wrench(user, I)) - return TRUE - -/obj/machinery/sleeper/crowbar_act(mob/living/user, obj/item/I) - . = ..() - if(default_pry_open(I)) - return TRUE - if(default_deconstruction_crowbar(I)) - return TRUE - -/obj/machinery/sleeper/default_pry_open(obj/item/I) //wew - . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR - if(.) - I.play_tool_sound(src, 50) - visible_message("[usr] pries open [src].", "You pry open [src].") - open_machine() - -/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - - if(controls_inside && state == GLOB.notcontained_state) - state = GLOB.default_state // If it has a set of controls on the inside, make it actually controllable by the mob in it. - - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Sleeper", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/sleeper/AltClick(mob/user) - if(!user.canUseTopic(src, !issilicon(user))) - return - if(state_open) - close_machine() - else - open_machine() - -/obj/machinery/sleeper/examine(mob/user) - . = ..() - . += "Alt-click [src] to [state_open ? "close" : "open"] it." - -/obj/machinery/sleeper/process() - ..() - check_nap_violations() - -/obj/machinery/sleeper/nap_violation(mob/violator) - open_machine() - -/obj/machinery/sleeper/ui_data() - var/list/data = list() - data["occupied"] = occupant ? 1 : 0 - data["open"] = state_open - - data["chems"] = list() - for(var/chem in available_chems) - var/datum/reagent/R = GLOB.chemical_reagents_list[chem] - data["chems"] += list(list("name" = R.name, "id" = R.type, "allowed" = chem_allowed(chem))) - - data["occupant"] = list() - var/mob/living/mob_occupant = occupant - if(mob_occupant) - data["occupant"]["name"] = mob_occupant.name - switch(mob_occupant.stat) - if(CONSCIOUS) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "good" - if(SOFT_CRIT) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "average" - if(UNCONSCIOUS) - data["occupant"]["stat"] = "Unconscious" - data["occupant"]["statstate"] = "average" - if(DEAD) - data["occupant"]["stat"] = "Dead" - data["occupant"]["statstate"] = "bad" - data["occupant"]["health"] = mob_occupant.health - data["occupant"]["maxHealth"] = mob_occupant.maxHealth - data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD - data["occupant"]["bruteLoss"] = mob_occupant.getBruteLoss() - data["occupant"]["oxyLoss"] = mob_occupant.getOxyLoss() - data["occupant"]["toxLoss"] = mob_occupant.getToxLoss() - data["occupant"]["fireLoss"] = mob_occupant.getFireLoss() - data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss() - data["occupant"]["brainLoss"] = mob_occupant.getOrganLoss(ORGAN_SLOT_BRAIN) - data["occupant"]["reagents"] = list() - if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len) - for(var/datum/reagent/R in mob_occupant.reagents.reagent_list) - data["occupant"]["reagents"] += list(list("name" = R.name, "volume" = R.volume)) - return data - -/obj/machinery/sleeper/ui_act(action, params) - if(..()) - return - var/mob/living/mob_occupant = occupant - check_nap_violations() - switch(action) - if("door") - if(state_open) - close_machine() - else - open_machine() - . = TRUE - if("inject") - var/chem = text2path(params["chem"]) - if(!is_operational() || !mob_occupant || isnull(chem)) - return - if(mob_occupant.health < min_health && chem != /datum/reagent/medicine/epinephrine) - return - if(inject_chem(chem, usr)) - . = TRUE - if(scrambled_chems && prob(5)) - to_chat(usr, "Chemical system re-route detected, results may not be as expected!") - -/obj/machinery/sleeper/emag_act(mob/user) - scramble_chem_buttons() - to_chat(user, "You scramble the sleeper's user interface!") - -/obj/machinery/sleeper/proc/inject_chem(chem, mob/user) - if((chem in available_chems) && chem_allowed(chem)) - occupant.reagents.add_reagent(chem_buttons[chem], 10) //emag effect kicks in here so that the "intended" chem is used for all checks, for extra FUUU - if(user) - log_combat(user, occupant, "injected [chem] into", addition = "via [src]") - return TRUE - -/obj/machinery/sleeper/proc/chem_allowed(chem) - var/mob/living/mob_occupant = occupant - if(!mob_occupant || !mob_occupant.reagents) - return - var/amount = mob_occupant.reagents.get_reagent_amount(chem) + 10 <= 20 * efficiency - var/occ_health = mob_occupant.health > min_health || chem == /datum/reagent/medicine/epinephrine - return amount && occ_health - -/obj/machinery/sleeper/proc/reset_chem_buttons() - scrambled_chems = FALSE - LAZYINITLIST(chem_buttons) - for(var/chem in available_chems) - chem_buttons[chem] = chem - -/obj/machinery/sleeper/proc/scramble_chem_buttons() - scrambled_chems = TRUE - var/list/av_chem = available_chems.Copy() - for(var/chem in av_chem) - chem_buttons[chem] = pick_n_take(av_chem) //no dupes, allow for random buttons to still be correct - - -/obj/machinery/sleeper/syndie - icon_state = "sleeper_s" - controls_inside = TRUE - -/obj/machinery/sleeper/syndie/fullupgrade/Initialize() - . = ..() - component_parts = list() - component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null) - component_parts += new /obj/item/stock_parts/manipulator/femto(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null) - RefreshParts() - -/obj/machinery/sleeper/old - icon_state = "oldpod" - -/obj/machinery/sleeper/party - name = "party pod" - desc = "'Sleeper' units were once known for their healing properties, until a lengthy investigation revealed they were also dosing patients with deadly lead acetate. This appears to be one of those old 'sleeper' units repurposed as a 'Party Pod'. It’s probably not a good idea to use it." - icon_state = "partypod" - idle_power_usage = 3000 - circuit = /obj/item/circuitboard/machine/sleeper/party - var/leddit = FALSE //Get it like reddit and lead alright fine - ui_x = 310 - ui_y = 400 - - controls_inside = TRUE - possible_chems = list( - list(/datum/reagent/consumable/ethanol/beer, /datum/reagent/consumable/laughter), - list(/datum/reagent/spraytan,/datum/reagent/barbers_aid), - list(/datum/reagent/colorful_reagent,/datum/reagent/hair_dye), - list(/datum/reagent/drug/space_drugs,/datum/reagent/baldium) - )//Exclusively uses non-lethal, "fun" chems. At an obvious downside. - var/spray_chems = list( - /datum/reagent/spraytan, /datum/reagent/hair_dye, /datum/reagent/baldium, /datum/reagent/barbers_aid - )//Chemicals that need to have a touch or vapor reaction to be applied, not the standard chamber reaction. - enter_message = "You're surrounded by some funky music inside the chamber. You zone out as you feel waves of krunk vibe within you." - -/obj/machinery/sleeper/party/inject_chem(chem, mob/user) - if(leddit) - occupant.reagents.add_reagent(/datum/reagent/toxin/leadacetate, 4) //You're injecting chemicals into yourself from a recalled, decrepit medical machine. What did you expect? - else if (prob(20)) - occupant.reagents.add_reagent(/datum/reagent/toxin/leadacetate, rand(1,3)) - if(chem in spray_chems) - var/datum/reagents/holder = new() - holder.add_reagent(chem_buttons[chem], 10) //I hope this is the correct way to do this. - holder.expose(occupant, VAPOR, 0) - holder.trans_to(occupant, 10) - playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6) - if(user) - log_combat(user, occupant, "sprayed [chem] into", addition = "via [src]") - return TRUE - ..() - -/obj/machinery/sleeper/party/emag_act(mob/user) - ..() - leddit = TRUE +/obj/machinery/sleep_console + name = "sleeper console" + icon = 'icons/obj/machines/sleeper.dmi' + icon_state = "console" + density = FALSE + +/obj/machinery/sleeper + name = "sleeper" + desc = "An enclosed machine used to stabilize and heal patients." + icon = 'icons/obj/machines/sleeper.dmi' + icon_state = "sleeper" + density = FALSE + state_open = TRUE + circuit = /obj/item/circuitboard/machine/sleeper + ui_x = 310 + ui_y = 465 + + var/efficiency = 1 + var/min_health = -25 + var/list/available_chems + var/controls_inside = FALSE + var/list/possible_chems = list( + list(/datum/reagent/medicine/epinephrine, /datum/reagent/medicine/morphine, /datum/reagent/medicine/c2/convermol, /datum/reagent/medicine/c2/libital, /datum/reagent/medicine/c2/aiuri), + list(/datum/reagent/medicine/oculine,/datum/reagent/medicine/inacusiate), + list(/datum/reagent/medicine/c2/multiver, /datum/reagent/medicine/mutadone, /datum/reagent/medicine/mannitol, /datum/reagent/medicine/salbutamol, /datum/reagent/medicine/pen_acid), + list(/datum/reagent/medicine/omnizine) + ) + var/list/chem_buttons //Used when emagged to scramble which chem is used, eg: mutadone -> morphine + var/scrambled_chems = FALSE //Are chem buttons scrambled? used as a warning + var/enter_message = "You feel cool air surround you. You go numb as your senses turn inward." + payment_department = ACCOUNT_MED + fair_market_price = 5 + +/obj/machinery/sleeper/Initialize(mapload) + . = ..() + if(mapload) + component_parts -= circuit + QDEL_NULL(circuit) + occupant_typecache = GLOB.typecache_living + update_icon() + reset_chem_buttons() + +/obj/machinery/sleeper/RefreshParts() + var/E + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + E += B.rating + var/I + for(var/obj/item/stock_parts/manipulator/M in component_parts) + I += M.rating + + efficiency = initial(efficiency)* E + min_health = initial(min_health) * E + available_chems = list() + for(var/i in 1 to I) + available_chems |= possible_chems[i] + reset_chem_buttons() + +/obj/machinery/sleeper/update_icon_state() + if(state_open) + icon_state = "[initial(icon_state)]-open" + else + icon_state = initial(icon_state) + +/obj/machinery/sleeper/container_resist(mob/living/user) + visible_message("[occupant] emerges from [src]!", + "You climb out of [src]!") + open_machine() + +/obj/machinery/sleeper/Exited(atom/movable/user) + if (!state_open && user == occupant) + container_resist(user) + +/obj/machinery/sleeper/relaymove(mob/user) + if (!state_open) + container_resist(user) + +/obj/machinery/sleeper/open_machine() + if(!state_open && !panel_open) + flick("[initial(icon_state)]-anim", src) + ..() + +/obj/machinery/sleeper/close_machine(mob/user) + if((isnull(user) || istype(user)) && state_open && !panel_open) + flick("[initial(icon_state)]-anim", src) + ..(user) + var/mob/living/mob_occupant = occupant + if(mob_occupant && mob_occupant.stat != DEAD) + to_chat(occupant, "[enter_message]") + +/obj/machinery/sleeper/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(is_operational() && occupant) + open_machine() + +/obj/machinery/sleeper/MouseDrop_T(mob/target, mob/user) + if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + close_machine(target) + +/obj/machinery/sleeper/screwdriver_act(mob/living/user, obj/item/I) + . = TRUE + if(..()) + return + if(occupant) + to_chat(user, "[src] is currently occupied!") + return + if(state_open) + to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!") + return + if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I)) + return + return FALSE + +/obj/machinery/sleeper/wrench_act(mob/living/user, obj/item/I) + . = ..() + if(default_change_direction_wrench(user, I)) + return TRUE + +/obj/machinery/sleeper/crowbar_act(mob/living/user, obj/item/I) + . = ..() + if(default_pry_open(I)) + return TRUE + if(default_deconstruction_crowbar(I)) + return TRUE + +/obj/machinery/sleeper/default_pry_open(obj/item/I) //wew + . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR + if(.) + I.play_tool_sound(src, 50) + visible_message("[usr] pries open [src].", "You pry open [src].") + open_machine() + +/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + + if(controls_inside && state == GLOB.notcontained_state) + state = GLOB.default_state // If it has a set of controls on the inside, make it actually controllable by the mob in it. + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Sleeper", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/sleeper/AltClick(mob/user) + if(!user.canUseTopic(src, !issilicon(user))) + return + if(state_open) + close_machine() + else + open_machine() + +/obj/machinery/sleeper/examine(mob/user) + . = ..() + . += "Alt-click [src] to [state_open ? "close" : "open"] it." + +/obj/machinery/sleeper/process() + ..() + check_nap_violations() + +/obj/machinery/sleeper/nap_violation(mob/violator) + open_machine() + +/obj/machinery/sleeper/ui_data() + var/list/data = list() + data["occupied"] = occupant ? 1 : 0 + data["open"] = state_open + + data["chems"] = list() + for(var/chem in available_chems) + var/datum/reagent/R = GLOB.chemical_reagents_list[chem] + data["chems"] += list(list("name" = R.name, "id" = R.type, "allowed" = chem_allowed(chem))) + + data["occupant"] = list() + var/mob/living/mob_occupant = occupant + if(mob_occupant) + data["occupant"]["name"] = mob_occupant.name + switch(mob_occupant.stat) + if(CONSCIOUS) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "good" + if(SOFT_CRIT) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "average" + if(UNCONSCIOUS) + data["occupant"]["stat"] = "Unconscious" + data["occupant"]["statstate"] = "average" + if(DEAD) + data["occupant"]["stat"] = "Dead" + data["occupant"]["statstate"] = "bad" + data["occupant"]["health"] = mob_occupant.health + data["occupant"]["maxHealth"] = mob_occupant.maxHealth + data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["occupant"]["bruteLoss"] = mob_occupant.getBruteLoss() + data["occupant"]["oxyLoss"] = mob_occupant.getOxyLoss() + data["occupant"]["toxLoss"] = mob_occupant.getToxLoss() + data["occupant"]["fireLoss"] = mob_occupant.getFireLoss() + data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss() + data["occupant"]["brainLoss"] = mob_occupant.getOrganLoss(ORGAN_SLOT_BRAIN) + data["occupant"]["reagents"] = list() + if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len) + for(var/datum/reagent/R in mob_occupant.reagents.reagent_list) + data["occupant"]["reagents"] += list(list("name" = R.name, "volume" = R.volume)) + return data + +/obj/machinery/sleeper/ui_act(action, params) + if(..()) + return + var/mob/living/mob_occupant = occupant + check_nap_violations() + switch(action) + if("door") + if(state_open) + close_machine() + else + open_machine() + . = TRUE + if("inject") + var/chem = text2path(params["chem"]) + if(!is_operational() || !mob_occupant || isnull(chem)) + return + if(mob_occupant.health < min_health && chem != /datum/reagent/medicine/epinephrine) + return + if(inject_chem(chem, usr)) + . = TRUE + if(scrambled_chems && prob(5)) + to_chat(usr, "Chemical system re-route detected, results may not be as expected!") + +/obj/machinery/sleeper/emag_act(mob/user) + scramble_chem_buttons() + to_chat(user, "You scramble the sleeper's user interface!") + +/obj/machinery/sleeper/proc/inject_chem(chem, mob/user) + if((chem in available_chems) && chem_allowed(chem)) + occupant.reagents.add_reagent(chem_buttons[chem], 10) //emag effect kicks in here so that the "intended" chem is used for all checks, for extra FUUU + if(user) + log_combat(user, occupant, "injected [chem] into", addition = "via [src]") + return TRUE + +/obj/machinery/sleeper/proc/chem_allowed(chem) + var/mob/living/mob_occupant = occupant + if(!mob_occupant || !mob_occupant.reagents) + return + var/amount = mob_occupant.reagents.get_reagent_amount(chem) + 10 <= 20 * efficiency + var/occ_health = mob_occupant.health > min_health || chem == /datum/reagent/medicine/epinephrine + return amount && occ_health + +/obj/machinery/sleeper/proc/reset_chem_buttons() + scrambled_chems = FALSE + LAZYINITLIST(chem_buttons) + for(var/chem in available_chems) + chem_buttons[chem] = chem + +/obj/machinery/sleeper/proc/scramble_chem_buttons() + scrambled_chems = TRUE + var/list/av_chem = available_chems.Copy() + for(var/chem in av_chem) + chem_buttons[chem] = pick_n_take(av_chem) //no dupes, allow for random buttons to still be correct + + +/obj/machinery/sleeper/syndie + icon_state = "sleeper_s" + controls_inside = TRUE + +/obj/machinery/sleeper/syndie/fullupgrade/Initialize() + . = ..() + component_parts = list() + component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null) + component_parts += new /obj/item/stock_parts/manipulator/femto(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null) + RefreshParts() + +/obj/machinery/sleeper/old + icon_state = "oldpod" + +/obj/machinery/sleeper/party + name = "party pod" + desc = "'Sleeper' units were once known for their healing properties, until a lengthy investigation revealed they were also dosing patients with deadly lead acetate. This appears to be one of those old 'sleeper' units repurposed as a 'Party Pod'. It’s probably not a good idea to use it." + icon_state = "partypod" + idle_power_usage = 3000 + circuit = /obj/item/circuitboard/machine/sleeper/party + var/leddit = FALSE //Get it like reddit and lead alright fine + ui_x = 310 + ui_y = 400 + + controls_inside = TRUE + possible_chems = list( + list(/datum/reagent/consumable/ethanol/beer, /datum/reagent/consumable/laughter), + list(/datum/reagent/spraytan,/datum/reagent/barbers_aid), + list(/datum/reagent/colorful_reagent,/datum/reagent/hair_dye), + list(/datum/reagent/drug/space_drugs,/datum/reagent/baldium) + )//Exclusively uses non-lethal, "fun" chems. At an obvious downside. + var/spray_chems = list( + /datum/reagent/spraytan, /datum/reagent/hair_dye, /datum/reagent/baldium, /datum/reagent/barbers_aid + )//Chemicals that need to have a touch or vapor reaction to be applied, not the standard chamber reaction. + enter_message = "You're surrounded by some funky music inside the chamber. You zone out as you feel waves of krunk vibe within you." + +/obj/machinery/sleeper/party/inject_chem(chem, mob/user) + if(leddit) + occupant.reagents.add_reagent(/datum/reagent/toxin/leadacetate, 4) //You're injecting chemicals into yourself from a recalled, decrepit medical machine. What did you expect? + else if (prob(20)) + occupant.reagents.add_reagent(/datum/reagent/toxin/leadacetate, rand(1,3)) + if(chem in spray_chems) + var/datum/reagents/holder = new() + holder.add_reagent(chem_buttons[chem], 10) //I hope this is the correct way to do this. + holder.expose(occupant, VAPOR, 0) + holder.trans_to(occupant, 10) + playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6) + if(user) + log_combat(user, occupant, "sprayed [chem] into", addition = "via [src]") + return TRUE + ..() + +/obj/machinery/sleeper/party/emag_act(mob/user) + ..() + leddit = TRUE diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm index 4ae99a0587d..43fafb819ee 100644 --- a/code/game/machinery/ai_slipper.dm +++ b/code/game/machinery/ai_slipper.dm @@ -1,43 +1,43 @@ -/obj/machinery/ai_slipper - name = "foam dispenser" - desc = "A remotely-activatable dispenser for crowd-controlling foam." - icon = 'icons/obj/device.dmi' - icon_state = "ai-slipper0" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - plane = FLOOR_PLANE - max_integrity = 200 - armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - - var/uses = 20 - var/cooldown = 0 - var/cooldown_time = 100 - req_access = list(ACCESS_AI_UPLOAD) - -/obj/machinery/ai_slipper/examine(mob/user) - . = ..() - . += "It has [uses] uses of foam remaining." - -/obj/machinery/ai_slipper/update_icon_state() - if(machine_stat & BROKEN) - return - if((machine_stat & NOPOWER) || cooldown_time > world.time || !uses) - icon_state = "ai-slipper0" - else - icon_state = "ai-slipper1" - -/obj/machinery/ai_slipper/interact(mob/user) - if(!allowed(user)) - to_chat(user, "Access denied.") - return - if(!uses) - to_chat(user, "[src] is out of foam and cannot be activated!") - return - if(cooldown_time > world.time) - to_chat(user, "[src] cannot be activated for [DisplayTimeText(world.time - cooldown_time)]!") - return - new /obj/effect/particle_effect/foam(loc) - uses-- - to_chat(user, "You activate [src]. It now has [uses] uses of foam remaining.") - cooldown = world.time + cooldown_time - power_change() - addtimer(CALLBACK(src, .proc/power_change), cooldown_time) +/obj/machinery/ai_slipper + name = "foam dispenser" + desc = "A remotely-activatable dispenser for crowd-controlling foam." + icon = 'icons/obj/device.dmi' + icon_state = "ai-slipper0" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + plane = FLOOR_PLANE + max_integrity = 200 + armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + + var/uses = 20 + var/cooldown = 0 + var/cooldown_time = 100 + req_access = list(ACCESS_AI_UPLOAD) + +/obj/machinery/ai_slipper/examine(mob/user) + . = ..() + . += "It has [uses] uses of foam remaining." + +/obj/machinery/ai_slipper/update_icon_state() + if(machine_stat & BROKEN) + return + if((machine_stat & NOPOWER) || cooldown_time > world.time || !uses) + icon_state = "ai-slipper0" + else + icon_state = "ai-slipper1" + +/obj/machinery/ai_slipper/interact(mob/user) + if(!allowed(user)) + to_chat(user, "Access denied.") + return + if(!uses) + to_chat(user, "[src] is out of foam and cannot be activated!") + return + if(cooldown_time > world.time) + to_chat(user, "[src] cannot be activated for [DisplayTimeText(world.time - cooldown_time)]!") + return + new /obj/effect/particle_effect/foam(loc) + uses-- + to_chat(user, "You activate [src]. It now has [uses] uses of foam remaining.") + cooldown = world.time + cooldown_time + power_change() + addtimer(CALLBACK(src, .proc/power_change), cooldown_time) diff --git a/code/game/machinery/airlock_control.dm b/code/game/machinery/airlock_control.dm index 200488b9cbf..4d7e59c32b3 100644 --- a/code/game/machinery/airlock_control.dm +++ b/code/game/machinery/airlock_control.dm @@ -1,164 +1,164 @@ -#define AIRLOCK_CONTROL_RANGE 5 - -// This code allows for airlocks to be controlled externally by setting an id_tag and comm frequency (disables ID access) -/obj/machinery/door/airlock - var/id_tag - var/frequency - var/datum/radio_frequency/radio_connection - - -/obj/machinery/door/airlock/receive_signal(datum/signal/signal) - if(!signal) - return - - if(id_tag != signal.data["tag"] || !signal.data["command"]) - return - - switch(signal.data["command"]) - if("open") - open(1) - - if("close") - close(1) - - if("unlock") - locked = FALSE - update_icon() - - if("lock") - locked = TRUE - update_icon() - - if("secure_open") - locked = FALSE - update_icon() - - sleep(2) - open(1) - - locked = TRUE - update_icon() - - if("secure_close") - locked = FALSE - close(1) - - locked = TRUE - sleep(2) - update_icon() - - send_status() - - -/obj/machinery/door/airlock/proc/send_status() - if(radio_connection) - var/datum/signal/signal = new(list( - "tag" = id_tag, - "timestamp" = world.time, - "door_status" = density ? "closed" : "open", - "lock_status" = locked ? "locked" : "unlocked" - )) - radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) - - -/obj/machinery/door/airlock/open(surpress_send) - . = ..() - if(!surpress_send) - send_status() - - -/obj/machinery/door/airlock/close(surpress_send) - . = ..() - if(!surpress_send) - send_status() - - -/obj/machinery/door/airlock/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - if(new_frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) - -/obj/machinery/door/airlock/Destroy() - if(frequency) - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/airlock_sensor - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_sensor_off" - name = "airlock sensor" - resistance_flags = FIRE_PROOF - - power_channel = AREA_USAGE_ENVIRON - - var/id_tag - var/master_tag - var/frequency = FREQ_AIRLOCK_CONTROL - - var/datum/radio_frequency/radio_connection - - var/on = TRUE - var/alert = FALSE - -/obj/machinery/airlock_sensor/incinerator_toxmix - id_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR - master_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER - -/obj/machinery/airlock_sensor/incinerator_atmos - id_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR - master_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER - -/obj/machinery/airlock_sensor/incinerator_syndicatelava - id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR - master_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER - -/obj/machinery/airlock_sensor/update_icon_state() - if(on) - if(alert) - icon_state = "airlock_sensor_alert" - else - icon_state = "airlock_sensor_standby" - else - icon_state = "airlock_sensor_off" - -/obj/machinery/airlock_sensor/attack_hand(mob/user) - . = ..() - if(.) - return - var/datum/signal/signal = new(list( - "tag" = master_tag, - "command" = "cycle" - )) - - radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) - flick("airlock_sensor_cycle", src) - -/obj/machinery/airlock_sensor/process() - if(on) - var/datum/gas_mixture/air_sample = return_air() - var/pressure = round(air_sample.return_pressure(),0.1) - alert = (pressure < ONE_ATMOSPHERE*0.8) - - var/datum/signal/signal = new(list( - "tag" = id_tag, - "timestamp" = world.time, - "pressure" = num2text(pressure) - )) - - radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) - - update_icon() - -/obj/machinery/airlock_sensor/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) - -/obj/machinery/airlock_sensor/Initialize() - . = ..() - set_frequency(frequency) - -/obj/machinery/airlock_sensor/Destroy() - SSradio.remove_object(src,frequency) - return ..() +#define AIRLOCK_CONTROL_RANGE 5 + +// This code allows for airlocks to be controlled externally by setting an id_tag and comm frequency (disables ID access) +/obj/machinery/door/airlock + var/id_tag + var/frequency + var/datum/radio_frequency/radio_connection + + +/obj/machinery/door/airlock/receive_signal(datum/signal/signal) + if(!signal) + return + + if(id_tag != signal.data["tag"] || !signal.data["command"]) + return + + switch(signal.data["command"]) + if("open") + open(1) + + if("close") + close(1) + + if("unlock") + locked = FALSE + update_icon() + + if("lock") + locked = TRUE + update_icon() + + if("secure_open") + locked = FALSE + update_icon() + + sleep(2) + open(1) + + locked = TRUE + update_icon() + + if("secure_close") + locked = FALSE + close(1) + + locked = TRUE + sleep(2) + update_icon() + + send_status() + + +/obj/machinery/door/airlock/proc/send_status() + if(radio_connection) + var/datum/signal/signal = new(list( + "tag" = id_tag, + "timestamp" = world.time, + "door_status" = density ? "closed" : "open", + "lock_status" = locked ? "locked" : "unlocked" + )) + radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) + + +/obj/machinery/door/airlock/open(surpress_send) + . = ..() + if(!surpress_send) + send_status() + + +/obj/machinery/door/airlock/close(surpress_send) + . = ..() + if(!surpress_send) + send_status() + + +/obj/machinery/door/airlock/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + if(new_frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) + +/obj/machinery/door/airlock/Destroy() + if(frequency) + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/airlock_sensor + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "airlock_sensor_off" + name = "airlock sensor" + resistance_flags = FIRE_PROOF + + power_channel = AREA_USAGE_ENVIRON + + var/id_tag + var/master_tag + var/frequency = FREQ_AIRLOCK_CONTROL + + var/datum/radio_frequency/radio_connection + + var/on = TRUE + var/alert = FALSE + +/obj/machinery/airlock_sensor/incinerator_toxmix + id_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR + master_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER + +/obj/machinery/airlock_sensor/incinerator_atmos + id_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR + master_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER + +/obj/machinery/airlock_sensor/incinerator_syndicatelava + id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR + master_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER + +/obj/machinery/airlock_sensor/update_icon_state() + if(on) + if(alert) + icon_state = "airlock_sensor_alert" + else + icon_state = "airlock_sensor_standby" + else + icon_state = "airlock_sensor_off" + +/obj/machinery/airlock_sensor/attack_hand(mob/user) + . = ..() + if(.) + return + var/datum/signal/signal = new(list( + "tag" = master_tag, + "command" = "cycle" + )) + + radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) + flick("airlock_sensor_cycle", src) + +/obj/machinery/airlock_sensor/process() + if(on) + var/datum/gas_mixture/air_sample = return_air() + var/pressure = round(air_sample.return_pressure(),0.1) + alert = (pressure < ONE_ATMOSPHERE*0.8) + + var/datum/signal/signal = new(list( + "tag" = id_tag, + "timestamp" = world.time, + "pressure" = num2text(pressure) + )) + + radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) + + update_icon() + +/obj/machinery/airlock_sensor/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) + +/obj/machinery/airlock_sensor/Initialize() + . = ..() + set_frequency(frequency) + +/obj/machinery/airlock_sensor/Destroy() + SSradio.remove_object(src,frequency) + return ..() diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index cdd69c318e7..46f0f3126b9 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -1,433 +1,433 @@ -#define AUTOLATHE_MAIN_MENU 1 -#define AUTOLATHE_CATEGORY_MENU 2 -#define AUTOLATHE_SEARCH_MENU 3 - -/obj/machinery/autolathe - name = "autolathe" - desc = "It produces items using metal and glass." - icon_state = "autolathe" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - circuit = /obj/item/circuitboard/machine/autolathe - layer = BELOW_OBJ_LAYER - - var/operating = FALSE - var/list/L = list() - var/list/LL = list() - var/hacked = FALSE - var/disabled = 0 - var/shocked = FALSE - var/hack_wire - var/disable_wire - var/shock_wire - - var/busy = FALSE - var/prod_coeff = 1 - - var/datum/design/being_built - var/datum/techweb/stored_research - var/list/datum/design/matching_designs - var/selected_category - var/screen = 1 - var/base_price = 25 - var/hacked_price = 50 - - var/list/categories = list( - "Tools", - "Electronics", - "Construction", - "T-Comm", - "Security", - "Machinery", - "Medical", - "Misc", - "Dinnerware", - "Imported" - ) - -/obj/machinery/autolathe/Initialize() - AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID], 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) - . = ..() - - wires = new /datum/wires/autolathe(src) - stored_research = new /datum/techweb/specialized/autounlocking/autolathe - matching_designs = list() - -/obj/machinery/autolathe/Destroy() - QDEL_NULL(wires) - return ..() - -/obj/machinery/autolathe/ui_interact(mob/user) - . = ..() - if(!is_operational()) - return - - if(shocked && !(machine_stat & NOPOWER)) - shock(user,50) - - var/dat - - switch(screen) - if(AUTOLATHE_MAIN_MENU) - dat = main_win(user) - if(AUTOLATHE_CATEGORY_MENU) - dat = category_win(user,selected_category) - if(AUTOLATHE_SEARCH_MENU) - dat = search_win(user) - - var/datum/browser/popup = new(user, "autolathe", name, 400, 500) - popup.set_content(dat) - popup.open() - -/obj/machinery/autolathe/on_deconstruction() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.retrieve_all() - -/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params) - if (busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return TRUE - - if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", O)) - updateUsrDialog() - return TRUE - - if(default_deconstruction_crowbar(O)) - return TRUE - - if(panel_open && is_wire_tool(O)) - wires.interact(user) - return TRUE - - if(user.a_intent == INTENT_HARM) //so we can hit the machine - return ..() - - if(machine_stat) - return TRUE - - if(istype(O, /obj/item/disk/design_disk)) - user.visible_message("[user] begins to load \the [O] in \the [src]...", - "You begin to load a design from \the [O]...", - "You hear the chatter of a floppy drive.") - busy = TRUE - var/obj/item/disk/design_disk/D = O - if(do_after(user, 14.4, target = src)) - for(var/B in D.blueprints) - if(B) - stored_research.add_design(B) - busy = FALSE - return TRUE - - return ..() - - -/obj/machinery/autolathe/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) - if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) - use_power(MINERAL_MATERIAL_AMOUNT / 10) - else if(custom_materials && custom_materials.len && custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)]) - flick("autolathe_r",src)//plays glass insertion animation by default otherwise - else - flick("autolathe_o",src)//plays metal insertion animation - - - use_power(min(1000, amount_inserted / 100)) - updateUsrDialog() - -/obj/machinery/autolathe/Topic(href, href_list) - if(..()) - return - if (!busy) - if(href_list["menu"]) - screen = text2num(href_list["menu"]) - updateUsrDialog() - - if(href_list["category"]) - selected_category = href_list["category"] - updateUsrDialog() - - if(href_list["make"]) - - ///////////////// - //href protection - being_built = stored_research.isDesignResearchedID(href_list["make"]) - if(!being_built) - return - - var/multiplier = text2num(href_list["multiplier"]) - var/is_stack = ispath(being_built.build_path, /obj/item/stack) - multiplier = clamp(multiplier,1,50) - - ///////////////// - - var/coeff = (is_stack ? 1 : prod_coeff) //stacks are unaffected by production coefficient - var/total_amount = 0 - - for(var/MAT in being_built.materials) - total_amount += being_built.materials[MAT] - - var/power = max(2000, (total_amount)*multiplier/5) //Change this to use all materials - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - - var/list/materials_used = list() - var/list/custom_materials = list() //These will apply their material effect, This should usually only be one. - - for(var/MAT in being_built.materials) - var/datum/material/used_material = MAT - var/amount_needed = being_built.materials[MAT] * coeff * multiplier - if(istext(used_material)) //This means its a category - var/list/list_to_show = list() - for(var/i in SSmaterials.materials_by_category[used_material]) - if(materials.materials[i] > 0) - list_to_show += i - - used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc) - if(!used_material) - return //Didn't pick any material, so you can't build shit either. - custom_materials[used_material] += amount_needed - - materials_used[used_material] = amount_needed - - if(materials.has_materials(materials_used)) - busy = TRUE - use_power(power) - icon_state = "autolathe_n" - var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8 - addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack, usr), time) - else - to_chat(usr, "Not enough materials for this operation.") - - if(href_list["search"]) - matching_designs.Cut() - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(findtext(D.name,href_list["to_search"])) - matching_designs.Add(D) - updateUsrDialog() - else - to_chat(usr, "The autolathe is busy. Please wait for completion of previous operation.") - - updateUsrDialog() - - return - -/obj/machinery/autolathe/proc/make_item(power, list/materials_used, list/picked_materials, multiplier, coeff, is_stack, mob/user) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/atom/A = drop_location() - use_power(power) - - materials.use_materials(materials_used) - - if(is_stack) - var/obj/item/stack/N = new being_built.build_path(A, multiplier) - N.update_icon() - N.autolathe_crafted(src) - else - for(var/i=1, i<=multiplier, i++) - var/obj/item/new_item = new being_built.build_path(A) - new_item.autolathe_crafted(src) - - if(length(picked_materials)) - new_item.set_custom_materials(picked_materials, 1 / multiplier) //Ensure we get the non multiplied amount - for(var/x in picked_materials) - var/datum/material/M = x - if(!istype(M, /datum/material/glass) && !istype(M, /datum/material/iron)) - user.client.give_award(/datum/award/achievement/misc/getting_an_upgrade, user) - - - icon_state = "autolathe" - busy = FALSE - updateDialog() - -/obj/machinery/autolathe/RefreshParts() - var/T = 0 - for(var/obj/item/stock_parts/matter_bin/MB in component_parts) - T += MB.rating*75000 - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.max_amount = T - T=1.2 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T -= M.rating*0.2 - prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4 - -/obj/machinery/autolathe/examine(mob/user) - . += ..() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [materials.max_amount] material units.
    Material consumption at [prod_coeff*100]%.
    " - -/obj/machinery/autolathe/proc/main_win(mob/user) - var/dat = "

    Autolathe Menu:


    " - dat += materials_printout() - - dat += "
    \ - \ - \ - \ - \ - \ -

    " - - var/line_length = 1 - dat += "" - - for(var/C in categories) - if(line_length > 2) - dat += "" - line_length = 1 - - dat += "" - line_length++ - - dat += "
    [C]
    " - return dat - -/obj/machinery/autolathe/proc/category_win(mob/user,selected_category) - var/dat = "Return to main menu" - dat += "

    Browsing [selected_category]:


    " - dat += materials_printout() - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)) - continue - - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - - if(ispath(D.build_path, /obj/item/stack)) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/max_multiplier - for(var/datum/material/mat in D.materials) - max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) - if (max_multiplier>10 && !disabled) - dat += " x10" - if (max_multiplier>25 && !disabled) - dat += " x25" - if(max_multiplier > 0 && !disabled) - dat += " x[max_multiplier]" - else - if(!disabled && can_build(D, 5)) - dat += " x5" - if(!disabled && can_build(D, 10)) - dat += " x10" - - dat += "[get_design_cost(D)]
    " - - dat += "
    " - return dat - -/obj/machinery/autolathe/proc/search_win(mob/user) - var/dat = "Return to main menu" - dat += "

    Search results:


    " - dat += materials_printout() - - for(var/v in matching_designs) - var/datum/design/D = v - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - - if(ispath(D.build_path, /obj/item/stack)) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/max_multiplier - for(var/datum/material/mat in D.materials) - max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) - if (max_multiplier>10 && !disabled) - dat += " x10" - if (max_multiplier>25 && !disabled) - dat += " x25" - if(max_multiplier > 0 && !disabled) - dat += " x[max_multiplier]" - - dat += "[get_design_cost(D)]
    " - - dat += "
    " - return dat - -/obj/machinery/autolathe/proc/materials_printout() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/dat = "Total amount: [materials.total_amount] / [materials.max_amount] cm3
    " - for(var/mat_id in materials.materials) - var/datum/material/M = mat_id - var/mineral_amount = materials.materials[mat_id] - if(mineral_amount > 0) - dat += "[M.name] amount: [mineral_amount] cm3
    " - return dat - -/obj/machinery/autolathe/proc/can_build(datum/design/D, amount = 1) - if(D.make_reagents.len) - return FALSE - - var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) - - var/list/required_materials = list() - - for(var/i in D.materials) - required_materials[i] = D.materials[i] * coeff * amount - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - - return materials.has_materials(required_materials) - - -/obj/machinery/autolathe/proc/get_design_cost(datum/design/D) - var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) - var/dat - for(var/i in D.materials) - if(istext(i)) //Category handling - dat += "[D.materials[i] * coeff] [i]" - else - var/datum/material/M = i - dat += "[D.materials[i] * coeff] [M.name] " - return dat - -/obj/machinery/autolathe/proc/reset(wire) - switch(wire) - if(WIRE_HACK) - if(!wires.is_cut(wire)) - adjust_hacked(FALSE) - if(WIRE_SHOCK) - if(!wires.is_cut(wire)) - shocked = FALSE - if(WIRE_DISABLE) - if(!wires.is_cut(wire)) - disabled = FALSE - -/obj/machinery/autolathe/proc/shock(mob/user, prb) - if(machine_stat & (BROKEN|NOPOWER)) // unpowered, no shock - return FALSE - if(!prob(prb)) - return FALSE - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(5, 1, src) - s.start() - if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) - return TRUE - else - return FALSE - -/obj/machinery/autolathe/proc/adjust_hacked(state) - hacked = state - for(var/id in SSresearch.techweb_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - if((D.build_type & AUTOLATHE) && ("hacked" in D.category)) - if(hacked) - stored_research.add_design(D) - else - stored_research.remove_design(D) - -/obj/machinery/autolathe/hacked/Initialize() - . = ..() - adjust_hacked(TRUE) - -//Called when the object is constructed by an autolathe -//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes -/obj/item/proc/autolathe_crafted(obj/machinery/autolathe/A) - return +#define AUTOLATHE_MAIN_MENU 1 +#define AUTOLATHE_CATEGORY_MENU 2 +#define AUTOLATHE_SEARCH_MENU 3 + +/obj/machinery/autolathe + name = "autolathe" + desc = "It produces items using metal and glass." + icon_state = "autolathe" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + circuit = /obj/item/circuitboard/machine/autolathe + layer = BELOW_OBJ_LAYER + + var/operating = FALSE + var/list/L = list() + var/list/LL = list() + var/hacked = FALSE + var/disabled = 0 + var/shocked = FALSE + var/hack_wire + var/disable_wire + var/shock_wire + + var/busy = FALSE + var/prod_coeff = 1 + + var/datum/design/being_built + var/datum/techweb/stored_research + var/list/datum/design/matching_designs + var/selected_category + var/screen = 1 + var/base_price = 25 + var/hacked_price = 50 + + var/list/categories = list( + "Tools", + "Electronics", + "Construction", + "T-Comm", + "Security", + "Machinery", + "Medical", + "Misc", + "Dinnerware", + "Imported" + ) + +/obj/machinery/autolathe/Initialize() + AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID], 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) + . = ..() + + wires = new /datum/wires/autolathe(src) + stored_research = new /datum/techweb/specialized/autounlocking/autolathe + matching_designs = list() + +/obj/machinery/autolathe/Destroy() + QDEL_NULL(wires) + return ..() + +/obj/machinery/autolathe/ui_interact(mob/user) + . = ..() + if(!is_operational()) + return + + if(shocked && !(machine_stat & NOPOWER)) + shock(user,50) + + var/dat + + switch(screen) + if(AUTOLATHE_MAIN_MENU) + dat = main_win(user) + if(AUTOLATHE_CATEGORY_MENU) + dat = category_win(user,selected_category) + if(AUTOLATHE_SEARCH_MENU) + dat = search_win(user) + + var/datum/browser/popup = new(user, "autolathe", name, 400, 500) + popup.set_content(dat) + popup.open() + +/obj/machinery/autolathe/on_deconstruction() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.retrieve_all() + +/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params) + if (busy) + to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") + return TRUE + + if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", O)) + updateUsrDialog() + return TRUE + + if(default_deconstruction_crowbar(O)) + return TRUE + + if(panel_open && is_wire_tool(O)) + wires.interact(user) + return TRUE + + if(user.a_intent == INTENT_HARM) //so we can hit the machine + return ..() + + if(machine_stat) + return TRUE + + if(istype(O, /obj/item/disk/design_disk)) + user.visible_message("[user] begins to load \the [O] in \the [src]...", + "You begin to load a design from \the [O]...", + "You hear the chatter of a floppy drive.") + busy = TRUE + var/obj/item/disk/design_disk/D = O + if(do_after(user, 14.4, target = src)) + for(var/B in D.blueprints) + if(B) + stored_research.add_design(B) + busy = FALSE + return TRUE + + return ..() + + +/obj/machinery/autolathe/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) + if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) + use_power(MINERAL_MATERIAL_AMOUNT / 10) + else if(custom_materials && custom_materials.len && custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)]) + flick("autolathe_r",src)//plays glass insertion animation by default otherwise + else + flick("autolathe_o",src)//plays metal insertion animation + + + use_power(min(1000, amount_inserted / 100)) + updateUsrDialog() + +/obj/machinery/autolathe/Topic(href, href_list) + if(..()) + return + if (!busy) + if(href_list["menu"]) + screen = text2num(href_list["menu"]) + updateUsrDialog() + + if(href_list["category"]) + selected_category = href_list["category"] + updateUsrDialog() + + if(href_list["make"]) + + ///////////////// + //href protection + being_built = stored_research.isDesignResearchedID(href_list["make"]) + if(!being_built) + return + + var/multiplier = text2num(href_list["multiplier"]) + var/is_stack = ispath(being_built.build_path, /obj/item/stack) + multiplier = clamp(multiplier,1,50) + + ///////////////// + + var/coeff = (is_stack ? 1 : prod_coeff) //stacks are unaffected by production coefficient + var/total_amount = 0 + + for(var/MAT in being_built.materials) + total_amount += being_built.materials[MAT] + + var/power = max(2000, (total_amount)*multiplier/5) //Change this to use all materials + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + + var/list/materials_used = list() + var/list/custom_materials = list() //These will apply their material effect, This should usually only be one. + + for(var/MAT in being_built.materials) + var/datum/material/used_material = MAT + var/amount_needed = being_built.materials[MAT] * coeff * multiplier + if(istext(used_material)) //This means its a category + var/list/list_to_show = list() + for(var/i in SSmaterials.materials_by_category[used_material]) + if(materials.materials[i] > 0) + list_to_show += i + + used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc) + if(!used_material) + return //Didn't pick any material, so you can't build shit either. + custom_materials[used_material] += amount_needed + + materials_used[used_material] = amount_needed + + if(materials.has_materials(materials_used)) + busy = TRUE + use_power(power) + icon_state = "autolathe_n" + var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8 + addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack, usr), time) + else + to_chat(usr, "Not enough materials for this operation.") + + if(href_list["search"]) + matching_designs.Cut() + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(findtext(D.name,href_list["to_search"])) + matching_designs.Add(D) + updateUsrDialog() + else + to_chat(usr, "The autolathe is busy. Please wait for completion of previous operation.") + + updateUsrDialog() + + return + +/obj/machinery/autolathe/proc/make_item(power, list/materials_used, list/picked_materials, multiplier, coeff, is_stack, mob/user) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/atom/A = drop_location() + use_power(power) + + materials.use_materials(materials_used) + + if(is_stack) + var/obj/item/stack/N = new being_built.build_path(A, multiplier) + N.update_icon() + N.autolathe_crafted(src) + else + for(var/i=1, i<=multiplier, i++) + var/obj/item/new_item = new being_built.build_path(A) + new_item.autolathe_crafted(src) + + if(length(picked_materials)) + new_item.set_custom_materials(picked_materials, 1 / multiplier) //Ensure we get the non multiplied amount + for(var/x in picked_materials) + var/datum/material/M = x + if(!istype(M, /datum/material/glass) && !istype(M, /datum/material/iron)) + user.client.give_award(/datum/award/achievement/misc/getting_an_upgrade, user) + + + icon_state = "autolathe" + busy = FALSE + updateDialog() + +/obj/machinery/autolathe/RefreshParts() + var/T = 0 + for(var/obj/item/stock_parts/matter_bin/MB in component_parts) + T += MB.rating*75000 + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.max_amount = T + T=1.2 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + T -= M.rating*0.2 + prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4 + +/obj/machinery/autolathe/examine(mob/user) + . += ..() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Storing up to [materials.max_amount] material units.
    Material consumption at [prod_coeff*100]%.
    " + +/obj/machinery/autolathe/proc/main_win(mob/user) + var/dat = "

    Autolathe Menu:


    " + dat += materials_printout() + + dat += "
    \ + \ + \ + \ + \ + \ +

    " + + var/line_length = 1 + dat += "" + + for(var/C in categories) + if(line_length > 2) + dat += "" + line_length = 1 + + dat += "" + line_length++ + + dat += "
    [C]
    " + return dat + +/obj/machinery/autolathe/proc/category_win(mob/user,selected_category) + var/dat = "Return to main menu" + dat += "

    Browsing [selected_category]:


    " + dat += materials_printout() + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(selected_category in D.category)) + continue + + if(disabled || !can_build(D)) + dat += "[D.name]" + else + dat += "[D.name]" + + if(ispath(D.build_path, /obj/item/stack)) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/max_multiplier + for(var/datum/material/mat in D.materials) + max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) + if (max_multiplier>10 && !disabled) + dat += " x10" + if (max_multiplier>25 && !disabled) + dat += " x25" + if(max_multiplier > 0 && !disabled) + dat += " x[max_multiplier]" + else + if(!disabled && can_build(D, 5)) + dat += " x5" + if(!disabled && can_build(D, 10)) + dat += " x10" + + dat += "[get_design_cost(D)]
    " + + dat += "
    " + return dat + +/obj/machinery/autolathe/proc/search_win(mob/user) + var/dat = "Return to main menu" + dat += "

    Search results:


    " + dat += materials_printout() + + for(var/v in matching_designs) + var/datum/design/D = v + if(disabled || !can_build(D)) + dat += "[D.name]" + else + dat += "[D.name]" + + if(ispath(D.build_path, /obj/item/stack)) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/max_multiplier + for(var/datum/material/mat in D.materials) + max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) + if (max_multiplier>10 && !disabled) + dat += " x10" + if (max_multiplier>25 && !disabled) + dat += " x25" + if(max_multiplier > 0 && !disabled) + dat += " x[max_multiplier]" + + dat += "[get_design_cost(D)]
    " + + dat += "
    " + return dat + +/obj/machinery/autolathe/proc/materials_printout() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/dat = "Total amount: [materials.total_amount] / [materials.max_amount] cm3
    " + for(var/mat_id in materials.materials) + var/datum/material/M = mat_id + var/mineral_amount = materials.materials[mat_id] + if(mineral_amount > 0) + dat += "[M.name] amount: [mineral_amount] cm3
    " + return dat + +/obj/machinery/autolathe/proc/can_build(datum/design/D, amount = 1) + if(D.make_reagents.len) + return FALSE + + var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) + + var/list/required_materials = list() + + for(var/i in D.materials) + required_materials[i] = D.materials[i] * coeff * amount + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + + return materials.has_materials(required_materials) + + +/obj/machinery/autolathe/proc/get_design_cost(datum/design/D) + var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) + var/dat + for(var/i in D.materials) + if(istext(i)) //Category handling + dat += "[D.materials[i] * coeff] [i]" + else + var/datum/material/M = i + dat += "[D.materials[i] * coeff] [M.name] " + return dat + +/obj/machinery/autolathe/proc/reset(wire) + switch(wire) + if(WIRE_HACK) + if(!wires.is_cut(wire)) + adjust_hacked(FALSE) + if(WIRE_SHOCK) + if(!wires.is_cut(wire)) + shocked = FALSE + if(WIRE_DISABLE) + if(!wires.is_cut(wire)) + disabled = FALSE + +/obj/machinery/autolathe/proc/shock(mob/user, prb) + if(machine_stat & (BROKEN|NOPOWER)) // unpowered, no shock + return FALSE + if(!prob(prb)) + return FALSE + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(5, 1, src) + s.start() + if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) + return TRUE + else + return FALSE + +/obj/machinery/autolathe/proc/adjust_hacked(state) + hacked = state + for(var/id in SSresearch.techweb_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(id) + if((D.build_type & AUTOLATHE) && ("hacked" in D.category)) + if(hacked) + stored_research.add_design(D) + else + stored_research.remove_design(D) + +/obj/machinery/autolathe/hacked/Initialize() + . = ..() + adjust_hacked(TRUE) + +//Called when the object is constructed by an autolathe +//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes +/obj/item/proc/autolathe_crafted(obj/machinery/autolathe/A) + return diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 3b8cf90fb4f..a336e62e74c 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -1,290 +1,290 @@ -/obj/machinery/button - name = "button" - desc = "A remote control switch." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "doorctrl" - var/skin = "doorctrl" - power_channel = AREA_USAGE_ENVIRON - var/obj/item/assembly/device - var/obj/item/electronics/airlock/board - var/device_type = null - var/id = null - var/initialized_button = 0 - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 70) - use_power = IDLE_POWER_USE - idle_power_usage = 2 - resistance_flags = LAVA_PROOF | FIRE_PROOF - -/obj/machinery/button/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/Initialize(mapload, ndir = 0, built = 0) - . = ..() - if(built) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - panel_open = TRUE - update_icon() - - - if(!built && !device && device_type) - device = new device_type(src) - - src.check_access(null) - - if(req_access.len || req_one_access.len) - board = new(src) - if(req_access.len) - board.accesses = req_access - else - board.one_access = 1 - board.accesses = req_one_access - - setup_device() - -/obj/machinery/button/update_icon_state() - if(panel_open) - icon_state = "button-open" - else if(machine_stat & (NOPOWER|BROKEN)) - icon_state = "[skin]-p" - else - icon_state = skin - -/obj/machinery/button/update_overlays() - . = ..() - if(!panel_open) - return - if(device) - . += "button-device" - if(board) - . += "button-board" - -/obj/machinery/button/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(panel_open || allowed(user)) - default_deconstruction_screwdriver(user, "button-open", "[skin]",W) - update_icon() - else - to_chat(user, "Maintenance Access Denied.") - flick("[skin]-denied", src) - return - - if(panel_open) - if(!device && istype(W, /obj/item/assembly)) - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to you!") - return - device = W - to_chat(user, "You add [W] to the button.") - - if(!board && istype(W, /obj/item/electronics/airlock)) - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to you!") - return - board = W - if(board.one_access) - req_one_access = board.accesses - else - req_access = board.accesses - to_chat(user, "You add [W] to the button.") - - if(!device && !board && W.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start unsecuring the button frame...") - W.play_tool_sound(src) - if(W.use_tool(src, user, 40)) - to_chat(user, "You unsecure the button frame.") - transfer_fingerprints_to(new /obj/item/wallframe/button(get_turf(src))) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - qdel(src) - - update_icon() - return - - if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) - return attack_hand(user) - else - return ..() - -/obj/machinery/button/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - req_access = list() - req_one_access = list() - playsound(src, "sparks", 100, TRUE) - obj_flags |= EMAGGED - -/obj/machinery/button/attack_ai(mob/user) - if(!panel_open) - return attack_hand(user) - -/obj/machinery/button/attack_robot(mob/user) - return attack_ai(user) - -/obj/machinery/button/proc/setup_device() - if(id && istype(device, /obj/item/assembly/control)) - var/obj/item/assembly/control/A = device - A.id = id - initialized_button = 1 - -/obj/machinery/button/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - if(id && istype(device, /obj/item/assembly/control)) - var/obj/item/assembly/control/A = device - A.id = "[idnum][id]" - -/obj/machinery/button/attack_hand(mob/user) - . = ..() - if(.) - return - if(!initialized_button) - setup_device() - add_fingerprint(user) - if(panel_open) - if(device || board) - if(device) - device.forceMove(drop_location()) - device = null - if(board) - board.forceMove(drop_location()) - req_access = list() - req_one_access = list() - board = null - update_icon() - to_chat(user, "You remove electronics from the button frame.") - - else - if(skin == "doorctrl") - skin = "launcher" - else - skin = "doorctrl" - to_chat(user, "You change the button frame's front panel.") - return - - if((machine_stat & (NOPOWER|BROKEN))) - return - - if(device && device.next_activate > world.time) - return - - if(!allowed(user)) - to_chat(user, "Access Denied.") - flick("[skin]-denied", src) - return - - use_power(5) - icon_state = "[skin]1" - - if(device) - device.pulsed() - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_BUTTON_PRESSED,src) - - addtimer(CALLBACK(src, /atom/.proc/update_icon), 15) - -/obj/machinery/button/door - name = "door button" - desc = "A door remote control switch." - var/normaldoorcontrol = FALSE - var/specialfunctions = OPEN // Bitflag, see assembly file - var/sync_doors = TRUE - -/obj/machinery/button/door/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/door/setup_device() - if(!device) - if(normaldoorcontrol) - var/obj/item/assembly/control/airlock/A = new(src) - A.specialfunctions = specialfunctions - device = A - else - var/obj/item/assembly/control/C = new(src) - C.sync_doors = sync_doors - device = C - ..() - -/obj/machinery/button/door/incinerator_vent_toxmix - name = "combustion chamber vent control" - id = INCINERATOR_TOXMIX_VENT - req_access = list(ACCESS_TOXINS) - -/obj/machinery/button/door/incinerator_vent_atmos_main - name = "turbine vent control" - id = INCINERATOR_ATMOS_MAINVENT - req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) - -/obj/machinery/button/door/incinerator_vent_atmos_aux - name = "combustion chamber vent control" - id = INCINERATOR_ATMOS_AUXVENT - req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) - -/obj/machinery/button/door/incinerator_vent_syndicatelava_main - name = "turbine vent control" - id = INCINERATOR_SYNDICATELAVA_MAINVENT - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/button/door/incinerator_vent_syndicatelava_aux - name = "combustion chamber vent control" - id = INCINERATOR_SYNDICATELAVA_AUXVENT - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/button/massdriver - name = "mass driver button" - desc = "A remote control switch for a mass driver." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/massdriver - -/obj/machinery/button/massdriver/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/ignition - name = "ignition switch" - desc = "A remote control switch for a mounted igniter." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/igniter - -/obj/machinery/button/ignition/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/ignition/incinerator - name = "combustion chamber ignition switch" - desc = "A remote control switch for the combustion chamber's igniter." - -/obj/machinery/button/ignition/incinerator/toxmix - id = INCINERATOR_TOXMIX_IGNITER - -/obj/machinery/button/ignition/incinerator/atmos - id = INCINERATOR_ATMOS_IGNITER - -/obj/machinery/button/ignition/incinerator/syndicatelava - id = INCINERATOR_SYNDICATELAVA_IGNITER - -/obj/machinery/button/flasher - name = "flasher button" - desc = "A remote control switch for a mounted flasher." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/flasher - -/obj/machinery/button/flasher/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/crematorium - name = "crematorium igniter" - desc = "Burn baby burn!" - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/crematorium - req_access = list() - id = 1 - -/obj/machinery/button/crematorium/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/wallframe/button - name = "button frame" - desc = "Used for building buttons." - icon_state = "button" - result_path = /obj/machinery/button - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) +/obj/machinery/button + name = "button" + desc = "A remote control switch." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "doorctrl" + var/skin = "doorctrl" + power_channel = AREA_USAGE_ENVIRON + var/obj/item/assembly/device + var/obj/item/electronics/airlock/board + var/device_type = null + var/id = null + var/initialized_button = 0 + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 70) + use_power = IDLE_POWER_USE + idle_power_usage = 2 + resistance_flags = LAVA_PROOF | FIRE_PROOF + +/obj/machinery/button/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/Initialize(mapload, ndir = 0, built = 0) + . = ..() + if(built) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + panel_open = TRUE + update_icon() + + + if(!built && !device && device_type) + device = new device_type(src) + + src.check_access(null) + + if(req_access.len || req_one_access.len) + board = new(src) + if(req_access.len) + board.accesses = req_access + else + board.one_access = 1 + board.accesses = req_one_access + + setup_device() + +/obj/machinery/button/update_icon_state() + if(panel_open) + icon_state = "button-open" + else if(machine_stat & (NOPOWER|BROKEN)) + icon_state = "[skin]-p" + else + icon_state = skin + +/obj/machinery/button/update_overlays() + . = ..() + if(!panel_open) + return + if(device) + . += "button-device" + if(board) + . += "button-board" + +/obj/machinery/button/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(panel_open || allowed(user)) + default_deconstruction_screwdriver(user, "button-open", "[skin]",W) + update_icon() + else + to_chat(user, "Maintenance Access Denied.") + flick("[skin]-denied", src) + return + + if(panel_open) + if(!device && istype(W, /obj/item/assembly)) + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to you!") + return + device = W + to_chat(user, "You add [W] to the button.") + + if(!board && istype(W, /obj/item/electronics/airlock)) + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to you!") + return + board = W + if(board.one_access) + req_one_access = board.accesses + else + req_access = board.accesses + to_chat(user, "You add [W] to the button.") + + if(!device && !board && W.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start unsecuring the button frame...") + W.play_tool_sound(src) + if(W.use_tool(src, user, 40)) + to_chat(user, "You unsecure the button frame.") + transfer_fingerprints_to(new /obj/item/wallframe/button(get_turf(src))) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + qdel(src) + + update_icon() + return + + if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) + return attack_hand(user) + else + return ..() + +/obj/machinery/button/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + req_access = list() + req_one_access = list() + playsound(src, "sparks", 100, TRUE) + obj_flags |= EMAGGED + +/obj/machinery/button/attack_ai(mob/user) + if(!panel_open) + return attack_hand(user) + +/obj/machinery/button/attack_robot(mob/user) + return attack_ai(user) + +/obj/machinery/button/proc/setup_device() + if(id && istype(device, /obj/item/assembly/control)) + var/obj/item/assembly/control/A = device + A.id = id + initialized_button = 1 + +/obj/machinery/button/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + if(id && istype(device, /obj/item/assembly/control)) + var/obj/item/assembly/control/A = device + A.id = "[idnum][id]" + +/obj/machinery/button/attack_hand(mob/user) + . = ..() + if(.) + return + if(!initialized_button) + setup_device() + add_fingerprint(user) + if(panel_open) + if(device || board) + if(device) + device.forceMove(drop_location()) + device = null + if(board) + board.forceMove(drop_location()) + req_access = list() + req_one_access = list() + board = null + update_icon() + to_chat(user, "You remove electronics from the button frame.") + + else + if(skin == "doorctrl") + skin = "launcher" + else + skin = "doorctrl" + to_chat(user, "You change the button frame's front panel.") + return + + if((machine_stat & (NOPOWER|BROKEN))) + return + + if(device && device.next_activate > world.time) + return + + if(!allowed(user)) + to_chat(user, "Access Denied.") + flick("[skin]-denied", src) + return + + use_power(5) + icon_state = "[skin]1" + + if(device) + device.pulsed() + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_BUTTON_PRESSED,src) + + addtimer(CALLBACK(src, /atom/.proc/update_icon), 15) + +/obj/machinery/button/door + name = "door button" + desc = "A door remote control switch." + var/normaldoorcontrol = FALSE + var/specialfunctions = OPEN // Bitflag, see assembly file + var/sync_doors = TRUE + +/obj/machinery/button/door/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/door/setup_device() + if(!device) + if(normaldoorcontrol) + var/obj/item/assembly/control/airlock/A = new(src) + A.specialfunctions = specialfunctions + device = A + else + var/obj/item/assembly/control/C = new(src) + C.sync_doors = sync_doors + device = C + ..() + +/obj/machinery/button/door/incinerator_vent_toxmix + name = "combustion chamber vent control" + id = INCINERATOR_TOXMIX_VENT + req_access = list(ACCESS_TOXINS) + +/obj/machinery/button/door/incinerator_vent_atmos_main + name = "turbine vent control" + id = INCINERATOR_ATMOS_MAINVENT + req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) + +/obj/machinery/button/door/incinerator_vent_atmos_aux + name = "combustion chamber vent control" + id = INCINERATOR_ATMOS_AUXVENT + req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) + +/obj/machinery/button/door/incinerator_vent_syndicatelava_main + name = "turbine vent control" + id = INCINERATOR_SYNDICATELAVA_MAINVENT + req_access = list(ACCESS_SYNDICATE) + +/obj/machinery/button/door/incinerator_vent_syndicatelava_aux + name = "combustion chamber vent control" + id = INCINERATOR_SYNDICATELAVA_AUXVENT + req_access = list(ACCESS_SYNDICATE) + +/obj/machinery/button/massdriver + name = "mass driver button" + desc = "A remote control switch for a mass driver." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/massdriver + +/obj/machinery/button/massdriver/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/ignition + name = "ignition switch" + desc = "A remote control switch for a mounted igniter." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/igniter + +/obj/machinery/button/ignition/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/ignition/incinerator + name = "combustion chamber ignition switch" + desc = "A remote control switch for the combustion chamber's igniter." + +/obj/machinery/button/ignition/incinerator/toxmix + id = INCINERATOR_TOXMIX_IGNITER + +/obj/machinery/button/ignition/incinerator/atmos + id = INCINERATOR_ATMOS_IGNITER + +/obj/machinery/button/ignition/incinerator/syndicatelava + id = INCINERATOR_SYNDICATELAVA_IGNITER + +/obj/machinery/button/flasher + name = "flasher button" + desc = "A remote control switch for a mounted flasher." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/flasher + +/obj/machinery/button/flasher/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/crematorium + name = "crematorium igniter" + desc = "Burn baby burn!" + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/crematorium + req_access = list() + id = 1 + +/obj/machinery/button/crematorium/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/wallframe/button + name = "button frame" + desc = "Used for building buttons." + icon_state = "button" + result_path = /obj/machinery/button + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm index ee4041c83bb..fa3ccf256de 100644 --- a/code/game/machinery/camera/camera_assembly.dm +++ b/code/game/machinery/camera/camera_assembly.dm @@ -1,287 +1,287 @@ -#define STATE_WRENCHED 1 -#define STATE_WELDED 2 -#define STATE_WIRED 3 -#define STATE_FINISHED 4 - -/obj/item/wallframe/camera - name = "camera assembly" - desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." - icon = 'icons/obj/machines/camera.dmi' - icon_state = "cameracase" - custom_materials = list(/datum/material/iron=400, /datum/material/glass=250) - result_path = /obj/structure/camera_assembly - -/obj/structure/camera_assembly - name = "camera assembly" - desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." - icon = 'icons/obj/machines/camera.dmi' - icon_state = "camera_assembly" - max_integrity = 150 - // Motion, EMP-Proof, X-ray - var/obj/item/analyzer/xray_module - var/malf_xray_firmware_active //used to keep from revealing malf AI upgrades for user facing isXRay() checks when they use Upgrade Camera Network ability - //will be false if the camera is upgraded with the proper parts. - var/malf_xray_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. - var/obj/item/stack/sheet/mineral/plasma/emp_module - var/malf_emp_firmware_active //used to keep from revealing malf AI upgrades for user facing isEmp() checks after they use Upgrade Camera Network ability - //will be false if the camera is upgraded with the proper parts. - var/malf_emp_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. - var/obj/item/assembly/prox_sensor/proxy_module - var/state = STATE_WRENCHED - -/obj/structure/camera_assembly/examine(mob/user) - . = ..() - //upgrade messages - var/has_upgrades - if(emp_module) - . += "It has electromagnetic interference shielding installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be shielded against electromagnetic interference with some plasma." - if(xray_module) - . += "It has an X-ray photodiode installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be upgraded with an X-ray photodiode with an analyzer." - if(proxy_module) - . += "It has a proximity sensor installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be upgraded with a proximity sensor." - - //construction states - switch(state) - if(STATE_WRENCHED) - . += "You can secure it in place with a welder, or removed with a wrench." - if(STATE_WELDED) - . += "You can add wires to it, or unweld it from the wall." - if(STATE_WIRED) - if(has_upgrades) - . += "You can remove the contained upgrades with a crowbar." - . += "You can complete it with a screwdriver, or unwire it to start removal." - if(STATE_FINISHED) - . += "You shouldn't be seeing this, tell a coder!" - -/obj/structure/camera_assembly/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - -/obj/structure/camera_assembly/update_icon_state() - icon_state = "[xray_module ? "xray" : null][initial(icon_state)]" - -/obj/structure/camera_assembly/handle_atom_del(atom/A) - if(A == xray_module) - xray_module = null - update_icon() - if(malf_xray_firmware_present) - malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeXRay(malf_xray_firmware_present) //make sure we don't remove MALF upgrades. - - else if(A == emp_module) - emp_module = null - if(malf_emp_firmware_present) - malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeEmpProof(malf_emp_firmware_present) //make sure we don't remove MALF upgrades - - else if(A == proxy_module) - emp_module = null - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeMotion() - - return ..() - - -/obj/structure/camera_assembly/Destroy() - QDEL_NULL(xray_module) - QDEL_NULL(emp_module) - QDEL_NULL(proxy_module) - return ..() - -/obj/structure/camera_assembly/proc/drop_upgrade(obj/item/I) - I.forceMove(drop_location()) - if(I == xray_module) - xray_module = null - if(malf_xray_firmware_present) - malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. - update_icon() - - else if(I == emp_module) - emp_module = null - if(malf_emp_firmware_present) - malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. - - else if(I == proxy_module) - proxy_module = null - - -/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) - switch(state) - if(STATE_WRENCHED) - if(W.tool_behaviour == TOOL_WELDER) - if(weld(W, user)) - to_chat(user, "You weld [src] securely into place.") - setAnchored(TRUE) - state = STATE_WELDED - return - - if(STATE_WELDED) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = W - if(C.use(2)) - to_chat(user, "You add wires to [src].") - state = STATE_WIRED - else - to_chat(user, "You need two lengths of cable to wire a camera!") - return - return - - else if(W.tool_behaviour == TOOL_WELDER) - - if(weld(W, user)) - to_chat(user, "You unweld [src] from its place.") - state = STATE_WRENCHED - setAnchored(TRUE) - return - - if(STATE_WIRED) // Upgrades! - if(istype(W, /obj/item/stack/sheet/mineral/plasma)) //emp upgrade - if(emp_module) - to_chat(user, "[src] already contains a [emp_module]!") - return - if(!W.use_tool(src, user, 0, amount=1)) //only use one sheet, otherwise the whole stack will be consumed. - return - emp_module = new(src) - if(malf_xray_firmware_active) - malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part - //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. - to_chat(user, "You attach [W] into [src]'s inner circuits.") - return - - else if(istype(W, /obj/item/analyzer)) //xray upgrade - if(xray_module) - to_chat(user, "[src] already contains a [xray_module]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You attach [W] into [src]'s inner circuits.") - xray_module = W - if(malf_xray_firmware_active) - malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part - //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. - update_icon() - return - - else if(istype(W, /obj/item/assembly/prox_sensor)) //motion sensing upgrade - if(proxy_module) - to_chat(user, "[src] already contains a [proxy_module]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You attach [W] into [src]'s inner circuits.") - proxy_module = W - return - - return ..() - -/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) - if(state != STATE_WIRED) - return FALSE - var/list/droppable_parts = list() - if(xray_module) - droppable_parts += xray_module - if(emp_module) - droppable_parts += emp_module - if(proxy_module) - droppable_parts += proxy_module - if(!droppable_parts.len) - return - var/obj/item/choice = input(user, "Select a part to remove:", src) as null|obj in sortNames(droppable_parts) - if(!choice || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - to_chat(user, "You remove [choice] from [src].") - drop_upgrade(choice) - tool.play_tool_sound(src) - return TRUE - -/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) - . = ..() - if(.) - return TRUE - if(state != STATE_WIRED) - return FALSE - - tool.play_tool_sound(src) - var/input = stripped_input(user, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13") - if(!input) - to_chat(user, "No input found, please hang up and try your call again!") - return - var/list/tempnetwork = splittext(input, ",") - if(tempnetwork.len < 1) - to_chat(user, "No network found, please hang up and try your call again!") - return - for(var/i in tempnetwork) - tempnetwork -= i - tempnetwork += lowertext(i) - state = STATE_FINISHED - var/obj/machinery/camera/C = new(loc, src) - forceMove(C) - C.setDir(src.dir) - - C.network = tempnetwork - var/area/A = get_area(src) - C.c_tag = "[A.name] ([rand(1, 999)])" - return TRUE - -/obj/structure/camera_assembly/wirecutter_act(mob/user, obj/item/I) - . = ..() - if(state != STATE_WIRED) - return - - new /obj/item/stack/cable_coil(drop_location(), 2) - I.play_tool_sound(src) - to_chat(user, "You cut the wires from the circuits.") - state = STATE_WELDED - return TRUE - -/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) - . = ..() - if(state != STATE_WRENCHED) - return - I.play_tool_sound(src) - to_chat(user, "You detach [src] from its place.") - new /obj/item/wallframe/camera(drop_location()) - //drop upgrades - if(xray_module) - drop_upgrade(xray_module) - if(emp_module) - drop_upgrade(emp_module) - if(proxy_module) - drop_upgrade(proxy_module) - - qdel(src) - return TRUE - -/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) - if(!W.tool_start_check(user, amount=3)) - return FALSE - to_chat(user, "You start to weld [src]...") - if(W.use_tool(src, user, 20, amount=3, volume = 50)) - return TRUE - return FALSE - -/obj/structure/camera_assembly/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc) - qdel(src) - - -#undef STATE_WRENCHED -#undef STATE_WELDED -#undef STATE_WIRED -#undef STATE_FINISHED +#define STATE_WRENCHED 1 +#define STATE_WELDED 2 +#define STATE_WIRED 3 +#define STATE_FINISHED 4 + +/obj/item/wallframe/camera + name = "camera assembly" + desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." + icon = 'icons/obj/machines/camera.dmi' + icon_state = "cameracase" + custom_materials = list(/datum/material/iron=400, /datum/material/glass=250) + result_path = /obj/structure/camera_assembly + +/obj/structure/camera_assembly + name = "camera assembly" + desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." + icon = 'icons/obj/machines/camera.dmi' + icon_state = "camera_assembly" + max_integrity = 150 + // Motion, EMP-Proof, X-ray + var/obj/item/analyzer/xray_module + var/malf_xray_firmware_active //used to keep from revealing malf AI upgrades for user facing isXRay() checks when they use Upgrade Camera Network ability + //will be false if the camera is upgraded with the proper parts. + var/malf_xray_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. + var/obj/item/stack/sheet/mineral/plasma/emp_module + var/malf_emp_firmware_active //used to keep from revealing malf AI upgrades for user facing isEmp() checks after they use Upgrade Camera Network ability + //will be false if the camera is upgraded with the proper parts. + var/malf_emp_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. + var/obj/item/assembly/prox_sensor/proxy_module + var/state = STATE_WRENCHED + +/obj/structure/camera_assembly/examine(mob/user) + . = ..() + //upgrade messages + var/has_upgrades + if(emp_module) + . += "It has electromagnetic interference shielding installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be shielded against electromagnetic interference with some plasma." + if(xray_module) + . += "It has an X-ray photodiode installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be upgraded with an X-ray photodiode with an analyzer." + if(proxy_module) + . += "It has a proximity sensor installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be upgraded with a proximity sensor." + + //construction states + switch(state) + if(STATE_WRENCHED) + . += "You can secure it in place with a welder, or removed with a wrench." + if(STATE_WELDED) + . += "You can add wires to it, or unweld it from the wall." + if(STATE_WIRED) + if(has_upgrades) + . += "You can remove the contained upgrades with a crowbar." + . += "You can complete it with a screwdriver, or unwire it to start removal." + if(STATE_FINISHED) + . += "You shouldn't be seeing this, tell a coder!" + +/obj/structure/camera_assembly/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + +/obj/structure/camera_assembly/update_icon_state() + icon_state = "[xray_module ? "xray" : null][initial(icon_state)]" + +/obj/structure/camera_assembly/handle_atom_del(atom/A) + if(A == xray_module) + xray_module = null + update_icon() + if(malf_xray_firmware_present) + malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeXRay(malf_xray_firmware_present) //make sure we don't remove MALF upgrades. + + else if(A == emp_module) + emp_module = null + if(malf_emp_firmware_present) + malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeEmpProof(malf_emp_firmware_present) //make sure we don't remove MALF upgrades + + else if(A == proxy_module) + emp_module = null + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeMotion() + + return ..() + + +/obj/structure/camera_assembly/Destroy() + QDEL_NULL(xray_module) + QDEL_NULL(emp_module) + QDEL_NULL(proxy_module) + return ..() + +/obj/structure/camera_assembly/proc/drop_upgrade(obj/item/I) + I.forceMove(drop_location()) + if(I == xray_module) + xray_module = null + if(malf_xray_firmware_present) + malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. + update_icon() + + else if(I == emp_module) + emp_module = null + if(malf_emp_firmware_present) + malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. + + else if(I == proxy_module) + proxy_module = null + + +/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) + switch(state) + if(STATE_WRENCHED) + if(W.tool_behaviour == TOOL_WELDER) + if(weld(W, user)) + to_chat(user, "You weld [src] securely into place.") + setAnchored(TRUE) + state = STATE_WELDED + return + + if(STATE_WELDED) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = W + if(C.use(2)) + to_chat(user, "You add wires to [src].") + state = STATE_WIRED + else + to_chat(user, "You need two lengths of cable to wire a camera!") + return + return + + else if(W.tool_behaviour == TOOL_WELDER) + + if(weld(W, user)) + to_chat(user, "You unweld [src] from its place.") + state = STATE_WRENCHED + setAnchored(TRUE) + return + + if(STATE_WIRED) // Upgrades! + if(istype(W, /obj/item/stack/sheet/mineral/plasma)) //emp upgrade + if(emp_module) + to_chat(user, "[src] already contains a [emp_module]!") + return + if(!W.use_tool(src, user, 0, amount=1)) //only use one sheet, otherwise the whole stack will be consumed. + return + emp_module = new(src) + if(malf_xray_firmware_active) + malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part + //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. + to_chat(user, "You attach [W] into [src]'s inner circuits.") + return + + else if(istype(W, /obj/item/analyzer)) //xray upgrade + if(xray_module) + to_chat(user, "[src] already contains a [xray_module]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You attach [W] into [src]'s inner circuits.") + xray_module = W + if(malf_xray_firmware_active) + malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part + //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. + update_icon() + return + + else if(istype(W, /obj/item/assembly/prox_sensor)) //motion sensing upgrade + if(proxy_module) + to_chat(user, "[src] already contains a [proxy_module]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You attach [W] into [src]'s inner circuits.") + proxy_module = W + return + + return ..() + +/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) + if(state != STATE_WIRED) + return FALSE + var/list/droppable_parts = list() + if(xray_module) + droppable_parts += xray_module + if(emp_module) + droppable_parts += emp_module + if(proxy_module) + droppable_parts += proxy_module + if(!droppable_parts.len) + return + var/obj/item/choice = input(user, "Select a part to remove:", src) as null|obj in sortNames(droppable_parts) + if(!choice || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + to_chat(user, "You remove [choice] from [src].") + drop_upgrade(choice) + tool.play_tool_sound(src) + return TRUE + +/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) + . = ..() + if(.) + return TRUE + if(state != STATE_WIRED) + return FALSE + + tool.play_tool_sound(src) + var/input = stripped_input(user, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13") + if(!input) + to_chat(user, "No input found, please hang up and try your call again!") + return + var/list/tempnetwork = splittext(input, ",") + if(tempnetwork.len < 1) + to_chat(user, "No network found, please hang up and try your call again!") + return + for(var/i in tempnetwork) + tempnetwork -= i + tempnetwork += lowertext(i) + state = STATE_FINISHED + var/obj/machinery/camera/C = new(loc, src) + forceMove(C) + C.setDir(src.dir) + + C.network = tempnetwork + var/area/A = get_area(src) + C.c_tag = "[A.name] ([rand(1, 999)])" + return TRUE + +/obj/structure/camera_assembly/wirecutter_act(mob/user, obj/item/I) + . = ..() + if(state != STATE_WIRED) + return + + new /obj/item/stack/cable_coil(drop_location(), 2) + I.play_tool_sound(src) + to_chat(user, "You cut the wires from the circuits.") + state = STATE_WELDED + return TRUE + +/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) + . = ..() + if(state != STATE_WRENCHED) + return + I.play_tool_sound(src) + to_chat(user, "You detach [src] from its place.") + new /obj/item/wallframe/camera(drop_location()) + //drop upgrades + if(xray_module) + drop_upgrade(xray_module) + if(emp_module) + drop_upgrade(emp_module) + if(proxy_module) + drop_upgrade(proxy_module) + + qdel(src) + return TRUE + +/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) + if(!W.tool_start_check(user, amount=3)) + return FALSE + to_chat(user, "You start to weld [src]...") + if(W.use_tool(src, user, 20, amount=3, volume = 50)) + return TRUE + return FALSE + +/obj/structure/camera_assembly/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc) + qdel(src) + + +#undef STATE_WRENCHED +#undef STATE_WELDED +#undef STATE_WIRED +#undef STATE_FINISHED diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 3638fb50ccd..a5f531cfd60 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -1,112 +1,112 @@ -/obj/machinery/camera - - var/list/datum/weakref/localMotionTargets = list() - var/detectTime = 0 - var/area/ai_monitored/area_motion = null - var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm() - -/obj/machinery/camera/process() - // motion camera event loop - if(!isMotion()) - . = PROCESS_KILL - return - if(machine_stat & EMPED) - return - if (detectTime > 0) - var/elapsed = world.time - detectTime - if (elapsed > alarm_delay) - triggerAlarm() - else if (detectTime == -1) - for (var/datum/weakref/targetref in getTargetList()) - var/mob/target = targetref.resolve() - if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target))) - //If not part of a monitored area and the camera is not in range or the target is dead - lostTargetRef(targetref) - -/obj/machinery/camera/proc/getTargetList() - if(area_motion) - return area_motion.motionTargets - return localMotionTargets - -/obj/machinery/camera/proc/newTarget(mob/target) - if(isAI(target)) - return FALSE - if (detectTime == 0) - detectTime = world.time // start the clock - var/list/targets = getTargetList() - targets |= WEAKREF(target) - return TRUE - -/obj/machinery/camera/Destroy() - var/area/ai_monitored/A = get_area(src) - localMotionTargets = null - if(istype(A)) - A.motioncameras -= src - cancelAlarm() - return ..() - -/obj/machinery/camera/proc/lostTargetRef(datum/weakref/R) - var/list/targets = getTargetList() - targets -= R - if (targets.len == 0) - cancelAlarm() - -/obj/machinery/camera/proc/cancelAlarm() - if (detectTime == -1) - for (var/i in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = i - if (status) - aiPlayer.cancelAlarm("Motion", get_area(src), src) - detectTime = 0 - return TRUE - -/obj/machinery/camera/proc/triggerAlarm() - if (!detectTime) - return FALSE - for (var/mob/living/silicon/aiPlayer in GLOB.player_list) - if (status) - aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src) - visible_message("A red light flashes on the [src]!") - detectTime = -1 - return TRUE - -/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) - // Motion cameras outside of an "ai monitored" area will use this to detect stuff. - if (!area_motion) - if(isliving(AM)) - newTarget(AM) - -/obj/machinery/camera/motion/thunderdome - name = "entertainment camera" - network = list("thunder") - c_tag = "Arena" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF - -/obj/machinery/camera/motion/thunderdome/Initialize() - . = ..() - proximity_monitor.SetRange(7) - -/obj/machinery/camera/motion/thunderdome/HasProximity(atom/movable/AM as mob|obj) - if (!isliving(AM) || get_area(AM) != get_area(src)) - return - localMotionTargets |= WEAKREF(AM) - if (!detectTime) - for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) - TV.notify(TRUE) - detectTime = world.time + 30 SECONDS - -/obj/machinery/camera/motion/thunderdome/process() - if (!detectTime) - return - - for (var/datum/weakref/targetref in localMotionTargets) - var/mob/target = targetref.resolve() - if(QDELETED(target) || target.stat == DEAD || get_dist(src, target) > 7 || get_area(src) != get_area(target)) - localMotionTargets -= targetref - - if (localMotionTargets.len) - detectTime = world.time + 30 SECONDS - else if (world.time > detectTime) - detectTime = 0 - for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) - TV.notify(FALSE) +/obj/machinery/camera + + var/list/datum/weakref/localMotionTargets = list() + var/detectTime = 0 + var/area/ai_monitored/area_motion = null + var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm() + +/obj/machinery/camera/process() + // motion camera event loop + if(!isMotion()) + . = PROCESS_KILL + return + if(machine_stat & EMPED) + return + if (detectTime > 0) + var/elapsed = world.time - detectTime + if (elapsed > alarm_delay) + triggerAlarm() + else if (detectTime == -1) + for (var/datum/weakref/targetref in getTargetList()) + var/mob/target = targetref.resolve() + if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target))) + //If not part of a monitored area and the camera is not in range or the target is dead + lostTargetRef(targetref) + +/obj/machinery/camera/proc/getTargetList() + if(area_motion) + return area_motion.motionTargets + return localMotionTargets + +/obj/machinery/camera/proc/newTarget(mob/target) + if(isAI(target)) + return FALSE + if (detectTime == 0) + detectTime = world.time // start the clock + var/list/targets = getTargetList() + targets |= WEAKREF(target) + return TRUE + +/obj/machinery/camera/Destroy() + var/area/ai_monitored/A = get_area(src) + localMotionTargets = null + if(istype(A)) + A.motioncameras -= src + cancelAlarm() + return ..() + +/obj/machinery/camera/proc/lostTargetRef(datum/weakref/R) + var/list/targets = getTargetList() + targets -= R + if (targets.len == 0) + cancelAlarm() + +/obj/machinery/camera/proc/cancelAlarm() + if (detectTime == -1) + for (var/i in GLOB.silicon_mobs) + var/mob/living/silicon/aiPlayer = i + if (status) + aiPlayer.cancelAlarm("Motion", get_area(src), src) + detectTime = 0 + return TRUE + +/obj/machinery/camera/proc/triggerAlarm() + if (!detectTime) + return FALSE + for (var/mob/living/silicon/aiPlayer in GLOB.player_list) + if (status) + aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src) + visible_message("A red light flashes on the [src]!") + detectTime = -1 + return TRUE + +/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) + // Motion cameras outside of an "ai monitored" area will use this to detect stuff. + if (!area_motion) + if(isliving(AM)) + newTarget(AM) + +/obj/machinery/camera/motion/thunderdome + name = "entertainment camera" + network = list("thunder") + c_tag = "Arena" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF + +/obj/machinery/camera/motion/thunderdome/Initialize() + . = ..() + proximity_monitor.SetRange(7) + +/obj/machinery/camera/motion/thunderdome/HasProximity(atom/movable/AM as mob|obj) + if (!isliving(AM) || get_area(AM) != get_area(src)) + return + localMotionTargets |= WEAKREF(AM) + if (!detectTime) + for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) + TV.notify(TRUE) + detectTime = world.time + 30 SECONDS + +/obj/machinery/camera/motion/thunderdome/process() + if (!detectTime) + return + + for (var/datum/weakref/targetref in localMotionTargets) + var/mob/target = targetref.resolve() + if(QDELETED(target) || target.stat == DEAD || get_dist(src, target) > 7 || get_area(src) != get_area(target)) + localMotionTargets -= targetref + + if (localMotionTargets.len) + detectTime = world.time + 30 SECONDS + else if (world.time > detectTime) + detectTime = 0 + for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) + TV.notify(FALSE) diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index 399ce386925..70faa5139d1 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -1,143 +1,143 @@ -// PRESETS - -// EMP -/obj/machinery/camera/emp_proof - start_active = TRUE - -/obj/machinery/camera/emp_proof/Initialize() - . = ..() - upgradeEmpProof() - -// EMP + Motion - -/obj/machinery/camera/emp_proof/motion/Initialize() - . = ..() - upgradeMotion() - -// X-ray - -/obj/machinery/camera/xray - start_active = TRUE - icon_state = "xraycamera" //mapping icon - Thanks to Krutchen for the icons. - -/obj/machinery/camera/xray/Initialize() - . = ..() - upgradeXRay() - -// MOTION -/obj/machinery/camera/motion - start_active = TRUE - name = "motion-sensitive security camera" - -/obj/machinery/camera/motion/Initialize() - . = ..() - upgradeMotion() - -// ALL UPGRADES -/obj/machinery/camera/all - start_active = TRUE - icon_state = "xraycamera" //mapping icon. - -/obj/machinery/camera/all/Initialize() - . = ..() - upgradeEmpProof() - upgradeXRay() - upgradeMotion() - -// AUTONAME - -/obj/machinery/camera/autoname - var/number = 0 //camera number in area - -//This camera type automatically sets it's name to whatever the area that it's in is called. -/obj/machinery/camera/autoname/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/camera/autoname/LateInitialize() - . = ..() - number = 1 - var/area/A = get_area(src) - if(A) - for(var/obj/machinery/camera/autoname/C in GLOB.machines) - if(C == src) - continue - var/area/CA = get_area(C) - if(CA.type == A.type) - if(C.number) - number = max(number, C.number+1) - c_tag = "[A.name] #[number]" - - -// UPGRADE PROCS - -/obj/machinery/camera/proc/isEmpProof(ignore_malf_upgrades) - return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly.malf_emp_firmware_active)) - -/obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades) - if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one - return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. - emp_component = AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) - if(malf_upgrade) - assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. - assembly.malf_emp_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. - - else if(!assembly.emp_module) //only happens via upgrading in camera/attackby() - assembly.emp_module = new(assembly) - if(assembly.malf_emp_firmware_active) - assembly.malf_emp_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. - - upgrades |= CAMERA_UPGRADE_EMP_PROOF - -/obj/machinery/camera/proc/removeEmpProof(ignore_malf_upgrades) - if(ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. - return - emp_component.RemoveComponent() - upgrades &= ~CAMERA_UPGRADE_EMP_PROOF - - - -/obj/machinery/camera/proc/isXRay(ignore_malf_upgrades) - return (upgrades & CAMERA_UPGRADE_XRAY) && (!(ignore_malf_upgrades && assembly.malf_xray_firmware_active)) - -/obj/machinery/camera/proc/upgradeXRay(malf_upgrade, ignore_malf_upgrades) - if(isXRay(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf upgrade with the normal one - return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. - if(malf_upgrade) - assembly.malf_xray_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. - assembly.malf_xray_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. - - else if(!assembly.xray_module) //only happens via upgrading in camera/attackby() - assembly.xray_module = new(assembly) - if(assembly.malf_xray_firmware_active) - assembly.malf_xray_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. - - upgrades |= CAMERA_UPGRADE_XRAY - update_icon() - -/obj/machinery/camera/proc/removeXRay(ignore_malf_upgrades) - if(!ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. - upgrades &= ~CAMERA_UPGRADE_XRAY - update_icon() - - - -/obj/machinery/camera/proc/isMotion() - return upgrades & CAMERA_UPGRADE_MOTION - -/obj/machinery/camera/proc/upgradeMotion() - if(isMotion()) - return - if(name == initial(name)) - name = "motion-sensitive security camera" - if(!assembly.proxy_module) - assembly.proxy_module = new(assembly) - upgrades |= CAMERA_UPGRADE_MOTION - create_prox_monitor() - -/obj/machinery/camera/proc/removeMotion() - if(name == "motion-sensitive security camera") - name = "security camera" - upgrades &= ~CAMERA_UPGRADE_MOTION - if(!area_motion) - QDEL_NULL(proximity_monitor) +// PRESETS + +// EMP +/obj/machinery/camera/emp_proof + start_active = TRUE + +/obj/machinery/camera/emp_proof/Initialize() + . = ..() + upgradeEmpProof() + +// EMP + Motion + +/obj/machinery/camera/emp_proof/motion/Initialize() + . = ..() + upgradeMotion() + +// X-ray + +/obj/machinery/camera/xray + start_active = TRUE + icon_state = "xraycamera" //mapping icon - Thanks to Krutchen for the icons. + +/obj/machinery/camera/xray/Initialize() + . = ..() + upgradeXRay() + +// MOTION +/obj/machinery/camera/motion + start_active = TRUE + name = "motion-sensitive security camera" + +/obj/machinery/camera/motion/Initialize() + . = ..() + upgradeMotion() + +// ALL UPGRADES +/obj/machinery/camera/all + start_active = TRUE + icon_state = "xraycamera" //mapping icon. + +/obj/machinery/camera/all/Initialize() + . = ..() + upgradeEmpProof() + upgradeXRay() + upgradeMotion() + +// AUTONAME + +/obj/machinery/camera/autoname + var/number = 0 //camera number in area + +//This camera type automatically sets it's name to whatever the area that it's in is called. +/obj/machinery/camera/autoname/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/camera/autoname/LateInitialize() + . = ..() + number = 1 + var/area/A = get_area(src) + if(A) + for(var/obj/machinery/camera/autoname/C in GLOB.machines) + if(C == src) + continue + var/area/CA = get_area(C) + if(CA.type == A.type) + if(C.number) + number = max(number, C.number+1) + c_tag = "[A.name] #[number]" + + +// UPGRADE PROCS + +/obj/machinery/camera/proc/isEmpProof(ignore_malf_upgrades) + return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly.malf_emp_firmware_active)) + +/obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades) + if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one + return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. + emp_component = AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) + if(malf_upgrade) + assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. + assembly.malf_emp_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. + + else if(!assembly.emp_module) //only happens via upgrading in camera/attackby() + assembly.emp_module = new(assembly) + if(assembly.malf_emp_firmware_active) + assembly.malf_emp_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. + + upgrades |= CAMERA_UPGRADE_EMP_PROOF + +/obj/machinery/camera/proc/removeEmpProof(ignore_malf_upgrades) + if(ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. + return + emp_component.RemoveComponent() + upgrades &= ~CAMERA_UPGRADE_EMP_PROOF + + + +/obj/machinery/camera/proc/isXRay(ignore_malf_upgrades) + return (upgrades & CAMERA_UPGRADE_XRAY) && (!(ignore_malf_upgrades && assembly.malf_xray_firmware_active)) + +/obj/machinery/camera/proc/upgradeXRay(malf_upgrade, ignore_malf_upgrades) + if(isXRay(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf upgrade with the normal one + return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. + if(malf_upgrade) + assembly.malf_xray_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. + assembly.malf_xray_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. + + else if(!assembly.xray_module) //only happens via upgrading in camera/attackby() + assembly.xray_module = new(assembly) + if(assembly.malf_xray_firmware_active) + assembly.malf_xray_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. + + upgrades |= CAMERA_UPGRADE_XRAY + update_icon() + +/obj/machinery/camera/proc/removeXRay(ignore_malf_upgrades) + if(!ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. + upgrades &= ~CAMERA_UPGRADE_XRAY + update_icon() + + + +/obj/machinery/camera/proc/isMotion() + return upgrades & CAMERA_UPGRADE_MOTION + +/obj/machinery/camera/proc/upgradeMotion() + if(isMotion()) + return + if(name == initial(name)) + name = "motion-sensitive security camera" + if(!assembly.proxy_module) + assembly.proxy_module = new(assembly) + upgrades |= CAMERA_UPGRADE_MOTION + create_prox_monitor() + +/obj/machinery/camera/proc/removeMotion() + if(name == "motion-sensitive security camera") + name = "security camera" + upgrades &= ~CAMERA_UPGRADE_MOTION + if(!area_motion) + QDEL_NULL(proximity_monitor) diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm index 26f08e0e295..1b4b01f204d 100644 --- a/code/game/machinery/camera/tracking.dm +++ b/code/game/machinery/camera/tracking.dm @@ -1,154 +1,154 @@ -/mob/living/silicon/ai/proc/get_camera_list() - var/list/L = list() - for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) - L.Add(C) - - camera_sort(L) - - var/list/T = list() - - for (var/obj/machinery/camera/C in L) - var/list/tempnetwork = C.network&src.network - if (tempnetwork.len) - T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C - - return T - -/mob/living/silicon/ai/proc/show_camera_list() - var/list/cameras = get_camera_list() - var/camera = input(src, "Choose which camera you want to view", "Cameras") as null|anything in cameras - switchCamera(cameras[camera]) - -/datum/trackable - var/initialized = FALSE - var/list/names = list() - var/list/namecounts = list() - var/list/humans = list() - var/list/others = list() - -/mob/living/silicon/ai/proc/trackable_mobs() - track.initialized = TRUE - track.names.Cut() - track.namecounts.Cut() - track.humans.Cut() - track.others.Cut() - - if(usr.stat == DEAD) - return list() - - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - if(!L.can_track(usr)) - continue - - var/name = L.name - while(name in track.names) - track.namecounts[name]++ - name = text("[] ([])", name, track.namecounts[name]) - track.names.Add(name) - track.namecounts[name] = 1 - - if(ishuman(L)) - track.humans[name] = L - else - track.others[name] = L - - var/list/targets = sortList(track.humans) + sortList(track.others) - - return targets - -/mob/living/silicon/ai/verb/ai_camera_track(target_name in trackable_mobs()) - set name = "track" - set hidden = 1 //Don't display it on the verb lists. This verb exists purely so you can type "track Oldman Robustin" and follow his ass - - if(!target_name) - return - - if(!track.initialized) - trackable_mobs() - - var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) - - ai_actual_track(target) - -/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) - if(!istype(target)) - return - var/mob/living/silicon/ai/U = usr - - U.cameraFollow = target - U.tracking = 1 - - if(!target || !target.can_track(usr)) - to_chat(U, "Target is not near any active cameras.") - U.cameraFollow = null - return - - to_chat(U, "Now tracking [target.get_visible_name()] on camera.") - - INVOKE_ASYNC(src, .proc/do_track, target, U) - -/mob/living/silicon/ai/proc/do_track(mob/living/target, mob/living/silicon/ai/U) - var/cameraticks = 0 - - while(U.cameraFollow == target) - if(U.cameraFollow == null) - return - - if(!target.can_track(usr)) - U.tracking = 1 - if(!cameraticks) - to_chat(U, "Target is not near any active cameras. Attempting to reacquire...") - cameraticks++ - if(cameraticks > 9) - U.cameraFollow = null - to_chat(U, "Unable to reacquire, cancelling track...") - tracking = 0 - return - else - sleep(10) - continue - - else - cameraticks = 0 - U.tracking = 0 - - if(U.eyeobj) - U.eyeobj.setLoc(get_turf(target)) - - else - view_core() - U.cameraFollow = null - return - - sleep(10) - -/proc/near_camera(mob/living/M) - if (!isturf(M.loc)) - return FALSE - if(issilicon(M)) - var/mob/living/silicon/S = M - if((QDELETED(S.builtInCamera) || !S.builtInCamera.can_use()) && !GLOB.cameranet.checkCameraVis(M)) - return FALSE - else if(!GLOB.cameranet.checkCameraVis(M)) - return FALSE - return TRUE - -/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) - if (!istype(user)) - return - if (!can_use()) - return - user.switchCamera(src) - -/proc/camera_sort(list/L) - var/obj/machinery/camera/a - var/obj/machinery/camera/b - - for (var/i = L.len, i > 0, i--) - for (var/j = 1 to i - 1) - a = L[j] - b = L[j + 1] - if (sorttext(a.c_tag, b.c_tag) < 0) - L.Swap(j, j + 1) - return L +/mob/living/silicon/ai/proc/get_camera_list() + var/list/L = list() + for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) + L.Add(C) + + camera_sort(L) + + var/list/T = list() + + for (var/obj/machinery/camera/C in L) + var/list/tempnetwork = C.network&src.network + if (tempnetwork.len) + T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C + + return T + +/mob/living/silicon/ai/proc/show_camera_list() + var/list/cameras = get_camera_list() + var/camera = input(src, "Choose which camera you want to view", "Cameras") as null|anything in cameras + switchCamera(cameras[camera]) + +/datum/trackable + var/initialized = FALSE + var/list/names = list() + var/list/namecounts = list() + var/list/humans = list() + var/list/others = list() + +/mob/living/silicon/ai/proc/trackable_mobs() + track.initialized = TRUE + track.names.Cut() + track.namecounts.Cut() + track.humans.Cut() + track.others.Cut() + + if(usr.stat == DEAD) + return list() + + for(var/i in GLOB.mob_living_list) + var/mob/living/L = i + if(!L.can_track(usr)) + continue + + var/name = L.name + while(name in track.names) + track.namecounts[name]++ + name = text("[] ([])", name, track.namecounts[name]) + track.names.Add(name) + track.namecounts[name] = 1 + + if(ishuman(L)) + track.humans[name] = L + else + track.others[name] = L + + var/list/targets = sortList(track.humans) + sortList(track.others) + + return targets + +/mob/living/silicon/ai/verb/ai_camera_track(target_name in trackable_mobs()) + set name = "track" + set hidden = 1 //Don't display it on the verb lists. This verb exists purely so you can type "track Oldman Robustin" and follow his ass + + if(!target_name) + return + + if(!track.initialized) + trackable_mobs() + + var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) + + ai_actual_track(target) + +/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) + if(!istype(target)) + return + var/mob/living/silicon/ai/U = usr + + U.cameraFollow = target + U.tracking = 1 + + if(!target || !target.can_track(usr)) + to_chat(U, "Target is not near any active cameras.") + U.cameraFollow = null + return + + to_chat(U, "Now tracking [target.get_visible_name()] on camera.") + + INVOKE_ASYNC(src, .proc/do_track, target, U) + +/mob/living/silicon/ai/proc/do_track(mob/living/target, mob/living/silicon/ai/U) + var/cameraticks = 0 + + while(U.cameraFollow == target) + if(U.cameraFollow == null) + return + + if(!target.can_track(usr)) + U.tracking = 1 + if(!cameraticks) + to_chat(U, "Target is not near any active cameras. Attempting to reacquire...") + cameraticks++ + if(cameraticks > 9) + U.cameraFollow = null + to_chat(U, "Unable to reacquire, cancelling track...") + tracking = 0 + return + else + sleep(10) + continue + + else + cameraticks = 0 + U.tracking = 0 + + if(U.eyeobj) + U.eyeobj.setLoc(get_turf(target)) + + else + view_core() + U.cameraFollow = null + return + + sleep(10) + +/proc/near_camera(mob/living/M) + if (!isturf(M.loc)) + return FALSE + if(issilicon(M)) + var/mob/living/silicon/S = M + if((QDELETED(S.builtInCamera) || !S.builtInCamera.can_use()) && !GLOB.cameranet.checkCameraVis(M)) + return FALSE + else if(!GLOB.cameranet.checkCameraVis(M)) + return FALSE + return TRUE + +/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) + if (!istype(user)) + return + if (!can_use()) + return + user.switchCamera(src) + +/proc/camera_sort(list/L) + var/obj/machinery/camera/a + var/obj/machinery/camera/b + + for (var/i = L.len, i > 0, i--) + for (var/j = 1 to i - 1) + a = L[j] + b = L[j + 1] + if (sorttext(a.c_tag, b.c_tag) < 0) + L.Swap(j, j + 1) + return L diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm index 2a4fd3452fd..f23c7b7bf1f 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -1,131 +1,131 @@ -/obj/machinery/cell_charger - name = "cell charger" - desc = "It charges power cells." - icon = 'icons/obj/power.dmi' - icon_state = "ccharger" - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 60 - power_channel = AREA_USAGE_EQUIP - circuit = /obj/item/circuitboard/machine/cell_charger - pass_flags = PASSTABLE - var/obj/item/stock_parts/cell/charging = null - var/charge_rate = 500 - -/obj/machinery/cell_charger/update_overlays() - . = ..() - - if(!charging) - return - - . += image(charging.icon, charging.icon_state) - . += "ccharger-on" - if(!(machine_stat & (BROKEN|NOPOWER))) - var/newlevel = round(charging.percent() * 4 / 100) - . += "ccharger-o[newlevel]" - -/obj/machinery/cell_charger/examine(mob/user) - . = ..() - . += "There's [charging ? "a" : "no"] cell in the charger." - if(charging) - . += "Current charge: [round(charging.percent(), 1)]%." - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Charge rate at [charge_rate]J per cycle." - -/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell) && !panel_open) - if(machine_stat & BROKEN) - to_chat(user, "[src] is broken!") - return - if(!anchored) - to_chat(user, "[src] isn't attached to the ground!") - return - if(charging) - to_chat(user, "There is already a cell in the charger!") - return - else - var/area/a = loc.loc // Gets our locations location, like a dream within a dream - if(!isarea(a)) - return - if(a.power_equip == 0) // There's no APC in this area, don't try to cheat power! - to_chat(user, "[src] blinks red as you try to insert the cell!") - return - if(!user.transferItemToLoc(W,src)) - return - - charging = W - user.visible_message("[user] inserts a cell into [src].", "You insert a cell into [src].") - update_icon() - else - if(!charging && default_deconstruction_screwdriver(user, icon_state, icon_state, W)) - return - if(default_deconstruction_crowbar(W)) - return - if(!charging && default_unfasten_wrench(user, W)) - return - return ..() - -/obj/machinery/cell_charger/deconstruct() - if(charging) - charging.forceMove(drop_location()) - return ..() - -/obj/machinery/cell_charger/Destroy() - QDEL_NULL(charging) - return ..() - -/obj/machinery/cell_charger/proc/removecell() - charging.update_icon() - charging = null - update_icon() - -/obj/machinery/cell_charger/attack_hand(mob/user) - . = ..() - if(.) - return - if(!charging) - return - - user.put_in_hands(charging) - charging.add_fingerprint(user) - - user.visible_message("[user] removes [charging] from [src].", "You remove [charging] from [src].") - - removecell() - -/obj/machinery/cell_charger/attack_tk(mob/user) - if(!charging) - return - - charging.forceMove(loc) - to_chat(user, "You telekinetically remove [charging] from [src].") - - removecell() - -/obj/machinery/cell_charger/attack_ai(mob/user) - return - -/obj/machinery/cell_charger/emp_act(severity) - . = ..() - - if(machine_stat & (BROKEN|NOPOWER) || . & EMP_PROTECT_CONTENTS) - return - - if(charging) - charging.emp_act(severity) - -/obj/machinery/cell_charger/RefreshParts() - charge_rate = 500 - for(var/obj/item/stock_parts/capacitor/C in component_parts) - charge_rate *= C.rating - -/obj/machinery/cell_charger/process() - if(!charging || !anchored || (machine_stat & (BROKEN|NOPOWER))) - return - - if(charging.percent() >= 100) - return - use_power(charge_rate) - charging.give(charge_rate) //this is 2558, efficient batteries exist - - update_icon() +/obj/machinery/cell_charger + name = "cell charger" + desc = "It charges power cells." + icon = 'icons/obj/power.dmi' + icon_state = "ccharger" + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 60 + power_channel = AREA_USAGE_EQUIP + circuit = /obj/item/circuitboard/machine/cell_charger + pass_flags = PASSTABLE + var/obj/item/stock_parts/cell/charging = null + var/charge_rate = 500 + +/obj/machinery/cell_charger/update_overlays() + . = ..() + + if(!charging) + return + + . += image(charging.icon, charging.icon_state) + . += "ccharger-on" + if(!(machine_stat & (BROKEN|NOPOWER))) + var/newlevel = round(charging.percent() * 4 / 100) + . += "ccharger-o[newlevel]" + +/obj/machinery/cell_charger/examine(mob/user) + . = ..() + . += "There's [charging ? "a" : "no"] cell in the charger." + if(charging) + . += "Current charge: [round(charging.percent(), 1)]%." + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Charge rate at [charge_rate]J per cycle." + +/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell) && !panel_open) + if(machine_stat & BROKEN) + to_chat(user, "[src] is broken!") + return + if(!anchored) + to_chat(user, "[src] isn't attached to the ground!") + return + if(charging) + to_chat(user, "There is already a cell in the charger!") + return + else + var/area/a = loc.loc // Gets our locations location, like a dream within a dream + if(!isarea(a)) + return + if(a.power_equip == 0) // There's no APC in this area, don't try to cheat power! + to_chat(user, "[src] blinks red as you try to insert the cell!") + return + if(!user.transferItemToLoc(W,src)) + return + + charging = W + user.visible_message("[user] inserts a cell into [src].", "You insert a cell into [src].") + update_icon() + else + if(!charging && default_deconstruction_screwdriver(user, icon_state, icon_state, W)) + return + if(default_deconstruction_crowbar(W)) + return + if(!charging && default_unfasten_wrench(user, W)) + return + return ..() + +/obj/machinery/cell_charger/deconstruct() + if(charging) + charging.forceMove(drop_location()) + return ..() + +/obj/machinery/cell_charger/Destroy() + QDEL_NULL(charging) + return ..() + +/obj/machinery/cell_charger/proc/removecell() + charging.update_icon() + charging = null + update_icon() + +/obj/machinery/cell_charger/attack_hand(mob/user) + . = ..() + if(.) + return + if(!charging) + return + + user.put_in_hands(charging) + charging.add_fingerprint(user) + + user.visible_message("[user] removes [charging] from [src].", "You remove [charging] from [src].") + + removecell() + +/obj/machinery/cell_charger/attack_tk(mob/user) + if(!charging) + return + + charging.forceMove(loc) + to_chat(user, "You telekinetically remove [charging] from [src].") + + removecell() + +/obj/machinery/cell_charger/attack_ai(mob/user) + return + +/obj/machinery/cell_charger/emp_act(severity) + . = ..() + + if(machine_stat & (BROKEN|NOPOWER) || . & EMP_PROTECT_CONTENTS) + return + + if(charging) + charging.emp_act(severity) + +/obj/machinery/cell_charger/RefreshParts() + charge_rate = 500 + for(var/obj/item/stock_parts/capacitor/C in component_parts) + charge_rate *= C.rating + +/obj/machinery/cell_charger/process() + if(!charging || !anchored || (machine_stat & (BROKEN|NOPOWER))) + return + + if(charging.percent() >= 100) + return + use_power(charge_rate) + charging.give(charge_rate) //this is 2558, efficient batteries exist + + update_icon() diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index c3fcdd8d0e6..ddd91794a8a 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -1,155 +1,155 @@ -#define MENU_OPERATION 1 -#define MENU_SURGERIES 2 - -/obj/machinery/computer/operating - name = "operating computer" - desc = "Monitors patient vitals and displays surgery steps. Can be loaded with surgery disks to perform experimental procedures. Automatically syncs to stasis beds within its line of sight for surgical tech advancement." - icon_screen = "crew" - icon_keyboard = "med_key" - circuit = /obj/item/circuitboard/computer/operating - ui_x = 350 - ui_y = 470 - - var/mob/living/carbon/human/patient - var/obj/structure/table/optable/table - var/obj/machinery/stasis/sbed - var/list/advanced_surgeries = list() - var/datum/techweb/linked_techweb - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/operating/Initialize() - . = ..() - linked_techweb = SSresearch.science_tech - find_table() - -/obj/machinery/computer/operating/Destroy() - for(var/direction in GLOB.alldirs) - table = locate(/obj/structure/table/optable) in get_step(src, direction) - if(table && table.computer == src) - table.computer = null - else - sbed = locate(/obj/machinery/stasis) in get_step(src, direction) - if(sbed && sbed.op_computer == src) - sbed.op_computer = null - . = ..() - -/obj/machinery/computer/operating/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/disk/surgery)) - user.visible_message("[user] begins to load \the [O] in \the [src]...", \ - "You begin to load a surgery protocol from \the [O]...", \ - "You hear the chatter of a floppy drive.") - var/obj/item/disk/surgery/D = O - if(do_after(user, 10, target = src)) - advanced_surgeries |= D.surgeries - return TRUE - return ..() - -/obj/machinery/computer/operating/proc/sync_surgeries() - for(var/i in linked_techweb.researched_designs) - var/datum/design/surgery/D = SSresearch.techweb_design_by_id(i) - if(!istype(D)) - continue - advanced_surgeries |= D.surgery - -/obj/machinery/computer/operating/proc/find_table() - for(var/direction in GLOB.alldirs) - table = locate(/obj/structure/table/optable) in get_step(src, direction) - if(table) - table.computer = src - break - else - sbed = locate(/obj/machinery/stasis) in get_step(src, direction) - if(sbed) - sbed.op_computer = src - break - -/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "OperatingComputer", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/operating/ui_data(mob/user) - var/list/data = list() - var/list/surgeries = list() - for(var/X in advanced_surgeries) - var/datum/surgery/S = X - var/list/surgery = list() - surgery["name"] = initial(S.name) - surgery["desc"] = initial(S.desc) - surgeries += list(surgery) - data["surgeries"] = surgeries - data["patient"] = null - if(table) - data["table"] = table - if(!table.check_eligible_patient()) - return data - data["patient"] = list() - patient = table.patient - else - if(sbed) - data["table"] = sbed - if(!ishuman(sbed.occupant) && !ismonkey(sbed.occupant)) - return data - data["patient"] = list() - patient = sbed.occupant - else - data["patient"] = null - return data - switch(patient.stat) - if(CONSCIOUS) - data["patient"]["stat"] = "Conscious" - data["patient"]["statstate"] = "good" - if(SOFT_CRIT) - data["patient"]["stat"] = "Conscious" - data["patient"]["statstate"] = "average" - if(UNCONSCIOUS) - data["patient"]["stat"] = "Unconscious" - data["patient"]["statstate"] = "average" - if(DEAD) - data["patient"]["stat"] = "Dead" - data["patient"]["statstate"] = "bad" - data["patient"]["health"] = patient.health - data["patient"]["blood_type"] = patient.dna.blood_type - data["patient"]["maxHealth"] = patient.maxHealth - data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD - data["patient"]["bruteLoss"] = patient.getBruteLoss() - data["patient"]["fireLoss"] = patient.getFireLoss() - data["patient"]["toxLoss"] = patient.getToxLoss() - data["patient"]["oxyLoss"] = patient.getOxyLoss() - data["procedures"] = list() - if(patient.surgeries.len) - for(var/datum/surgery/procedure in patient.surgeries) - var/datum/surgery_step/surgery_step = procedure.get_surgery_step() - var/chems_needed = surgery_step.get_chem_list() - var/alternative_step - var/alt_chems_needed = "" - if(surgery_step.repeatable) - var/datum/surgery_step/next_step = procedure.get_surgery_next_step() - if(next_step) - alternative_step = capitalize(next_step.name) - alt_chems_needed = next_step.get_chem_list() - else - alternative_step = "Finish operation" - data["procedures"] += list(list( - "name" = capitalize("[parse_zone(procedure.location)] [procedure.name]"), - "next_step" = capitalize(surgery_step.name), - "chems_needed" = chems_needed, - "alternative_step" = alternative_step, - "alt_chems_needed" = alt_chems_needed - )) - return data - - - -/obj/machinery/computer/operating/ui_act(action, params) - if(..()) - return - switch(action) - if("sync") - sync_surgeries() - . = TRUE - . = TRUE - -#undef MENU_OPERATION -#undef MENU_SURGERIES +#define MENU_OPERATION 1 +#define MENU_SURGERIES 2 + +/obj/machinery/computer/operating + name = "operating computer" + desc = "Monitors patient vitals and displays surgery steps. Can be loaded with surgery disks to perform experimental procedures. Automatically syncs to stasis beds within its line of sight for surgical tech advancement." + icon_screen = "crew" + icon_keyboard = "med_key" + circuit = /obj/item/circuitboard/computer/operating + ui_x = 350 + ui_y = 470 + + var/mob/living/carbon/human/patient + var/obj/structure/table/optable/table + var/obj/machinery/stasis/sbed + var/list/advanced_surgeries = list() + var/datum/techweb/linked_techweb + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/operating/Initialize() + . = ..() + linked_techweb = SSresearch.science_tech + find_table() + +/obj/machinery/computer/operating/Destroy() + for(var/direction in GLOB.alldirs) + table = locate(/obj/structure/table/optable) in get_step(src, direction) + if(table && table.computer == src) + table.computer = null + else + sbed = locate(/obj/machinery/stasis) in get_step(src, direction) + if(sbed && sbed.op_computer == src) + sbed.op_computer = null + . = ..() + +/obj/machinery/computer/operating/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/disk/surgery)) + user.visible_message("[user] begins to load \the [O] in \the [src]...", \ + "You begin to load a surgery protocol from \the [O]...", \ + "You hear the chatter of a floppy drive.") + var/obj/item/disk/surgery/D = O + if(do_after(user, 10, target = src)) + advanced_surgeries |= D.surgeries + return TRUE + return ..() + +/obj/machinery/computer/operating/proc/sync_surgeries() + for(var/i in linked_techweb.researched_designs) + var/datum/design/surgery/D = SSresearch.techweb_design_by_id(i) + if(!istype(D)) + continue + advanced_surgeries |= D.surgery + +/obj/machinery/computer/operating/proc/find_table() + for(var/direction in GLOB.alldirs) + table = locate(/obj/structure/table/optable) in get_step(src, direction) + if(table) + table.computer = src + break + else + sbed = locate(/obj/machinery/stasis) in get_step(src, direction) + if(sbed) + sbed.op_computer = src + break + +/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "OperatingComputer", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/operating/ui_data(mob/user) + var/list/data = list() + var/list/surgeries = list() + for(var/X in advanced_surgeries) + var/datum/surgery/S = X + var/list/surgery = list() + surgery["name"] = initial(S.name) + surgery["desc"] = initial(S.desc) + surgeries += list(surgery) + data["surgeries"] = surgeries + data["patient"] = null + if(table) + data["table"] = table + if(!table.check_eligible_patient()) + return data + data["patient"] = list() + patient = table.patient + else + if(sbed) + data["table"] = sbed + if(!ishuman(sbed.occupant) && !ismonkey(sbed.occupant)) + return data + data["patient"] = list() + patient = sbed.occupant + else + data["patient"] = null + return data + switch(patient.stat) + if(CONSCIOUS) + data["patient"]["stat"] = "Conscious" + data["patient"]["statstate"] = "good" + if(SOFT_CRIT) + data["patient"]["stat"] = "Conscious" + data["patient"]["statstate"] = "average" + if(UNCONSCIOUS) + data["patient"]["stat"] = "Unconscious" + data["patient"]["statstate"] = "average" + if(DEAD) + data["patient"]["stat"] = "Dead" + data["patient"]["statstate"] = "bad" + data["patient"]["health"] = patient.health + data["patient"]["blood_type"] = patient.dna.blood_type + data["patient"]["maxHealth"] = patient.maxHealth + data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["patient"]["bruteLoss"] = patient.getBruteLoss() + data["patient"]["fireLoss"] = patient.getFireLoss() + data["patient"]["toxLoss"] = patient.getToxLoss() + data["patient"]["oxyLoss"] = patient.getOxyLoss() + data["procedures"] = list() + if(patient.surgeries.len) + for(var/datum/surgery/procedure in patient.surgeries) + var/datum/surgery_step/surgery_step = procedure.get_surgery_step() + var/chems_needed = surgery_step.get_chem_list() + var/alternative_step + var/alt_chems_needed = "" + if(surgery_step.repeatable) + var/datum/surgery_step/next_step = procedure.get_surgery_next_step() + if(next_step) + alternative_step = capitalize(next_step.name) + alt_chems_needed = next_step.get_chem_list() + else + alternative_step = "Finish operation" + data["procedures"] += list(list( + "name" = capitalize("[parse_zone(procedure.location)] [procedure.name]"), + "next_step" = capitalize(surgery_step.name), + "chems_needed" = chems_needed, + "alternative_step" = alternative_step, + "alt_chems_needed" = alt_chems_needed + )) + return data + + + +/obj/machinery/computer/operating/ui_act(action, params) + if(..()) + return + switch(action) + if("sync") + sync_surgeries() + . = TRUE + . = TRUE + +#undef MENU_OPERATION +#undef MENU_SURGERIES diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index cd88eaa10fa..cb574e36c09 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -1,90 +1,90 @@ -/obj/machinery/computer/atmos_alert - name = "atmospheric alert console" - desc = "Used to monitor the station's air alarms." - circuit = /obj/item/circuitboard/computer/atmos_alert - ui_x = 350 - ui_y = 300 - icon_screen = "alert:0" - icon_keyboard = "atmos_key" - var/list/priority_alarms = list() - var/list/minor_alarms = list() - var/receive_frequency = FREQ_ATMOS_ALARMS - var/datum/radio_frequency/radio_connection - - light_color = LIGHT_COLOR_CYAN - -/obj/machinery/computer/atmos_alert/Initialize() - . = ..() - set_frequency(receive_frequency) - -/obj/machinery/computer/atmos_alert/Destroy() - SSradio.remove_object(src, receive_frequency) - return ..() - -/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "AtmosAlertConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/atmos_alert/ui_data(mob/user) - var/list/data = list() - - data["priority"] = list() - for(var/zone in priority_alarms) - data["priority"] += zone - data["minor"] = list() - for(var/zone in minor_alarms) - data["minor"] += zone - - return data - -/obj/machinery/computer/atmos_alert/ui_act(action, params) - if(..()) - return - switch(action) - if("clear") - var/zone = params["zone"] - if(zone in priority_alarms) - to_chat(usr, "Priority alarm for [zone] cleared.") - priority_alarms -= zone - . = TRUE - if(zone in minor_alarms) - to_chat(usr, "Minor alarm for [zone] cleared.") - minor_alarms -= zone - . = TRUE - update_icon() - -/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency) - SSradio.remove_object(src, receive_frequency) - receive_frequency = new_frequency - radio_connection = SSradio.add_object(src, receive_frequency, RADIO_ATMOSIA) - -/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal) - if(!signal) - return - - var/zone = signal.data["zone"] - var/severity = signal.data["alert"] - - if(!zone || !severity) - return - - minor_alarms -= zone - priority_alarms -= zone - if(severity == "severe") - priority_alarms += zone - else if (severity == "minor") - minor_alarms += zone - update_icon() - return - -/obj/machinery/computer/atmos_alert/update_overlays() - . = ..() - if(machine_stat & (NOPOWER|BROKEN)) - return - if(priority_alarms.len) - . += "alert:2" - else if(minor_alarms.len) - . += "alert:1" +/obj/machinery/computer/atmos_alert + name = "atmospheric alert console" + desc = "Used to monitor the station's air alarms." + circuit = /obj/item/circuitboard/computer/atmos_alert + ui_x = 350 + ui_y = 300 + icon_screen = "alert:0" + icon_keyboard = "atmos_key" + var/list/priority_alarms = list() + var/list/minor_alarms = list() + var/receive_frequency = FREQ_ATMOS_ALARMS + var/datum/radio_frequency/radio_connection + + light_color = LIGHT_COLOR_CYAN + +/obj/machinery/computer/atmos_alert/Initialize() + . = ..() + set_frequency(receive_frequency) + +/obj/machinery/computer/atmos_alert/Destroy() + SSradio.remove_object(src, receive_frequency) + return ..() + +/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "AtmosAlertConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/atmos_alert/ui_data(mob/user) + var/list/data = list() + + data["priority"] = list() + for(var/zone in priority_alarms) + data["priority"] += zone + data["minor"] = list() + for(var/zone in minor_alarms) + data["minor"] += zone + + return data + +/obj/machinery/computer/atmos_alert/ui_act(action, params) + if(..()) + return + switch(action) + if("clear") + var/zone = params["zone"] + if(zone in priority_alarms) + to_chat(usr, "Priority alarm for [zone] cleared.") + priority_alarms -= zone + . = TRUE + if(zone in minor_alarms) + to_chat(usr, "Minor alarm for [zone] cleared.") + minor_alarms -= zone + . = TRUE + update_icon() + +/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency) + SSradio.remove_object(src, receive_frequency) + receive_frequency = new_frequency + radio_connection = SSradio.add_object(src, receive_frequency, RADIO_ATMOSIA) + +/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal) + if(!signal) + return + + var/zone = signal.data["zone"] + var/severity = signal.data["alert"] + + if(!zone || !severity) + return + + minor_alarms -= zone + priority_alarms -= zone + if(severity == "severe") + priority_alarms += zone + else if (severity == "minor") + minor_alarms += zone + update_icon() + return + +/obj/machinery/computer/atmos_alert/update_overlays() + . = ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + if(priority_alarms.len) + . += "alert:2" + else if(minor_alarms.len) + . += "alert:1" diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 06ea408add3..420e7e64ab7 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -1,335 +1,335 @@ -///////////////////////////////////////////////////////////// -// AIR SENSOR (found in gas tanks) -///////////////////////////////////////////////////////////// - -/obj/machinery/air_sensor - name = "gas sensor" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "gsensor1" - resistance_flags = FIRE_PROOF - - var/on = TRUE - - var/id_tag - var/frequency = FREQ_ATMOS_STORAGE - var/datum/radio_frequency/radio_connection - -/obj/machinery/air_sensor/atmos/toxin_tank - name = "plasma tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_TOX -/obj/machinery/air_sensor/atmos/toxins_mixing_tank - name = "toxins mixing gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB -/obj/machinery/air_sensor/atmos/oxygen_tank - name = "oxygen tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_O2 -/obj/machinery/air_sensor/atmos/nitrogen_tank - name = "nitrogen tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_N2 -/obj/machinery/air_sensor/atmos/mix_tank - name = "mix tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_MIX -/obj/machinery/air_sensor/atmos/nitrous_tank - name = "nitrous oxide tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_N2O -/obj/machinery/air_sensor/atmos/air_tank - name = "air mix tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_AIR -/obj/machinery/air_sensor/atmos/carbon_tank - name = "carbon dioxide tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_CO2 -/obj/machinery/air_sensor/atmos/incinerator_tank - name = "incinerator chamber gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_INCINERATOR - -/obj/machinery/air_sensor/update_icon_state() - icon_state = "gsensor[on]" - -/obj/machinery/air_sensor/process_atmos() - if(on) - var/datum/gas_mixture/air_sample = return_air() - - var/datum/signal/signal = new(list( - "sigtype" = "status", - "id_tag" = id_tag, - "timestamp" = world.time, - "pressure" = air_sample.return_pressure(), - "temperature" = air_sample.temperature, - "gases" = list() - )) - var/total_moles = air_sample.total_moles() - if(total_moles) - for(var/gas_id in air_sample.gases) - var/gas_name = air_sample.gases[gas_id][GAS_META][META_GAS_NAME] - signal.data["gases"][gas_name] = air_sample.gases[gas_id][MOLES] / total_moles * 100 - - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - - -/obj/machinery/air_sensor/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) - -/obj/machinery/air_sensor/Initialize() - . = ..() - SSair.atmos_machinery += src - set_frequency(frequency) - -/obj/machinery/air_sensor/Destroy() - SSair.atmos_machinery -= src - SSradio.remove_object(src, frequency) - return ..() - -///////////////////////////////////////////////////////////// -// GENERAL AIR CONTROL (a.k.a atmos computer) -///////////////////////////////////////////////////////////// -GLOBAL_LIST_EMPTY(atmos_air_controllers) - -/obj/machinery/computer/atmos_control - name = "atmospherics monitoring" - desc = "Used to monitor the station's atmospherics sensors." - icon_screen = "tank" - icon_keyboard = "atmos_key" - circuit = /obj/item/circuitboard/computer/atmos_control - ui_x = 400 - ui_y = 925 - - var/frequency = FREQ_ATMOS_STORAGE - var/list/sensors = list( - ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank", - ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank", - ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank", - ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank", - ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank", - ATMOS_GAS_MONITOR_SENSOR_AIR = "Mixed Air Tank", - ATMOS_GAS_MONITOR_SENSOR_MIX = "Mix Tank", - ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION = "Distribution Loop", - ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE = "Atmos Waste Loop", - ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber", - ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB = "Toxins Mixing Chamber" - ) - var/list/sensor_information = list() - var/datum/radio_frequency/radio_connection - - light_color = LIGHT_COLOR_CYAN - -/obj/machinery/computer/atmos_control/Initialize() - . = ..() - GLOB.atmos_air_controllers += src - set_frequency(frequency) - -/obj/machinery/computer/atmos_control/Destroy() - GLOB.atmos_air_controllers -= src - SSradio.remove_object(src, frequency) - return ..() - -/obj/machinery/computer/atmos_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/atmos_control/ui_data(mob/user) - var/data = list() - - data["sensors"] = list() - for(var/id_tag in sensors) - var/long_name = sensors[id_tag] - var/list/info = sensor_information[id_tag] - if(!info) - continue - data["sensors"] += list(list( - "id_tag" = id_tag, - "long_name" = sanitize(long_name), - "pressure" = info["pressure"], - "temperature" = info["temperature"], - "gases" = info["gases"] - )) - return data - -/obj/machinery/computer/atmos_control/receive_signal(datum/signal/signal) - if(!signal) - return - - var/id_tag = signal.data["id_tag"] - if(!id_tag || !sensors.Find(id_tag)) - return - - sensor_information[id_tag] = signal.data - -/obj/machinery/computer/atmos_control/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) - -//Incinerator sensor only -/obj/machinery/computer/atmos_control/incinerator - name = "Incinerator Air Control" - sensors = list(ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber") - circuit = /obj/item/circuitboard/computer/atmos_control/incinerator - ui_x = 400 - ui_y = 300 - -//Toxins mix sensor only -/obj/machinery/computer/atmos_control/toxinsmix - name = "Toxins Mixing Air Control" - sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB = "Toxins Mixing Chamber") - circuit = /obj/item/circuitboard/computer/atmos_control/toxinsmix - ui_x = 400 - ui_y = 300 - -///////////////////////////////////////////////////////////// -// LARGE TANK CONTROL -///////////////////////////////////////////////////////////// - -/obj/machinery/computer/atmos_control/tank - var/input_tag - var/output_tag - frequency = FREQ_ATMOS_STORAGE - circuit = /obj/item/circuitboard/computer/atmos_control/tank - - var/list/input_info - var/list/output_info - - ui_x = 500 - ui_y = 315 - -/obj/machinery/computer/atmos_control/tank/oxygen_tank - name = "Oxygen Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_O2 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_O2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/oxygen_tank - -/obj/machinery/computer/atmos_control/tank/toxin_tank - name = "Plasma Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_TOX - output_tag = ATMOS_GAS_MONITOR_OUTPUT_TOX - sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/toxin_tank - -/obj/machinery/computer/atmos_control/tank/air_tank - name = "Mixed Air Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_AIR - output_tag = ATMOS_GAS_MONITOR_OUTPUT_AIR - sensors = list(ATMOS_GAS_MONITOR_SENSOR_AIR = "Air Mix Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/air_tank - -/obj/machinery/computer/atmos_control/tank/mix_tank - name = "Gas Mix Tank Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_MIX - output_tag = ATMOS_GAS_MONITOR_OUTPUT_MIX - sensors = list(ATMOS_GAS_MONITOR_SENSOR_MIX = "Gas Mix Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/mix_tank - -/obj/machinery/computer/atmos_control/tank/nitrous_tank - name = "Nitrous Oxide Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_N2O - output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2O - sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrous_tank - -/obj/machinery/computer/atmos_control/tank/nitrogen_tank - name = "Nitrogen Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_N2 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrogen_tank - -/obj/machinery/computer/atmos_control/tank/carbon_tank - name = "Carbon Dioxide Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_CO2 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_CO2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/carbon_tank - -// This hacky madness is the evidence of the fact that a lot of machines were never meant to be constructable, im so sorry you had to see this -/obj/machinery/computer/atmos_control/tank/proc/reconnect(mob/user) - var/list/IO = list() - var/datum/radio_frequency/freq = SSradio.return_frequency(frequency) - var/list/devices = freq.devices["_default"] - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) - var/list/text = splittext(U.id_tag, "_") - IO |= text[1] - for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) - var/list/text = splittext(U.id, "_") - IO |= text[1] - if(!IO.len) - to_chat(user, "No machinery detected.") - var/S = input("Select the device set: ", "Selection", IO[1]) as anything in sortList(IO) - if(src) - src.input_tag = "[S]_in" - src.output_tag = "[S]_out" - name = "[uppertext(S)] Supply Control" - var/list/new_devices = freq.devices["atmosia"] - sensors.Cut() - for(var/obj/machinery/air_sensor/U in new_devices) - var/list/text = splittext(U.id_tag, "_") - if(text[1] == S) - sensors = list("[S]_sensor" = "[S] Tank") - break - - for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) - U.broadcast_status() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) - U.broadcast_status() - -/obj/machinery/computer/atmos_control/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/atmos_control/tank/ui_data(mob/user) - var/list/data = ..() - data["tank"] = TRUE - data["inputting"] = input_info ? input_info["power"] : FALSE - data["inputRate"] = input_info ? input_info["volume_rate"] : 0 - data["outputting"] = output_info ? output_info["power"] : FALSE - data["outputPressure"] = output_info ? output_info["internal"] : 0 - - return data - -/obj/machinery/computer/atmos_control/tank/ui_act(action, params) - if(..() || !radio_connection) - return - var/datum/signal/signal = new(list("sigtype" = "command", "user" = usr)) - switch(action) - if("reconnect") - reconnect(usr) - . = TRUE - if("input") - signal.data += list("tag" = input_tag, "power_toggle" = TRUE) - . = TRUE - if("rate") - var/target = text2num(params["rate"]) - if(!isnull(target)) - target = clamp(target, 0, MAX_TRANSFER_RATE) - signal.data += list("tag" = input_tag, "set_volume_rate" = target) - . = TRUE - if("output") - signal.data += list("tag" = output_tag, "power_toggle" = TRUE) - . = TRUE - if("pressure") - var/target = text2num(params["pressure"]) - if(!isnull(target)) - target = clamp(target, 0, 4500) - signal.data += list("tag" = output_tag, "set_internal_pressure" = target) - . = TRUE - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - -/obj/machinery/computer/atmos_control/tank/receive_signal(datum/signal/signal) - if(!signal) - return - - var/id_tag = signal.data["tag"] - - if(input_tag == id_tag) - input_info = signal.data - else if(output_tag == id_tag) - output_info = signal.data - else - ..(signal) +///////////////////////////////////////////////////////////// +// AIR SENSOR (found in gas tanks) +///////////////////////////////////////////////////////////// + +/obj/machinery/air_sensor + name = "gas sensor" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "gsensor1" + resistance_flags = FIRE_PROOF + + var/on = TRUE + + var/id_tag + var/frequency = FREQ_ATMOS_STORAGE + var/datum/radio_frequency/radio_connection + +/obj/machinery/air_sensor/atmos/toxin_tank + name = "plasma tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_TOX +/obj/machinery/air_sensor/atmos/toxins_mixing_tank + name = "toxins mixing gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB +/obj/machinery/air_sensor/atmos/oxygen_tank + name = "oxygen tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_O2 +/obj/machinery/air_sensor/atmos/nitrogen_tank + name = "nitrogen tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_N2 +/obj/machinery/air_sensor/atmos/mix_tank + name = "mix tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_MIX +/obj/machinery/air_sensor/atmos/nitrous_tank + name = "nitrous oxide tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_N2O +/obj/machinery/air_sensor/atmos/air_tank + name = "air mix tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_AIR +/obj/machinery/air_sensor/atmos/carbon_tank + name = "carbon dioxide tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_CO2 +/obj/machinery/air_sensor/atmos/incinerator_tank + name = "incinerator chamber gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_INCINERATOR + +/obj/machinery/air_sensor/update_icon_state() + icon_state = "gsensor[on]" + +/obj/machinery/air_sensor/process_atmos() + if(on) + var/datum/gas_mixture/air_sample = return_air() + + var/datum/signal/signal = new(list( + "sigtype" = "status", + "id_tag" = id_tag, + "timestamp" = world.time, + "pressure" = air_sample.return_pressure(), + "temperature" = air_sample.temperature, + "gases" = list() + )) + var/total_moles = air_sample.total_moles() + if(total_moles) + for(var/gas_id in air_sample.gases) + var/gas_name = air_sample.gases[gas_id][GAS_META][META_GAS_NAME] + signal.data["gases"][gas_name] = air_sample.gases[gas_id][MOLES] / total_moles * 100 + + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + + +/obj/machinery/air_sensor/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) + +/obj/machinery/air_sensor/Initialize() + . = ..() + SSair.atmos_machinery += src + set_frequency(frequency) + +/obj/machinery/air_sensor/Destroy() + SSair.atmos_machinery -= src + SSradio.remove_object(src, frequency) + return ..() + +///////////////////////////////////////////////////////////// +// GENERAL AIR CONTROL (a.k.a atmos computer) +///////////////////////////////////////////////////////////// +GLOBAL_LIST_EMPTY(atmos_air_controllers) + +/obj/machinery/computer/atmos_control + name = "atmospherics monitoring" + desc = "Used to monitor the station's atmospherics sensors." + icon_screen = "tank" + icon_keyboard = "atmos_key" + circuit = /obj/item/circuitboard/computer/atmos_control + ui_x = 400 + ui_y = 925 + + var/frequency = FREQ_ATMOS_STORAGE + var/list/sensors = list( + ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank", + ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank", + ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank", + ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank", + ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank", + ATMOS_GAS_MONITOR_SENSOR_AIR = "Mixed Air Tank", + ATMOS_GAS_MONITOR_SENSOR_MIX = "Mix Tank", + ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION = "Distribution Loop", + ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE = "Atmos Waste Loop", + ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber", + ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB = "Toxins Mixing Chamber" + ) + var/list/sensor_information = list() + var/datum/radio_frequency/radio_connection + + light_color = LIGHT_COLOR_CYAN + +/obj/machinery/computer/atmos_control/Initialize() + . = ..() + GLOB.atmos_air_controllers += src + set_frequency(frequency) + +/obj/machinery/computer/atmos_control/Destroy() + GLOB.atmos_air_controllers -= src + SSradio.remove_object(src, frequency) + return ..() + +/obj/machinery/computer/atmos_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/atmos_control/ui_data(mob/user) + var/data = list() + + data["sensors"] = list() + for(var/id_tag in sensors) + var/long_name = sensors[id_tag] + var/list/info = sensor_information[id_tag] + if(!info) + continue + data["sensors"] += list(list( + "id_tag" = id_tag, + "long_name" = sanitize(long_name), + "pressure" = info["pressure"], + "temperature" = info["temperature"], + "gases" = info["gases"] + )) + return data + +/obj/machinery/computer/atmos_control/receive_signal(datum/signal/signal) + if(!signal) + return + + var/id_tag = signal.data["id_tag"] + if(!id_tag || !sensors.Find(id_tag)) + return + + sensor_information[id_tag] = signal.data + +/obj/machinery/computer/atmos_control/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) + +//Incinerator sensor only +/obj/machinery/computer/atmos_control/incinerator + name = "Incinerator Air Control" + sensors = list(ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber") + circuit = /obj/item/circuitboard/computer/atmos_control/incinerator + ui_x = 400 + ui_y = 300 + +//Toxins mix sensor only +/obj/machinery/computer/atmos_control/toxinsmix + name = "Toxins Mixing Air Control" + sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB = "Toxins Mixing Chamber") + circuit = /obj/item/circuitboard/computer/atmos_control/toxinsmix + ui_x = 400 + ui_y = 300 + +///////////////////////////////////////////////////////////// +// LARGE TANK CONTROL +///////////////////////////////////////////////////////////// + +/obj/machinery/computer/atmos_control/tank + var/input_tag + var/output_tag + frequency = FREQ_ATMOS_STORAGE + circuit = /obj/item/circuitboard/computer/atmos_control/tank + + var/list/input_info + var/list/output_info + + ui_x = 500 + ui_y = 315 + +/obj/machinery/computer/atmos_control/tank/oxygen_tank + name = "Oxygen Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_O2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_O2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/oxygen_tank + +/obj/machinery/computer/atmos_control/tank/toxin_tank + name = "Plasma Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_TOX + output_tag = ATMOS_GAS_MONITOR_OUTPUT_TOX + sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/toxin_tank + +/obj/machinery/computer/atmos_control/tank/air_tank + name = "Mixed Air Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_AIR + output_tag = ATMOS_GAS_MONITOR_OUTPUT_AIR + sensors = list(ATMOS_GAS_MONITOR_SENSOR_AIR = "Air Mix Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/air_tank + +/obj/machinery/computer/atmos_control/tank/mix_tank + name = "Gas Mix Tank Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_MIX + output_tag = ATMOS_GAS_MONITOR_OUTPUT_MIX + sensors = list(ATMOS_GAS_MONITOR_SENSOR_MIX = "Gas Mix Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/mix_tank + +/obj/machinery/computer/atmos_control/tank/nitrous_tank + name = "Nitrous Oxide Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_N2O + output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2O + sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrous_tank + +/obj/machinery/computer/atmos_control/tank/nitrogen_tank + name = "Nitrogen Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_N2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrogen_tank + +/obj/machinery/computer/atmos_control/tank/carbon_tank + name = "Carbon Dioxide Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_CO2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_CO2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/carbon_tank + +// This hacky madness is the evidence of the fact that a lot of machines were never meant to be constructable, im so sorry you had to see this +/obj/machinery/computer/atmos_control/tank/proc/reconnect(mob/user) + var/list/IO = list() + var/datum/radio_frequency/freq = SSradio.return_frequency(frequency) + var/list/devices = freq.devices["_default"] + for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) + var/list/text = splittext(U.id_tag, "_") + IO |= text[1] + for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) + var/list/text = splittext(U.id, "_") + IO |= text[1] + if(!IO.len) + to_chat(user, "No machinery detected.") + var/S = input("Select the device set: ", "Selection", IO[1]) as anything in sortList(IO) + if(src) + src.input_tag = "[S]_in" + src.output_tag = "[S]_out" + name = "[uppertext(S)] Supply Control" + var/list/new_devices = freq.devices["atmosia"] + sensors.Cut() + for(var/obj/machinery/air_sensor/U in new_devices) + var/list/text = splittext(U.id_tag, "_") + if(text[1] == S) + sensors = list("[S]_sensor" = "[S] Tank") + break + + for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) + U.broadcast_status() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) + U.broadcast_status() + +/obj/machinery/computer/atmos_control/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/atmos_control/tank/ui_data(mob/user) + var/list/data = ..() + data["tank"] = TRUE + data["inputting"] = input_info ? input_info["power"] : FALSE + data["inputRate"] = input_info ? input_info["volume_rate"] : 0 + data["outputting"] = output_info ? output_info["power"] : FALSE + data["outputPressure"] = output_info ? output_info["internal"] : 0 + + return data + +/obj/machinery/computer/atmos_control/tank/ui_act(action, params) + if(..() || !radio_connection) + return + var/datum/signal/signal = new(list("sigtype" = "command", "user" = usr)) + switch(action) + if("reconnect") + reconnect(usr) + . = TRUE + if("input") + signal.data += list("tag" = input_tag, "power_toggle" = TRUE) + . = TRUE + if("rate") + var/target = text2num(params["rate"]) + if(!isnull(target)) + target = clamp(target, 0, MAX_TRANSFER_RATE) + signal.data += list("tag" = input_tag, "set_volume_rate" = target) + . = TRUE + if("output") + signal.data += list("tag" = output_tag, "power_toggle" = TRUE) + . = TRUE + if("pressure") + var/target = text2num(params["pressure"]) + if(!isnull(target)) + target = clamp(target, 0, 4500) + signal.data += list("tag" = output_tag, "set_internal_pressure" = target) + . = TRUE + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + +/obj/machinery/computer/atmos_control/tank/receive_signal(datum/signal/signal) + if(!signal) + return + + var/id_tag = signal.data["tag"] + + if(input_tag == id_tag) + input_info = signal.data + else if(output_tag == id_tag) + output_info = signal.data + else + ..(signal) diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 270767b1a3c..24f26f80410 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -1,143 +1,143 @@ -/obj/structure/frame/computer - name = "computer frame" - icon_state = "0" - state = 0 - -/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) - add_fingerprint(user) - switch(state) - if(0) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start wrenching the frame into place...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You wrench the frame into place.") - setAnchored(TRUE) - state = 1 - return - if(P.tool_behaviour == TOOL_WELDER) - if(!P.tool_start_check(user, amount=0)) - return - - to_chat(user, "You start deconstructing the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You deconstruct the frame.") - var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) - M.add_fingerprint(user) - qdel(src) - return - if(1) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start to unfasten the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You unfasten the frame.") - setAnchored(FALSE) - state = 0 - return - if(istype(P, /obj/item/circuitboard/computer) && !circuit) - if(!user.transferItemToLoc(P, src)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You place [P] inside the frame.") - icon_state = "1" - circuit = P - circuit.add_fingerprint(user) - return - - else if(istype(P, /obj/item/circuitboard) && !circuit) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You screw [circuit] into place.") - state = 2 - icon_state = "2" - return - if(P.tool_behaviour == TOOL_CROWBAR && circuit) - P.play_tool_sound(src) - to_chat(user, "You remove [circuit].") - state = 1 - icon_state = "0" - circuit.forceMove(drop_location()) - circuit.add_fingerprint(user) - circuit = null - return - if(2) - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You unfasten the circuit board.") - state = 1 - icon_state = "1" - return - if(istype(P, /obj/item/stack/cable_coil)) - if(!P.tool_start_check(user, amount=5)) - return - to_chat(user, "You start adding cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) - if(state != 2) - return - to_chat(user, "You add cables to the frame.") - state = 3 - icon_state = "3" - return - if(3) - if(P.tool_behaviour == TOOL_WIRECUTTER) - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = 2 - icon_state = "2" - var/obj/item/stack/cable_coil/A = new (drop_location(), 5) - A.add_fingerprint(user) - return - - if(istype(P, /obj/item/stack/sheet/glass)) - if(!P.tool_start_check(user, amount=2)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You start to put in the glass panel...") - if(P.use_tool(src, user, 20, amount=2)) - if(state != 3) - return - to_chat(user, "You put in the glass panel.") - state = 4 - src.icon_state = "4" - return - if(4) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the glass panel.") - state = 3 - icon_state = "3" - var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) - G.add_fingerprint(user) - return - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - var/obj/B = new circuit.build_path (loc, circuit) - B.setDir(dir) - transfer_fingerprints_to(B) - qdel(src) - return - if(user.a_intent == INTENT_HARM) - return ..() - - -/obj/structure/frame/computer/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(state == 4) - new /obj/item/shard(drop_location()) - new /obj/item/shard(drop_location()) - if(state >= 3) - new /obj/item/stack/cable_coil(drop_location(), 5) - ..() - -/obj/structure/frame/computer/AltClick(mob/user) - ..() - if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - - if(anchored) - to_chat(usr, "You must unwrench [src] before rotating it!") - return - - setDir(turn(dir, -90)) +/obj/structure/frame/computer + name = "computer frame" + icon_state = "0" + state = 0 + +/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) + add_fingerprint(user) + switch(state) + if(0) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start wrenching the frame into place...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You wrench the frame into place.") + setAnchored(TRUE) + state = 1 + return + if(P.tool_behaviour == TOOL_WELDER) + if(!P.tool_start_check(user, amount=0)) + return + + to_chat(user, "You start deconstructing the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You deconstruct the frame.") + var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) + M.add_fingerprint(user) + qdel(src) + return + if(1) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start to unfasten the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You unfasten the frame.") + setAnchored(FALSE) + state = 0 + return + if(istype(P, /obj/item/circuitboard/computer) && !circuit) + if(!user.transferItemToLoc(P, src)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You place [P] inside the frame.") + icon_state = "1" + circuit = P + circuit.add_fingerprint(user) + return + + else if(istype(P, /obj/item/circuitboard) && !circuit) + to_chat(user, "This frame does not accept circuit boards of this type!") + return + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You screw [circuit] into place.") + state = 2 + icon_state = "2" + return + if(P.tool_behaviour == TOOL_CROWBAR && circuit) + P.play_tool_sound(src) + to_chat(user, "You remove [circuit].") + state = 1 + icon_state = "0" + circuit.forceMove(drop_location()) + circuit.add_fingerprint(user) + circuit = null + return + if(2) + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You unfasten the circuit board.") + state = 1 + icon_state = "1" + return + if(istype(P, /obj/item/stack/cable_coil)) + if(!P.tool_start_check(user, amount=5)) + return + to_chat(user, "You start adding cables to the frame...") + if(P.use_tool(src, user, 20, volume=50, amount=5)) + if(state != 2) + return + to_chat(user, "You add cables to the frame.") + state = 3 + icon_state = "3" + return + if(3) + if(P.tool_behaviour == TOOL_WIRECUTTER) + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = 2 + icon_state = "2" + var/obj/item/stack/cable_coil/A = new (drop_location(), 5) + A.add_fingerprint(user) + return + + if(istype(P, /obj/item/stack/sheet/glass)) + if(!P.tool_start_check(user, amount=2)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You start to put in the glass panel...") + if(P.use_tool(src, user, 20, amount=2)) + if(state != 3) + return + to_chat(user, "You put in the glass panel.") + state = 4 + src.icon_state = "4" + return + if(4) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the glass panel.") + state = 3 + icon_state = "3" + var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) + G.add_fingerprint(user) + return + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + var/obj/B = new circuit.build_path (loc, circuit) + B.setDir(dir) + transfer_fingerprints_to(B) + qdel(src) + return + if(user.a_intent == INTENT_HARM) + return ..() + + +/obj/structure/frame/computer/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(state == 4) + new /obj/item/shard(drop_location()) + new /obj/item/shard(drop_location()) + if(state >= 3) + new /obj/item/stack/cable_coil(drop_location(), 5) + ..() + +/obj/structure/frame/computer/AltClick(mob/user) + ..() + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if(anchored) + to_chat(usr, "You must unwrench [src] before rotating it!") + return + + setDir(turn(dir, -90)) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index d15054b6a9a..07f97e6b780 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -1,340 +1,340 @@ -#define DEFAULT_MAP_SIZE 15 - -/obj/machinery/computer/security - name = "security camera console" - desc = "Used to access the various cameras on the station." - icon_screen = "cameras" - icon_keyboard = "security_key" - circuit = /obj/item/circuitboard/computer/security - light_color = LIGHT_COLOR_RED - ui_x = 870 - ui_y = 708 - - var/list/network = list("ss13") - var/obj/machinery/camera/active_camera - var/list/concurrent_users = list() - - // Stuff needed to render the map - var/map_name - var/obj/screen/map_view/cam_screen - /// All the plane masters that need to be applied. - var/list/cam_plane_masters - var/obj/screen/background/cam_background - -/obj/machinery/computer/security/Initialize() - . = ..() - // Map name has to start and end with an A-Z character, - // and definitely NOT with a square bracket or even a number. - // I wasted 6 hours on this. :agony: - map_name = "camera_console_[REF(src)]_map" - // Convert networks to lowercase - for(var/i in network) - network -= i - network += lowertext(i) - // Initialize map objects - cam_screen = new - cam_screen.name = "screen" - cam_screen.assigned_map = map_name - cam_screen.del_on_map_removal = FALSE - cam_screen.screen_loc = "[map_name]:1,1" - cam_plane_masters = list() - for(var/plane in subtypesof(/obj/screen/plane_master)) - var/obj/screen/instance = new plane() - instance.assigned_map = map_name - instance.del_on_map_removal = FALSE - instance.screen_loc = "[map_name]:CENTER" - cam_plane_masters += instance - cam_background = new - cam_background.assigned_map = map_name - cam_background.del_on_map_removal = FALSE - -/obj/machinery/computer/security/Destroy() - qdel(cam_screen) - QDEL_LIST(cam_plane_masters) - qdel(cam_background) - return ..() - -/obj/machinery/computer/security/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - for(var/i in network) - network -= i - network += "[idnum][i]" - -/obj/machinery/computer/security/ui_interact(\ - mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - // Update UI - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() - if(!ui) - var/user_ref = REF(user) - var/is_living = isliving(user) - // Ghosts shouldn't count towards concurrent users, which produces - // an audible terminal_on click. - if(is_living) - concurrent_users += user_ref - // Turn on the console - if(length(concurrent_users) == 1 && is_living) - playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE) - use_power(active_power_usage) - // Register map objects - user.client.register_map_obj(cam_screen) - for(var/plane in cam_plane_masters) - user.client.register_map_obj(plane) - user.client.register_map_obj(cam_background) - // Open UI - ui = new(user, src, ui_key, "CameraConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/security/ui_data() - var/list/data = list() - data["network"] = network - data["activeCamera"] = null - if(active_camera) - data["activeCamera"] = list( - name = active_camera.c_tag, - status = active_camera.status, - ) - return data - -/obj/machinery/computer/security/ui_static_data() - var/list/data = list() - data["mapRef"] = map_name - var/list/cameras = get_available_cameras() - data["cameras"] = list() - for(var/i in cameras) - var/obj/machinery/camera/C = cameras[i] - data["cameras"] += list(list( - name = C.c_tag, - )) - return data - -/obj/machinery/computer/security/ui_act(action, params) - . = ..() - if(.) - return - - if(action == "switch_camera") - var/c_tag = params["name"] - var/list/cameras = get_available_cameras() - var/obj/machinery/camera/C = cameras[c_tag] - active_camera = C - playsound(src, get_sfx("terminal_type"), 25, FALSE) - - // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() - return TRUE - - var/list/visible_turfs = list() - for(var/turf/T in (C.isXRay() \ - ? range(C.view_range, C) \ - : view(C.view_range, C))) - visible_turfs += T - - var/list/bbox = get_bbox_of_atoms(visible_turfs) - var/size_x = bbox[3] - bbox[1] + 1 - var/size_y = bbox[4] - bbox[2] + 1 - - cam_screen.vis_contents = visible_turfs - cam_background.icon_state = "clear" - cam_background.fill_rect(1, 1, size_x, size_y) - - return TRUE - -/obj/machinery/computer/security/ui_close(mob/user) - var/user_ref = REF(user) - var/is_living = isliving(user) - // Living creature or not, we remove you anyway. - concurrent_users -= user_ref - // Unregister map objects - user.client.clear_map(map_name) - // Turn off the console - if(length(concurrent_users) == 0 && is_living) - active_camera = null - playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE) - use_power(0) - -/obj/machinery/computer/security/proc/show_camera_static() - cam_screen.vis_contents.Cut() - cam_background.icon_state = "scanline2" - cam_background.fill_rect(1, 1, DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE) - -// Returns the list of cameras accessible from this computer -/obj/machinery/computer/security/proc/get_available_cameras() - var/list/L = list() - for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) - if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras - continue - L.Add(C) - var/list/D = list() - for(var/obj/machinery/camera/C in L) - if(!C.network) - stack_trace("Camera in a cameranet has no camera network") - continue - if(!(islist(C.network))) - stack_trace("Camera in a cameranet has a non-list camera network") - continue - var/list/tempnetwork = C.network & network - if(tempnetwork.len) - D["[C.c_tag]"] = C - return D - -// SECURITY MONITORS - -/obj/machinery/computer/security/wooden_tv - name = "security camera monitor" - desc = "An old TV hooked into the station's camera network." - icon_state = "television" - icon_keyboard = "no_keyboard" - icon_screen = "detective_tv" - pass_flags = PASSTABLE - -/obj/machinery/computer/security/mining - name = "outpost camera console" - desc = "Used to access the various cameras on the outpost." - icon_screen = "mining" - icon_keyboard = "mining_key" - network = list("mine", "auxbase") - circuit = /obj/item/circuitboard/computer/mining - -/obj/machinery/computer/security/research - name = "research camera console" - desc = "Used to access the various cameras in science." - network = list("rd") - circuit = /obj/item/circuitboard/computer/research - -/obj/machinery/computer/security/hos - name = "\improper Head of Security's camera console" - desc = "A custom security console with added access to the labor camp network." - network = list("ss13", "labor") - circuit = null - -/obj/machinery/computer/security/labor - name = "labor camp monitoring" - desc = "Used to access the various cameras on the labor camp." - network = list("labor") - circuit = null - -/obj/machinery/computer/security/qm - name = "\improper Quartermaster's camera console" - desc = "A console with access to the mining, auxiliary base and vault camera networks." - network = list("mine", "auxbase", "vault") - circuit = null - -// TELESCREENS - -/obj/machinery/computer/security/telescreen - name = "\improper Telescreen" - desc = "Used for watching an empty arena." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "telescreen" - layer = SIGN_LAYER - network = list("thunder") - density = FALSE - circuit = null - light_power = 0 - -/obj/machinery/computer/security/telescreen/update_icon_state() - icon_state = initial(icon_state) - if(machine_stat & BROKEN) - icon_state += "b" - -/obj/machinery/computer/security/telescreen/entertainment - name = "entertainment monitor" - desc = "Damn, they better have the /tg/ channel on these things." - icon = 'icons/obj/status_display.dmi' - icon_state = "entertainment_blank" - network = list("thunder") - density = FALSE - circuit = null - interaction_flags_atom = NONE // interact() is called by BigClick() - var/icon_state_off = "entertainment_blank" - var/icon_state_on = "entertainment" - -/obj/machinery/computer/security/telescreen/entertainment/Initialize() - . = ..() - RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) - -// Bypass clickchain to allow humans to use the telescreen from a distance -/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() - interact(usr) - -/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) - if(on && icon_state == icon_state_off) - say(pick( - "Feats of bravery live now at the thunderdome!", - "Two enter, one leaves! Tune in now!", - "Violence like you've never seen it before!", - "Spears! Camera! Action! LIVE NOW!")) - icon_state = icon_state_on - else - icon_state = icon_state_off - -/obj/machinery/computer/security/telescreen/rd - name = "\improper Research Director's telescreen" - desc = "Used for watching the AI and the RD's goons from the safety of his office." - network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") - -/obj/machinery/computer/security/telescreen/research - name = "research telescreen" - desc = "A telescreen with access to the research division's camera network." - network = list("rd") - -/obj/machinery/computer/security/telescreen/ce - name = "\improper Chief Engineer's telescreen" - desc = "Used for watching the engine, telecommunications and the minisat." - network = list("engine", "singularity", "tcomms", "minisat") - -/obj/machinery/computer/security/telescreen/cmo - name = "\improper Chief Medical Officer's telescreen" - desc = "A telescreen with access to the medbay's camera network." - network = list("medbay") - -/obj/machinery/computer/security/telescreen/vault - name = "vault monitor" - desc = "A telescreen that connects to the vault's camera network." - network = list("vault") - -/obj/machinery/computer/security/telescreen/toxins - name = "bomb test site monitor" - desc = "A telescreen that connects to the bomb test site's camera." - network = list("toxins") - -/obj/machinery/computer/security/telescreen/engine - name = "engine monitor" - desc = "A telescreen that connects to the engine's camera network." - network = list("engine") - -/obj/machinery/computer/security/telescreen/turbine - name = "turbine monitor" - desc = "A telescreen that connects to the turbine's camera." - network = list("turbine") - -/obj/machinery/computer/security/telescreen/interrogation - name = "interrogation room monitor" - desc = "A telescreen that connects to the interrogation room's camera." - network = list("interrogation") - -/obj/machinery/computer/security/telescreen/prison - name = "prison monitor" - desc = "A telescreen that connects to the permabrig's camera network." - network = list("prison") - -/obj/machinery/computer/security/telescreen/auxbase - name = "auxiliary base monitor" - desc = "A telescreen that connects to the auxiliary base's camera." - network = list("auxbase") - -/obj/machinery/computer/security/telescreen/minisat - name = "minisat monitor" - desc = "A telescreen that connects to the minisat's camera network." - network = list("minisat") - -/obj/machinery/computer/security/telescreen/aiupload - name = "\improper AI upload monitor" - desc = "A telescreen that connects to the AI upload's camera network." - network = list("aiupload") - -#undef DEFAULT_MAP_SIZE +#define DEFAULT_MAP_SIZE 15 + +/obj/machinery/computer/security + name = "security camera console" + desc = "Used to access the various cameras on the station." + icon_screen = "cameras" + icon_keyboard = "security_key" + circuit = /obj/item/circuitboard/computer/security + light_color = LIGHT_COLOR_RED + ui_x = 870 + ui_y = 708 + + var/list/network = list("ss13") + var/obj/machinery/camera/active_camera + var/list/concurrent_users = list() + + // Stuff needed to render the map + var/map_name + var/obj/screen/map_view/cam_screen + /// All the plane masters that need to be applied. + var/list/cam_plane_masters + var/obj/screen/background/cam_background + +/obj/machinery/computer/security/Initialize() + . = ..() + // Map name has to start and end with an A-Z character, + // and definitely NOT with a square bracket or even a number. + // I wasted 6 hours on this. :agony: + map_name = "camera_console_[REF(src)]_map" + // Convert networks to lowercase + for(var/i in network) + network -= i + network += lowertext(i) + // Initialize map objects + cam_screen = new + cam_screen.name = "screen" + cam_screen.assigned_map = map_name + cam_screen.del_on_map_removal = FALSE + cam_screen.screen_loc = "[map_name]:1,1" + cam_plane_masters = list() + for(var/plane in subtypesof(/obj/screen/plane_master)) + var/obj/screen/instance = new plane() + instance.assigned_map = map_name + instance.del_on_map_removal = FALSE + instance.screen_loc = "[map_name]:CENTER" + cam_plane_masters += instance + cam_background = new + cam_background.assigned_map = map_name + cam_background.del_on_map_removal = FALSE + +/obj/machinery/computer/security/Destroy() + qdel(cam_screen) + QDEL_LIST(cam_plane_masters) + qdel(cam_background) + return ..() + +/obj/machinery/computer/security/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + for(var/i in network) + network -= i + network += "[idnum][i]" + +/obj/machinery/computer/security/ui_interact(\ + mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + // Update UI + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + if(!ui) + var/user_ref = REF(user) + var/is_living = isliving(user) + // Ghosts shouldn't count towards concurrent users, which produces + // an audible terminal_on click. + if(is_living) + concurrent_users += user_ref + // Turn on the console + if(length(concurrent_users) == 1 && is_living) + playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE) + use_power(active_power_usage) + // Register map objects + user.client.register_map_obj(cam_screen) + for(var/plane in cam_plane_masters) + user.client.register_map_obj(plane) + user.client.register_map_obj(cam_background) + // Open UI + ui = new(user, src, ui_key, "CameraConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/security/ui_data() + var/list/data = list() + data["network"] = network + data["activeCamera"] = null + if(active_camera) + data["activeCamera"] = list( + name = active_camera.c_tag, + status = active_camera.status, + ) + return data + +/obj/machinery/computer/security/ui_static_data() + var/list/data = list() + data["mapRef"] = map_name + var/list/cameras = get_available_cameras() + data["cameras"] = list() + for(var/i in cameras) + var/obj/machinery/camera/C = cameras[i] + data["cameras"] += list(list( + name = C.c_tag, + )) + return data + +/obj/machinery/computer/security/ui_act(action, params) + . = ..() + if(.) + return + + if(action == "switch_camera") + var/c_tag = params["name"] + var/list/cameras = get_available_cameras() + var/obj/machinery/camera/C = cameras[c_tag] + active_camera = C + playsound(src, get_sfx("terminal_type"), 25, FALSE) + + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + return TRUE + + var/list/visible_turfs = list() + for(var/turf/T in (C.isXRay() \ + ? range(C.view_range, C) \ + : view(C.view_range, C))) + visible_turfs += T + + var/list/bbox = get_bbox_of_atoms(visible_turfs) + var/size_x = bbox[3] - bbox[1] + 1 + var/size_y = bbox[4] - bbox[2] + 1 + + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, size_x, size_y) + + return TRUE + +/obj/machinery/computer/security/ui_close(mob/user) + var/user_ref = REF(user) + var/is_living = isliving(user) + // Living creature or not, we remove you anyway. + concurrent_users -= user_ref + // Unregister map objects + user.client.clear_map(map_name) + // Turn off the console + if(length(concurrent_users) == 0 && is_living) + active_camera = null + playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE) + use_power(0) + +/obj/machinery/computer/security/proc/show_camera_static() + cam_screen.vis_contents.Cut() + cam_background.icon_state = "scanline2" + cam_background.fill_rect(1, 1, DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE) + +// Returns the list of cameras accessible from this computer +/obj/machinery/computer/security/proc/get_available_cameras() + var/list/L = list() + for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) + if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras + continue + L.Add(C) + var/list/D = list() + for(var/obj/machinery/camera/C in L) + if(!C.network) + stack_trace("Camera in a cameranet has no camera network") + continue + if(!(islist(C.network))) + stack_trace("Camera in a cameranet has a non-list camera network") + continue + var/list/tempnetwork = C.network & network + if(tempnetwork.len) + D["[C.c_tag]"] = C + return D + +// SECURITY MONITORS + +/obj/machinery/computer/security/wooden_tv + name = "security camera monitor" + desc = "An old TV hooked into the station's camera network." + icon_state = "television" + icon_keyboard = "no_keyboard" + icon_screen = "detective_tv" + pass_flags = PASSTABLE + +/obj/machinery/computer/security/mining + name = "outpost camera console" + desc = "Used to access the various cameras on the outpost." + icon_screen = "mining" + icon_keyboard = "mining_key" + network = list("mine", "auxbase") + circuit = /obj/item/circuitboard/computer/mining + +/obj/machinery/computer/security/research + name = "research camera console" + desc = "Used to access the various cameras in science." + network = list("rd") + circuit = /obj/item/circuitboard/computer/research + +/obj/machinery/computer/security/hos + name = "\improper Head of Security's camera console" + desc = "A custom security console with added access to the labor camp network." + network = list("ss13", "labor") + circuit = null + +/obj/machinery/computer/security/labor + name = "labor camp monitoring" + desc = "Used to access the various cameras on the labor camp." + network = list("labor") + circuit = null + +/obj/machinery/computer/security/qm + name = "\improper Quartermaster's camera console" + desc = "A console with access to the mining, auxiliary base and vault camera networks." + network = list("mine", "auxbase", "vault") + circuit = null + +// TELESCREENS + +/obj/machinery/computer/security/telescreen + name = "\improper Telescreen" + desc = "Used for watching an empty arena." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "telescreen" + layer = SIGN_LAYER + network = list("thunder") + density = FALSE + circuit = null + light_power = 0 + +/obj/machinery/computer/security/telescreen/update_icon_state() + icon_state = initial(icon_state) + if(machine_stat & BROKEN) + icon_state += "b" + +/obj/machinery/computer/security/telescreen/entertainment + name = "entertainment monitor" + desc = "Damn, they better have the /tg/ channel on these things." + icon = 'icons/obj/status_display.dmi' + icon_state = "entertainment_blank" + network = list("thunder") + density = FALSE + circuit = null + interaction_flags_atom = NONE // interact() is called by BigClick() + var/icon_state_off = "entertainment_blank" + var/icon_state_on = "entertainment" + +/obj/machinery/computer/security/telescreen/entertainment/Initialize() + . = ..() + RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) + +// Bypass clickchain to allow humans to use the telescreen from a distance +/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() + interact(usr) + +/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) + if(on && icon_state == icon_state_off) + say(pick( + "Feats of bravery live now at the thunderdome!", + "Two enter, one leaves! Tune in now!", + "Violence like you've never seen it before!", + "Spears! Camera! Action! LIVE NOW!")) + icon_state = icon_state_on + else + icon_state = icon_state_off + +/obj/machinery/computer/security/telescreen/rd + name = "\improper Research Director's telescreen" + desc = "Used for watching the AI and the RD's goons from the safety of his office." + network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") + +/obj/machinery/computer/security/telescreen/research + name = "research telescreen" + desc = "A telescreen with access to the research division's camera network." + network = list("rd") + +/obj/machinery/computer/security/telescreen/ce + name = "\improper Chief Engineer's telescreen" + desc = "Used for watching the engine, telecommunications and the minisat." + network = list("engine", "singularity", "tcomms", "minisat") + +/obj/machinery/computer/security/telescreen/cmo + name = "\improper Chief Medical Officer's telescreen" + desc = "A telescreen with access to the medbay's camera network." + network = list("medbay") + +/obj/machinery/computer/security/telescreen/vault + name = "vault monitor" + desc = "A telescreen that connects to the vault's camera network." + network = list("vault") + +/obj/machinery/computer/security/telescreen/toxins + name = "bomb test site monitor" + desc = "A telescreen that connects to the bomb test site's camera." + network = list("toxins") + +/obj/machinery/computer/security/telescreen/engine + name = "engine monitor" + desc = "A telescreen that connects to the engine's camera network." + network = list("engine") + +/obj/machinery/computer/security/telescreen/turbine + name = "turbine monitor" + desc = "A telescreen that connects to the turbine's camera." + network = list("turbine") + +/obj/machinery/computer/security/telescreen/interrogation + name = "interrogation room monitor" + desc = "A telescreen that connects to the interrogation room's camera." + network = list("interrogation") + +/obj/machinery/computer/security/telescreen/prison + name = "prison monitor" + desc = "A telescreen that connects to the permabrig's camera network." + network = list("prison") + +/obj/machinery/computer/security/telescreen/auxbase + name = "auxiliary base monitor" + desc = "A telescreen that connects to the auxiliary base's camera." + network = list("auxbase") + +/obj/machinery/computer/security/telescreen/minisat + name = "minisat monitor" + desc = "A telescreen that connects to the minisat's camera network." + network = list("minisat") + +/obj/machinery/computer/security/telescreen/aiupload + name = "\improper AI upload monitor" + desc = "A telescreen that connects to the AI upload's camera network." + network = list("aiupload") + +#undef DEFAULT_MAP_SIZE diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index cb672742676..5e53df7973f 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -1,634 +1,634 @@ - - -//Keeps track of the time for the ID console. Having it as a global variable prevents people from dismantling/reassembling it to -//increase the slots of many jobs. -GLOBAL_VAR_INIT(time_last_changed_position, 0) - -#define JOB_ALLOWED 1 -#define JOB_COOLDOWN -2 -#define JOB_MAX_POSITIONS -1 // Trying to reduce the number of slots below that of current holders of that job, or trying to open more slots than allowed -#define JOB_DENIED 0 - -/obj/machinery/computer/card - name = "identification console" - desc = "You can use this to manage jobs and ID access." - icon_screen = "id" - icon_keyboard = "id_key" - req_one_access = list(ACCESS_HEADS, ACCESS_CHANGE_IDS) - circuit = /obj/item/circuitboard/computer/card - var/mode = 0 - var/printing = null - var/target_dept = 0 //Which department this computer has access to. 0=all departments - - //Cooldown for closing positions in seconds - //if set to -1: No cooldown... probably a bad idea - //if set to 0: Not able to close "original" positions. You can only close positions that you have opened before - var/change_position_cooldown = 30 - //Jobs you cannot open new positions for - var/list/blacklisted = list( - "AI", - "Assistant", - "Cyborg", - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer", - "Prisoner") - - //The scaling factor of max total positions in relation to the total amount of people on board the station in % - var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players - - //This is used to keep track of opened positions for jobs to allow instant closing - //Assoc array: "JobName" = (int) - var/list/opened_positions = list() - var/obj/item/card/id/inserted_scan_id - var/obj/item/card/id/inserted_modify_id - var/list/region_access = null - var/list/head_subordinates = null - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/card/proc/get_jobs() - return get_all_jobs() - -/obj/machinery/computer/card/centcom/get_jobs() - return get_all_centcom_jobs() - -/obj/machinery/computer/card/Initialize() - . = ..() - change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) - -/obj/machinery/computer/card/examine(mob/user) - . = ..() - if(inserted_scan_id || inserted_modify_id) - . += "Alt-click to eject the ID card." - -/obj/machinery/computer/card/attackby(obj/I, mob/user, params) - if(isidcard(I)) - if(check_access(I) && !inserted_scan_id) - if(id_insert(user, I, inserted_scan_id)) - inserted_scan_id = I - updateUsrDialog() - else if(id_insert(user, I, inserted_modify_id)) - inserted_modify_id = I - updateUsrDialog() - else - return ..() - -/obj/machinery/computer/card/Destroy() - if(inserted_scan_id) - qdel(inserted_scan_id) - inserted_scan_id = null - if(inserted_modify_id) - qdel(inserted_modify_id) - inserted_modify_id = null - return ..() - -/obj/machinery/computer/card/handle_atom_del(atom/A) - ..() - if(A == inserted_scan_id) - inserted_scan_id = null - updateUsrDialog() - if(A == inserted_modify_id) - inserted_modify_id = null - updateUsrDialog() - -/obj/machinery/computer/card/on_deconstruction() - if(inserted_scan_id) - inserted_scan_id.forceMove(drop_location()) - inserted_scan_id = null - if(inserted_modify_id) - inserted_modify_id.forceMove(drop_location()) - inserted_modify_id = null - -//Check if you can't open a new position for a certain job -/obj/machinery/computer/card/proc/job_blacklisted(jobtitle) - return (jobtitle in blacklisted) - -//Logic check for Topic() if you can open the job -/obj/machinery/computer/card/proc/can_open_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100))) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) - return JOB_ALLOWED - return JOB_COOLDOWN - return JOB_MAX_POSITIONS - return JOB_DENIED - -//Logic check for Topic() if you can close the job -/obj/machinery/computer/card/proc/can_close_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if(job.total_positions > job.current_positions) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) - return JOB_ALLOWED - return JOB_COOLDOWN - return JOB_MAX_POSITIONS - return JOB_DENIED - - -/obj/machinery/computer/card/proc/id_insert(mob/user, obj/item/inserting_item, obj/item/target) - var/obj/item/card/id/card_to_insert = inserting_item - var/holder_item = FALSE - - if(!isidcard(card_to_insert)) - card_to_insert = inserting_item.RemoveID() - holder_item = TRUE - - if(!card_to_insert || !user.transferItemToLoc(card_to_insert, src)) - return FALSE - - if(target) - if(holder_item && inserting_item.InsertID(target)) - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - else - id_eject(user, target) - - user.visible_message("[user] inserts \the [card_to_insert] into \the [src].", - "You insert \the [card_to_insert] into \the [src].") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - updateUsrDialog() - return TRUE - -/obj/machinery/computer/card/proc/id_eject(mob/user, obj/target) - if(!target) - to_chat(user, "That slot is empty!") - return FALSE - else - target.forceMove(drop_location()) - if(!issilicon(user) && Adjacent(user)) - user.put_in_hands(target) - user.visible_message("[user] gets \the [target] from \the [src].", \ - "You get \the [target] from \the [src].") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - updateUsrDialog() - return TRUE - -/obj/machinery/computer/card/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) - return - if(inserted_modify_id) - if(id_eject(user, inserted_modify_id)) - inserted_modify_id = null - updateUsrDialog() - return - if(inserted_scan_id) - if(id_eject(user, inserted_scan_id)) - inserted_scan_id = null - updateUsrDialog() - return - -/obj/machinery/computer/card/ui_interact(mob/user) - . = ..() - var/list/dat = list() - if (mode == 1) // accessing crew manifest - dat += "Crew Manifest:
    Please use security record computer to modify entries.

    " - for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - dat += {"[t.fields["name"]] - [t.fields["rank"]]
    "} - dat += "Print

    Access ID modification console.
    " - - else if(mode == 2) - // JOB MANAGEMENT - dat += {"Return - - "} - for(var/datum/job/job in SSjob.occupations) - dat += "" - if(job.title in blacklisted) - continue - dat += {" - - " - dat += "
    JobSlotsOpen jobClose jobPrioritize
    [job.title][job.current_positions]/[job.total_positions]"} - switch(can_open_job(job)) - if(JOB_ALLOWED) - if(authenticated == 2) - dat += "Open Position
    " - else - dat += "Open Position" - if(JOB_COOLDOWN) - var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) - var/mins = round(time_to_wait / 60) - var/seconds = time_to_wait - (60*mins) - dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" - else - dat += "Denied" - dat += "
    " - switch(can_close_job(job)) - if(JOB_ALLOWED) - if(authenticated == 2) - dat += "Close Position" - else - dat += "Close Position" - if(JOB_COOLDOWN) - var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) - var/mins = round(time_to_wait / 60) - var/seconds = time_to_wait - (60*mins) - dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" - else - dat += "Denied" - dat += "" - switch(job.total_positions) - if(0) - dat += "Denied" - else - if(authenticated == 2) - if(job in SSjob.prioritized_jobs) - dat += "Deprioritize" - else - if(SSjob.prioritized_jobs.len < 5) - dat += "Prioritize" - else - dat += "Denied" - else - dat += "Prioritize" - - dat += "
    " - else - var/list/header = list() - - var/scan_name = inserted_scan_id ? html_encode(inserted_scan_id.name) : "--------" - var/target_name = inserted_modify_id ? html_encode(inserted_modify_id.name) : "--------" - var/target_owner = (inserted_modify_id && inserted_modify_id.registered_name) ? html_encode(inserted_modify_id.registered_name) : "--------" - var/target_rank = (inserted_modify_id && inserted_modify_id.assignment) ? html_encode(inserted_modify_id.assignment) : "Unassigned" - var/target_age = (inserted_modify_id && inserted_modify_id.registered_age) ? html_encode(inserted_modify_id.registered_age) : "--------" - - if(!authenticated) - header += {"
    Please insert the cards into the slots
    - Target: [target_name]
    - Confirm Identity: [scan_name]
    "} - else - header += {"

    - Target: Remove [target_name] || - Confirm Identity: Remove [scan_name]
    - Access Crew Manifest
    - [!target_dept ? "Job Management
    " : ""] - Log Out
    "} - - header += "
    " - - var/body - - if (authenticated && inserted_modify_id) - var/list/carddesc = list() - var/list/jobs = list() - if (authenticated == 2) - var/list/jobs_all = list() - for(var/job in (list("Unassigned") + get_jobs() + "Custom")) - jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job - carddesc += {""} - carddesc += {"
    - - - registered name: - registered age: - -
    - Assignment: "} - - jobs += "[target_rank]" //CHECK THIS - - else - carddesc += "registered_name: [target_owner]" - jobs += "Assignment: [target_rank] (Demote)" - - var/list/accesses = list() - if(istype(src, /obj/machinery/computer/card/centcom)) // REE - accesses += "
    Central Command:
    " - for(var/A in get_all_centcom_access()) - if(A in inserted_modify_id.access) - accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " - else - accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " - else - accesses += {"
    Access
    - - "} - for(var/i = 1; i <= 7; i++) - if(authenticated == 1 && !(i in region_access)) - continue - accesses += "" - accesses += "" - for(var/i = 1; i <= 7; i++) - if(authenticated == 1 && !(i in region_access)) - continue - accesses += "" - accesses += "
    [get_region_accesses_name(i)]:
    " - for(var/A in get_region_accesses(i)) - if(A in inserted_modify_id.access) - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - else - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - accesses += "
    " - accesses += "
    " - body = "[carddesc.Join()]
    [jobs.Join()]

    [accesses.Join()]
    " //CHECK THIS - - else if (!authenticated) - body = {"Log In

    - Access Crew Manifest

    "} - if(!target_dept) - body += "Job Management
    " - - dat = list("", header.Join(), body, "
    ") - var/datum/browser/popup = new(user, "id_com", src.name, 900, 620) - popup.set_content(dat.Join()) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/card/Topic(href, href_list) - if(..()) - return - - if(!usr.canUseTopic(src, !issilicon(usr)) || !is_operational()) - usr.unset_machine() - usr << browse(null, "window=id_com") - return - - usr.set_machine(src) - switch(href_list["choice"]) - if ("inserted_modify_id") - if(inserted_modify_id && !usr.get_active_held_item()) - if(id_eject(usr, inserted_modify_id)) - inserted_modify_id = null - updateUsrDialog() - return - if(usr.get_id_in_hand()) - var/obj/item/held_item = usr.get_active_held_item() - var/obj/item/card/id/id_to_insert = held_item.GetID() - if(id_insert(usr, held_item, inserted_modify_id)) - inserted_modify_id = id_to_insert - updateUsrDialog() - if ("inserted_scan_id") - if(inserted_scan_id && !usr.get_active_held_item()) - if(id_eject(usr, inserted_scan_id)) - inserted_scan_id = null - updateUsrDialog() - return - if(usr.get_id_in_hand()) - var/obj/item/held_item = usr.get_active_held_item() - var/obj/item/card/id/id_to_insert = held_item.GetID() - if(id_insert(usr, held_item, inserted_scan_id)) - inserted_scan_id = id_to_insert - updateUsrDialog() - if ("auth") - if ((!( authenticated ) && (inserted_scan_id || issilicon(usr)) || mode)) - if (check_access(inserted_scan_id)) - region_access = list() - head_subordinates = list() - if(ACCESS_CHANGE_IDS in inserted_scan_id.access) - if(target_dept) - head_subordinates = get_all_jobs() - region_access |= target_dept - authenticated = 1 - else - authenticated = 2 - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - - else - if((ACCESS_HOP in inserted_scan_id.access) && ((target_dept==1) || !target_dept)) - region_access |= 1 - region_access |= 6 - get_subordinates("Head of Personnel") - if((ACCESS_HOS in inserted_scan_id.access) && ((target_dept==2) || !target_dept)) - region_access |= 2 - get_subordinates("Head of Security") - if((ACCESS_CMO in inserted_scan_id.access) && ((target_dept==3) || !target_dept)) - region_access |= 3 - get_subordinates("Chief Medical Officer") - if((ACCESS_RD in inserted_scan_id.access) && ((target_dept==4) || !target_dept)) - region_access |= 4 - get_subordinates("Research Director") - if((ACCESS_CE in inserted_scan_id.access) && ((target_dept==5) || !target_dept)) - region_access |= 5 - get_subordinates("Chief Engineer") - if(region_access) - authenticated = 1 - else if ((!( authenticated ) && issilicon(usr)) && (!inserted_modify_id)) - to_chat(usr, "You can't modify an ID without an ID inserted to modify! Once one is in the modify slot on the computer, you can log in.") - if ("logout") - region_access = null - head_subordinates = null - authenticated = 0 - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - - if("access") - if(href_list["allowed"]) - if(authenticated) - var/access_type = text2num(href_list["access_target"]) - var/access_allowed = text2num(href_list["allowed"]) - if(access_type in (istype(src, /obj/machinery/computer/card/centcom)?get_all_centcom_access() : get_all_accesses())) - inserted_modify_id.access -= access_type - if(access_allowed == 1) - inserted_modify_id.access += access_type - playsound(src, "terminal_type", 50, FALSE) - if ("assign") - if (authenticated == 2) - var/t1 = href_list["assign_target"] - if(t1 == "Custom") - var/newJob = reject_bad_text(input("Enter a custom job assignment.", "Assignment", inserted_modify_id ? inserted_modify_id.assignment : "Unassigned"), MAX_NAME_LEN) - if(newJob) - t1 = newJob - - else if(t1 == "Unassigned") - inserted_modify_id.access -= get_all_accesses() - - else - var/datum/job/jobdatum - for(var/jobtype in typesof(/datum/job)) - var/datum/job/J = new jobtype - if(ckey(J.title) == ckey(t1)) - jobdatum = J - updateUsrDialog() - break - if(!jobdatum) - to_chat(usr, "No log exists for this job.") - updateUsrDialog() - return - if(inserted_modify_id.registered_account) - inserted_modify_id.registered_account.account_job = jobdatum // this is a terrible idea and people will grief but sure whatever - - inserted_modify_id.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) - if (inserted_modify_id) - inserted_modify_id.assignment = t1 - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if ("demote") - if(inserted_modify_id.assignment in head_subordinates || inserted_modify_id.assignment == "Assistant") - inserted_modify_id.assignment = "Unassigned" - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else - to_chat(usr, "You are not authorized to demote this position.") - if ("reg") - if (authenticated) - var/t2 = inserted_modify_id - if ((authenticated && inserted_modify_id == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) - var/newAge = text2num(href_list["setage"])|null - if(newAge && isnum(newAge)) - inserted_modify_id.registered_age = newAge - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else if(!isnull(newAge)) - to_chat(usr, "Invalid age entered- age not updated.") - updateUsrDialog() - - var/newName = reject_bad_name(href_list["reg"]) - if(newName) - inserted_modify_id.registered_name = newName - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else - to_chat(usr, "Invalid name entered.") - updateUsrDialog() - return - if ("mode") - mode = text2num(href_list["mode_target"]) - - if("return") - //DISPLAY MAIN MENU - mode = 3; - playsound(src, "terminal_type", 25, FALSE) - - if("make_job_available") - // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS - if(authenticated && !target_dept) - var/edit_job_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(edit_job_target) - if(!j) - updateUsrDialog() - return 0 - if(can_open_job(j) != 1) - updateUsrDialog() - return 0 - if(opened_positions[edit_job_target] >= 0) - GLOB.time_last_changed_position = world.time / 10 - j.total_positions++ - opened_positions[edit_job_target]++ - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - - if("make_job_unavailable") - // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS - if(authenticated && !target_dept) - var/edit_job_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(edit_job_target) - if(!j) - updateUsrDialog() - return 0 - if(can_close_job(j) != 1) - updateUsrDialog() - return 0 - //Allow instant closing without cooldown if a position has been opened before - if(opened_positions[edit_job_target] <= 0) - GLOB.time_last_changed_position = world.time / 10 - j.total_positions-- - opened_positions[edit_job_target]-- - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - - if ("prioritize_job") - // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY - if(authenticated && !target_dept) - var/priority_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(priority_target) - if(!j) - updateUsrDialog() - return 0 - var/priority = TRUE - if(j in SSjob.prioritized_jobs) - SSjob.prioritized_jobs -= j - priority = FALSE - else if(j.total_positions <= j.current_positions) - to_chat(usr, "[j.title] has had all positions filled. Open up more slots before prioritizing it.") - updateUsrDialog() - return - else - SSjob.prioritized_jobs += j - to_chat(usr, "[j.title] has been successfully [priority ? "prioritized" : "unprioritized"]. Potential employees will notice your request.") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - - if ("print") - if (!( printing )) - printing = 1 - sleep(50) - var/obj/item/paper/P = new /obj/item/paper( loc ) - var/t1 = "Crew Manifest:
    " - for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - t1 += t.fields["name"] + " - " + t.fields["rank"] + "
    " - P.info = t1 - P.name = "paper- 'Crew Manifest'" - printing = null - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - if (inserted_modify_id) - inserted_modify_id.update_label() - updateUsrDialog() - -/obj/machinery/computer/card/proc/get_subordinates(rank) - for(var/datum/job/job in SSjob.occupations) - if(rank in job.department_head) - head_subordinates += job.title - -/obj/machinery/computer/card/centcom - name = "\improper CentCom identification console" - circuit = /obj/item/circuitboard/computer/card/centcom - req_access = list(ACCESS_CENT_CAPTAIN) - -/obj/machinery/computer/card/minor - name = "department management console" - desc = "You can use this to change ID's for specific departments." - icon_screen = "idminor" - circuit = /obj/item/circuitboard/computer/card/minor - -/obj/machinery/computer/card/minor/Initialize() - . = ..() - var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit - if(target_dept) - typed_circuit.target_dept = target_dept - else - target_dept = typed_circuit.target_dept - var/list/dept_list = list("general","security","medical","science","engineering") - name = "[dept_list[target_dept]] department console" - -/obj/machinery/computer/card/minor/hos - target_dept = 2 - icon_screen = "idhos" - - light_color = LIGHT_COLOR_RED - -/obj/machinery/computer/card/minor/cmo - target_dept = 3 - icon_screen = "idcmo" - -/obj/machinery/computer/card/minor/rd - target_dept = 4 - icon_screen = "idrd" - - light_color = LIGHT_COLOR_PINK - -/obj/machinery/computer/card/minor/ce - target_dept = 5 - icon_screen = "idce" - - light_color = LIGHT_COLOR_YELLOW - -#undef JOB_ALLOWED -#undef JOB_COOLDOWN -#undef JOB_MAX_POSITIONS -#undef JOB_DENIED + + +//Keeps track of the time for the ID console. Having it as a global variable prevents people from dismantling/reassembling it to +//increase the slots of many jobs. +GLOBAL_VAR_INIT(time_last_changed_position, 0) + +#define JOB_ALLOWED 1 +#define JOB_COOLDOWN -2 +#define JOB_MAX_POSITIONS -1 // Trying to reduce the number of slots below that of current holders of that job, or trying to open more slots than allowed +#define JOB_DENIED 0 + +/obj/machinery/computer/card + name = "identification console" + desc = "You can use this to manage jobs and ID access." + icon_screen = "id" + icon_keyboard = "id_key" + req_one_access = list(ACCESS_HEADS, ACCESS_CHANGE_IDS) + circuit = /obj/item/circuitboard/computer/card + var/mode = 0 + var/printing = null + var/target_dept = 0 //Which department this computer has access to. 0=all departments + + //Cooldown for closing positions in seconds + //if set to -1: No cooldown... probably a bad idea + //if set to 0: Not able to close "original" positions. You can only close positions that you have opened before + var/change_position_cooldown = 30 + //Jobs you cannot open new positions for + var/list/blacklisted = list( + "AI", + "Assistant", + "Cyborg", + "Captain", + "Head of Personnel", + "Head of Security", + "Chief Engineer", + "Research Director", + "Chief Medical Officer", + "Prisoner") + + //The scaling factor of max total positions in relation to the total amount of people on board the station in % + var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players + + //This is used to keep track of opened positions for jobs to allow instant closing + //Assoc array: "JobName" = (int) + var/list/opened_positions = list() + var/obj/item/card/id/inserted_scan_id + var/obj/item/card/id/inserted_modify_id + var/list/region_access = null + var/list/head_subordinates = null + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/card/proc/get_jobs() + return get_all_jobs() + +/obj/machinery/computer/card/centcom/get_jobs() + return get_all_centcom_jobs() + +/obj/machinery/computer/card/Initialize() + . = ..() + change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) + +/obj/machinery/computer/card/examine(mob/user) + . = ..() + if(inserted_scan_id || inserted_modify_id) + . += "Alt-click to eject the ID card." + +/obj/machinery/computer/card/attackby(obj/I, mob/user, params) + if(isidcard(I)) + if(check_access(I) && !inserted_scan_id) + if(id_insert(user, I, inserted_scan_id)) + inserted_scan_id = I + updateUsrDialog() + else if(id_insert(user, I, inserted_modify_id)) + inserted_modify_id = I + updateUsrDialog() + else + return ..() + +/obj/machinery/computer/card/Destroy() + if(inserted_scan_id) + qdel(inserted_scan_id) + inserted_scan_id = null + if(inserted_modify_id) + qdel(inserted_modify_id) + inserted_modify_id = null + return ..() + +/obj/machinery/computer/card/handle_atom_del(atom/A) + ..() + if(A == inserted_scan_id) + inserted_scan_id = null + updateUsrDialog() + if(A == inserted_modify_id) + inserted_modify_id = null + updateUsrDialog() + +/obj/machinery/computer/card/on_deconstruction() + if(inserted_scan_id) + inserted_scan_id.forceMove(drop_location()) + inserted_scan_id = null + if(inserted_modify_id) + inserted_modify_id.forceMove(drop_location()) + inserted_modify_id = null + +//Check if you can't open a new position for a certain job +/obj/machinery/computer/card/proc/job_blacklisted(jobtitle) + return (jobtitle in blacklisted) + +//Logic check for Topic() if you can open the job +/obj/machinery/computer/card/proc/can_open_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100))) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) + return JOB_ALLOWED + return JOB_COOLDOWN + return JOB_MAX_POSITIONS + return JOB_DENIED + +//Logic check for Topic() if you can close the job +/obj/machinery/computer/card/proc/can_close_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if(job.total_positions > job.current_positions) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) + return JOB_ALLOWED + return JOB_COOLDOWN + return JOB_MAX_POSITIONS + return JOB_DENIED + + +/obj/machinery/computer/card/proc/id_insert(mob/user, obj/item/inserting_item, obj/item/target) + var/obj/item/card/id/card_to_insert = inserting_item + var/holder_item = FALSE + + if(!isidcard(card_to_insert)) + card_to_insert = inserting_item.RemoveID() + holder_item = TRUE + + if(!card_to_insert || !user.transferItemToLoc(card_to_insert, src)) + return FALSE + + if(target) + if(holder_item && inserting_item.InsertID(target)) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + else + id_eject(user, target) + + user.visible_message("[user] inserts \the [card_to_insert] into \the [src].", + "You insert \the [card_to_insert] into \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + updateUsrDialog() + return TRUE + +/obj/machinery/computer/card/proc/id_eject(mob/user, obj/target) + if(!target) + to_chat(user, "That slot is empty!") + return FALSE + else + target.forceMove(drop_location()) + if(!issilicon(user) && Adjacent(user)) + user.put_in_hands(target) + user.visible_message("[user] gets \the [target] from \the [src].", \ + "You get \the [target] from \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + updateUsrDialog() + return TRUE + +/obj/machinery/computer/card/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) + return + if(inserted_modify_id) + if(id_eject(user, inserted_modify_id)) + inserted_modify_id = null + updateUsrDialog() + return + if(inserted_scan_id) + if(id_eject(user, inserted_scan_id)) + inserted_scan_id = null + updateUsrDialog() + return + +/obj/machinery/computer/card/ui_interact(mob/user) + . = ..() + var/list/dat = list() + if (mode == 1) // accessing crew manifest + dat += "Crew Manifest:
    Please use security record computer to modify entries.

    " + for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + dat += {"[t.fields["name"]] - [t.fields["rank"]]
    "} + dat += "Print

    Access ID modification console.
    " + + else if(mode == 2) + // JOB MANAGEMENT + dat += {"Return + + "} + for(var/datum/job/job in SSjob.occupations) + dat += "" + if(job.title in blacklisted) + continue + dat += {" + + " + dat += "
    JobSlotsOpen jobClose jobPrioritize
    [job.title][job.current_positions]/[job.total_positions]"} + switch(can_open_job(job)) + if(JOB_ALLOWED) + if(authenticated == 2) + dat += "Open Position
    " + else + dat += "Open Position" + if(JOB_COOLDOWN) + var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) + var/mins = round(time_to_wait / 60) + var/seconds = time_to_wait - (60*mins) + dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" + else + dat += "Denied" + dat += "
    " + switch(can_close_job(job)) + if(JOB_ALLOWED) + if(authenticated == 2) + dat += "Close Position" + else + dat += "Close Position" + if(JOB_COOLDOWN) + var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) + var/mins = round(time_to_wait / 60) + var/seconds = time_to_wait - (60*mins) + dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" + else + dat += "Denied" + dat += "" + switch(job.total_positions) + if(0) + dat += "Denied" + else + if(authenticated == 2) + if(job in SSjob.prioritized_jobs) + dat += "Deprioritize" + else + if(SSjob.prioritized_jobs.len < 5) + dat += "Prioritize" + else + dat += "Denied" + else + dat += "Prioritize" + + dat += "
    " + else + var/list/header = list() + + var/scan_name = inserted_scan_id ? html_encode(inserted_scan_id.name) : "--------" + var/target_name = inserted_modify_id ? html_encode(inserted_modify_id.name) : "--------" + var/target_owner = (inserted_modify_id && inserted_modify_id.registered_name) ? html_encode(inserted_modify_id.registered_name) : "--------" + var/target_rank = (inserted_modify_id && inserted_modify_id.assignment) ? html_encode(inserted_modify_id.assignment) : "Unassigned" + var/target_age = (inserted_modify_id && inserted_modify_id.registered_age) ? html_encode(inserted_modify_id.registered_age) : "--------" + + if(!authenticated) + header += {"
    Please insert the cards into the slots
    + Target: [target_name]
    + Confirm Identity: [scan_name]
    "} + else + header += {"

    + Target: Remove [target_name] || + Confirm Identity: Remove [scan_name]
    + Access Crew Manifest
    + [!target_dept ? "Job Management
    " : ""] + Log Out
    "} + + header += "
    " + + var/body + + if (authenticated && inserted_modify_id) + var/list/carddesc = list() + var/list/jobs = list() + if (authenticated == 2) + var/list/jobs_all = list() + for(var/job in (list("Unassigned") + get_jobs() + "Custom")) + jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job + carddesc += {""} + carddesc += {"
    + + + registered name: + registered age: + +
    + Assignment: "} + + jobs += "[target_rank]" //CHECK THIS + + else + carddesc += "registered_name: [target_owner]" + jobs += "Assignment: [target_rank] (Demote)" + + var/list/accesses = list() + if(istype(src, /obj/machinery/computer/card/centcom)) // REE + accesses += "
    Central Command:
    " + for(var/A in get_all_centcom_access()) + if(A in inserted_modify_id.access) + accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " + else + accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " + else + accesses += {"
    Access
    + + "} + for(var/i = 1; i <= 7; i++) + if(authenticated == 1 && !(i in region_access)) + continue + accesses += "" + accesses += "" + for(var/i = 1; i <= 7; i++) + if(authenticated == 1 && !(i in region_access)) + continue + accesses += "" + accesses += "
    [get_region_accesses_name(i)]:
    " + for(var/A in get_region_accesses(i)) + if(A in inserted_modify_id.access) + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + else + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + accesses += "
    " + accesses += "
    " + body = "[carddesc.Join()]
    [jobs.Join()]

    [accesses.Join()]
    " //CHECK THIS + + else if (!authenticated) + body = {"Log In

    + Access Crew Manifest

    "} + if(!target_dept) + body += "Job Management
    " + + dat = list("", header.Join(), body, "
    ") + var/datum/browser/popup = new(user, "id_com", src.name, 900, 620) + popup.set_content(dat.Join()) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/card/Topic(href, href_list) + if(..()) + return + + if(!usr.canUseTopic(src, !issilicon(usr)) || !is_operational()) + usr.unset_machine() + usr << browse(null, "window=id_com") + return + + usr.set_machine(src) + switch(href_list["choice"]) + if ("inserted_modify_id") + if(inserted_modify_id && !usr.get_active_held_item()) + if(id_eject(usr, inserted_modify_id)) + inserted_modify_id = null + updateUsrDialog() + return + if(usr.get_id_in_hand()) + var/obj/item/held_item = usr.get_active_held_item() + var/obj/item/card/id/id_to_insert = held_item.GetID() + if(id_insert(usr, held_item, inserted_modify_id)) + inserted_modify_id = id_to_insert + updateUsrDialog() + if ("inserted_scan_id") + if(inserted_scan_id && !usr.get_active_held_item()) + if(id_eject(usr, inserted_scan_id)) + inserted_scan_id = null + updateUsrDialog() + return + if(usr.get_id_in_hand()) + var/obj/item/held_item = usr.get_active_held_item() + var/obj/item/card/id/id_to_insert = held_item.GetID() + if(id_insert(usr, held_item, inserted_scan_id)) + inserted_scan_id = id_to_insert + updateUsrDialog() + if ("auth") + if ((!( authenticated ) && (inserted_scan_id || issilicon(usr)) || mode)) + if (check_access(inserted_scan_id)) + region_access = list() + head_subordinates = list() + if(ACCESS_CHANGE_IDS in inserted_scan_id.access) + if(target_dept) + head_subordinates = get_all_jobs() + region_access |= target_dept + authenticated = 1 + else + authenticated = 2 + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + + else + if((ACCESS_HOP in inserted_scan_id.access) && ((target_dept==1) || !target_dept)) + region_access |= 1 + region_access |= 6 + get_subordinates("Head of Personnel") + if((ACCESS_HOS in inserted_scan_id.access) && ((target_dept==2) || !target_dept)) + region_access |= 2 + get_subordinates("Head of Security") + if((ACCESS_CMO in inserted_scan_id.access) && ((target_dept==3) || !target_dept)) + region_access |= 3 + get_subordinates("Chief Medical Officer") + if((ACCESS_RD in inserted_scan_id.access) && ((target_dept==4) || !target_dept)) + region_access |= 4 + get_subordinates("Research Director") + if((ACCESS_CE in inserted_scan_id.access) && ((target_dept==5) || !target_dept)) + region_access |= 5 + get_subordinates("Chief Engineer") + if(region_access) + authenticated = 1 + else if ((!( authenticated ) && issilicon(usr)) && (!inserted_modify_id)) + to_chat(usr, "You can't modify an ID without an ID inserted to modify! Once one is in the modify slot on the computer, you can log in.") + if ("logout") + region_access = null + head_subordinates = null + authenticated = 0 + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + + if("access") + if(href_list["allowed"]) + if(authenticated) + var/access_type = text2num(href_list["access_target"]) + var/access_allowed = text2num(href_list["allowed"]) + if(access_type in (istype(src, /obj/machinery/computer/card/centcom)?get_all_centcom_access() : get_all_accesses())) + inserted_modify_id.access -= access_type + if(access_allowed == 1) + inserted_modify_id.access += access_type + playsound(src, "terminal_type", 50, FALSE) + if ("assign") + if (authenticated == 2) + var/t1 = href_list["assign_target"] + if(t1 == "Custom") + var/newJob = reject_bad_text(input("Enter a custom job assignment.", "Assignment", inserted_modify_id ? inserted_modify_id.assignment : "Unassigned"), MAX_NAME_LEN) + if(newJob) + t1 = newJob + + else if(t1 == "Unassigned") + inserted_modify_id.access -= get_all_accesses() + + else + var/datum/job/jobdatum + for(var/jobtype in typesof(/datum/job)) + var/datum/job/J = new jobtype + if(ckey(J.title) == ckey(t1)) + jobdatum = J + updateUsrDialog() + break + if(!jobdatum) + to_chat(usr, "No log exists for this job.") + updateUsrDialog() + return + if(inserted_modify_id.registered_account) + inserted_modify_id.registered_account.account_job = jobdatum // this is a terrible idea and people will grief but sure whatever + + inserted_modify_id.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) + if (inserted_modify_id) + inserted_modify_id.assignment = t1 + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + if ("demote") + if(inserted_modify_id.assignment in head_subordinates || inserted_modify_id.assignment == "Assistant") + inserted_modify_id.assignment = "Unassigned" + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else + to_chat(usr, "You are not authorized to demote this position.") + if ("reg") + if (authenticated) + var/t2 = inserted_modify_id + if ((authenticated && inserted_modify_id == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) + var/newAge = text2num(href_list["setage"])|null + if(newAge && isnum(newAge)) + inserted_modify_id.registered_age = newAge + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else if(!isnull(newAge)) + to_chat(usr, "Invalid age entered- age not updated.") + updateUsrDialog() + + var/newName = reject_bad_name(href_list["reg"]) + if(newName) + inserted_modify_id.registered_name = newName + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else + to_chat(usr, "Invalid name entered.") + updateUsrDialog() + return + if ("mode") + mode = text2num(href_list["mode_target"]) + + if("return") + //DISPLAY MAIN MENU + mode = 3; + playsound(src, "terminal_type", 25, FALSE) + + if("make_job_available") + // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS + if(authenticated && !target_dept) + var/edit_job_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + updateUsrDialog() + return 0 + if(can_open_job(j) != 1) + updateUsrDialog() + return 0 + if(opened_positions[edit_job_target] >= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions++ + opened_positions[edit_job_target]++ + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + if("make_job_unavailable") + // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS + if(authenticated && !target_dept) + var/edit_job_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + updateUsrDialog() + return 0 + if(can_close_job(j) != 1) + updateUsrDialog() + return 0 + //Allow instant closing without cooldown if a position has been opened before + if(opened_positions[edit_job_target] <= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions-- + opened_positions[edit_job_target]-- + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + + if ("prioritize_job") + // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY + if(authenticated && !target_dept) + var/priority_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(priority_target) + if(!j) + updateUsrDialog() + return 0 + var/priority = TRUE + if(j in SSjob.prioritized_jobs) + SSjob.prioritized_jobs -= j + priority = FALSE + else if(j.total_positions <= j.current_positions) + to_chat(usr, "[j.title] has had all positions filled. Open up more slots before prioritizing it.") + updateUsrDialog() + return + else + SSjob.prioritized_jobs += j + to_chat(usr, "[j.title] has been successfully [priority ? "prioritized" : "unprioritized"]. Potential employees will notice your request.") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + if ("print") + if (!( printing )) + printing = 1 + sleep(50) + var/obj/item/paper/P = new /obj/item/paper( loc ) + var/t1 = "Crew Manifest:
    " + for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + t1 += t.fields["name"] + " - " + t.fields["rank"] + "
    " + P.info = t1 + P.name = "paper- 'Crew Manifest'" + printing = null + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + if (inserted_modify_id) + inserted_modify_id.update_label() + updateUsrDialog() + +/obj/machinery/computer/card/proc/get_subordinates(rank) + for(var/datum/job/job in SSjob.occupations) + if(rank in job.department_head) + head_subordinates += job.title + +/obj/machinery/computer/card/centcom + name = "\improper CentCom identification console" + circuit = /obj/item/circuitboard/computer/card/centcom + req_access = list(ACCESS_CENT_CAPTAIN) + +/obj/machinery/computer/card/minor + name = "department management console" + desc = "You can use this to change ID's for specific departments." + icon_screen = "idminor" + circuit = /obj/item/circuitboard/computer/card/minor + +/obj/machinery/computer/card/minor/Initialize() + . = ..() + var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit + if(target_dept) + typed_circuit.target_dept = target_dept + else + target_dept = typed_circuit.target_dept + var/list/dept_list = list("general","security","medical","science","engineering") + name = "[dept_list[target_dept]] department console" + +/obj/machinery/computer/card/minor/hos + target_dept = 2 + icon_screen = "idhos" + + light_color = LIGHT_COLOR_RED + +/obj/machinery/computer/card/minor/cmo + target_dept = 3 + icon_screen = "idcmo" + +/obj/machinery/computer/card/minor/rd + target_dept = 4 + icon_screen = "idrd" + + light_color = LIGHT_COLOR_PINK + +/obj/machinery/computer/card/minor/ce + target_dept = 5 + icon_screen = "idce" + + light_color = LIGHT_COLOR_YELLOW + +#undef JOB_ALLOWED +#undef JOB_COOLDOWN +#undef JOB_MAX_POSITIONS +#undef JOB_DENIED diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 1323a2dd0c6..1dc29338448 100755 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -1,779 +1,779 @@ -#define STATE_DEFAULT 1 -#define STATE_CALLSHUTTLE 2 -#define STATE_CANCELSHUTTLE 3 -#define STATE_MESSAGELIST 4 -#define STATE_VIEWMESSAGE 5 -#define STATE_DELMESSAGE 6 -#define STATE_STATUSDISPLAY 7 -#define STATE_ALERT_LEVEL 8 -#define STATE_CONFIRM_LEVEL 9 -#define STATE_TOGGLE_EMERGENCY 10 -#define STATE_PURCHASE 11 - -// The communications computer -/obj/machinery/computer/communications - name = "communications console" - desc = "A console used for high-priority announcements and emergencies." - icon_screen = "comm" - icon_keyboard = "tech_key" - req_access = list(ACCESS_HEADS) - circuit = /obj/item/circuitboard/computer/communications - var/auth_id = "Unknown" //Who is currently logged in? - var/list/datum/comm_message/messages = list() - var/datum/comm_message/currmsg - var/datum/comm_message/aicurrmsg - var/state = STATE_DEFAULT - var/aistate = STATE_DEFAULT - var/message_cooldown = 0 - var/ai_message_cooldown = 0 - var/tmp_alertlevel = 0 - - var/stat_msg1 - var/stat_msg2 - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/communications/proc/checkCCcooldown() - var/obj/item/circuitboard/computer/communications/CM = circuit - if(CM.lastTimeUsed + 600 > world.time) - return FALSE - return TRUE - -/obj/machinery/computer/communications/Initialize() - . = ..() - GLOB.shuttle_caller_list += src - -/obj/machinery/computer/communications/Topic(href, href_list) - if(..()) - return - if(!usr.canUseTopic(src, !issilicon(usr))) - return - if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13 - to_chat(usr, "Unable to establish a connection: \black You're too far away from the station!") - return - usr.set_machine(src) - - - if(!href_list["operation"]) - return - var/obj/item/circuitboard/computer/communications/CM = circuit - switch(href_list["operation"]) - // main interface - if("main") - state = STATE_DEFAULT - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - if("login") - var/mob/M = usr - - var/obj/item/card/id/I = M.get_idcard(TRUE) - - if(I && istype(I)) - if(check_access(I)) - authenticated = 1 - auth_id = "[I.registered_name] ([I.assignment])" - if((20 in I.access)) - authenticated = 2 - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - if(obj_flags & EMAGGED) - authenticated = 2 - auth_id = "Unknown" - to_chat(M, "[src] lets out a quiet alarm as its login is overridden.") - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - playsound(src, 'sound/machines/terminal_alert.ogg', 25, FALSE) - if(prob(25)) - for(var/mob/living/silicon/ai/AI in active_ais()) - SEND_SOUND(AI, sound('sound/machines/terminal_alert.ogg', volume = 10)) //Very quiet for balance reasons - if("logout") - authenticated = 0 - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - - if("swipeidseclevel") - var/mob/M = usr - var/obj/item/card/id/I = M.get_active_held_item() - if (istype(I, /obj/item/pda)) - var/obj/item/pda/pda = I - I = pda.id - if (I && istype(I)) - if(ACCESS_CAPTAIN in I.access) - var/old_level = GLOB.security_level - if(!tmp_alertlevel) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel < SEC_LEVEL_GREEN) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel > SEC_LEVEL_BLUE) - tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this - set_security_level(tmp_alertlevel) - if(GLOB.security_level != old_level) - to_chat(usr, "Authorization confirmed. Modifying security level.") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - //Only notify people if an actual change happened - var/security_level = get_security_level() - log_game("[key_name(usr)] has changed the security level to [security_level] with [src] at [AREACOORD(usr)].") - message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] with [src] at [AREACOORD(usr)].") - deadchat_broadcast(" has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - tmp_alertlevel = 0 - else - to_chat(usr, "You are not authorized to do this!") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - tmp_alertlevel = 0 - state = STATE_DEFAULT - else - to_chat(usr, "You need to swipe your ID!") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - - if("announce") - if(authenticated==2) - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - make_announcement(usr) - - if("crossserver") - if(authenticated==2) - var/dest = href_list["cross_dest"] - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - return - var/warning = dest == "all" ? "Please choose a message to transmit to allied stations." : "Please choose a message to transmit to [dest] sector station." - var/input = stripped_multiline_input(usr, "[warning] Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if(dest == "all") - send2otherserver("[station_name()]", input,"Comms_Console") - else - send2otherserver("[station_name()]", input,"Comms_Console", list(dest)) - minor_announce(input, title = "Outgoing message to allied station") - usr.log_talk(input, LOG_SAY, tag="message to the other server") - message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server.") - deadchat_broadcast(" has sent an outgoing message to the other station(s).", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - CM.lastTimeUsed = world.time - - if("purchase_menu") - state = STATE_PURCHASE - - if("buyshuttle") - if(authenticated==2) - var/list/shuttles = flatten_list(SSmapping.shuttle_templates) - var/datum/map_template/shuttle/S = locate(href_list["chosen_shuttle"]) in shuttles - if(S && istype(S)) - if(SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE) - to_chat(usr, "It's a bit late to buy a new shuttle, don't you think?") - return - if(SSshuttle.shuttle_purchased) - to_chat(usr, "A replacement shuttle has already been purchased.") - else if(!S.prerequisites_met()) - to_chat(usr, "You have not met the requirements for purchasing this shuttle.") - else - var/points_to_check - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - points_to_check = D.account_balance - if(points_to_check >= S.credit_cost) - SSshuttle.shuttle_purchased = TRUE - SSshuttle.unload_preview() - SSshuttle.load_template(S) - SSshuttle.existing_shuttle = SSshuttle.emergency - SSshuttle.action_load(S) - D.adjust_money(-S.credit_cost) - minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits.[S.extra_desc ? " [S.extra_desc]" : ""]" , "Shuttle Purchase") - message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].") - log_shuttle("[key_name(usr)] has purchased [S.name].") - SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]") - else - to_chat(usr, "Insufficient credits.") - - if("callshuttle") - state = STATE_DEFAULT - if(authenticated && SSshuttle.canEvac(usr)) - state = STATE_CALLSHUTTLE - if("callshuttle2") - if(authenticated) - SSshuttle.requestEvac(usr, href_list["call"]) - if(SSshuttle.emergency.timer) - post_status("shuttle") - state = STATE_DEFAULT - if("cancelshuttle") - state = STATE_DEFAULT - if(authenticated) - state = STATE_CANCELSHUTTLE - if("cancelshuttle2") - if(authenticated) - SSshuttle.cancelEvac(usr) - state = STATE_DEFAULT - if("messagelist") - currmsg = 0 - state = STATE_MESSAGELIST - if("viewmessage") - state = STATE_VIEWMESSAGE - if (!currmsg) - if(href_list["message-num"]) - var/msgnum = text2num(href_list["message-num"]) - currmsg = messages[msgnum] - else - state = STATE_MESSAGELIST - if("delmessage") - state = currmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST - if("delmessage2") - if(authenticated) - if(currmsg) - if(aicurrmsg == currmsg) - aicurrmsg = null - messages -= currmsg - currmsg = null - state = STATE_MESSAGELIST - else - state = STATE_VIEWMESSAGE - if("respond") - var/answer = text2num(href_list["answer"]) - if(!currmsg || !answer || currmsg.possible_answers.len < answer) - state = STATE_MESSAGELIST - else - if(!currmsg.answered) - currmsg.answered = answer - log_game("[key_name(usr)] answered [currmsg.title] comm message. Answer : [currmsg.answered]") - if(currmsg) - currmsg.answer_callback.InvokeAsync() - state = STATE_VIEWMESSAGE - updateDialog() - if("status") - state = STATE_STATUSDISPLAY - if("securitylevel") - tmp_alertlevel = text2num( href_list["newalertlevel"] ) - if(!tmp_alertlevel) - tmp_alertlevel = 0 - state = STATE_CONFIRM_LEVEL - if("changeseclevel") - state = STATE_ALERT_LEVEL - - if("emergencyaccess") - state = STATE_TOGGLE_EMERGENCY - if("enableemergency") - make_maint_all_access() - log_game("[key_name(usr)] enabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") - deadchat_broadcast(" enabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - state = STATE_DEFAULT - if("disableemergency") - revoke_maint_all_access() - log_game("[key_name(usr)] disabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") - deadchat_broadcast(" disabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - state = STATE_DEFAULT - - // Status display stuff - if("setstat") - playsound(src, "terminal_type", 50, FALSE) - switch(href_list["statdisp"]) - if("message") - post_status("message", stat_msg1, stat_msg2) - if("alert") - post_status("alert", href_list["alert"]) - else - post_status(href_list["statdisp"]) - - if("setmsg1") - stat_msg1 = reject_bad_text(input("Line 1", "Enter Message Text", stat_msg1) as text|null, 40) - updateDialog() - if("setmsg2") - stat_msg2 = reject_bad_text(input("Line 2", "Enter Message Text", stat_msg2) as text|null, 40) - updateDialog() - - // OMG CENTCOM LETTERHEAD - if("MessageCentCom") - if(authenticated) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - return - var/input = stripped_input(usr, "Please choose a message to transmit to CentCom via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to CentCom.", "") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - CentCom_announce(input, usr) - to_chat(usr, "Message transmitted to Central Command.") - usr.log_talk(input, LOG_SAY, tag="CentCom announcement") - deadchat_broadcast(" has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - CM.lastTimeUsed = world.time - - // OMG SYNDICATE ...LETTERHEAD - if("MessageSyndicate") - if((authenticated) && (obj_flags & EMAGGED)) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - return - var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING COORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to /??????/.", "") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - Syndicate_announce(input, usr) - to_chat(usr, "SYSERR @l(19833)of(transmit.dm): !@$ MESSAGE TRANSMITTED TO SYNDICATE COMMAND.") - usr.log_talk(input, LOG_SAY, tag="Syndicate announcement") - deadchat_broadcast(" has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - CM.lastTimeUsed = world.time - - if("RestoreBackup") - to_chat(usr, "Backup routing data restored!") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - obj_flags &= ~EMAGGED - updateDialog() - - if("nukerequest") //When there's no other way - if(authenticated==2) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - return - var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self-Destruct Code Request.","") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - Nuke_request(input, usr) - to_chat(usr, "Request sent.") - usr.log_message("has requested the nuclear codes from CentCom with reason \"[input]\"", LOG_SAY) - priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self-Destruct Codes Requested",'sound/ai/commandreport.ogg') - CM.lastTimeUsed = world.time - - - // AI interface - if("ai-main") - aicurrmsg = null - aistate = STATE_DEFAULT - if("ai-callshuttle") - aistate = STATE_DEFAULT - if(SSshuttle.canEvac(usr)) - aistate = STATE_CALLSHUTTLE - if("ai-callshuttle2") - SSshuttle.requestEvac(usr, href_list["call"]) - aistate = STATE_DEFAULT - if("ai-messagelist") - aicurrmsg = null - aistate = STATE_MESSAGELIST - if("ai-viewmessage") - aistate = STATE_VIEWMESSAGE - if (!aicurrmsg) - if(href_list["message-num"]) - var/msgnum = text2num(href_list["message-num"]) - aicurrmsg = messages[msgnum] - else - aistate = STATE_MESSAGELIST - if("ai-delmessage") - aistate = aicurrmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST - if("ai-delmessage2") - if(aicurrmsg) - if(currmsg == aicurrmsg) - currmsg = null - messages -= aicurrmsg - aicurrmsg = null - aistate = STATE_MESSAGELIST - if("ai-respond") - var/answer = text2num(href_list["answer"]) - if(!aicurrmsg || !answer || aicurrmsg.possible_answers.len < answer) - aistate = STATE_MESSAGELIST - else - if(!aicurrmsg.answered) - aicurrmsg.answered = answer - log_game("[key_name(usr)] answered [aicurrmsg.title] comm message. Answer : [aicurrmsg.answered]") - if(aicurrmsg.answer_callback) - aicurrmsg.answer_callback.InvokeAsync() - aistate = STATE_VIEWMESSAGE - if("ai-status") - aistate = STATE_STATUSDISPLAY - if("ai-announce") - make_announcement(usr, 1) - if("ai-securitylevel") - tmp_alertlevel = text2num( href_list["newalertlevel"] ) - if(!tmp_alertlevel) - tmp_alertlevel = 0 - var/old_level = GLOB.security_level - if(!tmp_alertlevel) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel < SEC_LEVEL_GREEN) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel > SEC_LEVEL_BLUE) - tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this - set_security_level(tmp_alertlevel) - if(GLOB.security_level != old_level) - //Only notify people if an actual change happened - var/security_level = get_security_level() - log_game("[key_name(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") - message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") - deadchat_broadcast(" has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - tmp_alertlevel = 0 - aistate = STATE_DEFAULT - if("ai-changeseclevel") - aistate = STATE_ALERT_LEVEL - if("ai-emergencyaccess") - aistate = STATE_TOGGLE_EMERGENCY - if("ai-enableemergency") - make_maint_all_access() - log_game("[key_name(usr)] enabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") - deadchat_broadcast(" enabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - aistate = STATE_DEFAULT - if("ai-disableemergency") - revoke_maint_all_access() - log_game("[key_name(usr)] disabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") - deadchat_broadcast(" disabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - aistate = STATE_DEFAULT - - updateUsrDialog() - -/obj/machinery/computer/communications/attackby(obj/I, mob/user, params) - if(istype(I, /obj/item/card/id)) - attack_hand(user) - else - return ..() - -/obj/machinery/computer/communications/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - if(authenticated == 1) - authenticated = 2 - to_chat(user, "You scramble the communication routing circuits!") - playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE) - -/obj/machinery/computer/communications/ui_interact(mob/user) - . = ..() - if (z > 6) - to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") - return - - var/dat = "" - if(SSshuttle.emergency.mode == SHUTTLE_CALL) - var/timeleft = SSshuttle.emergency.timeLeft() - dat += "Emergency shuttle\n
    \nETA: [timeleft / 60 % 60]:[add_leading(num2text(timeleft % 60), 2, "0")]" - - - var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - - if(issilicon(user)) - var/dat2 = interact_ai(user) // give the AI a different interact proc to limit its access - if(dat2) - dat += dat2 - popup.set_content(dat) - popup.open() - return - - switch(state) - if(STATE_DEFAULT) - if (authenticated) - if(SSshuttle.emergencyCallAmount) - if(SSshuttle.emergencyLastCallLoc) - dat += "Most recent shuttle call/recall traced to: [format_text(SSshuttle.emergencyLastCallLoc.name)]
    " - else - dat += "Unable to trace most recent shuttle call/recall signal.
    " - dat += "Logged in as: [auth_id]" - dat += "
    " - dat += "
    \[ Log Out \]
    " - dat += "
    General Functions" - dat += "
    \[ Message List \]" - switch(SSshuttle.emergency.mode) - if(SHUTTLE_IDLE, SHUTTLE_RECALL) - dat += "
    \[ Call Emergency Shuttle \]" - else - dat += "
    \[ Cancel Shuttle Call \]" - - dat += "
    \[ Set Status Display \]" - if (authenticated==2) - dat += "

    Captain Functions" - dat += "
    \[ Make a Captain's Announcement \]" - var/list/cross_servers = CONFIG_GET(keyed_list/cross_server) - var/our_id = CONFIG_GET(string/cross_comms_name) - if(cross_servers.len) - for(var/server in cross_servers) - if(server == our_id) - continue - dat += "
    \[ Send a message to station in [server] sector. \]" - if(cross_servers.len > 2) - dat += "
    \[ Send a message to all allied stations \]" - if(SSmapping.config.allow_custom_shuttles) - dat += "
    \[ Purchase Shuttle \]" - dat += "
    \[ Change Alert Level \]" - dat += "
    \[ Emergency Maintenance Access \]" - dat += "
    \[ Request Nuclear Authentication Codes \]" - if(!(obj_flags & EMAGGED)) - dat += "
    \[ Send Message to CentCom \]" - else - dat += "
    \[ Send Message to \[UNKNOWN\] \]" - dat += "
    \[ Restore Backup Routing Data \]" - else - dat += "
    \[ Log In \]" - if(STATE_CALLSHUTTLE) - dat += get_call_shuttle_form() - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - if(STATE_CANCELSHUTTLE) - dat += get_cancel_shuttle_form() - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - if(STATE_MESSAGELIST) - dat += "Messages:" - for(var/i in 1 to messages.len) - var/datum/comm_message/M = messages[i] - dat += "
    [M.title]" - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if(STATE_VIEWMESSAGE) - if (currmsg) - dat += "[currmsg.title]

    [currmsg.content]" - if(!currmsg.answered && currmsg.possible_answers.len) - for(var/i in 1 to currmsg.possible_answers.len) - var/answer = currmsg.possible_answers[i] - dat += "
    \[ Answer : [answer] \]" - else if(currmsg.answered) - var/answered = currmsg.possible_answers[currmsg.answered] - dat += "
    Archived Answer : [answered]" - dat += "

    \[ Delete \]" - else - aistate = STATE_MESSAGELIST - attack_hand(user) - return - if(STATE_DELMESSAGE) - if (currmsg) - dat += "Are you sure you want to delete this message? \[ OK | Cancel \]" - else - state = STATE_MESSAGELIST - attack_hand(user) - return - if(STATE_STATUSDISPLAY) - dat += "Set Status Displays
    " - dat += "\[ Clear \]
    " - dat += "\[ Shuttle ETA \]
    " - dat += "\[ Message \]" - dat += "
    " - dat += "\[ Alert: None |" - dat += " Red Alert |" - dat += " Lockdown |" - dat += " Biohazard \]

    " - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if(STATE_ALERT_LEVEL) - dat += "Current alert level: [get_security_level()]
    " - if(GLOB.security_level == SEC_LEVEL_DELTA) - dat += "The self-destruct mechanism is active. Find a way to deactivate the mechanism to lower the alert level or evacuate." - else - dat += "Blue
    " - dat += "Green" - if(STATE_CONFIRM_LEVEL) - dat += "Current alert level: [get_security_level()]
    " - dat += "Confirm the change to: [num2seclevel(tmp_alertlevel)]
    " - dat += "Swipe ID to confirm change.
    " - if(STATE_TOGGLE_EMERGENCY) - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - if(GLOB.emergency_access == 1) - dat += "Emergency Maintenance Access is currently ENABLED" - dat += "
    Restore maintenance access restrictions?
    \[ OK | Cancel \]" - else - dat += "Emergency Maintenance Access is currently DISABLED" - dat += "
    Lift access restrictions on maintenance and external airlocks?
    \[ OK | Cancel \]" - - if(STATE_PURCHASE) - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - dat += "Budget: [D.account_balance] Credits.
    " - dat += "
    " - dat += "Caution: Purchasing dangerous shuttles may lead to mutiny and/or death.
    " - dat += "
    " - for(var/shuttle_id in SSmapping.shuttle_templates) - var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] - if(S.can_be_bought && S.credit_cost < INFINITY) - dat += "[S.name] | [S.credit_cost] Credits
    " - dat += "[S.description]
    " - if(S.prerequisites) - dat += "Prerequisites: [S.prerequisites]
    " - dat += "(Purchase)

    " - - dat += "

    \[ [(state != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" - - popup.set_content(dat) - popup.open() - -/obj/machinery/computer/communications/proc/get_javascript_header(form_id) - var/dat = {""} - return dat - -/obj/machinery/computer/communications/proc/get_call_shuttle_form(ai_interface = 0) - var/form_id = "callshuttle" - var/dat = get_javascript_header(form_id) - dat += "
    " - dat += "" - dat += "" - dat += "Nature of emergency:
    " - dat += "
    Are you sure you want to call the shuttle? \[ Call \]" - return dat - -/obj/machinery/computer/communications/proc/get_cancel_shuttle_form() - var/form_id = "cancelshuttle" - var/dat = get_javascript_header(form_id) - dat += "" - dat += "" - dat += "" - - dat += "
    Are you sure you want to cancel the shuttle? \[ Cancel \]" - return dat - -/obj/machinery/computer/communications/proc/interact_ai(mob/living/silicon/ai/user) - var/dat = "" - switch(aistate) - if(STATE_DEFAULT) - if(SSshuttle.emergencyCallAmount) - if(SSshuttle.emergencyLastCallLoc) - dat += "Latest emergency signal trace attempt successful.
    Last signal origin: [format_text(SSshuttle.emergencyLastCallLoc.name)].
    " - else - dat += "Latest emergency signal trace attempt failed.
    " - if(authenticated) - dat += "Current login: [auth_id]" - else - dat += "Current login: None" - dat += "

    General Functions" - dat += "
    \[ Message List \]" - if(SSshuttle.emergency.mode == SHUTTLE_IDLE) - dat += "
    \[ Call Emergency Shuttle \]" - dat += "
    \[ Set Status Display \]" - dat += "

    Special Functions" - dat += "
    \[ Make an Announcement \]" - dat += "
    \[ Change Alert Level \]" - dat += "
    \[ Emergency Maintenance Access \]" - if(STATE_CALLSHUTTLE) - dat += get_call_shuttle_form(1) - if(STATE_MESSAGELIST) - dat += "Messages:" - for(var/i in 1 to messages.len) - var/datum/comm_message/M = messages[i] - dat += "
    [M.title]" - if(STATE_VIEWMESSAGE) - if (aicurrmsg) - dat += "[aicurrmsg.title]

    [aicurrmsg.content]" - if(!aicurrmsg.answered && aicurrmsg.possible_answers.len) - for(var/i in 1 to aicurrmsg.possible_answers.len) - var/answer = aicurrmsg.possible_answers[i] - dat += "
    \[ Answer : [answer] \]" - else if(aicurrmsg.answered) - var/answered = aicurrmsg.possible_answers[aicurrmsg.answered] - dat += "
    Archived Answer : [answered]" - dat += "

    \[ Delete \]" - else - aistate = STATE_MESSAGELIST - attack_hand(user) - return null - if(STATE_DELMESSAGE) - if(aicurrmsg) - dat += "Are you sure you want to delete this message? \[ OK | Cancel \]" - else - aistate = STATE_MESSAGELIST - attack_hand(user) - return - - if(STATE_STATUSDISPLAY) - dat += "Set Status Displays
    " - dat += "\[ Clear \]
    " - dat += "\[ Shuttle ETA \]
    " - dat += "\[ Message \]" - dat += "
    " - dat += "\[ Alert: None |" - dat += " Red Alert |" - dat += " Lockdown |" - dat += " Biohazard \]

    " - - if(STATE_ALERT_LEVEL) - dat += "Current alert level: [get_security_level()]
    " - if(GLOB.security_level == SEC_LEVEL_DELTA) - dat += "The self-destruct mechanism is active. Find a way to deactivate the mechanism to lower the alert level or evacuate." - else - dat += "Blue
    " - dat += "Green" - - if(STATE_TOGGLE_EMERGENCY) - if(GLOB.emergency_access == 1) - dat += "Emergency Maintenance Access is currently ENABLED" - dat += "
    Restore maintenance access restrictions?
    \[ OK | Cancel \]" - else - dat += "Emergency Maintenance Access is currently DISABLED" - dat += "
    Lift access restrictions on maintenance and external airlocks?
    \[ OK | Cancel \]" - - dat += "

    \[ [(aistate != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" - return dat - -/obj/machinery/computer/communications/proc/make_announcement(mob/living/user, is_silicon) - if(!SScommunications.can_announce(user, is_silicon)) - to_chat(user, "Intercomms recharging. Please stand by.") - return - var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?") - if(!input || !user.canUseTopic(src, !issilicon(usr))) - return - if(!(user.can_speak())) //No more cheating, mime/random mute guy! - input = "..." - to_chat(user, "You find yourself unable to speak.") - else - input = user.treat_message(input) //Adds slurs and so on. Someone should make this use languages too. - SScommunications.make_announcement(user, is_silicon, input) - deadchat_broadcast(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", user, message_type=DEADCHAT_ANNOUNCEMENT) - -/obj/machinery/computer/communications/proc/post_status(command, data1, data2) - - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = command)) - switch(command) - if("message") - status_signal.data["msg1"] = data1 - status_signal.data["msg2"] = data2 - if("alert") - status_signal.data["picture_state"] = data1 - - frequency.post_signal(src, status_signal) - - -/obj/machinery/computer/communications/Destroy() - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - return ..() - -/obj/machinery/computer/communications/proc/overrideCooldown() - var/obj/item/circuitboard/computer/communications/CM = circuit - CM.lastTimeUsed = 0 - -/obj/machinery/computer/communications/proc/add_message(datum/comm_message/new_message) - messages += new_message - -/datum/comm_message - var/title - var/content - var/list/possible_answers = list() - var/answered - var/datum/callback/answer_callback - -/datum/comm_message/New(new_title,new_content,new_possible_answers) - ..() - if(new_title) - title = new_title - if(new_content) - content = new_content - if(new_possible_answers) - possible_answers = new_possible_answers - -#undef STATE_DEFAULT -#undef STATE_CALLSHUTTLE -#undef STATE_CANCELSHUTTLE -#undef STATE_MESSAGELIST -#undef STATE_VIEWMESSAGE -#undef STATE_DELMESSAGE -#undef STATE_STATUSDISPLAY -#undef STATE_ALERT_LEVEL -#undef STATE_CONFIRM_LEVEL -#undef STATE_TOGGLE_EMERGENCY -#undef STATE_PURCHASE +#define STATE_DEFAULT 1 +#define STATE_CALLSHUTTLE 2 +#define STATE_CANCELSHUTTLE 3 +#define STATE_MESSAGELIST 4 +#define STATE_VIEWMESSAGE 5 +#define STATE_DELMESSAGE 6 +#define STATE_STATUSDISPLAY 7 +#define STATE_ALERT_LEVEL 8 +#define STATE_CONFIRM_LEVEL 9 +#define STATE_TOGGLE_EMERGENCY 10 +#define STATE_PURCHASE 11 + +// The communications computer +/obj/machinery/computer/communications + name = "communications console" + desc = "A console used for high-priority announcements and emergencies." + icon_screen = "comm" + icon_keyboard = "tech_key" + req_access = list(ACCESS_HEADS) + circuit = /obj/item/circuitboard/computer/communications + var/auth_id = "Unknown" //Who is currently logged in? + var/list/datum/comm_message/messages = list() + var/datum/comm_message/currmsg + var/datum/comm_message/aicurrmsg + var/state = STATE_DEFAULT + var/aistate = STATE_DEFAULT + var/message_cooldown = 0 + var/ai_message_cooldown = 0 + var/tmp_alertlevel = 0 + + var/stat_msg1 + var/stat_msg2 + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/communications/proc/checkCCcooldown() + var/obj/item/circuitboard/computer/communications/CM = circuit + if(CM.lastTimeUsed + 600 > world.time) + return FALSE + return TRUE + +/obj/machinery/computer/communications/Initialize() + . = ..() + GLOB.shuttle_caller_list += src + +/obj/machinery/computer/communications/Topic(href, href_list) + if(..()) + return + if(!usr.canUseTopic(src, !issilicon(usr))) + return + if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13 + to_chat(usr, "Unable to establish a connection: \black You're too far away from the station!") + return + usr.set_machine(src) + + + if(!href_list["operation"]) + return + var/obj/item/circuitboard/computer/communications/CM = circuit + switch(href_list["operation"]) + // main interface + if("main") + state = STATE_DEFAULT + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + if("login") + var/mob/M = usr + + var/obj/item/card/id/I = M.get_idcard(TRUE) + + if(I && istype(I)) + if(check_access(I)) + authenticated = 1 + auth_id = "[I.registered_name] ([I.assignment])" + if((20 in I.access)) + authenticated = 2 + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + if(obj_flags & EMAGGED) + authenticated = 2 + auth_id = "Unknown" + to_chat(M, "[src] lets out a quiet alarm as its login is overridden.") + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + playsound(src, 'sound/machines/terminal_alert.ogg', 25, FALSE) + if(prob(25)) + for(var/mob/living/silicon/ai/AI in active_ais()) + SEND_SOUND(AI, sound('sound/machines/terminal_alert.ogg', volume = 10)) //Very quiet for balance reasons + if("logout") + authenticated = 0 + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + + if("swipeidseclevel") + var/mob/M = usr + var/obj/item/card/id/I = M.get_active_held_item() + if (istype(I, /obj/item/pda)) + var/obj/item/pda/pda = I + I = pda.id + if (I && istype(I)) + if(ACCESS_CAPTAIN in I.access) + var/old_level = GLOB.security_level + if(!tmp_alertlevel) + tmp_alertlevel = SEC_LEVEL_GREEN + if(tmp_alertlevel < SEC_LEVEL_GREEN) + tmp_alertlevel = SEC_LEVEL_GREEN + if(tmp_alertlevel > SEC_LEVEL_BLUE) + tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this + set_security_level(tmp_alertlevel) + if(GLOB.security_level != old_level) + to_chat(usr, "Authorization confirmed. Modifying security level.") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + //Only notify people if an actual change happened + var/security_level = get_security_level() + log_game("[key_name(usr)] has changed the security level to [security_level] with [src] at [AREACOORD(usr)].") + message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] with [src] at [AREACOORD(usr)].") + deadchat_broadcast(" has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + tmp_alertlevel = 0 + else + to_chat(usr, "You are not authorized to do this!") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + tmp_alertlevel = 0 + state = STATE_DEFAULT + else + to_chat(usr, "You need to swipe your ID!") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + + if("announce") + if(authenticated==2) + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + make_announcement(usr) + + if("crossserver") + if(authenticated==2) + var/dest = href_list["cross_dest"] + if(!checkCCcooldown()) + to_chat(usr, "Arrays recycling. Please stand by.") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + return + var/warning = dest == "all" ? "Please choose a message to transmit to allied stations." : "Please choose a message to transmit to [dest] sector station." + var/input = stripped_multiline_input(usr, "[warning] Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "") + if(!input || !(usr in view(1,src)) || !checkCCcooldown()) + return + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + if(dest == "all") + send2otherserver("[station_name()]", input,"Comms_Console") + else + send2otherserver("[station_name()]", input,"Comms_Console", list(dest)) + minor_announce(input, title = "Outgoing message to allied station") + usr.log_talk(input, LOG_SAY, tag="message to the other server") + message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server.") + deadchat_broadcast(" has sent an outgoing message to the other station(s).", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + CM.lastTimeUsed = world.time + + if("purchase_menu") + state = STATE_PURCHASE + + if("buyshuttle") + if(authenticated==2) + var/list/shuttles = flatten_list(SSmapping.shuttle_templates) + var/datum/map_template/shuttle/S = locate(href_list["chosen_shuttle"]) in shuttles + if(S && istype(S)) + if(SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE) + to_chat(usr, "It's a bit late to buy a new shuttle, don't you think?") + return + if(SSshuttle.shuttle_purchased) + to_chat(usr, "A replacement shuttle has already been purchased.") + else if(!S.prerequisites_met()) + to_chat(usr, "You have not met the requirements for purchasing this shuttle.") + else + var/points_to_check + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + points_to_check = D.account_balance + if(points_to_check >= S.credit_cost) + SSshuttle.shuttle_purchased = TRUE + SSshuttle.unload_preview() + SSshuttle.load_template(S) + SSshuttle.existing_shuttle = SSshuttle.emergency + SSshuttle.action_load(S) + D.adjust_money(-S.credit_cost) + minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits.[S.extra_desc ? " [S.extra_desc]" : ""]" , "Shuttle Purchase") + message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].") + log_shuttle("[key_name(usr)] has purchased [S.name].") + SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]") + else + to_chat(usr, "Insufficient credits.") + + if("callshuttle") + state = STATE_DEFAULT + if(authenticated && SSshuttle.canEvac(usr)) + state = STATE_CALLSHUTTLE + if("callshuttle2") + if(authenticated) + SSshuttle.requestEvac(usr, href_list["call"]) + if(SSshuttle.emergency.timer) + post_status("shuttle") + state = STATE_DEFAULT + if("cancelshuttle") + state = STATE_DEFAULT + if(authenticated) + state = STATE_CANCELSHUTTLE + if("cancelshuttle2") + if(authenticated) + SSshuttle.cancelEvac(usr) + state = STATE_DEFAULT + if("messagelist") + currmsg = 0 + state = STATE_MESSAGELIST + if("viewmessage") + state = STATE_VIEWMESSAGE + if (!currmsg) + if(href_list["message-num"]) + var/msgnum = text2num(href_list["message-num"]) + currmsg = messages[msgnum] + else + state = STATE_MESSAGELIST + if("delmessage") + state = currmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST + if("delmessage2") + if(authenticated) + if(currmsg) + if(aicurrmsg == currmsg) + aicurrmsg = null + messages -= currmsg + currmsg = null + state = STATE_MESSAGELIST + else + state = STATE_VIEWMESSAGE + if("respond") + var/answer = text2num(href_list["answer"]) + if(!currmsg || !answer || currmsg.possible_answers.len < answer) + state = STATE_MESSAGELIST + else + if(!currmsg.answered) + currmsg.answered = answer + log_game("[key_name(usr)] answered [currmsg.title] comm message. Answer : [currmsg.answered]") + if(currmsg) + currmsg.answer_callback.InvokeAsync() + state = STATE_VIEWMESSAGE + updateDialog() + if("status") + state = STATE_STATUSDISPLAY + if("securitylevel") + tmp_alertlevel = text2num( href_list["newalertlevel"] ) + if(!tmp_alertlevel) + tmp_alertlevel = 0 + state = STATE_CONFIRM_LEVEL + if("changeseclevel") + state = STATE_ALERT_LEVEL + + if("emergencyaccess") + state = STATE_TOGGLE_EMERGENCY + if("enableemergency") + make_maint_all_access() + log_game("[key_name(usr)] enabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") + deadchat_broadcast(" enabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + state = STATE_DEFAULT + if("disableemergency") + revoke_maint_all_access() + log_game("[key_name(usr)] disabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") + deadchat_broadcast(" disabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + state = STATE_DEFAULT + + // Status display stuff + if("setstat") + playsound(src, "terminal_type", 50, FALSE) + switch(href_list["statdisp"]) + if("message") + post_status("message", stat_msg1, stat_msg2) + if("alert") + post_status("alert", href_list["alert"]) + else + post_status(href_list["statdisp"]) + + if("setmsg1") + stat_msg1 = reject_bad_text(input("Line 1", "Enter Message Text", stat_msg1) as text|null, 40) + updateDialog() + if("setmsg2") + stat_msg2 = reject_bad_text(input("Line 2", "Enter Message Text", stat_msg2) as text|null, 40) + updateDialog() + + // OMG CENTCOM LETTERHEAD + if("MessageCentCom") + if(authenticated) + if(!checkCCcooldown()) + to_chat(usr, "Arrays recycling. Please stand by.") + return + var/input = stripped_input(usr, "Please choose a message to transmit to CentCom via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to CentCom.", "") + if(!input || !(usr in view(1,src)) || !checkCCcooldown()) + return + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + CentCom_announce(input, usr) + to_chat(usr, "Message transmitted to Central Command.") + usr.log_talk(input, LOG_SAY, tag="CentCom announcement") + deadchat_broadcast(" has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + CM.lastTimeUsed = world.time + + // OMG SYNDICATE ...LETTERHEAD + if("MessageSyndicate") + if((authenticated) && (obj_flags & EMAGGED)) + if(!checkCCcooldown()) + to_chat(usr, "Arrays recycling. Please stand by.") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + return + var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING COORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to /??????/.", "") + if(!input || !(usr in view(1,src)) || !checkCCcooldown()) + return + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + Syndicate_announce(input, usr) + to_chat(usr, "SYSERR @l(19833)of(transmit.dm): !@$ MESSAGE TRANSMITTED TO SYNDICATE COMMAND.") + usr.log_talk(input, LOG_SAY, tag="Syndicate announcement") + deadchat_broadcast(" has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + CM.lastTimeUsed = world.time + + if("RestoreBackup") + to_chat(usr, "Backup routing data restored!") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + obj_flags &= ~EMAGGED + updateDialog() + + if("nukerequest") //When there's no other way + if(authenticated==2) + if(!checkCCcooldown()) + to_chat(usr, "Arrays recycling. Please stand by.") + return + var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self-Destruct Code Request.","") + if(!input || !(usr in view(1,src)) || !checkCCcooldown()) + return + Nuke_request(input, usr) + to_chat(usr, "Request sent.") + usr.log_message("has requested the nuclear codes from CentCom with reason \"[input]\"", LOG_SAY) + priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self-Destruct Codes Requested",'sound/ai/commandreport.ogg') + CM.lastTimeUsed = world.time + + + // AI interface + if("ai-main") + aicurrmsg = null + aistate = STATE_DEFAULT + if("ai-callshuttle") + aistate = STATE_DEFAULT + if(SSshuttle.canEvac(usr)) + aistate = STATE_CALLSHUTTLE + if("ai-callshuttle2") + SSshuttle.requestEvac(usr, href_list["call"]) + aistate = STATE_DEFAULT + if("ai-messagelist") + aicurrmsg = null + aistate = STATE_MESSAGELIST + if("ai-viewmessage") + aistate = STATE_VIEWMESSAGE + if (!aicurrmsg) + if(href_list["message-num"]) + var/msgnum = text2num(href_list["message-num"]) + aicurrmsg = messages[msgnum] + else + aistate = STATE_MESSAGELIST + if("ai-delmessage") + aistate = aicurrmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST + if("ai-delmessage2") + if(aicurrmsg) + if(currmsg == aicurrmsg) + currmsg = null + messages -= aicurrmsg + aicurrmsg = null + aistate = STATE_MESSAGELIST + if("ai-respond") + var/answer = text2num(href_list["answer"]) + if(!aicurrmsg || !answer || aicurrmsg.possible_answers.len < answer) + aistate = STATE_MESSAGELIST + else + if(!aicurrmsg.answered) + aicurrmsg.answered = answer + log_game("[key_name(usr)] answered [aicurrmsg.title] comm message. Answer : [aicurrmsg.answered]") + if(aicurrmsg.answer_callback) + aicurrmsg.answer_callback.InvokeAsync() + aistate = STATE_VIEWMESSAGE + if("ai-status") + aistate = STATE_STATUSDISPLAY + if("ai-announce") + make_announcement(usr, 1) + if("ai-securitylevel") + tmp_alertlevel = text2num( href_list["newalertlevel"] ) + if(!tmp_alertlevel) + tmp_alertlevel = 0 + var/old_level = GLOB.security_level + if(!tmp_alertlevel) + tmp_alertlevel = SEC_LEVEL_GREEN + if(tmp_alertlevel < SEC_LEVEL_GREEN) + tmp_alertlevel = SEC_LEVEL_GREEN + if(tmp_alertlevel > SEC_LEVEL_BLUE) + tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this + set_security_level(tmp_alertlevel) + if(GLOB.security_level != old_level) + //Only notify people if an actual change happened + var/security_level = get_security_level() + log_game("[key_name(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") + message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") + deadchat_broadcast(" has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + tmp_alertlevel = 0 + aistate = STATE_DEFAULT + if("ai-changeseclevel") + aistate = STATE_ALERT_LEVEL + if("ai-emergencyaccess") + aistate = STATE_TOGGLE_EMERGENCY + if("ai-enableemergency") + make_maint_all_access() + log_game("[key_name(usr)] enabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") + deadchat_broadcast(" enabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + aistate = STATE_DEFAULT + if("ai-disableemergency") + revoke_maint_all_access() + log_game("[key_name(usr)] disabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") + deadchat_broadcast(" disabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + aistate = STATE_DEFAULT + + updateUsrDialog() + +/obj/machinery/computer/communications/attackby(obj/I, mob/user, params) + if(istype(I, /obj/item/card/id)) + attack_hand(user) + else + return ..() + +/obj/machinery/computer/communications/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + if(authenticated == 1) + authenticated = 2 + to_chat(user, "You scramble the communication routing circuits!") + playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE) + +/obj/machinery/computer/communications/ui_interact(mob/user) + . = ..() + if (z > 6) + to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") + return + + var/dat = "" + if(SSshuttle.emergency.mode == SHUTTLE_CALL) + var/timeleft = SSshuttle.emergency.timeLeft() + dat += "Emergency shuttle\n
    \nETA: [timeleft / 60 % 60]:[add_leading(num2text(timeleft % 60), 2, "0")]" + + + var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + + if(issilicon(user)) + var/dat2 = interact_ai(user) // give the AI a different interact proc to limit its access + if(dat2) + dat += dat2 + popup.set_content(dat) + popup.open() + return + + switch(state) + if(STATE_DEFAULT) + if (authenticated) + if(SSshuttle.emergencyCallAmount) + if(SSshuttle.emergencyLastCallLoc) + dat += "Most recent shuttle call/recall traced to: [format_text(SSshuttle.emergencyLastCallLoc.name)]
    " + else + dat += "Unable to trace most recent shuttle call/recall signal.
    " + dat += "Logged in as: [auth_id]" + dat += "
    " + dat += "
    \[ Log Out \]
    " + dat += "
    General Functions" + dat += "
    \[ Message List \]" + switch(SSshuttle.emergency.mode) + if(SHUTTLE_IDLE, SHUTTLE_RECALL) + dat += "
    \[ Call Emergency Shuttle \]" + else + dat += "
    \[ Cancel Shuttle Call \]" + + dat += "
    \[ Set Status Display \]" + if (authenticated==2) + dat += "

    Captain Functions" + dat += "
    \[ Make a Captain's Announcement \]" + var/list/cross_servers = CONFIG_GET(keyed_list/cross_server) + var/our_id = CONFIG_GET(string/cross_comms_name) + if(cross_servers.len) + for(var/server in cross_servers) + if(server == our_id) + continue + dat += "
    \[ Send a message to station in [server] sector. \]" + if(cross_servers.len > 2) + dat += "
    \[ Send a message to all allied stations \]" + if(SSmapping.config.allow_custom_shuttles) + dat += "
    \[ Purchase Shuttle \]" + dat += "
    \[ Change Alert Level \]" + dat += "
    \[ Emergency Maintenance Access \]" + dat += "
    \[ Request Nuclear Authentication Codes \]" + if(!(obj_flags & EMAGGED)) + dat += "
    \[ Send Message to CentCom \]" + else + dat += "
    \[ Send Message to \[UNKNOWN\] \]" + dat += "
    \[ Restore Backup Routing Data \]" + else + dat += "
    \[ Log In \]" + if(STATE_CALLSHUTTLE) + dat += get_call_shuttle_form() + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + if(STATE_CANCELSHUTTLE) + dat += get_cancel_shuttle_form() + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + if(STATE_MESSAGELIST) + dat += "Messages:" + for(var/i in 1 to messages.len) + var/datum/comm_message/M = messages[i] + dat += "
    [M.title]" + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + if(STATE_VIEWMESSAGE) + if (currmsg) + dat += "[currmsg.title]

    [currmsg.content]" + if(!currmsg.answered && currmsg.possible_answers.len) + for(var/i in 1 to currmsg.possible_answers.len) + var/answer = currmsg.possible_answers[i] + dat += "
    \[ Answer : [answer] \]" + else if(currmsg.answered) + var/answered = currmsg.possible_answers[currmsg.answered] + dat += "
    Archived Answer : [answered]" + dat += "

    \[ Delete \]" + else + aistate = STATE_MESSAGELIST + attack_hand(user) + return + if(STATE_DELMESSAGE) + if (currmsg) + dat += "Are you sure you want to delete this message? \[ OK | Cancel \]" + else + state = STATE_MESSAGELIST + attack_hand(user) + return + if(STATE_STATUSDISPLAY) + dat += "Set Status Displays
    " + dat += "\[ Clear \]
    " + dat += "\[ Shuttle ETA \]
    " + dat += "\[ Message \]" + dat += "
    " + dat += "\[ Alert: None |" + dat += " Red Alert |" + dat += " Lockdown |" + dat += " Biohazard \]

    " + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + if(STATE_ALERT_LEVEL) + dat += "Current alert level: [get_security_level()]
    " + if(GLOB.security_level == SEC_LEVEL_DELTA) + dat += "The self-destruct mechanism is active. Find a way to deactivate the mechanism to lower the alert level or evacuate." + else + dat += "Blue
    " + dat += "Green" + if(STATE_CONFIRM_LEVEL) + dat += "Current alert level: [get_security_level()]
    " + dat += "Confirm the change to: [num2seclevel(tmp_alertlevel)]
    " + dat += "Swipe ID to confirm change.
    " + if(STATE_TOGGLE_EMERGENCY) + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + if(GLOB.emergency_access == 1) + dat += "Emergency Maintenance Access is currently ENABLED" + dat += "
    Restore maintenance access restrictions?
    \[ OK | Cancel \]" + else + dat += "Emergency Maintenance Access is currently DISABLED" + dat += "
    Lift access restrictions on maintenance and external airlocks?
    \[ OK | Cancel \]" + + if(STATE_PURCHASE) + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + dat += "Budget: [D.account_balance] Credits.
    " + dat += "
    " + dat += "Caution: Purchasing dangerous shuttles may lead to mutiny and/or death.
    " + dat += "
    " + for(var/shuttle_id in SSmapping.shuttle_templates) + var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] + if(S.can_be_bought && S.credit_cost < INFINITY) + dat += "[S.name] | [S.credit_cost] Credits
    " + dat += "[S.description]
    " + if(S.prerequisites) + dat += "Prerequisites: [S.prerequisites]
    " + dat += "(Purchase)

    " + + dat += "

    \[ [(state != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" + + popup.set_content(dat) + popup.open() + +/obj/machinery/computer/communications/proc/get_javascript_header(form_id) + var/dat = {""} + return dat + +/obj/machinery/computer/communications/proc/get_call_shuttle_form(ai_interface = 0) + var/form_id = "callshuttle" + var/dat = get_javascript_header(form_id) + dat += "" + dat += "" + dat += "" + dat += "Nature of emergency:
    " + dat += "
    Are you sure you want to call the shuttle? \[ Call \]" + return dat + +/obj/machinery/computer/communications/proc/get_cancel_shuttle_form() + var/form_id = "cancelshuttle" + var/dat = get_javascript_header(form_id) + dat += "" + dat += "" + dat += "" + + dat += "
    Are you sure you want to cancel the shuttle? \[ Cancel \]" + return dat + +/obj/machinery/computer/communications/proc/interact_ai(mob/living/silicon/ai/user) + var/dat = "" + switch(aistate) + if(STATE_DEFAULT) + if(SSshuttle.emergencyCallAmount) + if(SSshuttle.emergencyLastCallLoc) + dat += "Latest emergency signal trace attempt successful.
    Last signal origin: [format_text(SSshuttle.emergencyLastCallLoc.name)].
    " + else + dat += "Latest emergency signal trace attempt failed.
    " + if(authenticated) + dat += "Current login: [auth_id]" + else + dat += "Current login: None" + dat += "

    General Functions" + dat += "
    \[ Message List \]" + if(SSshuttle.emergency.mode == SHUTTLE_IDLE) + dat += "
    \[ Call Emergency Shuttle \]" + dat += "
    \[ Set Status Display \]" + dat += "

    Special Functions" + dat += "
    \[ Make an Announcement \]" + dat += "
    \[ Change Alert Level \]" + dat += "
    \[ Emergency Maintenance Access \]" + if(STATE_CALLSHUTTLE) + dat += get_call_shuttle_form(1) + if(STATE_MESSAGELIST) + dat += "Messages:" + for(var/i in 1 to messages.len) + var/datum/comm_message/M = messages[i] + dat += "
    [M.title]" + if(STATE_VIEWMESSAGE) + if (aicurrmsg) + dat += "[aicurrmsg.title]

    [aicurrmsg.content]" + if(!aicurrmsg.answered && aicurrmsg.possible_answers.len) + for(var/i in 1 to aicurrmsg.possible_answers.len) + var/answer = aicurrmsg.possible_answers[i] + dat += "
    \[ Answer : [answer] \]" + else if(aicurrmsg.answered) + var/answered = aicurrmsg.possible_answers[aicurrmsg.answered] + dat += "
    Archived Answer : [answered]" + dat += "

    \[ Delete \]" + else + aistate = STATE_MESSAGELIST + attack_hand(user) + return null + if(STATE_DELMESSAGE) + if(aicurrmsg) + dat += "Are you sure you want to delete this message? \[ OK | Cancel \]" + else + aistate = STATE_MESSAGELIST + attack_hand(user) + return + + if(STATE_STATUSDISPLAY) + dat += "Set Status Displays
    " + dat += "\[ Clear \]
    " + dat += "\[ Shuttle ETA \]
    " + dat += "\[ Message \]" + dat += "
    " + dat += "\[ Alert: None |" + dat += " Red Alert |" + dat += " Lockdown |" + dat += " Biohazard \]

    " + + if(STATE_ALERT_LEVEL) + dat += "Current alert level: [get_security_level()]
    " + if(GLOB.security_level == SEC_LEVEL_DELTA) + dat += "The self-destruct mechanism is active. Find a way to deactivate the mechanism to lower the alert level or evacuate." + else + dat += "Blue
    " + dat += "Green" + + if(STATE_TOGGLE_EMERGENCY) + if(GLOB.emergency_access == 1) + dat += "Emergency Maintenance Access is currently ENABLED" + dat += "
    Restore maintenance access restrictions?
    \[ OK | Cancel \]" + else + dat += "Emergency Maintenance Access is currently DISABLED" + dat += "
    Lift access restrictions on maintenance and external airlocks?
    \[ OK | Cancel \]" + + dat += "

    \[ [(aistate != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" + return dat + +/obj/machinery/computer/communications/proc/make_announcement(mob/living/user, is_silicon) + if(!SScommunications.can_announce(user, is_silicon)) + to_chat(user, "Intercomms recharging. Please stand by.") + return + var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?") + if(!input || !user.canUseTopic(src, !issilicon(usr))) + return + if(!(user.can_speak())) //No more cheating, mime/random mute guy! + input = "..." + to_chat(user, "You find yourself unable to speak.") + else + input = user.treat_message(input) //Adds slurs and so on. Someone should make this use languages too. + SScommunications.make_announcement(user, is_silicon, input) + deadchat_broadcast(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", user, message_type=DEADCHAT_ANNOUNCEMENT) + +/obj/machinery/computer/communications/proc/post_status(command, data1, data2) + + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = command)) + switch(command) + if("message") + status_signal.data["msg1"] = data1 + status_signal.data["msg2"] = data2 + if("alert") + status_signal.data["picture_state"] = data1 + + frequency.post_signal(src, status_signal) + + +/obj/machinery/computer/communications/Destroy() + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + return ..() + +/obj/machinery/computer/communications/proc/overrideCooldown() + var/obj/item/circuitboard/computer/communications/CM = circuit + CM.lastTimeUsed = 0 + +/obj/machinery/computer/communications/proc/add_message(datum/comm_message/new_message) + messages += new_message + +/datum/comm_message + var/title + var/content + var/list/possible_answers = list() + var/answered + var/datum/callback/answer_callback + +/datum/comm_message/New(new_title,new_content,new_possible_answers) + ..() + if(new_title) + title = new_title + if(new_content) + content = new_content + if(new_possible_answers) + possible_answers = new_possible_answers + +#undef STATE_DEFAULT +#undef STATE_CALLSHUTTLE +#undef STATE_CANCELSHUTTLE +#undef STATE_MESSAGELIST +#undef STATE_VIEWMESSAGE +#undef STATE_DELMESSAGE +#undef STATE_STATUSDISPLAY +#undef STATE_ALERT_LEVEL +#undef STATE_CONFIRM_LEVEL +#undef STATE_TOGGLE_EMERGENCY +#undef STATE_PURCHASE diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index f9a7c3fc14b..199049ba6df 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -1,236 +1,236 @@ -#define SENSORS_UPDATE_PERIOD 100 //How often the sensor data updates. - -/obj/machinery/computer/crew - name = "crew monitoring console" - desc = "Used to monitor active health sensors built into most of the crew's uniforms." - icon_screen = "crew" - icon_keyboard = "med_key" - use_power = IDLE_POWER_USE - idle_power_usage = 250 - active_power_usage = 500 - circuit = /obj/item/circuitboard/computer/crew - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/crew/syndie - icon_keyboard = "syndie_key" - -/obj/machinery/computer/crew/interact(mob/user) - GLOB.crewmonitor.show(user,src) - -GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) - -/datum/crewmonitor - var/list/ui_sources = list() //List of user -> ui source - var/list/jobs - var/list/data_by_z = list() - var/list/last_update = list() - -/datum/crewmonitor/New() - . = ..() - - var/list/jobs = new/list() - jobs["Captain"] = 00 - jobs["Head of Personnel"] = 50 - jobs["Head of Security"] = 10 - jobs["Warden"] = 11 - jobs["Security Officer"] = 12 - jobs["Detective"] = 13 - jobs["Chief Medical Officer"] = 20 - jobs["Chemist"] = 21 - jobs["Virologist"] = 22 - jobs["Medical Doctor"] = 23 - jobs["Paramedic"] = 24 - jobs["Research Director"] = 30 - jobs["Scientist"] = 31 - jobs["Roboticist"] = 32 - jobs["Geneticist"] = 33 - jobs["Chief Engineer"] = 40 - jobs["Station Engineer"] = 41 - jobs["Atmospheric Technician"] = 42 - jobs["Quartermaster"] = 51 - jobs["Shaft Miner"] = 52 - jobs["Cargo Technician"] = 53 - jobs["Bartender"] = 61 - jobs["Cook"] = 62 - jobs["Botanist"] = 63 - jobs["Curator"] = 64 - jobs["Chaplain"] = 65 - jobs["Clown"] = 66 - jobs["Mime"] = 67 - jobs["Janitor"] = 68 - jobs["Lawyer"] = 69 - jobs["Psychologist"] = 70 - jobs["Admiral"] = 200 - jobs["CentCom Commander"] = 210 - jobs["Custodian"] = 211 - jobs["Medical Officer"] = 212 - jobs["Research Officer"] = 213 - jobs["Emergency Response Team Commander"] = 220 - jobs["Security Response Officer"] = 221 - jobs["Engineer Response Officer"] = 222 - jobs["Medical Response Officer"] = 223 - jobs["Assistant"] = 999 //Unknowns/custom jobs should appear after civilians, and before assistants - - src.jobs = jobs - -/datum/crewmonitor/Destroy() - return ..() - -/datum/crewmonitor/ui_interact(mob/user, ui_key = "crew", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if (!ui) - ui = new(user, src, ui_key, "CrewConsole", "crew monitor", 800, 600 , master_ui, state) - ui.open() - -/datum/crewmonitor/proc/show(mob/M, source) - ui_sources[M] = source - ui_interact(M) - -/datum/crewmonitor/ui_host(mob/user) - return ui_sources[user] - -/datum/crewmonitor/ui_data(mob/user) - var/z = user.z - if(!z) - var/turf/T = get_turf(user) - z = T.z - var/list/zdata = update_data(z) - . = list() - .["sensors"] = zdata - .["link_allowed"] = isAI(user) - -/datum/crewmonitor/proc/update_data(z) - if(data_by_z["[z]"] && last_update["[z]"] && world.time <= last_update["[z]"] + SENSORS_UPDATE_PERIOD) - return data_by_z["[z]"] - - var/list/results = list() - var/obj/item/clothing/under/U - var/obj/item/card/id/I - var/turf/pos - var/ijob - var/name - var/assignment - var/oxydam - var/toxdam - var/burndam - var/brutedam - var/area - var/pos_x - var/pos_y - var/life_status - - for(var/i in GLOB.nanite_sensors_list) - var/mob/living/carbon/human/H = i - var/nanite_sensors = FALSE - if(H in SSnanites.nanite_monitored_mobs) - nanite_sensors = TRUE - // Check if their z-level is correct and if they are wearing a uniform. - // Accept H.z==0 as well in case the mob is inside an object. - if(((H.z == 0 || H.z == z) && (nanite_sensors))) - - pos = H.z == 0 || (nanite_sensors) ? get_turf(H) : null - - // Special case: If the mob is inside an object confirm the z-level on turf level. - if (H.z == 0 && (!pos || pos.z != z)) - continue - - I = H.wear_id ? H.wear_id.GetID() : null - - if (I) - name = I.registered_name - assignment = I.assignment - ijob = jobs[I.assignment] - else - name = "Unknown" - assignment = "" - ijob = 80 - - life_status = (!H.stat ? TRUE : FALSE) - - oxydam = round(H.getOxyLoss(),1) - toxdam = round(H.getToxLoss(),1) - burndam = round(H.getFireLoss(),1) - brutedam = round(H.getBruteLoss(),1) - - if (!pos) - pos = get_turf(H) - area = get_area_name(H, TRUE) - pos_x = pos.x - pos_y = pos.y - - results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) - - for(var/i in GLOB.suit_sensors_list) - var/mob/living/carbon/human/H = i - // Check if their z-level is correct and if they are wearing a uniform. - // Accept H.z==0 as well in case the mob is inside an object. Nanite sensors come before normal sensors. - if((H.z == 0 || H.z == z) && (istype(H.w_uniform, /obj/item/clothing/under)) && !(H in GLOB.nanite_sensors_list)) - U = H.w_uniform - - // Are the suit sensors on? - if (((U.has_sensor > 0) && U.sensor_mode)) - pos = H.z == 0 || (U.sensor_mode == SENSOR_COORDS) ? get_turf(H) : null - - // Special case: If the mob is inside an object confirm the z-level on turf level. - if (H.z == 0 && (!pos || pos.z != z)) - continue - - I = H.wear_id ? H.wear_id.GetID() : null - - if (I) - name = I.registered_name - assignment = I.assignment - ijob = jobs[I.assignment] - else - name = "Unknown" - assignment = "" - ijob = 80 - - if (U.sensor_mode >= SENSOR_LIVING) - life_status = (!H.stat ? TRUE : FALSE) - else - life_status = null - - if (U.sensor_mode >= SENSOR_VITALS) - oxydam = round(H.getOxyLoss(),1) - toxdam = round(H.getToxLoss(),1) - burndam = round(H.getFireLoss(),1) - brutedam = round(H.getBruteLoss(),1) - else - oxydam = null - toxdam = null - burndam = null - brutedam = null - - if (U.sensor_mode >= SENSOR_COORDS) - if (!pos) - pos = get_turf(H) - area = get_area_name(H, TRUE) - pos_x = pos.x - pos_y = pos.y - else - area = null - pos_x = null - pos_y = null - - results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) - - data_by_z["[z]"] = sortTim(results,/proc/sensor_compare) - last_update["[z]"] = world.time - - return results - -/proc/sensor_compare(list/a,list/b) - return a["ijob"] - b["ijob"] - -/datum/crewmonitor/ui_act(action,params) - var/mob/living/silicon/ai/AI = usr - if(!istype(AI)) - return - switch (action) - if ("select_person") - AI.ai_camera_track(params["name"]) - -#undef SENSORS_UPDATE_PERIOD +#define SENSORS_UPDATE_PERIOD 100 //How often the sensor data updates. + +/obj/machinery/computer/crew + name = "crew monitoring console" + desc = "Used to monitor active health sensors built into most of the crew's uniforms." + icon_screen = "crew" + icon_keyboard = "med_key" + use_power = IDLE_POWER_USE + idle_power_usage = 250 + active_power_usage = 500 + circuit = /obj/item/circuitboard/computer/crew + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/crew/syndie + icon_keyboard = "syndie_key" + +/obj/machinery/computer/crew/interact(mob/user) + GLOB.crewmonitor.show(user,src) + +GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) + +/datum/crewmonitor + var/list/ui_sources = list() //List of user -> ui source + var/list/jobs + var/list/data_by_z = list() + var/list/last_update = list() + +/datum/crewmonitor/New() + . = ..() + + var/list/jobs = new/list() + jobs["Captain"] = 00 + jobs["Head of Personnel"] = 50 + jobs["Head of Security"] = 10 + jobs["Warden"] = 11 + jobs["Security Officer"] = 12 + jobs["Detective"] = 13 + jobs["Chief Medical Officer"] = 20 + jobs["Chemist"] = 21 + jobs["Virologist"] = 22 + jobs["Medical Doctor"] = 23 + jobs["Paramedic"] = 24 + jobs["Research Director"] = 30 + jobs["Scientist"] = 31 + jobs["Roboticist"] = 32 + jobs["Geneticist"] = 33 + jobs["Chief Engineer"] = 40 + jobs["Station Engineer"] = 41 + jobs["Atmospheric Technician"] = 42 + jobs["Quartermaster"] = 51 + jobs["Shaft Miner"] = 52 + jobs["Cargo Technician"] = 53 + jobs["Bartender"] = 61 + jobs["Cook"] = 62 + jobs["Botanist"] = 63 + jobs["Curator"] = 64 + jobs["Chaplain"] = 65 + jobs["Clown"] = 66 + jobs["Mime"] = 67 + jobs["Janitor"] = 68 + jobs["Lawyer"] = 69 + jobs["Psychologist"] = 70 + jobs["Admiral"] = 200 + jobs["CentCom Commander"] = 210 + jobs["Custodian"] = 211 + jobs["Medical Officer"] = 212 + jobs["Research Officer"] = 213 + jobs["Emergency Response Team Commander"] = 220 + jobs["Security Response Officer"] = 221 + jobs["Engineer Response Officer"] = 222 + jobs["Medical Response Officer"] = 223 + jobs["Assistant"] = 999 //Unknowns/custom jobs should appear after civilians, and before assistants + + src.jobs = jobs + +/datum/crewmonitor/Destroy() + return ..() + +/datum/crewmonitor/ui_interact(mob/user, ui_key = "crew", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + ui = new(user, src, ui_key, "CrewConsole", "crew monitor", 800, 600 , master_ui, state) + ui.open() + +/datum/crewmonitor/proc/show(mob/M, source) + ui_sources[M] = source + ui_interact(M) + +/datum/crewmonitor/ui_host(mob/user) + return ui_sources[user] + +/datum/crewmonitor/ui_data(mob/user) + var/z = user.z + if(!z) + var/turf/T = get_turf(user) + z = T.z + var/list/zdata = update_data(z) + . = list() + .["sensors"] = zdata + .["link_allowed"] = isAI(user) + +/datum/crewmonitor/proc/update_data(z) + if(data_by_z["[z]"] && last_update["[z]"] && world.time <= last_update["[z]"] + SENSORS_UPDATE_PERIOD) + return data_by_z["[z]"] + + var/list/results = list() + var/obj/item/clothing/under/U + var/obj/item/card/id/I + var/turf/pos + var/ijob + var/name + var/assignment + var/oxydam + var/toxdam + var/burndam + var/brutedam + var/area + var/pos_x + var/pos_y + var/life_status + + for(var/i in GLOB.nanite_sensors_list) + var/mob/living/carbon/human/H = i + var/nanite_sensors = FALSE + if(H in SSnanites.nanite_monitored_mobs) + nanite_sensors = TRUE + // Check if their z-level is correct and if they are wearing a uniform. + // Accept H.z==0 as well in case the mob is inside an object. + if(((H.z == 0 || H.z == z) && (nanite_sensors))) + + pos = H.z == 0 || (nanite_sensors) ? get_turf(H) : null + + // Special case: If the mob is inside an object confirm the z-level on turf level. + if (H.z == 0 && (!pos || pos.z != z)) + continue + + I = H.wear_id ? H.wear_id.GetID() : null + + if (I) + name = I.registered_name + assignment = I.assignment + ijob = jobs[I.assignment] + else + name = "Unknown" + assignment = "" + ijob = 80 + + life_status = (!H.stat ? TRUE : FALSE) + + oxydam = round(H.getOxyLoss(),1) + toxdam = round(H.getToxLoss(),1) + burndam = round(H.getFireLoss(),1) + brutedam = round(H.getBruteLoss(),1) + + if (!pos) + pos = get_turf(H) + area = get_area_name(H, TRUE) + pos_x = pos.x + pos_y = pos.y + + results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) + + for(var/i in GLOB.suit_sensors_list) + var/mob/living/carbon/human/H = i + // Check if their z-level is correct and if they are wearing a uniform. + // Accept H.z==0 as well in case the mob is inside an object. Nanite sensors come before normal sensors. + if((H.z == 0 || H.z == z) && (istype(H.w_uniform, /obj/item/clothing/under)) && !(H in GLOB.nanite_sensors_list)) + U = H.w_uniform + + // Are the suit sensors on? + if (((U.has_sensor > 0) && U.sensor_mode)) + pos = H.z == 0 || (U.sensor_mode == SENSOR_COORDS) ? get_turf(H) : null + + // Special case: If the mob is inside an object confirm the z-level on turf level. + if (H.z == 0 && (!pos || pos.z != z)) + continue + + I = H.wear_id ? H.wear_id.GetID() : null + + if (I) + name = I.registered_name + assignment = I.assignment + ijob = jobs[I.assignment] + else + name = "Unknown" + assignment = "" + ijob = 80 + + if (U.sensor_mode >= SENSOR_LIVING) + life_status = (!H.stat ? TRUE : FALSE) + else + life_status = null + + if (U.sensor_mode >= SENSOR_VITALS) + oxydam = round(H.getOxyLoss(),1) + toxdam = round(H.getToxLoss(),1) + burndam = round(H.getFireLoss(),1) + brutedam = round(H.getBruteLoss(),1) + else + oxydam = null + toxdam = null + burndam = null + brutedam = null + + if (U.sensor_mode >= SENSOR_COORDS) + if (!pos) + pos = get_turf(H) + area = get_area_name(H, TRUE) + pos_x = pos.x + pos_y = pos.y + else + area = null + pos_x = null + pos_y = null + + results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) + + data_by_z["[z]"] = sortTim(results,/proc/sensor_compare) + last_update["[z]"] = world.time + + return results + +/proc/sensor_compare(list/a,list/b) + return a["ijob"] - b["ijob"] + +/datum/crewmonitor/ui_act(action,params) + var/mob/living/silicon/ai/AI = usr + if(!istype(AI)) + return + switch (action) + if ("select_person") + AI.ai_camera_track(params["name"]) + +#undef SENSORS_UPDATE_PERIOD diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm index 9d086bc0904..8b436b96380 100644 --- a/code/game/machinery/computer/dna_console.dm +++ b/code/game/machinery/computer/dna_console.dm @@ -1,2011 +1,2011 @@ -/// Base timeout for creating mutation activators and other injectors -#define INJECTOR_TIMEOUT 100 -/// Maximum number of genetic makeup storage slots in DNA Console -#define NUMBER_OF_BUFFERS 3 -/// Timeout for DNA Scramble in DNA Consoles -#define SCRAMBLE_TIMEOUT 600 -/// Timeout for using the Joker feature to solve a gene in DNA Console -#define JOKER_TIMEOUT 12000 -/// How much time DNA Scanner upgrade tiers remove from JOKER_TIMEOUT -#define JOKER_UPGRADE 3000 - -/// Maximum value for radiaton strength when pulsing enzymes -#define RADIATION_STRENGTH_MAX 15 -/// Larger multipliers will affect the range of values when pulsing enzymes -#define RADIATION_STRENGTH_MULTIPLIER 1 - -/// Maximum value for the radiation pulse duration when pulsing enzymes -#define RADIATION_DURATION_MAX 30 -/// Large values reduce pulse accuracy and may pulse other enzymes than selected -#define RADIATION_ACCURACY_MULTIPLIER 3 - -/// Special status indicating a scanner occupant is transforming eg. from monkey to human -#define STATUS_TRANSFORMING 4 - -/// Multiplier for how much radiation received from DNA Console functionality -#define RADIATION_IRRADIATION_MULTIPLIER 1 - -/// Flag for the mutation ref search system. Search will include scanner occupant -#define SEARCH_OCCUPANT 1 -/// Flag for the mutation ref search system. Search will include console storage -#define SEARCH_STORED 2 -/// Flag for the mutation ref search system. Search will include diskette storage -#define SEARCH_DISKETTE 4 -/// Flag for the mutation ref search system. Search will include advanced injector mutations -#define SEARCH_ADV_INJ 8 - -/obj/machinery/computer/scan_consolenew - name = "DNA Console" - desc = "Scan DNA." - icon_screen = "dna" - icon_keyboard = "med_key" - density = TRUE - circuit = /obj/item/circuitboard/computer/scan_consolenew - - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 400 - light_color = LIGHT_COLOR_BLUE - - /// Link to the techweb's stored research. Used to retrieve stored mutations - var/datum/techweb/stored_research - /// Duration for enzyme radiation pulses - var/radduration = 2 - /// Strength for enzyme radiation pulses - var/radstrength = 1 - /// Maximum number of enzymes we can store - var/list/genetic_makeup_buffer[NUMBER_OF_BUFFERS] - /// List of all mutations stored on the DNA Console - var/list/stored_mutations = list() - /// List of all chromosomes stored in the DNA Console - var/list/stored_chromosomes = list() - /// Assoc list of all advanced injectors. Keys are injector names. Values are lists of mutations. - var/list/list/injector_selection = list() - /// Maximum number of advanced injectors that DNA Consoles store - var/max_injector_selections = 2 - /// Maximum number of mutation that an advanced injector can store - var/max_injector_mutations = 10 - /// Maximum total instability of all combined mutations allowed on an advanced injector - var/max_injector_instability = 50 - - /// World time when injectors are ready to be printed - var/injectorready = 0 - /// World time when JOKER algorithm can be used in DNA Consoles - var/jokerready = 0 - /// World time when Scramble can be used in DNA Consoles - var/scrambleready = 0 - - /// Currently stored genetic data diskette - var/obj/item/disk/data/diskette = null - - /// Current delayed action, used for delayed enzyme transfer on scanner door close - var/list/delayed_action = null - - /// Index of the enzyme being modified during delayed enzyme pulse operations - var/rad_pulse_index = 0 - /// World time when the enzyme pulse should complete - var/rad_pulse_timer = 0 - - /// Used for setting tgui data - Whether the connected DNA Scanner is usable - var/can_use_scanner = FALSE - /// Used for setting tgui data - Whether the current DNA Scanner occupant is viable for genetic modification - var/is_viable_occupant = FALSE - /// Used for setting tgui data - Whether Scramble DNA is ready - var/is_scramble_ready = FALSE - /// Used for setting tgui data - Whether JOKER algorithm is ready - var/is_joker_ready = FALSE - /// Used for setting tgui data - Whether injectors are ready to be printed - var/is_injector_ready = FALSE - /// Used for setting tgui data - Wheher an enzyme pulse operation is ongoing - var/is_pulsing_rads = FALSE - /// Used for setting tgui data - Time until scramble is ready - var/time_to_scramble = 0 - /// Used for setting tgui data - Time until joker is ready - var/time_to_joker = 0 - /// Used for setting tgui data - Time until injectors are ready - var/time_to_injector = 0 - /// Used for setting tgui data - Time until the enzyme pulse is complete - var/time_to_pulse = 0 - - /// Currently connected DNA Scanner - var/obj/machinery/dna_scannernew/connected_scanner = null - /// Current DNA Scanner occupant - var/mob/living/carbon/scanner_occupant = null - - /// Used for setting tgui data - List of occupant mutations - var/list/tgui_occupant_mutations = list() - /// Used for setting tgui data - List of DNA Console stored mutations - var/list/tgui_console_mutations = list() - /// Used for setting tgui data - List of diskette stored mutations - var/list/tgui_diskette_mutations = list() - /// Used for setting tgui data - List of DNA Console chromosomes - var/list/tgui_console_chromosomes = list() - /// Used for setting tgui data - List of occupant mutations - var/list/tgui_genetic_makeup = list() - /// Used for setting tgui data - List of occupant mutations - var/list/tgui_advinjector_mutations = list() - - - /// State of tgui view, i.e. which tab is currently active, or which genome we're currently looking at. - var/list/list/tgui_view_state = list() - -/obj/machinery/computer/scan_consolenew/process() - . = ..() - - // This is for pulsing the UI element with radiation as part of genetic makeup - // If rad_pulse_index > 0 then it means we're attempting a rad pulse - if((rad_pulse_index > 0) && (rad_pulse_timer <= world.time)) - rad_pulse() - return - -/obj/machinery/computer/scan_consolenew/attackby(obj/item/I, mob/user, params) - // Store chromosomes in the console if there's room - if (istype(I, /obj/item/chromosome)) - I.forceMove(src) - stored_chromosomes += I - to_chat(user, "You insert [I].") - return - - // Insert data disk if console disk slot is empty - // Swap data disk if there is one already a disk in the console - if (istype(I, /obj/item/disk/data)) //INSERT SOME DISKETTES - // Insert disk into DNA Console - if (!user.transferItemToLoc(I,src)) - return - // If insertion was successful and there's already a diskette in the console, eject the old one. - if(diskette) - eject_disk(user) - // Set the new diskette. - diskette = I - to_chat(user, "You insert [I].") - return - - // Recycle non-activator used injectors - // Turn activator used injectors (aka research injectors) to chromosomes - if(istype(I, /obj/item/dnainjector/activator)) - var/obj/item/dnainjector/activator/A = I - if(A.used) - to_chat(user,"Recycled [I].") - if(A.research) - if(prob(60)) - var/c_typepath = generate_chromosome() - var/obj/item/chromosome/CM = new c_typepath (src) - stored_chromosomes += CM - to_chat(user,"[capitalize(CM.name)] added to storage.") - else - to_chat(user, "There was not enough genetic data to extract a viable chromosome.") - qdel(I) - return - - return ..() - - -/obj/machinery/computer/scan_consolenew/AltClick(mob/user) - // Make sure the user can interact with the machine. - if(!user.canUseTopic(src, !issilicon(user))) - return - - eject_disk(user) - -/obj/machinery/computer/scan_consolenew/Initialize() - . = ..() - - // Connect with a nearby DNA Scanner on init - connect_to_scanner() - - // Set appropriate ready timers and limits for machines functions - injectorready = world.time + INJECTOR_TIMEOUT - scrambleready = world.time + SCRAMBLE_TIMEOUT - jokerready = world.time + JOKER_TIMEOUT - - // Set the default tgui state - set_default_state() - - // Link machine with research techweb. Used for discovering and accessing - // already discovered mutations - stored_research = SSresearch.science_tech - -/obj/machinery/computer/scan_consolenew/examine(mob/user) - . = ..() - -/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - . = ..() - - // Most of ui_interact is spent setting variables for passing to the tgui - // interface. - // We can also do some general state processing here too as it's a good - // indication that a player is using the console. - - var/scanner_op = scanner_operational() - var/can_modify_occ = can_modify_occupant() - - // Check for connected AND operational scanner. - if(scanner_op) - can_use_scanner = TRUE - else - can_use_scanner = FALSE - connected_scanner = null - is_viable_occupant = FALSE - - // Check for a viable occupant in the scanner. - if(can_modify_occ) - is_viable_occupant = TRUE - else - is_viable_occupant = FALSE - - - // Populates various buffers for passing to tgui - build_mutation_list(can_modify_occ) - build_genetic_makeup_list() - - // Populate variables for passing to tgui interface - is_scramble_ready = (scrambleready < world.time) - time_to_scramble = round((scrambleready - world.time)/10) - - is_joker_ready = (jokerready < world.time) - time_to_joker = round((jokerready - world.time)/10) - - is_injector_ready = (injectorready < world.time) - time_to_injector = round((injectorready - world.time)/10) - - is_pulsing_rads = ((rad_pulse_index > 0) && (rad_pulse_timer > world.time)) - time_to_pulse = round((rad_pulse_timer - world.time)/10) - - // Attempt to update tgui ui, open and update if needed. - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - - if(!ui) - ui = new(user, src, ui_key, "DnaConsole", name, 539, 710, master_ui, state) - ui.open() - -/obj/machinery/computer/scan_consolenew/ui_data(mob/user) - var/list/data = list() - - data["view"] = tgui_view_state - data["storage"] = list() - - // This block of code generates the huge data structure passed to the tgui - // interface for displaying all the various bits of console/scanner data - // Should all be very self-explanatory - data["isScannerConnected"] = can_use_scanner - if(can_use_scanner) - data["scannerOpen"] = connected_scanner.state_open - data["scannerLocked"] = connected_scanner.locked - data["radStrength"] = radstrength - data["radDuration"] = radduration - data["stdDevStr"] = radstrength * RADIATION_STRENGTH_MULTIPLIER - switch(RADIATION_ACCURACY_MULTIPLIER / (radduration + (connected_scanner.precision_coeff ** 2))) //hardcoded values from a z-table for a normal distribution - if(0 to 0.25) - data["stdDevAcc"] = ">95 %" - if(0.25 to 0.5) - data["stdDevAcc"] = "68-95 %" - if(0.5 to 0.75) - data["stdDevAcc"] = "55-68 %" - else - data["stdDevAcc"] = "<38 %" - - data["isViableSubject"] = is_viable_occupant - if(is_viable_occupant) - data["subjectName"] = scanner_occupant.name - if(scanner_occupant.transformation_timer) - data["subjectStatus"] = STATUS_TRANSFORMING - else - data["subjectStatus"] = scanner_occupant.stat - data["subjectHealth"] = scanner_occupant.health - data["subjectRads"] = scanner_occupant.radiation/(RAD_MOB_SAFE/100) - data["subjectEnzymes"] = scanner_occupant.dna.unique_enzymes - data["isMonkey"] = ismonkey(scanner_occupant) - data["subjectUNI"] = scanner_occupant.dna.uni_identity - data["storage"]["occupant"] = tgui_occupant_mutations - //data["subjectMutations"] = tgui_occupant_mutations - else - data["subjectName"] = null - data["subjectStatus"] = null - data["subjectHealth"] = null - data["subjectRads"] = null - data["subjectEnzymes"] = null - //data["subjectMutations"] = null - data["storage"]["occupant"] = null - - data["hasDelayedAction"] = (delayed_action != null) - data["isScrambleReady"] = is_scramble_ready - data["isJokerReady"] = is_joker_ready - data["isInjectorReady"] = is_injector_ready - data["scrambleSeconds"] = time_to_scramble - data["jokerSeconds"] = time_to_joker - data["injectorSeconds"] = time_to_injector - data["isPulsingRads"] = is_pulsing_rads - data["radPulseSeconds"] = time_to_pulse - - if(diskette != null) - data["hasDisk"] = TRUE - data["diskCapacity"] = diskette.max_mutations - LAZYLEN(diskette.mutations) - data["diskReadOnly"] = diskette.read_only - //data["diskMutations"] = tgui_diskette_mutations - data["storage"]["disk"] = tgui_diskette_mutations - data["diskHasMakeup"] = (LAZYLEN(diskette.genetic_makeup_buffer) > 0) - data["diskMakeupBuffer"] = diskette.genetic_makeup_buffer.Copy() - else - data["hasDisk"] = FALSE - data["diskCapacity"] = 0 - data["diskReadOnly"] = TRUE - //data["diskMutations"] = null - data["storage"]["disk"] = null - data["diskHasMakeup"] = FALSE - data["diskMakeupBuffer"] = null - - //data["mutationStorage"] = tgui_console_mutations - data["storage"]["console"] = tgui_console_mutations - data["chromoStorage"] = tgui_console_chromosomes - data["makeupCapacity"] = NUMBER_OF_BUFFERS - data["makeupStorage"] = tgui_genetic_makeup - - //data["advInjectors"] = tgui_advinjector_mutations - data["storage"]["injector"] = tgui_advinjector_mutations - data["maxAdvInjectors"] = max_injector_selections - - return data - -/obj/machinery/computer/scan_consolenew/ui_act(action, var/list/params) - if(..()) - return TRUE - - . = TRUE - - add_fingerprint(usr) - usr.set_machine(src) - - switch(action) - // Connect this DNA Console to a nearby DNA Scanner - // Usually only activate as an option if there is no connected scanner - if("connect_scanner") - connect_to_scanner() - return - - // Toggle the door open/closed status on attached DNA Scanner - if("toggle_door") - // GUARD CHECK - Scanner still connected and operational? - if(!scanner_operational()) - return - - connected_scanner.toggle_open(usr) - return - - // Toggle the door bolts on the attached DNA Scanner - if("toggle_lock") - // GUARD CHECK - Scanner still connected and operational? - if(!scanner_operational()) - return - - connected_scanner.locked = !connected_scanner.locked - return - - // Scramble scanner occupant's DNA - if("scramble_dna") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - // GUARD CHECK - Is scramble DNA actually ready? - if(!can_modify_occupant() || !(scrambleready < world.time)) - return - - scanner_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA)) - scanner_occupant.dna.generate_dna_blocks() - scrambleready = world.time + SCRAMBLE_TIMEOUT - to_chat(usr,"DNA scrambled.") - scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected_scanner.damage_coeff ** 2) - return - - // Check whether a specific mutation is eligible for discovery within the - // scanner occupant - // This is additionally done when a mutation's tab is selected in the tgui - // interface. This is because some mutations, such as Monkified on monkeys, - // are infact completed by default but not yet discovered. Likewise, all - // mutations can have their sequence completed while Monkified is still an - // active mutation and thus won't immediately be discovered but could be - // discovered when Monkified is removed - // ---------------------------------------------------------------------- // - // params["alias"] - Alias of a mutation. The alias is the "hidden" name of - // the mutation, for example "Mutation 5" or "Mutation 33" - if("check_discovery") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - // GUARD CHECK - Have we somehow cheekily swapped occupants? This is - // unexpected. - if(!(scanner_occupant == connected_scanner.occupant)) - return - - check_discovery(params["alias"]) - return - - // Check all mutations of the occupant and check if any are discovered. - // This is called when the Genetic Sequencer is selected. It'll do things - // like immediately discover Monkified without needing to click through - // the mutation tabs and handle cases where mutations are solved but not - // discovered due to the Monkified mutation being active then removed. - if("all_check_discovery") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - // GUARD CHECK - Have we somehow cheekily swapped occupants? This is - // unexpected. - if(!(scanner_occupant == connected_scanner.occupant)) - return - - // Go over all standard mutations and check if they've been discovered. - for(var/mutation_type in scanner_occupant.dna.mutation_index) - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type) - check_discovery(HM.alias) - - return - - // Set a gene in a mutation's genetic sequence. Will also check for mutations - // discovery as part of the process. - // ---------------------------------------------------------------------- // - // params["alias"] - Alias of a mutation. The alias is the "hidden" name of - // the mutation, for example "Mutation 5" or "Mutation 33" - // params["gene"] - The letter of the new gene - // params["pos"] - The BYOND index of the letter in the gene sequence to be - // changed. Expects a text string from TGUI and will convert to a number - if("pulse_gene") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - // GUARD CHECK - Have we somehow cheekily swapped occupants? This is - // unexpected. - if(!(scanner_occupant == connected_scanner.occupant)) - return - - // GUARD CHECK - Is the occupant currently undergoing some form of - // transformation? If so, we don't want to be pulsing genes. - if(scanner_occupant.transformation_timer) - to_chat(usr,"Gene pulse failed: The scanner occupant undergoing a transformation.") - return - - // Resolve mutation's BYOND path from the alias - var/alias = params["alias"] - var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias) - - // Make sure the occupant still has this mutation - if(!(path in scanner_occupant.dna.mutation_index)) - return - - // Resolve BYOND path to genome sequence of scanner occupant - var/sequence = GET_GENE_STRING(path, scanner_occupant.dna) - - var/newgene = params["gene"] - var/genepos = text2num(params["pos"]) - - // If the new gene is J, this means we're dealing with a JOKER - // GUARD CHECK - Is JOKER actually ready? - if((newgene == "J") && (jokerready < world.time)) - var/truegenes = GET_SEQUENCE(path) - newgene = truegenes[genepos] - jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected_scanner.precision_coeff-1)) - - // If the gene is an X, we want to update the default genes with the new - // X to allow highlighting logic to work on the tgui interface. - if(newgene == "X") - var/defaultseq = scanner_occupant.dna.default_mutation_genes[path] - defaultseq = copytext_char(defaultseq, 1, genepos) + newgene + copytext_char(defaultseq, genepos + 1) - scanner_occupant.dna.default_mutation_genes[path] = defaultseq - - // Copy genome to scanner occupant and do some basic mutation checks as - // we've increased the occupant rads - sequence = copytext_char(sequence, 1, genepos) + newgene + copytext_char(sequence, genepos + 1) - scanner_occupant.dna.mutation_index[path] = sequence - scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER/connected_scanner.damage_coeff - scanner_occupant.domutcheck() - - // GUARD CHECK - Modifying genetics can lead to edge cases where the - // scanner occupant is qdel'd and replaced with a different entity. - // Examples of this include adding/removing the Monkified mutation which - // qdels the previous entity and creates a brand new one in its place. - // We should redo all of our occupant modification checks again, although - // it is less than ideal. - if(!can_modify_occupant()) - return - - // Check if we cracked a mutation - check_discovery(alias) - - return - - // Apply a chromosome to a specific mutation. - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to apply the chromo to - // params["chromo"] - Name of the chromosome to apply to the mutation - if("apply_chromo") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - // GUARD CHECK - Have we somehow cheekily swapped occupants? This is - // unexpected. - if(!(scanner_occupant == connected_scanner.occupant)) - return - - var/bref = params["mutref"] - - // GUARD CHECK - Only search occupant for this specific ref, since your - // can only apply chromosomes to mutations occupants. - var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT) - - // GUARD CHECK - This should not be possible. Unexpected result - if(!HM) - return - - // Look through our stored chromos and compare names to find a - // stored chromo we can apply. - for(var/obj/item/chromosome/CM in stored_chromosomes) - if(CM.can_apply(HM) && (CM.name == params["chromo"])) - stored_chromosomes -= CM - CM.apply(HM) - - return - - // Print any type of standard injector, limited right now to activators that - // activate a dormant mutation and mutators that forcibly create a new - // MUT_EXTRA mutation - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to create an injector of - // params["is_activator"] - Is this an "Activator" style injector, also - // referred to as a "Research" type. Expects a string with 0 or 1, which - // then gets converted to a number. - // params["source"] - The source the request came from. - // Expected results: - // "occupant" - From genetic sequencer - // "console" - From DNA Console storage - // "disk" - From inserted diskette - if("print_injector") - // Because printing mutators and activators share a bunch of code, - // it makes sense to keep them both together and set unique vars - // later in the code - - // As a side note, because mutations can contain unique metadata, - // this system uses BYOND Atom Refs to safely and accurately - // identify mutations from big ol' lists - - // GUARD CHECK - Is the injector actually ready? - if(world.time < injectorready) - return - - var/search_flags = 0 - - switch(params["source"]) - if("occupant") - // GUARD CHECK - Make sure we can modify the occupant before we - // attempt to search them for any given mutation refs. This could - // lead to no search flags being passed to get_mut_by_ref and this - // is intended functionality to prevent any cheese or abuse - if(can_modify_occupant()) - search_flags |= SEARCH_OCCUPANT - if("console") - search_flags |= SEARCH_STORED - if("disk") - search_flags |= SEARCH_DISKETTE - - var/bref = params["mutref"] - var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags) - - // GUARD CHECK - This should not be possible. Unexpected result - if(!HM) - return - - // Create a new DNA Injector and add the appropriate mutations to it - var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc) - I.add_mutations += new HM.type(copymut = HM) - - var/is_activator = text2num(params["is_activator"]) - - // Activators are also called "research" injectors and are used to create - // chromosomes by recycling at the DNA Console - if(is_activator) - I.name = "[HM.name] activator" - I.research = TRUE - // If there's an operational connected scanner, we can use its upgrades - // to improve our injector's radiation generation - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff*4 - injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected_scanner.precision_coeff) - else - injectorready = world.time + INJECTOR_TIMEOUT - else - I.name = "[HM.name] mutator" - I.doitanyway = TRUE - // If there's an operational connected scanner, we can use its upgrades - // to improve our injector's radiation generation - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected_scanner.precision_coeff) - else - injectorready = world.time + INJECTOR_TIMEOUT * 5 - - return - - // Save a mutation to the console's storage buffer. - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to store - // params["source"] - The source the request came from. - // Expected results: - // "occupant" - From genetic sequencer - // "disk" - From inserted diskette - if("save_console") - var/search_flags = 0 - - switch(params["source"]) - if("occupant") - // GUARD CHECK - Make sure we can modify the occupant before we - // attempt to search them for any given mutation refs. This could - // lead to no search flags being passed to get_mut_by_ref and this - // is intended functionality to prevent any cheese or abuse - if(can_modify_occupant()) - search_flags |= SEARCH_OCCUPANT - if("disk") - search_flags |= SEARCH_DISKETTE - - var/bref = params["mutref"] - var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags) - - // GUARD CHECK - This should not be possible. Unexpected result - if(!HM) - return - - var/datum/mutation/human/A = new HM.type() - A.copy_mutation(HM) - stored_mutations += A - to_chat(usr,"Mutation successfully stored.") - return - - // Save a mutation to the diskette's storage buffer. - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to store - // params["source"] - The source the request came from - // Expected results: - // "occupant" - From genetic sequencer - // "console" - From DNA Console storage - if("save_disk") - // GUARD CHECK - This code shouldn't even be callable without a diskette - // inserted. Unexpected result - if(!diskette) - return - - // GUARD CHECK - Make sure the disk is not full - if(LAZYLEN(diskette.mutations) >= diskette.max_mutations) - to_chat(usr,"Disk storage is full.") - return - - // GUARD CHECK - Make sure the disk isn't set to read only, as we're - // attempting to write to it - if(diskette.read_only) - to_chat(usr,"Disk is set to read only mode.") - return - - var/search_flags = 0 - - switch(params["source"]) - if("occupant") - // GUARD CHECK - Make sure we can modify the occupant before we - // attempt to search them for any given mutation refs. This could - // lead to no search flags being passed to get_mut_by_ref and this - // is intended functionality to prevent any cheese or abuse - if(can_modify_occupant()) - search_flags |= SEARCH_OCCUPANT - if("console") - search_flags |= SEARCH_STORED - - var/bref = params["mutref"] - var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags) - - // GUARD CHECK - This should not be possible. Unexpected result - if(!HM) - return - - var/datum/mutation/human/A = new HM.type() - A.copy_mutation(HM) - diskette.mutations += A - to_chat(usr,"Mutation successfully stored to disk.") - return - - // Completely removes a MUT_EXTRA mutation or mutation with corrupt gene - // sequence from the scanner occupant - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to nullify - if("nullify") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - var/bref = params["mutref"] - var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT) - - // GUARD CHECK - This should not be possible. Unexpected result - if(!HM) - return - - // GUARD CHECK - Nullify should only be used on scrambled or "extra" - // mutations. - if(!HM.scrambled && !(HM.class == MUT_EXTRA)) - return - - scanner_occupant.dna.remove_mutation(HM.type) - return - - // Deletes saved mutation from console buffer. - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to delete - if("delete_console_mut") - var/bref = params["mutref"] - var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_STORED) - - if(HM) - stored_mutations.Remove(HM) - qdel(HM) - - return - - // Deletes saved mutation from disk buffer. - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to delete - if("delete_disk_mut") - // GUARD CHECK - This code shouldn't even be callable without a diskette - // inserted. Unexpected result - if(!diskette) - return - - // GUARD CHECK - Make sure the disk isn't set to read only, as we're - // attempting to write to it (via deletion) - if(diskette.read_only) - to_chat(usr,"Disk is set to read only mode.") - return - - var/bref = params["mutref"] - var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_DISKETTE) - - if(HM) - diskette.mutations.Remove(HM) - qdel(HM) - - return - - // Ejects a stored chromosome from the DNA Console - // ---------------------------------------------------------------------- // - // params["chromo"] - Text string of the chromosome name - if("eject_chromo") - var/chromname = params["chromo"] - - for(var/obj/item/chromosome/CM in stored_chromosomes) - if(chromname == CM.name) - CM.forceMove(drop_location()) - adjust_item_drop_location(CM) - stored_chromosomes -= CM - return - - return - - // Combines two mutations from the console to try and create a new mutation - // ---------------------------------------------------------------------- // - // params["firstref"] - ATOM Ref of first mutation for combination - // params["secondref"] - ATOM Ref of second mutation for combination - // mutation - if("combine_console") - // GUARD CHECK - We're running a research-type operation. If, for some - // reason, somehow the DNA Console has been disconnected from the research - // network - Or was never in it to begin with - don't proceed - if(!stored_research) - return - - var/first_bref = params["firstref"] - var/second_bref = params["secondref"] - - // GUARD CHECK - Find the source and destination mutations on the console - // and make sure they actually exist. - var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE) - if(!source_mut) - return - - var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE) - if(!dest_mut) - return - - // Attempt to mix the two mutations to get a new type - var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type) - - if(!result_path) - return - - // If we got a new type, add it to our storage - stored_mutations += new result_path() - to_chat(usr, "Success! New mutation has been added to console storage.") - - // If it's already discovered, end here. Otherwise, add it to the list of - // discovered mutations. - // We've already checked for stored_research earlier - if(result_path in stored_research.discovered_mutations) - return - - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path) - stored_research.discovered_mutations += result_path - say("Successfully mutated [HM.name].") - return - - // Combines two mutations from the disk to try and create a new mutation - // ---------------------------------------------------------------------- // - // params["firstref"] - ATOM Ref of first mutation for combination - // params["secondref"] - ATOM Ref of second mutation for combination - // mutation - if("combine_disk") - // GUARD CHECK - This code shouldn't even be callable without a diskette - // inserted. Unexpected result - if(!diskette) - return - - // GUARD CHECK - Make sure the disk is not full. - if(LAZYLEN(diskette.mutations) >= diskette.max_mutations) - to_chat(usr,"Disk storage is full.") - return - - // GUARD CHECK - Make sure the disk isn't set to read only, as we're - // attempting to write to it - if(diskette.read_only) - to_chat(usr,"Disk is set to read only mode.") - return - - // GUARD CHECK - We're running a research-type operation. If, for some - // reason, somehow the DNA Console has been disconnected from the research - // network - Or was never in it to begin with - don't proceed - if(!stored_research) - return - - var/first_bref = params["firstref"] - var/second_bref = params["secondref"] - - // GUARD CHECK - Find the source and destination mutations on the console - // and make sure they actually exist. - var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE) - if(!source_mut) - return - - var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE) - if(!dest_mut) - return - - // Attempt to mix the two mutations to get a new type - var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type) - - if(!result_path) - return - - // If we got a new type, add it to our storage - diskette.mutations += new result_path() - to_chat(usr, "Success! New mutation has been added to the disk.") - - // If it's already discovered, end here. Otherwise, add it to the list of - // discovered mutations - // We've already checked for stored_research earlier - if(result_path in stored_research.discovered_mutations) - return - - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path) - stored_research.discovered_mutations += result_path - say("Successfully mutated [HM.name].") - return - - // Sets the Genetic Makeup pulse strength. - // ---------------------------------------------------------------------- // - // params["val"] - New strength value as text string, converted to number - // later on in code - if("set_pulse_strength") - var/value = round(text2num(params["val"])) - radstrength = WRAP(value, 1, RADIATION_STRENGTH_MAX+1) - return - - // Sets the Genetic Makeup pulse duration - // ---------------------------------------------------------------------- // - // params["val"] - New strength value as text string, converted to number - // later on in code - if("set_pulse_duration") - var/value = round(text2num(params["val"])) - radduration = WRAP(value, 1, RADIATION_DURATION_MAX+1) - return - - // Saves Genetic Makeup information to disk - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the console genetic makeup buffer to - // copy to disk - if("save_makeup_disk") - // GUARD CHECK - This code shouldn't even be callable without a diskette - // inserted. Unexpected result - if(!diskette) - return - - // GUARD CHECK - Make sure the disk isn't set to read only, as we're - // attempting to write to it - if(diskette.read_only) - to_chat(usr,"Disk is set to read only mode.") - return - - // Convert the index to a number and clamp within the array range - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - - var/list/buffer_slot = genetic_makeup_buffer[buffer_index] - - // GUARD CHECK - This should not be possible to activate on a buffer slot - // that doesn't have any genetic data. Unexpected result - if(!istype(buffer_slot)) - return - - diskette.genetic_makeup_buffer = buffer_slot.Copy() - return - - // Loads Genetic Makeup from disk to a console buffer - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the console genetic makeup buffer to - // copy to. Expected as text string, converted to number later - if("load_makeup_disk") - // GUARD CHECK - This code shouldn't even be callable without a diskette - // inserted. Unexpected result - if(!diskette) - return - - // GUARD CHECK - This should not be possible to activate on a diskette - // that doesn't have any genetic data. Unexpected result - if(LAZYLEN(diskette.genetic_makeup_buffer) == 0) - return - - // Convert the index to a number and clamp within the array range, then - // copy the data from the disk to that buffer - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - genetic_makeup_buffer[buffer_index] = diskette.genetic_makeup_buffer.Copy() - return - - // Deletes genetic makeup buffer from the inserted diskette - if("del_makeup_disk") - // GUARD CHECK - This code shouldn't even be callable without a diskette - // inserted. Unexpected result - if(!diskette) - return - - // GUARD CHECK - Make sure the disk isn't set to read only, as we're - // attempting to write (via deletion) to it - if(diskette.read_only) - to_chat(usr,"Disk is set to read only mode.") - return - - diskette.genetic_makeup_buffer.Cut() - return - - // Saves the scanner occupant's genetic makeup to a given console buffer - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the console genetic makeup buffer to - // save the new genetic data to. Expected as text string, converted to - // number later - if("save_makeup_console") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - // Convert the index to a number and clamp within the array range, then - // copy the data from the disk to that buffer - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - - // Set the new information - genetic_makeup_buffer[buffer_index] = list( - "label"="Slot [buffer_index]:[scanner_occupant.real_name]", - "UI"=scanner_occupant.dna.uni_identity, - "UE"=scanner_occupant.dna.unique_enzymes, - "name"=scanner_occupant.real_name, - "blood_type"=scanner_occupant.dna.blood_type) - - return - - // Deleted genetic makeup data from a console buffer slot - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the console genetic makeup buffer to - // delete the genetic data from. Expected as text string, converted to - // number later - if("del_makeup_console") - // Convert the index to a number and clamp within the array range, then - // copy the data from the disk to that buffer - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - var/list/buffer_slot = genetic_makeup_buffer[buffer_index] - - // GUARD CHECK - This shouldn't be possible to execute this on a null - // buffer. Unexpected resut - if(!istype(buffer_slot)) - return - - genetic_makeup_buffer[buffer_index] = null - return - - // Eject stored diskette from console - if("eject_disk") - eject_disk(usr) - return - - // Create a Genetic Makeup injector. These injectors are timed and thus are - // only temporary - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the console genetic makeup buffer to - // create the makeup injector from. Expected as text string, converted to - // number later - // params["type"] - Type of injector to create - // Expected results: - // "ue" - Unique Enzyme, changes name and blood type - // "ui" - Unique Identity, changes looks - // "mixed" - Combination of both ue and ui - if("makeup_injector") - // Convert the index to a number and clamp within the array range, then - // copy the data from the disk to that buffer - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - var/list/buffer_slot = genetic_makeup_buffer[buffer_index] - - // GUARD CHECK - This shouldn't be possible to execute this on a null - // buffer. Unexpected resut - if(!istype(buffer_slot)) - return - - var/type = params["type"] - var/obj/item/dnainjector/timed/I - - switch(type) - if("ui") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["UI"]) - to_chat(usr,"Genetic data corrupted, unable to create injector.") - return - - I = new /obj/item/dnainjector/timed(loc) - I.fields = list("UI"=buffer_slot["UI"]) - - // If there is a connected scanner, we can use its upgrades to reduce - // the radiation generated by this injector - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - if("ue") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) - to_chat(usr,"Genetic data corrupted, unable to create injector.") - return - - I = new /obj/item/dnainjector/timed(loc) - I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"]) - - // If there is a connected scanner, we can use its upgrades to reduce - // the radiation generated by this injector - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - if("mixed") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) - to_chat(usr,"Genetic data corrupted, unable to create injector.") - return - - I = new /obj/item/dnainjector/timed(loc) - I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"]) - - // If there is a connected scanner, we can use its upgrades to reduce - // the radiation generated by this injector - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - - // If we successfully created an injector, don't forget to set the new - // ready timer. - if(I) - injectorready = world.time + INJECTOR_TIMEOUT - - return - - // Applies a genetic makeup buffer to the scanner occupant - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the console genetic makeup buffer to - // apply to the scanner occupant. Expected as text string, converted to - // number later - // params["type"] - Type of genetic makeup copy to implement - // Expected results: - // "ue" - Unique Enzyme, changes name and blood type - // "ui" - Unique Identity, changes looks - // "mixed" - Combination of both ue and ui - if("makeup_apply") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - // Convert the index to a number and clamp within the array range, then - // copy the data from the disk to that buffer - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - var/list/buffer_slot = genetic_makeup_buffer[buffer_index] - - // GUARD CHECK - This shouldn't be possible to execute this on a null - // buffer. Unexpected resut - if(!istype(buffer_slot)) - return - - var/type = params["type"] - - apply_genetic_makeup(type, buffer_slot) - return - - // Applies a genetic makeup buffer to the next scanner occupant. This sets - // some code that will run when the connected DNA Scanner door is next - // closed - // This allows people to self-modify their genetic makeup, as tgui - // interfaces can not be accessed while inside the DNA Scanner and genetic - // makeup injectors are only temporary - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the console genetic makeup buffer to - // apply to the scanner occupant. Expected as text string, converted to - // number later - // params["type"] - Type of genetic makeup copy to implement - // Expected results: - // "ue" - Unique Enzyme, changes name and blood type - // "ui" - Unique Identity, changes looks - // "mixed" - Combination of both ue and ui - if("makeup_delay") - // Convert the index to a number and clamp within the array range, then - // copy the data from the disk to that buffer - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - var/list/buffer_slot = genetic_makeup_buffer[buffer_index] - - // GUARD CHECK - This shouldn't be possible to execute this on a null - // buffer. Unexpected resut - if(!istype(buffer_slot)) - return - - var/type = params["type"] - - // Set the delayed action. The next time the scanner door is closed, - // unless this is cancelled in the UI, the action will happen - delayed_action = list("type" = type, "buffer_slot" = buffer_slot) - return - - // Attempts to modify the indexed element of the Unique Identity string - // This is a time delayed action that is handled in process() - // ---------------------------------------------------------------------- // - // params["index"] - The BYOND index of the Unique Identity string to - // attempt to modify - if("makeup_pulse") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - if(!can_modify_occupant()) - return - - // Set the appropriate timer and index to pulse. This is then managed - // later on in process() - var/len = length_char(scanner_occupant.dna.uni_identity) - rad_pulse_timer = world.time + (radduration*10) - rad_pulse_index = WRAP(text2num(params["index"]), 1, len+1) - begin_processing() - return - - // Cancels the delayed action - In this context it is not the radiation - // pulse from "makeup_pulse", which can not be cancelled. It is instead - // the delayed genetic transfer from "makeup_delay" - if("cancel_delay") - delayed_action = null - return - - // Creates a new advanced injector storage buffer in the console - // ---------------------------------------------------------------------- // - // params["name"] - The name to apply to the new injector - if("new_adv_inj") - // GUARD CHECK - Make sure we can make a new injector. This code should - // not be called if we're already maxed out and this is an Unexpected - // result - if(!(LAZYLEN(injector_selection) < max_injector_selections)) - return - - // GUARD CHECK - Sanitise and trim the proposed name. This prevents HTML - // injection and equivalent as tgui input is not stripped - var/inj_name = params["name"] - inj_name = trim(sanitize(inj_name)) - - // GUARD CHECK - If the name is null or blank, or the name is already in - // the list of advanced injectors, we want to reject it as we can't have - // duplicate named advanced injectors - if(!inj_name || (inj_name in injector_selection)) - return - - injector_selection[inj_name] = list() - return - - // Deleted an advanced injector storage buffer from the console - // ---------------------------------------------------------------------- // - // params["name"] - The name of the injector to delete - if("del_adv_inj") - var/inj_name = params["name"] - - // GUARD CHECK - If the name is null or blank, reject. - // GUARD CHECK - If the name isn't in the list of advanced injectors, we - // want to reject this as it shouldn't be possible ever do this. - // Unexpected result - if(!inj_name || !(inj_name in injector_selection)) - return - - injector_selection.Remove(inj_name) - return - - // Creates an injector from an advanced injector buffer - // ---------------------------------------------------------------------- // - // params["name"] - The name of the injector to print - if("print_adv_inj") - // As a side note, because mutations can contain unique metadata, - // this system uses BYOND Atom Refs to safely and accurately - // identify mutations from big ol' lists. - - // GUARD CHECK - Is the injector actually ready? - if(world.time < injectorready) - return - - var/inj_name = params["name"] - - // GUARD CHECK - If the name is null or blank, reject. - // GUARD CHECK - If the name isn't in the list of advanced injectors, we - // want to reject this as it shouldn't be possible ever do this. - // Unexpected result - if(!inj_name || !(inj_name in injector_selection)) - return - - var/list/injector = injector_selection[inj_name] - var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc) - - // Run through each mutation in our Advanced Injector and add them to a - // new injector - for(var/A in injector) - var/datum/mutation/human/HM = A - I.add_mutations += new HM.type(copymut=HM) - - // Force apply any mutations, this is functionality similar to mutators - I.doitanyway = TRUE - I.name = "Advanced [inj_name] injector" - - // If there's an operational connected scanner, we can use its upgrades - // to improve our injector's radiation generation - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected_scanner.precision_coeff) - else - injectorready = world.time + INJECTOR_TIMEOUT * 8 - - return - - // Adds a mutation to an advanced injector - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to add to the injector - // params["advinj"] - Name of the advanced injector to add the mutation to - if("add_advinj_mut") - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - // This is needed because this operation can only be completed from the - // genetic sequencer. - if(!can_modify_occupant()) - return - - var/adv_inj = params["advinj"] - - // GUARD CHECK - Make sure our advanced injector actually exists. This - // should not be possible. Unexpected result - if(!(adv_inj in injector_selection)) - return - - // GUARD CHECK - Make sure we limit the number of mutations appropriately - if(LAZYLEN(injector_selection[adv_inj]) >= max_injector_mutations) - to_chat(usr,"Advanced injector mutation storage is full.") - return - - var/mut_source = params["source"] - var/search_flag = 0 - - switch(mut_source) - if("disk") - search_flag = SEARCH_DISKETTE - if("occupant") - search_flag = SEARCH_OCCUPANT - if("console") - search_flag = SEARCH_STORED - - if(!search_flag) - return - - var/bref = params["mutref"] - // We've already made sure we can modify the occupant, so this is safe to - // call - var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flag) - - // GUARD CHECK - This should not be possible. Unexpected result - if(!HM) - return - - // We want to make sure we stick within the instability limit. - // We start with the instability of the mutation we're intending to add. - var/instability_total = HM.instability - - // We then add the instabilities of all other mutations in the injector, - // remembering to apply the Stabilizer chromosome modifiers - for(var/datum/mutation/human/I in injector_selection[adv_inj]) - instability_total += I.instability * GET_MUTATION_STABILIZER(I) - - // If this would take us over the max instability, we inform the user. - if(instability_total > max_injector_instability) - to_chat(usr,"Extra mutation would make the advanced injector too instable.") - return - - // If we've got here, all our checks are passed and we can successfully - // add the mutation to the advanced injector. - var/datum/mutation/human/A = new HM.type() - A.copy_mutation(HM) - injector_selection[adv_inj] += A - to_chat(usr,"Mutation successfully added to advanced injector.") - return - - // Deletes a mutation from an advanced injector - // ---------------------------------------------------------------------- // - // params["mutref"] - ATOM Ref of specific mutation to del from the injector - if("delete_injector_mut") - var/bref = params["mutref"] - - var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_ADV_INJ) - - // GUARD CHECK - This should not be possible. Unexpected result - if(!HM) - return - - // Check Advanced Injectors to find and remove the mutation - for(var/I in injector_selection) - if(injector_selection["[I]"].Remove(HM)) - qdel(HM) - return - - return - - // Sets a new tgui view state - // ---------------------------------------------------------------------- // - // params["id"] - Key for the state to set - // params[...] - Every other element is used to set state variables - if("set_view") - for (var/key in params) - if(key == "src") - continue - tgui_view_state[key] = params[key] - return TRUE - return FALSE - -/** - * Applies the enzyme buffer to the current scanner occupant - * - * Applies the type of a specific genetic makeup buffer to the current scanner - * occupant - * - * Arguments: - * * type - "ui"/"ue"/"mixed" - Which part of the enzyme buffer to apply - * * buffer_slot - Index of the enzyme buffer to apply - */ -/obj/machinery/computer/scan_consolenew/proc/apply_genetic_makeup(type, buffer_slot) - // Note - This proc is only called from code that has already performed the - // necessary occupant guard checks. If you call this code yourself, please - // apply can_modify_occupant() or equivalent checks first. - - // Pre-calc the rad increase since we'll be using it in all the possible - // operations - var/rad_increase = rand(100/(connected_scanner.damage_coeff ** 2),250/(connected_scanner.damage_coeff ** 2)) - - switch(type) - if("ui") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["UI"]) - to_chat(usr,"Genetic data corrupted, unable to apply genetic data.") - return FALSE - scanner_occupant.dna.uni_identity = buffer_slot["UI"] - scanner_occupant.updateappearance(mutations_overlay_update=1) - scanner_occupant.radiation += rad_increase - scanner_occupant.domutcheck() - return TRUE - if("ue") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) - to_chat(usr,"Genetic data corrupted, unable to apply genetic data.") - return FALSE - scanner_occupant.real_name = buffer_slot["name"] - scanner_occupant.name = buffer_slot["name"] - scanner_occupant.dna.unique_enzymes = buffer_slot["UE"] - scanner_occupant.dna.blood_type = buffer_slot["blood_type"] - scanner_occupant.radiation += rad_increase - scanner_occupant.domutcheck() - return TRUE - if("mixed") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) - to_chat(usr,"Genetic data corrupted, unable to apply genetic data.") - return FALSE - scanner_occupant.dna.uni_identity = buffer_slot["UI"] - scanner_occupant.updateappearance(mutations_overlay_update=1) - scanner_occupant.real_name = buffer_slot["name"] - scanner_occupant.name = buffer_slot["name"] - scanner_occupant.dna.unique_enzymes = buffer_slot["UE"] - scanner_occupant.dna.blood_type = buffer_slot["blood_type"] - scanner_occupant.radiation += rad_increase - scanner_occupant.domutcheck() - return TRUE - - return FALSE -/** - * Checks if there is a connected DNA Scanner that is operational - */ -/obj/machinery/computer/scan_consolenew/proc/scanner_operational() - if(!connected_scanner) - return FALSE - - return (connected_scanner && connected_scanner.is_operational()) - -/** - * Checks if there is a valid DNA Scanner occupant for genetic modification - * - * Checks if there is a valid subject in the DNA Scanner that can be genetically - * modified. Will set the scanner occupant var as part of this check. - * Requires that the scanner can be operated and will return early if it can't - */ -/obj/machinery/computer/scan_consolenew/proc/can_modify_occupant() - // GUARD CHECK - We always want to perform the scanner operational check as - // part of checking if we can modify the occupant. - // We can never modify the occupant of a broken scanner. - if(!scanner_operational()) - return FALSE - - if(!connected_scanner.occupant) - return FALSE - - scanner_occupant = connected_scanner.occupant - - // Check validity of occupent for DNA Modification - // DNA Modification: - // requires DNA - // this DNA can not be bad - // is done via radiation bursts, so radiation immune carbons are not viable - // And the DNA Scanner itself must have a valid scan level - if(scanner_occupant.has_dna() && !HAS_TRAIT(scanner_occupant, TRAIT_GENELESS) && !HAS_TRAIT(scanner_occupant, TRAIT_BADDNA) || (connected_scanner.scan_level == 3)) - return TRUE - - return FALSE - -/** - * Checks for adjacent DNA scanners and connects when it finds a viable one - * - * Seearches cardinal directions in order. Stops when it finds a viable DNA Scanner. - * Will connect to a broken scanner if no functional scanner is available. - * Links itself to the DNA Scanner to receive door open and close events. - */ -/obj/machinery/computer/scan_consolenew/proc/connect_to_scanner() - var/obj/machinery/dna_scannernew/test_scanner = null - var/obj/machinery/dna_scannernew/broken_scanner = null - - // Look in each cardinal direction and try and find a DNA Scanner - // If you find a DNA Scanner, check to see if it broken or working - // If it's working, set the current scanner and return early - // If it's not working, remember it anyway as a broken scanner - for(var/direction in GLOB.cardinals) - test_scanner = locate(/obj/machinery/dna_scannernew, get_step(src, direction)) - if(!isnull(test_scanner)) - if(test_scanner.is_operational()) - connected_scanner = test_scanner - connected_scanner.linked_console = src - return - else - broken_scanner = test_scanner - - // Ultimately, if we have a broken scanner, we'll attempt to connect to it as - // a fallback case, but the code above will prefer a working scanner - if(!isnull(broken_scanner)) - connected_scanner = broken_scanner - connected_scanner.linked_console = src - -/** - * Called by connected DNA Scanners when their doors close. - * - * Sets the new scanner occupant and completes delayed enzyme transfer if one - * is queued. - */ -/obj/machinery/computer/scan_consolenew/proc/on_scanner_close() - // Set the appropriate occupant now the scanner is closed - if(connected_scanner.occupant) - scanner_occupant = connected_scanner.occupant - else - scanner_occupant = null - - // If we have a delayed action - In this case the only delayed action is - // applying a genetic makeup buffer the next time the DNA Scanner is closed - - // we want to perform it. - // GUARD CHECK - Make sure we can modify the occupant, apply_genetic_makeup() - // assumes we've already done this. - if(delayed_action && can_modify_occupant()) - var/type = delayed_action["type"] - var/buffer_slot = delayed_action["buffer_slot"] - if(apply_genetic_makeup(type, buffer_slot)) - to_chat(connected_scanner.occupant, "[src] activates!") - delayed_action = null - -/** - * Called by connected DNA Scanners when their doors open. - * - * Clears enzyme pulse operations, stops processing and nulls the current - * scanner occupant var. - */ -/obj/machinery/computer/scan_consolenew/proc/on_scanner_open() - // If we had a radiation pulse action ongoing, we want to stop this. - // Imagine it being like a microwave stopping when you open the door. - rad_pulse_index = 0 - rad_pulse_timer = 0 - end_processing() - scanner_occupant = null - -/** - * Builds the genetic makeup list which will be sent to tgui interface. - */ -/obj/machinery/computer/scan_consolenew/proc/build_genetic_makeup_list() - // No code will ever null this list, we can safely Cut it. - tgui_genetic_makeup.Cut() - - for(var/i=1, i <= NUMBER_OF_BUFFERS, i++) - if(genetic_makeup_buffer[i]) - tgui_genetic_makeup["[i]"] = genetic_makeup_buffer[i].Copy() - else - tgui_genetic_makeup["[i]"] = null - -/** - * Builds the genetic makeup list which will be sent to tgui interface. - * - * Will iterate over the connected scanner occupant, DNA Console, inserted - * diskette and chromosomes and any advanced injectors, building the main data - * structures which get passed to the tgui interface. - */ -/obj/machinery/computer/scan_consolenew/proc/build_mutation_list(can_modify_occ) - // No code will ever null these lists. We can safely Cut them. - tgui_occupant_mutations.Cut() - tgui_diskette_mutations.Cut() - tgui_console_mutations.Cut() - tgui_console_chromosomes.Cut() - tgui_advinjector_mutations.Cut() - - // ------------------------------------------------------------------------ // - // GUARD CHECK - Can we genetically modify the occupant? This check will have - // previously included checks to make sure the DNA Scanner is still - // operational - if(can_modify_occ) - // ---------------------------------------------------------------------- // - // Start cataloguing all mutations that the occupant has by default - for(var/mutation_type in scanner_occupant.dna.mutation_index) - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type) - - var/list/mutation_data = list() - var/text_sequence = scanner_occupant.dna.mutation_index[mutation_type] - var/default_sequence = scanner_occupant.dna.default_mutation_genes[mutation_type] - var/discovered = (stored_research && (mutation_type in stored_research.discovered_mutations)) - - mutation_data["Alias"] = HM.alias - mutation_data["Sequence"] = text_sequence - mutation_data["DefaultSeq"] = default_sequence - mutation_data["Discovered"] = discovered - mutation_data["Source"] = "occupant" - - // We only want to pass this information along to the tgui interface if - // the mutation has been discovered. Prevents people being able to cheese - // or "hack" their way to figuring out what undiscovered mutations are - if(discovered) - mutation_data["Name"] = HM.name - mutation_data["Description"] = HM.desc - mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) - mutation_data["Quality"] = HM.quality - - // Assume the mutation is normal unless assigned otherwise. - var/mut_class = MUT_NORMAL - - // Check if the mutation is currently activated. If it is, we can add even - // MORE information to send to tgui. - var/datum/mutation/human/A = scanner_occupant.dna.get_mutation(mutation_type) - if(A) - mutation_data["Active"] = TRUE - mutation_data["Scrambled"] = A.scrambled - mutation_data["Class"] = A.class - mut_class = A.class - mutation_data["CanChromo"] = A.can_chromosome - mutation_data["ByondRef"] = REF(A) - mutation_data["Type"] = A.type - if(A.can_chromosome) - mutation_data["ValidChromos"] = jointext(A.valid_chrom_list, ", ") - mutation_data["AppliedChromo"] = A.chromosome_name - mutation_data["ValidStoredChromos"] = build_chrom_list(A) - else - mutation_data["Active"] = FALSE - mutation_data["Scrambled"] = FALSE - mutation_data["Class"] = MUT_NORMAL - - // Technically NONE of these mutations should be MUT_EXTRA but this will - // catch any weird edge cases - // Assign icons by priority - MUT_EXTRA will ALSO be discovered, so it - // has a higher priority for icon/image assignment - if (mut_class == MUT_EXTRA) - mutation_data["Image"] = "dna_extra.gif" - else if(discovered) - mutation_data["Image"] = "dna_discovered.gif" - else - mutation_data["Image"] = "dna_undiscovered.gif" - - tgui_occupant_mutations += list(mutation_data) - - // ---------------------------------------------------------------------- // - // Now get additional/"extra" mutations that they shouldn't have by default - for(var/datum/mutation/human/HM in scanner_occupant.dna.mutations) - // If it's in the mutation index array, we've already catalogued this - // mutation and can safely skip over it. It really shouldn't be, but this - // will catch any weird edge cases - if(HM.type in scanner_occupant.dna.mutation_index) - continue - - var/list/mutation_data = list() - var/text_sequence = GET_SEQUENCE(HM.type) - - // These will all be active mutations. They're added by injector and their - // sequencing code can't be changed. They can only be nullified, which - // completely removes them. - var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) - - mutation_data["Alias"] = A.alias - mutation_data["Sequence"] = text_sequence - mutation_data["Discovered"] = TRUE - mutation_data["Quality"] = HM.quality - mutation_data["Source"] = "occupant" - - mutation_data["Name"] = HM.name - mutation_data["Description"] = HM.desc - mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) - - mutation_data["Active"] = TRUE - mutation_data["Scrambled"] = HM.scrambled - mutation_data["Class"] = HM.class - mutation_data["CanChromo"] = HM.can_chromosome - mutation_data["ByondRef"] = REF(HM) - mutation_data["Type"] = HM.type - - if(HM.can_chromosome) - mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ") - mutation_data["AppliedChromo"] = HM.chromosome_name - mutation_data["ValidStoredChromos"] = build_chrom_list(HM) - - // Nothing in this list should be undiscovered. Technically nothing - // should be anything but EXTRA. But we're just handling some edge cases. - if (HM.class == MUT_EXTRA) - mutation_data["Image"] = "dna_extra.gif" - else - mutation_data["Image"] = "dna_discovered.gif" - - tgui_occupant_mutations += list(mutation_data) - - // ------------------------------------------------------------------------ // - // Build the list of mutations stored within the DNA Console - for(var/datum/mutation/human/HM in stored_mutations) - var/list/mutation_data = list() - - var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) - - mutation_data["Alias"] = A.alias - mutation_data["Name"] = HM.name - mutation_data["Source"] = "console" - mutation_data["Active"] = TRUE - mutation_data["Description"] = HM.desc - mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) - mutation_data["ByondRef"] = REF(HM) - mutation_data["Type"] = HM.type - - mutation_data["CanChromo"] = HM.can_chromosome - if(HM.can_chromosome) - mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ") - mutation_data["AppliedChromo"] = HM.chromosome_name - mutation_data["ValidStoredChromos"] = build_chrom_list(HM) - - tgui_console_mutations += list(mutation_data) - - // ------------------------------------------------------------------------ // - // Build the list of chromosomes stored within the DNA Console - var/chrom_index = 1 - for(var/obj/item/chromosome/CM in stored_chromosomes) - var/list/chromo_data = list() - - chromo_data["Name"] = CM.name - chromo_data["Description"] = CM.desc - chromo_data["Index"] = chrom_index - - tgui_console_chromosomes += list(chromo_data) - ++chrom_index - - // ------------------------------------------------------------------------ // - // Build the list of mutations stored on any inserted diskettes - if(diskette) - for(var/datum/mutation/human/HM in diskette.mutations) - var/list/mutation_data = list() - - var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) - - mutation_data["Alias"] = A.alias - mutation_data["Name"] = HM.name - mutation_data["Active"] = TRUE - //mutation_data["Sequence"] = GET_SEQUENCE(HM.type) - mutation_data["Source"] = "disk" - mutation_data["Description"] = HM.desc - mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) - mutation_data["ByondRef"] = REF(HM) - mutation_data["Type"] = HM.type - - mutation_data["CanChromo"] = HM.can_chromosome - if(HM.can_chromosome) - mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ") - mutation_data["AppliedChromo"] = HM.chromosome_name - mutation_data["ValidStoredChromos"] = build_chrom_list(HM) - - tgui_diskette_mutations += list(mutation_data) - - // ------------------------------------------------------------------------ // - // Build the list of mutations stored within any Advanced Injectors - if(LAZYLEN(injector_selection)) - for(var/I in injector_selection) - var/list/mutations = list() - for(var/datum/mutation/human/HM in injector_selection[I]) - var/list/mutation_data = list() - - var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) - - mutation_data["Alias"] = A.alias - mutation_data["Name"] = HM.name - mutation_data["Active"] = TRUE - //mutation_data["Sequence"] = GET_SEQUENCE(HM.type) - mutation_data["Source"] = "injector" - mutation_data["Description"] = HM.desc - mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) - mutation_data["ByondRef"] = REF(HM) - mutation_data["Type"] = HM.type - - if(HM.can_chromosome) - mutation_data["AppliedChromo"] = HM.chromosome_name - - mutations += list(mutation_data) - tgui_advinjector_mutations += list(list( - "name" = "[I]", - "mutations" = mutations, - )) - -/** - * Takes any given chromosome and calculates chromosome compatibility - * - * Will iterate over the stored chromosomes in the DNA Console and will check - * whether it can be applied to the supplied mutation. Then returns a list of - * names of chromosomes that were compatible. - * - * Arguments: - * * mutation - The mutation to check chromosome compatibility with - */ -/obj/machinery/computer/scan_consolenew/proc/build_chrom_list(mutation) - var/list/chromosomes = list() - - for(var/obj/item/chromosome/CM in stored_chromosomes) - if(CM.can_apply(mutation)) - chromosomes += CM.name - - return chromosomes - -/** - * Checks whether a mutation alias has been discovered - * - * Checks whether a given mutation's genetic sequence has been completed and - * discovers it if appropriate - * - * Arguments: - * * alias - Alias of the mutation to check (ie "Mutation 51" or "Mutation 12") - */ -/obj/machinery/computer/scan_consolenew/proc/check_discovery(alias) - // Note - All code paths that call this have already done checks on the - // current occupant to prevent cheese and other abuses. If you call this - // proc please also do the following checks first: - // if(!can_modify_occupant()) - // return - // if(!(scanner_occupant == connected_scanner.occupant)) - // return - - // Turn the alias ("Mutation 1", "Mutation 35") into a mutation path - var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias) - - // Check to see if this mutation is in the active mutation list. If it isn't, - // then the mutation isn't eligible for discovery. If it is but is scrambled, - // then the mutation isn't eligible for discovery. Finally, check if the - // mutation is in discovered mutations - If it isn't, add it to discover. - var/datum/mutation/human/M = scanner_occupant.dna.get_mutation(path) - if(!M) - return FALSE - if(M.scrambled) - return FALSE - if(stored_research && !(path in stored_research.discovered_mutations)) - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(path) - stored_research.discovered_mutations += path - say("Successfully discovered [HM.name].") - return TRUE - - return FALSE - -/** - * Find a mutation from various storage locations via ATOM ref - * - * Takes an ATOM Ref and searches the appropriate mutation buffers and storage - * vars to try and find the associated mutation. - * - * Arguments: - * * ref - ATOM ref of the mutation to locate - * * target_flags - Flags for storage mediums to search, see #defines - */ -/obj/machinery/computer/scan_consolenew/proc/get_mut_by_ref(ref, target_flags) - var/mutation - - // Assume the occupant is valid and the check has been carried out before - // calling this proc with the relevant flags. - if(target_flags & SEARCH_OCCUPANT) - mutation = (locate(ref) in scanner_occupant.dna.mutations) - if(mutation) - return mutation - - if(target_flags & SEARCH_STORED) - mutation = (locate(ref) in stored_mutations) - if(mutation) - return mutation - - if(diskette && (target_flags & SEARCH_DISKETTE)) - mutation = (locate(ref) in diskette.mutations) - if(mutation) - return mutation - - if(injector_selection && (target_flags & SEARCH_ADV_INJ)) - for(var/I in injector_selection) - mutation = (locate(ref) in injector_selection["[I]"]) - if(mutation) - return mutation - - return null - -/** - * Creates a randomised accuracy value for the enzyme pulse functionality. - * - * Donor code from previous DNA Console iteration. - * - * Arguments: - * * position - Index of the intended enzyme element to pulse - * * radduration - Duration of intended radiation pulse - * * number_of_blocks - Number of individual data blocks in the pulsed enzyme - */ -/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks) - var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + position, 1) - return WRAP(val, 1, number_of_blocks+1) - -/** - * Scrambles an enzyme element value for the enzyme pulse functionality. - * - * Donor code from previous DNA Console iteration. - * - * Arguments: - * * input - Enzyme identity element to scramble, expected hex value - * * rs - Strength of radiation pulse, increases the range of possible outcomes - */ -/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs) - var/length = length(input) - var/ran = gaussian(0, rs*RADIATION_STRENGTH_MULTIPLIER) - if(ran == 0) - ran = pick(-1,1) //hacky, statistically should almost never happen. 0-chance makes people mad though - else if(ran < 0) - ran = round(ran) //negative, so floor it - else - ran = -round(-ran) //positive, so ceiling it - return num2hex(WRAP(hex2num(input)+ran, 0, 16**length), length) - - /** - * Performs the enzyme radiation pulse. - * - * Donor code from previous DNA Console iteration. Called from process() when - * there is a radiation pulse in progress. Ends processing. - */ -/obj/machinery/computer/scan_consolenew/proc/rad_pulse() - // GUARD CHECK - Can we genetically modify the occupant? Includes scanner - // operational guard checks. - // If we can't, abort the procedure. - if(!can_modify_occupant()) - rad_pulse_index = 0 - end_processing() - return - - var/len = length_char(scanner_occupant.dna.uni_identity) - var/num = randomize_radiation_accuracy(rad_pulse_index, radduration + (connected_scanner.precision_coeff ** 2), len) //Each manipulator level above 1 makes randomization as accurate as selected time + manipulator lvl^2 //Value is this high for the same reason as with laser - not worth the hassle of upgrading if the bonus is low - var/hex = copytext_char(scanner_occupant.dna.uni_identity, num, num+1) - hex = scramble(hex, radstrength, radduration) - - scanner_occupant.dna.uni_identity = copytext_char(scanner_occupant.dna.uni_identity, 1, num) + hex + copytext_char(scanner_occupant.dna.uni_identity, num + 1) - scanner_occupant.updateappearance(mutations_overlay_update=1) - - rad_pulse_index = 0 - end_processing() - return - -/** - * Sets the default state for the tgui interface. - */ -/obj/machinery/computer/scan_consolenew/proc/set_default_state() - tgui_view_state["consoleMode"] = "storage" - tgui_view_state["storageMode"] = "console" - tgui_view_state["storageConsSubMode"] = "mutations" - tgui_view_state["storageDiskSubMode"] = "mutations" - -/** - * Ejects the DNA Disk from the console. - * - * Will insert into the user's hand if possible, otherwise will drop it at the - * console's location. - * - * Arguments: - * * user - The mob that is attempting to eject the diskette. - */ -/obj/machinery/computer/scan_consolenew/proc/eject_disk(mob/user) - // Check for diskette. - if(!diskette) - return - - to_chat(user, "You eject [diskette] from [src].") - - // Reset the state to console storage. - tgui_view_state["storageMode"] = "console" - - // If the disk shouldn't pop into the user's hand for any reason, drop it on the console instead. - if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(diskette)) - diskette.forceMove(drop_location()) - diskette = null - -#undef INJECTOR_TIMEOUT -#undef NUMBER_OF_BUFFERS -#undef SCRAMBLE_TIMEOUT -#undef JOKER_TIMEOUT -#undef JOKER_UPGRADE - -#undef RADIATION_STRENGTH_MAX -#undef RADIATION_STRENGTH_MULTIPLIER - -#undef RADIATION_DURATION_MAX -#undef RADIATION_ACCURACY_MULTIPLIER - -#undef RADIATION_IRRADIATION_MULTIPLIER - -#undef STATUS_TRANSFORMING - -#undef SEARCH_OCCUPANT -#undef SEARCH_STORED -#undef SEARCH_DISKETTE -#undef SEARCH_ADV_INJ +/// Base timeout for creating mutation activators and other injectors +#define INJECTOR_TIMEOUT 100 +/// Maximum number of genetic makeup storage slots in DNA Console +#define NUMBER_OF_BUFFERS 3 +/// Timeout for DNA Scramble in DNA Consoles +#define SCRAMBLE_TIMEOUT 600 +/// Timeout for using the Joker feature to solve a gene in DNA Console +#define JOKER_TIMEOUT 12000 +/// How much time DNA Scanner upgrade tiers remove from JOKER_TIMEOUT +#define JOKER_UPGRADE 3000 + +/// Maximum value for radiaton strength when pulsing enzymes +#define RADIATION_STRENGTH_MAX 15 +/// Larger multipliers will affect the range of values when pulsing enzymes +#define RADIATION_STRENGTH_MULTIPLIER 1 + +/// Maximum value for the radiation pulse duration when pulsing enzymes +#define RADIATION_DURATION_MAX 30 +/// Large values reduce pulse accuracy and may pulse other enzymes than selected +#define RADIATION_ACCURACY_MULTIPLIER 3 + +/// Special status indicating a scanner occupant is transforming eg. from monkey to human +#define STATUS_TRANSFORMING 4 + +/// Multiplier for how much radiation received from DNA Console functionality +#define RADIATION_IRRADIATION_MULTIPLIER 1 + +/// Flag for the mutation ref search system. Search will include scanner occupant +#define SEARCH_OCCUPANT 1 +/// Flag for the mutation ref search system. Search will include console storage +#define SEARCH_STORED 2 +/// Flag for the mutation ref search system. Search will include diskette storage +#define SEARCH_DISKETTE 4 +/// Flag for the mutation ref search system. Search will include advanced injector mutations +#define SEARCH_ADV_INJ 8 + +/obj/machinery/computer/scan_consolenew + name = "DNA Console" + desc = "Scan DNA." + icon_screen = "dna" + icon_keyboard = "med_key" + density = TRUE + circuit = /obj/item/circuitboard/computer/scan_consolenew + + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 400 + light_color = LIGHT_COLOR_BLUE + + /// Link to the techweb's stored research. Used to retrieve stored mutations + var/datum/techweb/stored_research + /// Duration for enzyme radiation pulses + var/radduration = 2 + /// Strength for enzyme radiation pulses + var/radstrength = 1 + /// Maximum number of enzymes we can store + var/list/genetic_makeup_buffer[NUMBER_OF_BUFFERS] + /// List of all mutations stored on the DNA Console + var/list/stored_mutations = list() + /// List of all chromosomes stored in the DNA Console + var/list/stored_chromosomes = list() + /// Assoc list of all advanced injectors. Keys are injector names. Values are lists of mutations. + var/list/list/injector_selection = list() + /// Maximum number of advanced injectors that DNA Consoles store + var/max_injector_selections = 2 + /// Maximum number of mutation that an advanced injector can store + var/max_injector_mutations = 10 + /// Maximum total instability of all combined mutations allowed on an advanced injector + var/max_injector_instability = 50 + + /// World time when injectors are ready to be printed + var/injectorready = 0 + /// World time when JOKER algorithm can be used in DNA Consoles + var/jokerready = 0 + /// World time when Scramble can be used in DNA Consoles + var/scrambleready = 0 + + /// Currently stored genetic data diskette + var/obj/item/disk/data/diskette = null + + /// Current delayed action, used for delayed enzyme transfer on scanner door close + var/list/delayed_action = null + + /// Index of the enzyme being modified during delayed enzyme pulse operations + var/rad_pulse_index = 0 + /// World time when the enzyme pulse should complete + var/rad_pulse_timer = 0 + + /// Used for setting tgui data - Whether the connected DNA Scanner is usable + var/can_use_scanner = FALSE + /// Used for setting tgui data - Whether the current DNA Scanner occupant is viable for genetic modification + var/is_viable_occupant = FALSE + /// Used for setting tgui data - Whether Scramble DNA is ready + var/is_scramble_ready = FALSE + /// Used for setting tgui data - Whether JOKER algorithm is ready + var/is_joker_ready = FALSE + /// Used for setting tgui data - Whether injectors are ready to be printed + var/is_injector_ready = FALSE + /// Used for setting tgui data - Wheher an enzyme pulse operation is ongoing + var/is_pulsing_rads = FALSE + /// Used for setting tgui data - Time until scramble is ready + var/time_to_scramble = 0 + /// Used for setting tgui data - Time until joker is ready + var/time_to_joker = 0 + /// Used for setting tgui data - Time until injectors are ready + var/time_to_injector = 0 + /// Used for setting tgui data - Time until the enzyme pulse is complete + var/time_to_pulse = 0 + + /// Currently connected DNA Scanner + var/obj/machinery/dna_scannernew/connected_scanner = null + /// Current DNA Scanner occupant + var/mob/living/carbon/scanner_occupant = null + + /// Used for setting tgui data - List of occupant mutations + var/list/tgui_occupant_mutations = list() + /// Used for setting tgui data - List of DNA Console stored mutations + var/list/tgui_console_mutations = list() + /// Used for setting tgui data - List of diskette stored mutations + var/list/tgui_diskette_mutations = list() + /// Used for setting tgui data - List of DNA Console chromosomes + var/list/tgui_console_chromosomes = list() + /// Used for setting tgui data - List of occupant mutations + var/list/tgui_genetic_makeup = list() + /// Used for setting tgui data - List of occupant mutations + var/list/tgui_advinjector_mutations = list() + + + /// State of tgui view, i.e. which tab is currently active, or which genome we're currently looking at. + var/list/list/tgui_view_state = list() + +/obj/machinery/computer/scan_consolenew/process() + . = ..() + + // This is for pulsing the UI element with radiation as part of genetic makeup + // If rad_pulse_index > 0 then it means we're attempting a rad pulse + if((rad_pulse_index > 0) && (rad_pulse_timer <= world.time)) + rad_pulse() + return + +/obj/machinery/computer/scan_consolenew/attackby(obj/item/I, mob/user, params) + // Store chromosomes in the console if there's room + if (istype(I, /obj/item/chromosome)) + I.forceMove(src) + stored_chromosomes += I + to_chat(user, "You insert [I].") + return + + // Insert data disk if console disk slot is empty + // Swap data disk if there is one already a disk in the console + if (istype(I, /obj/item/disk/data)) //INSERT SOME DISKETTES + // Insert disk into DNA Console + if (!user.transferItemToLoc(I,src)) + return + // If insertion was successful and there's already a diskette in the console, eject the old one. + if(diskette) + eject_disk(user) + // Set the new diskette. + diskette = I + to_chat(user, "You insert [I].") + return + + // Recycle non-activator used injectors + // Turn activator used injectors (aka research injectors) to chromosomes + if(istype(I, /obj/item/dnainjector/activator)) + var/obj/item/dnainjector/activator/A = I + if(A.used) + to_chat(user,"Recycled [I].") + if(A.research) + if(prob(60)) + var/c_typepath = generate_chromosome() + var/obj/item/chromosome/CM = new c_typepath (src) + stored_chromosomes += CM + to_chat(user,"[capitalize(CM.name)] added to storage.") + else + to_chat(user, "There was not enough genetic data to extract a viable chromosome.") + qdel(I) + return + + return ..() + + +/obj/machinery/computer/scan_consolenew/AltClick(mob/user) + // Make sure the user can interact with the machine. + if(!user.canUseTopic(src, !issilicon(user))) + return + + eject_disk(user) + +/obj/machinery/computer/scan_consolenew/Initialize() + . = ..() + + // Connect with a nearby DNA Scanner on init + connect_to_scanner() + + // Set appropriate ready timers and limits for machines functions + injectorready = world.time + INJECTOR_TIMEOUT + scrambleready = world.time + SCRAMBLE_TIMEOUT + jokerready = world.time + JOKER_TIMEOUT + + // Set the default tgui state + set_default_state() + + // Link machine with research techweb. Used for discovering and accessing + // already discovered mutations + stored_research = SSresearch.science_tech + +/obj/machinery/computer/scan_consolenew/examine(mob/user) + . = ..() + +/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + . = ..() + + // Most of ui_interact is spent setting variables for passing to the tgui + // interface. + // We can also do some general state processing here too as it's a good + // indication that a player is using the console. + + var/scanner_op = scanner_operational() + var/can_modify_occ = can_modify_occupant() + + // Check for connected AND operational scanner. + if(scanner_op) + can_use_scanner = TRUE + else + can_use_scanner = FALSE + connected_scanner = null + is_viable_occupant = FALSE + + // Check for a viable occupant in the scanner. + if(can_modify_occ) + is_viable_occupant = TRUE + else + is_viable_occupant = FALSE + + + // Populates various buffers for passing to tgui + build_mutation_list(can_modify_occ) + build_genetic_makeup_list() + + // Populate variables for passing to tgui interface + is_scramble_ready = (scrambleready < world.time) + time_to_scramble = round((scrambleready - world.time)/10) + + is_joker_ready = (jokerready < world.time) + time_to_joker = round((jokerready - world.time)/10) + + is_injector_ready = (injectorready < world.time) + time_to_injector = round((injectorready - world.time)/10) + + is_pulsing_rads = ((rad_pulse_index > 0) && (rad_pulse_timer > world.time)) + time_to_pulse = round((rad_pulse_timer - world.time)/10) + + // Attempt to update tgui ui, open and update if needed. + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + + if(!ui) + ui = new(user, src, ui_key, "DnaConsole", name, 539, 710, master_ui, state) + ui.open() + +/obj/machinery/computer/scan_consolenew/ui_data(mob/user) + var/list/data = list() + + data["view"] = tgui_view_state + data["storage"] = list() + + // This block of code generates the huge data structure passed to the tgui + // interface for displaying all the various bits of console/scanner data + // Should all be very self-explanatory + data["isScannerConnected"] = can_use_scanner + if(can_use_scanner) + data["scannerOpen"] = connected_scanner.state_open + data["scannerLocked"] = connected_scanner.locked + data["radStrength"] = radstrength + data["radDuration"] = radduration + data["stdDevStr"] = radstrength * RADIATION_STRENGTH_MULTIPLIER + switch(RADIATION_ACCURACY_MULTIPLIER / (radduration + (connected_scanner.precision_coeff ** 2))) //hardcoded values from a z-table for a normal distribution + if(0 to 0.25) + data["stdDevAcc"] = ">95 %" + if(0.25 to 0.5) + data["stdDevAcc"] = "68-95 %" + if(0.5 to 0.75) + data["stdDevAcc"] = "55-68 %" + else + data["stdDevAcc"] = "<38 %" + + data["isViableSubject"] = is_viable_occupant + if(is_viable_occupant) + data["subjectName"] = scanner_occupant.name + if(scanner_occupant.transformation_timer) + data["subjectStatus"] = STATUS_TRANSFORMING + else + data["subjectStatus"] = scanner_occupant.stat + data["subjectHealth"] = scanner_occupant.health + data["subjectRads"] = scanner_occupant.radiation/(RAD_MOB_SAFE/100) + data["subjectEnzymes"] = scanner_occupant.dna.unique_enzymes + data["isMonkey"] = ismonkey(scanner_occupant) + data["subjectUNI"] = scanner_occupant.dna.uni_identity + data["storage"]["occupant"] = tgui_occupant_mutations + //data["subjectMutations"] = tgui_occupant_mutations + else + data["subjectName"] = null + data["subjectStatus"] = null + data["subjectHealth"] = null + data["subjectRads"] = null + data["subjectEnzymes"] = null + //data["subjectMutations"] = null + data["storage"]["occupant"] = null + + data["hasDelayedAction"] = (delayed_action != null) + data["isScrambleReady"] = is_scramble_ready + data["isJokerReady"] = is_joker_ready + data["isInjectorReady"] = is_injector_ready + data["scrambleSeconds"] = time_to_scramble + data["jokerSeconds"] = time_to_joker + data["injectorSeconds"] = time_to_injector + data["isPulsingRads"] = is_pulsing_rads + data["radPulseSeconds"] = time_to_pulse + + if(diskette != null) + data["hasDisk"] = TRUE + data["diskCapacity"] = diskette.max_mutations - LAZYLEN(diskette.mutations) + data["diskReadOnly"] = diskette.read_only + //data["diskMutations"] = tgui_diskette_mutations + data["storage"]["disk"] = tgui_diskette_mutations + data["diskHasMakeup"] = (LAZYLEN(diskette.genetic_makeup_buffer) > 0) + data["diskMakeupBuffer"] = diskette.genetic_makeup_buffer.Copy() + else + data["hasDisk"] = FALSE + data["diskCapacity"] = 0 + data["diskReadOnly"] = TRUE + //data["diskMutations"] = null + data["storage"]["disk"] = null + data["diskHasMakeup"] = FALSE + data["diskMakeupBuffer"] = null + + //data["mutationStorage"] = tgui_console_mutations + data["storage"]["console"] = tgui_console_mutations + data["chromoStorage"] = tgui_console_chromosomes + data["makeupCapacity"] = NUMBER_OF_BUFFERS + data["makeupStorage"] = tgui_genetic_makeup + + //data["advInjectors"] = tgui_advinjector_mutations + data["storage"]["injector"] = tgui_advinjector_mutations + data["maxAdvInjectors"] = max_injector_selections + + return data + +/obj/machinery/computer/scan_consolenew/ui_act(action, var/list/params) + if(..()) + return TRUE + + . = TRUE + + add_fingerprint(usr) + usr.set_machine(src) + + switch(action) + // Connect this DNA Console to a nearby DNA Scanner + // Usually only activate as an option if there is no connected scanner + if("connect_scanner") + connect_to_scanner() + return + + // Toggle the door open/closed status on attached DNA Scanner + if("toggle_door") + // GUARD CHECK - Scanner still connected and operational? + if(!scanner_operational()) + return + + connected_scanner.toggle_open(usr) + return + + // Toggle the door bolts on the attached DNA Scanner + if("toggle_lock") + // GUARD CHECK - Scanner still connected and operational? + if(!scanner_operational()) + return + + connected_scanner.locked = !connected_scanner.locked + return + + // Scramble scanner occupant's DNA + if("scramble_dna") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + // GUARD CHECK - Is scramble DNA actually ready? + if(!can_modify_occupant() || !(scrambleready < world.time)) + return + + scanner_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA)) + scanner_occupant.dna.generate_dna_blocks() + scrambleready = world.time + SCRAMBLE_TIMEOUT + to_chat(usr,"DNA scrambled.") + scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected_scanner.damage_coeff ** 2) + return + + // Check whether a specific mutation is eligible for discovery within the + // scanner occupant + // This is additionally done when a mutation's tab is selected in the tgui + // interface. This is because some mutations, such as Monkified on monkeys, + // are infact completed by default but not yet discovered. Likewise, all + // mutations can have their sequence completed while Monkified is still an + // active mutation and thus won't immediately be discovered but could be + // discovered when Monkified is removed + // ---------------------------------------------------------------------- // + // params["alias"] - Alias of a mutation. The alias is the "hidden" name of + // the mutation, for example "Mutation 5" or "Mutation 33" + if("check_discovery") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + // GUARD CHECK - Have we somehow cheekily swapped occupants? This is + // unexpected. + if(!(scanner_occupant == connected_scanner.occupant)) + return + + check_discovery(params["alias"]) + return + + // Check all mutations of the occupant and check if any are discovered. + // This is called when the Genetic Sequencer is selected. It'll do things + // like immediately discover Monkified without needing to click through + // the mutation tabs and handle cases where mutations are solved but not + // discovered due to the Monkified mutation being active then removed. + if("all_check_discovery") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + // GUARD CHECK - Have we somehow cheekily swapped occupants? This is + // unexpected. + if(!(scanner_occupant == connected_scanner.occupant)) + return + + // Go over all standard mutations and check if they've been discovered. + for(var/mutation_type in scanner_occupant.dna.mutation_index) + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type) + check_discovery(HM.alias) + + return + + // Set a gene in a mutation's genetic sequence. Will also check for mutations + // discovery as part of the process. + // ---------------------------------------------------------------------- // + // params["alias"] - Alias of a mutation. The alias is the "hidden" name of + // the mutation, for example "Mutation 5" or "Mutation 33" + // params["gene"] - The letter of the new gene + // params["pos"] - The BYOND index of the letter in the gene sequence to be + // changed. Expects a text string from TGUI and will convert to a number + if("pulse_gene") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + // GUARD CHECK - Have we somehow cheekily swapped occupants? This is + // unexpected. + if(!(scanner_occupant == connected_scanner.occupant)) + return + + // GUARD CHECK - Is the occupant currently undergoing some form of + // transformation? If so, we don't want to be pulsing genes. + if(scanner_occupant.transformation_timer) + to_chat(usr,"Gene pulse failed: The scanner occupant undergoing a transformation.") + return + + // Resolve mutation's BYOND path from the alias + var/alias = params["alias"] + var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias) + + // Make sure the occupant still has this mutation + if(!(path in scanner_occupant.dna.mutation_index)) + return + + // Resolve BYOND path to genome sequence of scanner occupant + var/sequence = GET_GENE_STRING(path, scanner_occupant.dna) + + var/newgene = params["gene"] + var/genepos = text2num(params["pos"]) + + // If the new gene is J, this means we're dealing with a JOKER + // GUARD CHECK - Is JOKER actually ready? + if((newgene == "J") && (jokerready < world.time)) + var/truegenes = GET_SEQUENCE(path) + newgene = truegenes[genepos] + jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected_scanner.precision_coeff-1)) + + // If the gene is an X, we want to update the default genes with the new + // X to allow highlighting logic to work on the tgui interface. + if(newgene == "X") + var/defaultseq = scanner_occupant.dna.default_mutation_genes[path] + defaultseq = copytext_char(defaultseq, 1, genepos) + newgene + copytext_char(defaultseq, genepos + 1) + scanner_occupant.dna.default_mutation_genes[path] = defaultseq + + // Copy genome to scanner occupant and do some basic mutation checks as + // we've increased the occupant rads + sequence = copytext_char(sequence, 1, genepos) + newgene + copytext_char(sequence, genepos + 1) + scanner_occupant.dna.mutation_index[path] = sequence + scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER/connected_scanner.damage_coeff + scanner_occupant.domutcheck() + + // GUARD CHECK - Modifying genetics can lead to edge cases where the + // scanner occupant is qdel'd and replaced with a different entity. + // Examples of this include adding/removing the Monkified mutation which + // qdels the previous entity and creates a brand new one in its place. + // We should redo all of our occupant modification checks again, although + // it is less than ideal. + if(!can_modify_occupant()) + return + + // Check if we cracked a mutation + check_discovery(alias) + + return + + // Apply a chromosome to a specific mutation. + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to apply the chromo to + // params["chromo"] - Name of the chromosome to apply to the mutation + if("apply_chromo") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + // GUARD CHECK - Have we somehow cheekily swapped occupants? This is + // unexpected. + if(!(scanner_occupant == connected_scanner.occupant)) + return + + var/bref = params["mutref"] + + // GUARD CHECK - Only search occupant for this specific ref, since your + // can only apply chromosomes to mutations occupants. + var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT) + + // GUARD CHECK - This should not be possible. Unexpected result + if(!HM) + return + + // Look through our stored chromos and compare names to find a + // stored chromo we can apply. + for(var/obj/item/chromosome/CM in stored_chromosomes) + if(CM.can_apply(HM) && (CM.name == params["chromo"])) + stored_chromosomes -= CM + CM.apply(HM) + + return + + // Print any type of standard injector, limited right now to activators that + // activate a dormant mutation and mutators that forcibly create a new + // MUT_EXTRA mutation + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to create an injector of + // params["is_activator"] - Is this an "Activator" style injector, also + // referred to as a "Research" type. Expects a string with 0 or 1, which + // then gets converted to a number. + // params["source"] - The source the request came from. + // Expected results: + // "occupant" - From genetic sequencer + // "console" - From DNA Console storage + // "disk" - From inserted diskette + if("print_injector") + // Because printing mutators and activators share a bunch of code, + // it makes sense to keep them both together and set unique vars + // later in the code + + // As a side note, because mutations can contain unique metadata, + // this system uses BYOND Atom Refs to safely and accurately + // identify mutations from big ol' lists + + // GUARD CHECK - Is the injector actually ready? + if(world.time < injectorready) + return + + var/search_flags = 0 + + switch(params["source"]) + if("occupant") + // GUARD CHECK - Make sure we can modify the occupant before we + // attempt to search them for any given mutation refs. This could + // lead to no search flags being passed to get_mut_by_ref and this + // is intended functionality to prevent any cheese or abuse + if(can_modify_occupant()) + search_flags |= SEARCH_OCCUPANT + if("console") + search_flags |= SEARCH_STORED + if("disk") + search_flags |= SEARCH_DISKETTE + + var/bref = params["mutref"] + var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags) + + // GUARD CHECK - This should not be possible. Unexpected result + if(!HM) + return + + // Create a new DNA Injector and add the appropriate mutations to it + var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc) + I.add_mutations += new HM.type(copymut = HM) + + var/is_activator = text2num(params["is_activator"]) + + // Activators are also called "research" injectors and are used to create + // chromosomes by recycling at the DNA Console + if(is_activator) + I.name = "[HM.name] activator" + I.research = TRUE + // If there's an operational connected scanner, we can use its upgrades + // to improve our injector's radiation generation + if(scanner_operational()) + I.damage_coeff = connected_scanner.damage_coeff*4 + injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected_scanner.precision_coeff) + else + injectorready = world.time + INJECTOR_TIMEOUT + else + I.name = "[HM.name] mutator" + I.doitanyway = TRUE + // If there's an operational connected scanner, we can use its upgrades + // to improve our injector's radiation generation + if(scanner_operational()) + I.damage_coeff = connected_scanner.damage_coeff + injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected_scanner.precision_coeff) + else + injectorready = world.time + INJECTOR_TIMEOUT * 5 + + return + + // Save a mutation to the console's storage buffer. + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to store + // params["source"] - The source the request came from. + // Expected results: + // "occupant" - From genetic sequencer + // "disk" - From inserted diskette + if("save_console") + var/search_flags = 0 + + switch(params["source"]) + if("occupant") + // GUARD CHECK - Make sure we can modify the occupant before we + // attempt to search them for any given mutation refs. This could + // lead to no search flags being passed to get_mut_by_ref and this + // is intended functionality to prevent any cheese or abuse + if(can_modify_occupant()) + search_flags |= SEARCH_OCCUPANT + if("disk") + search_flags |= SEARCH_DISKETTE + + var/bref = params["mutref"] + var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags) + + // GUARD CHECK - This should not be possible. Unexpected result + if(!HM) + return + + var/datum/mutation/human/A = new HM.type() + A.copy_mutation(HM) + stored_mutations += A + to_chat(usr,"Mutation successfully stored.") + return + + // Save a mutation to the diskette's storage buffer. + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to store + // params["source"] - The source the request came from + // Expected results: + // "occupant" - From genetic sequencer + // "console" - From DNA Console storage + if("save_disk") + // GUARD CHECK - This code shouldn't even be callable without a diskette + // inserted. Unexpected result + if(!diskette) + return + + // GUARD CHECK - Make sure the disk is not full + if(LAZYLEN(diskette.mutations) >= diskette.max_mutations) + to_chat(usr,"Disk storage is full.") + return + + // GUARD CHECK - Make sure the disk isn't set to read only, as we're + // attempting to write to it + if(diskette.read_only) + to_chat(usr,"Disk is set to read only mode.") + return + + var/search_flags = 0 + + switch(params["source"]) + if("occupant") + // GUARD CHECK - Make sure we can modify the occupant before we + // attempt to search them for any given mutation refs. This could + // lead to no search flags being passed to get_mut_by_ref and this + // is intended functionality to prevent any cheese or abuse + if(can_modify_occupant()) + search_flags |= SEARCH_OCCUPANT + if("console") + search_flags |= SEARCH_STORED + + var/bref = params["mutref"] + var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags) + + // GUARD CHECK - This should not be possible. Unexpected result + if(!HM) + return + + var/datum/mutation/human/A = new HM.type() + A.copy_mutation(HM) + diskette.mutations += A + to_chat(usr,"Mutation successfully stored to disk.") + return + + // Completely removes a MUT_EXTRA mutation or mutation with corrupt gene + // sequence from the scanner occupant + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to nullify + if("nullify") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + var/bref = params["mutref"] + var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT) + + // GUARD CHECK - This should not be possible. Unexpected result + if(!HM) + return + + // GUARD CHECK - Nullify should only be used on scrambled or "extra" + // mutations. + if(!HM.scrambled && !(HM.class == MUT_EXTRA)) + return + + scanner_occupant.dna.remove_mutation(HM.type) + return + + // Deletes saved mutation from console buffer. + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to delete + if("delete_console_mut") + var/bref = params["mutref"] + var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_STORED) + + if(HM) + stored_mutations.Remove(HM) + qdel(HM) + + return + + // Deletes saved mutation from disk buffer. + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to delete + if("delete_disk_mut") + // GUARD CHECK - This code shouldn't even be callable without a diskette + // inserted. Unexpected result + if(!diskette) + return + + // GUARD CHECK - Make sure the disk isn't set to read only, as we're + // attempting to write to it (via deletion) + if(diskette.read_only) + to_chat(usr,"Disk is set to read only mode.") + return + + var/bref = params["mutref"] + var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_DISKETTE) + + if(HM) + diskette.mutations.Remove(HM) + qdel(HM) + + return + + // Ejects a stored chromosome from the DNA Console + // ---------------------------------------------------------------------- // + // params["chromo"] - Text string of the chromosome name + if("eject_chromo") + var/chromname = params["chromo"] + + for(var/obj/item/chromosome/CM in stored_chromosomes) + if(chromname == CM.name) + CM.forceMove(drop_location()) + adjust_item_drop_location(CM) + stored_chromosomes -= CM + return + + return + + // Combines two mutations from the console to try and create a new mutation + // ---------------------------------------------------------------------- // + // params["firstref"] - ATOM Ref of first mutation for combination + // params["secondref"] - ATOM Ref of second mutation for combination + // mutation + if("combine_console") + // GUARD CHECK - We're running a research-type operation. If, for some + // reason, somehow the DNA Console has been disconnected from the research + // network - Or was never in it to begin with - don't proceed + if(!stored_research) + return + + var/first_bref = params["firstref"] + var/second_bref = params["secondref"] + + // GUARD CHECK - Find the source and destination mutations on the console + // and make sure they actually exist. + var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE) + if(!source_mut) + return + + var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE) + if(!dest_mut) + return + + // Attempt to mix the two mutations to get a new type + var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type) + + if(!result_path) + return + + // If we got a new type, add it to our storage + stored_mutations += new result_path() + to_chat(usr, "Success! New mutation has been added to console storage.") + + // If it's already discovered, end here. Otherwise, add it to the list of + // discovered mutations. + // We've already checked for stored_research earlier + if(result_path in stored_research.discovered_mutations) + return + + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path) + stored_research.discovered_mutations += result_path + say("Successfully mutated [HM.name].") + return + + // Combines two mutations from the disk to try and create a new mutation + // ---------------------------------------------------------------------- // + // params["firstref"] - ATOM Ref of first mutation for combination + // params["secondref"] - ATOM Ref of second mutation for combination + // mutation + if("combine_disk") + // GUARD CHECK - This code shouldn't even be callable without a diskette + // inserted. Unexpected result + if(!diskette) + return + + // GUARD CHECK - Make sure the disk is not full. + if(LAZYLEN(diskette.mutations) >= diskette.max_mutations) + to_chat(usr,"Disk storage is full.") + return + + // GUARD CHECK - Make sure the disk isn't set to read only, as we're + // attempting to write to it + if(diskette.read_only) + to_chat(usr,"Disk is set to read only mode.") + return + + // GUARD CHECK - We're running a research-type operation. If, for some + // reason, somehow the DNA Console has been disconnected from the research + // network - Or was never in it to begin with - don't proceed + if(!stored_research) + return + + var/first_bref = params["firstref"] + var/second_bref = params["secondref"] + + // GUARD CHECK - Find the source and destination mutations on the console + // and make sure they actually exist. + var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE) + if(!source_mut) + return + + var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE) + if(!dest_mut) + return + + // Attempt to mix the two mutations to get a new type + var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type) + + if(!result_path) + return + + // If we got a new type, add it to our storage + diskette.mutations += new result_path() + to_chat(usr, "Success! New mutation has been added to the disk.") + + // If it's already discovered, end here. Otherwise, add it to the list of + // discovered mutations + // We've already checked for stored_research earlier + if(result_path in stored_research.discovered_mutations) + return + + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path) + stored_research.discovered_mutations += result_path + say("Successfully mutated [HM.name].") + return + + // Sets the Genetic Makeup pulse strength. + // ---------------------------------------------------------------------- // + // params["val"] - New strength value as text string, converted to number + // later on in code + if("set_pulse_strength") + var/value = round(text2num(params["val"])) + radstrength = WRAP(value, 1, RADIATION_STRENGTH_MAX+1) + return + + // Sets the Genetic Makeup pulse duration + // ---------------------------------------------------------------------- // + // params["val"] - New strength value as text string, converted to number + // later on in code + if("set_pulse_duration") + var/value = round(text2num(params["val"])) + radduration = WRAP(value, 1, RADIATION_DURATION_MAX+1) + return + + // Saves Genetic Makeup information to disk + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the console genetic makeup buffer to + // copy to disk + if("save_makeup_disk") + // GUARD CHECK - This code shouldn't even be callable without a diskette + // inserted. Unexpected result + if(!diskette) + return + + // GUARD CHECK - Make sure the disk isn't set to read only, as we're + // attempting to write to it + if(diskette.read_only) + to_chat(usr,"Disk is set to read only mode.") + return + + // Convert the index to a number and clamp within the array range + var/buffer_index = text2num(params["index"]) + buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) + + var/list/buffer_slot = genetic_makeup_buffer[buffer_index] + + // GUARD CHECK - This should not be possible to activate on a buffer slot + // that doesn't have any genetic data. Unexpected result + if(!istype(buffer_slot)) + return + + diskette.genetic_makeup_buffer = buffer_slot.Copy() + return + + // Loads Genetic Makeup from disk to a console buffer + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the console genetic makeup buffer to + // copy to. Expected as text string, converted to number later + if("load_makeup_disk") + // GUARD CHECK - This code shouldn't even be callable without a diskette + // inserted. Unexpected result + if(!diskette) + return + + // GUARD CHECK - This should not be possible to activate on a diskette + // that doesn't have any genetic data. Unexpected result + if(LAZYLEN(diskette.genetic_makeup_buffer) == 0) + return + + // Convert the index to a number and clamp within the array range, then + // copy the data from the disk to that buffer + var/buffer_index = text2num(params["index"]) + buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) + genetic_makeup_buffer[buffer_index] = diskette.genetic_makeup_buffer.Copy() + return + + // Deletes genetic makeup buffer from the inserted diskette + if("del_makeup_disk") + // GUARD CHECK - This code shouldn't even be callable without a diskette + // inserted. Unexpected result + if(!diskette) + return + + // GUARD CHECK - Make sure the disk isn't set to read only, as we're + // attempting to write (via deletion) to it + if(diskette.read_only) + to_chat(usr,"Disk is set to read only mode.") + return + + diskette.genetic_makeup_buffer.Cut() + return + + // Saves the scanner occupant's genetic makeup to a given console buffer + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the console genetic makeup buffer to + // save the new genetic data to. Expected as text string, converted to + // number later + if("save_makeup_console") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + // Convert the index to a number and clamp within the array range, then + // copy the data from the disk to that buffer + var/buffer_index = text2num(params["index"]) + buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) + + // Set the new information + genetic_makeup_buffer[buffer_index] = list( + "label"="Slot [buffer_index]:[scanner_occupant.real_name]", + "UI"=scanner_occupant.dna.uni_identity, + "UE"=scanner_occupant.dna.unique_enzymes, + "name"=scanner_occupant.real_name, + "blood_type"=scanner_occupant.dna.blood_type) + + return + + // Deleted genetic makeup data from a console buffer slot + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the console genetic makeup buffer to + // delete the genetic data from. Expected as text string, converted to + // number later + if("del_makeup_console") + // Convert the index to a number and clamp within the array range, then + // copy the data from the disk to that buffer + var/buffer_index = text2num(params["index"]) + buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) + var/list/buffer_slot = genetic_makeup_buffer[buffer_index] + + // GUARD CHECK - This shouldn't be possible to execute this on a null + // buffer. Unexpected resut + if(!istype(buffer_slot)) + return + + genetic_makeup_buffer[buffer_index] = null + return + + // Eject stored diskette from console + if("eject_disk") + eject_disk(usr) + return + + // Create a Genetic Makeup injector. These injectors are timed and thus are + // only temporary + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the console genetic makeup buffer to + // create the makeup injector from. Expected as text string, converted to + // number later + // params["type"] - Type of injector to create + // Expected results: + // "ue" - Unique Enzyme, changes name and blood type + // "ui" - Unique Identity, changes looks + // "mixed" - Combination of both ue and ui + if("makeup_injector") + // Convert the index to a number and clamp within the array range, then + // copy the data from the disk to that buffer + var/buffer_index = text2num(params["index"]) + buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) + var/list/buffer_slot = genetic_makeup_buffer[buffer_index] + + // GUARD CHECK - This shouldn't be possible to execute this on a null + // buffer. Unexpected resut + if(!istype(buffer_slot)) + return + + var/type = params["type"] + var/obj/item/dnainjector/timed/I + + switch(type) + if("ui") + // GUARD CHECK - There's currently no way to save partial genetic data. + // However, if this is the case, we can't make a complete injector and + // this catches that edge case + if(!buffer_slot["UI"]) + to_chat(usr,"Genetic data corrupted, unable to create injector.") + return + + I = new /obj/item/dnainjector/timed(loc) + I.fields = list("UI"=buffer_slot["UI"]) + + // If there is a connected scanner, we can use its upgrades to reduce + // the radiation generated by this injector + if(scanner_operational()) + I.damage_coeff = connected_scanner.damage_coeff + if("ue") + // GUARD CHECK - There's currently no way to save partial genetic data. + // However, if this is the case, we can't make a complete injector and + // this catches that edge case + if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) + to_chat(usr,"Genetic data corrupted, unable to create injector.") + return + + I = new /obj/item/dnainjector/timed(loc) + I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"]) + + // If there is a connected scanner, we can use its upgrades to reduce + // the radiation generated by this injector + if(scanner_operational()) + I.damage_coeff = connected_scanner.damage_coeff + if("mixed") + // GUARD CHECK - There's currently no way to save partial genetic data. + // However, if this is the case, we can't make a complete injector and + // this catches that edge case + if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) + to_chat(usr,"Genetic data corrupted, unable to create injector.") + return + + I = new /obj/item/dnainjector/timed(loc) + I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"]) + + // If there is a connected scanner, we can use its upgrades to reduce + // the radiation generated by this injector + if(scanner_operational()) + I.damage_coeff = connected_scanner.damage_coeff + + // If we successfully created an injector, don't forget to set the new + // ready timer. + if(I) + injectorready = world.time + INJECTOR_TIMEOUT + + return + + // Applies a genetic makeup buffer to the scanner occupant + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the console genetic makeup buffer to + // apply to the scanner occupant. Expected as text string, converted to + // number later + // params["type"] - Type of genetic makeup copy to implement + // Expected results: + // "ue" - Unique Enzyme, changes name and blood type + // "ui" - Unique Identity, changes looks + // "mixed" - Combination of both ue and ui + if("makeup_apply") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + // Convert the index to a number and clamp within the array range, then + // copy the data from the disk to that buffer + var/buffer_index = text2num(params["index"]) + buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) + var/list/buffer_slot = genetic_makeup_buffer[buffer_index] + + // GUARD CHECK - This shouldn't be possible to execute this on a null + // buffer. Unexpected resut + if(!istype(buffer_slot)) + return + + var/type = params["type"] + + apply_genetic_makeup(type, buffer_slot) + return + + // Applies a genetic makeup buffer to the next scanner occupant. This sets + // some code that will run when the connected DNA Scanner door is next + // closed + // This allows people to self-modify their genetic makeup, as tgui + // interfaces can not be accessed while inside the DNA Scanner and genetic + // makeup injectors are only temporary + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the console genetic makeup buffer to + // apply to the scanner occupant. Expected as text string, converted to + // number later + // params["type"] - Type of genetic makeup copy to implement + // Expected results: + // "ue" - Unique Enzyme, changes name and blood type + // "ui" - Unique Identity, changes looks + // "mixed" - Combination of both ue and ui + if("makeup_delay") + // Convert the index to a number and clamp within the array range, then + // copy the data from the disk to that buffer + var/buffer_index = text2num(params["index"]) + buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) + var/list/buffer_slot = genetic_makeup_buffer[buffer_index] + + // GUARD CHECK - This shouldn't be possible to execute this on a null + // buffer. Unexpected resut + if(!istype(buffer_slot)) + return + + var/type = params["type"] + + // Set the delayed action. The next time the scanner door is closed, + // unless this is cancelled in the UI, the action will happen + delayed_action = list("type" = type, "buffer_slot" = buffer_slot) + return + + // Attempts to modify the indexed element of the Unique Identity string + // This is a time delayed action that is handled in process() + // ---------------------------------------------------------------------- // + // params["index"] - The BYOND index of the Unique Identity string to + // attempt to modify + if("makeup_pulse") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + if(!can_modify_occupant()) + return + + // Set the appropriate timer and index to pulse. This is then managed + // later on in process() + var/len = length_char(scanner_occupant.dna.uni_identity) + rad_pulse_timer = world.time + (radduration*10) + rad_pulse_index = WRAP(text2num(params["index"]), 1, len+1) + begin_processing() + return + + // Cancels the delayed action - In this context it is not the radiation + // pulse from "makeup_pulse", which can not be cancelled. It is instead + // the delayed genetic transfer from "makeup_delay" + if("cancel_delay") + delayed_action = null + return + + // Creates a new advanced injector storage buffer in the console + // ---------------------------------------------------------------------- // + // params["name"] - The name to apply to the new injector + if("new_adv_inj") + // GUARD CHECK - Make sure we can make a new injector. This code should + // not be called if we're already maxed out and this is an Unexpected + // result + if(!(LAZYLEN(injector_selection) < max_injector_selections)) + return + + // GUARD CHECK - Sanitise and trim the proposed name. This prevents HTML + // injection and equivalent as tgui input is not stripped + var/inj_name = params["name"] + inj_name = trim(sanitize(inj_name)) + + // GUARD CHECK - If the name is null or blank, or the name is already in + // the list of advanced injectors, we want to reject it as we can't have + // duplicate named advanced injectors + if(!inj_name || (inj_name in injector_selection)) + return + + injector_selection[inj_name] = list() + return + + // Deleted an advanced injector storage buffer from the console + // ---------------------------------------------------------------------- // + // params["name"] - The name of the injector to delete + if("del_adv_inj") + var/inj_name = params["name"] + + // GUARD CHECK - If the name is null or blank, reject. + // GUARD CHECK - If the name isn't in the list of advanced injectors, we + // want to reject this as it shouldn't be possible ever do this. + // Unexpected result + if(!inj_name || !(inj_name in injector_selection)) + return + + injector_selection.Remove(inj_name) + return + + // Creates an injector from an advanced injector buffer + // ---------------------------------------------------------------------- // + // params["name"] - The name of the injector to print + if("print_adv_inj") + // As a side note, because mutations can contain unique metadata, + // this system uses BYOND Atom Refs to safely and accurately + // identify mutations from big ol' lists. + + // GUARD CHECK - Is the injector actually ready? + if(world.time < injectorready) + return + + var/inj_name = params["name"] + + // GUARD CHECK - If the name is null or blank, reject. + // GUARD CHECK - If the name isn't in the list of advanced injectors, we + // want to reject this as it shouldn't be possible ever do this. + // Unexpected result + if(!inj_name || !(inj_name in injector_selection)) + return + + var/list/injector = injector_selection[inj_name] + var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc) + + // Run through each mutation in our Advanced Injector and add them to a + // new injector + for(var/A in injector) + var/datum/mutation/human/HM = A + I.add_mutations += new HM.type(copymut=HM) + + // Force apply any mutations, this is functionality similar to mutators + I.doitanyway = TRUE + I.name = "Advanced [inj_name] injector" + + // If there's an operational connected scanner, we can use its upgrades + // to improve our injector's radiation generation + if(scanner_operational()) + I.damage_coeff = connected_scanner.damage_coeff + injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected_scanner.precision_coeff) + else + injectorready = world.time + INJECTOR_TIMEOUT * 8 + + return + + // Adds a mutation to an advanced injector + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to add to the injector + // params["advinj"] - Name of the advanced injector to add the mutation to + if("add_advinj_mut") + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + // This is needed because this operation can only be completed from the + // genetic sequencer. + if(!can_modify_occupant()) + return + + var/adv_inj = params["advinj"] + + // GUARD CHECK - Make sure our advanced injector actually exists. This + // should not be possible. Unexpected result + if(!(adv_inj in injector_selection)) + return + + // GUARD CHECK - Make sure we limit the number of mutations appropriately + if(LAZYLEN(injector_selection[adv_inj]) >= max_injector_mutations) + to_chat(usr,"Advanced injector mutation storage is full.") + return + + var/mut_source = params["source"] + var/search_flag = 0 + + switch(mut_source) + if("disk") + search_flag = SEARCH_DISKETTE + if("occupant") + search_flag = SEARCH_OCCUPANT + if("console") + search_flag = SEARCH_STORED + + if(!search_flag) + return + + var/bref = params["mutref"] + // We've already made sure we can modify the occupant, so this is safe to + // call + var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flag) + + // GUARD CHECK - This should not be possible. Unexpected result + if(!HM) + return + + // We want to make sure we stick within the instability limit. + // We start with the instability of the mutation we're intending to add. + var/instability_total = HM.instability + + // We then add the instabilities of all other mutations in the injector, + // remembering to apply the Stabilizer chromosome modifiers + for(var/datum/mutation/human/I in injector_selection[adv_inj]) + instability_total += I.instability * GET_MUTATION_STABILIZER(I) + + // If this would take us over the max instability, we inform the user. + if(instability_total > max_injector_instability) + to_chat(usr,"Extra mutation would make the advanced injector too instable.") + return + + // If we've got here, all our checks are passed and we can successfully + // add the mutation to the advanced injector. + var/datum/mutation/human/A = new HM.type() + A.copy_mutation(HM) + injector_selection[adv_inj] += A + to_chat(usr,"Mutation successfully added to advanced injector.") + return + + // Deletes a mutation from an advanced injector + // ---------------------------------------------------------------------- // + // params["mutref"] - ATOM Ref of specific mutation to del from the injector + if("delete_injector_mut") + var/bref = params["mutref"] + + var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_ADV_INJ) + + // GUARD CHECK - This should not be possible. Unexpected result + if(!HM) + return + + // Check Advanced Injectors to find and remove the mutation + for(var/I in injector_selection) + if(injector_selection["[I]"].Remove(HM)) + qdel(HM) + return + + return + + // Sets a new tgui view state + // ---------------------------------------------------------------------- // + // params["id"] - Key for the state to set + // params[...] - Every other element is used to set state variables + if("set_view") + for (var/key in params) + if(key == "src") + continue + tgui_view_state[key] = params[key] + return TRUE + return FALSE + +/** + * Applies the enzyme buffer to the current scanner occupant + * + * Applies the type of a specific genetic makeup buffer to the current scanner + * occupant + * + * Arguments: + * * type - "ui"/"ue"/"mixed" - Which part of the enzyme buffer to apply + * * buffer_slot - Index of the enzyme buffer to apply + */ +/obj/machinery/computer/scan_consolenew/proc/apply_genetic_makeup(type, buffer_slot) + // Note - This proc is only called from code that has already performed the + // necessary occupant guard checks. If you call this code yourself, please + // apply can_modify_occupant() or equivalent checks first. + + // Pre-calc the rad increase since we'll be using it in all the possible + // operations + var/rad_increase = rand(100/(connected_scanner.damage_coeff ** 2),250/(connected_scanner.damage_coeff ** 2)) + + switch(type) + if("ui") + // GUARD CHECK - There's currently no way to save partial genetic data. + // However, if this is the case, we can't make a complete injector and + // this catches that edge case + if(!buffer_slot["UI"]) + to_chat(usr,"Genetic data corrupted, unable to apply genetic data.") + return FALSE + scanner_occupant.dna.uni_identity = buffer_slot["UI"] + scanner_occupant.updateappearance(mutations_overlay_update=1) + scanner_occupant.radiation += rad_increase + scanner_occupant.domutcheck() + return TRUE + if("ue") + // GUARD CHECK - There's currently no way to save partial genetic data. + // However, if this is the case, we can't make a complete injector and + // this catches that edge case + if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) + to_chat(usr,"Genetic data corrupted, unable to apply genetic data.") + return FALSE + scanner_occupant.real_name = buffer_slot["name"] + scanner_occupant.name = buffer_slot["name"] + scanner_occupant.dna.unique_enzymes = buffer_slot["UE"] + scanner_occupant.dna.blood_type = buffer_slot["blood_type"] + scanner_occupant.radiation += rad_increase + scanner_occupant.domutcheck() + return TRUE + if("mixed") + // GUARD CHECK - There's currently no way to save partial genetic data. + // However, if this is the case, we can't make a complete injector and + // this catches that edge case + if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) + to_chat(usr,"Genetic data corrupted, unable to apply genetic data.") + return FALSE + scanner_occupant.dna.uni_identity = buffer_slot["UI"] + scanner_occupant.updateappearance(mutations_overlay_update=1) + scanner_occupant.real_name = buffer_slot["name"] + scanner_occupant.name = buffer_slot["name"] + scanner_occupant.dna.unique_enzymes = buffer_slot["UE"] + scanner_occupant.dna.blood_type = buffer_slot["blood_type"] + scanner_occupant.radiation += rad_increase + scanner_occupant.domutcheck() + return TRUE + + return FALSE +/** + * Checks if there is a connected DNA Scanner that is operational + */ +/obj/machinery/computer/scan_consolenew/proc/scanner_operational() + if(!connected_scanner) + return FALSE + + return (connected_scanner && connected_scanner.is_operational()) + +/** + * Checks if there is a valid DNA Scanner occupant for genetic modification + * + * Checks if there is a valid subject in the DNA Scanner that can be genetically + * modified. Will set the scanner occupant var as part of this check. + * Requires that the scanner can be operated and will return early if it can't + */ +/obj/machinery/computer/scan_consolenew/proc/can_modify_occupant() + // GUARD CHECK - We always want to perform the scanner operational check as + // part of checking if we can modify the occupant. + // We can never modify the occupant of a broken scanner. + if(!scanner_operational()) + return FALSE + + if(!connected_scanner.occupant) + return FALSE + + scanner_occupant = connected_scanner.occupant + + // Check validity of occupent for DNA Modification + // DNA Modification: + // requires DNA + // this DNA can not be bad + // is done via radiation bursts, so radiation immune carbons are not viable + // And the DNA Scanner itself must have a valid scan level + if(scanner_occupant.has_dna() && !HAS_TRAIT(scanner_occupant, TRAIT_GENELESS) && !HAS_TRAIT(scanner_occupant, TRAIT_BADDNA) || (connected_scanner.scan_level == 3)) + return TRUE + + return FALSE + +/** + * Checks for adjacent DNA scanners and connects when it finds a viable one + * + * Seearches cardinal directions in order. Stops when it finds a viable DNA Scanner. + * Will connect to a broken scanner if no functional scanner is available. + * Links itself to the DNA Scanner to receive door open and close events. + */ +/obj/machinery/computer/scan_consolenew/proc/connect_to_scanner() + var/obj/machinery/dna_scannernew/test_scanner = null + var/obj/machinery/dna_scannernew/broken_scanner = null + + // Look in each cardinal direction and try and find a DNA Scanner + // If you find a DNA Scanner, check to see if it broken or working + // If it's working, set the current scanner and return early + // If it's not working, remember it anyway as a broken scanner + for(var/direction in GLOB.cardinals) + test_scanner = locate(/obj/machinery/dna_scannernew, get_step(src, direction)) + if(!isnull(test_scanner)) + if(test_scanner.is_operational()) + connected_scanner = test_scanner + connected_scanner.linked_console = src + return + else + broken_scanner = test_scanner + + // Ultimately, if we have a broken scanner, we'll attempt to connect to it as + // a fallback case, but the code above will prefer a working scanner + if(!isnull(broken_scanner)) + connected_scanner = broken_scanner + connected_scanner.linked_console = src + +/** + * Called by connected DNA Scanners when their doors close. + * + * Sets the new scanner occupant and completes delayed enzyme transfer if one + * is queued. + */ +/obj/machinery/computer/scan_consolenew/proc/on_scanner_close() + // Set the appropriate occupant now the scanner is closed + if(connected_scanner.occupant) + scanner_occupant = connected_scanner.occupant + else + scanner_occupant = null + + // If we have a delayed action - In this case the only delayed action is + // applying a genetic makeup buffer the next time the DNA Scanner is closed - + // we want to perform it. + // GUARD CHECK - Make sure we can modify the occupant, apply_genetic_makeup() + // assumes we've already done this. + if(delayed_action && can_modify_occupant()) + var/type = delayed_action["type"] + var/buffer_slot = delayed_action["buffer_slot"] + if(apply_genetic_makeup(type, buffer_slot)) + to_chat(connected_scanner.occupant, "[src] activates!") + delayed_action = null + +/** + * Called by connected DNA Scanners when their doors open. + * + * Clears enzyme pulse operations, stops processing and nulls the current + * scanner occupant var. + */ +/obj/machinery/computer/scan_consolenew/proc/on_scanner_open() + // If we had a radiation pulse action ongoing, we want to stop this. + // Imagine it being like a microwave stopping when you open the door. + rad_pulse_index = 0 + rad_pulse_timer = 0 + end_processing() + scanner_occupant = null + +/** + * Builds the genetic makeup list which will be sent to tgui interface. + */ +/obj/machinery/computer/scan_consolenew/proc/build_genetic_makeup_list() + // No code will ever null this list, we can safely Cut it. + tgui_genetic_makeup.Cut() + + for(var/i=1, i <= NUMBER_OF_BUFFERS, i++) + if(genetic_makeup_buffer[i]) + tgui_genetic_makeup["[i]"] = genetic_makeup_buffer[i].Copy() + else + tgui_genetic_makeup["[i]"] = null + +/** + * Builds the genetic makeup list which will be sent to tgui interface. + * + * Will iterate over the connected scanner occupant, DNA Console, inserted + * diskette and chromosomes and any advanced injectors, building the main data + * structures which get passed to the tgui interface. + */ +/obj/machinery/computer/scan_consolenew/proc/build_mutation_list(can_modify_occ) + // No code will ever null these lists. We can safely Cut them. + tgui_occupant_mutations.Cut() + tgui_diskette_mutations.Cut() + tgui_console_mutations.Cut() + tgui_console_chromosomes.Cut() + tgui_advinjector_mutations.Cut() + + // ------------------------------------------------------------------------ // + // GUARD CHECK - Can we genetically modify the occupant? This check will have + // previously included checks to make sure the DNA Scanner is still + // operational + if(can_modify_occ) + // ---------------------------------------------------------------------- // + // Start cataloguing all mutations that the occupant has by default + for(var/mutation_type in scanner_occupant.dna.mutation_index) + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type) + + var/list/mutation_data = list() + var/text_sequence = scanner_occupant.dna.mutation_index[mutation_type] + var/default_sequence = scanner_occupant.dna.default_mutation_genes[mutation_type] + var/discovered = (stored_research && (mutation_type in stored_research.discovered_mutations)) + + mutation_data["Alias"] = HM.alias + mutation_data["Sequence"] = text_sequence + mutation_data["DefaultSeq"] = default_sequence + mutation_data["Discovered"] = discovered + mutation_data["Source"] = "occupant" + + // We only want to pass this information along to the tgui interface if + // the mutation has been discovered. Prevents people being able to cheese + // or "hack" their way to figuring out what undiscovered mutations are + if(discovered) + mutation_data["Name"] = HM.name + mutation_data["Description"] = HM.desc + mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) + mutation_data["Quality"] = HM.quality + + // Assume the mutation is normal unless assigned otherwise. + var/mut_class = MUT_NORMAL + + // Check if the mutation is currently activated. If it is, we can add even + // MORE information to send to tgui. + var/datum/mutation/human/A = scanner_occupant.dna.get_mutation(mutation_type) + if(A) + mutation_data["Active"] = TRUE + mutation_data["Scrambled"] = A.scrambled + mutation_data["Class"] = A.class + mut_class = A.class + mutation_data["CanChromo"] = A.can_chromosome + mutation_data["ByondRef"] = REF(A) + mutation_data["Type"] = A.type + if(A.can_chromosome) + mutation_data["ValidChromos"] = jointext(A.valid_chrom_list, ", ") + mutation_data["AppliedChromo"] = A.chromosome_name + mutation_data["ValidStoredChromos"] = build_chrom_list(A) + else + mutation_data["Active"] = FALSE + mutation_data["Scrambled"] = FALSE + mutation_data["Class"] = MUT_NORMAL + + // Technically NONE of these mutations should be MUT_EXTRA but this will + // catch any weird edge cases + // Assign icons by priority - MUT_EXTRA will ALSO be discovered, so it + // has a higher priority for icon/image assignment + if (mut_class == MUT_EXTRA) + mutation_data["Image"] = "dna_extra.gif" + else if(discovered) + mutation_data["Image"] = "dna_discovered.gif" + else + mutation_data["Image"] = "dna_undiscovered.gif" + + tgui_occupant_mutations += list(mutation_data) + + // ---------------------------------------------------------------------- // + // Now get additional/"extra" mutations that they shouldn't have by default + for(var/datum/mutation/human/HM in scanner_occupant.dna.mutations) + // If it's in the mutation index array, we've already catalogued this + // mutation and can safely skip over it. It really shouldn't be, but this + // will catch any weird edge cases + if(HM.type in scanner_occupant.dna.mutation_index) + continue + + var/list/mutation_data = list() + var/text_sequence = GET_SEQUENCE(HM.type) + + // These will all be active mutations. They're added by injector and their + // sequencing code can't be changed. They can only be nullified, which + // completely removes them. + var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) + + mutation_data["Alias"] = A.alias + mutation_data["Sequence"] = text_sequence + mutation_data["Discovered"] = TRUE + mutation_data["Quality"] = HM.quality + mutation_data["Source"] = "occupant" + + mutation_data["Name"] = HM.name + mutation_data["Description"] = HM.desc + mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) + + mutation_data["Active"] = TRUE + mutation_data["Scrambled"] = HM.scrambled + mutation_data["Class"] = HM.class + mutation_data["CanChromo"] = HM.can_chromosome + mutation_data["ByondRef"] = REF(HM) + mutation_data["Type"] = HM.type + + if(HM.can_chromosome) + mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ") + mutation_data["AppliedChromo"] = HM.chromosome_name + mutation_data["ValidStoredChromos"] = build_chrom_list(HM) + + // Nothing in this list should be undiscovered. Technically nothing + // should be anything but EXTRA. But we're just handling some edge cases. + if (HM.class == MUT_EXTRA) + mutation_data["Image"] = "dna_extra.gif" + else + mutation_data["Image"] = "dna_discovered.gif" + + tgui_occupant_mutations += list(mutation_data) + + // ------------------------------------------------------------------------ // + // Build the list of mutations stored within the DNA Console + for(var/datum/mutation/human/HM in stored_mutations) + var/list/mutation_data = list() + + var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) + + mutation_data["Alias"] = A.alias + mutation_data["Name"] = HM.name + mutation_data["Source"] = "console" + mutation_data["Active"] = TRUE + mutation_data["Description"] = HM.desc + mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) + mutation_data["ByondRef"] = REF(HM) + mutation_data["Type"] = HM.type + + mutation_data["CanChromo"] = HM.can_chromosome + if(HM.can_chromosome) + mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ") + mutation_data["AppliedChromo"] = HM.chromosome_name + mutation_data["ValidStoredChromos"] = build_chrom_list(HM) + + tgui_console_mutations += list(mutation_data) + + // ------------------------------------------------------------------------ // + // Build the list of chromosomes stored within the DNA Console + var/chrom_index = 1 + for(var/obj/item/chromosome/CM in stored_chromosomes) + var/list/chromo_data = list() + + chromo_data["Name"] = CM.name + chromo_data["Description"] = CM.desc + chromo_data["Index"] = chrom_index + + tgui_console_chromosomes += list(chromo_data) + ++chrom_index + + // ------------------------------------------------------------------------ // + // Build the list of mutations stored on any inserted diskettes + if(diskette) + for(var/datum/mutation/human/HM in diskette.mutations) + var/list/mutation_data = list() + + var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) + + mutation_data["Alias"] = A.alias + mutation_data["Name"] = HM.name + mutation_data["Active"] = TRUE + //mutation_data["Sequence"] = GET_SEQUENCE(HM.type) + mutation_data["Source"] = "disk" + mutation_data["Description"] = HM.desc + mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) + mutation_data["ByondRef"] = REF(HM) + mutation_data["Type"] = HM.type + + mutation_data["CanChromo"] = HM.can_chromosome + if(HM.can_chromosome) + mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ") + mutation_data["AppliedChromo"] = HM.chromosome_name + mutation_data["ValidStoredChromos"] = build_chrom_list(HM) + + tgui_diskette_mutations += list(mutation_data) + + // ------------------------------------------------------------------------ // + // Build the list of mutations stored within any Advanced Injectors + if(LAZYLEN(injector_selection)) + for(var/I in injector_selection) + var/list/mutations = list() + for(var/datum/mutation/human/HM in injector_selection[I]) + var/list/mutation_data = list() + + var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type) + + mutation_data["Alias"] = A.alias + mutation_data["Name"] = HM.name + mutation_data["Active"] = TRUE + //mutation_data["Sequence"] = GET_SEQUENCE(HM.type) + mutation_data["Source"] = "injector" + mutation_data["Description"] = HM.desc + mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM) + mutation_data["ByondRef"] = REF(HM) + mutation_data["Type"] = HM.type + + if(HM.can_chromosome) + mutation_data["AppliedChromo"] = HM.chromosome_name + + mutations += list(mutation_data) + tgui_advinjector_mutations += list(list( + "name" = "[I]", + "mutations" = mutations, + )) + +/** + * Takes any given chromosome and calculates chromosome compatibility + * + * Will iterate over the stored chromosomes in the DNA Console and will check + * whether it can be applied to the supplied mutation. Then returns a list of + * names of chromosomes that were compatible. + * + * Arguments: + * * mutation - The mutation to check chromosome compatibility with + */ +/obj/machinery/computer/scan_consolenew/proc/build_chrom_list(mutation) + var/list/chromosomes = list() + + for(var/obj/item/chromosome/CM in stored_chromosomes) + if(CM.can_apply(mutation)) + chromosomes += CM.name + + return chromosomes + +/** + * Checks whether a mutation alias has been discovered + * + * Checks whether a given mutation's genetic sequence has been completed and + * discovers it if appropriate + * + * Arguments: + * * alias - Alias of the mutation to check (ie "Mutation 51" or "Mutation 12") + */ +/obj/machinery/computer/scan_consolenew/proc/check_discovery(alias) + // Note - All code paths that call this have already done checks on the + // current occupant to prevent cheese and other abuses. If you call this + // proc please also do the following checks first: + // if(!can_modify_occupant()) + // return + // if(!(scanner_occupant == connected_scanner.occupant)) + // return + + // Turn the alias ("Mutation 1", "Mutation 35") into a mutation path + var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias) + + // Check to see if this mutation is in the active mutation list. If it isn't, + // then the mutation isn't eligible for discovery. If it is but is scrambled, + // then the mutation isn't eligible for discovery. Finally, check if the + // mutation is in discovered mutations - If it isn't, add it to discover. + var/datum/mutation/human/M = scanner_occupant.dna.get_mutation(path) + if(!M) + return FALSE + if(M.scrambled) + return FALSE + if(stored_research && !(path in stored_research.discovered_mutations)) + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(path) + stored_research.discovered_mutations += path + say("Successfully discovered [HM.name].") + return TRUE + + return FALSE + +/** + * Find a mutation from various storage locations via ATOM ref + * + * Takes an ATOM Ref and searches the appropriate mutation buffers and storage + * vars to try and find the associated mutation. + * + * Arguments: + * * ref - ATOM ref of the mutation to locate + * * target_flags - Flags for storage mediums to search, see #defines + */ +/obj/machinery/computer/scan_consolenew/proc/get_mut_by_ref(ref, target_flags) + var/mutation + + // Assume the occupant is valid and the check has been carried out before + // calling this proc with the relevant flags. + if(target_flags & SEARCH_OCCUPANT) + mutation = (locate(ref) in scanner_occupant.dna.mutations) + if(mutation) + return mutation + + if(target_flags & SEARCH_STORED) + mutation = (locate(ref) in stored_mutations) + if(mutation) + return mutation + + if(diskette && (target_flags & SEARCH_DISKETTE)) + mutation = (locate(ref) in diskette.mutations) + if(mutation) + return mutation + + if(injector_selection && (target_flags & SEARCH_ADV_INJ)) + for(var/I in injector_selection) + mutation = (locate(ref) in injector_selection["[I]"]) + if(mutation) + return mutation + + return null + +/** + * Creates a randomised accuracy value for the enzyme pulse functionality. + * + * Donor code from previous DNA Console iteration. + * + * Arguments: + * * position - Index of the intended enzyme element to pulse + * * radduration - Duration of intended radiation pulse + * * number_of_blocks - Number of individual data blocks in the pulsed enzyme + */ +/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks) + var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + position, 1) + return WRAP(val, 1, number_of_blocks+1) + +/** + * Scrambles an enzyme element value for the enzyme pulse functionality. + * + * Donor code from previous DNA Console iteration. + * + * Arguments: + * * input - Enzyme identity element to scramble, expected hex value + * * rs - Strength of radiation pulse, increases the range of possible outcomes + */ +/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs) + var/length = length(input) + var/ran = gaussian(0, rs*RADIATION_STRENGTH_MULTIPLIER) + if(ran == 0) + ran = pick(-1,1) //hacky, statistically should almost never happen. 0-chance makes people mad though + else if(ran < 0) + ran = round(ran) //negative, so floor it + else + ran = -round(-ran) //positive, so ceiling it + return num2hex(WRAP(hex2num(input)+ran, 0, 16**length), length) + + /** + * Performs the enzyme radiation pulse. + * + * Donor code from previous DNA Console iteration. Called from process() when + * there is a radiation pulse in progress. Ends processing. + */ +/obj/machinery/computer/scan_consolenew/proc/rad_pulse() + // GUARD CHECK - Can we genetically modify the occupant? Includes scanner + // operational guard checks. + // If we can't, abort the procedure. + if(!can_modify_occupant()) + rad_pulse_index = 0 + end_processing() + return + + var/len = length_char(scanner_occupant.dna.uni_identity) + var/num = randomize_radiation_accuracy(rad_pulse_index, radduration + (connected_scanner.precision_coeff ** 2), len) //Each manipulator level above 1 makes randomization as accurate as selected time + manipulator lvl^2 //Value is this high for the same reason as with laser - not worth the hassle of upgrading if the bonus is low + var/hex = copytext_char(scanner_occupant.dna.uni_identity, num, num+1) + hex = scramble(hex, radstrength, radduration) + + scanner_occupant.dna.uni_identity = copytext_char(scanner_occupant.dna.uni_identity, 1, num) + hex + copytext_char(scanner_occupant.dna.uni_identity, num + 1) + scanner_occupant.updateappearance(mutations_overlay_update=1) + + rad_pulse_index = 0 + end_processing() + return + +/** + * Sets the default state for the tgui interface. + */ +/obj/machinery/computer/scan_consolenew/proc/set_default_state() + tgui_view_state["consoleMode"] = "storage" + tgui_view_state["storageMode"] = "console" + tgui_view_state["storageConsSubMode"] = "mutations" + tgui_view_state["storageDiskSubMode"] = "mutations" + +/** + * Ejects the DNA Disk from the console. + * + * Will insert into the user's hand if possible, otherwise will drop it at the + * console's location. + * + * Arguments: + * * user - The mob that is attempting to eject the diskette. + */ +/obj/machinery/computer/scan_consolenew/proc/eject_disk(mob/user) + // Check for diskette. + if(!diskette) + return + + to_chat(user, "You eject [diskette] from [src].") + + // Reset the state to console storage. + tgui_view_state["storageMode"] = "console" + + // If the disk shouldn't pop into the user's hand for any reason, drop it on the console instead. + if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(diskette)) + diskette.forceMove(drop_location()) + diskette = null + +#undef INJECTOR_TIMEOUT +#undef NUMBER_OF_BUFFERS +#undef SCRAMBLE_TIMEOUT +#undef JOKER_TIMEOUT +#undef JOKER_UPGRADE + +#undef RADIATION_STRENGTH_MAX +#undef RADIATION_STRENGTH_MULTIPLIER + +#undef RADIATION_DURATION_MAX +#undef RADIATION_ACCURACY_MULTIPLIER + +#undef RADIATION_IRRADIATION_MULTIPLIER + +#undef STATUS_TRANSFORMING + +#undef SEARCH_OCCUPANT +#undef SEARCH_STORED +#undef SEARCH_DISKETTE +#undef SEARCH_ADV_INJ diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm index 31f25e57f9f..d170cd8cd58 100644 --- a/code/game/machinery/computer/law.dm +++ b/code/game/machinery/computer/law.dm @@ -1,77 +1,77 @@ - - -/obj/machinery/computer/upload - var/mob/living/silicon/current = null //The target of future law uploads - icon_screen = "command" - time_to_screwdrive = 60 - -/obj/machinery/computer/upload/Initialize() - . = ..() - AddComponent(/datum/component/gps, "Encrypted Upload") - -/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/ai_module)) - var/obj/item/ai_module/M = O - if(machine_stat & (NOPOWER|BROKEN|MAINT)) - return - if(!current) - to_chat(user, "You haven't selected anything to transmit laws to!") - return - if(!can_upload_to(current)) - to_chat(user, "Upload failed! Check to make sure [current.name] is functioning properly.") - current = null - return - var/turf/currentloc = get_turf(current) - if(currentloc && user.z != currentloc.z) - to_chat(user, "Upload failed! Unable to establish a connection to [current.name]. You're too far away!") - current = null - return - M.install(current.laws, user) - else - return ..() - -/obj/machinery/computer/upload/proc/can_upload_to(mob/living/silicon/S) - if(S.stat == DEAD) - return FALSE - return TRUE - -/obj/machinery/computer/upload/ai - name = "\improper AI upload console" - desc = "Used to upload laws to the AI." - circuit = /obj/item/circuitboard/computer/aiupload - -/obj/machinery/computer/upload/ai/interact(mob/user) - current = select_active_ai(user, z) - - if (!current) - to_chat(user, "No active AIs detected!") - else - to_chat(user, "[current.name] selected for law changes.") - -/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) - if(!A || !isAI(A)) - return FALSE - if(A.control_disabled) - return FALSE - return ..() - - -/obj/machinery/computer/upload/borg - name = "cyborg upload console" - desc = "Used to upload laws to Cyborgs." - circuit = /obj/item/circuitboard/computer/borgupload - -/obj/machinery/computer/upload/borg/interact(mob/user) - current = select_active_free_borg(user) - - if(!current) - to_chat(user, "No active unslaved cyborgs detected.") - else - to_chat(user, "[current.name] selected for law changes.") - -/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) - if(!B || !iscyborg(B)) - return FALSE - if(B.scrambledcodes || B.emagged) - return FALSE - return ..() + + +/obj/machinery/computer/upload + var/mob/living/silicon/current = null //The target of future law uploads + icon_screen = "command" + time_to_screwdrive = 60 + +/obj/machinery/computer/upload/Initialize() + . = ..() + AddComponent(/datum/component/gps, "Encrypted Upload") + +/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/ai_module)) + var/obj/item/ai_module/M = O + if(machine_stat & (NOPOWER|BROKEN|MAINT)) + return + if(!current) + to_chat(user, "You haven't selected anything to transmit laws to!") + return + if(!can_upload_to(current)) + to_chat(user, "Upload failed! Check to make sure [current.name] is functioning properly.") + current = null + return + var/turf/currentloc = get_turf(current) + if(currentloc && user.z != currentloc.z) + to_chat(user, "Upload failed! Unable to establish a connection to [current.name]. You're too far away!") + current = null + return + M.install(current.laws, user) + else + return ..() + +/obj/machinery/computer/upload/proc/can_upload_to(mob/living/silicon/S) + if(S.stat == DEAD) + return FALSE + return TRUE + +/obj/machinery/computer/upload/ai + name = "\improper AI upload console" + desc = "Used to upload laws to the AI." + circuit = /obj/item/circuitboard/computer/aiupload + +/obj/machinery/computer/upload/ai/interact(mob/user) + current = select_active_ai(user, z) + + if (!current) + to_chat(user, "No active AIs detected!") + else + to_chat(user, "[current.name] selected for law changes.") + +/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) + if(!A || !isAI(A)) + return FALSE + if(A.control_disabled) + return FALSE + return ..() + + +/obj/machinery/computer/upload/borg + name = "cyborg upload console" + desc = "Used to upload laws to Cyborgs." + circuit = /obj/item/circuitboard/computer/borgupload + +/obj/machinery/computer/upload/borg/interact(mob/user) + current = select_active_free_borg(user) + + if(!current) + to_chat(user, "No active unslaved cyborgs detected.") + else + to_chat(user, "[current.name] selected for law changes.") + +/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) + if(!B || !iscyborg(B)) + return FALSE + if(B.scrambledcodes || B.emagged) + return FALSE + return ..() diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 584487a6398..9ec6eea008b 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -1,578 +1,578 @@ - - -/obj/machinery/computer/med_data//TODO:SANITY - name = "medical records console" - desc = "This can be used to check medical records." - icon_screen = "medcomp" - icon_keyboard = "med_key" - req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS) - circuit = /obj/item/circuitboard/computer/med_data - var/rank = null - var/screen = null - var/datum/data/record/active1 - var/datum/data/record/active2 - var/temp = null - var/printing = null - //Sorting Variables - var/sortBy = "name" - var/order = 1 // -1 = Descending - 1 = Ascending - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/med_data/syndie - icon_keyboard = "syndie_key" - -/obj/machinery/computer/med_data/ui_interact(mob/user) - . = ..() - if(isliving(user)) - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - var/dat - if(temp) - dat = text("[temp]

    Clear Screen") - else - if(authenticated) - switch(screen) - if(1) - dat += {" -Search Records -
    List Records -
    -
    Virus Database -
    Medbot Tracking -
    -
    Record Maintenance -
    {Log Out}
    -"} - if(2) - dat += {" -

    - - - - -
    Records:
    - - - - - - - - -"} - - - if(!isnull(GLOB.data_core.general)) - for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order)) - var/blood_type = "" - var/b_dna = "" - for(var/datum/data/record/E in GLOB.data_core.medical) - if((E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])) - blood_type = E.fields["blood_type"] - b_dna = E.fields["b_dna"] - var/background - - if(R.fields["m_stat"] == "*Insane*" || R.fields["p_stat"] == "*Deceased*") - background = "'background-color:#990000;'" - else if(R.fields["p_stat"] == "*Unconscious*" || R.fields["m_stat"] == "*Unstable*") - background = "'background-color:#CD6500;'" - else if(R.fields["p_stat"] == "Physically Unfit" || R.fields["m_stat"] == "*Watch*") - background = "'background-color:#3BB9FF;'" - else - background = "'background-color:#4F7529;'" - - dat += text("", background, R.fields["id"], R.fields["name"]) - dat += text("", R.fields["id"]) - dat += text("", R.fields["fingerprint"], b_dna) - dat += text("", blood_type) - dat += text("", R.fields["p_stat"]) - dat += text("", R.fields["m_stat"]) - dat += "
    NameIDFingerprints (F) | DNA UE (D)Blood TypePhysical StatusMental Status
    [][]F: []
    D: []
    [][][]

    " - dat += "
    Back" - if(3) - dat += "Records Maintenance
    \nBackup To Disk
    \nUpload From Disk
    \nDelete All Records
    \n
    \nBack" - if(4) - - dat += "" - if(active1 in GLOB.data_core.general) - if(istype(active1.fields["photo_front"], /obj/item/photo)) - var/obj/item/photo/P1 = active1.fields["photo_front"] - user << browse_rsc(P1.picture.picture_image, "photo_front") - if(istype(active1.fields["photo_side"], /obj/item/photo)) - var/obj/item/photo/P2 = active1.fields["photo_side"] - user << browse_rsc(P2.picture.picture_image, "photo_side") - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - else - dat += "" - - dat += "" - if(active2 in GLOB.data_core.medical) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" //(per disease info placed in log/comment section) - dat += "" - dat += "" - dat += "" - - dat += "" - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - dat += "" - counter++ - dat += "" - - dat += "" - else - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
    Medical Record
    Name:[active1.fields["name"]]
    ID:[active1.fields["id"]]
    Gender: [active1.fields["gender"]] 
    Age: [active1.fields["age"]] 
    Species: [active1.fields["species"]] 
    Fingerprint: [active1.fields["fingerprint"]] 
    Physical Status: [active1.fields["p_stat"]] 
    Mental Status: [active1.fields["m_stat"]] 
    General Record Lost!

    Medical Data
    Blood Type: [active2.fields["blood_type"]] 
    DNA: [active2.fields["b_dna"]] 

    Minor Disabilities:

     [active2.fields["mi_dis"]] 
    Details: [active2.fields["mi_dis_d"]] 

    Major Disabilities:

     [active2.fields["ma_dis"]] 
    Details: [active2.fields["ma_dis_d"]] 

    Current Diseases:

     [active2.fields["cdi"]] 
    Details: [active2.fields["cdi_d"]] 

    Important Notes:

     [active2.fields["notes"]] 

    Notes Cont'd:

     [active2.fields["notes_d"]] 

    Comments/Log
    [active2.fields[text("com_[]", counter)]]
    Delete Entry
    Add Entry

    Delete Record (Medical Only)
    Medical Record Lost!

    New Record
    Print Record
    Back
    " - if(5) - dat += "
    Virus Database
    " - for(var/Dt in typesof(/datum/disease/)) - var/datum/disease/Dis = new Dt(0) - if(istype(Dis, /datum/disease/advance)) - continue // TODO (tm): Add advance diseases to the virus database which no one uses. - if(!Dis.desc) - continue - dat += "
    [Dis.name]" - dat += "
    Back" - if(6) - dat += "
    Medical Robot Monitor
    " - dat += "Back" - dat += "
    Medical Robots:" - var/bdat = null - for(var/mob/living/simple_animal/bot/medbot/M in GLOB.alive_mob_list) - if(M.z != z) - continue //only find medibots on the same z-level as the computer - var/turf/bl = get_turf(M) - if(bl) //if it can't find a turf for the medibot, then it probably shouldn't be showing up - bdat += "[M.name] - \[[bl.x],[bl.y]\] - [M.on ? "Online" : "Offline"]
    " - if(!bdat) - dat += "
    None detected
    " - else - dat += "
    [bdat]" - - else - else - dat += "{Log In}" - var/datum/browser/popup = new(user, "med_rec", "Medical Records Console", 600, 400) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/med_data/Topic(href, href_list) - . = ..() - if(.) - return . - if(!(active1 in GLOB.data_core.general)) - active1 = null - if(!(active2 in GLOB.data_core.medical)) - active2 = null - - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr) || IsAdminGhost(usr)) - usr.set_machine(src) - if(href_list["temp"]) - temp = null - else if(href_list["logout"]) - authenticated = null - screen = null - active1 = null - active2 = null - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - else if(href_list["choice"]) - // SORTING! - if(href_list["choice"] == "Sorting") - // Reverse the order if clicked twice - if(sortBy == href_list["sort"]) - if(order == 1) - order = -1 - else - order = 1 - else - // New sorting order! - sortBy = href_list["sort"] - order = initial(order) - else if(href_list["login"]) - var/mob/M = usr - var/obj/item/card/id/I = M.get_idcard(TRUE) - if(issilicon(M)) - active1 = null - active2 = null - authenticated = 1 - rank = "AI" - screen = 1 - else if(IsAdminGhost(M)) - active1 = null - active2 = null - authenticated = 1 - rank = "Central Command" - screen = 1 - else if(istype(I) && check_access(I)) - active1 = null - active2 = null - authenticated = I.registered_name - rank = I.assignment - screen = 1 - else - to_chat(usr, "Unauthorized access.") - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - if(authenticated) - if(href_list["screen"]) - screen = text2num(href_list["screen"]) - if(screen < 1) - screen = 1 - - active1 = null - active2 = null - - else if(href_list["vir"]) - var/type = href_list["vir"] - var/datum/disease/Dis = new type(0) - var/AfS = "" - for(var/mob/M in Dis.viable_mobtypes) - AfS += " [initial(M.name)];" - temp = {"Name: [Dis.name] -
    Number of stages: [Dis.max_stages] -
    Spread: [Dis.spread_text] Transmission -
    Possible Cure: [(Dis.cure_text||"none")] -
    Affected Lifeforms:[AfS] -
    -
    Notes: [Dis.desc] -
    -
    Severity: [Dis.severity]"} - - else if(href_list["del_all"]) - temp = "Are you sure you wish to delete all records?
    \n\tYes
    \n\tNo
    " - - else if(href_list["del_all2"]) - investigate_log("[key_name(usr)] has deleted all medical records.", INVESTIGATE_RECORDS) - GLOB.data_core.medical.Cut() - temp = "All records deleted." - - else if(href_list["field"]) - var/a1 = active1 - var/a2 = active2 - switch(href_list["field"]) - if("fingerprint") - if(active1) - var/t1 = stripped_input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - active1.fields["fingerprint"] = t1 - if("gender") - if(active1) - if(active1.fields["gender"] == "Male") - active1.fields["gender"] = "Female" - else if(active1.fields["gender"] == "Female") - active1.fields["gender"] = "Other" - else - active1.fields["gender"] = "Male" - if("age") - if(active1) - var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - active1.fields["age"] = t1 - if("species") - if(active1) - var/t1 = stripped_input("Please input species name", "Med. records", active1.fields["species"], null) - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - active1.fields["species"] = t1 - if("mi_dis") - if(active2) - var/t1 = stripped_input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["mi_dis"] = t1 - if("mi_dis_d") - if(active2) - var/t1 = stripped_input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["mi_dis_d"] = t1 - if("ma_dis") - if(active2) - var/t1 = stripped_input("Please input major disabilities list:", "Med. records", active2.fields["ma_dis"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["ma_dis"] = t1 - if("ma_dis_d") - if(active2) - var/t1 = stripped_input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["ma_dis_d"] = t1 - if("alg") - if(active2) - var/t1 = stripped_input("Please state allergies:", "Med. records", active2.fields["alg"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["alg"] = t1 - if("alg_d") - if(active2) - var/t1 = stripped_input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["alg_d"] = t1 - if("cdi") - if(active2) - var/t1 = stripped_input("Please state diseases:", "Med. records", active2.fields["cdi"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["cdi"] = t1 - if("cdi_d") - if(active2) - var/t1 = stripped_input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["cdi_d"] = t1 - if("notes") - if(active2) - var/t1 = stripped_input("Please summarize notes:", "Med. records", active2.fields["notes"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["notes"] = t1 - if("p_stat") - if(active1) - temp = "Physical Condition:
    \n\t*Deceased*
    \n\t*Unconscious*
    \n\tActive
    \n\tPhysically Unfit
    " - if("m_stat") - if(active1) - temp = "Mental Condition:
    \n\t*Insane*
    \n\t*Unstable*
    \n\t*Watch*
    \n\tStable
    " - if("blood_type") - if(active2) - temp = "Blood Type:
    \n\tA- A+
    \n\tB- B+
    \n\tAB- AB+
    \n\tO- O+
    " - if("b_dna") - if(active2) - var/t1 = stripped_input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["b_dna"] = t1 - if("show_photo_front") - if(active1) - if(active1.fields["photo_front"]) - if(istype(active1.fields["photo_front"], /obj/item/photo)) - var/obj/item/photo/P = active1.fields["photo_front"] - P.show(usr) - if("show_photo_side") - if(active1) - if(active1.fields["photo_side"]) - if(istype(active1.fields["photo_side"], /obj/item/photo)) - var/obj/item/photo/P = active1.fields["photo_side"] - P.show(usr) - else - - else if(href_list["p_stat"]) - if(active1) - switch(href_list["p_stat"]) - if("deceased") - active1.fields["p_stat"] = "*Deceased*" - if("unconscious") - active1.fields["p_stat"] = "*Unconscious*" - if("active") - active1.fields["p_stat"] = "Active" - if("unfit") - active1.fields["p_stat"] = "Physically Unfit" - - else if(href_list["m_stat"]) - if(active1) - switch(href_list["m_stat"]) - if("insane") - active1.fields["m_stat"] = "*Insane*" - if("unstable") - active1.fields["m_stat"] = "*Unstable*" - if("watch") - active1.fields["m_stat"] = "*Watch*" - if("stable") - active1.fields["m_stat"] = "Stable" - - - else if(href_list["blood_type"]) - if(active2) - switch(href_list["blood_type"]) - if("an") - active2.fields["blood_type"] = "A-" - if("bn") - active2.fields["blood_type"] = "B-" - if("abn") - active2.fields["blood_type"] = "AB-" - if("on") - active2.fields["blood_type"] = "O-" - if("ap") - active2.fields["blood_type"] = "A+" - if("bp") - active2.fields["blood_type"] = "B+" - if("abp") - active2.fields["blood_type"] = "AB+" - if("op") - active2.fields["blood_type"] = "O+" - - - else if(href_list["del_r"]) - if(active2) - temp = "Are you sure you wish to delete the record (Medical Portion Only)?
    \n\tYes
    \n\tNo
    " - - else if(href_list["del_r2"]) - investigate_log("[key_name(usr)] has deleted the medical records for [active1.fields["name"]].", INVESTIGATE_RECORDS) - if(active2) - qdel(active2) - active2 = null - - else if(href_list["d_rec"]) - active1 = find_record("id", href_list["d_rec"], GLOB.data_core.general) - if(active1) - active2 = find_record("id", href_list["d_rec"], GLOB.data_core.medical) - if(!active2) - active1 = null - screen = 4 - - else if(href_list["new"]) - if((istype(active1, /datum/data/record) && !( istype(active2, /datum/data/record) ))) - var/datum/data/record/R = new /datum/data/record( ) - R.fields["name"] = active1.fields["name"] - R.fields["id"] = active1.fields["id"] - R.name = text("Medical Record #[]", R.fields["id"]) - R.fields["blood_type"] = "Unknown" - R.fields["b_dna"] = "Unknown" - R.fields["mi_dis"] = "None" - R.fields["mi_dis_d"] = "No minor disabilities have been diagnosed." - R.fields["ma_dis"] = "None" - R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." - R.fields["alg"] = "None" - R.fields["alg_d"] = "No allergies have been detected in this patient." - R.fields["cdi"] = "None" - R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - R.fields["notes"] = "No notes." - GLOB.data_core.medical += R - active2 = R - screen = 4 - - else if(href_list["add_c"]) - if(!(active2 in GLOB.data_core.medical)) - return - var/a2 = active2 - var/t1 = stripped_multiline_input("Add Comment:", "Med. records", null, null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - counter++ - active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", authenticated, rank, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) - - else if(href_list["del_c"]) - if((istype(active2, /datum/data/record) && active2.fields[text("com_[]", href_list["del_c"])])) - active2.fields[text("com_[]", href_list["del_c"])] = "Deleted" - - else if(href_list["search"]) - var/t1 = stripped_input(usr, "Search String: (Name, DNA, or ID)", "Med. records") - if(!canUseMedicalRecordsConsole(usr, t1)) - return - active1 = null - active2 = null - t1 = lowertext(t1) - for(var/datum/data/record/R in GLOB.data_core.medical) - if((lowertext(R.fields["name"]) == t1 || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"]))) - active2 = R - else - //Foreach continue //goto(3229) - if(!( active2 )) - temp = text("Could not locate record [].", sanitize(t1)) - else - for(var/datum/data/record/E in GLOB.data_core.general) - if((E.fields["name"] == active2.fields["name"] || E.fields["id"] == active2.fields["id"])) - active1 = E - else - //Foreach continue //goto(3334) - screen = 4 - - else if(href_list["print_p"]) - if(!( printing )) - printing = 1 - GLOB.data_core.medicalPrintCount++ - playsound(loc, 'sound/items/poster_being_created.ogg', 100, TRUE) - sleep(30) - var/obj/item/paper/P = new /obj/item/paper( loc ) - P.info = "
    Medical Record - (MR-[GLOB.data_core.medicalPrintCount])

    " - if(active1 in GLOB.data_core.general) - P.info += text("Name: [] ID: []
    \nGender: []
    \nAge: []
    ", active1.fields["name"], active1.fields["id"], active1.fields["gender"], active1.fields["age"]) - P.info += "\nSpecies: [active1.fields["species"]]
    " - P.info += text("\nFingerprint: []
    \nPhysical Status: []
    \nMental Status: []
    ", active1.fields["fingerprint"], active1.fields["p_stat"], active1.fields["m_stat"]) - else - P.info += "General Record Lost!
    " - if(active2 in GLOB.data_core.medical) - P.info += text("
    \n
    Medical Data

    \nBlood Type: []
    \nDNA: []
    \n
    \nMinor Disabilities: []
    \nDetails: []
    \n
    \nMajor Disabilities: []
    \nDetails: []
    \n
    \nAllergies: []
    \nDetails: []
    \n
    \nCurrent Diseases: [] (per disease info placed in log/comment section)
    \nDetails: []
    \n
    \nImportant Notes:
    \n\t[]
    \n
    \n
    Comments/Log

    ", active2.fields["blood_type"], active2.fields["b_dna"], active2.fields["mi_dis"], active2.fields["mi_dis_d"], active2.fields["ma_dis"], active2.fields["ma_dis_d"], active2.fields["alg"], active2.fields["alg_d"], active2.fields["cdi"], active2.fields["cdi_d"], active2.fields["notes"]) - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - P.info += text("[]
    ", active2.fields[text("com_[]", counter)]) - counter++ - P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, active1.fields["name"]) - else - P.info += "Medical Record Lost!
    " - P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, "Record Lost") - P.info += "" - P.update_icon() - printing = null - - add_fingerprint(usr) - updateUsrDialog() - return - -/obj/machinery/computer/med_data/emp_act(severity) - . = ..() - if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) - for(var/datum/data/record/R in GLOB.data_core.medical) - if(prob(10/severity)) - switch(rand(1,6)) - if(1) - if(prob(10)) - R.fields["name"] = random_unique_lizard_name(R.fields["gender"],1) - else - R.fields["name"] = random_unique_name(R.fields["gender"],1) - if(2) - R.fields["gender"] = pick("Male", "Female", "Other") - if(3) - R.fields["age"] = rand(AGE_MIN, AGE_MAX) - if(4) - R.fields["blood_type"] = random_blood_type() - if(5) - R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit") - if(6) - R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") - continue - - else if(prob(1)) - qdel(R) - continue - -/obj/machinery/computer/med_data/proc/canUseMedicalRecordsConsole(mob/user, message = 1, record1, record2) - if(user) - if(message) - if(authenticated) - if(user.canUseTopic(src, !issilicon(user))) - if(!record1 || record1 == active1) - if(!record2 || record2 == active2) - return 1 - return 0 - -/obj/machinery/computer/med_data/laptop - name = "medical laptop" - desc = "A cheap Nanotrasen medical laptop, it functions as a medical records computer. It's bolted to the table." - icon_state = "laptop" - icon_screen = "medlaptop" - icon_keyboard = "laptop_key" - pass_flags = PASSTABLE + + +/obj/machinery/computer/med_data//TODO:SANITY + name = "medical records console" + desc = "This can be used to check medical records." + icon_screen = "medcomp" + icon_keyboard = "med_key" + req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS) + circuit = /obj/item/circuitboard/computer/med_data + var/rank = null + var/screen = null + var/datum/data/record/active1 + var/datum/data/record/active2 + var/temp = null + var/printing = null + //Sorting Variables + var/sortBy = "name" + var/order = 1 // -1 = Descending - 1 = Ascending + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/med_data/syndie + icon_keyboard = "syndie_key" + +/obj/machinery/computer/med_data/ui_interact(mob/user) + . = ..() + if(isliving(user)) + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + var/dat + if(temp) + dat = text("[temp]

    Clear Screen") + else + if(authenticated) + switch(screen) + if(1) + dat += {" +Search Records +
    List Records +
    +
    Virus Database +
    Medbot Tracking +
    +
    Record Maintenance +
    {Log Out}
    +"} + if(2) + dat += {" +

    + + + + +
    Records:
    + + + + + + + + +"} + + + if(!isnull(GLOB.data_core.general)) + for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order)) + var/blood_type = "" + var/b_dna = "" + for(var/datum/data/record/E in GLOB.data_core.medical) + if((E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])) + blood_type = E.fields["blood_type"] + b_dna = E.fields["b_dna"] + var/background + + if(R.fields["m_stat"] == "*Insane*" || R.fields["p_stat"] == "*Deceased*") + background = "'background-color:#990000;'" + else if(R.fields["p_stat"] == "*Unconscious*" || R.fields["m_stat"] == "*Unstable*") + background = "'background-color:#CD6500;'" + else if(R.fields["p_stat"] == "Physically Unfit" || R.fields["m_stat"] == "*Watch*") + background = "'background-color:#3BB9FF;'" + else + background = "'background-color:#4F7529;'" + + dat += text("", background, R.fields["id"], R.fields["name"]) + dat += text("", R.fields["id"]) + dat += text("", R.fields["fingerprint"], b_dna) + dat += text("", blood_type) + dat += text("", R.fields["p_stat"]) + dat += text("", R.fields["m_stat"]) + dat += "
    NameIDFingerprints (F) | DNA UE (D)Blood TypePhysical StatusMental Status
    [][]F: []
    D: []
    [][][]

    " + dat += "
    Back" + if(3) + dat += "Records Maintenance
    \nBackup To Disk
    \nUpload From Disk
    \nDelete All Records
    \n
    \nBack" + if(4) + + dat += "" + if(active1 in GLOB.data_core.general) + if(istype(active1.fields["photo_front"], /obj/item/photo)) + var/obj/item/photo/P1 = active1.fields["photo_front"] + user << browse_rsc(P1.picture.picture_image, "photo_front") + if(istype(active1.fields["photo_side"], /obj/item/photo)) + var/obj/item/photo/P2 = active1.fields["photo_side"] + user << browse_rsc(P2.picture.picture_image, "photo_side") + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + else + dat += "" + + dat += "" + if(active2 in GLOB.data_core.medical) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" //(per disease info placed in log/comment section) + dat += "" + dat += "" + dat += "" + + dat += "" + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + dat += "" + counter++ + dat += "" + + dat += "" + else + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
    Medical Record
    Name:[active1.fields["name"]]
    ID:[active1.fields["id"]]
    Gender: [active1.fields["gender"]] 
    Age: [active1.fields["age"]] 
    Species: [active1.fields["species"]] 
    Fingerprint: [active1.fields["fingerprint"]] 
    Physical Status: [active1.fields["p_stat"]] 
    Mental Status: [active1.fields["m_stat"]] 
    General Record Lost!

    Medical Data
    Blood Type: [active2.fields["blood_type"]] 
    DNA: [active2.fields["b_dna"]] 

    Minor Disabilities:

     [active2.fields["mi_dis"]] 
    Details: [active2.fields["mi_dis_d"]] 

    Major Disabilities:

     [active2.fields["ma_dis"]] 
    Details: [active2.fields["ma_dis_d"]] 

    Current Diseases:

     [active2.fields["cdi"]] 
    Details: [active2.fields["cdi_d"]] 

    Important Notes:

     [active2.fields["notes"]] 

    Notes Cont'd:

     [active2.fields["notes_d"]] 

    Comments/Log
    [active2.fields[text("com_[]", counter)]]
    Delete Entry
    Add Entry

    Delete Record (Medical Only)
    Medical Record Lost!

    New Record
    Print Record
    Back
    " + if(5) + dat += "
    Virus Database
    " + for(var/Dt in typesof(/datum/disease/)) + var/datum/disease/Dis = new Dt(0) + if(istype(Dis, /datum/disease/advance)) + continue // TODO (tm): Add advance diseases to the virus database which no one uses. + if(!Dis.desc) + continue + dat += "
    [Dis.name]" + dat += "
    Back" + if(6) + dat += "
    Medical Robot Monitor
    " + dat += "Back" + dat += "
    Medical Robots:" + var/bdat = null + for(var/mob/living/simple_animal/bot/medbot/M in GLOB.alive_mob_list) + if(M.z != z) + continue //only find medibots on the same z-level as the computer + var/turf/bl = get_turf(M) + if(bl) //if it can't find a turf for the medibot, then it probably shouldn't be showing up + bdat += "[M.name] - \[[bl.x],[bl.y]\] - [M.on ? "Online" : "Offline"]
    " + if(!bdat) + dat += "
    None detected
    " + else + dat += "
    [bdat]" + + else + else + dat += "{Log In}" + var/datum/browser/popup = new(user, "med_rec", "Medical Records Console", 600, 400) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/med_data/Topic(href, href_list) + . = ..() + if(.) + return . + if(!(active1 in GLOB.data_core.general)) + active1 = null + if(!(active2 in GLOB.data_core.medical)) + active2 = null + + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr) || IsAdminGhost(usr)) + usr.set_machine(src) + if(href_list["temp"]) + temp = null + else if(href_list["logout"]) + authenticated = null + screen = null + active1 = null + active2 = null + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + else if(href_list["choice"]) + // SORTING! + if(href_list["choice"] == "Sorting") + // Reverse the order if clicked twice + if(sortBy == href_list["sort"]) + if(order == 1) + order = -1 + else + order = 1 + else + // New sorting order! + sortBy = href_list["sort"] + order = initial(order) + else if(href_list["login"]) + var/mob/M = usr + var/obj/item/card/id/I = M.get_idcard(TRUE) + if(issilicon(M)) + active1 = null + active2 = null + authenticated = 1 + rank = "AI" + screen = 1 + else if(IsAdminGhost(M)) + active1 = null + active2 = null + authenticated = 1 + rank = "Central Command" + screen = 1 + else if(istype(I) && check_access(I)) + active1 = null + active2 = null + authenticated = I.registered_name + rank = I.assignment + screen = 1 + else + to_chat(usr, "Unauthorized access.") + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + if(authenticated) + if(href_list["screen"]) + screen = text2num(href_list["screen"]) + if(screen < 1) + screen = 1 + + active1 = null + active2 = null + + else if(href_list["vir"]) + var/type = href_list["vir"] + var/datum/disease/Dis = new type(0) + var/AfS = "" + for(var/mob/M in Dis.viable_mobtypes) + AfS += " [initial(M.name)];" + temp = {"Name: [Dis.name] +
    Number of stages: [Dis.max_stages] +
    Spread: [Dis.spread_text] Transmission +
    Possible Cure: [(Dis.cure_text||"none")] +
    Affected Lifeforms:[AfS] +
    +
    Notes: [Dis.desc] +
    +
    Severity: [Dis.severity]"} + + else if(href_list["del_all"]) + temp = "Are you sure you wish to delete all records?
    \n\tYes
    \n\tNo
    " + + else if(href_list["del_all2"]) + investigate_log("[key_name(usr)] has deleted all medical records.", INVESTIGATE_RECORDS) + GLOB.data_core.medical.Cut() + temp = "All records deleted." + + else if(href_list["field"]) + var/a1 = active1 + var/a2 = active2 + switch(href_list["field"]) + if("fingerprint") + if(active1) + var/t1 = stripped_input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + active1.fields["fingerprint"] = t1 + if("gender") + if(active1) + if(active1.fields["gender"] == "Male") + active1.fields["gender"] = "Female" + else if(active1.fields["gender"] == "Female") + active1.fields["gender"] = "Other" + else + active1.fields["gender"] = "Male" + if("age") + if(active1) + var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + active1.fields["age"] = t1 + if("species") + if(active1) + var/t1 = stripped_input("Please input species name", "Med. records", active1.fields["species"], null) + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + active1.fields["species"] = t1 + if("mi_dis") + if(active2) + var/t1 = stripped_input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["mi_dis"] = t1 + if("mi_dis_d") + if(active2) + var/t1 = stripped_input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["mi_dis_d"] = t1 + if("ma_dis") + if(active2) + var/t1 = stripped_input("Please input major disabilities list:", "Med. records", active2.fields["ma_dis"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["ma_dis"] = t1 + if("ma_dis_d") + if(active2) + var/t1 = stripped_input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["ma_dis_d"] = t1 + if("alg") + if(active2) + var/t1 = stripped_input("Please state allergies:", "Med. records", active2.fields["alg"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["alg"] = t1 + if("alg_d") + if(active2) + var/t1 = stripped_input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["alg_d"] = t1 + if("cdi") + if(active2) + var/t1 = stripped_input("Please state diseases:", "Med. records", active2.fields["cdi"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["cdi"] = t1 + if("cdi_d") + if(active2) + var/t1 = stripped_input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["cdi_d"] = t1 + if("notes") + if(active2) + var/t1 = stripped_input("Please summarize notes:", "Med. records", active2.fields["notes"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["notes"] = t1 + if("p_stat") + if(active1) + temp = "Physical Condition:
    \n\t*Deceased*
    \n\t*Unconscious*
    \n\tActive
    \n\tPhysically Unfit
    " + if("m_stat") + if(active1) + temp = "Mental Condition:
    \n\t*Insane*
    \n\t*Unstable*
    \n\t*Watch*
    \n\tStable
    " + if("blood_type") + if(active2) + temp = "Blood Type:
    \n\tA- A+
    \n\tB- B+
    \n\tAB- AB+
    \n\tO- O+
    " + if("b_dna") + if(active2) + var/t1 = stripped_input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["b_dna"] = t1 + if("show_photo_front") + if(active1) + if(active1.fields["photo_front"]) + if(istype(active1.fields["photo_front"], /obj/item/photo)) + var/obj/item/photo/P = active1.fields["photo_front"] + P.show(usr) + if("show_photo_side") + if(active1) + if(active1.fields["photo_side"]) + if(istype(active1.fields["photo_side"], /obj/item/photo)) + var/obj/item/photo/P = active1.fields["photo_side"] + P.show(usr) + else + + else if(href_list["p_stat"]) + if(active1) + switch(href_list["p_stat"]) + if("deceased") + active1.fields["p_stat"] = "*Deceased*" + if("unconscious") + active1.fields["p_stat"] = "*Unconscious*" + if("active") + active1.fields["p_stat"] = "Active" + if("unfit") + active1.fields["p_stat"] = "Physically Unfit" + + else if(href_list["m_stat"]) + if(active1) + switch(href_list["m_stat"]) + if("insane") + active1.fields["m_stat"] = "*Insane*" + if("unstable") + active1.fields["m_stat"] = "*Unstable*" + if("watch") + active1.fields["m_stat"] = "*Watch*" + if("stable") + active1.fields["m_stat"] = "Stable" + + + else if(href_list["blood_type"]) + if(active2) + switch(href_list["blood_type"]) + if("an") + active2.fields["blood_type"] = "A-" + if("bn") + active2.fields["blood_type"] = "B-" + if("abn") + active2.fields["blood_type"] = "AB-" + if("on") + active2.fields["blood_type"] = "O-" + if("ap") + active2.fields["blood_type"] = "A+" + if("bp") + active2.fields["blood_type"] = "B+" + if("abp") + active2.fields["blood_type"] = "AB+" + if("op") + active2.fields["blood_type"] = "O+" + + + else if(href_list["del_r"]) + if(active2) + temp = "Are you sure you wish to delete the record (Medical Portion Only)?
    \n\tYes
    \n\tNo
    " + + else if(href_list["del_r2"]) + investigate_log("[key_name(usr)] has deleted the medical records for [active1.fields["name"]].", INVESTIGATE_RECORDS) + if(active2) + qdel(active2) + active2 = null + + else if(href_list["d_rec"]) + active1 = find_record("id", href_list["d_rec"], GLOB.data_core.general) + if(active1) + active2 = find_record("id", href_list["d_rec"], GLOB.data_core.medical) + if(!active2) + active1 = null + screen = 4 + + else if(href_list["new"]) + if((istype(active1, /datum/data/record) && !( istype(active2, /datum/data/record) ))) + var/datum/data/record/R = new /datum/data/record( ) + R.fields["name"] = active1.fields["name"] + R.fields["id"] = active1.fields["id"] + R.name = text("Medical Record #[]", R.fields["id"]) + R.fields["blood_type"] = "Unknown" + R.fields["b_dna"] = "Unknown" + R.fields["mi_dis"] = "None" + R.fields["mi_dis_d"] = "No minor disabilities have been diagnosed." + R.fields["ma_dis"] = "None" + R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." + R.fields["alg"] = "None" + R.fields["alg_d"] = "No allergies have been detected in this patient." + R.fields["cdi"] = "None" + R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + R.fields["notes"] = "No notes." + GLOB.data_core.medical += R + active2 = R + screen = 4 + + else if(href_list["add_c"]) + if(!(active2 in GLOB.data_core.medical)) + return + var/a2 = active2 + var/t1 = stripped_multiline_input("Add Comment:", "Med. records", null, null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + counter++ + active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", authenticated, rank, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) + + else if(href_list["del_c"]) + if((istype(active2, /datum/data/record) && active2.fields[text("com_[]", href_list["del_c"])])) + active2.fields[text("com_[]", href_list["del_c"])] = "Deleted" + + else if(href_list["search"]) + var/t1 = stripped_input(usr, "Search String: (Name, DNA, or ID)", "Med. records") + if(!canUseMedicalRecordsConsole(usr, t1)) + return + active1 = null + active2 = null + t1 = lowertext(t1) + for(var/datum/data/record/R in GLOB.data_core.medical) + if((lowertext(R.fields["name"]) == t1 || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"]))) + active2 = R + else + //Foreach continue //goto(3229) + if(!( active2 )) + temp = text("Could not locate record [].", sanitize(t1)) + else + for(var/datum/data/record/E in GLOB.data_core.general) + if((E.fields["name"] == active2.fields["name"] || E.fields["id"] == active2.fields["id"])) + active1 = E + else + //Foreach continue //goto(3334) + screen = 4 + + else if(href_list["print_p"]) + if(!( printing )) + printing = 1 + GLOB.data_core.medicalPrintCount++ + playsound(loc, 'sound/items/poster_being_created.ogg', 100, TRUE) + sleep(30) + var/obj/item/paper/P = new /obj/item/paper( loc ) + P.info = "
    Medical Record - (MR-[GLOB.data_core.medicalPrintCount])

    " + if(active1 in GLOB.data_core.general) + P.info += text("Name: [] ID: []
    \nGender: []
    \nAge: []
    ", active1.fields["name"], active1.fields["id"], active1.fields["gender"], active1.fields["age"]) + P.info += "\nSpecies: [active1.fields["species"]]
    " + P.info += text("\nFingerprint: []
    \nPhysical Status: []
    \nMental Status: []
    ", active1.fields["fingerprint"], active1.fields["p_stat"], active1.fields["m_stat"]) + else + P.info += "General Record Lost!
    " + if(active2 in GLOB.data_core.medical) + P.info += text("
    \n
    Medical Data

    \nBlood Type: []
    \nDNA: []
    \n
    \nMinor Disabilities: []
    \nDetails: []
    \n
    \nMajor Disabilities: []
    \nDetails: []
    \n
    \nAllergies: []
    \nDetails: []
    \n
    \nCurrent Diseases: [] (per disease info placed in log/comment section)
    \nDetails: []
    \n
    \nImportant Notes:
    \n\t[]
    \n
    \n
    Comments/Log

    ", active2.fields["blood_type"], active2.fields["b_dna"], active2.fields["mi_dis"], active2.fields["mi_dis_d"], active2.fields["ma_dis"], active2.fields["ma_dis_d"], active2.fields["alg"], active2.fields["alg_d"], active2.fields["cdi"], active2.fields["cdi_d"], active2.fields["notes"]) + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + P.info += text("[]
    ", active2.fields[text("com_[]", counter)]) + counter++ + P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, active1.fields["name"]) + else + P.info += "Medical Record Lost!
    " + P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, "Record Lost") + P.info += "" + P.update_icon() + printing = null + + add_fingerprint(usr) + updateUsrDialog() + return + +/obj/machinery/computer/med_data/emp_act(severity) + . = ..() + if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) + for(var/datum/data/record/R in GLOB.data_core.medical) + if(prob(10/severity)) + switch(rand(1,6)) + if(1) + if(prob(10)) + R.fields["name"] = random_unique_lizard_name(R.fields["gender"],1) + else + R.fields["name"] = random_unique_name(R.fields["gender"],1) + if(2) + R.fields["gender"] = pick("Male", "Female", "Other") + if(3) + R.fields["age"] = rand(AGE_MIN, AGE_MAX) + if(4) + R.fields["blood_type"] = random_blood_type() + if(5) + R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit") + if(6) + R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") + continue + + else if(prob(1)) + qdel(R) + continue + +/obj/machinery/computer/med_data/proc/canUseMedicalRecordsConsole(mob/user, message = 1, record1, record2) + if(user) + if(message) + if(authenticated) + if(user.canUseTopic(src, !issilicon(user))) + if(!record1 || record1 == active1) + if(!record2 || record2 == active2) + return 1 + return 0 + +/obj/machinery/computer/med_data/laptop + name = "medical laptop" + desc = "A cheap Nanotrasen medical laptop, it functions as a medical records computer. It's bolted to the table." + icon_state = "laptop" + icon_screen = "medlaptop" + icon_keyboard = "laptop_key" + pass_flags = PASSTABLE diff --git a/code/game/machinery/computer/pod.dm b/code/game/machinery/computer/pod.dm index 8d3cf18ea65..dce48531d61 100644 --- a/code/game/machinery/computer/pod.dm +++ b/code/game/machinery/computer/pod.dm @@ -1,133 +1,133 @@ -/obj/machinery/computer/pod - name = "mass driver launch control" - desc = "A combined blastdoor and mass driver control unit." - var/obj/machinery/mass_driver/connected = null - var/title = "Mass Driver Controls" - var/id = 1 - var/timing = 0 - var/time = 30 - var/range = 4 - - -/obj/machinery/computer/pod/Initialize() - . = ..() - for(var/obj/machinery/mass_driver/M in range(range, src)) - if(M.id == id) - connected = M - - -/obj/machinery/computer/pod/proc/alarm() - if(machine_stat & (NOPOWER|BROKEN)) - return - - if(!connected) - say("Cannot locate mass driver connector. Cancelling firing sequence!") - return - - for(var/obj/machinery/door/poddoor/M in range(range, src)) - if(M.id == id) - M.open() - - sleep(20) - for(var/obj/machinery/mass_driver/M in range(range, src)) - if(M.id == id) - M.power = connected.power - M.drive() - - sleep(50) - for(var/obj/machinery/door/poddoor/M in range(range, src)) - if(M.id == id) - M.close() - -/obj/machinery/computer/pod/ui_interact(mob/user) - . = ..() - if(!allowed(user)) - to_chat(user, "Access denied.") - return - - var/dat = "" - if(connected) - var/d2 - if(timing) //door controls do not need timers. - d2 = "Stop Time Launch" - else - d2 = "Initiate Time Launch" - dat += "
    \nTimer System: [d2]\nTime Left: [DisplayTimeText((time SECONDS))] - - + +" - var/temp = "" - var/list/L = list( 0.25, 0.5, 1, 2, 4, 8, 16 ) - for(var/t in L) - if(t == connected.power) - temp += "[t] " - else - temp += "[t] " - dat += "
    \nPower Level: [temp]
    \nFiring Sequence
    \nTest Fire Driver
    \nToggle Outer Door
    " - else - dat += "
    \nToggle Outer Door
    " - dat += "

    Close" - add_fingerprint(usr) - var/datum/browser/popup = new(user, "computer", title, 400, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/pod/process() - if(!..()) - return - if(timing) - if(time > 0) - time = round(time) - 1 - else - alarm() - time = 0 - timing = 0 - updateDialog() - - -/obj/machinery/computer/pod/Topic(href, href_list) - if(..()) - return - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) - usr.set_machine(src) - if(href_list["power"]) - var/t = text2num(href_list["power"]) - t = min(max(0.25, t), 16) - if(connected) - connected.power = t - if(href_list["alarm"]) - alarm() - if(href_list["time"]) - timing = text2num(href_list["time"]) - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 0), 120) - if(href_list["door"]) - for(var/obj/machinery/door/poddoor/M in range(range, src)) - if(M.id == id) - if(M.density) - M.open() - else - M.close() - if(href_list["drive"]) - for(var/obj/machinery/mass_driver/M in range(range, src)) - if(M.id == id) - M.power = connected.power - M.drive() - updateUsrDialog() - -/obj/machinery/computer/pod/old - name = "\improper DoorMex control console" - title = "Door Controls" - icon_state = "oldcomp" - icon_screen = "library" - icon_keyboard = "no_keyboard" - -/obj/machinery/computer/pod/old/syndicate - name = "\improper ProComp Executive IIc" - desc = "The Syndicate operate on a tight budget. Operates external airlocks." - title = "External Airlock Controls" - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/computer/pod/old/swf - name = "\improper Magix System IV" - desc = "An arcane artifact that holds much magic. Running E-Knock 2.2: Sorcerer's Edition." +/obj/machinery/computer/pod + name = "mass driver launch control" + desc = "A combined blastdoor and mass driver control unit." + var/obj/machinery/mass_driver/connected = null + var/title = "Mass Driver Controls" + var/id = 1 + var/timing = 0 + var/time = 30 + var/range = 4 + + +/obj/machinery/computer/pod/Initialize() + . = ..() + for(var/obj/machinery/mass_driver/M in range(range, src)) + if(M.id == id) + connected = M + + +/obj/machinery/computer/pod/proc/alarm() + if(machine_stat & (NOPOWER|BROKEN)) + return + + if(!connected) + say("Cannot locate mass driver connector. Cancelling firing sequence!") + return + + for(var/obj/machinery/door/poddoor/M in range(range, src)) + if(M.id == id) + M.open() + + sleep(20) + for(var/obj/machinery/mass_driver/M in range(range, src)) + if(M.id == id) + M.power = connected.power + M.drive() + + sleep(50) + for(var/obj/machinery/door/poddoor/M in range(range, src)) + if(M.id == id) + M.close() + +/obj/machinery/computer/pod/ui_interact(mob/user) + . = ..() + if(!allowed(user)) + to_chat(user, "Access denied.") + return + + var/dat = "" + if(connected) + var/d2 + if(timing) //door controls do not need timers. + d2 = "Stop Time Launch" + else + d2 = "Initiate Time Launch" + dat += "
    \nTimer System: [d2]\nTime Left: [DisplayTimeText((time SECONDS))] - - + +" + var/temp = "" + var/list/L = list( 0.25, 0.5, 1, 2, 4, 8, 16 ) + for(var/t in L) + if(t == connected.power) + temp += "[t] " + else + temp += "[t] " + dat += "
    \nPower Level: [temp]
    \nFiring Sequence
    \nTest Fire Driver
    \nToggle Outer Door
    " + else + dat += "
    \nToggle Outer Door
    " + dat += "

    Close" + add_fingerprint(usr) + var/datum/browser/popup = new(user, "computer", title, 400, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/pod/process() + if(!..()) + return + if(timing) + if(time > 0) + time = round(time) - 1 + else + alarm() + time = 0 + timing = 0 + updateDialog() + + +/obj/machinery/computer/pod/Topic(href, href_list) + if(..()) + return + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) + usr.set_machine(src) + if(href_list["power"]) + var/t = text2num(href_list["power"]) + t = min(max(0.25, t), 16) + if(connected) + connected.power = t + if(href_list["alarm"]) + alarm() + if(href_list["time"]) + timing = text2num(href_list["time"]) + if(href_list["tp"]) + var/tp = text2num(href_list["tp"]) + time += tp + time = min(max(round(time), 0), 120) + if(href_list["door"]) + for(var/obj/machinery/door/poddoor/M in range(range, src)) + if(M.id == id) + if(M.density) + M.open() + else + M.close() + if(href_list["drive"]) + for(var/obj/machinery/mass_driver/M in range(range, src)) + if(M.id == id) + M.power = connected.power + M.drive() + updateUsrDialog() + +/obj/machinery/computer/pod/old + name = "\improper DoorMex control console" + title = "Door Controls" + icon_state = "oldcomp" + icon_screen = "library" + icon_keyboard = "no_keyboard" + +/obj/machinery/computer/pod/old/syndicate + name = "\improper ProComp Executive IIc" + desc = "The Syndicate operate on a tight budget. Operates external airlocks." + title = "External Airlock Controls" + req_access = list(ACCESS_SYNDICATE) + +/obj/machinery/computer/pod/old/swf + name = "\improper Magix System IV" + desc = "An arcane artifact that holds much magic. Running E-Knock 2.2: Sorcerer's Edition." diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index 305d2f6815f..7ebc5044402 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -1,127 +1,127 @@ -/obj/machinery/computer/robotics - name = "robotics control console" - desc = "Used to remotely lockdown or detonate linked Cyborgs and Drones." - icon_screen = "robot" - icon_keyboard = "rd_key" - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/computer/robotics - light_color = LIGHT_COLOR_PINK - ui_x = 500 - ui_y = 460 - -/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) - . = FALSE - if(!istype(R)) - return - if(isAI(user)) - if(R.connected_ai != user) - return - if(iscyborg(user)) - if(R != user) - return - if(R.scrambledcodes) - return - return TRUE - -/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "RoboticsControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/robotics/ui_data(mob/user) - var/list/data = list() - - data["can_hack"] = FALSE - if(issilicon(user)) - var/mob/living/silicon/S = user - if(S.hack_software) - data["can_hack"] = TRUE - else if(IsAdminGhost(user)) - data["can_hack"] = TRUE - - data["cyborgs"] = list() - for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) - if(!can_control(user, R)) - continue - if(z != (get_turf(R)).z) - continue - var/list/cyborg_data = list( - name = R.name, - locked_down = R.lockcharge, - status = R.stat, - charge = R.cell ? round(R.cell.percent()) : null, - module = R.module ? "[R.module.name] Module" : "No Module Detected", - synchronization = R.connected_ai, - emagged = R.emagged, - ref = REF(R) - ) - data["cyborgs"] += list(cyborg_data) - - data["drones"] = list() - for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) - if(D.hacked) - continue - if(z != (get_turf(D)).z) - continue - var/list/drone_data = list( - name = D.name, - status = D.stat, - ref = REF(D) - ) - data["drones"] += list(drone_data) - - return data - -/obj/machinery/computer/robotics/ui_act(action, params) - if(..()) - return - - switch(action) - if("killbot") - if(allowed(usr)) - var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs - if(can_control(usr, R) && !..()) - var/turf/T = get_turf(R) - message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(R, R.client)] at [ADMIN_VERBOSEJMP(T)]!") - log_game("\[key_name(usr)] detonated [key_name(R)]!") - if(R.connected_ai) - to_chat(R.connected_ai, "

    ALERT - Cyborg detonation detected: [R.name]
    ") - R.self_destruct() - else - to_chat(usr, "Access Denied.") - if("stopbot") - if(allowed(usr)) - var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs - if(can_control(usr, R) && !..()) - message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!") - log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") - R.SetLockdown(!R.lockcharge) - to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") - if(R.connected_ai) - to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name]
    ") - else - to_chat(usr, "Access Denied.") - if("magbot") - var/mob/living/silicon/S = usr - if((istype(S) && S.hack_software) || IsAdminGhost(usr)) - var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs - if(istype(R) && !R.emagged && (R.connected_ai == usr || IsAdminGhost(usr)) && !R.scrambledcodes && can_control(usr, R)) - log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!") - message_admins("[ADMIN_LOOKUPFLW(usr)] emagged cyborg [key_name_admin(R)] using robotic console!") - R.SetEmagged(TRUE) - if("killdrone") - if(allowed(usr)) - var/mob/living/simple_animal/drone/D = locate(params["ref"]) in GLOB.mob_list - if(D.hacked) - to_chat(usr, "ERROR: [D] is not responding to external commands.") - else - var/turf/T = get_turf(D) - message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(D)] at [ADMIN_VERBOSEJMP(T)]!") - log_game("[key_name(usr)] detonated [key_name(D)]!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, TRUE, D) - s.start() - D.visible_message("\the [D] self-destructs!") - D.gib() +/obj/machinery/computer/robotics + name = "robotics control console" + desc = "Used to remotely lockdown or detonate linked Cyborgs and Drones." + icon_screen = "robot" + icon_keyboard = "rd_key" + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/computer/robotics + light_color = LIGHT_COLOR_PINK + ui_x = 500 + ui_y = 460 + +/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) + . = FALSE + if(!istype(R)) + return + if(isAI(user)) + if(R.connected_ai != user) + return + if(iscyborg(user)) + if(R != user) + return + if(R.scrambledcodes) + return + return TRUE + +/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "RoboticsControlConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/robotics/ui_data(mob/user) + var/list/data = list() + + data["can_hack"] = FALSE + if(issilicon(user)) + var/mob/living/silicon/S = user + if(S.hack_software) + data["can_hack"] = TRUE + else if(IsAdminGhost(user)) + data["can_hack"] = TRUE + + data["cyborgs"] = list() + for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) + if(!can_control(user, R)) + continue + if(z != (get_turf(R)).z) + continue + var/list/cyborg_data = list( + name = R.name, + locked_down = R.lockcharge, + status = R.stat, + charge = R.cell ? round(R.cell.percent()) : null, + module = R.module ? "[R.module.name] Module" : "No Module Detected", + synchronization = R.connected_ai, + emagged = R.emagged, + ref = REF(R) + ) + data["cyborgs"] += list(cyborg_data) + + data["drones"] = list() + for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) + if(D.hacked) + continue + if(z != (get_turf(D)).z) + continue + var/list/drone_data = list( + name = D.name, + status = D.stat, + ref = REF(D) + ) + data["drones"] += list(drone_data) + + return data + +/obj/machinery/computer/robotics/ui_act(action, params) + if(..()) + return + + switch(action) + if("killbot") + if(allowed(usr)) + var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs + if(can_control(usr, R) && !..()) + var/turf/T = get_turf(R) + message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(R, R.client)] at [ADMIN_VERBOSEJMP(T)]!") + log_game("\[key_name(usr)] detonated [key_name(R)]!") + if(R.connected_ai) + to_chat(R.connected_ai, "

    ALERT - Cyborg detonation detected: [R.name]
    ") + R.self_destruct() + else + to_chat(usr, "Access Denied.") + if("stopbot") + if(allowed(usr)) + var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs + if(can_control(usr, R) && !..()) + message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!") + log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") + R.SetLockdown(!R.lockcharge) + to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") + if(R.connected_ai) + to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name]
    ") + else + to_chat(usr, "Access Denied.") + if("magbot") + var/mob/living/silicon/S = usr + if((istype(S) && S.hack_software) || IsAdminGhost(usr)) + var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs + if(istype(R) && !R.emagged && (R.connected_ai == usr || IsAdminGhost(usr)) && !R.scrambledcodes && can_control(usr, R)) + log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!") + message_admins("[ADMIN_LOOKUPFLW(usr)] emagged cyborg [key_name_admin(R)] using robotic console!") + R.SetEmagged(TRUE) + if("killdrone") + if(allowed(usr)) + var/mob/living/simple_animal/drone/D = locate(params["ref"]) in GLOB.mob_list + if(D.hacked) + to_chat(usr, "ERROR: [D] is not responding to external commands.") + else + var/turf/T = get_turf(D) + message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(D)] at [ADMIN_VERBOSEJMP(T)]!") + log_game("[key_name(usr)] detonated [key_name(D)]!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, TRUE, D) + s.start() + D.visible_message("\the [D] self-destructs!") + D.gib() diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index ab42b16d8ae..1258463348d 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -1,91 +1,91 @@ -/obj/machinery/computer/station_alert - name = "station alert console" - desc = "Used to access the station's automated alert system." - icon_screen = "alert:0" - icon_keyboard = "atmos_key" - circuit = /obj/item/circuitboard/computer/stationalert - ui_x = 325 - ui_y = 500 - var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) - - light_color = LIGHT_COLOR_CYAN - -/obj/machinery/computer/station_alert/Initialize() - . = ..() - GLOB.alert_consoles += src - -/obj/machinery/computer/station_alert/Destroy() - GLOB.alert_consoles -= src - return ..() - -/obj/machinery/computer/station_alert/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "StationAlertConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/station_alert/ui_data(mob/user) - var/list/data = list() - - data["alarms"] = list() - for(var/class in alarms) - data["alarms"][class] = list() - for(var/area in alarms[class]) - data["alarms"][class] += area - - return data - -/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, O, obj/source) - if(source.z != z) - return - if(machine_stat & (BROKEN)) - return - - var/list/L = alarms[class] - for(var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return 1 - var/obj/machinery/camera/C = null - var/list/CL = null - if(O && islist(O)) - CL = O - if (CL.len == 1) - C = CL[1] - else if(O && istype(O, /obj/machinery/camera)) - C = O - L[A.name] = list(A, (C ? C : O), list(source)) - return 1 - - -/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin) - if(machine_stat & (BROKEN)) - return - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - return !cleared - -/obj/machinery/computer/station_alert/update_overlays() - . = ..() - if(machine_stat & (NOPOWER|BROKEN)) - return - var/active_alarms = FALSE - for(var/cat in alarms) - var/list/L = alarms[cat] - if(L.len) - active_alarms = TRUE - if(active_alarms) - . += "alert:2" +/obj/machinery/computer/station_alert + name = "station alert console" + desc = "Used to access the station's automated alert system." + icon_screen = "alert:0" + icon_keyboard = "atmos_key" + circuit = /obj/item/circuitboard/computer/stationalert + ui_x = 325 + ui_y = 500 + var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + + light_color = LIGHT_COLOR_CYAN + +/obj/machinery/computer/station_alert/Initialize() + . = ..() + GLOB.alert_consoles += src + +/obj/machinery/computer/station_alert/Destroy() + GLOB.alert_consoles -= src + return ..() + +/obj/machinery/computer/station_alert/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "StationAlertConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/station_alert/ui_data(mob/user) + var/list/data = list() + + data["alarms"] = list() + for(var/class in alarms) + data["alarms"][class] = list() + for(var/area in alarms[class]) + data["alarms"][class] += area + + return data + +/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, O, obj/source) + if(source.z != z) + return + if(machine_stat & (BROKEN)) + return + + var/list/L = alarms[class] + for(var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/sources = alarm[3] + if (!(source in sources)) + sources += source + return 1 + var/obj/machinery/camera/C = null + var/list/CL = null + if(O && islist(O)) + CL = O + if (CL.len == 1) + C = CL[1] + else if(O && istype(O, /obj/machinery/camera)) + C = O + L[A.name] = list(A, (C ? C : O), list(source)) + return 1 + + +/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin) + if(machine_stat & (BROKEN)) + return + var/list/L = alarms[class] + var/cleared = 0 + for (var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/srcs = alarm[3] + if (origin in srcs) + srcs -= origin + if (srcs.len == 0) + cleared = 1 + L -= I + return !cleared + +/obj/machinery/computer/station_alert/update_overlays() + . = ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + var/active_alarms = FALSE + for(var/cat in alarms) + var/list/L = alarms[cat] + if(L.len) + active_alarms = TRUE + if(active_alarms) + . += "alert:2" diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 0cd8dca07bc..9293e9c8e76 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -1,281 +1,281 @@ -/obj/structure/frame - name = "frame" - icon = 'icons/obj/stock_parts.dmi' - icon_state = "box_0" - density = TRUE - max_integrity = 250 - var/obj/item/circuitboard/machine/circuit = null - var/state = 1 - -/obj/structure/frame/examine(user) - . = ..() - if(circuit) - . += "It has \a [circuit] installed." - - -/obj/structure/frame/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 5) - if(circuit) - circuit.forceMove(loc) - circuit = null - qdel(src) - - -/obj/structure/frame/machine - name = "machine frame" - var/list/components = null - var/list/req_components = null - var/list/req_component_names = null // user-friendly names of components - -/obj/structure/frame/machine/examine(user) - . = ..() - if(state == 3 && req_components && req_component_names) - var/hasContent = 0 - var/requires = "It requires" - - for(var/i = 1 to req_components.len) - var/tname = req_components[i] - var/amt = req_components[tname] - if(amt == 0) - continue - var/use_and = i == req_components.len - requires += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" - hasContent = 1 - - if(hasContent) - . += "[requires]." - else - . += "It does not require any more components." - -/obj/structure/frame/machine/proc/update_namelist() - if(!req_components) - return - - req_component_names = new() - for(var/tname in req_components) - if(ispath(tname, /obj/item/stack)) - var/obj/item/stack/S = tname - var/singular_name = initial(S.singular_name) - if(singular_name) - req_component_names[tname] = singular_name - else - req_component_names[tname] = initial(S.name) - else - var/obj/O = tname - req_component_names[tname] = initial(O.name) - -/obj/structure/frame/machine/proc/get_req_components_amt() - var/amt = 0 - for(var/path in req_components) - amt += req_components[path] - return amt - -/obj/structure/frame/machine/attackby(obj/item/P, mob/user, params) - switch(state) - if(1) - if(istype(P, /obj/item/circuitboard/machine)) - to_chat(user, "The frame needs wiring first!") - return - else if(istype(P, /obj/item/circuitboard)) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(istype(P, /obj/item/stack/cable_coil)) - if(!P.tool_start_check(user, amount=5)) - return - - to_chat(user, "You start to add cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) - to_chat(user, "You add cables to the frame.") - state = 2 - icon_state = "box_1" - - return - if(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) - user.visible_message("[user] disassembles the frame.", \ - "You start to disassemble the frame...", "You hear banging and clanking.") - if(P.use_tool(src, user, 40, volume=50)) - if(state == 1) - to_chat(user, "You disassemble the frame.") - var/obj/item/stack/sheet/metal/M = new (loc, 5) - M.add_fingerprint(user) - qdel(src) - return - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") - if(P.use_tool(src, user, 40, volume=75)) - if(state == 1) - to_chat(user, "You [anchored ? "un" : ""]secure [src].") - setAnchored(!anchored) - return - - if(2) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") - if(P.use_tool(src, user, 40, volume=75)) - to_chat(user, "You [anchored ? "un" : ""]secure [src].") - setAnchored(!anchored) - return - - if(istype(P, /obj/item/circuitboard/machine)) - var/obj/item/circuitboard/machine/B = P - if(!B.build_path) - to_chat(user, "This circuitboard seems to be broken.") - return - if(!anchored && B.needs_anchored) - to_chat(user, "The frame needs to be secured first!") - return - if(!user.transferItemToLoc(B, src)) - return - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You add the circuit board to the frame.") - circuit = B - icon_state = "box_2" - state = 3 - components = list() - req_components = B.req_components.Copy() - update_namelist() - return - - else if(istype(P, /obj/item/circuitboard)) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - - if(P.tool_behaviour == TOOL_WIRECUTTER) - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = 1 - icon_state = "box_0" - new /obj/item/stack/cable_coil(drop_location(), 5) - return - - if(3) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - state = 2 - circuit.forceMove(drop_location()) - components.Remove(circuit) - circuit = null - if(components.len == 0) - to_chat(user, "You remove the circuit board.") - else - to_chat(user, "You remove the circuit board and other components.") - for(var/atom/movable/AM in components) - AM.forceMove(drop_location()) - desc = initial(desc) - req_components = null - components = null - icon_state = "box_1" - return - - if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) - to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") - if(P.use_tool(src, user, 40, volume=75)) - to_chat(user, "You [anchored ? "un" : ""]secure [src].") - setAnchored(!anchored) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - var/component_check = 1 - for(var/R in req_components) - if(req_components[R] > 0) - component_check = 0 - break - if(component_check) - P.play_tool_sound(src) - var/obj/machinery/new_machine = new circuit.build_path(loc) - if(new_machine.circuit) - QDEL_NULL(new_machine.circuit) - new_machine.circuit = circuit - new_machine.setAnchored(anchored) - new_machine.on_construction() - for(var/obj/O in new_machine.component_parts) - qdel(O) - new_machine.component_parts = list() - for(var/obj/O in src) - O.moveToNullspace() - new_machine.component_parts += O - circuit.moveToNullspace() - new_machine.RefreshParts() - qdel(src) - return - - if(istype(P, /obj/item/storage/part_replacer) && P.contents.len && get_req_components_amt()) - var/obj/item/storage/part_replacer/replacer = P - var/list/added_components = list() - var/list/part_list = list() - - //Assemble a list of current parts, then sort them by their rating! - for(var/obj/item/co in replacer) - part_list += co - //Sort the parts. This ensures that higher tier items are applied first. - part_list = sortTim(part_list, /proc/cmp_rped_sort) - - for(var/path in req_components) - while(req_components[path] > 0 && (locate(path) in part_list)) - var/obj/item/part = (locate(path) in part_list) - part_list -= part - if(istype(part,/obj/item/stack)) - var/obj/item/stack/S = part - var/used_amt = min(round(S.get_amount()), req_components[path]) - if(!used_amt || !S.use(used_amt)) - continue - var/NS = new S.merge_type(src, used_amt) - added_components[NS] = path - req_components[path] -= used_amt - else - added_components[part] = path - if(SEND_SIGNAL(replacer, COMSIG_TRY_STORAGE_TAKE, part, src)) - req_components[path]-- - - for(var/obj/item/part in added_components) - if(istype(part,/obj/item/stack)) - var/obj/item/stack/S = part - var/obj/item/stack/NS = locate(S.merge_type) in components //find a stack to merge with - if(NS) - S.merge(NS) - if(!QDELETED(part)) //If we're a stack and we merged we might not exist anymore - components += part - to_chat(user, "[part.name] applied.") - if(added_components.len) - replacer.play_rped_sound() - return - - if(isitem(P) && get_req_components_amt()) - for(var/I in req_components) - if(istype(P, I) && (req_components[I] > 0)) - if(istype(P, /obj/item/stack)) - var/obj/item/stack/S = P - var/used_amt = min(round(S.get_amount()), req_components[I]) - - if(used_amt && S.use(used_amt)) - var/obj/item/stack/NS = locate(S.merge_type) in components - - if(!NS) - NS = new S.merge_type(src, used_amt) - components += NS - else - NS.add(used_amt) - - req_components[I] -= used_amt - to_chat(user, "You add [P] to [src].") - return - if(!user.transferItemToLoc(P, src)) - break - to_chat(user, "You add [P] to [src].") - components += P - req_components[I]-- - return 1 - to_chat(user, "You cannot add that to the machine!") - return 0 - if(user.a_intent == INTENT_HARM) - return ..() - -/obj/structure/frame/machine/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(state >= 2) - new /obj/item/stack/cable_coil(loc , 5) - for(var/X in components) - var/obj/item/I = X - I.forceMove(loc) - ..() +/obj/structure/frame + name = "frame" + icon = 'icons/obj/stock_parts.dmi' + icon_state = "box_0" + density = TRUE + max_integrity = 250 + var/obj/item/circuitboard/machine/circuit = null + var/state = 1 + +/obj/structure/frame/examine(user) + . = ..() + if(circuit) + . += "It has \a [circuit] installed." + + +/obj/structure/frame/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc, 5) + if(circuit) + circuit.forceMove(loc) + circuit = null + qdel(src) + + +/obj/structure/frame/machine + name = "machine frame" + var/list/components = null + var/list/req_components = null + var/list/req_component_names = null // user-friendly names of components + +/obj/structure/frame/machine/examine(user) + . = ..() + if(state == 3 && req_components && req_component_names) + var/hasContent = 0 + var/requires = "It requires" + + for(var/i = 1 to req_components.len) + var/tname = req_components[i] + var/amt = req_components[tname] + if(amt == 0) + continue + var/use_and = i == req_components.len + requires += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" + hasContent = 1 + + if(hasContent) + . += "[requires]." + else + . += "It does not require any more components." + +/obj/structure/frame/machine/proc/update_namelist() + if(!req_components) + return + + req_component_names = new() + for(var/tname in req_components) + if(ispath(tname, /obj/item/stack)) + var/obj/item/stack/S = tname + var/singular_name = initial(S.singular_name) + if(singular_name) + req_component_names[tname] = singular_name + else + req_component_names[tname] = initial(S.name) + else + var/obj/O = tname + req_component_names[tname] = initial(O.name) + +/obj/structure/frame/machine/proc/get_req_components_amt() + var/amt = 0 + for(var/path in req_components) + amt += req_components[path] + return amt + +/obj/structure/frame/machine/attackby(obj/item/P, mob/user, params) + switch(state) + if(1) + if(istype(P, /obj/item/circuitboard/machine)) + to_chat(user, "The frame needs wiring first!") + return + else if(istype(P, /obj/item/circuitboard)) + to_chat(user, "This frame does not accept circuit boards of this type!") + return + if(istype(P, /obj/item/stack/cable_coil)) + if(!P.tool_start_check(user, amount=5)) + return + + to_chat(user, "You start to add cables to the frame...") + if(P.use_tool(src, user, 20, volume=50, amount=5)) + to_chat(user, "You add cables to the frame.") + state = 2 + icon_state = "box_1" + + return + if(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) + user.visible_message("[user] disassembles the frame.", \ + "You start to disassemble the frame...", "You hear banging and clanking.") + if(P.use_tool(src, user, 40, volume=50)) + if(state == 1) + to_chat(user, "You disassemble the frame.") + var/obj/item/stack/sheet/metal/M = new (loc, 5) + M.add_fingerprint(user) + qdel(src) + return + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") + if(P.use_tool(src, user, 40, volume=75)) + if(state == 1) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + setAnchored(!anchored) + return + + if(2) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") + if(P.use_tool(src, user, 40, volume=75)) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + setAnchored(!anchored) + return + + if(istype(P, /obj/item/circuitboard/machine)) + var/obj/item/circuitboard/machine/B = P + if(!B.build_path) + to_chat(user, "This circuitboard seems to be broken.") + return + if(!anchored && B.needs_anchored) + to_chat(user, "The frame needs to be secured first!") + return + if(!user.transferItemToLoc(B, src)) + return + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You add the circuit board to the frame.") + circuit = B + icon_state = "box_2" + state = 3 + components = list() + req_components = B.req_components.Copy() + update_namelist() + return + + else if(istype(P, /obj/item/circuitboard)) + to_chat(user, "This frame does not accept circuit boards of this type!") + return + + if(P.tool_behaviour == TOOL_WIRECUTTER) + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = 1 + icon_state = "box_0" + new /obj/item/stack/cable_coil(drop_location(), 5) + return + + if(3) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + state = 2 + circuit.forceMove(drop_location()) + components.Remove(circuit) + circuit = null + if(components.len == 0) + to_chat(user, "You remove the circuit board.") + else + to_chat(user, "You remove the circuit board and other components.") + for(var/atom/movable/AM in components) + AM.forceMove(drop_location()) + desc = initial(desc) + req_components = null + components = null + icon_state = "box_1" + return + + if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") + if(P.use_tool(src, user, 40, volume=75)) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + setAnchored(!anchored) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + var/component_check = 1 + for(var/R in req_components) + if(req_components[R] > 0) + component_check = 0 + break + if(component_check) + P.play_tool_sound(src) + var/obj/machinery/new_machine = new circuit.build_path(loc) + if(new_machine.circuit) + QDEL_NULL(new_machine.circuit) + new_machine.circuit = circuit + new_machine.setAnchored(anchored) + new_machine.on_construction() + for(var/obj/O in new_machine.component_parts) + qdel(O) + new_machine.component_parts = list() + for(var/obj/O in src) + O.moveToNullspace() + new_machine.component_parts += O + circuit.moveToNullspace() + new_machine.RefreshParts() + qdel(src) + return + + if(istype(P, /obj/item/storage/part_replacer) && P.contents.len && get_req_components_amt()) + var/obj/item/storage/part_replacer/replacer = P + var/list/added_components = list() + var/list/part_list = list() + + //Assemble a list of current parts, then sort them by their rating! + for(var/obj/item/co in replacer) + part_list += co + //Sort the parts. This ensures that higher tier items are applied first. + part_list = sortTim(part_list, /proc/cmp_rped_sort) + + for(var/path in req_components) + while(req_components[path] > 0 && (locate(path) in part_list)) + var/obj/item/part = (locate(path) in part_list) + part_list -= part + if(istype(part,/obj/item/stack)) + var/obj/item/stack/S = part + var/used_amt = min(round(S.get_amount()), req_components[path]) + if(!used_amt || !S.use(used_amt)) + continue + var/NS = new S.merge_type(src, used_amt) + added_components[NS] = path + req_components[path] -= used_amt + else + added_components[part] = path + if(SEND_SIGNAL(replacer, COMSIG_TRY_STORAGE_TAKE, part, src)) + req_components[path]-- + + for(var/obj/item/part in added_components) + if(istype(part,/obj/item/stack)) + var/obj/item/stack/S = part + var/obj/item/stack/NS = locate(S.merge_type) in components //find a stack to merge with + if(NS) + S.merge(NS) + if(!QDELETED(part)) //If we're a stack and we merged we might not exist anymore + components += part + to_chat(user, "[part.name] applied.") + if(added_components.len) + replacer.play_rped_sound() + return + + if(isitem(P) && get_req_components_amt()) + for(var/I in req_components) + if(istype(P, I) && (req_components[I] > 0)) + if(istype(P, /obj/item/stack)) + var/obj/item/stack/S = P + var/used_amt = min(round(S.get_amount()), req_components[I]) + + if(used_amt && S.use(used_amt)) + var/obj/item/stack/NS = locate(S.merge_type) in components + + if(!NS) + NS = new S.merge_type(src, used_amt) + components += NS + else + NS.add(used_amt) + + req_components[I] -= used_amt + to_chat(user, "You add [P] to [src].") + return + if(!user.transferItemToLoc(P, src)) + break + to_chat(user, "You add [P] to [src].") + components += P + req_components[I]-- + return 1 + to_chat(user, "You cannot add that to the machine!") + return 0 + if(user.a_intent == INTENT_HARM) + return ..() + +/obj/structure/frame/machine/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(state >= 2) + new /obj/item/stack/cable_coil(loc , 5) + for(var/X in components) + var/obj/item/I = X + I.forceMove(loc) + ..() diff --git a/code/game/machinery/doors/alarmlock.dm b/code/game/machinery/doors/alarmlock.dm index 67904fb336d..07722469a7d 100644 --- a/code/game/machinery/doors/alarmlock.dm +++ b/code/game/machinery/doors/alarmlock.dm @@ -1,43 +1,43 @@ -/obj/machinery/door/airlock/alarmlock - name = "glass alarm airlock" - icon = 'icons/obj/doors/airlocks/station2/glass.dmi' - overlays_file = 'icons/obj/doors/airlocks/station2/overlays.dmi' - opacity = 0 - assemblytype = /obj/structure/door_assembly/door_assembly_public - glass = TRUE - - var/datum/radio_frequency/air_connection - var/air_frequency = FREQ_ATMOS_ALARMS - autoclose = FALSE - -/obj/machinery/door/airlock/alarmlock/Initialize() - . = ..() - air_connection = new - -/obj/machinery/door/airlock/alarmlock/Destroy() - SSradio.remove_object(src,air_frequency) - air_connection = null - return ..() - -/obj/machinery/door/airlock/alarmlock/Initialize() - . = ..() - SSradio.remove_object(src, air_frequency) - air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM) - open() - -/obj/machinery/door/airlock/alarmlock/receive_signal(datum/signal/signal) - ..() - if(machine_stat & (NOPOWER|BROKEN)) - return - - var/alarm_area = signal.data["zone"] - var/alert = signal.data["alert"] - - if(alarm_area == get_area_name(src)) - switch(alert) - if("severe") - autoclose = TRUE - close() - if("minor", "clear") - autoclose = FALSE - open() +/obj/machinery/door/airlock/alarmlock + name = "glass alarm airlock" + icon = 'icons/obj/doors/airlocks/station2/glass.dmi' + overlays_file = 'icons/obj/doors/airlocks/station2/overlays.dmi' + opacity = 0 + assemblytype = /obj/structure/door_assembly/door_assembly_public + glass = TRUE + + var/datum/radio_frequency/air_connection + var/air_frequency = FREQ_ATMOS_ALARMS + autoclose = FALSE + +/obj/machinery/door/airlock/alarmlock/Initialize() + . = ..() + air_connection = new + +/obj/machinery/door/airlock/alarmlock/Destroy() + SSradio.remove_object(src,air_frequency) + air_connection = null + return ..() + +/obj/machinery/door/airlock/alarmlock/Initialize() + . = ..() + SSradio.remove_object(src, air_frequency) + air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM) + open() + +/obj/machinery/door/airlock/alarmlock/receive_signal(datum/signal/signal) + ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + + var/alarm_area = signal.data["zone"] + var/alert = signal.data["alert"] + + if(alarm_area == get_area_name(src)) + switch(alert) + if("severe") + autoclose = TRUE + close() + if("minor", "clear") + autoclose = FALSE + open() diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 315ef79b289..0729e909f9a 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -1,255 +1,255 @@ -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Small Fonts" -#define MAX_TIMER 9000 - -#define PRESET_SHORT 1200 -#define PRESET_MEDIUM 1800 -#define PRESET_LONG 3000 - - - -/////////////////////////////////////////////////////////////////////////////////////////////// -// Brig Door control displays. -// Description: This is a controls the timer for the brig doors, displays the timer on itself and -// has a popup window when used, allowing to set the timer. -// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code -// Date: 01/September/2010 -// Programmer: Veryinky -///////////////////////////////////////////////////////////////////////////////////////////////// -/obj/machinery/door_timer - name = "door timer" - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - desc = "A remote control for a door." - req_access = list(ACCESS_SECURITY) - density = FALSE - var/id = null // id of linked machinery/lockers - - var/activation_time = 0 - var/timer_duration = 0 - - var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing - var/list/obj/machinery/targets = list() - var/obj/item/radio/Radio //needed to send messages to sec radio - - maptext_height = 26 - maptext_width = 32 - maptext_y = -1 - ui_x = 300 - ui_y = 138 - -/obj/machinery/door_timer/Initialize() - . = ..() - - Radio = new/obj/item/radio(src) - Radio.listening = 0 - -/obj/machinery/door_timer/Initialize() - . = ..() - if(id != null) - for(var/obj/machinery/door/window/brigdoor/M in urange(20, src)) - if (M.id == id) - targets += M - - for(var/obj/machinery/flasher/F in urange(20, src)) - if(F.id == id) - targets += F - - for(var/obj/structure/closet/secure_closet/brig/C in urange(20, src)) - if(C.id == id) - targets += C - - if(!targets.len) - obj_break() - update_icon() - - -//Main door timer loop, if it's timing and time is >0 reduce time by 1. -// if it's less than 0, open door, reset timer -// update the door_timer window and the icon -/obj/machinery/door_timer/process() - if(machine_stat & (NOPOWER|BROKEN)) - return - - if(timing) - if(world.time - activation_time >= timer_duration) - timer_end() // open doors, reset timer, clear status screen - update_icon() - -// open/closedoor checks if door_timer has power, if so it checks if the -// linked door is open/closed (by density) then opens it/closes it. -/obj/machinery/door_timer/proc/timer_start() - if(machine_stat & (NOPOWER|BROKEN)) - return 0 - - activation_time = world.time - timing = TRUE - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(door.density) - continue - INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/close) - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened && !C.close()) - continue - C.locked = TRUE - C.update_icon() - return 1 - - -/obj/machinery/door_timer/proc/timer_end(forced = FALSE) - - if(machine_stat & (NOPOWER|BROKEN)) - return 0 - - if(!forced) - Radio.set_frequency(FREQ_SECURITY) - Radio.talk_into(src, "Timer has expired. Releasing prisoner.", FREQ_SECURITY) - - timing = FALSE - activation_time = null - set_timer(0) - update_icon() - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(!door.density) - continue - INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open) - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened) - continue - C.locked = FALSE - C.update_icon() - - return 1 - - -/obj/machinery/door_timer/proc/time_left(seconds = FALSE) - . = max(0,timer_duration - (activation_time ? world.time - activation_time : 0)) - if(seconds) - . /= 10 - -/obj/machinery/door_timer/proc/set_timer(value) - var/new_time = clamp(value,0,MAX_TIMER) - . = new_time == timer_duration //return 1 on no change - timer_duration = new_time - -/obj/machinery/door_timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "BrigTimer", name, ui_x, ui_y, master_ui, state) - ui.open() - -//icon update function -// if NOPOWER, display blank -// if BROKEN, display blue screen of death icon AI uses -// if timing=true, run update display function -/obj/machinery/door_timer/update_icon() - if(machine_stat & (NOPOWER)) - icon_state = "frame" - return - - if(machine_stat & (BROKEN)) - set_picture("ai_bsod") - return - - if(timing) - var/disp1 = id - var/time_left = time_left(seconds = TRUE) - var/disp2 = "[add_leading(num2text((time_left / 60) % 60), 2, "0")]:[add_leading(num2text(time_left % 60), 2, "0")]" - if(length(disp2) > CHARS_PER_LINE) - disp2 = "Error" - update_display(disp1, disp2) - else - if(maptext) - maptext = "" - return - - -// Adds an icon in case the screen is broken/off, stolen from status_display.dm -/obj/machinery/door_timer/proc/set_picture(state) - if(maptext) - maptext = "" - cut_overlays() - add_overlay(mutable_appearance('icons/obj/status_display.dmi', state)) - - -//Checks to see if there's 1 line or 2, adds text-icons-numbers/letters over display -// Stolen from status_display -/obj/machinery/door_timer/proc/update_display(line1, line2) - line1 = uppertext(line1) - line2 = uppertext(line2) - var/new_text = {"
    [line1]
    [line2]
    "} - if(maptext != new_text) - maptext = new_text - -/obj/machinery/door_timer/ui_data() - var/list/data = list() - var/time_left = time_left(seconds = TRUE) - data["seconds"] = round(time_left % 60) - data["minutes"] = round((time_left - data["seconds"]) / 60) - data["timing"] = timing - data["flash_charging"] = FALSE - for(var/obj/machinery/flasher/F in targets) - if(F.last_flash && (F.last_flash + 150) > world.time) - data["flash_charging"] = TRUE - break - return data - - -/obj/machinery/door_timer/ui_act(action, params) - if(..()) - return - . = TRUE - - if(!allowed(usr)) - to_chat(usr, "Access denied.") - return FALSE - - switch(action) - if("time") - var/value = text2num(params["adjust"]) - if(value) - . = set_timer(time_left()+value) - if("start") - timer_start() - if("stop") - timer_end(forced = TRUE) - if("flash") - for(var/obj/machinery/flasher/F in targets) - F.flash() - if("preset") - var/preset = params["preset"] - var/preset_time = time_left() - switch(preset) - if("short") - preset_time = PRESET_SHORT - if("medium") - preset_time = PRESET_MEDIUM - if("long") - preset_time = PRESET_LONG - . = set_timer(preset_time) - if(timing) - activation_time = world.time - else - . = FALSE - - -#undef PRESET_SHORT -#undef PRESET_MEDIUM -#undef PRESET_LONG - -#undef MAX_TIMER -#undef FONT_SIZE -#undef FONT_COLOR -#undef FONT_STYLE -#undef CHARS_PER_LINE +#define CHARS_PER_LINE 5 +#define FONT_SIZE "5pt" +#define FONT_COLOR "#09f" +#define FONT_STYLE "Small Fonts" +#define MAX_TIMER 9000 + +#define PRESET_SHORT 1200 +#define PRESET_MEDIUM 1800 +#define PRESET_LONG 3000 + + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Brig Door control displays. +// Description: This is a controls the timer for the brig doors, displays the timer on itself and +// has a popup window when used, allowing to set the timer. +// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code +// Date: 01/September/2010 +// Programmer: Veryinky +///////////////////////////////////////////////////////////////////////////////////////////////// +/obj/machinery/door_timer + name = "door timer" + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + desc = "A remote control for a door." + req_access = list(ACCESS_SECURITY) + density = FALSE + var/id = null // id of linked machinery/lockers + + var/activation_time = 0 + var/timer_duration = 0 + + var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing + var/list/obj/machinery/targets = list() + var/obj/item/radio/Radio //needed to send messages to sec radio + + maptext_height = 26 + maptext_width = 32 + maptext_y = -1 + ui_x = 300 + ui_y = 138 + +/obj/machinery/door_timer/Initialize() + . = ..() + + Radio = new/obj/item/radio(src) + Radio.listening = 0 + +/obj/machinery/door_timer/Initialize() + . = ..() + if(id != null) + for(var/obj/machinery/door/window/brigdoor/M in urange(20, src)) + if (M.id == id) + targets += M + + for(var/obj/machinery/flasher/F in urange(20, src)) + if(F.id == id) + targets += F + + for(var/obj/structure/closet/secure_closet/brig/C in urange(20, src)) + if(C.id == id) + targets += C + + if(!targets.len) + obj_break() + update_icon() + + +//Main door timer loop, if it's timing and time is >0 reduce time by 1. +// if it's less than 0, open door, reset timer +// update the door_timer window and the icon +/obj/machinery/door_timer/process() + if(machine_stat & (NOPOWER|BROKEN)) + return + + if(timing) + if(world.time - activation_time >= timer_duration) + timer_end() // open doors, reset timer, clear status screen + update_icon() + +// open/closedoor checks if door_timer has power, if so it checks if the +// linked door is open/closed (by density) then opens it/closes it. +/obj/machinery/door_timer/proc/timer_start() + if(machine_stat & (NOPOWER|BROKEN)) + return 0 + + activation_time = world.time + timing = TRUE + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(door.density) + continue + INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/close) + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened && !C.close()) + continue + C.locked = TRUE + C.update_icon() + return 1 + + +/obj/machinery/door_timer/proc/timer_end(forced = FALSE) + + if(machine_stat & (NOPOWER|BROKEN)) + return 0 + + if(!forced) + Radio.set_frequency(FREQ_SECURITY) + Radio.talk_into(src, "Timer has expired. Releasing prisoner.", FREQ_SECURITY) + + timing = FALSE + activation_time = null + set_timer(0) + update_icon() + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(!door.density) + continue + INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open) + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened) + continue + C.locked = FALSE + C.update_icon() + + return 1 + + +/obj/machinery/door_timer/proc/time_left(seconds = FALSE) + . = max(0,timer_duration - (activation_time ? world.time - activation_time : 0)) + if(seconds) + . /= 10 + +/obj/machinery/door_timer/proc/set_timer(value) + var/new_time = clamp(value,0,MAX_TIMER) + . = new_time == timer_duration //return 1 on no change + timer_duration = new_time + +/obj/machinery/door_timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "BrigTimer", name, ui_x, ui_y, master_ui, state) + ui.open() + +//icon update function +// if NOPOWER, display blank +// if BROKEN, display blue screen of death icon AI uses +// if timing=true, run update display function +/obj/machinery/door_timer/update_icon() + if(machine_stat & (NOPOWER)) + icon_state = "frame" + return + + if(machine_stat & (BROKEN)) + set_picture("ai_bsod") + return + + if(timing) + var/disp1 = id + var/time_left = time_left(seconds = TRUE) + var/disp2 = "[add_leading(num2text((time_left / 60) % 60), 2, "0")]:[add_leading(num2text(time_left % 60), 2, "0")]" + if(length(disp2) > CHARS_PER_LINE) + disp2 = "Error" + update_display(disp1, disp2) + else + if(maptext) + maptext = "" + return + + +// Adds an icon in case the screen is broken/off, stolen from status_display.dm +/obj/machinery/door_timer/proc/set_picture(state) + if(maptext) + maptext = "" + cut_overlays() + add_overlay(mutable_appearance('icons/obj/status_display.dmi', state)) + + +//Checks to see if there's 1 line or 2, adds text-icons-numbers/letters over display +// Stolen from status_display +/obj/machinery/door_timer/proc/update_display(line1, line2) + line1 = uppertext(line1) + line2 = uppertext(line2) + var/new_text = {"
    [line1]
    [line2]
    "} + if(maptext != new_text) + maptext = new_text + +/obj/machinery/door_timer/ui_data() + var/list/data = list() + var/time_left = time_left(seconds = TRUE) + data["seconds"] = round(time_left % 60) + data["minutes"] = round((time_left - data["seconds"]) / 60) + data["timing"] = timing + data["flash_charging"] = FALSE + for(var/obj/machinery/flasher/F in targets) + if(F.last_flash && (F.last_flash + 150) > world.time) + data["flash_charging"] = TRUE + break + return data + + +/obj/machinery/door_timer/ui_act(action, params) + if(..()) + return + . = TRUE + + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return FALSE + + switch(action) + if("time") + var/value = text2num(params["adjust"]) + if(value) + . = set_timer(time_left()+value) + if("start") + timer_start() + if("stop") + timer_end(forced = TRUE) + if("flash") + for(var/obj/machinery/flasher/F in targets) + F.flash() + if("preset") + var/preset = params["preset"] + var/preset_time = time_left() + switch(preset) + if("short") + preset_time = PRESET_SHORT + if("medium") + preset_time = PRESET_MEDIUM + if("long") + preset_time = PRESET_LONG + . = set_timer(preset_time) + if(timing) + activation_time = world.time + else + . = FALSE + + +#undef PRESET_SHORT +#undef PRESET_MEDIUM +#undef PRESET_LONG + +#undef MAX_TIMER +#undef FONT_SIZE +#undef FONT_COLOR +#undef FONT_STYLE +#undef CHARS_PER_LINE diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 2f4746eff31..c3a7e06586b 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -1,430 +1,430 @@ -#define DOOR_CLOSE_WAIT 60 ///Default wait until doors autoclose -/obj/machinery/door - name = "door" - desc = "It opens and closes." - icon = 'icons/obj/doors/Doorint.dmi' - icon_state = "door1" - opacity = 1 - density = TRUE - move_resist = MOVE_FORCE_VERY_STRONG - layer = OPEN_DOOR_LAYER - power_channel = AREA_USAGE_ENVIRON - max_integrity = 350 - armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70) - CanAtmosPass = ATMOS_PASS_DENSITY - flags_1 = PREVENT_CLICK_UNDER_1 - ricochet_chance_mod = 0.8 - damage_deflection = 10 - - interaction_flags_atom = INTERACT_ATOM_UI_INTERACT - - var/secondsElectrified = MACHINE_NOT_ELECTRIFIED - var/shockedby - var/visible = TRUE - var/operating = FALSE - var/glass = FALSE - var/welded = FALSE - var/normalspeed = 1 - var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors - var/emergency = FALSE // Emergency access override - var/sub_door = FALSE // true if it's meant to go under another door. - var/closingLayer = CLOSED_DOOR_LAYER - var/autoclose = FALSE //does it automatically close after some time - var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. - var/locked = FALSE //whether the door is bolted or not. - var/assemblytype //the type of door frame to drop during deconstruction - var/datum/effect_system/spark_spread/spark_system - var/real_explosion_block //ignore this, just use explosion_block - var/red_alert_access = FALSE //if TRUE, this door will always open on red alert - var/poddoor = FALSE - var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access - var/safety_mode = FALSE ///Whether or not the airlock can be opened with bare hands while unpowered - var/can_crush = TRUE /// Whether or not the door can crush mobs. - -/obj/machinery/door/examine(mob/user) - . = ..() - if(red_alert_access) - if(GLOB.security_level >= SEC_LEVEL_RED) - . += "Due to a security threat, its access requirements have been lifted!" - else - . += "In the event of a red alert, its access requirements will automatically lift." - if(!poddoor) - . += "Its maintenance panel is screwed in place." - if(safety_mode) - . += "It has labels indicating that it has an emergency mechanism to open it with just your hands if there's no power." - -/obj/machinery/door/check_access_list(list/access_list) - if(red_alert_access && GLOB.security_level >= SEC_LEVEL_RED) - return TRUE - return ..() - -/obj/machinery/door/Initialize() - . = ..() - set_init_door_layer() - update_freelook_sight() - air_update_turf(1) - GLOB.airlocks += src - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(2, 1, src) - if(density) - flags_1 |= PREVENT_CLICK_UNDER_1 - else - flags_1 &= ~PREVENT_CLICK_UNDER_1 - - //doors only block while dense though so we have to use the proc - real_explosion_block = explosion_block - explosion_block = EXPLOSION_BLOCK_PROC - -/obj/machinery/door/proc/set_init_door_layer() - if(density) - layer = closingLayer - else - layer = initial(layer) - -/obj/machinery/door/Destroy() - update_freelook_sight() - GLOB.airlocks -= src - if(spark_system) - qdel(spark_system) - spark_system = null - return ..() - -/obj/machinery/door/proc/try_safety_unlock(mob/user) - if(safety_mode && !hasPower() && density) - to_chat(user, "You begin unlocking the airlock safety mechanism...") - if(do_after(user, 15 SECONDS, target = src)) - try_to_crowbar(null, user) - return TRUE - return FALSE - -/obj/machinery/door/Bumped(atom/movable/AM) - . = ..() - if(operating || (obj_flags & EMAGGED)) - return - if(ismob(AM)) - var/mob/B = AM - if((isdrone(B) || iscyborg(B)) && B.stat) - return - if(isliving(AM)) - var/mob/living/M = AM - if(world.time - M.last_bumped <= 10) - return //Can bump-open one airlock per second. This is to prevent shock spam. - M.last_bumped = world.time - if(M.restrained() && !check_access(null)) - return - if(try_safety_unlock(M)) - return - bumpopen(M) - return - - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(density) - if(mecha.occupant) - if(world.time - mecha.occupant.last_bumped <= 10) - return - mecha.occupant.last_bumped = world.time - if(mecha.occupant && (src.allowed(mecha.occupant) || src.check_access_list(mecha.operation_req_access))) - open() - else - do_animate("deny") - return - - if(isitem(AM)) - var/obj/item/I = AM - if(!density || (I.w_class < WEIGHT_CLASS_NORMAL && !LAZYLEN(I.GetAccess()))) - return - if(check_access(I)) - open() - else - do_animate("deny") - return - -/obj/machinery/door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/machinery/door/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(.) - return - - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return !opacity - -/obj/machinery/door/proc/bumpopen(mob/user) - if(operating) - return - add_fingerprint(user) - if(!requiresID()) - user = null - - if(density && !(obj_flags & EMAGGED)) - if(allowed(user)) - open() - else - do_animate("deny") - return - -/obj/machinery/door/attack_hand(mob/user) - . = ..() - if(.) - return - if(try_safety_unlock(user)) - return - return try_to_activate_door(user) - -/obj/machinery/door/attack_tk(mob/user) - if(requiresID() && !allowed(null)) - return - ..() - -/obj/machinery/door/proc/try_to_activate_door(mob/user) - add_fingerprint(user) - if(operating || (obj_flags & EMAGGED)) - return - if(!requiresID()) - user = null //so allowed(user) always succeeds - if(allowed(user)) - if(density) - open() - else - close() - return TRUE - if(density) - do_animate("deny") - -/obj/machinery/door/allowed(mob/M) - if(emergency) - return TRUE - if(unrestricted_side(M)) - return TRUE - return ..() - -/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) - return get_dir(src, M) & unres_sides - -/obj/machinery/door/proc/try_to_weld(obj/item/weldingtool/W, mob/user) - return - -/obj/machinery/door/proc/try_to_crowbar(obj/item/I, mob/user) - return - -/obj/machinery/door/attackby(obj/item/I, mob/user, params) - if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/fireaxe))) - var/forced_open = FALSE - if(istype(I, /obj/item/crowbar)) - var/obj/item/crowbar/C = I - forced_open = C.force_opens - try_to_crowbar(I, user, forced_open) - return TRUE - else if(I.tool_behaviour == TOOL_WELDER) - try_to_weld(I, user) - return TRUE - else if(!(I.item_flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) - try_to_activate_door(user) - return TRUE - return ..() - -/obj/machinery/door/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(. && obj_integrity > 0) - if(damage_amount >= 10 && prob(30)) - spark_system.start() - -/obj/machinery/door/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(glass) - playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) - else if(damage_amount) - playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/machinery/door/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(prob(20/severity) && (istype(src, /obj/machinery/door/airlock) || istype(src, /obj/machinery/door/window)) ) - INVOKE_ASYNC(src, .proc/open) - if(prob(severity*10 - 20)) - if(secondsElectrified == MACHINE_NOT_ELECTRIFIED) - secondsElectrified = MACHINE_ELECTRIFIED_PERMANENT - LAZYADD(shockedby, "\[[time_stamp()]\]EM Pulse") - addtimer(CALLBACK(src, .proc/unelectrify), 300) - -/obj/machinery/door/proc/unelectrify() - secondsElectrified = MACHINE_NOT_ELECTRIFIED - -/obj/machinery/door/update_icon_state() - if(density) - icon_state = "door1" - else - icon_state = "door0" - -/obj/machinery/door/proc/do_animate(animation) - switch(animation) - if("opening") - if(panel_open) - flick("o_doorc0", src) - else - flick("doorc0", src) - if("closing") - if(panel_open) - flick("o_doorc1", src) - else - flick("doorc1", src) - if("deny") - if(!machine_stat) - flick("door_deny", src) - - -/obj/machinery/door/proc/open() - if(!density) - return 1 - if(operating) - return - operating = TRUE - do_animate("opening") - set_opacity(0) - sleep(5) - density = FALSE - flags_1 &= ~PREVENT_CLICK_UNDER_1 - sleep(5) - layer = initial(layer) - update_icon() - set_opacity(0) - operating = FALSE - air_update_turf(1) - update_freelook_sight() - if(autoclose) - autoclose_in(DOOR_CLOSE_WAIT) - return 1 - -/obj/machinery/door/proc/close() - if(density) - return TRUE - if(operating || welded) - return - if(safe) - for(var/atom/movable/M in get_turf(src)) - if(M.density && M != src) //something is blocking the door - if(autoclose) - autoclose_in(DOOR_CLOSE_WAIT) - return - - operating = TRUE - - do_animate("closing") - layer = closingLayer - sleep(5) - density = TRUE - flags_1 |= PREVENT_CLICK_UNDER_1 - sleep(5) - update_icon() - if(visible && !glass) - set_opacity(1) - operating = FALSE - air_update_turf(1) - update_freelook_sight() - - if(!can_crush) - return TRUE - - if(safe) - CheckForMobs() - else - crush() - return TRUE - -/obj/machinery/door/proc/CheckForMobs() - if(locate(/mob/living) in get_turf(src)) - sleep(1) - open() - -/obj/machinery/door/proc/crush() - for(var/mob/living/L in get_turf(src)) - L.visible_message("[src] closes on [L], crushing [L.p_them()]!", "[src] closes on you and crushes you!") - if(iscarbon(L)) - var/mob/living/carbon/C = L - for(var/datum/wound/W in C.all_wounds) - W.crush(DOOR_CRUSH_DAMAGE) - if(isalien(L)) //For xenos - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) //Xenos go into crit after aproximately the same amount of crushes as humans. - L.emote("roar") - else if(ishuman(L)) //For humans - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - L.emote("scream") - L.Paralyze(100) - else if(ismonkey(L)) //For monkeys - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - L.Paralyze(100) - else //for simple_animals & borgs - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - var/turf/location = get_turf(src) - //add_blood doesn't work for borgs/xenos, but add_blood_floor does. - L.add_splatter_floor(location) - log_combat(src, L, "crushed") - for(var/obj/mecha/M in get_turf(src)) - M.take_damage(DOOR_CRUSH_DAMAGE) - log_combat(src, M, "crushed") - -/obj/machinery/door/proc/autoclose() - if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose) - close() - -/obj/machinery/door/proc/autoclose_in(wait) - addtimer(CALLBACK(src, .proc/autoclose), wait, TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE) - -/obj/machinery/door/proc/requiresID() - return 1 - -/obj/machinery/door/proc/hasPower() - return !(machine_stat & NOPOWER) - -/obj/machinery/door/proc/update_freelook_sight() - if(!glass && GLOB.cameranet) - GLOB.cameranet.updateVisibility(src, 0) - -/obj/machinery/door/BlockSuperconductivity() // All non-glass airlocks block heat, this is intended. - if(opacity || heat_proof) - return 1 - return 0 - -/obj/machinery/door/morgue - icon = 'icons/obj/doors/doormorgue.dmi' - -/obj/machinery/door/get_dumping_location(obj/item/storage/source,mob/user) - return null - -/obj/machinery/door/proc/lock() - return - -/obj/machinery/door/proc/unlock() - return - -/obj/machinery/door/proc/hostile_lockdown(mob/origin) - if(!machine_stat) //So that only powered doors are closed. - close() //Close ALL the doors! - -/obj/machinery/door/proc/disable_lockdown() - if(!machine_stat) //Opens only powered doors. - open() //Open everything! - -/obj/machinery/door/ex_act(severity, target) - //if it blows up a wall it should blow up a door - ..(severity ? max(1, severity - 1) : 0, target) - -/obj/machinery/door/GetExplosionBlock() - return density ? real_explosion_block : 0 - -/obj/machinery/door/power_change() - . = ..() - if(. && !(machine_stat & NOPOWER)) - autoclose_in(DOOR_CLOSE_WAIT) - -#undef DOOR_CLOSE_WAIT +#define DOOR_CLOSE_WAIT 60 ///Default wait until doors autoclose +/obj/machinery/door + name = "door" + desc = "It opens and closes." + icon = 'icons/obj/doors/Doorint.dmi' + icon_state = "door1" + opacity = 1 + density = TRUE + move_resist = MOVE_FORCE_VERY_STRONG + layer = OPEN_DOOR_LAYER + power_channel = AREA_USAGE_ENVIRON + max_integrity = 350 + armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70) + CanAtmosPass = ATMOS_PASS_DENSITY + flags_1 = PREVENT_CLICK_UNDER_1 + ricochet_chance_mod = 0.8 + damage_deflection = 10 + + interaction_flags_atom = INTERACT_ATOM_UI_INTERACT + + var/secondsElectrified = MACHINE_NOT_ELECTRIFIED + var/shockedby + var/visible = TRUE + var/operating = FALSE + var/glass = FALSE + var/welded = FALSE + var/normalspeed = 1 + var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors + var/emergency = FALSE // Emergency access override + var/sub_door = FALSE // true if it's meant to go under another door. + var/closingLayer = CLOSED_DOOR_LAYER + var/autoclose = FALSE //does it automatically close after some time + var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. + var/locked = FALSE //whether the door is bolted or not. + var/assemblytype //the type of door frame to drop during deconstruction + var/datum/effect_system/spark_spread/spark_system + var/real_explosion_block //ignore this, just use explosion_block + var/red_alert_access = FALSE //if TRUE, this door will always open on red alert + var/poddoor = FALSE + var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access + var/safety_mode = FALSE ///Whether or not the airlock can be opened with bare hands while unpowered + var/can_crush = TRUE /// Whether or not the door can crush mobs. + +/obj/machinery/door/examine(mob/user) + . = ..() + if(red_alert_access) + if(GLOB.security_level >= SEC_LEVEL_RED) + . += "Due to a security threat, its access requirements have been lifted!" + else + . += "In the event of a red alert, its access requirements will automatically lift." + if(!poddoor) + . += "Its maintenance panel is screwed in place." + if(safety_mode) + . += "It has labels indicating that it has an emergency mechanism to open it with just your hands if there's no power." + +/obj/machinery/door/check_access_list(list/access_list) + if(red_alert_access && GLOB.security_level >= SEC_LEVEL_RED) + return TRUE + return ..() + +/obj/machinery/door/Initialize() + . = ..() + set_init_door_layer() + update_freelook_sight() + air_update_turf(1) + GLOB.airlocks += src + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(2, 1, src) + if(density) + flags_1 |= PREVENT_CLICK_UNDER_1 + else + flags_1 &= ~PREVENT_CLICK_UNDER_1 + + //doors only block while dense though so we have to use the proc + real_explosion_block = explosion_block + explosion_block = EXPLOSION_BLOCK_PROC + +/obj/machinery/door/proc/set_init_door_layer() + if(density) + layer = closingLayer + else + layer = initial(layer) + +/obj/machinery/door/Destroy() + update_freelook_sight() + GLOB.airlocks -= src + if(spark_system) + qdel(spark_system) + spark_system = null + return ..() + +/obj/machinery/door/proc/try_safety_unlock(mob/user) + if(safety_mode && !hasPower() && density) + to_chat(user, "You begin unlocking the airlock safety mechanism...") + if(do_after(user, 15 SECONDS, target = src)) + try_to_crowbar(null, user) + return TRUE + return FALSE + +/obj/machinery/door/Bumped(atom/movable/AM) + . = ..() + if(operating || (obj_flags & EMAGGED)) + return + if(ismob(AM)) + var/mob/B = AM + if((isdrone(B) || iscyborg(B)) && B.stat) + return + if(isliving(AM)) + var/mob/living/M = AM + if(world.time - M.last_bumped <= 10) + return //Can bump-open one airlock per second. This is to prevent shock spam. + M.last_bumped = world.time + if(M.restrained() && !check_access(null)) + return + if(try_safety_unlock(M)) + return + bumpopen(M) + return + + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(density) + if(mecha.occupant) + if(world.time - mecha.occupant.last_bumped <= 10) + return + mecha.occupant.last_bumped = world.time + if(mecha.occupant && (src.allowed(mecha.occupant) || src.check_access_list(mecha.operation_req_access))) + open() + else + do_animate("deny") + return + + if(isitem(AM)) + var/obj/item/I = AM + if(!density || (I.w_class < WEIGHT_CLASS_NORMAL && !LAZYLEN(I.GetAccess()))) + return + if(check_access(I)) + open() + else + do_animate("deny") + return + +/obj/machinery/door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/machinery/door/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(.) + return + + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return !opacity + +/obj/machinery/door/proc/bumpopen(mob/user) + if(operating) + return + add_fingerprint(user) + if(!requiresID()) + user = null + + if(density && !(obj_flags & EMAGGED)) + if(allowed(user)) + open() + else + do_animate("deny") + return + +/obj/machinery/door/attack_hand(mob/user) + . = ..() + if(.) + return + if(try_safety_unlock(user)) + return + return try_to_activate_door(user) + +/obj/machinery/door/attack_tk(mob/user) + if(requiresID() && !allowed(null)) + return + ..() + +/obj/machinery/door/proc/try_to_activate_door(mob/user) + add_fingerprint(user) + if(operating || (obj_flags & EMAGGED)) + return + if(!requiresID()) + user = null //so allowed(user) always succeeds + if(allowed(user)) + if(density) + open() + else + close() + return TRUE + if(density) + do_animate("deny") + +/obj/machinery/door/allowed(mob/M) + if(emergency) + return TRUE + if(unrestricted_side(M)) + return TRUE + return ..() + +/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) + return get_dir(src, M) & unres_sides + +/obj/machinery/door/proc/try_to_weld(obj/item/weldingtool/W, mob/user) + return + +/obj/machinery/door/proc/try_to_crowbar(obj/item/I, mob/user) + return + +/obj/machinery/door/attackby(obj/item/I, mob/user, params) + if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/fireaxe))) + var/forced_open = FALSE + if(istype(I, /obj/item/crowbar)) + var/obj/item/crowbar/C = I + forced_open = C.force_opens + try_to_crowbar(I, user, forced_open) + return TRUE + else if(I.tool_behaviour == TOOL_WELDER) + try_to_weld(I, user) + return TRUE + else if(!(I.item_flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) + try_to_activate_door(user) + return TRUE + return ..() + +/obj/machinery/door/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(. && obj_integrity > 0) + if(damage_amount >= 10 && prob(30)) + spark_system.start() + +/obj/machinery/door/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(glass) + playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) + else if(damage_amount) + playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/machinery/door/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(prob(20/severity) && (istype(src, /obj/machinery/door/airlock) || istype(src, /obj/machinery/door/window)) ) + INVOKE_ASYNC(src, .proc/open) + if(prob(severity*10 - 20)) + if(secondsElectrified == MACHINE_NOT_ELECTRIFIED) + secondsElectrified = MACHINE_ELECTRIFIED_PERMANENT + LAZYADD(shockedby, "\[[time_stamp()]\]EM Pulse") + addtimer(CALLBACK(src, .proc/unelectrify), 300) + +/obj/machinery/door/proc/unelectrify() + secondsElectrified = MACHINE_NOT_ELECTRIFIED + +/obj/machinery/door/update_icon_state() + if(density) + icon_state = "door1" + else + icon_state = "door0" + +/obj/machinery/door/proc/do_animate(animation) + switch(animation) + if("opening") + if(panel_open) + flick("o_doorc0", src) + else + flick("doorc0", src) + if("closing") + if(panel_open) + flick("o_doorc1", src) + else + flick("doorc1", src) + if("deny") + if(!machine_stat) + flick("door_deny", src) + + +/obj/machinery/door/proc/open() + if(!density) + return 1 + if(operating) + return + operating = TRUE + do_animate("opening") + set_opacity(0) + sleep(5) + density = FALSE + flags_1 &= ~PREVENT_CLICK_UNDER_1 + sleep(5) + layer = initial(layer) + update_icon() + set_opacity(0) + operating = FALSE + air_update_turf(1) + update_freelook_sight() + if(autoclose) + autoclose_in(DOOR_CLOSE_WAIT) + return 1 + +/obj/machinery/door/proc/close() + if(density) + return TRUE + if(operating || welded) + return + if(safe) + for(var/atom/movable/M in get_turf(src)) + if(M.density && M != src) //something is blocking the door + if(autoclose) + autoclose_in(DOOR_CLOSE_WAIT) + return + + operating = TRUE + + do_animate("closing") + layer = closingLayer + sleep(5) + density = TRUE + flags_1 |= PREVENT_CLICK_UNDER_1 + sleep(5) + update_icon() + if(visible && !glass) + set_opacity(1) + operating = FALSE + air_update_turf(1) + update_freelook_sight() + + if(!can_crush) + return TRUE + + if(safe) + CheckForMobs() + else + crush() + return TRUE + +/obj/machinery/door/proc/CheckForMobs() + if(locate(/mob/living) in get_turf(src)) + sleep(1) + open() + +/obj/machinery/door/proc/crush() + for(var/mob/living/L in get_turf(src)) + L.visible_message("[src] closes on [L], crushing [L.p_them()]!", "[src] closes on you and crushes you!") + if(iscarbon(L)) + var/mob/living/carbon/C = L + for(var/datum/wound/W in C.all_wounds) + W.crush(DOOR_CRUSH_DAMAGE) + if(isalien(L)) //For xenos + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) //Xenos go into crit after aproximately the same amount of crushes as humans. + L.emote("roar") + else if(ishuman(L)) //For humans + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + L.emote("scream") + L.Paralyze(100) + else if(ismonkey(L)) //For monkeys + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + L.Paralyze(100) + else //for simple_animals & borgs + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + var/turf/location = get_turf(src) + //add_blood doesn't work for borgs/xenos, but add_blood_floor does. + L.add_splatter_floor(location) + log_combat(src, L, "crushed") + for(var/obj/mecha/M in get_turf(src)) + M.take_damage(DOOR_CRUSH_DAMAGE) + log_combat(src, M, "crushed") + +/obj/machinery/door/proc/autoclose() + if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose) + close() + +/obj/machinery/door/proc/autoclose_in(wait) + addtimer(CALLBACK(src, .proc/autoclose), wait, TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE) + +/obj/machinery/door/proc/requiresID() + return 1 + +/obj/machinery/door/proc/hasPower() + return !(machine_stat & NOPOWER) + +/obj/machinery/door/proc/update_freelook_sight() + if(!glass && GLOB.cameranet) + GLOB.cameranet.updateVisibility(src, 0) + +/obj/machinery/door/BlockSuperconductivity() // All non-glass airlocks block heat, this is intended. + if(opacity || heat_proof) + return 1 + return 0 + +/obj/machinery/door/morgue + icon = 'icons/obj/doors/doormorgue.dmi' + +/obj/machinery/door/get_dumping_location(obj/item/storage/source,mob/user) + return null + +/obj/machinery/door/proc/lock() + return + +/obj/machinery/door/proc/unlock() + return + +/obj/machinery/door/proc/hostile_lockdown(mob/origin) + if(!machine_stat) //So that only powered doors are closed. + close() //Close ALL the doors! + +/obj/machinery/door/proc/disable_lockdown() + if(!machine_stat) //Opens only powered doors. + open() //Open everything! + +/obj/machinery/door/ex_act(severity, target) + //if it blows up a wall it should blow up a door + ..(severity ? max(1, severity - 1) : 0, target) + +/obj/machinery/door/GetExplosionBlock() + return density ? real_explosion_block : 0 + +/obj/machinery/door/power_change() + . = ..() + if(. && !(machine_stat & NOPOWER)) + autoclose_in(DOOR_CLOSE_WAIT) + +#undef DOOR_CLOSE_WAIT diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 4ec92526c42..669dbd029bd 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -1,483 +1,483 @@ -#define CONSTRUCTION_COMPLETE 0 //No construction done - functioning as normal -#define CONSTRUCTION_PANEL_OPEN 1 //Maintenance panel is open, still functioning -#define CONSTRUCTION_WIRES_EXPOSED 2 //Cover plate is removed, wires are available -#define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove -#define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart - -/obj/machinery/door/firedoor - name = "firelock" - desc = "Apply crowbar." - icon = 'icons/obj/doors/Doorfireglass.dmi' - icon_state = "door_open" - opacity = FALSE - density = FALSE - max_integrity = 300 - resistance_flags = FIRE_PROOF - heat_proof = TRUE - glass = TRUE - sub_door = TRUE - explosion_block = 1 - safe = FALSE - layer = BELOW_OPEN_DOOR_LAYER - closingLayer = CLOSED_FIREDOOR_LAYER - assemblytype = /obj/structure/firelock_frame - armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70) - interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN - var/nextstate = null - var/boltslocked = TRUE - var/list/affecting_areas - -/obj/machinery/door/firedoor/Initialize() - . = ..() - CalculateAffectingAreas() - -/obj/machinery/door/firedoor/examine(mob/user) - . = ..() - if(!density) - . += "It is open, but could be pried closed." - else if(!welded) - . += "It is closed, but could be pried open. Deconstruction would require it to be welded shut." - else if(boltslocked) - . += "It is welded shut. The floor bolts have been locked by screws." - else - . += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor." - -/obj/machinery/door/firedoor/proc/CalculateAffectingAreas() - remove_from_areas() - affecting_areas = get_adjacent_open_areas(src) | get_area(src) - for(var/I in affecting_areas) - var/area/A = I - LAZYADD(A.firedoors, src) - -/obj/machinery/door/firedoor/closed - icon_state = "door_closed" - opacity = TRUE - density = TRUE - -//see also turf/AfterChange for adjacency shennanigans - -/obj/machinery/door/firedoor/proc/remove_from_areas() - if(affecting_areas) - for(var/I in affecting_areas) - var/area/A = I - LAZYREMOVE(A.firedoors, src) - -/obj/machinery/door/firedoor/Destroy() - remove_from_areas() - affecting_areas.Cut() - return ..() - -/obj/machinery/door/firedoor/Bumped(atom/movable/AM) - if(panel_open || operating) - return - if(!density) - return ..() - return FALSE - - -/obj/machinery/door/firedoor/power_change() - . = ..() - latetoggle() - -/obj/machinery/door/firedoor/attack_hand(mob/user) - . = ..() - if(.) - return - if(operating || !density) - return - user.changeNext_move(CLICK_CD_MELEE) - - user.visible_message("[user] bangs on \the [src].", \ - "You bang on \the [src].") - playsound(loc, 'sound/effects/glassknock.ogg', 10, FALSE, frequency = 32000) - -/obj/machinery/door/firedoor/attackby(obj/item/C, mob/user, params) - add_fingerprint(user) - if(operating) - return - - if(welded) - if(C.tool_behaviour == TOOL_WRENCH) - if(boltslocked) - to_chat(user, "There are screws locking the bolts in place!") - return - C.play_tool_sound(src) - user.visible_message("[user] starts undoing [src]'s bolts...", \ - "You start unfastening [src]'s floor bolts...") - if(!C.use_tool(src, user, 50)) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - user.visible_message("[user] unfastens [src]'s bolts.", \ - "You undo [src]'s floor bolts.") - deconstruct(TRUE) - return - if(C.tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ - "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") - C.play_tool_sound(src) - boltslocked = !boltslocked - return - - return ..() - -/obj/machinery/door/firedoor/try_to_activate_door(mob/user) - return - -/obj/machinery/door/firedoor/try_to_weld(obj/item/weldingtool/W, mob/user) - if(!W.tool_start_check(user, amount=0)) - return - user.visible_message("[user] starts [welded ? "unwelding" : "welding"] [src].", "You start welding [src].") - if(W.use_tool(src, user, 40, volume=50)) - welded = !welded - to_chat(user, "[user] [welded?"welds":"unwelds"] [src].", "You [welded ? "weld" : "unweld"] [src].") - update_icon() - -/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) - if(welded || operating) - return - - if(density) - open() - else - close() - -/obj/machinery/door/firedoor/attack_ai(mob/user) - add_fingerprint(user) - if(welded || operating || machine_stat & NOPOWER) - return TRUE - if(density) - open() - else - close() - return TRUE - -/obj/machinery/door/firedoor/attack_robot(mob/user) - return attack_ai(user) - -/obj/machinery/door/firedoor/attack_alien(mob/user) - add_fingerprint(user) - if(welded) - to_chat(user, "[src] refuses to budge!") - return - open() - -/obj/machinery/door/firedoor/do_animate(animation) - switch(animation) - if("opening") - flick("door_opening", src) - if("closing") - flick("door_closing", src) - -/obj/machinery/door/firedoor/update_icon_state() - if(density) - icon_state = "door_closed" - else - icon_state = "door_open" - -/obj/machinery/door/firedoor/update_overlays() - . = ..() - if(!welded) - return - if(density) - . += "welded" - else - . += "welded_open" - -/obj/machinery/door/firedoor/open() - . = ..() - latetoggle() - -/obj/machinery/door/firedoor/close() - . = ..() - latetoggle() - -/obj/machinery/door/firedoor/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - var/obj/structure/firelock_frame/F = new assemblytype(get_turf(src)) - if(disassembled) - F.constructionStep = CONSTRUCTION_PANEL_OPEN - else - F.constructionStep = CONSTRUCTION_WIRES_EXPOSED - F.obj_integrity = F.max_integrity * 0.5 - F.update_icon() - qdel(src) - - -/obj/machinery/door/firedoor/proc/latetoggle() - if(operating || machine_stat & NOPOWER || !nextstate) - return - switch(nextstate) - if(FIREDOOR_OPEN) - nextstate = null - open() - if(FIREDOOR_CLOSED) - nextstate = null - close() - -/obj/machinery/door/firedoor/border_only - icon = 'icons/obj/doors/edge_Doorfire.dmi' - can_crush = FALSE - flags_1 = ON_BORDER_1 - CanAtmosPass = ATMOS_PASS_PROC - -/obj/machinery/door/firedoor/border_only/closed - icon_state = "door_closed" - opacity = TRUE - density = TRUE - -/obj/machinery/door/firedoor/border_only/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(!(get_dir(loc, target) == dir)) //Make sure looking at appropriate border - return TRUE - -/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover as mob|obj, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(get_dir(loc, target) == dir) - return !density - else - return TRUE - -/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return TRUE - -/obj/machinery/door/firedoor/heavy - name = "heavy firelock" - icon = 'icons/obj/doors/Doorfire.dmi' - glass = FALSE - explosion_block = 2 - assemblytype = /obj/structure/firelock_frame/heavy - max_integrity = 550 - - -/obj/item/electronics/firelock - name = "firelock circuitry" - custom_price = 50 - desc = "A circuit board used in construction of firelocks." - icon_state = "mainboard" - -/obj/structure/firelock_frame - name = "firelock frame" - desc = "A partially completed firelock." - icon = 'icons/obj/doors/Doorfire.dmi' - icon_state = "frame1" - anchored = FALSE - density = TRUE - var/constructionStep = CONSTRUCTION_NOCIRCUIT - var/reinforced = 0 - -/obj/structure/firelock_frame/examine(mob/user) - . = ..() - switch(constructionStep) - if(CONSTRUCTION_PANEL_OPEN) - . += "It is unbolted from the floor. A small loosely connected metal plate is covering the wires." - if(!reinforced) - . += "It could be reinforced with plasteel." - if(CONSTRUCTION_WIRES_EXPOSED) - . += "The maintenance plate has been pried away, and wires are trailing." - if(CONSTRUCTION_GUTTED) - . += "The maintenance panel is missing wires and the circuit board is loosely connected." - if(CONSTRUCTION_NOCIRCUIT) - . += "There are no firelock electronics in the frame. The frame could be cut apart." - -/obj/structure/firelock_frame/update_icon_state() - icon_state = "frame[constructionStep]" - -/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) - switch(constructionStep) - if(CONSTRUCTION_PANEL_OPEN) - if(C.tool_behaviour == TOOL_CROWBAR) - C.play_tool_sound(src) - user.visible_message("[user] starts prying something out from [src]...", \ - "You begin prying out the wire cover...") - if(!C.use_tool(src, user, 50)) - return - if(constructionStep != CONSTRUCTION_PANEL_OPEN) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - user.visible_message("[user] pries out a metal plate from [src], exposing the wires.", \ - "You remove the cover plate from [src], exposing the wires.") - constructionStep = CONSTRUCTION_WIRES_EXPOSED - update_icon() - return - if(C.tool_behaviour == TOOL_WRENCH) - if(locate(/obj/machinery/door/firedoor) in get_turf(src)) - to_chat(user, "There's already a firelock there.") - return - C.play_tool_sound(src) - user.visible_message("[user] starts bolting down [src]...", \ - "You begin bolting [src]...") - if(!C.use_tool(src, user, 30)) - return - if(locate(/obj/machinery/door/firedoor) in get_turf(src)) - return - user.visible_message("[user] finishes the firelock.", \ - "You finish the firelock.") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(reinforced) - new /obj/machinery/door/firedoor/heavy(get_turf(src)) - else - new /obj/machinery/door/firedoor(get_turf(src)) - qdel(src) - return - if(istype(C, /obj/item/stack/sheet/plasteel)) - var/obj/item/stack/sheet/plasteel/P = C - if(reinforced) - to_chat(user, "[src] is already reinforced.") - return - if(P.get_amount() < 2) - to_chat(user, "You need more plasteel to reinforce [src].") - return - user.visible_message("[user] begins reinforcing [src]...", \ - "You begin reinforcing [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(do_after(user, 60, target = src)) - if(constructionStep != CONSTRUCTION_PANEL_OPEN || reinforced || P.get_amount() < 2 || !P) - return - user.visible_message("[user] reinforces [src].", \ - "You reinforce [src].") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - P.use(2) - reinforced = 1 - return - - if(CONSTRUCTION_WIRES_EXPOSED) - if(C.tool_behaviour == TOOL_WIRECUTTER) - C.play_tool_sound(src) - user.visible_message("[user] starts cutting the wires from [src]...", \ - "You begin removing [src]'s wires...") - if(!C.use_tool(src, user, 60)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - user.visible_message("[user] removes the wires from [src].", \ - "You remove the wiring from [src], exposing the circuit board.") - new/obj/item/stack/cable_coil(get_turf(src), 5) - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - if(C.tool_behaviour == TOOL_CROWBAR) - C.play_tool_sound(src) - user.visible_message("[user] starts prying a metal plate into [src]...", \ - "You begin prying the cover plate back onto [src]...") - if(!C.use_tool(src, user, 80)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - user.visible_message("[user] pries the metal plate into [src].", \ - "You pry [src]'s cover plate into place, hiding the wires.") - constructionStep = CONSTRUCTION_PANEL_OPEN - update_icon() - return - if(CONSTRUCTION_GUTTED) - if(C.tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user] begins removing the circuit board from [src]...", \ - "You begin prying out the circuit board from [src]...") - if(!C.use_tool(src, user, 50, volume=50)) - return - if(constructionStep != CONSTRUCTION_GUTTED) - return - user.visible_message("[user] removes [src]'s circuit board.", \ - "You remove the circuit board from [src].") - new /obj/item/electronics/firelock(drop_location()) - constructionStep = CONSTRUCTION_NOCIRCUIT - update_icon() - return - if(istype(C, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/B = C - if(B.get_amount() < 5) - to_chat(user, "You need more wires to add wiring to [src].") - return - user.visible_message("[user] begins wiring [src]...", \ - "You begin adding wires to [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(do_after(user, 60, target = src)) - if(constructionStep != CONSTRUCTION_GUTTED || B.get_amount() < 5 || !B) - return - user.visible_message("[user] adds wires to [src].", \ - "You wire [src].") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - B.use(5) - constructionStep = CONSTRUCTION_WIRES_EXPOSED - update_icon() - return - if(CONSTRUCTION_NOCIRCUIT) - if(C.tool_behaviour == TOOL_WELDER) - if(!C.tool_start_check(user, amount=1)) - return - user.visible_message("[user] begins cutting apart [src]'s frame...", \ - "You begin slicing [src] apart...") - - if(C.use_tool(src, user, 40, volume=50, amount=1)) - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - user.visible_message("[user] cuts apart [src]!", \ - "You cut [src] into metal.") - var/turf/T = get_turf(src) - new /obj/item/stack/sheet/metal(T, 3) - if(reinforced) - new /obj/item/stack/sheet/plasteel(T, 2) - qdel(src) - return - if(istype(C, /obj/item/electronics/firelock)) - user.visible_message("[user] starts adding [C] to [src]...", \ - "You begin adding a circuit board to [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(!do_after(user, 40, target = src)) - return - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - qdel(C) - user.visible_message("[user] adds a circuit to [src].", \ - "You insert and secure [C].") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - if(istype(C, /obj/item/electroadaptive_pseudocircuit)) - var/obj/item/electroadaptive_pseudocircuit/P = C - if(!P.adapt_circuit(user, 30)) - return - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a firelock circuit and slot it into the assembly.") - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - return ..() - -/obj/structure/firelock_frame/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(the_rcd.mode == RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 16) - else if((constructionStep == CONSTRUCTION_NOCIRCUIT) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) - return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) - return FALSE - -/obj/structure/firelock_frame/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_UPGRADE_SIMPLE_CIRCUITS) - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a firelock circuit and slot it into the assembly.") - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return TRUE - if(RCD_DECONSTRUCT) - to_chat(user, "You deconstruct [src].") - qdel(src) - return TRUE - return FALSE - -/obj/structure/firelock_frame/heavy - name = "heavy firelock frame" - reinforced = TRUE - -#undef CONSTRUCTION_COMPLETE -#undef CONSTRUCTION_PANEL_OPEN -#undef CONSTRUCTION_WIRES_EXPOSED -#undef CONSTRUCTION_GUTTED -#undef CONSTRUCTION_NOCIRCUIT +#define CONSTRUCTION_COMPLETE 0 //No construction done - functioning as normal +#define CONSTRUCTION_PANEL_OPEN 1 //Maintenance panel is open, still functioning +#define CONSTRUCTION_WIRES_EXPOSED 2 //Cover plate is removed, wires are available +#define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove +#define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart + +/obj/machinery/door/firedoor + name = "firelock" + desc = "Apply crowbar." + icon = 'icons/obj/doors/Doorfireglass.dmi' + icon_state = "door_open" + opacity = FALSE + density = FALSE + max_integrity = 300 + resistance_flags = FIRE_PROOF + heat_proof = TRUE + glass = TRUE + sub_door = TRUE + explosion_block = 1 + safe = FALSE + layer = BELOW_OPEN_DOOR_LAYER + closingLayer = CLOSED_FIREDOOR_LAYER + assemblytype = /obj/structure/firelock_frame + armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70) + interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN + var/nextstate = null + var/boltslocked = TRUE + var/list/affecting_areas + +/obj/machinery/door/firedoor/Initialize() + . = ..() + CalculateAffectingAreas() + +/obj/machinery/door/firedoor/examine(mob/user) + . = ..() + if(!density) + . += "It is open, but could be pried closed." + else if(!welded) + . += "It is closed, but could be pried open. Deconstruction would require it to be welded shut." + else if(boltslocked) + . += "It is welded shut. The floor bolts have been locked by screws." + else + . += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor." + +/obj/machinery/door/firedoor/proc/CalculateAffectingAreas() + remove_from_areas() + affecting_areas = get_adjacent_open_areas(src) | get_area(src) + for(var/I in affecting_areas) + var/area/A = I + LAZYADD(A.firedoors, src) + +/obj/machinery/door/firedoor/closed + icon_state = "door_closed" + opacity = TRUE + density = TRUE + +//see also turf/AfterChange for adjacency shennanigans + +/obj/machinery/door/firedoor/proc/remove_from_areas() + if(affecting_areas) + for(var/I in affecting_areas) + var/area/A = I + LAZYREMOVE(A.firedoors, src) + +/obj/machinery/door/firedoor/Destroy() + remove_from_areas() + affecting_areas.Cut() + return ..() + +/obj/machinery/door/firedoor/Bumped(atom/movable/AM) + if(panel_open || operating) + return + if(!density) + return ..() + return FALSE + + +/obj/machinery/door/firedoor/power_change() + . = ..() + latetoggle() + +/obj/machinery/door/firedoor/attack_hand(mob/user) + . = ..() + if(.) + return + if(operating || !density) + return + user.changeNext_move(CLICK_CD_MELEE) + + user.visible_message("[user] bangs on \the [src].", \ + "You bang on \the [src].") + playsound(loc, 'sound/effects/glassknock.ogg', 10, FALSE, frequency = 32000) + +/obj/machinery/door/firedoor/attackby(obj/item/C, mob/user, params) + add_fingerprint(user) + if(operating) + return + + if(welded) + if(C.tool_behaviour == TOOL_WRENCH) + if(boltslocked) + to_chat(user, "There are screws locking the bolts in place!") + return + C.play_tool_sound(src) + user.visible_message("[user] starts undoing [src]'s bolts...", \ + "You start unfastening [src]'s floor bolts...") + if(!C.use_tool(src, user, 50)) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + user.visible_message("[user] unfastens [src]'s bolts.", \ + "You undo [src]'s floor bolts.") + deconstruct(TRUE) + return + if(C.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ + "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") + C.play_tool_sound(src) + boltslocked = !boltslocked + return + + return ..() + +/obj/machinery/door/firedoor/try_to_activate_door(mob/user) + return + +/obj/machinery/door/firedoor/try_to_weld(obj/item/weldingtool/W, mob/user) + if(!W.tool_start_check(user, amount=0)) + return + user.visible_message("[user] starts [welded ? "unwelding" : "welding"] [src].", "You start welding [src].") + if(W.use_tool(src, user, 40, volume=50)) + welded = !welded + to_chat(user, "[user] [welded?"welds":"unwelds"] [src].", "You [welded ? "weld" : "unweld"] [src].") + update_icon() + +/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) + if(welded || operating) + return + + if(density) + open() + else + close() + +/obj/machinery/door/firedoor/attack_ai(mob/user) + add_fingerprint(user) + if(welded || operating || machine_stat & NOPOWER) + return TRUE + if(density) + open() + else + close() + return TRUE + +/obj/machinery/door/firedoor/attack_robot(mob/user) + return attack_ai(user) + +/obj/machinery/door/firedoor/attack_alien(mob/user) + add_fingerprint(user) + if(welded) + to_chat(user, "[src] refuses to budge!") + return + open() + +/obj/machinery/door/firedoor/do_animate(animation) + switch(animation) + if("opening") + flick("door_opening", src) + if("closing") + flick("door_closing", src) + +/obj/machinery/door/firedoor/update_icon_state() + if(density) + icon_state = "door_closed" + else + icon_state = "door_open" + +/obj/machinery/door/firedoor/update_overlays() + . = ..() + if(!welded) + return + if(density) + . += "welded" + else + . += "welded_open" + +/obj/machinery/door/firedoor/open() + . = ..() + latetoggle() + +/obj/machinery/door/firedoor/close() + . = ..() + latetoggle() + +/obj/machinery/door/firedoor/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + var/obj/structure/firelock_frame/F = new assemblytype(get_turf(src)) + if(disassembled) + F.constructionStep = CONSTRUCTION_PANEL_OPEN + else + F.constructionStep = CONSTRUCTION_WIRES_EXPOSED + F.obj_integrity = F.max_integrity * 0.5 + F.update_icon() + qdel(src) + + +/obj/machinery/door/firedoor/proc/latetoggle() + if(operating || machine_stat & NOPOWER || !nextstate) + return + switch(nextstate) + if(FIREDOOR_OPEN) + nextstate = null + open() + if(FIREDOOR_CLOSED) + nextstate = null + close() + +/obj/machinery/door/firedoor/border_only + icon = 'icons/obj/doors/edge_Doorfire.dmi' + can_crush = FALSE + flags_1 = ON_BORDER_1 + CanAtmosPass = ATMOS_PASS_PROC + +/obj/machinery/door/firedoor/border_only/closed + icon_state = "door_closed" + opacity = TRUE + density = TRUE + +/obj/machinery/door/firedoor/border_only/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(!(get_dir(loc, target) == dir)) //Make sure looking at appropriate border + return TRUE + +/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover as mob|obj, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(get_dir(loc, target) == dir) + return !density + else + return TRUE + +/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return TRUE + +/obj/machinery/door/firedoor/heavy + name = "heavy firelock" + icon = 'icons/obj/doors/Doorfire.dmi' + glass = FALSE + explosion_block = 2 + assemblytype = /obj/structure/firelock_frame/heavy + max_integrity = 550 + + +/obj/item/electronics/firelock + name = "firelock circuitry" + custom_price = 50 + desc = "A circuit board used in construction of firelocks." + icon_state = "mainboard" + +/obj/structure/firelock_frame + name = "firelock frame" + desc = "A partially completed firelock." + icon = 'icons/obj/doors/Doorfire.dmi' + icon_state = "frame1" + anchored = FALSE + density = TRUE + var/constructionStep = CONSTRUCTION_NOCIRCUIT + var/reinforced = 0 + +/obj/structure/firelock_frame/examine(mob/user) + . = ..() + switch(constructionStep) + if(CONSTRUCTION_PANEL_OPEN) + . += "It is unbolted from the floor. A small loosely connected metal plate is covering the wires." + if(!reinforced) + . += "It could be reinforced with plasteel." + if(CONSTRUCTION_WIRES_EXPOSED) + . += "The maintenance plate has been pried away, and wires are trailing." + if(CONSTRUCTION_GUTTED) + . += "The maintenance panel is missing wires and the circuit board is loosely connected." + if(CONSTRUCTION_NOCIRCUIT) + . += "There are no firelock electronics in the frame. The frame could be cut apart." + +/obj/structure/firelock_frame/update_icon_state() + icon_state = "frame[constructionStep]" + +/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) + switch(constructionStep) + if(CONSTRUCTION_PANEL_OPEN) + if(C.tool_behaviour == TOOL_CROWBAR) + C.play_tool_sound(src) + user.visible_message("[user] starts prying something out from [src]...", \ + "You begin prying out the wire cover...") + if(!C.use_tool(src, user, 50)) + return + if(constructionStep != CONSTRUCTION_PANEL_OPEN) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + user.visible_message("[user] pries out a metal plate from [src], exposing the wires.", \ + "You remove the cover plate from [src], exposing the wires.") + constructionStep = CONSTRUCTION_WIRES_EXPOSED + update_icon() + return + if(C.tool_behaviour == TOOL_WRENCH) + if(locate(/obj/machinery/door/firedoor) in get_turf(src)) + to_chat(user, "There's already a firelock there.") + return + C.play_tool_sound(src) + user.visible_message("[user] starts bolting down [src]...", \ + "You begin bolting [src]...") + if(!C.use_tool(src, user, 30)) + return + if(locate(/obj/machinery/door/firedoor) in get_turf(src)) + return + user.visible_message("[user] finishes the firelock.", \ + "You finish the firelock.") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(reinforced) + new /obj/machinery/door/firedoor/heavy(get_turf(src)) + else + new /obj/machinery/door/firedoor(get_turf(src)) + qdel(src) + return + if(istype(C, /obj/item/stack/sheet/plasteel)) + var/obj/item/stack/sheet/plasteel/P = C + if(reinforced) + to_chat(user, "[src] is already reinforced.") + return + if(P.get_amount() < 2) + to_chat(user, "You need more plasteel to reinforce [src].") + return + user.visible_message("[user] begins reinforcing [src]...", \ + "You begin reinforcing [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(do_after(user, 60, target = src)) + if(constructionStep != CONSTRUCTION_PANEL_OPEN || reinforced || P.get_amount() < 2 || !P) + return + user.visible_message("[user] reinforces [src].", \ + "You reinforce [src].") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + P.use(2) + reinforced = 1 + return + + if(CONSTRUCTION_WIRES_EXPOSED) + if(C.tool_behaviour == TOOL_WIRECUTTER) + C.play_tool_sound(src) + user.visible_message("[user] starts cutting the wires from [src]...", \ + "You begin removing [src]'s wires...") + if(!C.use_tool(src, user, 60)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + user.visible_message("[user] removes the wires from [src].", \ + "You remove the wiring from [src], exposing the circuit board.") + new/obj/item/stack/cable_coil(get_turf(src), 5) + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + if(C.tool_behaviour == TOOL_CROWBAR) + C.play_tool_sound(src) + user.visible_message("[user] starts prying a metal plate into [src]...", \ + "You begin prying the cover plate back onto [src]...") + if(!C.use_tool(src, user, 80)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + user.visible_message("[user] pries the metal plate into [src].", \ + "You pry [src]'s cover plate into place, hiding the wires.") + constructionStep = CONSTRUCTION_PANEL_OPEN + update_icon() + return + if(CONSTRUCTION_GUTTED) + if(C.tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user] begins removing the circuit board from [src]...", \ + "You begin prying out the circuit board from [src]...") + if(!C.use_tool(src, user, 50, volume=50)) + return + if(constructionStep != CONSTRUCTION_GUTTED) + return + user.visible_message("[user] removes [src]'s circuit board.", \ + "You remove the circuit board from [src].") + new /obj/item/electronics/firelock(drop_location()) + constructionStep = CONSTRUCTION_NOCIRCUIT + update_icon() + return + if(istype(C, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/B = C + if(B.get_amount() < 5) + to_chat(user, "You need more wires to add wiring to [src].") + return + user.visible_message("[user] begins wiring [src]...", \ + "You begin adding wires to [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(do_after(user, 60, target = src)) + if(constructionStep != CONSTRUCTION_GUTTED || B.get_amount() < 5 || !B) + return + user.visible_message("[user] adds wires to [src].", \ + "You wire [src].") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + B.use(5) + constructionStep = CONSTRUCTION_WIRES_EXPOSED + update_icon() + return + if(CONSTRUCTION_NOCIRCUIT) + if(C.tool_behaviour == TOOL_WELDER) + if(!C.tool_start_check(user, amount=1)) + return + user.visible_message("[user] begins cutting apart [src]'s frame...", \ + "You begin slicing [src] apart...") + + if(C.use_tool(src, user, 40, volume=50, amount=1)) + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + user.visible_message("[user] cuts apart [src]!", \ + "You cut [src] into metal.") + var/turf/T = get_turf(src) + new /obj/item/stack/sheet/metal(T, 3) + if(reinforced) + new /obj/item/stack/sheet/plasteel(T, 2) + qdel(src) + return + if(istype(C, /obj/item/electronics/firelock)) + user.visible_message("[user] starts adding [C] to [src]...", \ + "You begin adding a circuit board to [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(!do_after(user, 40, target = src)) + return + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + qdel(C) + user.visible_message("[user] adds a circuit to [src].", \ + "You insert and secure [C].") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + if(istype(C, /obj/item/electroadaptive_pseudocircuit)) + var/obj/item/electroadaptive_pseudocircuit/P = C + if(!P.adapt_circuit(user, 30)) + return + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a firelock circuit and slot it into the assembly.") + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + return ..() + +/obj/structure/firelock_frame/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + if(the_rcd.mode == RCD_DECONSTRUCT) + return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 16) + else if((constructionStep == CONSTRUCTION_NOCIRCUIT) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) + return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) + return FALSE + +/obj/structure/firelock_frame/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + switch(passed_mode) + if(RCD_UPGRADE_SIMPLE_CIRCUITS) + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a firelock circuit and slot it into the assembly.") + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return TRUE + if(RCD_DECONSTRUCT) + to_chat(user, "You deconstruct [src].") + qdel(src) + return TRUE + return FALSE + +/obj/structure/firelock_frame/heavy + name = "heavy firelock frame" + reinforced = TRUE + +#undef CONSTRUCTION_COMPLETE +#undef CONSTRUCTION_PANEL_OPEN +#undef CONSTRUCTION_WIRES_EXPOSED +#undef CONSTRUCTION_GUTTED +#undef CONSTRUCTION_NOCIRCUIT diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index dfe84db4e54..c38755b53f4 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -1,116 +1,116 @@ -/obj/machinery/door/poddoor - name = "blast door" - desc = "A heavy duty blast door that opens mechanically." - icon = 'icons/obj/doors/blastdoor.dmi' - icon_state = "closed" - var/id = 1 - layer = BLASTDOOR_LAYER - closingLayer = CLOSED_BLASTDOOR_LAYER - sub_door = TRUE - explosion_block = 3 - heat_proof = TRUE - safe = FALSE - max_integrity = 600 - armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - damage_deflection = 70 - poddoor = TRUE - -/obj/machinery/door/poddoor/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -/obj/machinery/door/poddoor/preopen - icon_state = "open" - density = FALSE - opacity = 0 - -/obj/machinery/door/poddoor/ert - name = "hardened blast door" - desc = "A heavy duty blast door that only opens for dire emergencies." - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -//special poddoors that open when emergency shuttle docks at centcom -/obj/machinery/door/poddoor/shuttledock - var/checkdir = 4 //door won't open if turf in this dir is `turftype` - var/turftype = /turf/open/space - -/obj/machinery/door/poddoor/shuttledock/proc/check() - var/turf/T = get_step(src, checkdir) - if(!istype(T, turftype)) - INVOKE_ASYNC(src, .proc/open) - else - INVOKE_ASYNC(src, .proc/close) - -/obj/machinery/door/poddoor/incinerator_toxmix - name = "combustion chamber vent" - id = INCINERATOR_TOXMIX_VENT - -/obj/machinery/door/poddoor/incinerator_atmos_main - name = "turbine vent" - id = INCINERATOR_ATMOS_MAINVENT - -/obj/machinery/door/poddoor/incinerator_atmos_aux - name = "combustion chamber vent" - id = INCINERATOR_ATMOS_AUXVENT - -/obj/machinery/door/poddoor/incinerator_syndicatelava_main - name = "turbine vent" - id = INCINERATOR_SYNDICATELAVA_MAINVENT - -/obj/machinery/door/poddoor/incinerator_syndicatelava_aux - name = "combustion chamber vent" - id = INCINERATOR_SYNDICATELAVA_AUXVENT - -/obj/machinery/door/poddoor/Bumped(atom/movable/AM) - if(density) - return 0 - else - return ..() - -//"BLAST" doors are obviously stronger than regular doors when it comes to BLASTS. -/obj/machinery/door/poddoor/ex_act(severity, target) - if(severity == 3) - return - ..() - -/obj/machinery/door/poddoor/do_animate(animation) - switch(animation) - if("opening") - flick("opening", src) - playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) - if("closing") - flick("closing", src) - playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) - -/obj/machinery/door/poddoor/update_icon_state() - if(density) - icon_state = "closed" - else - icon_state = "open" - -/obj/machinery/door/poddoor/try_to_activate_door(mob/user) - return - -/obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user) - if(machine_stat & NOPOWER) - open(TRUE) - -/obj/machinery/door/poddoor/attack_alien(mob/living/carbon/alien/humanoid/user) - if(density & !(resistance_flags & INDESTRUCTIBLE)) - add_fingerprint(user) - user.visible_message("[user] begins prying open [src].",\ - "You begin digging your claws into [src] with all your might!",\ - "You hear groaning metal...") - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - - var/time_to_open = 5 SECONDS - if(hasPower()) - time_to_open = 15 SECONDS - - if(do_after(user, time_to_open, TRUE, src)) - if(density && !open(TRUE)) //The airlock is still closed, but something prevented it opening. (Another player noticed and bolted/welded the airlock in time!) - to_chat(user, "Despite your efforts, [src] managed to resist your attempts to open it!") - - else - return ..() - +/obj/machinery/door/poddoor + name = "blast door" + desc = "A heavy duty blast door that opens mechanically." + icon = 'icons/obj/doors/blastdoor.dmi' + icon_state = "closed" + var/id = 1 + layer = BLASTDOOR_LAYER + closingLayer = CLOSED_BLASTDOOR_LAYER + sub_door = TRUE + explosion_block = 3 + heat_proof = TRUE + safe = FALSE + max_integrity = 600 + armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + damage_deflection = 70 + poddoor = TRUE + +/obj/machinery/door/poddoor/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +/obj/machinery/door/poddoor/preopen + icon_state = "open" + density = FALSE + opacity = 0 + +/obj/machinery/door/poddoor/ert + name = "hardened blast door" + desc = "A heavy duty blast door that only opens for dire emergencies." + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +//special poddoors that open when emergency shuttle docks at centcom +/obj/machinery/door/poddoor/shuttledock + var/checkdir = 4 //door won't open if turf in this dir is `turftype` + var/turftype = /turf/open/space + +/obj/machinery/door/poddoor/shuttledock/proc/check() + var/turf/T = get_step(src, checkdir) + if(!istype(T, turftype)) + INVOKE_ASYNC(src, .proc/open) + else + INVOKE_ASYNC(src, .proc/close) + +/obj/machinery/door/poddoor/incinerator_toxmix + name = "combustion chamber vent" + id = INCINERATOR_TOXMIX_VENT + +/obj/machinery/door/poddoor/incinerator_atmos_main + name = "turbine vent" + id = INCINERATOR_ATMOS_MAINVENT + +/obj/machinery/door/poddoor/incinerator_atmos_aux + name = "combustion chamber vent" + id = INCINERATOR_ATMOS_AUXVENT + +/obj/machinery/door/poddoor/incinerator_syndicatelava_main + name = "turbine vent" + id = INCINERATOR_SYNDICATELAVA_MAINVENT + +/obj/machinery/door/poddoor/incinerator_syndicatelava_aux + name = "combustion chamber vent" + id = INCINERATOR_SYNDICATELAVA_AUXVENT + +/obj/machinery/door/poddoor/Bumped(atom/movable/AM) + if(density) + return 0 + else + return ..() + +//"BLAST" doors are obviously stronger than regular doors when it comes to BLASTS. +/obj/machinery/door/poddoor/ex_act(severity, target) + if(severity == 3) + return + ..() + +/obj/machinery/door/poddoor/do_animate(animation) + switch(animation) + if("opening") + flick("opening", src) + playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) + if("closing") + flick("closing", src) + playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) + +/obj/machinery/door/poddoor/update_icon_state() + if(density) + icon_state = "closed" + else + icon_state = "open" + +/obj/machinery/door/poddoor/try_to_activate_door(mob/user) + return + +/obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user) + if(machine_stat & NOPOWER) + open(TRUE) + +/obj/machinery/door/poddoor/attack_alien(mob/living/carbon/alien/humanoid/user) + if(density & !(resistance_flags & INDESTRUCTIBLE)) + add_fingerprint(user) + user.visible_message("[user] begins prying open [src].",\ + "You begin digging your claws into [src] with all your might!",\ + "You hear groaning metal...") + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + + var/time_to_open = 5 SECONDS + if(hasPower()) + time_to_open = 15 SECONDS + + if(do_after(user, time_to_open, TRUE, src)) + if(density && !open(TRUE)) //The airlock is still closed, but something prevented it opening. (Another player noticed and bolted/welded the airlock in time!) + to_chat(user, "Despite your efforts, [src] managed to resist your attempts to open it!") + + else + return ..() + diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index fb75dc275fe..cb0262e2d8e 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -1,17 +1,17 @@ -/obj/machinery/door/poddoor/shutters - gender = PLURAL - name = "shutters" - desc = "Heavy duty metal shutters that open mechanically." - icon = 'icons/obj/doors/shutters.dmi' - layer = SHUTTER_LAYER - closingLayer = SHUTTER_LAYER - damage_deflection = 20 - -/obj/machinery/door/poddoor/shutters/preopen - icon_state = "open" - density = FALSE - opacity = 0 - -/obj/machinery/door/poddoor/shutters/indestructible - name = "hardened shutters" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF +/obj/machinery/door/poddoor/shutters + gender = PLURAL + name = "shutters" + desc = "Heavy duty metal shutters that open mechanically." + icon = 'icons/obj/doors/shutters.dmi' + layer = SHUTTER_LAYER + closingLayer = SHUTTER_LAYER + damage_deflection = 20 + +/obj/machinery/door/poddoor/shutters/preopen + icon_state = "open" + density = FALSE + opacity = 0 + +/obj/machinery/door/poddoor/shutters/indestructible + name = "hardened shutters" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm index 324d8087ce3..5ced36c775b 100644 --- a/code/game/machinery/doors/unpowered.dm +++ b/code/game/machinery/doors/unpowered.dm @@ -1,25 +1,25 @@ -/obj/machinery/door/unpowered - -/obj/machinery/door/unpowered/Bumped(atom/movable/AM) - if(src.locked) - return - ..() - return - - -/obj/machinery/door/unpowered/attackby(obj/item/I, mob/user, params) - if(locked) - return - else - return ..() - -/obj/machinery/door/unpowered/emag_act() - return - -/obj/machinery/door/unpowered/shuttle - icon = 'icons/turf/shuttle.dmi' - name = "door" - icon_state = "door1" - opacity = 1 - density = TRUE - explosion_block = 1 +/obj/machinery/door/unpowered + +/obj/machinery/door/unpowered/Bumped(atom/movable/AM) + if(src.locked) + return + ..() + return + + +/obj/machinery/door/unpowered/attackby(obj/item/I, mob/user, params) + if(locked) + return + else + return ..() + +/obj/machinery/door/unpowered/emag_act() + return + +/obj/machinery/door/unpowered/shuttle + icon = 'icons/turf/shuttle.dmi' + name = "door" + icon_state = "door1" + opacity = 1 + density = TRUE + explosion_block = 1 diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 3f419f6a2e8..59018c13b98 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -1,481 +1,481 @@ -/obj/machinery/door/window - name = "interior door" - desc = "A strong door." - icon = 'icons/obj/doors/windoor.dmi' - icon_state = "left" - layer = ABOVE_WINDOW_LAYER - closingLayer = ABOVE_WINDOW_LAYER - resistance_flags = ACID_PROOF - var/base_state = "left" - max_integrity = 150 //If you change this, consider changing ../door/window/brigdoor/ max_integrity at the bottom of this .dm file - integrity_failure = 0 - armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100) - visible = FALSE - flags_1 = ON_BORDER_1 - opacity = 0 - CanAtmosPass = ATMOS_PASS_PROC - interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN - var/obj/item/electronics/airlock/electronics = null - var/reinf = 0 - var/shards = 2 - var/rods = 2 - var/cable = 1 - var/list/debris = list() - -/obj/machinery/door/window/Initialize(mapload, set_dir) - . = ..() - flags_1 &= ~PREVENT_CLICK_UNDER_1 - if(set_dir) - setDir(set_dir) - if(req_access && req_access.len) - icon_state = "[icon_state]" - base_state = icon_state - for(var/i in 1 to shards) - debris += new /obj/item/shard(src) - if(rods) - debris += new /obj/item/stack/rods(src, rods) - if(cable) - debris += new /obj/item/stack/cable_coil(src, cable) - -/obj/machinery/door/window/ComponentInitialize() - . = ..() - AddComponent(/datum/component/ntnet_interface) - -/obj/machinery/door/window/Destroy() - density = FALSE - QDEL_LIST(debris) - if(obj_integrity == 0) - playsound(src, "shatter", 70, TRUE) - electronics = null - return ..() - -/obj/machinery/door/window/update_icon_state() - if(density) - icon_state = base_state - else - icon_state = "[base_state]open" - -/obj/machinery/door/window/proc/open_and_close() - if(!open()) - return - autoclose = TRUE - if(check_access(null)) - sleep(50) - else //secure doors close faster - sleep(20) - if(!density && autoclose) //did someone change state while we slept? - close() - -/obj/machinery/door/window/Bumped(atom/movable/AM) - if( operating || !density ) - return - if (!( ismob(AM) )) - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(mecha.occupant && allowed(mecha.occupant)) - open_and_close() - else - do_animate("deny") - return - if (!( SSticker )) - return - var/mob/M = AM - if(M.restrained() || ((isdrone(M) || iscyborg(M)) && M.stat)) - return - bumpopen(M) - -/obj/machinery/door/window/bumpopen(mob/user) - if( operating || !density ) - return - add_fingerprint(user) - if(!requiresID()) - user = null - - if(allowed(user)) - open_and_close() - else - do_animate("deny") - return - -/obj/machinery/door/window/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) - return FALSE - else - return TRUE - -/obj/machinery/door/window/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return 1 - -//used in the AStar algorithm to determinate if the turf the door is on is passable -/obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir) - return !density || (dir != to_dir) || (check_access(ID) && hasPower()) - -/obj/machinery/door/window/CheckExit(atom/movable/mover as mob|obj, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) - return !density - else - return 1 - -/obj/machinery/door/window/open(forced=FALSE) - if (operating) //doors can still open when emag-disabled - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(obj_flags & EMAGGED) - return 0 - if(!operating) //in case of emag - operating = TRUE - do_animate("opening") - playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) - icon_state ="[base_state]open" - sleep(10) - density = FALSE - air_update_turf(1) - update_freelook_sight() - - if(operating == 1) //emag again - operating = FALSE - return 1 - -/obj/machinery/door/window/close(forced=FALSE) - if (operating) - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(obj_flags & EMAGGED) - return 0 - operating = TRUE - do_animate("closing") - playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) - icon_state = base_state - - density = TRUE - air_update_turf(1) - update_freelook_sight() - sleep(10) - - operating = FALSE - return 1 - -/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src, 'sound/effects/glasshit.ogg', 90, TRUE) - if(BURN) - playsound(src, 'sound/items/welder.ogg', 100, TRUE) - - -/obj/machinery/door/window/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1) && !disassembled) - for(var/obj/fragment in debris) - fragment.forceMove(get_turf(src)) - transfer_fingerprints_to(fragment) - debris -= fragment - qdel(src) - -/obj/machinery/door/window/narsie_act() - add_atom_colour("#7D1919", FIXED_COLOUR_PRIORITY) - -/obj/machinery/door/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > T0C + (reinf ? 1600 : 800)) - take_damage(round(exposed_volume / 200), BURN, 0, 0) - ..() - -/obj/machinery/door/window/emag_act(mob/user) - if(!operating && density && !(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - operating = TRUE - flick("[base_state]spark", src) - playsound(src, "sparks", 75, TRUE) - sleep(6) - operating = FALSE - desc += "
    Its access panel is smoking slightly." - open(2) - -/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) - - if(operating) - return - - add_fingerprint(user) - if(!(flags_1&NODECONSTRUCT_1)) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(density || operating) - to_chat(user, "You need to open the door to access the maintenance panel!") - return - I.play_tool_sound(src) - panel_open = !panel_open - to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [name].") - return - - if(I.tool_behaviour == TOOL_CROWBAR) - if(panel_open && !density && !operating) - user.visible_message("[user] removes the electronics from the [name].", \ - "You start to remove electronics from the [name]...") - if(I.use_tool(src, user, 40, volume=50)) - if(panel_open && !density && !operating && loc) - var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) - switch(base_state) - if("left") - WA.facing = "l" - if("right") - WA.facing = "r" - if("leftsecure") - WA.facing = "l" - WA.secure = TRUE - if("rightsecure") - WA.facing = "r" - WA.secure = TRUE - WA.setAnchored(TRUE) - WA.state= "02" - WA.setDir(dir) - WA.ini_dir = dir - WA.update_icon() - WA.created_name = name - - if(obj_flags & EMAGGED) - to_chat(user, "You discard the damaged electronics.") - qdel(src) - return - - to_chat(user, "You remove the airlock electronics.") - - var/obj/item/electronics/airlock/ae - if(!electronics) - ae = new/obj/item/electronics/airlock(drop_location()) - if(req_one_access) - ae.one_access = 1 - ae.accesses = req_one_access - else - ae.accesses = req_access - else - ae = electronics - electronics = null - ae.forceMove(drop_location()) - - qdel(src) - return - return ..() - -/obj/machinery/door/window/interact(mob/user) //for sillycones - try_to_activate_door(user) - -/obj/machinery/door/window/try_to_activate_door(mob/user) - if (..()) - autoclose = FALSE - -/obj/machinery/door/window/try_to_crowbar(obj/item/I, mob/user) - if(!hasPower()) - if(density) - open(2) - else - close(2) - else - to_chat(user, "The door's motors resist your efforts to force it!") - -/obj/machinery/door/window/do_animate(animation) - switch(animation) - if("opening") - flick("[base_state]opening", src) - if("closing") - flick("[base_state]closing", src) - if("deny") - flick("[base_state]deny", src) - -/obj/machinery/door/window/check_access_ntnet(datum/netdata/data) - return !requiresID() || ..() - -/obj/machinery/door/window/ntnet_receive(datum/netdata/data) - // Check if the airlock is powered. - if(!hasPower()) - return - - // Check packet access level. - if(!check_access_ntnet(data)) - return - - // Handle received packet. - var/command = lowertext(data.data["data"]) - var/command_value = lowertext(data.data["data_secondary"]) - switch(command) - if("open") - if(command_value == "on" && !density) - return - - if(command_value == "off" && density) - return - - if(density) - INVOKE_ASYNC(src, .proc/open) - else - INVOKE_ASYNC(src, .proc/close) - if("touch") - INVOKE_ASYNC(src, .proc/open_and_close) - -/obj/machinery/door/window/brigdoor - name = "secure door" - icon_state = "leftsecure" - base_state = "leftsecure" - var/id = null - max_integrity = 300 //Stronger doors for prison (regular window door health is 200) - reinf = 1 - explosion_block = 1 - -/obj/machinery/door/window/brigdoor/security/cell - name = "cell door" - desc = "For keeping in criminal scum." - req_access = list(ACCESS_BRIG) - -/obj/machinery/door/window/brigdoor/security/holding - name = "holding cell door" - req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER) //love for the lawyer - -/obj/machinery/door/window/northleft - dir = NORTH - -/obj/machinery/door/window/eastleft - dir = EAST - -/obj/machinery/door/window/westleft - dir = WEST - -/obj/machinery/door/window/southleft - dir = SOUTH - -/obj/machinery/door/window/northright - dir = NORTH - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/eastright - dir = EAST - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/westright - dir = WEST - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/southright - dir = SOUTH - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/brigdoor/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/security/cell/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/security/cell/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/security/cell/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/security/cell/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/security/holding/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/security/holding/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/security/holding/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/security/holding/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" +/obj/machinery/door/window + name = "interior door" + desc = "A strong door." + icon = 'icons/obj/doors/windoor.dmi' + icon_state = "left" + layer = ABOVE_WINDOW_LAYER + closingLayer = ABOVE_WINDOW_LAYER + resistance_flags = ACID_PROOF + var/base_state = "left" + max_integrity = 150 //If you change this, consider changing ../door/window/brigdoor/ max_integrity at the bottom of this .dm file + integrity_failure = 0 + armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100) + visible = FALSE + flags_1 = ON_BORDER_1 + opacity = 0 + CanAtmosPass = ATMOS_PASS_PROC + interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN + var/obj/item/electronics/airlock/electronics = null + var/reinf = 0 + var/shards = 2 + var/rods = 2 + var/cable = 1 + var/list/debris = list() + +/obj/machinery/door/window/Initialize(mapload, set_dir) + . = ..() + flags_1 &= ~PREVENT_CLICK_UNDER_1 + if(set_dir) + setDir(set_dir) + if(req_access && req_access.len) + icon_state = "[icon_state]" + base_state = icon_state + for(var/i in 1 to shards) + debris += new /obj/item/shard(src) + if(rods) + debris += new /obj/item/stack/rods(src, rods) + if(cable) + debris += new /obj/item/stack/cable_coil(src, cable) + +/obj/machinery/door/window/ComponentInitialize() + . = ..() + AddComponent(/datum/component/ntnet_interface) + +/obj/machinery/door/window/Destroy() + density = FALSE + QDEL_LIST(debris) + if(obj_integrity == 0) + playsound(src, "shatter", 70, TRUE) + electronics = null + return ..() + +/obj/machinery/door/window/update_icon_state() + if(density) + icon_state = base_state + else + icon_state = "[base_state]open" + +/obj/machinery/door/window/proc/open_and_close() + if(!open()) + return + autoclose = TRUE + if(check_access(null)) + sleep(50) + else //secure doors close faster + sleep(20) + if(!density && autoclose) //did someone change state while we slept? + close() + +/obj/machinery/door/window/Bumped(atom/movable/AM) + if( operating || !density ) + return + if (!( ismob(AM) )) + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(mecha.occupant && allowed(mecha.occupant)) + open_and_close() + else + do_animate("deny") + return + if (!( SSticker )) + return + var/mob/M = AM + if(M.restrained() || ((isdrone(M) || iscyborg(M)) && M.stat)) + return + bumpopen(M) + +/obj/machinery/door/window/bumpopen(mob/user) + if( operating || !density ) + return + add_fingerprint(user) + if(!requiresID()) + user = null + + if(allowed(user)) + open_and_close() + else + do_animate("deny") + return + +/obj/machinery/door/window/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + return FALSE + else + return TRUE + +/obj/machinery/door/window/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return 1 + +//used in the AStar algorithm to determinate if the turf the door is on is passable +/obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir) + return !density || (dir != to_dir) || (check_access(ID) && hasPower()) + +/obj/machinery/door/window/CheckExit(atom/movable/mover as mob|obj, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) + return !density + else + return 1 + +/obj/machinery/door/window/open(forced=FALSE) + if (operating) //doors can still open when emag-disabled + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(obj_flags & EMAGGED) + return 0 + if(!operating) //in case of emag + operating = TRUE + do_animate("opening") + playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) + icon_state ="[base_state]open" + sleep(10) + density = FALSE + air_update_turf(1) + update_freelook_sight() + + if(operating == 1) //emag again + operating = FALSE + return 1 + +/obj/machinery/door/window/close(forced=FALSE) + if (operating) + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(obj_flags & EMAGGED) + return 0 + operating = TRUE + do_animate("closing") + playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) + icon_state = base_state + + density = TRUE + air_update_turf(1) + update_freelook_sight() + sleep(10) + + operating = FALSE + return 1 + +/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src, 'sound/effects/glasshit.ogg', 90, TRUE) + if(BURN) + playsound(src, 'sound/items/welder.ogg', 100, TRUE) + + +/obj/machinery/door/window/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1) && !disassembled) + for(var/obj/fragment in debris) + fragment.forceMove(get_turf(src)) + transfer_fingerprints_to(fragment) + debris -= fragment + qdel(src) + +/obj/machinery/door/window/narsie_act() + add_atom_colour("#7D1919", FIXED_COLOUR_PRIORITY) + +/obj/machinery/door/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > T0C + (reinf ? 1600 : 800)) + take_damage(round(exposed_volume / 200), BURN, 0, 0) + ..() + +/obj/machinery/door/window/emag_act(mob/user) + if(!operating && density && !(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + operating = TRUE + flick("[base_state]spark", src) + playsound(src, "sparks", 75, TRUE) + sleep(6) + operating = FALSE + desc += "
    Its access panel is smoking slightly." + open(2) + +/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) + + if(operating) + return + + add_fingerprint(user) + if(!(flags_1&NODECONSTRUCT_1)) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(density || operating) + to_chat(user, "You need to open the door to access the maintenance panel!") + return + I.play_tool_sound(src) + panel_open = !panel_open + to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [name].") + return + + if(I.tool_behaviour == TOOL_CROWBAR) + if(panel_open && !density && !operating) + user.visible_message("[user] removes the electronics from the [name].", \ + "You start to remove electronics from the [name]...") + if(I.use_tool(src, user, 40, volume=50)) + if(panel_open && !density && !operating && loc) + var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) + switch(base_state) + if("left") + WA.facing = "l" + if("right") + WA.facing = "r" + if("leftsecure") + WA.facing = "l" + WA.secure = TRUE + if("rightsecure") + WA.facing = "r" + WA.secure = TRUE + WA.setAnchored(TRUE) + WA.state= "02" + WA.setDir(dir) + WA.ini_dir = dir + WA.update_icon() + WA.created_name = name + + if(obj_flags & EMAGGED) + to_chat(user, "You discard the damaged electronics.") + qdel(src) + return + + to_chat(user, "You remove the airlock electronics.") + + var/obj/item/electronics/airlock/ae + if(!electronics) + ae = new/obj/item/electronics/airlock(drop_location()) + if(req_one_access) + ae.one_access = 1 + ae.accesses = req_one_access + else + ae.accesses = req_access + else + ae = electronics + electronics = null + ae.forceMove(drop_location()) + + qdel(src) + return + return ..() + +/obj/machinery/door/window/interact(mob/user) //for sillycones + try_to_activate_door(user) + +/obj/machinery/door/window/try_to_activate_door(mob/user) + if (..()) + autoclose = FALSE + +/obj/machinery/door/window/try_to_crowbar(obj/item/I, mob/user) + if(!hasPower()) + if(density) + open(2) + else + close(2) + else + to_chat(user, "The door's motors resist your efforts to force it!") + +/obj/machinery/door/window/do_animate(animation) + switch(animation) + if("opening") + flick("[base_state]opening", src) + if("closing") + flick("[base_state]closing", src) + if("deny") + flick("[base_state]deny", src) + +/obj/machinery/door/window/check_access_ntnet(datum/netdata/data) + return !requiresID() || ..() + +/obj/machinery/door/window/ntnet_receive(datum/netdata/data) + // Check if the airlock is powered. + if(!hasPower()) + return + + // Check packet access level. + if(!check_access_ntnet(data)) + return + + // Handle received packet. + var/command = lowertext(data.data["data"]) + var/command_value = lowertext(data.data["data_secondary"]) + switch(command) + if("open") + if(command_value == "on" && !density) + return + + if(command_value == "off" && density) + return + + if(density) + INVOKE_ASYNC(src, .proc/open) + else + INVOKE_ASYNC(src, .proc/close) + if("touch") + INVOKE_ASYNC(src, .proc/open_and_close) + +/obj/machinery/door/window/brigdoor + name = "secure door" + icon_state = "leftsecure" + base_state = "leftsecure" + var/id = null + max_integrity = 300 //Stronger doors for prison (regular window door health is 200) + reinf = 1 + explosion_block = 1 + +/obj/machinery/door/window/brigdoor/security/cell + name = "cell door" + desc = "For keeping in criminal scum." + req_access = list(ACCESS_BRIG) + +/obj/machinery/door/window/brigdoor/security/holding + name = "holding cell door" + req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER) //love for the lawyer + +/obj/machinery/door/window/northleft + dir = NORTH + +/obj/machinery/door/window/eastleft + dir = EAST + +/obj/machinery/door/window/westleft + dir = WEST + +/obj/machinery/door/window/southleft + dir = SOUTH + +/obj/machinery/door/window/northright + dir = NORTH + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/eastright + dir = EAST + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/westright + dir = WEST + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/southright + dir = SOUTH + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/brigdoor/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/security/cell/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/security/cell/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/security/cell/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/security/cell/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/security/holding/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/security/holding/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/security/holding/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/security/holding/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" diff --git a/code/game/machinery/doppler_array.dm b/code/game/machinery/doppler_array.dm index b834c66ff68..2494dd6faa6 100644 --- a/code/game/machinery/doppler_array.dm +++ b/code/game/machinery/doppler_array.dm @@ -1,243 +1,243 @@ -#define PRINTER_TIMEOUT 40 - -/obj/machinery/doppler_array - name = "tachyon-doppler array" - desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n" - icon = 'icons/obj/machines/research.dmi' - icon_state = "tdoppler" - density = TRUE - verb_say = "states coldly" - ui_x = 500 - ui_y = 225 - var/cooldown = 10 - var/next_announce = 0 - var/max_dist = 150 - /// Number which will be part of the name of the next record, increased by one for each already created record - var/record_number = 1 - /// Cooldown for the print function - var/printer_ready = 0 - /// List of all explosion records in the form of /datum/data/tachyon_record - var/list/records = list() - -/obj/machinery/doppler_array/Initialize() - . = ..() - RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion) - printer_ready = world.time + PRINTER_TIMEOUT - -/obj/machinery/doppler_array/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE,null,null,CALLBACK(src,.proc/rot_message)) - -/datum/data/tachyon_record - name = "Log Recording" - var/timestamp - var/coordinates = "" - var/displacement = 0 - var/factual_radius = list() - var/theory_radius = list() - -/obj/machinery/doppler_array/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "TachyonArray", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/doppler_array/ui_data(mob/user) - var/list/data = list() - data["records"] = list() - for(var/datum/data/tachyon_record/R in records) - var/list/record_data = list( - name = R.name, - timestamp = R.timestamp, - coordinates = R.coordinates, - displacement = R.displacement, - factual_epicenter_radius = R.factual_radius["epicenter_radius"], - factual_outer_radius = R.factual_radius["outer_radius"], - factual_shockwave_radius = R.factual_radius["shockwave_radius"], - theory_epicenter_radius = R.theory_radius["epicenter_radius"], - theory_outer_radius = R.theory_radius["outer_radius"], - theory_shockwave_radius = R.theory_radius["shockwave_radius"], - ref = REF(R) - ) - data["records"] += list(record_data) - return data - -/obj/machinery/doppler_array/ui_act(action, list/params) - if(..()) - return - - switch(action) - if("delete_record") - var/datum/data/tachyon_record/record = locate(params["ref"]) in records - if(!records || !(record in records)) - return - records -= record - return TRUE - if("print_record") - var/datum/data/tachyon_record/record = locate(params["ref"]) in records - if(!records || !(record in records)) - return - print(usr, record) - return TRUE - -/obj/machinery/doppler_array/proc/print(mob/user, datum/data/tachyon_record/record) - if(!record) - return - if(printer_ready < world.time) - printer_ready = world.time + PRINTER_TIMEOUT - new /obj/item/paper/record_printout(loc, record) - else if(user) - to_chat(user, "[src] is busy right now.") - -/obj/item/paper/record_printout - name = "paper - Log Recording" - -/obj/item/paper/record_printout/Initialize(mapload, datum/data/tachyon_record/record) - . = ..() - - if(record) - name = "paper - [record.name]" - - info += {"

    [record.name]

    -
    • Timestamp: [record.timestamp]
    • -
    • Coordinates: [record.coordinates]
    • -
    • Displacement: [record.displacement] seconds
    • -
    • Epicenter Radius: [record.factual_radius["epicenter_radius"]]
    • -
    • Outer Radius: [record.factual_radius["outer_radius"]]
    • -
    • Shockwave Radius: [record.factual_radius["shockwave_radius"]]
    "} - - if(length(record.theory_radius)) - info += {"
    • Theoretical Epicenter Radius: [record.theory_radius["epicenter_radius"]]
    • -
    • Theoretical Outer Radius: [record.theory_radius["outer_radius"]]
    • -
    • Theoretical Shockwave Radius: [record.theory_radius["shockwave_radius"]]
    "} - - update_icon() - -/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - if(!anchored && !isinspace()) - anchored = TRUE - power_change() - to_chat(user, "You fasten [src].") - else if(anchored) - anchored = FALSE - power_change() - to_chat(user, "You unfasten [src].") - I.play_tool_sound(src) - return - return ..() - -/obj/machinery/doppler_array/proc/rot_message(mob/user) - to_chat(user, "You adjust [src]'s dish to face to the [dir2text(dir)].") - playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) - -/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, - took, orig_dev_range, orig_heavy_range, orig_light_range) - if(machine_stat & NOPOWER) - return FALSE - var/turf/zone = get_turf(src) - if(zone.z != epicenter.z) - return FALSE - - if(next_announce > world.time) - return FALSE - next_announce = world.time + cooldown - - var/distance = get_dist(epicenter, zone) - var/direct = get_dir(zone, epicenter) - - if(distance > max_dist) - return FALSE - if(!(direct & dir)) - return FALSE - - var/datum/data/tachyon_record/R = new /datum/data/tachyon_record() - R.name = "Log Recording #[record_number]" - R.timestamp = station_time_timestamp() - R.coordinates = "[epicenter.x], [epicenter.y]" - R.displacement = took - R.factual_radius["epicenter_radius"] = devastation_range - R.factual_radius["outer_radius"] = heavy_impact_range - R.factual_radius["shockwave_radius"] = light_impact_range - - var/list/messages = list("Explosive disturbance detected.", - "Epicenter at: grid ([epicenter.x], [epicenter.y]). Temporal displacement of tachyons: [took] seconds.", - "Factual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].") - - // If the bomb was capped, say its theoretical size. - if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) - messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." - R.theory_radius["epicenter_radius"] = orig_dev_range - R.theory_radius["outer_radius"] = orig_heavy_range - R.theory_radius["shockwave_radius"] = orig_light_range - - for(var/message in messages) - say(message) - - record_number++ - records += R - return TRUE - -/obj/machinery/doppler_array/powered() - if(!anchored) - return FALSE - return ..() - -/obj/machinery/doppler_array/update_icon_state() - if(machine_stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - else if(powered()) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]-off" - -/obj/machinery/doppler_array/research - name = "tachyon-doppler research array" - desc = "A specialized tachyon-doppler bomb detection array that uses the results of the highest yield of explosions for research." - var/datum/techweb/linked_techweb - -/obj/machinery/doppler_array/research/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, - took, orig_dev_range, orig_heavy_range, orig_light_range) //probably needs a way to ignore admin explosives later on - . = ..() - if(!.) - return - if(!istype(linked_techweb)) - say("Warning: No linked research system!") - return - - var/point_gain = 0 - /*****The Point Calculator*****/ - if(orig_light_range < 10) - say("Explosion not large enough for research calculations.") - return - else if(orig_light_range < 4500) - point_gain = (83300 * orig_light_range) / (orig_light_range + 3000) - else - point_gain = TECHWEB_BOMB_POINTCAP - - /*****The Point Capper*****/ - if(point_gain > linked_techweb.largest_bomb_value) - if(point_gain <= TECHWEB_BOMB_POINTCAP || linked_techweb.largest_bomb_value < TECHWEB_BOMB_POINTCAP) - var/old_tech_largest_bomb_value = linked_techweb.largest_bomb_value //held so we can pull old before we do math - linked_techweb.largest_bomb_value = point_gain - point_gain -= old_tech_largest_bomb_value - point_gain = min(point_gain,TECHWEB_BOMB_POINTCAP) - else - linked_techweb.largest_bomb_value = TECHWEB_BOMB_POINTCAP - point_gain = 1000 - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) - if(D) - D.adjust_money(point_gain) - linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) - say("Explosion details and mixture analyzed and sold to the highest bidder for [point_gain] cr, with a reward of [point_gain] points.") - - else //you've made smaller bombs - say("Data already captured. Aborting.") - return - -/obj/machinery/doppler_array/research/science/Initialize() - . = ..() - linked_techweb = SSresearch.science_tech - -#undef PRINTER_TIMEOUT +#define PRINTER_TIMEOUT 40 + +/obj/machinery/doppler_array + name = "tachyon-doppler array" + desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n" + icon = 'icons/obj/machines/research.dmi' + icon_state = "tdoppler" + density = TRUE + verb_say = "states coldly" + ui_x = 500 + ui_y = 225 + var/cooldown = 10 + var/next_announce = 0 + var/max_dist = 150 + /// Number which will be part of the name of the next record, increased by one for each already created record + var/record_number = 1 + /// Cooldown for the print function + var/printer_ready = 0 + /// List of all explosion records in the form of /datum/data/tachyon_record + var/list/records = list() + +/obj/machinery/doppler_array/Initialize() + . = ..() + RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion) + printer_ready = world.time + PRINTER_TIMEOUT + +/obj/machinery/doppler_array/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE,null,null,CALLBACK(src,.proc/rot_message)) + +/datum/data/tachyon_record + name = "Log Recording" + var/timestamp + var/coordinates = "" + var/displacement = 0 + var/factual_radius = list() + var/theory_radius = list() + +/obj/machinery/doppler_array/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "TachyonArray", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/doppler_array/ui_data(mob/user) + var/list/data = list() + data["records"] = list() + for(var/datum/data/tachyon_record/R in records) + var/list/record_data = list( + name = R.name, + timestamp = R.timestamp, + coordinates = R.coordinates, + displacement = R.displacement, + factual_epicenter_radius = R.factual_radius["epicenter_radius"], + factual_outer_radius = R.factual_radius["outer_radius"], + factual_shockwave_radius = R.factual_radius["shockwave_radius"], + theory_epicenter_radius = R.theory_radius["epicenter_radius"], + theory_outer_radius = R.theory_radius["outer_radius"], + theory_shockwave_radius = R.theory_radius["shockwave_radius"], + ref = REF(R) + ) + data["records"] += list(record_data) + return data + +/obj/machinery/doppler_array/ui_act(action, list/params) + if(..()) + return + + switch(action) + if("delete_record") + var/datum/data/tachyon_record/record = locate(params["ref"]) in records + if(!records || !(record in records)) + return + records -= record + return TRUE + if("print_record") + var/datum/data/tachyon_record/record = locate(params["ref"]) in records + if(!records || !(record in records)) + return + print(usr, record) + return TRUE + +/obj/machinery/doppler_array/proc/print(mob/user, datum/data/tachyon_record/record) + if(!record) + return + if(printer_ready < world.time) + printer_ready = world.time + PRINTER_TIMEOUT + new /obj/item/paper/record_printout(loc, record) + else if(user) + to_chat(user, "[src] is busy right now.") + +/obj/item/paper/record_printout + name = "paper - Log Recording" + +/obj/item/paper/record_printout/Initialize(mapload, datum/data/tachyon_record/record) + . = ..() + + if(record) + name = "paper - [record.name]" + + info += {"

    [record.name]

    +
    • Timestamp: [record.timestamp]
    • +
    • Coordinates: [record.coordinates]
    • +
    • Displacement: [record.displacement] seconds
    • +
    • Epicenter Radius: [record.factual_radius["epicenter_radius"]]
    • +
    • Outer Radius: [record.factual_radius["outer_radius"]]
    • +
    • Shockwave Radius: [record.factual_radius["shockwave_radius"]]
    "} + + if(length(record.theory_radius)) + info += {"
    • Theoretical Epicenter Radius: [record.theory_radius["epicenter_radius"]]
    • +
    • Theoretical Outer Radius: [record.theory_radius["outer_radius"]]
    • +
    • Theoretical Shockwave Radius: [record.theory_radius["shockwave_radius"]]
    "} + + update_icon() + +/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + if(!anchored && !isinspace()) + anchored = TRUE + power_change() + to_chat(user, "You fasten [src].") + else if(anchored) + anchored = FALSE + power_change() + to_chat(user, "You unfasten [src].") + I.play_tool_sound(src) + return + return ..() + +/obj/machinery/doppler_array/proc/rot_message(mob/user) + to_chat(user, "You adjust [src]'s dish to face to the [dir2text(dir)].") + playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) + +/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, + took, orig_dev_range, orig_heavy_range, orig_light_range) + if(machine_stat & NOPOWER) + return FALSE + var/turf/zone = get_turf(src) + if(zone.z != epicenter.z) + return FALSE + + if(next_announce > world.time) + return FALSE + next_announce = world.time + cooldown + + var/distance = get_dist(epicenter, zone) + var/direct = get_dir(zone, epicenter) + + if(distance > max_dist) + return FALSE + if(!(direct & dir)) + return FALSE + + var/datum/data/tachyon_record/R = new /datum/data/tachyon_record() + R.name = "Log Recording #[record_number]" + R.timestamp = station_time_timestamp() + R.coordinates = "[epicenter.x], [epicenter.y]" + R.displacement = took + R.factual_radius["epicenter_radius"] = devastation_range + R.factual_radius["outer_radius"] = heavy_impact_range + R.factual_radius["shockwave_radius"] = light_impact_range + + var/list/messages = list("Explosive disturbance detected.", + "Epicenter at: grid ([epicenter.x], [epicenter.y]). Temporal displacement of tachyons: [took] seconds.", + "Factual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].") + + // If the bomb was capped, say its theoretical size. + if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) + messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." + R.theory_radius["epicenter_radius"] = orig_dev_range + R.theory_radius["outer_radius"] = orig_heavy_range + R.theory_radius["shockwave_radius"] = orig_light_range + + for(var/message in messages) + say(message) + + record_number++ + records += R + return TRUE + +/obj/machinery/doppler_array/powered() + if(!anchored) + return FALSE + return ..() + +/obj/machinery/doppler_array/update_icon_state() + if(machine_stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + else if(powered()) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]-off" + +/obj/machinery/doppler_array/research + name = "tachyon-doppler research array" + desc = "A specialized tachyon-doppler bomb detection array that uses the results of the highest yield of explosions for research." + var/datum/techweb/linked_techweb + +/obj/machinery/doppler_array/research/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, + took, orig_dev_range, orig_heavy_range, orig_light_range) //probably needs a way to ignore admin explosives later on + . = ..() + if(!.) + return + if(!istype(linked_techweb)) + say("Warning: No linked research system!") + return + + var/point_gain = 0 + /*****The Point Calculator*****/ + if(orig_light_range < 10) + say("Explosion not large enough for research calculations.") + return + else if(orig_light_range < 4500) + point_gain = (83300 * orig_light_range) / (orig_light_range + 3000) + else + point_gain = TECHWEB_BOMB_POINTCAP + + /*****The Point Capper*****/ + if(point_gain > linked_techweb.largest_bomb_value) + if(point_gain <= TECHWEB_BOMB_POINTCAP || linked_techweb.largest_bomb_value < TECHWEB_BOMB_POINTCAP) + var/old_tech_largest_bomb_value = linked_techweb.largest_bomb_value //held so we can pull old before we do math + linked_techweb.largest_bomb_value = point_gain + point_gain -= old_tech_largest_bomb_value + point_gain = min(point_gain,TECHWEB_BOMB_POINTCAP) + else + linked_techweb.largest_bomb_value = TECHWEB_BOMB_POINTCAP + point_gain = 1000 + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) + if(D) + D.adjust_money(point_gain) + linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) + say("Explosion details and mixture analyzed and sold to the highest bidder for [point_gain] cr, with a reward of [point_gain] points.") + + else //you've made smaller bombs + say("Data already captured. Aborting.") + return + +/obj/machinery/doppler_array/research/science/Initialize() + . = ..() + linked_techweb = SSresearch.science_tech + +#undef PRINTER_TIMEOUT diff --git a/code/game/machinery/embedded_controller/access_controller.dm b/code/game/machinery/embedded_controller/access_controller.dm index 1402fefcfd4..caf13541624 100644 --- a/code/game/machinery/embedded_controller/access_controller.dm +++ b/code/game/machinery/embedded_controller/access_controller.dm @@ -1,309 +1,309 @@ -#define CLOSING 1 -#define OPENING 2 -#define CYCLE 3 -#define CYCLE_EXTERIOR 4 -#define CYCLE_INTERIOR 5 - -/obj/machinery/door_buttons - power_channel = AREA_USAGE_ENVIRON - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/idSelf - -/obj/machinery/door_buttons/attackby(obj/O, mob/user) - return attack_hand(user) - -/obj/machinery/door_buttons/proc/findObjsByTag() - return - -/obj/machinery/door_buttons/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/door_buttons/LateInitialize() - findObjsByTag() - -/obj/machinery/door_buttons/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - req_access = list() - req_one_access = list() - playsound(src, "sparks", 100, TRUE) - to_chat(user, "You short out the access controller.") - -/obj/machinery/door_buttons/proc/removeMe() - - -/obj/machinery/door_buttons/access_button - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "access_button_standby" - name = "access button" - desc = "A button used for the explicit purpose of opening an airlock." - var/idDoor - var/obj/machinery/door/airlock/door - var/obj/machinery/door_buttons/airlock_controller/controller - var/busy - -/obj/machinery/door_buttons/access_button/findObjsByTag() - for(var/obj/machinery/door_buttons/airlock_controller/A in GLOB.machines) - if(A.idSelf == idSelf) - controller = A - break - for(var/obj/machinery/door/airlock/I in GLOB.machines) - if(I.id_tag == idDoor) - door = I - break - -/obj/machinery/door_buttons/access_button/interact(mob/user) - if(busy) - return - if(!allowed(user)) - to_chat(user, "Access denied.") - return - if(controller && !controller.busy && door) - if(controller.machine_stat & NOPOWER) - return - busy = TRUE - update_icon() - if(door.density) - if(!controller.exteriorAirlock || !controller.interiorAirlock) - controller.onlyOpen(door) - else - if(controller.exteriorAirlock.density && controller.interiorAirlock.density) - controller.onlyOpen(door) - else - controller.cycleClose(door) - else - controller.onlyClose(door) - sleep(20) - busy = FALSE - update_icon() - -/obj/machinery/door_buttons/access_button/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "access_button_off" - else - if(busy) - icon_state = "access_button_cycle" - else - icon_state = "access_button_standby" - -/obj/machinery/door_buttons/access_button/removeMe(obj/O) - if(O == door) - door = null - - - -/obj/machinery/door_buttons/airlock_controller - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "access_control_standby" - name = "access console" - desc = "A small console that can cycle opening between two airlocks." - var/obj/machinery/door/airlock/interiorAirlock - var/obj/machinery/door/airlock/exteriorAirlock - var/idInterior - var/idExterior - var/busy - var/lostPower - -/obj/machinery/door_buttons/airlock_controller/removeMe(obj/O) - if(O == interiorAirlock) - interiorAirlock = null - else if(O == exteriorAirlock) - exteriorAirlock = null - -/obj/machinery/door_buttons/airlock_controller/Destroy() - for(var/obj/machinery/door_buttons/access_button/A in GLOB.machines) - if(A.controller == src) - A.controller = null - return ..() - -/obj/machinery/door_buttons/airlock_controller/Topic(href, href_list) - if(..()) - return - if(busy) - return - if(!allowed(usr)) - to_chat(usr, "Access denied.") - return - switch(href_list["command"]) - if("close_exterior") - onlyClose(exteriorAirlock) - if("close_interior") - onlyClose(interiorAirlock) - if("cycle_exterior") - cycleClose(exteriorAirlock) - if("cycle_interior") - cycleClose(interiorAirlock) - if("open_exterior") - onlyOpen(exteriorAirlock) - if("open_interior") - onlyOpen(interiorAirlock) - -/obj/machinery/door_buttons/airlock_controller/proc/onlyOpen(obj/machinery/door/airlock/A) - if(A) - busy = CLOSING - update_icon() - openDoor(A) - -/obj/machinery/door_buttons/airlock_controller/proc/onlyClose(obj/machinery/door/airlock/A) - if(A) - busy = CLOSING - closeDoor(A) - -/obj/machinery/door_buttons/airlock_controller/proc/closeDoor(obj/machinery/door/airlock/A) - if(A.density) - goIdle() - return FALSE - update_icon() - A.safe = FALSE //Door crushies, manual door after all. Set every time in case someone changed it, safe doors can end up waiting forever. - A.unbolt() - if(A.close()) - if(machine_stat & NOPOWER || lostPower || !A || QDELETED(A)) - goIdle(TRUE) - return FALSE - A.bolt() - goIdle(TRUE) - return TRUE - goIdle(TRUE) - return FALSE - -/obj/machinery/door_buttons/airlock_controller/proc/cycleClose(obj/machinery/door/airlock/A) - if(!A || !exteriorAirlock || !interiorAirlock) - return - if(exteriorAirlock.density == interiorAirlock.density || !A.density) - return - busy = CYCLE - update_icon() - if(A == interiorAirlock) - if(closeDoor(exteriorAirlock)) - busy = CYCLE_INTERIOR - else - if(closeDoor(interiorAirlock)) - busy = CYCLE_EXTERIOR - -/obj/machinery/door_buttons/airlock_controller/proc/cycleOpen(obj/machinery/door/airlock/A) - if(!A) - goIdle(TRUE) - if(A == exteriorAirlock) - if(interiorAirlock) - if(!interiorAirlock.density || !interiorAirlock.locked) - return - else - if(exteriorAirlock) - if(!exteriorAirlock.density || !exteriorAirlock.locked) - return - if(busy != OPENING) - busy = OPENING - openDoor(A) - -/obj/machinery/door_buttons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) - if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) - goIdle(TRUE) - return - A.unbolt() - INVOKE_ASYNC(src, .proc/do_openDoor, A) - -/obj/machinery/door_buttons/airlock_controller/proc/do_openDoor(obj/machinery/door/airlock/A) - if(A && A.open()) - if(machine_stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) - A.bolt() - goIdle(TRUE) - -/obj/machinery/door_buttons/airlock_controller/proc/goIdle(update) - lostPower = FALSE - busy = FALSE - if(update) - update_icon() - updateUsrDialog() - -/obj/machinery/door_buttons/airlock_controller/process() - if(machine_stat & NOPOWER) - return - if(busy == CYCLE_EXTERIOR) - cycleOpen(exteriorAirlock) - else if(busy == CYCLE_INTERIOR) - cycleOpen(interiorAirlock) - -/obj/machinery/door_buttons/airlock_controller/power_change() - . = ..() - if(machine_stat & NOPOWER) - lostPower = TRUE - else - if(!busy) - lostPower = FALSE - -/obj/machinery/door_buttons/airlock_controller/findObjsByTag() - for(var/obj/machinery/door/airlock/A in GLOB.machines) - if(A.id_tag == idInterior) - interiorAirlock = A - else if(A.id_tag == idExterior) - exteriorAirlock = A - -/obj/machinery/door_buttons/airlock_controller/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "access_control_off" - return - if(busy || lostPower) - icon_state = "access_control_process" - else - icon_state = "access_control_standby" - -/obj/machinery/door_buttons/airlock_controller/ui_interact(mob/user) - var/datum/browser/popup = new(user, "computer", name) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.set_content(returnText()) - popup.open() - -/obj/machinery/door_buttons/airlock_controller/proc/returnText() - var/output - if(!exteriorAirlock && !interiorAirlock) - return "ERROR ERROR ERROR ERROR" - if(lostPower) - output = "Initializing..." - else - if(!exteriorAirlock || !interiorAirlock) - if(!exteriorAirlock) - if(interiorAirlock.density) - output = "Open Interior Airlock
    " - else - output = "Close Interior Airlock
    " - else - if(exteriorAirlock.density) - output = "Open Exterior Airlock
    " - else - output = "Close Exterior Airlock
    " - else - if(exteriorAirlock.density) - if(interiorAirlock.density) - output = {"Open Exterior Airlock
    - Open Interior Airlock
    "} - else - output = {"Cycle to Exterior Airlock
    - Close Interior Airlock
    "} - else - if(interiorAirlock.density) - output = {"Close Exterior Airlock
    - Cycle to Interior Airlock
    "} - else - output = {"Close Exterior Airlock
    - Close Interior Airlock
    "} - - - output = {"Access Control Console
    - [output]
    "} - if(exteriorAirlock) - output += "Exterior Door: [exteriorAirlock.density ? "closed" : "open"]
    " - if(interiorAirlock) - output += "Interior Door: [interiorAirlock.density ? "closed" : "open"]
    " - - return output - -#undef CLOSING -#undef OPENING -#undef CYCLE -#undef CYCLE_EXTERIOR -#undef CYCLE_INTERIOR +#define CLOSING 1 +#define OPENING 2 +#define CYCLE 3 +#define CYCLE_EXTERIOR 4 +#define CYCLE_INTERIOR 5 + +/obj/machinery/door_buttons + power_channel = AREA_USAGE_ENVIRON + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/idSelf + +/obj/machinery/door_buttons/attackby(obj/O, mob/user) + return attack_hand(user) + +/obj/machinery/door_buttons/proc/findObjsByTag() + return + +/obj/machinery/door_buttons/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/door_buttons/LateInitialize() + findObjsByTag() + +/obj/machinery/door_buttons/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + req_access = list() + req_one_access = list() + playsound(src, "sparks", 100, TRUE) + to_chat(user, "You short out the access controller.") + +/obj/machinery/door_buttons/proc/removeMe() + + +/obj/machinery/door_buttons/access_button + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "access_button_standby" + name = "access button" + desc = "A button used for the explicit purpose of opening an airlock." + var/idDoor + var/obj/machinery/door/airlock/door + var/obj/machinery/door_buttons/airlock_controller/controller + var/busy + +/obj/machinery/door_buttons/access_button/findObjsByTag() + for(var/obj/machinery/door_buttons/airlock_controller/A in GLOB.machines) + if(A.idSelf == idSelf) + controller = A + break + for(var/obj/machinery/door/airlock/I in GLOB.machines) + if(I.id_tag == idDoor) + door = I + break + +/obj/machinery/door_buttons/access_button/interact(mob/user) + if(busy) + return + if(!allowed(user)) + to_chat(user, "Access denied.") + return + if(controller && !controller.busy && door) + if(controller.machine_stat & NOPOWER) + return + busy = TRUE + update_icon() + if(door.density) + if(!controller.exteriorAirlock || !controller.interiorAirlock) + controller.onlyOpen(door) + else + if(controller.exteriorAirlock.density && controller.interiorAirlock.density) + controller.onlyOpen(door) + else + controller.cycleClose(door) + else + controller.onlyClose(door) + sleep(20) + busy = FALSE + update_icon() + +/obj/machinery/door_buttons/access_button/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "access_button_off" + else + if(busy) + icon_state = "access_button_cycle" + else + icon_state = "access_button_standby" + +/obj/machinery/door_buttons/access_button/removeMe(obj/O) + if(O == door) + door = null + + + +/obj/machinery/door_buttons/airlock_controller + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "access_control_standby" + name = "access console" + desc = "A small console that can cycle opening between two airlocks." + var/obj/machinery/door/airlock/interiorAirlock + var/obj/machinery/door/airlock/exteriorAirlock + var/idInterior + var/idExterior + var/busy + var/lostPower + +/obj/machinery/door_buttons/airlock_controller/removeMe(obj/O) + if(O == interiorAirlock) + interiorAirlock = null + else if(O == exteriorAirlock) + exteriorAirlock = null + +/obj/machinery/door_buttons/airlock_controller/Destroy() + for(var/obj/machinery/door_buttons/access_button/A in GLOB.machines) + if(A.controller == src) + A.controller = null + return ..() + +/obj/machinery/door_buttons/airlock_controller/Topic(href, href_list) + if(..()) + return + if(busy) + return + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return + switch(href_list["command"]) + if("close_exterior") + onlyClose(exteriorAirlock) + if("close_interior") + onlyClose(interiorAirlock) + if("cycle_exterior") + cycleClose(exteriorAirlock) + if("cycle_interior") + cycleClose(interiorAirlock) + if("open_exterior") + onlyOpen(exteriorAirlock) + if("open_interior") + onlyOpen(interiorAirlock) + +/obj/machinery/door_buttons/airlock_controller/proc/onlyOpen(obj/machinery/door/airlock/A) + if(A) + busy = CLOSING + update_icon() + openDoor(A) + +/obj/machinery/door_buttons/airlock_controller/proc/onlyClose(obj/machinery/door/airlock/A) + if(A) + busy = CLOSING + closeDoor(A) + +/obj/machinery/door_buttons/airlock_controller/proc/closeDoor(obj/machinery/door/airlock/A) + if(A.density) + goIdle() + return FALSE + update_icon() + A.safe = FALSE //Door crushies, manual door after all. Set every time in case someone changed it, safe doors can end up waiting forever. + A.unbolt() + if(A.close()) + if(machine_stat & NOPOWER || lostPower || !A || QDELETED(A)) + goIdle(TRUE) + return FALSE + A.bolt() + goIdle(TRUE) + return TRUE + goIdle(TRUE) + return FALSE + +/obj/machinery/door_buttons/airlock_controller/proc/cycleClose(obj/machinery/door/airlock/A) + if(!A || !exteriorAirlock || !interiorAirlock) + return + if(exteriorAirlock.density == interiorAirlock.density || !A.density) + return + busy = CYCLE + update_icon() + if(A == interiorAirlock) + if(closeDoor(exteriorAirlock)) + busy = CYCLE_INTERIOR + else + if(closeDoor(interiorAirlock)) + busy = CYCLE_EXTERIOR + +/obj/machinery/door_buttons/airlock_controller/proc/cycleOpen(obj/machinery/door/airlock/A) + if(!A) + goIdle(TRUE) + if(A == exteriorAirlock) + if(interiorAirlock) + if(!interiorAirlock.density || !interiorAirlock.locked) + return + else + if(exteriorAirlock) + if(!exteriorAirlock.density || !exteriorAirlock.locked) + return + if(busy != OPENING) + busy = OPENING + openDoor(A) + +/obj/machinery/door_buttons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) + if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) + goIdle(TRUE) + return + A.unbolt() + INVOKE_ASYNC(src, .proc/do_openDoor, A) + +/obj/machinery/door_buttons/airlock_controller/proc/do_openDoor(obj/machinery/door/airlock/A) + if(A && A.open()) + if(machine_stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) + A.bolt() + goIdle(TRUE) + +/obj/machinery/door_buttons/airlock_controller/proc/goIdle(update) + lostPower = FALSE + busy = FALSE + if(update) + update_icon() + updateUsrDialog() + +/obj/machinery/door_buttons/airlock_controller/process() + if(machine_stat & NOPOWER) + return + if(busy == CYCLE_EXTERIOR) + cycleOpen(exteriorAirlock) + else if(busy == CYCLE_INTERIOR) + cycleOpen(interiorAirlock) + +/obj/machinery/door_buttons/airlock_controller/power_change() + . = ..() + if(machine_stat & NOPOWER) + lostPower = TRUE + else + if(!busy) + lostPower = FALSE + +/obj/machinery/door_buttons/airlock_controller/findObjsByTag() + for(var/obj/machinery/door/airlock/A in GLOB.machines) + if(A.id_tag == idInterior) + interiorAirlock = A + else if(A.id_tag == idExterior) + exteriorAirlock = A + +/obj/machinery/door_buttons/airlock_controller/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "access_control_off" + return + if(busy || lostPower) + icon_state = "access_control_process" + else + icon_state = "access_control_standby" + +/obj/machinery/door_buttons/airlock_controller/ui_interact(mob/user) + var/datum/browser/popup = new(user, "computer", name) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.set_content(returnText()) + popup.open() + +/obj/machinery/door_buttons/airlock_controller/proc/returnText() + var/output + if(!exteriorAirlock && !interiorAirlock) + return "ERROR ERROR ERROR ERROR" + if(lostPower) + output = "Initializing..." + else + if(!exteriorAirlock || !interiorAirlock) + if(!exteriorAirlock) + if(interiorAirlock.density) + output = "Open Interior Airlock
    " + else + output = "Close Interior Airlock
    " + else + if(exteriorAirlock.density) + output = "Open Exterior Airlock
    " + else + output = "Close Exterior Airlock
    " + else + if(exteriorAirlock.density) + if(interiorAirlock.density) + output = {"Open Exterior Airlock
    + Open Interior Airlock
    "} + else + output = {"Cycle to Exterior Airlock
    + Close Interior Airlock
    "} + else + if(interiorAirlock.density) + output = {"Close Exterior Airlock
    + Cycle to Interior Airlock
    "} + else + output = {"Close Exterior Airlock
    + Close Interior Airlock
    "} + + + output = {"Access Control Console
    + [output]
    "} + if(exteriorAirlock) + output += "Exterior Door: [exteriorAirlock.density ? "closed" : "open"]
    " + if(interiorAirlock) + output += "Interior Door: [interiorAirlock.density ? "closed" : "open"]
    " + + return output + +#undef CLOSING +#undef OPENING +#undef CYCLE +#undef CYCLE_EXTERIOR +#undef CYCLE_INTERIOR diff --git a/code/game/machinery/embedded_controller/airlock_controller.dm b/code/game/machinery/embedded_controller/airlock_controller.dm index 493929381b4..c028beb3d69 100644 --- a/code/game/machinery/embedded_controller/airlock_controller.dm +++ b/code/game/machinery/embedded_controller/airlock_controller.dm @@ -1,315 +1,315 @@ -//States for airlock_control -#define AIRLOCK_STATE_INOPEN -2 -#define AIRLOCK_STATE_PRESSURIZE -1 -#define AIRLOCK_STATE_CLOSED 0 -#define AIRLOCK_STATE_DEPRESSURIZE 1 -#define AIRLOCK_STATE_OUTOPEN 2 - -/datum/computer/file/embedded_program/airlock_controller - var/id_tag - var/exterior_door_tag //Burn chamber facing door - var/interior_door_tag //Station facing door - var/airpump_tag //See: dp_vent_pump.dm - var/sensor_tag //See: /obj/machinery/airlock_sensor - var/sanitize_external //Before the interior airlock opens, do we first drain all gases inside the chamber and then repressurize? - - state = AIRLOCK_STATE_CLOSED - var/target_state = AIRLOCK_STATE_CLOSED - var/sensor_pressure = null - -/datum/computer/file/embedded_program/airlock_controller/receive_signal(datum/signal/signal) - var/receive_tag = signal.data["tag"] - if(!receive_tag) - return - - if(receive_tag==sensor_tag) - if(signal.data["pressure"]) - sensor_pressure = text2num(signal.data["pressure"]) - - else if(receive_tag==exterior_door_tag) - memory["exterior_status"] = signal.data["door_status"] - - else if(receive_tag==interior_door_tag) - memory["interior_status"] = signal.data["door_status"] - - else if(receive_tag==airpump_tag) - if(signal.data["power"]) - memory["pump_status"] = signal.data["direction"] - else - memory["pump_status"] = "off" - - else if(receive_tag==id_tag) - switch(signal.data["command"]) - if("cycle") - if(state < AIRLOCK_STATE_CLOSED) - target_state = AIRLOCK_STATE_OUTOPEN - else - target_state = AIRLOCK_STATE_INOPEN - -/datum/computer/file/embedded_program/airlock_controller/receive_user_command(command) - switch(command) - if("cycle_closed") - target_state = AIRLOCK_STATE_CLOSED - if("cycle_exterior") - target_state = AIRLOCK_STATE_OUTOPEN - if("cycle_interior") - target_state = AIRLOCK_STATE_INOPEN - if("abort") - target_state = AIRLOCK_STATE_CLOSED - -/datum/computer/file/embedded_program/airlock_controller/process() - var/process_again = 1 - while(process_again) - process_again = 0 - switch(state) - if(AIRLOCK_STATE_INOPEN) // state -2 - if(target_state > state) - if(memory["interior_status"] == "closed") - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = interior_door_tag, - "command" = "secure_close" - ))) - else - if(memory["pump_status"] != "off") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "power" = 0, - "sigtype" = "command" - ))) - - if(AIRLOCK_STATE_PRESSURIZE) - if(target_state < state) - if(sensor_pressure >= ONE_ATMOSPHERE*0.95) - if(memory["interior_status"] == "open") - state = AIRLOCK_STATE_INOPEN - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = interior_door_tag, - "command" = "secure_open" - ))) - else - var/datum/signal/signal = new(list( - "tag" = airpump_tag, - "sigtype" = "command" - )) - if(memory["pump_status"] == "siphon") - signal.data["stabilize"] = 1 - else if(memory["pump_status"] != "release") - signal.data["power"] = 1 - post_signal(signal) - else if(target_state > state) - state = AIRLOCK_STATE_CLOSED - process_again = 1 - - if(AIRLOCK_STATE_CLOSED) - if(target_state > state) - if(memory["interior_status"] == "closed") - state = AIRLOCK_STATE_DEPRESSURIZE - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = interior_door_tag, - "command" = "secure_close" - ))) - else if(target_state < state) - if(memory["exterior_status"] == "closed") - state = AIRLOCK_STATE_PRESSURIZE - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = exterior_door_tag, - "command" = "secure_close" - ))) - - else - if(memory["pump_status"] != "off") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "power" = 0, - "sigtype" = "command" - ))) - - if(AIRLOCK_STATE_DEPRESSURIZE) - var/target_pressure = ONE_ATMOSPHERE*0.05 - if(sanitize_external) - target_pressure = ONE_ATMOSPHERE*0.01 - - if(sensor_pressure <= target_pressure) - if(target_state > state) - if(memory["exterior_status"] == "open") - state = AIRLOCK_STATE_OUTOPEN - else - post_signal(new /datum/signal(list( - "tag" = exterior_door_tag, - "command" = "secure_open" - ))) - else if(target_state < state) - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else if((target_state < state) && !sanitize_external) - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else - var/datum/signal/signal = new(list( - "tag" = airpump_tag, - "sigtype" = "command" - )) - if(memory["pump_status"] == "release") - signal.data["purge"] = 1 - else if(memory["pump_status"] != "siphon") - signal.data["power"] = 1 - post_signal(signal) - - if(AIRLOCK_STATE_OUTOPEN) //state 2 - if(target_state < state) - if(memory["exterior_status"] == "closed") - if(sanitize_external) - state = AIRLOCK_STATE_DEPRESSURIZE - process_again = 1 - else - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = exterior_door_tag, - "command" = "secure_close" - ))) - else - if(memory["pump_status"] != "off") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "power" = 0, - "sigtype" = "command" - ))) - - memory["sensor_pressure"] = sensor_pressure - memory["processing"] = state != target_state - //sensor_pressure = null //not sure if we can comment this out. Uncomment in case of problems -rastaf0 - - return 1 - - -/obj/machinery/embedded_controller/radio/airlock_controller - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_control_standby" - - name = "airlock console" - density = FALSE - - frequency = FREQ_AIRLOCK_CONTROL - power_channel = AREA_USAGE_ENVIRON - - // Setup parameters only - var/id_tag - var/exterior_door_tag - var/interior_door_tag - var/airpump_tag - var/sensor_tag - var/sanitize_external - -/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_toxmix - name = "Incinerator Access Console" - airpump_tag = INCINERATOR_TOXMIX_DP_VENTPUMP - exterior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR - id_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER - interior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_INTERIOR - sanitize_external = TRUE - sensor_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR - -/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_atmos - name = "Incinerator Access Console" - airpump_tag = INCINERATOR_ATMOS_DP_VENTPUMP - exterior_door_tag = INCINERATOR_ATMOS_AIRLOCK_EXTERIOR - id_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER - interior_door_tag = INCINERATOR_ATMOS_AIRLOCK_INTERIOR - sanitize_external = TRUE - sensor_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR - -/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_syndicatelava - name = "Incinerator Access Console" - airpump_tag = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP - exterior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR - id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER - interior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR - sanitize_external = TRUE - sensor_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR - -/obj/machinery/embedded_controller/radio/airlock_controller/Initialize(mapload) - . = ..() - if(!mapload) - return - - var/datum/computer/file/embedded_program/airlock_controller/new_prog = new - - new_prog.id_tag = id_tag - new_prog.exterior_door_tag = exterior_door_tag - new_prog.interior_door_tag = interior_door_tag - new_prog.airpump_tag = airpump_tag - new_prog.sensor_tag = sensor_tag - new_prog.sanitize_external = sanitize_external - - new_prog.master = src - program = new_prog - -/obj/machinery/embedded_controller/radio/airlock_controller/update_icon_state() - if(on && program) - if(program.memory["processing"]) - icon_state = "airlock_control_process" - else - icon_state = "airlock_control_standby" - else - icon_state = "airlock_control_off" - - -/obj/machinery/embedded_controller/radio/airlock_controller/return_text() - var/state_options = null - - var/state = 0 - var/sensor_pressure = "----" - var/exterior_status = "----" - var/interior_status = "----" - var/pump_status = "----" - var/current_status = "Inactive
     " - if(program) - state = program.state - sensor_pressure = program.memory["sensor_pressure"] ? program.memory["sensor_pressure"] : "----" - exterior_status = program.memory["exterior_status"] ? program.memory["exterior_status"] : "----" - interior_status = program.memory["interior_status"] ? program.memory["interior_status"] : "----" - pump_status = program.memory["pump_status"] ? program.memory["pump_status"] : "----" - - switch(state) - if(AIRLOCK_STATE_INOPEN) - state_options = {"Close Interior Airlock
    -Cycle to Exterior Airlock
    "} - current_status = "Interior Airlock Open
    Chamber Pressurized" - if(AIRLOCK_STATE_PRESSURIZE) - state_options = "Abort Cycling
    " - current_status = "Cycling to Interior Airlock
    Chamber Pressurizing" - if(AIRLOCK_STATE_CLOSED) - state_options = {"Open Interior Airlock
    -Open Exterior Airlock
    "} - if(AIRLOCK_STATE_DEPRESSURIZE) - state_options = "Abort Cycling
    " - current_status = "Cycling to Exterior Airlock
    Chamber Depressurizing" - if(AIRLOCK_STATE_OUTOPEN) - state_options = {"Cycle to Interior Airlock
    -Close Exterior Airlock
    "} - current_status = "Exterior Airlock Open
    Chamber Depressurized" - - var/output = {"

    Airlock Status

    -
    -
    Current Status:
    [current_status]
    -
     
    -
    \> Chamber Pressure:
    [sensor_pressure] kPa
    -
    \> Control Pump:
    [pump_status]
    -
    \> Interior Door:
    [interior_status]
    -
    \> Exterior Door:
    [exterior_status]
    -
    -
    -[state_options]"} - - return output +//States for airlock_control +#define AIRLOCK_STATE_INOPEN -2 +#define AIRLOCK_STATE_PRESSURIZE -1 +#define AIRLOCK_STATE_CLOSED 0 +#define AIRLOCK_STATE_DEPRESSURIZE 1 +#define AIRLOCK_STATE_OUTOPEN 2 + +/datum/computer/file/embedded_program/airlock_controller + var/id_tag + var/exterior_door_tag //Burn chamber facing door + var/interior_door_tag //Station facing door + var/airpump_tag //See: dp_vent_pump.dm + var/sensor_tag //See: /obj/machinery/airlock_sensor + var/sanitize_external //Before the interior airlock opens, do we first drain all gases inside the chamber and then repressurize? + + state = AIRLOCK_STATE_CLOSED + var/target_state = AIRLOCK_STATE_CLOSED + var/sensor_pressure = null + +/datum/computer/file/embedded_program/airlock_controller/receive_signal(datum/signal/signal) + var/receive_tag = signal.data["tag"] + if(!receive_tag) + return + + if(receive_tag==sensor_tag) + if(signal.data["pressure"]) + sensor_pressure = text2num(signal.data["pressure"]) + + else if(receive_tag==exterior_door_tag) + memory["exterior_status"] = signal.data["door_status"] + + else if(receive_tag==interior_door_tag) + memory["interior_status"] = signal.data["door_status"] + + else if(receive_tag==airpump_tag) + if(signal.data["power"]) + memory["pump_status"] = signal.data["direction"] + else + memory["pump_status"] = "off" + + else if(receive_tag==id_tag) + switch(signal.data["command"]) + if("cycle") + if(state < AIRLOCK_STATE_CLOSED) + target_state = AIRLOCK_STATE_OUTOPEN + else + target_state = AIRLOCK_STATE_INOPEN + +/datum/computer/file/embedded_program/airlock_controller/receive_user_command(command) + switch(command) + if("cycle_closed") + target_state = AIRLOCK_STATE_CLOSED + if("cycle_exterior") + target_state = AIRLOCK_STATE_OUTOPEN + if("cycle_interior") + target_state = AIRLOCK_STATE_INOPEN + if("abort") + target_state = AIRLOCK_STATE_CLOSED + +/datum/computer/file/embedded_program/airlock_controller/process() + var/process_again = 1 + while(process_again) + process_again = 0 + switch(state) + if(AIRLOCK_STATE_INOPEN) // state -2 + if(target_state > state) + if(memory["interior_status"] == "closed") + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = interior_door_tag, + "command" = "secure_close" + ))) + else + if(memory["pump_status"] != "off") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "power" = 0, + "sigtype" = "command" + ))) + + if(AIRLOCK_STATE_PRESSURIZE) + if(target_state < state) + if(sensor_pressure >= ONE_ATMOSPHERE*0.95) + if(memory["interior_status"] == "open") + state = AIRLOCK_STATE_INOPEN + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = interior_door_tag, + "command" = "secure_open" + ))) + else + var/datum/signal/signal = new(list( + "tag" = airpump_tag, + "sigtype" = "command" + )) + if(memory["pump_status"] == "siphon") + signal.data["stabilize"] = 1 + else if(memory["pump_status"] != "release") + signal.data["power"] = 1 + post_signal(signal) + else if(target_state > state) + state = AIRLOCK_STATE_CLOSED + process_again = 1 + + if(AIRLOCK_STATE_CLOSED) + if(target_state > state) + if(memory["interior_status"] == "closed") + state = AIRLOCK_STATE_DEPRESSURIZE + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = interior_door_tag, + "command" = "secure_close" + ))) + else if(target_state < state) + if(memory["exterior_status"] == "closed") + state = AIRLOCK_STATE_PRESSURIZE + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = exterior_door_tag, + "command" = "secure_close" + ))) + + else + if(memory["pump_status"] != "off") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "power" = 0, + "sigtype" = "command" + ))) + + if(AIRLOCK_STATE_DEPRESSURIZE) + var/target_pressure = ONE_ATMOSPHERE*0.05 + if(sanitize_external) + target_pressure = ONE_ATMOSPHERE*0.01 + + if(sensor_pressure <= target_pressure) + if(target_state > state) + if(memory["exterior_status"] == "open") + state = AIRLOCK_STATE_OUTOPEN + else + post_signal(new /datum/signal(list( + "tag" = exterior_door_tag, + "command" = "secure_open" + ))) + else if(target_state < state) + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else if((target_state < state) && !sanitize_external) + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else + var/datum/signal/signal = new(list( + "tag" = airpump_tag, + "sigtype" = "command" + )) + if(memory["pump_status"] == "release") + signal.data["purge"] = 1 + else if(memory["pump_status"] != "siphon") + signal.data["power"] = 1 + post_signal(signal) + + if(AIRLOCK_STATE_OUTOPEN) //state 2 + if(target_state < state) + if(memory["exterior_status"] == "closed") + if(sanitize_external) + state = AIRLOCK_STATE_DEPRESSURIZE + process_again = 1 + else + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = exterior_door_tag, + "command" = "secure_close" + ))) + else + if(memory["pump_status"] != "off") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "power" = 0, + "sigtype" = "command" + ))) + + memory["sensor_pressure"] = sensor_pressure + memory["processing"] = state != target_state + //sensor_pressure = null //not sure if we can comment this out. Uncomment in case of problems -rastaf0 + + return 1 + + +/obj/machinery/embedded_controller/radio/airlock_controller + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "airlock_control_standby" + + name = "airlock console" + density = FALSE + + frequency = FREQ_AIRLOCK_CONTROL + power_channel = AREA_USAGE_ENVIRON + + // Setup parameters only + var/id_tag + var/exterior_door_tag + var/interior_door_tag + var/airpump_tag + var/sensor_tag + var/sanitize_external + +/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_toxmix + name = "Incinerator Access Console" + airpump_tag = INCINERATOR_TOXMIX_DP_VENTPUMP + exterior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR + id_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER + interior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_INTERIOR + sanitize_external = TRUE + sensor_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR + +/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_atmos + name = "Incinerator Access Console" + airpump_tag = INCINERATOR_ATMOS_DP_VENTPUMP + exterior_door_tag = INCINERATOR_ATMOS_AIRLOCK_EXTERIOR + id_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER + interior_door_tag = INCINERATOR_ATMOS_AIRLOCK_INTERIOR + sanitize_external = TRUE + sensor_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR + +/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_syndicatelava + name = "Incinerator Access Console" + airpump_tag = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP + exterior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR + id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER + interior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR + sanitize_external = TRUE + sensor_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR + +/obj/machinery/embedded_controller/radio/airlock_controller/Initialize(mapload) + . = ..() + if(!mapload) + return + + var/datum/computer/file/embedded_program/airlock_controller/new_prog = new + + new_prog.id_tag = id_tag + new_prog.exterior_door_tag = exterior_door_tag + new_prog.interior_door_tag = interior_door_tag + new_prog.airpump_tag = airpump_tag + new_prog.sensor_tag = sensor_tag + new_prog.sanitize_external = sanitize_external + + new_prog.master = src + program = new_prog + +/obj/machinery/embedded_controller/radio/airlock_controller/update_icon_state() + if(on && program) + if(program.memory["processing"]) + icon_state = "airlock_control_process" + else + icon_state = "airlock_control_standby" + else + icon_state = "airlock_control_off" + + +/obj/machinery/embedded_controller/radio/airlock_controller/return_text() + var/state_options = null + + var/state = 0 + var/sensor_pressure = "----" + var/exterior_status = "----" + var/interior_status = "----" + var/pump_status = "----" + var/current_status = "Inactive
     " + if(program) + state = program.state + sensor_pressure = program.memory["sensor_pressure"] ? program.memory["sensor_pressure"] : "----" + exterior_status = program.memory["exterior_status"] ? program.memory["exterior_status"] : "----" + interior_status = program.memory["interior_status"] ? program.memory["interior_status"] : "----" + pump_status = program.memory["pump_status"] ? program.memory["pump_status"] : "----" + + switch(state) + if(AIRLOCK_STATE_INOPEN) + state_options = {"Close Interior Airlock
    +Cycle to Exterior Airlock
    "} + current_status = "Interior Airlock Open
    Chamber Pressurized" + if(AIRLOCK_STATE_PRESSURIZE) + state_options = "Abort Cycling
    " + current_status = "Cycling to Interior Airlock
    Chamber Pressurizing" + if(AIRLOCK_STATE_CLOSED) + state_options = {"Open Interior Airlock
    +Open Exterior Airlock
    "} + if(AIRLOCK_STATE_DEPRESSURIZE) + state_options = "Abort Cycling
    " + current_status = "Cycling to Exterior Airlock
    Chamber Depressurizing" + if(AIRLOCK_STATE_OUTOPEN) + state_options = {"Cycle to Interior Airlock
    +Close Exterior Airlock
    "} + current_status = "Exterior Airlock Open
    Chamber Depressurized" + + var/output = {"

    Airlock Status

    +
    +
    Current Status:
    [current_status]
    +
     
    +
    \> Chamber Pressure:
    [sensor_pressure] kPa
    +
    \> Control Pump:
    [pump_status]
    +
    \> Interior Door:
    [interior_status]
    +
    \> Exterior Door:
    [exterior_status]
    +
    +
    +[state_options]"} + + return output diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm index 8607b26642f..6fd351bcff6 100644 --- a/code/game/machinery/embedded_controller/embedded_controller_base.dm +++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm @@ -1,85 +1,85 @@ -/datum/computer/file/embedded_program - var/list/memory = list() - var/state - var/obj/machinery/embedded_controller/master - -/datum/computer/file/embedded_program/proc/post_signal(datum/signal/signal, comm_line) - if(master) - master.post_signal(signal, comm_line) - else - qdel(signal) - -/datum/computer/file/embedded_program/proc/receive_user_command(command) - -/datum/computer/file/embedded_program/proc/receive_signal(datum/signal/signal) - return null - -/datum/computer/file/embedded_program/process() - return 0 - -/obj/machinery/embedded_controller - var/datum/computer/file/embedded_program/program - - name = "embedded controller" - density = FALSE - - var/on = TRUE - -/obj/machinery/embedded_controller/ui_interact(mob/user) - . = ..() - user.set_machine(src) - var/datum/browser/popup = new(user, "computer", name) // Set up the popup browser window - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.set_content(return_text()) - popup.open() - -/obj/machinery/embedded_controller/proc/return_text() - -/obj/machinery/embedded_controller/proc/post_signal(datum/signal/signal, comm_line) - return 0 - -/obj/machinery/embedded_controller/receive_signal(datum/signal/signal) - if(istype(signal) && program) - program.receive_signal(signal) - -/obj/machinery/embedded_controller/Topic(href, href_list) - if(..()) - return 0 - - if(program) - program.receive_user_command(href_list["command"]) - addtimer(CALLBACK(program, /datum/computer/file/embedded_program.proc/process), 5) - - usr.set_machine(src) - addtimer(CALLBACK(src, .proc/updateDialog), 5) - -/obj/machinery/embedded_controller/process() - if(program) - program.process() - - update_icon() - src.updateDialog() - -/obj/machinery/embedded_controller/radio - var/frequency - var/datum/radio_frequency/radio_connection - -/obj/machinery/embedded_controller/radio/Destroy() - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/embedded_controller/radio/Initialize() - . = ..() - set_frequency(frequency) - -/obj/machinery/embedded_controller/radio/post_signal(datum/signal/signal) - signal.transmission_method = TRANSMISSION_RADIO - if(radio_connection) - return radio_connection.post_signal(src, signal) - else - signal = null - -/obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency) +/datum/computer/file/embedded_program + var/list/memory = list() + var/state + var/obj/machinery/embedded_controller/master + +/datum/computer/file/embedded_program/proc/post_signal(datum/signal/signal, comm_line) + if(master) + master.post_signal(signal, comm_line) + else + qdel(signal) + +/datum/computer/file/embedded_program/proc/receive_user_command(command) + +/datum/computer/file/embedded_program/proc/receive_signal(datum/signal/signal) + return null + +/datum/computer/file/embedded_program/process() + return 0 + +/obj/machinery/embedded_controller + var/datum/computer/file/embedded_program/program + + name = "embedded controller" + density = FALSE + + var/on = TRUE + +/obj/machinery/embedded_controller/ui_interact(mob/user) + . = ..() + user.set_machine(src) + var/datum/browser/popup = new(user, "computer", name) // Set up the popup browser window + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.set_content(return_text()) + popup.open() + +/obj/machinery/embedded_controller/proc/return_text() + +/obj/machinery/embedded_controller/proc/post_signal(datum/signal/signal, comm_line) + return 0 + +/obj/machinery/embedded_controller/receive_signal(datum/signal/signal) + if(istype(signal) && program) + program.receive_signal(signal) + +/obj/machinery/embedded_controller/Topic(href, href_list) + if(..()) + return 0 + + if(program) + program.receive_user_command(href_list["command"]) + addtimer(CALLBACK(program, /datum/computer/file/embedded_program.proc/process), 5) + + usr.set_machine(src) + addtimer(CALLBACK(src, .proc/updateDialog), 5) + +/obj/machinery/embedded_controller/process() + if(program) + program.process() + + update_icon() + src.updateDialog() + +/obj/machinery/embedded_controller/radio + var/frequency + var/datum/radio_frequency/radio_connection + +/obj/machinery/embedded_controller/radio/Destroy() + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/embedded_controller/radio/Initialize() + . = ..() + set_frequency(frequency) + +/obj/machinery/embedded_controller/radio/post_signal(datum/signal/signal) + signal.transmission_method = TRANSMISSION_RADIO + if(radio_connection) + return radio_connection.post_signal(src, signal) + else + signal = null + +/obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency) diff --git a/code/game/machinery/embedded_controller/simple_vent_controller.dm b/code/game/machinery/embedded_controller/simple_vent_controller.dm index 6de766090c2..4de102becb0 100644 --- a/code/game/machinery/embedded_controller/simple_vent_controller.dm +++ b/code/game/machinery/embedded_controller/simple_vent_controller.dm @@ -1,72 +1,72 @@ -/datum/computer/file/embedded_program/simple_vent_controller - - var/airpump_tag - -/datum/computer/file/embedded_program/simple_vent_controller/receive_user_command(command) - switch(command) - if("vent_inactive") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "sigtype" = "command", - "power" = 0 - ))) - - if("vent_pump") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "sigtype" = "command", - "stabilize" = 1, - "power" = 1 - ))) - - if("vent_clear") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "sigtype" = "command", - "purge" = 1, - "power" = 1 - ))) - -/datum/computer/file/embedded_program/simple_vent_controller/process() - return 0 - - -/obj/machinery/embedded_controller/radio/simple_vent_controller - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_control_standby" - - name = "vent controller" - density = FALSE - - frequency = FREQ_ATMOS_CONTROL - power_channel = AREA_USAGE_ENVIRON - - // Setup parameters only - var/airpump_tag - -/obj/machinery/embedded_controller/radio/simple_vent_controller/Initialize(mapload) - . = ..() - if(!mapload) - return - var/datum/computer/file/embedded_program/simple_vent_controller/new_prog = new - - new_prog.airpump_tag = airpump_tag - new_prog.master = src - program = new_prog - -/obj/machinery/embedded_controller/radio/simple_vent_controller/update_icon_state() - if(on && program) - icon_state = "airlock_control_standby" - else - icon_state = "airlock_control_off" - - -/obj/machinery/embedded_controller/radio/simple_vent_controller/return_text() - var/state_options = null - state_options = {"Deactivate Vent
    -Activate Vent / Pump
    -Activate Vent / Clear
    "} - var/output = {"Vent Control Console
    -[state_options]
    "} - - return output +/datum/computer/file/embedded_program/simple_vent_controller + + var/airpump_tag + +/datum/computer/file/embedded_program/simple_vent_controller/receive_user_command(command) + switch(command) + if("vent_inactive") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "sigtype" = "command", + "power" = 0 + ))) + + if("vent_pump") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "sigtype" = "command", + "stabilize" = 1, + "power" = 1 + ))) + + if("vent_clear") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "sigtype" = "command", + "purge" = 1, + "power" = 1 + ))) + +/datum/computer/file/embedded_program/simple_vent_controller/process() + return 0 + + +/obj/machinery/embedded_controller/radio/simple_vent_controller + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "airlock_control_standby" + + name = "vent controller" + density = FALSE + + frequency = FREQ_ATMOS_CONTROL + power_channel = AREA_USAGE_ENVIRON + + // Setup parameters only + var/airpump_tag + +/obj/machinery/embedded_controller/radio/simple_vent_controller/Initialize(mapload) + . = ..() + if(!mapload) + return + var/datum/computer/file/embedded_program/simple_vent_controller/new_prog = new + + new_prog.airpump_tag = airpump_tag + new_prog.master = src + program = new_prog + +/obj/machinery/embedded_controller/radio/simple_vent_controller/update_icon_state() + if(on && program) + icon_state = "airlock_control_standby" + else + icon_state = "airlock_control_off" + + +/obj/machinery/embedded_controller/radio/simple_vent_controller/return_text() + var/state_options = null + state_options = {"Deactivate Vent
    +Activate Vent / Pump
    +Activate Vent / Clear
    "} + var/output = {"Vent Control Console
    +[state_options]
    "} + + return output diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm index b8fd1110220..21b948eddf1 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -1,347 +1,347 @@ -#define FIREALARM_COOLDOWN 67 // Chosen fairly arbitrarily, it is the length of the audio in FireAlarm.ogg. The actual track length is 7 seconds 8ms but but the audio stops at 6s 700ms - -/obj/item/electronics/firealarm - name = "fire alarm electronics" - custom_price = 50 - desc = "A fire alarm circuit. Can handle heat levels up to 40 degrees celsius." - -/obj/item/wallframe/firealarm - name = "fire alarm frame" - desc = "Used for building fire alarms." - icon = 'icons/obj/monitors.dmi' - icon_state = "fire_bitem" - result_path = /obj/machinery/firealarm - -/obj/machinery/firealarm - name = "fire alarm" - desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever." - icon = 'icons/obj/monitors.dmi' - icon_state = "fire0" - max_integrity = 250 - integrity_failure = 0.4 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 6 - power_channel = AREA_USAGE_ENVIRON - resistance_flags = FIRE_PROOF - - light_power = 0 - light_range = 7 - light_color = "#ff3232" - - //Trick to get the glowing overlay visible from a distance - luminosity = 1 - - var/detecting = 1 - var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone - var/last_alarm = 0 - var/area/myarea = null - -/obj/machinery/firealarm/Initialize(mapload, dir, building) - . = ..() - if(dir) - src.setDir(dir) - if(building) - buildstage = 0 - panel_open = TRUE - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - update_icon() - myarea = get_area(src) - LAZYADD(myarea.firealarms, src) - -/obj/machinery/firealarm/Destroy() - LAZYREMOVE(myarea.firealarms, src) - return ..() - -/obj/machinery/firealarm/update_icon_state() - if(panel_open) - icon_state = "fire_b[buildstage]" - return - - if(machine_stat & BROKEN) - icon_state = "firex" - return - - icon_state = "fire0" - -/obj/machinery/firealarm/update_overlays() - . = ..() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - - if(machine_stat & NOPOWER) - return - - . += "fire_overlay" - - if(is_station_level(z)) - . += "fire_[GLOB.security_level]" - SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, EMISSIVE_PLANE, dir) - else - . += "fire_[SEC_LEVEL_GREEN]" - SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, EMISSIVE_PLANE, dir) - - var/area/A = get_area(src) - - if(!detecting || !A.fire) - . += "fire_off" - SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, EMISSIVE_PLANE, dir) - else if(obj_flags & EMAGGED) - . += "fire_emagged" - SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, EMISSIVE_PLANE, dir) - else - . += "fire_on" - SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, EMISSIVE_PLANE, dir) - -/obj/machinery/firealarm/emp_act(severity) - . = ..() - - if (. & EMP_PROTECT_SELF) - return - - if(prob(50 / severity)) - alarm() - -/obj/machinery/firealarm/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - update_icon() - if(user) - user.visible_message("Sparks fly out of [src]!", - "You emag [src], disabling its thermal sensors.") - playsound(src, "sparks", 50, TRUE) - -/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume) - if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !machine_stat) - alarm() - ..() - -/obj/machinery/firealarm/proc/alarm(mob/user) - if(!is_operational() || (last_alarm+FIREALARM_COOLDOWN > world.time)) - return - last_alarm = world.time - var/area/A = get_area(src) - A.firealert(src) - playsound(loc, 'goon/sound/machinery/FireAlarm.ogg', 75) - if(user) - log_game("[user] triggered a fire alarm at [COORD(src)]") - -/obj/machinery/firealarm/proc/reset(mob/user) - if(!is_operational()) - return - var/area/A = get_area(src) - A.firereset(src) - if(user) - log_game("[user] reset a fire alarm at [COORD(src)]") - -/obj/machinery/firealarm/attack_hand(mob/user) - if(buildstage != 2) - return ..() - add_fingerprint(user) - var/area/A = get_area(src) - if(A.fire) - reset(user) - else - alarm(user) - -/obj/machinery/firealarm/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/firealarm/attack_robot(mob/user) - return attack_hand(user) - -/obj/machinery/firealarm/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - - if(W.tool_behaviour == TOOL_SCREWDRIVER && buildstage == 2) - W.play_tool_sound(src) - panel_open = !panel_open - to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") - update_icon() - return - - if(panel_open) - - if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP) - if(obj_integrity < max_integrity) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin repairing [src]...") - if(W.use_tool(src, user, 40, volume=50)) - obj_integrity = max_integrity - to_chat(user, "You repair [src].") - else - to_chat(user, "[src] is already in good condition!") - return - - switch(buildstage) - if(2) - if(W.tool_behaviour == TOOL_MULTITOOL) - detecting = !detecting - if (src.detecting) - user.visible_message("[user] reconnects [src]'s detecting unit!", "You reconnect [src]'s detecting unit.") - else - user.visible_message("[user] disconnects [src]'s detecting unit!", "You disconnect [src]'s detecting unit.") - return - - else if(W.tool_behaviour == TOOL_WIRECUTTER) - buildstage = 1 - W.play_tool_sound(src) - new /obj/item/stack/cable_coil(user.loc, 5) - to_chat(user, "You cut the wires from \the [src].") - update_icon() - return - - else if(W.force) //hit and turn it on - ..() - var/area/A = get_area(src) - if(!A.fire) - alarm() - return - - if(1) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil = W - if(coil.get_amount() < 5) - to_chat(user, "You need more cable for this!") - else - coil.use(5) - buildstage = 2 - to_chat(user, "You wire \the [src].") - update_icon() - return - - else if(W.tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user.name] removes the electronics from [src.name].", \ - "You start prying out the circuit...") - if(W.use_tool(src, user, 20, volume=50)) - if(buildstage == 1) - if(machine_stat & BROKEN) - to_chat(user, "You remove the destroyed circuit.") - machine_stat &= ~BROKEN - else - to_chat(user, "You pry out the circuit.") - new /obj/item/electronics/firealarm(user.loc) - buildstage = 0 - update_icon() - return - if(0) - if(istype(W, /obj/item/electronics/firealarm)) - to_chat(user, "You insert the circuit.") - qdel(W) - buildstage = 1 - update_icon() - return - - else if(istype(W, /obj/item/electroadaptive_pseudocircuit)) - var/obj/item/electroadaptive_pseudocircuit/P = W - if(!P.adapt_circuit(user, 15)) - return - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a fire alarm circuit and slot it into the assembly.") - buildstage = 1 - update_icon() - return - - else if(W.tool_behaviour == TOOL_WRENCH) - user.visible_message("[user] removes the fire alarm assembly from the wall.", \ - "You remove the fire alarm assembly from the wall.") - var/obj/item/wallframe/firealarm/frame = new /obj/item/wallframe/firealarm() - frame.forceMove(user.drop_location()) - W.play_tool_sound(src) - qdel(src) - return - - return ..() - -/obj/machinery/firealarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if((buildstage == 0) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) - return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) - return FALSE - -/obj/machinery/firealarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_UPGRADE_SIMPLE_CIRCUITS) - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a fire alarm circuit and slot it into the assembly.") - buildstage = 1 - update_icon() - return TRUE - return FALSE - -/obj/machinery/firealarm/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(.) //damage received - if(obj_integrity > 0 && !(machine_stat & BROKEN) && buildstage != 0) - if(prob(33)) - alarm() - -/obj/machinery/firealarm/singularity_pull(S, current_size) - if (current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects, the fire alarm experiences integrity failure - deconstruct() - ..() - -/obj/machinery/firealarm/obj_break(damage_flag) - if(buildstage == 0) //can't break the electronics if there isn't any inside. - return - . = ..() - if(.) - LAZYREMOVE(myarea.firealarms, src) - -/obj/machinery/firealarm/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 1) - if(!(machine_stat & BROKEN)) - var/obj/item/I = new /obj/item/electronics/firealarm(loc) - if(!disassembled) - I.obj_integrity = I.max_integrity * 0.5 - new /obj/item/stack/cable_coil(loc, 3) - qdel(src) - -/obj/machinery/firealarm/proc/update_fire_light(fire) - if(fire == !!light_power) - return // do nothing if we're already active - if(fire) - set_light(l_power = 0.8) - else - set_light(l_power = 0) - -/* - * Return of Party button - */ - -/area - var/party = FALSE - -/obj/machinery/firealarm/partyalarm - name = "\improper PARTY BUTTON" - desc = "Cuban Pete is in the house!" - var/static/party_overlay - -/obj/machinery/firealarm/partyalarm/reset() - if (machine_stat & (NOPOWER|BROKEN)) - return - var/area/A = get_area(src) - if (!A || !A.party) - return - A.party = FALSE - A.cut_overlay(party_overlay) - -/obj/machinery/firealarm/partyalarm/alarm() - if (machine_stat & (NOPOWER|BROKEN)) - return - var/area/A = get_area(src) - if (!A || A.party || A.name == "Space") - return - A.party = TRUE - if (!party_overlay) - party_overlay = iconstate2appearance('icons/turf/areas.dmi', "party") - A.add_overlay(party_overlay) +#define FIREALARM_COOLDOWN 67 // Chosen fairly arbitrarily, it is the length of the audio in FireAlarm.ogg. The actual track length is 7 seconds 8ms but but the audio stops at 6s 700ms + +/obj/item/electronics/firealarm + name = "fire alarm electronics" + custom_price = 50 + desc = "A fire alarm circuit. Can handle heat levels up to 40 degrees celsius." + +/obj/item/wallframe/firealarm + name = "fire alarm frame" + desc = "Used for building fire alarms." + icon = 'icons/obj/monitors.dmi' + icon_state = "fire_bitem" + result_path = /obj/machinery/firealarm + +/obj/machinery/firealarm + name = "fire alarm" + desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever." + icon = 'icons/obj/monitors.dmi' + icon_state = "fire0" + max_integrity = 250 + integrity_failure = 0.4 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 6 + power_channel = AREA_USAGE_ENVIRON + resistance_flags = FIRE_PROOF + + light_power = 0 + light_range = 7 + light_color = "#ff3232" + + //Trick to get the glowing overlay visible from a distance + luminosity = 1 + + var/detecting = 1 + var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone + var/last_alarm = 0 + var/area/myarea = null + +/obj/machinery/firealarm/Initialize(mapload, dir, building) + . = ..() + if(dir) + src.setDir(dir) + if(building) + buildstage = 0 + panel_open = TRUE + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + update_icon() + myarea = get_area(src) + LAZYADD(myarea.firealarms, src) + +/obj/machinery/firealarm/Destroy() + LAZYREMOVE(myarea.firealarms, src) + return ..() + +/obj/machinery/firealarm/update_icon_state() + if(panel_open) + icon_state = "fire_b[buildstage]" + return + + if(machine_stat & BROKEN) + icon_state = "firex" + return + + icon_state = "fire0" + +/obj/machinery/firealarm/update_overlays() + . = ..() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + + if(machine_stat & NOPOWER) + return + + . += "fire_overlay" + + if(is_station_level(z)) + . += "fire_[GLOB.security_level]" + SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, EMISSIVE_PLANE, dir) + else + . += "fire_[SEC_LEVEL_GREEN]" + SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, EMISSIVE_PLANE, dir) + + var/area/A = get_area(src) + + if(!detecting || !A.fire) + . += "fire_off" + SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, EMISSIVE_PLANE, dir) + else if(obj_flags & EMAGGED) + . += "fire_emagged" + SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, EMISSIVE_PLANE, dir) + else + . += "fire_on" + SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, EMISSIVE_PLANE, dir) + +/obj/machinery/firealarm/emp_act(severity) + . = ..() + + if (. & EMP_PROTECT_SELF) + return + + if(prob(50 / severity)) + alarm() + +/obj/machinery/firealarm/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + update_icon() + if(user) + user.visible_message("Sparks fly out of [src]!", + "You emag [src], disabling its thermal sensors.") + playsound(src, "sparks", 50, TRUE) + +/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume) + if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !machine_stat) + alarm() + ..() + +/obj/machinery/firealarm/proc/alarm(mob/user) + if(!is_operational() || (last_alarm+FIREALARM_COOLDOWN > world.time)) + return + last_alarm = world.time + var/area/A = get_area(src) + A.firealert(src) + playsound(loc, 'goon/sound/machinery/FireAlarm.ogg', 75) + if(user) + log_game("[user] triggered a fire alarm at [COORD(src)]") + +/obj/machinery/firealarm/proc/reset(mob/user) + if(!is_operational()) + return + var/area/A = get_area(src) + A.firereset(src) + if(user) + log_game("[user] reset a fire alarm at [COORD(src)]") + +/obj/machinery/firealarm/attack_hand(mob/user) + if(buildstage != 2) + return ..() + add_fingerprint(user) + var/area/A = get_area(src) + if(A.fire) + reset(user) + else + alarm(user) + +/obj/machinery/firealarm/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/firealarm/attack_robot(mob/user) + return attack_hand(user) + +/obj/machinery/firealarm/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + + if(W.tool_behaviour == TOOL_SCREWDRIVER && buildstage == 2) + W.play_tool_sound(src) + panel_open = !panel_open + to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") + update_icon() + return + + if(panel_open) + + if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP) + if(obj_integrity < max_integrity) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin repairing [src]...") + if(W.use_tool(src, user, 40, volume=50)) + obj_integrity = max_integrity + to_chat(user, "You repair [src].") + else + to_chat(user, "[src] is already in good condition!") + return + + switch(buildstage) + if(2) + if(W.tool_behaviour == TOOL_MULTITOOL) + detecting = !detecting + if (src.detecting) + user.visible_message("[user] reconnects [src]'s detecting unit!", "You reconnect [src]'s detecting unit.") + else + user.visible_message("[user] disconnects [src]'s detecting unit!", "You disconnect [src]'s detecting unit.") + return + + else if(W.tool_behaviour == TOOL_WIRECUTTER) + buildstage = 1 + W.play_tool_sound(src) + new /obj/item/stack/cable_coil(user.loc, 5) + to_chat(user, "You cut the wires from \the [src].") + update_icon() + return + + else if(W.force) //hit and turn it on + ..() + var/area/A = get_area(src) + if(!A.fire) + alarm() + return + + if(1) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = W + if(coil.get_amount() < 5) + to_chat(user, "You need more cable for this!") + else + coil.use(5) + buildstage = 2 + to_chat(user, "You wire \the [src].") + update_icon() + return + + else if(W.tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user.name] removes the electronics from [src.name].", \ + "You start prying out the circuit...") + if(W.use_tool(src, user, 20, volume=50)) + if(buildstage == 1) + if(machine_stat & BROKEN) + to_chat(user, "You remove the destroyed circuit.") + machine_stat &= ~BROKEN + else + to_chat(user, "You pry out the circuit.") + new /obj/item/electronics/firealarm(user.loc) + buildstage = 0 + update_icon() + return + if(0) + if(istype(W, /obj/item/electronics/firealarm)) + to_chat(user, "You insert the circuit.") + qdel(W) + buildstage = 1 + update_icon() + return + + else if(istype(W, /obj/item/electroadaptive_pseudocircuit)) + var/obj/item/electroadaptive_pseudocircuit/P = W + if(!P.adapt_circuit(user, 15)) + return + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a fire alarm circuit and slot it into the assembly.") + buildstage = 1 + update_icon() + return + + else if(W.tool_behaviour == TOOL_WRENCH) + user.visible_message("[user] removes the fire alarm assembly from the wall.", \ + "You remove the fire alarm assembly from the wall.") + var/obj/item/wallframe/firealarm/frame = new /obj/item/wallframe/firealarm() + frame.forceMove(user.drop_location()) + W.play_tool_sound(src) + qdel(src) + return + + return ..() + +/obj/machinery/firealarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + if((buildstage == 0) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) + return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) + return FALSE + +/obj/machinery/firealarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + switch(passed_mode) + if(RCD_UPGRADE_SIMPLE_CIRCUITS) + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a fire alarm circuit and slot it into the assembly.") + buildstage = 1 + update_icon() + return TRUE + return FALSE + +/obj/machinery/firealarm/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(.) //damage received + if(obj_integrity > 0 && !(machine_stat & BROKEN) && buildstage != 0) + if(prob(33)) + alarm() + +/obj/machinery/firealarm/singularity_pull(S, current_size) + if (current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects, the fire alarm experiences integrity failure + deconstruct() + ..() + +/obj/machinery/firealarm/obj_break(damage_flag) + if(buildstage == 0) //can't break the electronics if there isn't any inside. + return + . = ..() + if(.) + LAZYREMOVE(myarea.firealarms, src) + +/obj/machinery/firealarm/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc, 1) + if(!(machine_stat & BROKEN)) + var/obj/item/I = new /obj/item/electronics/firealarm(loc) + if(!disassembled) + I.obj_integrity = I.max_integrity * 0.5 + new /obj/item/stack/cable_coil(loc, 3) + qdel(src) + +/obj/machinery/firealarm/proc/update_fire_light(fire) + if(fire == !!light_power) + return // do nothing if we're already active + if(fire) + set_light(l_power = 0.8) + else + set_light(l_power = 0) + +/* + * Return of Party button + */ + +/area + var/party = FALSE + +/obj/machinery/firealarm/partyalarm + name = "\improper PARTY BUTTON" + desc = "Cuban Pete is in the house!" + var/static/party_overlay + +/obj/machinery/firealarm/partyalarm/reset() + if (machine_stat & (NOPOWER|BROKEN)) + return + var/area/A = get_area(src) + if (!A || !A.party) + return + A.party = FALSE + A.cut_overlay(party_overlay) + +/obj/machinery/firealarm/partyalarm/alarm() + if (machine_stat & (NOPOWER|BROKEN)) + return + var/area/A = get_area(src) + if (!A || A.party || A.name == "Space") + return + A.party = TRUE + if (!party_overlay) + party_overlay = iconstate2appearance('icons/turf/areas.dmi', "party") + A.add_overlay(party_overlay) diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm index a8196b91573..dcc36d24dbf 100644 --- a/code/game/machinery/flasher.dm +++ b/code/game/machinery/flasher.dm @@ -1,205 +1,205 @@ -// It is a gizmo that flashes a small area - -/obj/machinery/flasher - name = "mounted flash" - desc = "A wall-mounted flashbulb device." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mflash1" - max_integrity = 250 - integrity_failure = 0.4 - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - damage_deflection = 10 - var/obj/item/assembly/flash/handheld/bulb - var/id = null - var/range = 2 //this is roughly the size of brig cell - var/last_flash = 0 //Don't want it getting spammed like regular flashes - var/strength = 100 //How knocked down targets are when flashed. - var/base_state = "mflash" - -/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored - name = "portable flasher" - desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements." - icon_state = "pflash1-p" - strength = 80 - anchored = FALSE - base_state = "pflash" - density = TRUE - -/obj/machinery/flasher/Initialize(mapload, ndir = 0, built = 0) - . = ..() // ..() is EXTREMELY IMPORTANT, never forget to add it - if(built) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -28 : 28) - pixel_y = (dir & 3)? (dir ==1 ? -28 : 28) : 0 - else - bulb = new(src) - -/obj/machinery/flasher/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -/obj/machinery/flasher/Destroy() - QDEL_NULL(bulb) - return ..() - -/obj/machinery/flasher/powered() - if(!anchored || !bulb) - return FALSE - return ..() - -/obj/machinery/flasher/update_icon_state() - if (powered()) - if(bulb.burnt_out) - icon_state = "[base_state]1-p" - else - icon_state = "[base_state]1" - else - icon_state = "[base_state]1-p" - -//Don't want to render prison breaks impossible -/obj/machinery/flasher/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if (W.tool_behaviour == TOOL_WIRECUTTER) - if (bulb) - user.visible_message("[user] begins to disconnect [src]'s flashbulb.", "You begin to disconnect [src]'s flashbulb...") - if(W.use_tool(src, user, 30, volume=50) && bulb) - user.visible_message("[user] disconnects [src]'s flashbulb!", "You disconnect [src]'s flashbulb.") - bulb.forceMove(loc) - bulb = null - power_change() - - else if (istype(W, /obj/item/assembly/flash/handheld)) - if (!bulb) - if(!user.transferItemToLoc(W, src)) - return - user.visible_message("[user] installs [W] into [src].", "You install [W] into [src].") - bulb = W - power_change() - else - to_chat(user, "A flashbulb is already installed in [src]!") - - else if (W.tool_behaviour == TOOL_WRENCH) - if(!bulb) - to_chat(user, "You start unsecuring the flasher frame...") - if(W.use_tool(src, user, 40, volume=50)) - to_chat(user, "You unsecure the flasher frame.") - deconstruct(TRUE) - else - to_chat(user, "Remove a flashbulb from [src] first!") - else - return ..() - -//Let the AI trigger them directly. -/obj/machinery/flasher/attack_ai() - if (anchored) - return flash() - -/obj/machinery/flasher/proc/flash() - if (!powered() || !bulb) - return - - if (bulb.burnt_out || (last_flash && world.time < src.last_flash + 150)) - return - - if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast - power_change() - return - - playsound(src.loc, 'sound/weapons/flash.ogg', 100, TRUE) - flick("[base_state]_flash", src) - flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) - last_flash = world.time - use_power(1000) - - var/flashed = FALSE - for (var/mob/living/L in viewers(src, null)) - if (get_dist(src, L) > range) - continue - - if(L.flash_act(affect_silicon = 1)) - L.Paralyze(strength) - flashed = TRUE - - if(flashed) - bulb.times_used++ - - return 1 - - -/obj/machinery/flasher/emp_act(severity) - . = ..() - if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) - if(bulb && prob(75/severity)) - flash() - bulb.burn_out() - power_change() - -/obj/machinery/flasher/obj_break(damage_flag) - . = ..() - if(. && bulb) - bulb.burn_out() - power_change() - -/obj/machinery/flasher/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(bulb) - bulb.forceMove(loc) - bulb = null - if(disassembled) - var/obj/item/wallframe/flasher/F = new(get_turf(src)) - transfer_fingerprints_to(F) - F.id = id - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - else - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/machinery/flasher/portable/Initialize() - . = ..() - proximity_monitor = new(src, 0) - -/obj/machinery/flasher/portable/HasProximity(atom/movable/AM) - if (last_flash && world.time < last_flash + 150) - return - - if(istype(AM, /mob/living/carbon)) - var/mob/living/carbon/M = AM - if (M.m_intent != MOVE_INTENT_WALK && anchored) - flash() - -/obj/machinery/flasher/portable/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_WRENCH) - W.play_tool_sound(src, 100) - - if (!anchored && !isinspace()) - to_chat(user, "[src] is now secured.") - add_overlay("[base_state]-s") - setAnchored(TRUE) - power_change() - proximity_monitor.SetRange(range) - else - to_chat(user, "[src] can now be moved.") - cut_overlays() - setAnchored(FALSE) - power_change() - proximity_monitor.SetRange(0) - - else - return ..() - -/obj/item/wallframe/flasher - name = "mounted flash frame" - desc = "Used for building wall-mounted flashers." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mflash_frame" - result_path = /obj/machinery/flasher - var/id = null - -/obj/item/wallframe/flasher/examine(mob/user) - . = ..() - . += "Its channel ID is '[id]'." - -/obj/item/wallframe/flasher/after_attach(var/obj/O) - ..() - var/obj/machinery/flasher/F = O - F.id = id +// It is a gizmo that flashes a small area + +/obj/machinery/flasher + name = "mounted flash" + desc = "A wall-mounted flashbulb device." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mflash1" + max_integrity = 250 + integrity_failure = 0.4 + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + damage_deflection = 10 + var/obj/item/assembly/flash/handheld/bulb + var/id = null + var/range = 2 //this is roughly the size of brig cell + var/last_flash = 0 //Don't want it getting spammed like regular flashes + var/strength = 100 //How knocked down targets are when flashed. + var/base_state = "mflash" + +/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored + name = "portable flasher" + desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements." + icon_state = "pflash1-p" + strength = 80 + anchored = FALSE + base_state = "pflash" + density = TRUE + +/obj/machinery/flasher/Initialize(mapload, ndir = 0, built = 0) + . = ..() // ..() is EXTREMELY IMPORTANT, never forget to add it + if(built) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -28 : 28) + pixel_y = (dir & 3)? (dir ==1 ? -28 : 28) : 0 + else + bulb = new(src) + +/obj/machinery/flasher/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +/obj/machinery/flasher/Destroy() + QDEL_NULL(bulb) + return ..() + +/obj/machinery/flasher/powered() + if(!anchored || !bulb) + return FALSE + return ..() + +/obj/machinery/flasher/update_icon_state() + if (powered()) + if(bulb.burnt_out) + icon_state = "[base_state]1-p" + else + icon_state = "[base_state]1" + else + icon_state = "[base_state]1-p" + +//Don't want to render prison breaks impossible +/obj/machinery/flasher/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if (W.tool_behaviour == TOOL_WIRECUTTER) + if (bulb) + user.visible_message("[user] begins to disconnect [src]'s flashbulb.", "You begin to disconnect [src]'s flashbulb...") + if(W.use_tool(src, user, 30, volume=50) && bulb) + user.visible_message("[user] disconnects [src]'s flashbulb!", "You disconnect [src]'s flashbulb.") + bulb.forceMove(loc) + bulb = null + power_change() + + else if (istype(W, /obj/item/assembly/flash/handheld)) + if (!bulb) + if(!user.transferItemToLoc(W, src)) + return + user.visible_message("[user] installs [W] into [src].", "You install [W] into [src].") + bulb = W + power_change() + else + to_chat(user, "A flashbulb is already installed in [src]!") + + else if (W.tool_behaviour == TOOL_WRENCH) + if(!bulb) + to_chat(user, "You start unsecuring the flasher frame...") + if(W.use_tool(src, user, 40, volume=50)) + to_chat(user, "You unsecure the flasher frame.") + deconstruct(TRUE) + else + to_chat(user, "Remove a flashbulb from [src] first!") + else + return ..() + +//Let the AI trigger them directly. +/obj/machinery/flasher/attack_ai() + if (anchored) + return flash() + +/obj/machinery/flasher/proc/flash() + if (!powered() || !bulb) + return + + if (bulb.burnt_out || (last_flash && world.time < src.last_flash + 150)) + return + + if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast + power_change() + return + + playsound(src.loc, 'sound/weapons/flash.ogg', 100, TRUE) + flick("[base_state]_flash", src) + flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) + last_flash = world.time + use_power(1000) + + var/flashed = FALSE + for (var/mob/living/L in viewers(src, null)) + if (get_dist(src, L) > range) + continue + + if(L.flash_act(affect_silicon = 1)) + L.Paralyze(strength) + flashed = TRUE + + if(flashed) + bulb.times_used++ + + return 1 + + +/obj/machinery/flasher/emp_act(severity) + . = ..() + if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) + if(bulb && prob(75/severity)) + flash() + bulb.burn_out() + power_change() + +/obj/machinery/flasher/obj_break(damage_flag) + . = ..() + if(. && bulb) + bulb.burn_out() + power_change() + +/obj/machinery/flasher/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(bulb) + bulb.forceMove(loc) + bulb = null + if(disassembled) + var/obj/item/wallframe/flasher/F = new(get_turf(src)) + transfer_fingerprints_to(F) + F.id = id + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + else + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/machinery/flasher/portable/Initialize() + . = ..() + proximity_monitor = new(src, 0) + +/obj/machinery/flasher/portable/HasProximity(atom/movable/AM) + if (last_flash && world.time < last_flash + 150) + return + + if(istype(AM, /mob/living/carbon)) + var/mob/living/carbon/M = AM + if (M.m_intent != MOVE_INTENT_WALK && anchored) + flash() + +/obj/machinery/flasher/portable/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_WRENCH) + W.play_tool_sound(src, 100) + + if (!anchored && !isinspace()) + to_chat(user, "[src] is now secured.") + add_overlay("[base_state]-s") + setAnchored(TRUE) + power_change() + proximity_monitor.SetRange(range) + else + to_chat(user, "[src] can now be moved.") + cut_overlays() + setAnchored(FALSE) + power_change() + proximity_monitor.SetRange(0) + + else + return ..() + +/obj/item/wallframe/flasher + name = "mounted flash frame" + desc = "Used for building wall-mounted flashers." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mflash_frame" + result_path = /obj/machinery/flasher + var/id = null + +/obj/item/wallframe/flasher/examine(mob/user) + . = ..() + . += "Its channel ID is '[id]'." + +/obj/item/wallframe/flasher/after_attach(var/obj/O) + ..() + var/obj/machinery/flasher/F = O + F.id = id diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index 2e24be98302..50841416bf1 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -1,701 +1,701 @@ -/* Holograms! - * Contains: - * Holopad - * Hologram - * Other stuff - */ - -/* -Revised. Original based on space ninja hologram code. Which is also mine. /N -How it works: -AI clicks on holopad in camera view. View centers on holopad. -AI clicks again on the holopad to display a hologram. Hologram stays as long as AI is looking at the pad and it (the hologram) is in range of the pad. -AI can use the directional keys to move the hologram around, provided the above conditions are met and the AI in question is the holopad's master. -Any number of AIs can use a holopad. /Lo6 -AI may cancel the hologram at any time by clicking on the holopad once more. - -Possible to do for anyone motivated enough: - Give an AI variable for different hologram icons. - Itegrate EMP effect to disable the unit. -*/ - - -/* - * Holopad - */ - -#define HOLOPAD_PASSIVE_POWER_USAGE 1 -#define HOLOGRAM_POWER_USAGE 2 - -/obj/machinery/holopad - name = "holopad" - desc = "It's a floor-mounted device for projecting holographic images." - icon_state = "holopad0" - layer = LOW_OBJ_LAYER - plane = FLOOR_PLANE - flags_1 = HEAR_1 - req_access = list(ACCESS_KEYCARD_AUTH) //Used to allow for forced connecting to other (not secure) holopads. Anyone can make a call, though. - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 100 - max_integrity = 300 - armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - circuit = /obj/item/circuitboard/machine/holopad - ui_x = 440 - ui_y = 245 - /// List of living mobs that use the holopad - var/list/masters - /// Holoray-mob link - var/list/holorays - /// To prevent request spam. ~Carn - var/last_request = 0 - /// Change to change how far the AI can move away from the holopad before deactivating - var/holo_range = 5 - /// Array of /datum/holocalls - var/list/holo_calls - /// Currently outgoing holocall, do not modify the datums only check and call the public procs - var/datum/holocall/outgoing_call - /// Record disk - var/obj/item/disk/holodisk/disk - /// Currently replaying a recording - var/replay_mode = FALSE - /// Currently looping a recording - var/loop_mode = FALSE - /// Currently recording - var/record_mode = FALSE - /// Recording start time - var/record_start = 0 - /// User that inititiated the recording - var/record_user - /// Replay hologram - var/obj/effect/overlay/holo_pad_hologram/replay_holo - /// Calls will be automatically answered after a couple rings, here for debugging - var/static/force_answer_call = FALSE - var/static/list/holopads = list() - var/obj/effect/overlay/holoray/ray - var/ringing = FALSE - var/offset = FALSE - var/on_network = TRUE - /// For pads in secure areas; do not allow forced connecting - var/secure = FALSE - /// If we are currently calling another holopad - var/calling = FALSE - -/obj/machinery/holopad/secure - name = "secure holopad" - desc = "It's a floor-mounted device for projecting holographic images. This one will refuse to auto-connect incoming calls." - secure = TRUE - -obj/machinery/holopad/secure/Initialize() - . = ..() - var/obj/item/circuitboard/machine/holopad/board = circuit - board.secure = TRUE - board.build_path = /obj/machinery/holopad/secure - -/obj/machinery/holopad/tutorial - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - flags_1 = NODECONSTRUCT_1 - on_network = FALSE - var/proximity_range = 1 - -/obj/machinery/holopad/tutorial/Initialize(mapload) - . = ..() - if(proximity_range) - proximity_monitor = new(src, proximity_range) - if(mapload) - var/obj/item/disk/holodisk/new_disk = locate(/obj/item/disk/holodisk) in src.loc - if(new_disk && !disk) - new_disk.forceMove(src) - disk = new_disk - -/obj/machinery/holopad/tutorial/attack_hand(mob/user) - if(!istype(user)) - return - if(user.incapacitated() || !is_operational()) - return - if(replay_mode) - replay_stop() - else if(disk && disk.record) - replay_start() - -/obj/machinery/holopad/tutorial/HasProximity(atom/movable/AM) - if (!isliving(AM)) - return - if(!replay_mode && (disk && disk.record)) - replay_start() - -/obj/machinery/holopad/Initialize() - . = ..() - if(on_network) - holopads += src - -/obj/machinery/holopad/Destroy() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.ConnectionFailure(src) - - for (var/I in masters) - clear_holo(I) - - if(replay_mode) - replay_stop() - if(record_mode) - record_stop() - - QDEL_NULL(disk) - - holopads -= src - return ..() - -/obj/machinery/holopad/power_change() - . = ..() - if (!powered()) - if(replay_mode) - replay_stop() - if(record_mode) - record_stop() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/holopad/obj_break() - . = ..() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/holopad/RefreshParts() - var/holograph_range = 4 - for(var/obj/item/stock_parts/capacitor/B in component_parts) - holograph_range += 1 * B.rating - holo_range = holograph_range - -/obj/machinery/holopad/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Current projection range: [holo_range] units." - -/obj/machinery/holopad/attackby(obj/item/P, mob/user, params) - if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) - return - - if(default_pry_open(P)) - return - - if(default_unfasten_wrench(user, P)) - return - - if(default_deconstruction_crowbar(P)) - return - - if(istype(P,/obj/item/disk/holodisk)) - if(disk) - to_chat(user,"There's already a disk inside [src]!") - return - if (!user.transferItemToLoc(P,src)) - return - to_chat(user,"You insert [P] into [src].") - disk = P - return - - return ..() - -/obj/machinery/holopad/ui_status(mob/user) - if(!is_operational()) - return UI_CLOSE - if(outgoing_call && !calling) - return UI_CLOSE - return ..() - -/obj/machinery/holopad/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Holopad", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/holopad/ui_data(mob/user) - var/list/data = list() - data["calling"] = calling - data["on_network"] = on_network - data["on_cooldown"] = last_request + 200 < world.time ? FALSE : TRUE - data["allowed"] = allowed(user) - data["disk"] = disk ? TRUE : FALSE - data["disk_record"] = disk?.record ? TRUE : FALSE - data["replay_mode"] = replay_mode - data["loop_mode"] = loop_mode - data["record_mode"] = record_mode - data["holo_calls"] = list() - for(var/I in holo_calls) - var/datum/holocall/HC = I - var/list/call_data = list( - caller = HC.user, - connected = HC.connected_holopad == src ? TRUE : FALSE, - ref = REF(HC) - ) - data["holo_calls"] += list(call_data) - return data - -/obj/machinery/holopad/ui_act(action, list/params) - . = ..() - if(.) - return - - switch(action) - if("AIrequest") - if(last_request + 200 < world.time) - last_request = world.time - to_chat(usr, "You requested an AI's presence.") - var/area/area = get_area(src) - for(var/mob/living/silicon/ai/AI in GLOB.silicon_mobs) - if(!AI.client) - continue - to_chat(AI, "Your presence is requested at \the [area].") - return TRUE - else - to_chat(usr, "A request for AI presence was already sent recently.") - return - if("holocall") - if(outgoing_call) - return - if(usr.loc == loc) - var/list/callnames = list() - for(var/I in holopads) - var/area/A = get_area(I) - if(A) - LAZYADD(callnames[A], I) - callnames -= get_area(src) - var/result = input(usr, "Choose an area to call", "Holocall") as null|anything in sortNames(callnames) - if(QDELETED(usr) || !result || outgoing_call) - return - if(usr.loc == loc) - var/input = text2num(params["headcall"]) - var/headcall = input == 1 ? TRUE : FALSE - new /datum/holocall(usr, src, callnames[result], headcall) - calling = TRUE - return TRUE - else - to_chat(usr, "You must stand on the holopad to make a call!") - if("connectcall") - var/datum/holocall/call_to_connect = locate(params["holopad"]) in holo_calls - if(!QDELETED(call_to_connect)) - call_to_connect.Answer(src) - return TRUE - if("disconnectcall") - var/datum/holocall/call_to_disconnect = locate(params["holopad"]) in holo_calls - if(!QDELETED(call_to_disconnect)) - call_to_disconnect.Disconnect(src) - return TRUE - if("disk_eject") - if(disk && !replay_mode) - disk.forceMove(drop_location()) - disk = null - return TRUE - if("replay_mode") - if(replay_mode) - replay_stop() - return TRUE - else - replay_start() - return TRUE - if("loop_mode") - loop_mode = !loop_mode - return TRUE - if("record_mode") - if(record_mode) - record_stop() - return TRUE - else - record_start(usr) - return TRUE - if("record_clear") - record_clear() - return TRUE - if("offset") - offset++ - if(offset > 4) - offset = FALSE - var/turf/new_turf - if(!offset) - new_turf = get_turf(src) - else - new_turf = get_step(src, GLOB.cardinals[offset]) - replay_holo.forceMove(new_turf) - return TRUE - if("hang_up") - if(outgoing_call) - outgoing_call.Disconnect(src) - return TRUE - -/** - * hangup_all_calls: Disconnects all current holocalls from the holopad - */ -/obj/machinery/holopad/proc/hangup_all_calls() - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.Disconnect(src) - -//do not allow AIs to answer calls or people will use it to meta the AI sattelite -/obj/machinery/holopad/attack_ai(mob/living/silicon/ai/user) - if (!istype(user)) - return - if (!on_network) - return - /*There are pretty much only three ways to interact here. - I don't need to check for client since they're clicking on an object. - This may change in the future but for now will suffice.*/ - if(user.eyeobj.loc != src.loc)//Set client eye on the object if it's not already. - user.eyeobj.setLoc(get_turf(src)) - else if(!LAZYLEN(masters) || !masters[user])//If there is no hologram, possibly make one. - activate_holo(user) - else//If there is a hologram, remove it. - clear_holo(user) - -/obj/machinery/holopad/process() - if(LAZYLEN(masters)) - for(var/I in masters) - var/mob/living/master = I - var/mob/living/silicon/ai/AI = master - if(!istype(AI)) - AI = null - - if(!is_operational() || !validate_user(master)) - clear_holo(master) - - if(outgoing_call) - outgoing_call.Check() - - ringing = FALSE - - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad != src) - if(force_answer_call && world.time > (HC.call_start_time + (HOLOPAD_MAX_DIAL_TIME / 2))) - HC.Answer(src) - break - if(HC.head_call && !secure) - HC.Answer(src) - break - if(outgoing_call) - HC.Disconnect(src)//can't answer calls while calling - else - playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring! - ringing = TRUE - - update_icon() - -/obj/machinery/holopad/proc/activate_holo(mob/living/user) - var/mob/living/silicon/ai/AI = user - if(!istype(AI)) - AI = null - - if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it - if (AI && istype(AI.current, /obj/machinery/holopad)) - to_chat(user, "ERROR: \black Image feed in progress.") - return - - var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. - if(AI) - Hologram.icon = AI.holo_icon - else //make it like real life - Hologram.icon = user.icon - Hologram.icon_state = user.icon_state - Hologram.copy_overlays(user, TRUE) - //codersprite some holo effects here - Hologram.alpha = 100 - Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) - Hologram.Impersonation = user - - Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. - Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. - Hologram.setAnchored(TRUE)//So space wind cannot drag it. - Hologram.name = "[user.name] (Hologram)"//If someone decides to right click. - Hologram.set_light(2) //hologram lighting - move_hologram() - - set_holo(user, Hologram) - visible_message("A holographic image of [user] flickers to life before your eyes!") - - return Hologram - else - to_chat(user, "ERROR: Unable to project hologram.") - -/*This is the proc for special two-way communication between AI and holopad/people talking near holopad. -For the other part of the code, check silicon say.dm. Particularly robot talk.*/ -/obj/machinery/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker && LAZYLEN(masters) && !radio_freq)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. - for(var/mob/living/silicon/ai/master in masters) - if(masters[master] && speaker != master) - master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad == src && speaker != HC.hologram) - HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - if(outgoing_call && speaker == outgoing_call.user) - outgoing_call.hologram.say(raw_message) - - if(record_mode && speaker == record_user) - record_message(speaker,raw_message,message_language) - -/obj/machinery/holopad/proc/SetLightsAndPower() - var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) - use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE - active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) - if(total_users || replay_mode) - set_light(2) - else - set_light(0) - update_icon() - -/obj/machinery/holopad/update_icon_state() - var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) - if(ringing) - icon_state = "holopad_ringing" - else if(total_users || replay_mode) - icon_state = "holopad1" - else - icon_state = "holopad0" - -/obj/machinery/holopad/proc/set_holo(mob/living/user, obj/effect/overlay/holo_pad_hologram/h) - LAZYSET(masters, user, h) - LAZYSET(holorays, user, new /obj/effect/overlay/holoray(loc)) - var/mob/living/silicon/ai/AI = user - if(istype(AI)) - AI.current = src - SetLightsAndPower() - update_holoray(user, get_turf(loc)) - return TRUE - -/obj/machinery/holopad/proc/clear_holo(mob/living/user) - qdel(masters[user]) // Get rid of user's hologram - unset_holo(user) - return TRUE - -/obj/machinery/holopad/proc/unset_holo(mob/living/user) - var/mob/living/silicon/ai/AI = user - if(istype(AI) && AI.current == src) - AI.current = null - LAZYREMOVE(masters, user) // Discard AI from the list of those who use holopad - qdel(holorays[user]) - LAZYREMOVE(holorays, user) - SetLightsAndPower() - return TRUE - -//Try to transfer hologram to another pad that can project on T -/obj/machinery/holopad/proc/transfer_to_nearby_pad(turf/T,mob/holo_owner) - var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] - if(!h || h.HC) //Holocalls can't change source. - return FALSE - for(var/pad in holopads) - var/obj/machinery/holopad/another = pad - if(another == src) - continue - if(another.validate_location(T)) - unset_holo(holo_owner) - if(another.masters && another.masters[holo_owner]) - another.clear_holo(holo_owner) - another.set_holo(holo_owner, h) - return TRUE - return FALSE - -/obj/machinery/holopad/proc/validate_user(mob/living/user) - if(QDELETED(user) || user.incapacitated() || !user.client) - return FALSE - return TRUE - -//Can we display holos there -//Area check instead of line of sight check because this is a called a lot if AI wants to move around. -/obj/machinery/holopad/proc/validate_location(turf/T,check_los = FALSE) - if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) - return TRUE - else - return FALSE - -/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) - if(LAZYLEN(masters) && masters[user]) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/transfered = FALSE - if(!validate_location(new_turf)) - if(!transfer_to_nearby_pad(new_turf,user)) - clear_holo(user) - return FALSE - else - transfered = TRUE - //All is good. - holo.forceMove(new_turf) - if(!transfered) - update_holoray(user,new_turf) - return TRUE - - -/obj/machinery/holopad/proc/update_holoray(mob/living/user, turf/new_turf) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/obj/effect/overlay/holoray/ray = holorays[user] - var/disty = holo.y - ray.y - var/distx = holo.x - ray.x - var/newangle - if(!disty) - if(distx >= 0) - newangle = 90 - else - newangle = 270 - else - newangle = arctan(distx/disty) - if(disty < 0) - newangle += 180 - else if(distx < 0) - newangle += 360 - var/matrix/M = matrix() - if (get_dist(get_turf(holo),new_turf) <= 1) - animate(ray, transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle),time = 1) - else - ray.transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle) - -// RECORDED MESSAGES - -/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) - var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. - Hologram.add_overlay(record.caller_image) - Hologram.alpha = 170 - Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) - Hologram.dir = SOUTH //for now - var/datum/language_holder/holder = Hologram.get_language_holder() - holder.selected_language = record.language - Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. - Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. - Hologram.setAnchored(TRUE)//So space wind cannot drag it. - Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click. - Hologram.set_light(2) //hologram lighting - visible_message("A holographic image of [record.caller_name] flickers to life before your eyes!") - return Hologram - -/obj/machinery/holopad/proc/replay_start() - if(!replay_mode) - replay_mode = TRUE - replay_holo = setup_replay_holo(disk.record) - SetLightsAndPower() - replay_entry(1) - -/obj/machinery/holopad/proc/replay_stop() - if(replay_mode) - replay_mode = FALSE - offset = FALSE - QDEL_NULL(replay_holo) - SetLightsAndPower() - -/obj/machinery/holopad/proc/record_start(mob/living/user) - if(!user || !disk || disk.record) - return - disk.record = new - record_mode = TRUE - record_start = world.time - record_user = user - disk.record.set_caller_image(user) - -/obj/machinery/holopad/proc/record_message(mob/living/speaker,message,language) - if(!record_mode) - return - //make this command so you can have multiple languages in single record - if((!disk.record.caller_name || disk.record.caller_name == "Unknown") && istype(speaker)) - disk.record.caller_name = speaker.name - if(!disk.record.language) - disk.record.language = language - else if(language != disk.record.language) - disk.record.entries += list(list(HOLORECORD_LANGUAGE,language)) - - var/current_delay = 0 - for(var/E in disk.record.entries) - var/list/entry = E - if(entry[1] != HOLORECORD_DELAY) - continue - current_delay += entry[2] - - var/time_delta = world.time - record_start - current_delay - - if(time_delta >= 1) - disk.record.entries += list(list(HOLORECORD_DELAY,time_delta)) - disk.record.entries += list(list(HOLORECORD_SAY,message)) - if(disk.record.entries.len >= HOLORECORD_MAX_LENGTH) - record_stop() - -/obj/machinery/holopad/proc/replay_entry(entry_number) - if(!replay_mode) - return - if (!disk.record.entries.len) // check for zero entries such as photographs and no text recordings - return // and pretty much just display them statically untill manually stopped - if(disk.record.entries.len < entry_number) - if(loop_mode) - entry_number = 1 - else - replay_stop() - return - var/list/entry = disk.record.entries[entry_number] - var/command = entry[1] - switch(command) - if(HOLORECORD_SAY) - var/message = entry[2] - if(replay_holo) - replay_holo.say(message) - if(HOLORECORD_SOUND) - playsound(src,entry[2],50,TRUE) - if(HOLORECORD_DELAY) - addtimer(CALLBACK(src,.proc/replay_entry,entry_number+1),entry[2]) - return - if(HOLORECORD_LANGUAGE) - var/datum/language_holder/holder = replay_holo.get_language_holder() - holder.selected_language = entry[2] - if(HOLORECORD_PRESET) - var/preset_type = entry[2] - var/datum/preset_holoimage/H = new preset_type - replay_holo.cut_overlays() - replay_holo.add_overlay(H.build_image()) - if(HOLORECORD_RENAME) - replay_holo.name = entry[2] + " (Hologram)" - .(entry_number+1) - -/obj/machinery/holopad/proc/record_stop() - if(record_mode) - record_mode = FALSE - record_user = null - -/obj/machinery/holopad/proc/record_clear() - if(disk && disk.record) - QDEL_NULL(disk.record) - -/obj/effect/overlay/holo_pad_hologram - initial_language_holder = /datum/language_holder/universal - var/mob/living/Impersonation - var/datum/holocall/HC - -/obj/effect/overlay/holo_pad_hologram/Destroy() - Impersonation = null - if(!QDELETED(HC)) - HC.Disconnect(HC.calling_holopad) - return ..() - -/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0) - return TRUE - -/obj/effect/overlay/holo_pad_hologram/examine(mob/user) - if(Impersonation) - return Impersonation.examine(user) - return ..() - -/obj/effect/overlay/holoray - name = "holoray" - icon = 'icons/effects/96x96.dmi' - icon_state = "holoray" - layer = FLY_LAYER - density = FALSE - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - pixel_x = -32 - pixel_y = -32 - alpha = 100 - -#undef HOLOPAD_PASSIVE_POWER_USAGE -#undef HOLOGRAM_POWER_USAGE +/* Holograms! + * Contains: + * Holopad + * Hologram + * Other stuff + */ + +/* +Revised. Original based on space ninja hologram code. Which is also mine. /N +How it works: +AI clicks on holopad in camera view. View centers on holopad. +AI clicks again on the holopad to display a hologram. Hologram stays as long as AI is looking at the pad and it (the hologram) is in range of the pad. +AI can use the directional keys to move the hologram around, provided the above conditions are met and the AI in question is the holopad's master. +Any number of AIs can use a holopad. /Lo6 +AI may cancel the hologram at any time by clicking on the holopad once more. + +Possible to do for anyone motivated enough: + Give an AI variable for different hologram icons. + Itegrate EMP effect to disable the unit. +*/ + + +/* + * Holopad + */ + +#define HOLOPAD_PASSIVE_POWER_USAGE 1 +#define HOLOGRAM_POWER_USAGE 2 + +/obj/machinery/holopad + name = "holopad" + desc = "It's a floor-mounted device for projecting holographic images." + icon_state = "holopad0" + layer = LOW_OBJ_LAYER + plane = FLOOR_PLANE + flags_1 = HEAR_1 + req_access = list(ACCESS_KEYCARD_AUTH) //Used to allow for forced connecting to other (not secure) holopads. Anyone can make a call, though. + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 100 + max_integrity = 300 + armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) + circuit = /obj/item/circuitboard/machine/holopad + ui_x = 440 + ui_y = 245 + /// List of living mobs that use the holopad + var/list/masters + /// Holoray-mob link + var/list/holorays + /// To prevent request spam. ~Carn + var/last_request = 0 + /// Change to change how far the AI can move away from the holopad before deactivating + var/holo_range = 5 + /// Array of /datum/holocalls + var/list/holo_calls + /// Currently outgoing holocall, do not modify the datums only check and call the public procs + var/datum/holocall/outgoing_call + /// Record disk + var/obj/item/disk/holodisk/disk + /// Currently replaying a recording + var/replay_mode = FALSE + /// Currently looping a recording + var/loop_mode = FALSE + /// Currently recording + var/record_mode = FALSE + /// Recording start time + var/record_start = 0 + /// User that inititiated the recording + var/record_user + /// Replay hologram + var/obj/effect/overlay/holo_pad_hologram/replay_holo + /// Calls will be automatically answered after a couple rings, here for debugging + var/static/force_answer_call = FALSE + var/static/list/holopads = list() + var/obj/effect/overlay/holoray/ray + var/ringing = FALSE + var/offset = FALSE + var/on_network = TRUE + /// For pads in secure areas; do not allow forced connecting + var/secure = FALSE + /// If we are currently calling another holopad + var/calling = FALSE + +/obj/machinery/holopad/secure + name = "secure holopad" + desc = "It's a floor-mounted device for projecting holographic images. This one will refuse to auto-connect incoming calls." + secure = TRUE + +obj/machinery/holopad/secure/Initialize() + . = ..() + var/obj/item/circuitboard/machine/holopad/board = circuit + board.secure = TRUE + board.build_path = /obj/machinery/holopad/secure + +/obj/machinery/holopad/tutorial + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + flags_1 = NODECONSTRUCT_1 + on_network = FALSE + var/proximity_range = 1 + +/obj/machinery/holopad/tutorial/Initialize(mapload) + . = ..() + if(proximity_range) + proximity_monitor = new(src, proximity_range) + if(mapload) + var/obj/item/disk/holodisk/new_disk = locate(/obj/item/disk/holodisk) in src.loc + if(new_disk && !disk) + new_disk.forceMove(src) + disk = new_disk + +/obj/machinery/holopad/tutorial/attack_hand(mob/user) + if(!istype(user)) + return + if(user.incapacitated() || !is_operational()) + return + if(replay_mode) + replay_stop() + else if(disk && disk.record) + replay_start() + +/obj/machinery/holopad/tutorial/HasProximity(atom/movable/AM) + if (!isliving(AM)) + return + if(!replay_mode && (disk && disk.record)) + replay_start() + +/obj/machinery/holopad/Initialize() + . = ..() + if(on_network) + holopads += src + +/obj/machinery/holopad/Destroy() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.ConnectionFailure(src) + + for (var/I in masters) + clear_holo(I) + + if(replay_mode) + replay_stop() + if(record_mode) + record_stop() + + QDEL_NULL(disk) + + holopads -= src + return ..() + +/obj/machinery/holopad/power_change() + . = ..() + if (!powered()) + if(replay_mode) + replay_stop() + if(record_mode) + record_stop() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/holopad/obj_break() + . = ..() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/holopad/RefreshParts() + var/holograph_range = 4 + for(var/obj/item/stock_parts/capacitor/B in component_parts) + holograph_range += 1 * B.rating + holo_range = holograph_range + +/obj/machinery/holopad/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Current projection range: [holo_range] units." + +/obj/machinery/holopad/attackby(obj/item/P, mob/user, params) + if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) + return + + if(default_pry_open(P)) + return + + if(default_unfasten_wrench(user, P)) + return + + if(default_deconstruction_crowbar(P)) + return + + if(istype(P,/obj/item/disk/holodisk)) + if(disk) + to_chat(user,"There's already a disk inside [src]!") + return + if (!user.transferItemToLoc(P,src)) + return + to_chat(user,"You insert [P] into [src].") + disk = P + return + + return ..() + +/obj/machinery/holopad/ui_status(mob/user) + if(!is_operational()) + return UI_CLOSE + if(outgoing_call && !calling) + return UI_CLOSE + return ..() + +/obj/machinery/holopad/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Holopad", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/holopad/ui_data(mob/user) + var/list/data = list() + data["calling"] = calling + data["on_network"] = on_network + data["on_cooldown"] = last_request + 200 < world.time ? FALSE : TRUE + data["allowed"] = allowed(user) + data["disk"] = disk ? TRUE : FALSE + data["disk_record"] = disk?.record ? TRUE : FALSE + data["replay_mode"] = replay_mode + data["loop_mode"] = loop_mode + data["record_mode"] = record_mode + data["holo_calls"] = list() + for(var/I in holo_calls) + var/datum/holocall/HC = I + var/list/call_data = list( + caller = HC.user, + connected = HC.connected_holopad == src ? TRUE : FALSE, + ref = REF(HC) + ) + data["holo_calls"] += list(call_data) + return data + +/obj/machinery/holopad/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("AIrequest") + if(last_request + 200 < world.time) + last_request = world.time + to_chat(usr, "You requested an AI's presence.") + var/area/area = get_area(src) + for(var/mob/living/silicon/ai/AI in GLOB.silicon_mobs) + if(!AI.client) + continue + to_chat(AI, "Your presence is requested at \the [area].") + return TRUE + else + to_chat(usr, "A request for AI presence was already sent recently.") + return + if("holocall") + if(outgoing_call) + return + if(usr.loc == loc) + var/list/callnames = list() + for(var/I in holopads) + var/area/A = get_area(I) + if(A) + LAZYADD(callnames[A], I) + callnames -= get_area(src) + var/result = input(usr, "Choose an area to call", "Holocall") as null|anything in sortNames(callnames) + if(QDELETED(usr) || !result || outgoing_call) + return + if(usr.loc == loc) + var/input = text2num(params["headcall"]) + var/headcall = input == 1 ? TRUE : FALSE + new /datum/holocall(usr, src, callnames[result], headcall) + calling = TRUE + return TRUE + else + to_chat(usr, "You must stand on the holopad to make a call!") + if("connectcall") + var/datum/holocall/call_to_connect = locate(params["holopad"]) in holo_calls + if(!QDELETED(call_to_connect)) + call_to_connect.Answer(src) + return TRUE + if("disconnectcall") + var/datum/holocall/call_to_disconnect = locate(params["holopad"]) in holo_calls + if(!QDELETED(call_to_disconnect)) + call_to_disconnect.Disconnect(src) + return TRUE + if("disk_eject") + if(disk && !replay_mode) + disk.forceMove(drop_location()) + disk = null + return TRUE + if("replay_mode") + if(replay_mode) + replay_stop() + return TRUE + else + replay_start() + return TRUE + if("loop_mode") + loop_mode = !loop_mode + return TRUE + if("record_mode") + if(record_mode) + record_stop() + return TRUE + else + record_start(usr) + return TRUE + if("record_clear") + record_clear() + return TRUE + if("offset") + offset++ + if(offset > 4) + offset = FALSE + var/turf/new_turf + if(!offset) + new_turf = get_turf(src) + else + new_turf = get_step(src, GLOB.cardinals[offset]) + replay_holo.forceMove(new_turf) + return TRUE + if("hang_up") + if(outgoing_call) + outgoing_call.Disconnect(src) + return TRUE + +/** + * hangup_all_calls: Disconnects all current holocalls from the holopad + */ +/obj/machinery/holopad/proc/hangup_all_calls() + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.Disconnect(src) + +//do not allow AIs to answer calls or people will use it to meta the AI sattelite +/obj/machinery/holopad/attack_ai(mob/living/silicon/ai/user) + if (!istype(user)) + return + if (!on_network) + return + /*There are pretty much only three ways to interact here. + I don't need to check for client since they're clicking on an object. + This may change in the future but for now will suffice.*/ + if(user.eyeobj.loc != src.loc)//Set client eye on the object if it's not already. + user.eyeobj.setLoc(get_turf(src)) + else if(!LAZYLEN(masters) || !masters[user])//If there is no hologram, possibly make one. + activate_holo(user) + else//If there is a hologram, remove it. + clear_holo(user) + +/obj/machinery/holopad/process() + if(LAZYLEN(masters)) + for(var/I in masters) + var/mob/living/master = I + var/mob/living/silicon/ai/AI = master + if(!istype(AI)) + AI = null + + if(!is_operational() || !validate_user(master)) + clear_holo(master) + + if(outgoing_call) + outgoing_call.Check() + + ringing = FALSE + + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad != src) + if(force_answer_call && world.time > (HC.call_start_time + (HOLOPAD_MAX_DIAL_TIME / 2))) + HC.Answer(src) + break + if(HC.head_call && !secure) + HC.Answer(src) + break + if(outgoing_call) + HC.Disconnect(src)//can't answer calls while calling + else + playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring! + ringing = TRUE + + update_icon() + +/obj/machinery/holopad/proc/activate_holo(mob/living/user) + var/mob/living/silicon/ai/AI = user + if(!istype(AI)) + AI = null + + if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it + if (AI && istype(AI.current, /obj/machinery/holopad)) + to_chat(user, "ERROR: \black Image feed in progress.") + return + + var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. + if(AI) + Hologram.icon = AI.holo_icon + else //make it like real life + Hologram.icon = user.icon + Hologram.icon_state = user.icon_state + Hologram.copy_overlays(user, TRUE) + //codersprite some holo effects here + Hologram.alpha = 100 + Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) + Hologram.Impersonation = user + + Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. + Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. + Hologram.setAnchored(TRUE)//So space wind cannot drag it. + Hologram.name = "[user.name] (Hologram)"//If someone decides to right click. + Hologram.set_light(2) //hologram lighting + move_hologram() + + set_holo(user, Hologram) + visible_message("A holographic image of [user] flickers to life before your eyes!") + + return Hologram + else + to_chat(user, "ERROR: Unable to project hologram.") + +/*This is the proc for special two-way communication between AI and holopad/people talking near holopad. +For the other part of the code, check silicon say.dm. Particularly robot talk.*/ +/obj/machinery/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(speaker && LAZYLEN(masters) && !radio_freq)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. + for(var/mob/living/silicon/ai/master in masters) + if(masters[master] && speaker != master) + master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad == src && speaker != HC.hologram) + HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + if(outgoing_call && speaker == outgoing_call.user) + outgoing_call.hologram.say(raw_message) + + if(record_mode && speaker == record_user) + record_message(speaker,raw_message,message_language) + +/obj/machinery/holopad/proc/SetLightsAndPower() + var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) + use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE + active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) + if(total_users || replay_mode) + set_light(2) + else + set_light(0) + update_icon() + +/obj/machinery/holopad/update_icon_state() + var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) + if(ringing) + icon_state = "holopad_ringing" + else if(total_users || replay_mode) + icon_state = "holopad1" + else + icon_state = "holopad0" + +/obj/machinery/holopad/proc/set_holo(mob/living/user, obj/effect/overlay/holo_pad_hologram/h) + LAZYSET(masters, user, h) + LAZYSET(holorays, user, new /obj/effect/overlay/holoray(loc)) + var/mob/living/silicon/ai/AI = user + if(istype(AI)) + AI.current = src + SetLightsAndPower() + update_holoray(user, get_turf(loc)) + return TRUE + +/obj/machinery/holopad/proc/clear_holo(mob/living/user) + qdel(masters[user]) // Get rid of user's hologram + unset_holo(user) + return TRUE + +/obj/machinery/holopad/proc/unset_holo(mob/living/user) + var/mob/living/silicon/ai/AI = user + if(istype(AI) && AI.current == src) + AI.current = null + LAZYREMOVE(masters, user) // Discard AI from the list of those who use holopad + qdel(holorays[user]) + LAZYREMOVE(holorays, user) + SetLightsAndPower() + return TRUE + +//Try to transfer hologram to another pad that can project on T +/obj/machinery/holopad/proc/transfer_to_nearby_pad(turf/T,mob/holo_owner) + var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] + if(!h || h.HC) //Holocalls can't change source. + return FALSE + for(var/pad in holopads) + var/obj/machinery/holopad/another = pad + if(another == src) + continue + if(another.validate_location(T)) + unset_holo(holo_owner) + if(another.masters && another.masters[holo_owner]) + another.clear_holo(holo_owner) + another.set_holo(holo_owner, h) + return TRUE + return FALSE + +/obj/machinery/holopad/proc/validate_user(mob/living/user) + if(QDELETED(user) || user.incapacitated() || !user.client) + return FALSE + return TRUE + +//Can we display holos there +//Area check instead of line of sight check because this is a called a lot if AI wants to move around. +/obj/machinery/holopad/proc/validate_location(turf/T,check_los = FALSE) + if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) + return TRUE + else + return FALSE + +/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) + if(LAZYLEN(masters) && masters[user]) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/transfered = FALSE + if(!validate_location(new_turf)) + if(!transfer_to_nearby_pad(new_turf,user)) + clear_holo(user) + return FALSE + else + transfered = TRUE + //All is good. + holo.forceMove(new_turf) + if(!transfered) + update_holoray(user,new_turf) + return TRUE + + +/obj/machinery/holopad/proc/update_holoray(mob/living/user, turf/new_turf) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/obj/effect/overlay/holoray/ray = holorays[user] + var/disty = holo.y - ray.y + var/distx = holo.x - ray.x + var/newangle + if(!disty) + if(distx >= 0) + newangle = 90 + else + newangle = 270 + else + newangle = arctan(distx/disty) + if(disty < 0) + newangle += 180 + else if(distx < 0) + newangle += 360 + var/matrix/M = matrix() + if (get_dist(get_turf(holo),new_turf) <= 1) + animate(ray, transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle),time = 1) + else + ray.transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle) + +// RECORDED MESSAGES + +/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) + var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. + Hologram.add_overlay(record.caller_image) + Hologram.alpha = 170 + Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) + Hologram.dir = SOUTH //for now + var/datum/language_holder/holder = Hologram.get_language_holder() + holder.selected_language = record.language + Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. + Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. + Hologram.setAnchored(TRUE)//So space wind cannot drag it. + Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click. + Hologram.set_light(2) //hologram lighting + visible_message("A holographic image of [record.caller_name] flickers to life before your eyes!") + return Hologram + +/obj/machinery/holopad/proc/replay_start() + if(!replay_mode) + replay_mode = TRUE + replay_holo = setup_replay_holo(disk.record) + SetLightsAndPower() + replay_entry(1) + +/obj/machinery/holopad/proc/replay_stop() + if(replay_mode) + replay_mode = FALSE + offset = FALSE + QDEL_NULL(replay_holo) + SetLightsAndPower() + +/obj/machinery/holopad/proc/record_start(mob/living/user) + if(!user || !disk || disk.record) + return + disk.record = new + record_mode = TRUE + record_start = world.time + record_user = user + disk.record.set_caller_image(user) + +/obj/machinery/holopad/proc/record_message(mob/living/speaker,message,language) + if(!record_mode) + return + //make this command so you can have multiple languages in single record + if((!disk.record.caller_name || disk.record.caller_name == "Unknown") && istype(speaker)) + disk.record.caller_name = speaker.name + if(!disk.record.language) + disk.record.language = language + else if(language != disk.record.language) + disk.record.entries += list(list(HOLORECORD_LANGUAGE,language)) + + var/current_delay = 0 + for(var/E in disk.record.entries) + var/list/entry = E + if(entry[1] != HOLORECORD_DELAY) + continue + current_delay += entry[2] + + var/time_delta = world.time - record_start - current_delay + + if(time_delta >= 1) + disk.record.entries += list(list(HOLORECORD_DELAY,time_delta)) + disk.record.entries += list(list(HOLORECORD_SAY,message)) + if(disk.record.entries.len >= HOLORECORD_MAX_LENGTH) + record_stop() + +/obj/machinery/holopad/proc/replay_entry(entry_number) + if(!replay_mode) + return + if (!disk.record.entries.len) // check for zero entries such as photographs and no text recordings + return // and pretty much just display them statically untill manually stopped + if(disk.record.entries.len < entry_number) + if(loop_mode) + entry_number = 1 + else + replay_stop() + return + var/list/entry = disk.record.entries[entry_number] + var/command = entry[1] + switch(command) + if(HOLORECORD_SAY) + var/message = entry[2] + if(replay_holo) + replay_holo.say(message) + if(HOLORECORD_SOUND) + playsound(src,entry[2],50,TRUE) + if(HOLORECORD_DELAY) + addtimer(CALLBACK(src,.proc/replay_entry,entry_number+1),entry[2]) + return + if(HOLORECORD_LANGUAGE) + var/datum/language_holder/holder = replay_holo.get_language_holder() + holder.selected_language = entry[2] + if(HOLORECORD_PRESET) + var/preset_type = entry[2] + var/datum/preset_holoimage/H = new preset_type + replay_holo.cut_overlays() + replay_holo.add_overlay(H.build_image()) + if(HOLORECORD_RENAME) + replay_holo.name = entry[2] + " (Hologram)" + .(entry_number+1) + +/obj/machinery/holopad/proc/record_stop() + if(record_mode) + record_mode = FALSE + record_user = null + +/obj/machinery/holopad/proc/record_clear() + if(disk && disk.record) + QDEL_NULL(disk.record) + +/obj/effect/overlay/holo_pad_hologram + initial_language_holder = /datum/language_holder/universal + var/mob/living/Impersonation + var/datum/holocall/HC + +/obj/effect/overlay/holo_pad_hologram/Destroy() + Impersonation = null + if(!QDELETED(HC)) + HC.Disconnect(HC.calling_holopad) + return ..() + +/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0) + return TRUE + +/obj/effect/overlay/holo_pad_hologram/examine(mob/user) + if(Impersonation) + return Impersonation.examine(user) + return ..() + +/obj/effect/overlay/holoray + name = "holoray" + icon = 'icons/effects/96x96.dmi' + icon_state = "holoray" + layer = FLY_LAYER + density = FALSE + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + pixel_x = -32 + pixel_y = -32 + alpha = 100 + +#undef HOLOPAD_PASSIVE_POWER_USAGE +#undef HOLOGRAM_POWER_USAGE diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm index 4d97055d7d5..8121cae2ea2 100644 --- a/code/game/machinery/igniter.dm +++ b/code/game/machinery/igniter.dm @@ -1,138 +1,138 @@ -/obj/machinery/igniter - name = "igniter" - desc = "It's useful for igniting plasma." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "igniter0" - plane = FLOOR_PLANE - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - max_integrity = 300 - armor = list("melee" = 50, "bullet" = 30, "laser" = 70, "energy" = 50, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - var/id = null - var/on = FALSE - -/obj/machinery/igniter/incinerator_toxmix - id = INCINERATOR_TOXMIX_IGNITER - -/obj/machinery/igniter/incinerator_atmos - id = INCINERATOR_ATMOS_IGNITER - -/obj/machinery/igniter/incinerator_syndicatelava - id = INCINERATOR_SYNDICATELAVA_IGNITER - -/obj/machinery/igniter/on - on = TRUE - icon_state = "igniter1" - -/obj/machinery/igniter/attack_hand(mob/user) - . = ..() - if(.) - return - add_fingerprint(user) - - use_power(50) - on = !( on ) - update_icon() - -/obj/machinery/igniter/process() //ugh why is this even in process()? - if (src.on && !(machine_stat & NOPOWER) ) - var/turf/location = src.loc - if (isturf(location)) - location.hotspot_expose(1000,500,1) - return 1 - -/obj/machinery/igniter/Initialize() - . = ..() - icon_state = "igniter[on]" - -/obj/machinery/igniter/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "igniter0" - else - icon_state = "igniter[on]" - -/obj/machinery/igniter/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -// Wall mounted remote-control igniter. - -/obj/machinery/sparker - name = "mounted igniter" - desc = "A wall-mounted ignition device." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "migniter" - resistance_flags = FIRE_PROOF - var/id = null - var/disable = 0 - var/last_spark = 0 - var/datum/effect_system/spark_spread/spark_system - -/obj/machinery/sparker/toxmix - id = INCINERATOR_TOXMIX_IGNITER - -/obj/machinery/sparker/Initialize() - . = ..() - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(2, 1, src) - spark_system.attach(src) - -/obj/machinery/sparker/Destroy() - QDEL_NULL(spark_system) - return ..() - -/obj/machinery/sparker/update_icon_state() - if(disable) - icon_state = "[initial(icon_state)]-d" - else if(powered()) - icon_state = "[initial(icon_state)]" - else - icon_state = "[initial(icon_state)]-p" - -/obj/machinery/sparker/powered() - if(!disable) - return FALSE - return ..() - -/obj/machinery/sparker/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_SCREWDRIVER) - add_fingerprint(user) - src.disable = !src.disable - if (src.disable) - user.visible_message("[user] disables \the [src]!", "You disable the connection to \the [src].") - if (!src.disable) - user.visible_message("[user] reconnects \the [src]!", "You fix the connection to \the [src].") - update_icon() - else - return ..() - -/obj/machinery/sparker/attack_ai() - if (anchored) - return src.ignite() - else - return - -/obj/machinery/sparker/proc/ignite() - if (!(powered())) - return - - if ((src.disable) || (src.last_spark && world.time < src.last_spark + 50)) - return - - - flick("[initial(icon_state)]-spark", src) - spark_system.start() - last_spark = world.time - use_power(1000) - var/turf/location = src.loc - if (isturf(location)) - location.hotspot_expose(1000,2500,1) - return 1 - -/obj/machinery/sparker/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(!(machine_stat & (BROKEN|NOPOWER))) - ignite() +/obj/machinery/igniter + name = "igniter" + desc = "It's useful for igniting plasma." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "igniter0" + plane = FLOOR_PLANE + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + max_integrity = 300 + armor = list("melee" = 50, "bullet" = 30, "laser" = 70, "energy" = 50, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + var/id = null + var/on = FALSE + +/obj/machinery/igniter/incinerator_toxmix + id = INCINERATOR_TOXMIX_IGNITER + +/obj/machinery/igniter/incinerator_atmos + id = INCINERATOR_ATMOS_IGNITER + +/obj/machinery/igniter/incinerator_syndicatelava + id = INCINERATOR_SYNDICATELAVA_IGNITER + +/obj/machinery/igniter/on + on = TRUE + icon_state = "igniter1" + +/obj/machinery/igniter/attack_hand(mob/user) + . = ..() + if(.) + return + add_fingerprint(user) + + use_power(50) + on = !( on ) + update_icon() + +/obj/machinery/igniter/process() //ugh why is this even in process()? + if (src.on && !(machine_stat & NOPOWER) ) + var/turf/location = src.loc + if (isturf(location)) + location.hotspot_expose(1000,500,1) + return 1 + +/obj/machinery/igniter/Initialize() + . = ..() + icon_state = "igniter[on]" + +/obj/machinery/igniter/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "igniter0" + else + icon_state = "igniter[on]" + +/obj/machinery/igniter/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +// Wall mounted remote-control igniter. + +/obj/machinery/sparker + name = "mounted igniter" + desc = "A wall-mounted ignition device." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "migniter" + resistance_flags = FIRE_PROOF + var/id = null + var/disable = 0 + var/last_spark = 0 + var/datum/effect_system/spark_spread/spark_system + +/obj/machinery/sparker/toxmix + id = INCINERATOR_TOXMIX_IGNITER + +/obj/machinery/sparker/Initialize() + . = ..() + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(2, 1, src) + spark_system.attach(src) + +/obj/machinery/sparker/Destroy() + QDEL_NULL(spark_system) + return ..() + +/obj/machinery/sparker/update_icon_state() + if(disable) + icon_state = "[initial(icon_state)]-d" + else if(powered()) + icon_state = "[initial(icon_state)]" + else + icon_state = "[initial(icon_state)]-p" + +/obj/machinery/sparker/powered() + if(!disable) + return FALSE + return ..() + +/obj/machinery/sparker/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_SCREWDRIVER) + add_fingerprint(user) + src.disable = !src.disable + if (src.disable) + user.visible_message("[user] disables \the [src]!", "You disable the connection to \the [src].") + if (!src.disable) + user.visible_message("[user] reconnects \the [src]!", "You fix the connection to \the [src].") + update_icon() + else + return ..() + +/obj/machinery/sparker/attack_ai() + if (anchored) + return src.ignite() + else + return + +/obj/machinery/sparker/proc/ignite() + if (!(powered())) + return + + if ((src.disable) || (src.last_spark && world.time < src.last_spark + 50)) + return + + + flick("[initial(icon_state)]-spark", src) + spark_system.start() + last_spark = world.time + use_power(1000) + var/turf/location = src.loc + if (isturf(location)) + location.hotspot_expose(1000,2500,1) + return 1 + +/obj/machinery/sparker/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(!(machine_stat & (BROKEN|NOPOWER))) + ignite() diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm index 5841080ad23..72590088a06 100644 --- a/code/game/machinery/lightswitch.dm +++ b/code/game/machinery/lightswitch.dm @@ -1,64 +1,64 @@ -/// The light switch. Can have multiple per area. -/obj/machinery/light_switch - name = "light switch" - icon = 'icons/obj/power.dmi' - icon_state = "light1" - desc = "Make dark." - power_channel = AREA_USAGE_LIGHT - /// Set this to a string, path, or area instance to control that area - /// instead of the switch's location. - var/area/area = null - -/obj/machinery/light_switch/Initialize() - . = ..() - if(istext(area)) - area = text2path(area) - if(ispath(area)) - area = GLOB.areas_by_type[area] - if(!area) - area = get_area(src) - - if(!name) - name = "light switch ([area.name])" - - update_icon() - -/obj/machinery/light_switch/update_icon_state() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - luminosity = 0 - if(machine_stat & NOPOWER) - icon_state = "light-p" - else - luminosity = 1 - SSvis_overlays.add_vis_overlay(src, icon, "light-glow", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - if(area.lightswitch) - icon_state = "light1" - else - icon_state = "light0" - -/obj/machinery/light_switch/examine(mob/user) - . = ..() - . += "It is [area.lightswitch ? "on" : "off"]." - -/obj/machinery/light_switch/interact(mob/user) - . = ..() - - area.lightswitch = !area.lightswitch - area.update_icon() - - for(var/obj/machinery/light_switch/L in area) - L.update_icon() - - area.power_change() - -/obj/machinery/light_switch/power_change() - SHOULD_CALL_PARENT(0) - if(area == get_area(src)) - return ..() - -/obj/machinery/light_switch/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(!(machine_stat & (BROKEN|NOPOWER))) - power_change() +/// The light switch. Can have multiple per area. +/obj/machinery/light_switch + name = "light switch" + icon = 'icons/obj/power.dmi' + icon_state = "light1" + desc = "Make dark." + power_channel = AREA_USAGE_LIGHT + /// Set this to a string, path, or area instance to control that area + /// instead of the switch's location. + var/area/area = null + +/obj/machinery/light_switch/Initialize() + . = ..() + if(istext(area)) + area = text2path(area) + if(ispath(area)) + area = GLOB.areas_by_type[area] + if(!area) + area = get_area(src) + + if(!name) + name = "light switch ([area.name])" + + update_icon() + +/obj/machinery/light_switch/update_icon_state() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + luminosity = 0 + if(machine_stat & NOPOWER) + icon_state = "light-p" + else + luminosity = 1 + SSvis_overlays.add_vis_overlay(src, icon, "light-glow", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + if(area.lightswitch) + icon_state = "light1" + else + icon_state = "light0" + +/obj/machinery/light_switch/examine(mob/user) + . = ..() + . += "It is [area.lightswitch ? "on" : "off"]." + +/obj/machinery/light_switch/interact(mob/user) + . = ..() + + area.lightswitch = !area.lightswitch + area.update_icon() + + for(var/obj/machinery/light_switch/L in area) + L.update_icon() + + area.power_change() + +/obj/machinery/light_switch/power_change() + SHOULD_CALL_PARENT(0) + if(area == get_area(src)) + return ..() + +/obj/machinery/light_switch/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(!(machine_stat & (BROKEN|NOPOWER))) + power_change() diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm index 02c320f2a48..4c18e747af4 100644 --- a/code/game/machinery/magnet.dm +++ b/code/game/machinery/magnet.dm @@ -1,378 +1,378 @@ -// Magnetic attractor, creates variable magnetic fields and attraction. -// Can also be used to emit electron/proton beams to create a center of magnetism on another tile - -// tl;dr: it's magnets lol -// This was created for firing ranges, but I suppose this could have other applications - Doohl - -/obj/machinery/magnetic_module - icon = 'icons/obj/objects.dmi' - icon_state = "floor_magnet-f" - name = "electromagnetic generator" - desc = "A device that uses station power to create points of magnetic energy." - layer = LOW_OBJ_LAYER - use_power = IDLE_POWER_USE - idle_power_usage = 50 - - var/freq = FREQ_MAGNETS // radio frequency - var/electricity_level = 1 // intensity of the magnetic pull - var/magnetic_field = 1 // the range of magnetic attraction - var/code = 0 // frequency code, they should be different unless you have a group of magnets working together or something - var/turf/center // the center of magnetic attraction - var/on = FALSE - var/magneting = FALSE - - // x, y modifiers to the center turf; (0, 0) is centered on the magnet, whereas (1, -1) is one tile right, one tile down - var/center_x = 0 - var/center_y = 0 - var/max_dist = 20 // absolute value of center_x,y cannot exceed this integer - -/obj/machinery/magnetic_module/Initialize() - ..() - var/turf/T = loc - center = T - SSradio.add_object(src, freq, RADIO_MAGNETS) - - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) - - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/magnetic_module/LateInitialize() - magnetic_process() - -/obj/machinery/magnetic_module/Destroy() - SSradio.remove_object(src, freq) - center = null - return ..() - - -// update the icon_state -/obj/machinery/magnetic_module/update_icon_state() - var/state="floor_magnet" - var/onstate="" - - if(!on) - onstate="0" - - icon_state = "[state][onstate]" - -/obj/machinery/magnetic_module/receive_signal(datum/signal/signal) - - var/command = signal.data["command"] - var/modifier = signal.data["modifier"] - var/signal_code = signal.data["code"] - if(command && (signal_code == code)) - - Cmd(command, modifier) - - - -/obj/machinery/magnetic_module/proc/Cmd(command, modifier) - - if(command) - switch(command) - if("set-electriclevel") - if(modifier) - electricity_level = modifier - if("set-magneticfield") - if(modifier) - magnetic_field = modifier - - if("add-elec") - electricity_level++ - if(electricity_level > 12) - electricity_level = 12 - if("sub-elec") - electricity_level-- - if(electricity_level <= 0) - electricity_level = 1 - if("add-mag") - magnetic_field++ - if(magnetic_field > 4) - magnetic_field = 4 - if("sub-mag") - magnetic_field-- - if(magnetic_field <= 0) - magnetic_field = 1 - - if("set-x") - if(modifier) - center_x = modifier - if("set-y") - if(modifier) - center_y = modifier - - if("N") // NORTH - center_y++ - if("S") // SOUTH - center_y-- - if("E") // EAST - center_x++ - if("W") // WEST - center_x-- - if("C") // CENTER - center_x = 0 - center_y = 0 - if("R") // RANDOM - center_x = rand(-max_dist, max_dist) - center_y = rand(-max_dist, max_dist) - - if("set-code") - if(modifier) - code = modifier - if("toggle-power") - on = !on - - if(on) - INVOKE_ASYNC(src, .proc/magnetic_process) - - - -/obj/machinery/magnetic_module/process() - if(machine_stat & NOPOWER) - on = FALSE - - // Sanity checks: - if(electricity_level <= 0) - electricity_level = 1 - if(magnetic_field <= 0) - magnetic_field = 1 - - - // Limitations: - if(abs(center_x) > max_dist) - center_x = max_dist - if(abs(center_y) > max_dist) - center_y = max_dist - if(magnetic_field > 4) - magnetic_field = 4 - if(electricity_level > 12) - electricity_level = 12 - - // Update power usage: - if(on) - use_power = ACTIVE_POWER_USE - active_power_usage = electricity_level*15 - else - use_power = NO_POWER_USE - - update_icon() - - -/obj/machinery/magnetic_module/proc/magnetic_process() // proc that actually does the magneting - if(magneting) - return - while(on) - - magneting = TRUE - center = locate(x+center_x, y+center_y, z) - if(center) - for(var/obj/M in orange(magnetic_field, center)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - step_towards(M, center) - - for(var/mob/living/silicon/S in orange(magnetic_field, center)) - if(isAI(S)) - continue - step_towards(S, center) - - use_power(electricity_level * 5) - sleep(13 - electricity_level) - - magneting = FALSE - - - - -/obj/machinery/magnetic_controller - name = "magnetic control console" - icon = 'icons/obj/airlock_machines.dmi' // uses an airlock machine icon, THINK GREEN HELP THE ENVIRONMENT - RECYCLING! - icon_state = "airlock_control_standby" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 45 - var/frequency = FREQ_MAGNETS - var/code = 0 - var/list/magnets = list() - var/title = "Magnetic Control Console" - var/autolink = 0 // if set to 1, can't probe for other magnets! - - var/pathpos = 1 // position in the path - var/path = "w;e;e;w;s;n;n;s" // text path of the magnet - var/speed = 1 // lowest = 1, highest = 10 - var/list/rpath = list() // real path of the magnet, used in iterator - - var/moving = 0 // 1 if scheduled to loop - var/looping = 0 // 1 if looping - - var/datum/radio_frequency/radio_connection - - -/obj/machinery/magnetic_controller/Initialize() - . = ..() - if(autolink) - for(var/obj/machinery/magnetic_module/M in GLOB.machines) - if(M.freq == frequency && M.code == code) - magnets.Add(M) - - if(path) // check for default path - filter_path() // renders rpath - radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS) - -/obj/machinery/magnetic_controller/Destroy() - SSradio.remove_object(src, frequency) - magnets = null - rpath = null - return ..() - -/obj/machinery/magnetic_controller/process() - if(magnets.len == 0 && autolink) - for(var/obj/machinery/magnetic_module/M in GLOB.machines) - if(M.freq == frequency && M.code == code) - magnets.Add(M) - -/obj/machinery/magnetic_controller/ui_interact(mob/user) - . = ..() - var/dat = "Magnetic Control Console

    " - if(!autolink) - dat += {" - Frequency: [frequency]
    - Code: [code]
    - Probe Generators
    - "} - - if(magnets.len >= 1) - - dat += "Magnets confirmed:
    " - var/i = 0 - for(var/obj/machinery/magnetic_module/M in magnets) - i++ - dat += "     < \[[i]\] ([M.on ? "On":"Off"]) | Electricity level: - [M.electricity_level] +; Magnetic field: - [M.magnetic_field] +
    " - - dat += "
    Speed: - [speed] +
    " - dat += "Path: {[path]}
    " - dat += "Moving: [moving ? "Enabled":"Disabled"]" - - - user << browse(dat, "window=magnet;size=400x500") - onclose(user, "magnet") - -/obj/machinery/magnetic_controller/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - - if(href_list["radio-op"]) - - // Prepare signal beforehand, because this is a radio operation - var/datum/signal/signal = new(list("code" = code)) - - // Apply any necessary commands - switch(href_list["radio-op"]) - if("togglepower") - signal.data["command"] = "toggle-power" - - if("minuselec") - signal.data["command"] = "sub-elec" - if("pluselec") - signal.data["command"] = "add-elec" - - if("minusmag") - signal.data["command"] = "sub-mag" - if("plusmag") - signal.data["command"] = "add-mag" - - - // Broadcast the signal - - radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) - - updateUsrDialog() - - if(href_list["operation"]) - switch(href_list["operation"]) - if("plusspeed") - speed ++ - if(speed > 10) - speed = 10 - if("minusspeed") - speed -- - if(speed <= 0) - speed = 1 - if("setpath") - var/newpath = stripped_input(usr, "Please define a new path!", "New Path", path, MAX_MESSAGE_LEN) - if(newpath && newpath != "") - moving = 0 // stop moving - path = newpath - pathpos = 1 // reset position - filter_path() // renders rpath - - if("togglemoving") - moving = !moving - if(moving) - INVOKE_ASYNC(src, .proc/MagnetMove) - - - updateUsrDialog() - -/obj/machinery/magnetic_controller/proc/MagnetMove() - if(looping) - return - - while(moving && rpath.len >= 1) - - if(machine_stat & (BROKEN|NOPOWER)) - break - - looping = 1 - - // Prepare the radio signal - var/datum/signal/signal = new(list("code" = code)) - - if(pathpos > rpath.len) // if the position is greater than the length, we just loop through the list! - pathpos = 1 - - var/nextmove = uppertext(rpath[pathpos]) // makes it un-case-sensitive - - if(!(nextmove in list("N","S","E","W","C","R"))) - // N, S, E, W are directional - // C is center - // R is random (in magnetic field's bounds) - qdel(signal) - break // break the loop if the character located is invalid - - signal.data["command"] = nextmove - - - pathpos++ // increase iterator - - // Broadcast the signal - INVOKE_ASYNC(radio_connection, /datum/radio_frequency.proc/post_signal, src, signal, RADIO_MAGNETS) - - if(speed == 10) - sleep(1) - else - sleep(12-speed) - - looping = 0 - - -/obj/machinery/magnetic_controller/proc/filter_path() - // Generates the rpath variable using the path string, think of this as "string2list" - // Doesn't use params2list() because of the akward way it stacks entities - rpath = list() // clear rpath - var/maximum_characters = 50 - var/lentext = length(path) - var/nextchar = "" - var/charcount = 0 - - for(var/i = 1, i <= lentext, i += length(nextchar)) // iterates through all characters in path - nextchar = path[i] // find next character - - if(nextchar in list(";", "&", "*", " ")) // if char is a separator, ignore - continue - - rpath += nextchar // else, add to list - // there doesn't HAVE to be separators but it makes paths syntatically visible - charcount++ - if(charcount >= maximum_characters) - break +// Magnetic attractor, creates variable magnetic fields and attraction. +// Can also be used to emit electron/proton beams to create a center of magnetism on another tile + +// tl;dr: it's magnets lol +// This was created for firing ranges, but I suppose this could have other applications - Doohl + +/obj/machinery/magnetic_module + icon = 'icons/obj/objects.dmi' + icon_state = "floor_magnet-f" + name = "electromagnetic generator" + desc = "A device that uses station power to create points of magnetic energy." + layer = LOW_OBJ_LAYER + use_power = IDLE_POWER_USE + idle_power_usage = 50 + + var/freq = FREQ_MAGNETS // radio frequency + var/electricity_level = 1 // intensity of the magnetic pull + var/magnetic_field = 1 // the range of magnetic attraction + var/code = 0 // frequency code, they should be different unless you have a group of magnets working together or something + var/turf/center // the center of magnetic attraction + var/on = FALSE + var/magneting = FALSE + + // x, y modifiers to the center turf; (0, 0) is centered on the magnet, whereas (1, -1) is one tile right, one tile down + var/center_x = 0 + var/center_y = 0 + var/max_dist = 20 // absolute value of center_x,y cannot exceed this integer + +/obj/machinery/magnetic_module/Initialize() + ..() + var/turf/T = loc + center = T + SSradio.add_object(src, freq, RADIO_MAGNETS) + + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) + + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/magnetic_module/LateInitialize() + magnetic_process() + +/obj/machinery/magnetic_module/Destroy() + SSradio.remove_object(src, freq) + center = null + return ..() + + +// update the icon_state +/obj/machinery/magnetic_module/update_icon_state() + var/state="floor_magnet" + var/onstate="" + + if(!on) + onstate="0" + + icon_state = "[state][onstate]" + +/obj/machinery/magnetic_module/receive_signal(datum/signal/signal) + + var/command = signal.data["command"] + var/modifier = signal.data["modifier"] + var/signal_code = signal.data["code"] + if(command && (signal_code == code)) + + Cmd(command, modifier) + + + +/obj/machinery/magnetic_module/proc/Cmd(command, modifier) + + if(command) + switch(command) + if("set-electriclevel") + if(modifier) + electricity_level = modifier + if("set-magneticfield") + if(modifier) + magnetic_field = modifier + + if("add-elec") + electricity_level++ + if(electricity_level > 12) + electricity_level = 12 + if("sub-elec") + electricity_level-- + if(electricity_level <= 0) + electricity_level = 1 + if("add-mag") + magnetic_field++ + if(magnetic_field > 4) + magnetic_field = 4 + if("sub-mag") + magnetic_field-- + if(magnetic_field <= 0) + magnetic_field = 1 + + if("set-x") + if(modifier) + center_x = modifier + if("set-y") + if(modifier) + center_y = modifier + + if("N") // NORTH + center_y++ + if("S") // SOUTH + center_y-- + if("E") // EAST + center_x++ + if("W") // WEST + center_x-- + if("C") // CENTER + center_x = 0 + center_y = 0 + if("R") // RANDOM + center_x = rand(-max_dist, max_dist) + center_y = rand(-max_dist, max_dist) + + if("set-code") + if(modifier) + code = modifier + if("toggle-power") + on = !on + + if(on) + INVOKE_ASYNC(src, .proc/magnetic_process) + + + +/obj/machinery/magnetic_module/process() + if(machine_stat & NOPOWER) + on = FALSE + + // Sanity checks: + if(electricity_level <= 0) + electricity_level = 1 + if(magnetic_field <= 0) + magnetic_field = 1 + + + // Limitations: + if(abs(center_x) > max_dist) + center_x = max_dist + if(abs(center_y) > max_dist) + center_y = max_dist + if(magnetic_field > 4) + magnetic_field = 4 + if(electricity_level > 12) + electricity_level = 12 + + // Update power usage: + if(on) + use_power = ACTIVE_POWER_USE + active_power_usage = electricity_level*15 + else + use_power = NO_POWER_USE + + update_icon() + + +/obj/machinery/magnetic_module/proc/magnetic_process() // proc that actually does the magneting + if(magneting) + return + while(on) + + magneting = TRUE + center = locate(x+center_x, y+center_y, z) + if(center) + for(var/obj/M in orange(magnetic_field, center)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + step_towards(M, center) + + for(var/mob/living/silicon/S in orange(magnetic_field, center)) + if(isAI(S)) + continue + step_towards(S, center) + + use_power(electricity_level * 5) + sleep(13 - electricity_level) + + magneting = FALSE + + + + +/obj/machinery/magnetic_controller + name = "magnetic control console" + icon = 'icons/obj/airlock_machines.dmi' // uses an airlock machine icon, THINK GREEN HELP THE ENVIRONMENT - RECYCLING! + icon_state = "airlock_control_standby" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 45 + var/frequency = FREQ_MAGNETS + var/code = 0 + var/list/magnets = list() + var/title = "Magnetic Control Console" + var/autolink = 0 // if set to 1, can't probe for other magnets! + + var/pathpos = 1 // position in the path + var/path = "w;e;e;w;s;n;n;s" // text path of the magnet + var/speed = 1 // lowest = 1, highest = 10 + var/list/rpath = list() // real path of the magnet, used in iterator + + var/moving = 0 // 1 if scheduled to loop + var/looping = 0 // 1 if looping + + var/datum/radio_frequency/radio_connection + + +/obj/machinery/magnetic_controller/Initialize() + . = ..() + if(autolink) + for(var/obj/machinery/magnetic_module/M in GLOB.machines) + if(M.freq == frequency && M.code == code) + magnets.Add(M) + + if(path) // check for default path + filter_path() // renders rpath + radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS) + +/obj/machinery/magnetic_controller/Destroy() + SSradio.remove_object(src, frequency) + magnets = null + rpath = null + return ..() + +/obj/machinery/magnetic_controller/process() + if(magnets.len == 0 && autolink) + for(var/obj/machinery/magnetic_module/M in GLOB.machines) + if(M.freq == frequency && M.code == code) + magnets.Add(M) + +/obj/machinery/magnetic_controller/ui_interact(mob/user) + . = ..() + var/dat = "Magnetic Control Console

    " + if(!autolink) + dat += {" + Frequency: [frequency]
    + Code: [code]
    + Probe Generators
    + "} + + if(magnets.len >= 1) + + dat += "Magnets confirmed:
    " + var/i = 0 + for(var/obj/machinery/magnetic_module/M in magnets) + i++ + dat += "     < \[[i]\] ([M.on ? "On":"Off"]) | Electricity level: - [M.electricity_level] +; Magnetic field: - [M.magnetic_field] +
    " + + dat += "
    Speed: - [speed] +
    " + dat += "Path: {[path]}
    " + dat += "Moving: [moving ? "Enabled":"Disabled"]" + + + user << browse(dat, "window=magnet;size=400x500") + onclose(user, "magnet") + +/obj/machinery/magnetic_controller/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + + if(href_list["radio-op"]) + + // Prepare signal beforehand, because this is a radio operation + var/datum/signal/signal = new(list("code" = code)) + + // Apply any necessary commands + switch(href_list["radio-op"]) + if("togglepower") + signal.data["command"] = "toggle-power" + + if("minuselec") + signal.data["command"] = "sub-elec" + if("pluselec") + signal.data["command"] = "add-elec" + + if("minusmag") + signal.data["command"] = "sub-mag" + if("plusmag") + signal.data["command"] = "add-mag" + + + // Broadcast the signal + + radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) + + updateUsrDialog() + + if(href_list["operation"]) + switch(href_list["operation"]) + if("plusspeed") + speed ++ + if(speed > 10) + speed = 10 + if("minusspeed") + speed -- + if(speed <= 0) + speed = 1 + if("setpath") + var/newpath = stripped_input(usr, "Please define a new path!", "New Path", path, MAX_MESSAGE_LEN) + if(newpath && newpath != "") + moving = 0 // stop moving + path = newpath + pathpos = 1 // reset position + filter_path() // renders rpath + + if("togglemoving") + moving = !moving + if(moving) + INVOKE_ASYNC(src, .proc/MagnetMove) + + + updateUsrDialog() + +/obj/machinery/magnetic_controller/proc/MagnetMove() + if(looping) + return + + while(moving && rpath.len >= 1) + + if(machine_stat & (BROKEN|NOPOWER)) + break + + looping = 1 + + // Prepare the radio signal + var/datum/signal/signal = new(list("code" = code)) + + if(pathpos > rpath.len) // if the position is greater than the length, we just loop through the list! + pathpos = 1 + + var/nextmove = uppertext(rpath[pathpos]) // makes it un-case-sensitive + + if(!(nextmove in list("N","S","E","W","C","R"))) + // N, S, E, W are directional + // C is center + // R is random (in magnetic field's bounds) + qdel(signal) + break // break the loop if the character located is invalid + + signal.data["command"] = nextmove + + + pathpos++ // increase iterator + + // Broadcast the signal + INVOKE_ASYNC(radio_connection, /datum/radio_frequency.proc/post_signal, src, signal, RADIO_MAGNETS) + + if(speed == 10) + sleep(1) + else + sleep(12-speed) + + looping = 0 + + +/obj/machinery/magnetic_controller/proc/filter_path() + // Generates the rpath variable using the path string, think of this as "string2list" + // Doesn't use params2list() because of the akward way it stacks entities + rpath = list() // clear rpath + var/maximum_characters = 50 + var/lentext = length(path) + var/nextchar = "" + var/charcount = 0 + + for(var/i = 1, i <= lentext, i += length(nextchar)) // iterates through all characters in path + nextchar = path[i] // find next character + + if(nextchar in list(";", "&", "*", " ")) // if char is a separator, ignore + continue + + rpath += nextchar // else, add to list + // there doesn't HAVE to be separators but it makes paths syntatically visible + charcount++ + if(charcount >= maximum_characters) + break diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm index 5d300f992dc..06e081698c1 100644 --- a/code/game/machinery/mass_driver.dm +++ b/code/game/machinery/mass_driver.dm @@ -1,42 +1,42 @@ -/obj/machinery/mass_driver - name = "mass driver" - desc = "The finest in spring-loaded piston toy technology, now on a space station near you." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mass_driver" - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 50 - var/power = 1 - var/code = 1 - var/id = 1 - var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess. - -/obj/machinery/mass_driver/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -/obj/machinery/mass_driver/proc/drive(amount) - if(machine_stat & (BROKEN|NOPOWER)) - return - use_power(500) - var/O_limit - var/atom/target = get_edge_target_turf(src, dir) - for(var/atom/movable/O in loc) - if(!O.anchored || ismecha(O)) //Mechs need their launch platforms. - if(ismob(O) && !isliving(O)) - continue - O_limit++ - if(O_limit >= 20) - audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.") - break - use_power(500) - O.throw_at(target, drive_range * power, power) - flick("mass_driver1", src) - - -/obj/machinery/mass_driver/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(machine_stat & (BROKEN|NOPOWER)) - return - drive() +/obj/machinery/mass_driver + name = "mass driver" + desc = "The finest in spring-loaded piston toy technology, now on a space station near you." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mass_driver" + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 50 + var/power = 1 + var/code = 1 + var/id = 1 + var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess. + +/obj/machinery/mass_driver/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +/obj/machinery/mass_driver/proc/drive(amount) + if(machine_stat & (BROKEN|NOPOWER)) + return + use_power(500) + var/O_limit + var/atom/target = get_edge_target_turf(src, dir) + for(var/atom/movable/O in loc) + if(!O.anchored || ismecha(O)) //Mechs need their launch platforms. + if(ismob(O) && !isliving(O)) + continue + O_limit++ + if(O_limit >= 20) + audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.") + break + use_power(500) + O.throw_at(target, drive_range * power, power) + flick("mass_driver1", src) + + +/obj/machinery/mass_driver/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(machine_stat & (BROKEN|NOPOWER)) + return + drive() diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index 64ea63081cf..a6ac127b337 100644 --- a/code/game/machinery/navbeacon.dm +++ b/code/game/machinery/navbeacon.dm @@ -1,226 +1,226 @@ -// Navigation beacon for AI robots -// No longer exists on the radio controller, it is managed by a global list. - -/obj/machinery/navbeacon - - icon = 'icons/obj/objects.dmi' - icon_state = "navbeacon0-f" - name = "navigation beacon" - desc = "A radio beacon used for bot navigation and crew wayfinding." - layer = LOW_OBJ_LAYER - max_integrity = 500 - armor = list("melee" = 70, "bullet" = 70, "laser" = 70, "energy" = 70, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - - var/open = FALSE // true if cover is open - var/locked = TRUE // true if controls are locked - var/freq = FREQ_NAV_BEACON - var/location = "" // location response text - var/list/codes // assoc. list of transponder codes - var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" - var/wayfinding = FALSE - - req_one_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) - -/obj/machinery/navbeacon/Initialize() - . = ..() - - if(wayfinding) - if(!location) - var/obj/machinery/door/airlock/A = locate(/obj/machinery/door/airlock) in loc - if(A) - location = A.name - else - location = get_area(src) - codes_txt += "wayfinding=[location]" - - set_codes() - - glob_lists_register(init=TRUE) - - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) - -/obj/machinery/navbeacon/Destroy() - glob_lists_deregister() - return ..() - -/obj/machinery/navbeacon/onTransitZ(old_z, new_z) - if (GLOB.navbeacons["[old_z]"]) - GLOB.navbeacons["[old_z]"] -= src - if (GLOB.navbeacons["[new_z]"]) - GLOB.navbeacons["[new_z]"] += src - ..() - -// set the transponder codes assoc list from codes_txt -/obj/machinery/navbeacon/proc/set_codes() - if(!codes_txt) - return - - codes = new() - - var/list/entries = splittext(codes_txt, ";") // entries are separated by semicolons - - for(var/e in entries) - var/index = findtext(e, "=") // format is "key=value" - if(index) - var/key = copytext(e, 1, index) - var/val = copytext(e, index + length(e[index])) - codes[key] = val - else - codes[e] = "1" - -/obj/machinery/navbeacon/proc/glob_lists_deregister() - if (GLOB.navbeacons["[z]"]) - GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. - GLOB.deliverybeacons -= src - GLOB.deliverybeacontags -= location - GLOB.wayfindingbeacons -= src - -/obj/machinery/navbeacon/proc/glob_lists_register(var/init=FALSE) - if(!init) - glob_lists_deregister() - if(codes["patrol"]) - if(!GLOB.navbeacons["[z]"]) - GLOB.navbeacons["[z]"] = list() - GLOB.navbeacons["[z]"] += src //Register with the patrol list! - if(codes["delivery"]) - GLOB.deliverybeacons += src - GLOB.deliverybeacontags += location - if(codes["wayfinding"]) - GLOB.wayfindingbeacons += src - -// update the icon_state -/obj/machinery/navbeacon/update_icon_state() - icon_state = "navbeacon[open]" - -/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params) - var/turf/T = loc - if(T.intact) - return // prevent intraction when T-scanner revealed - - if(I.tool_behaviour == TOOL_SCREWDRIVER) - open = !open - - user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") - - update_icon() - - else if (istype(I, /obj/item/card/id)||istype(I, /obj/item/pda)) - if(open) - if (src.allowed(user)) - src.locked = !src.locked - to_chat(user, "Controls are now [src.locked ? "locked" : "unlocked"].") - else - to_chat(user, "Access denied.") - updateDialog() - else - to_chat(user, "You must open the cover first!") - else - return ..() - -/obj/machinery/navbeacon/attack_ai(mob/user) - interact(user, 1) - -/obj/machinery/navbeacon/attack_paw() - return - -/obj/machinery/navbeacon/ui_interact(mob/user) - . = ..() - var/ai = isAI(user) - var/turf/T = loc - if(T.intact) - return // prevent intraction when T-scanner revealed - - if(!open && !ai) // can't alter controls if not open, unless you're an AI - to_chat(user, "The beacon's control cover is closed!") - return - - - var/t - - if(locked && !ai) - t = {"Navigation Beacon

    -(swipe card to unlock controls)
    -Location: [location ? location : "(none)"]
    -Transponder Codes:
      "} - - for(var/key in codes) - t += "
    • [key] ... [codes[key]]" - t+= "
        " - - else - - t = {"Navigation Beacon

        -(swipe card to lock controls)
        - -
        -Location: [location ? location : "None"]
        -Transponder Codes:
          "} - - for(var/key in codes) - t += "
        • [key] ... [codes[key]]" - t += " Edit" - t += " Delete
          " - t += " Add New
          " - t+= "
            " - - var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) - popup.set_content(t) - popup.open() - return - -/obj/machinery/navbeacon/Topic(href, href_list) - if(..()) - return - if(open && !locked) - usr.set_machine(src) - - if(href_list["locedit"]) - var/newloc = stripped_input(usr, "Enter New Location", "Navigation Beacon", location, MAX_MESSAGE_LEN) - if(newloc) - location = newloc - glob_lists_register() - updateDialog() - - else if(href_list["edit"]) - var/codekey = href_list["code"] - - var/newkey = stripped_input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) - if(!newkey) - return - - var/codeval = codes[codekey] - var/newval = stripped_input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) - if(!newval) - newval = codekey - return - - codes.Remove(codekey) - codes[newkey] = newval - glob_lists_register() - - updateDialog() - - else if(href_list["delete"]) - var/codekey = href_list["code"] - codes.Remove(codekey) - glob_lists_register() - updateDialog() - - else if(href_list["add"]) - - var/newkey = stripped_input(usr, "Enter New Transponder Code Key", "Navigation Beacon") - if(!newkey) - return - - var/newval = stripped_input(usr, "Enter New Transponder Code Value", "Navigation Beacon") - if(!newval) - newval = "1" - return - - if(!codes) - codes = new() - - codes[newkey] = newval - glob_lists_register() - - updateDialog() +// Navigation beacon for AI robots +// No longer exists on the radio controller, it is managed by a global list. + +/obj/machinery/navbeacon + + icon = 'icons/obj/objects.dmi' + icon_state = "navbeacon0-f" + name = "navigation beacon" + desc = "A radio beacon used for bot navigation and crew wayfinding." + layer = LOW_OBJ_LAYER + max_integrity = 500 + armor = list("melee" = 70, "bullet" = 70, "laser" = 70, "energy" = 70, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + + var/open = FALSE // true if cover is open + var/locked = TRUE // true if controls are locked + var/freq = FREQ_NAV_BEACON + var/location = "" // location response text + var/list/codes // assoc. list of transponder codes + var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" + var/wayfinding = FALSE + + req_one_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) + +/obj/machinery/navbeacon/Initialize() + . = ..() + + if(wayfinding) + if(!location) + var/obj/machinery/door/airlock/A = locate(/obj/machinery/door/airlock) in loc + if(A) + location = A.name + else + location = get_area(src) + codes_txt += "wayfinding=[location]" + + set_codes() + + glob_lists_register(init=TRUE) + + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) + +/obj/machinery/navbeacon/Destroy() + glob_lists_deregister() + return ..() + +/obj/machinery/navbeacon/onTransitZ(old_z, new_z) + if (GLOB.navbeacons["[old_z]"]) + GLOB.navbeacons["[old_z]"] -= src + if (GLOB.navbeacons["[new_z]"]) + GLOB.navbeacons["[new_z]"] += src + ..() + +// set the transponder codes assoc list from codes_txt +/obj/machinery/navbeacon/proc/set_codes() + if(!codes_txt) + return + + codes = new() + + var/list/entries = splittext(codes_txt, ";") // entries are separated by semicolons + + for(var/e in entries) + var/index = findtext(e, "=") // format is "key=value" + if(index) + var/key = copytext(e, 1, index) + var/val = copytext(e, index + length(e[index])) + codes[key] = val + else + codes[e] = "1" + +/obj/machinery/navbeacon/proc/glob_lists_deregister() + if (GLOB.navbeacons["[z]"]) + GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. + GLOB.deliverybeacons -= src + GLOB.deliverybeacontags -= location + GLOB.wayfindingbeacons -= src + +/obj/machinery/navbeacon/proc/glob_lists_register(var/init=FALSE) + if(!init) + glob_lists_deregister() + if(codes["patrol"]) + if(!GLOB.navbeacons["[z]"]) + GLOB.navbeacons["[z]"] = list() + GLOB.navbeacons["[z]"] += src //Register with the patrol list! + if(codes["delivery"]) + GLOB.deliverybeacons += src + GLOB.deliverybeacontags += location + if(codes["wayfinding"]) + GLOB.wayfindingbeacons += src + +// update the icon_state +/obj/machinery/navbeacon/update_icon_state() + icon_state = "navbeacon[open]" + +/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params) + var/turf/T = loc + if(T.intact) + return // prevent intraction when T-scanner revealed + + if(I.tool_behaviour == TOOL_SCREWDRIVER) + open = !open + + user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") + + update_icon() + + else if (istype(I, /obj/item/card/id)||istype(I, /obj/item/pda)) + if(open) + if (src.allowed(user)) + src.locked = !src.locked + to_chat(user, "Controls are now [src.locked ? "locked" : "unlocked"].") + else + to_chat(user, "Access denied.") + updateDialog() + else + to_chat(user, "You must open the cover first!") + else + return ..() + +/obj/machinery/navbeacon/attack_ai(mob/user) + interact(user, 1) + +/obj/machinery/navbeacon/attack_paw() + return + +/obj/machinery/navbeacon/ui_interact(mob/user) + . = ..() + var/ai = isAI(user) + var/turf/T = loc + if(T.intact) + return // prevent intraction when T-scanner revealed + + if(!open && !ai) // can't alter controls if not open, unless you're an AI + to_chat(user, "The beacon's control cover is closed!") + return + + + var/t + + if(locked && !ai) + t = {"Navigation Beacon

            +(swipe card to unlock controls)
            +Location: [location ? location : "(none)"]
            +Transponder Codes:
              "} + + for(var/key in codes) + t += "
            • [key] ... [codes[key]]" + t+= "
                " + + else + + t = {"Navigation Beacon

                +(swipe card to lock controls)
                + +
                +Location: [location ? location : "None"]
                +Transponder Codes:
                  "} + + for(var/key in codes) + t += "
                • [key] ... [codes[key]]" + t += " Edit" + t += " Delete
                  " + t += " Add New
                  " + t+= "
                    " + + var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) + popup.set_content(t) + popup.open() + return + +/obj/machinery/navbeacon/Topic(href, href_list) + if(..()) + return + if(open && !locked) + usr.set_machine(src) + + if(href_list["locedit"]) + var/newloc = stripped_input(usr, "Enter New Location", "Navigation Beacon", location, MAX_MESSAGE_LEN) + if(newloc) + location = newloc + glob_lists_register() + updateDialog() + + else if(href_list["edit"]) + var/codekey = href_list["code"] + + var/newkey = stripped_input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) + if(!newkey) + return + + var/codeval = codes[codekey] + var/newval = stripped_input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) + if(!newval) + newval = codekey + return + + codes.Remove(codekey) + codes[newkey] = newval + glob_lists_register() + + updateDialog() + + else if(href_list["delete"]) + var/codekey = href_list["code"] + codes.Remove(codekey) + glob_lists_register() + updateDialog() + + else if(href_list["add"]) + + var/newkey = stripped_input(usr, "Enter New Transponder Code Key", "Navigation Beacon") + if(!newkey) + return + + var/newval = stripped_input(usr, "Enter New Transponder Code Value", "Navigation Beacon") + if(!newval) + newval = "1" + return + + if(!codes) + codes = new() + + codes[newkey] = newval + glob_lists_register() + + updateDialog() diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm index 449810f4db7..66b196c192f 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -1,234 +1,234 @@ -/*CONTENTS -Buildable pipes -Buildable meters -*/ - -//construction defines are in __defines/pipe_construction.dm -//update those defines ANY TIME an atmos path is changed... -//...otherwise construction will stop working - -/obj/item/pipe - name = "pipe" - desc = "A pipe." - var/pipe_type - var/pipename - force = 7 - throwforce = 7 - icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' - icon_state = "simple" - inhand_icon_state = "buildpipe" - w_class = WEIGHT_CLASS_NORMAL - var/piping_layer = PIPING_LAYER_DEFAULT - var/RPD_type - -/obj/item/pipe/directional - RPD_type = PIPE_UNARY -/obj/item/pipe/binary - RPD_type = PIPE_STRAIGHT -/obj/item/pipe/binary/bendable - RPD_type = PIPE_BENDABLE -/obj/item/pipe/trinary - RPD_type = PIPE_TRINARY -/obj/item/pipe/trinary/flippable - RPD_type = PIPE_TRIN_M - var/flipped = FALSE -/obj/item/pipe/quaternary - RPD_type = PIPE_ONEDIR - -/obj/item/pipe/ComponentInitialize() - //Flipping handled manually due to custom handling for trinary pipes - AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) - -/obj/item/pipe/Initialize(mapload, _pipe_type, _dir, obj/machinery/atmospherics/make_from) - if(make_from) - make_from_existing(make_from) - else - pipe_type = _pipe_type - setDir(_dir) - - update() - pixel_x += rand(-5, 5) - pixel_y += rand(-5, 5) - return ..() - -/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from) - setDir(make_from.dir) - pipename = make_from.name - add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY) - pipe_type = make_from.type - -/obj/item/pipe/trinary/flippable/make_from_existing(obj/machinery/atmospherics/components/trinary/make_from) - ..() - if(make_from.flipped) - do_a_flip() - -/obj/item/pipe/dropped() - if(loc) - setPipingLayer(piping_layer) - return ..() - -/obj/item/pipe/proc/setPipingLayer(new_layer = PIPING_LAYER_DEFAULT) - var/obj/machinery/atmospherics/fakeA = pipe_type - - if(initial(fakeA.pipe_flags) & PIPING_ALL_LAYER) - new_layer = PIPING_LAYER_DEFAULT - piping_layer = new_layer - - PIPING_LAYER_SHIFT(src, piping_layer) - layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) - -/obj/item/pipe/proc/update() - var/obj/machinery/atmospherics/fakeA = pipe_type - name = "[initial(fakeA.name)] fitting" - icon_state = initial(fakeA.pipe_state) - if(ispath(pipe_type,/obj/machinery/atmospherics/pipe/heat_exchanging)) - resistance_flags |= FIRE_PROOF | LAVA_PROOF - -/obj/item/pipe/verb/flip() - set category = "Object" - set name = "Flip Pipe" - set src in view(1) - - if ( usr.incapacitated() ) - return - - do_a_flip() - -/obj/item/pipe/proc/do_a_flip() - setDir(turn(dir, -180)) - -/obj/item/pipe/trinary/flippable/do_a_flip() - setDir(turn(dir, flipped ? 45 : -45)) - flipped = !flipped - -/obj/item/pipe/Move() - var/old_dir = dir - ..() - setDir(old_dir) //pipes changing direction when moved is just annoying and buggy - -// Convert dir of fitting into dir of built component -/obj/item/pipe/proc/fixed_dir() - return dir - -/obj/item/pipe/binary/fixed_dir() - . = dir - if(dir == SOUTH) - . = NORTH - else if(dir == WEST) - . = EAST - -/obj/item/pipe/trinary/flippable/fixed_dir() - . = dir - if(dir in GLOB.diagonals) - . = turn(dir, 45) - -/obj/item/pipe/attack_self(mob/user) - setDir(turn(dir,-90)) - -/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/W) - . = ..() - if(!isturf(loc)) - return TRUE - - add_fingerprint(user) - - var/obj/machinery/atmospherics/fakeA = pipe_type - var/flags = initial(fakeA.pipe_flags) - for(var/obj/machinery/atmospherics/M in loc) - if((M.pipe_flags & flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers. - to_chat(user, "Something is hogging the tile!") - return TRUE - if((M.piping_layer != piping_layer) && !((M.pipe_flags | flags) & PIPING_ALL_LAYER)) //don't continue if either pipe goes across all layers - continue - if(M.GetInitDirections() & SSair.get_init_dirs(pipe_type, fixed_dir())) // matches at least one direction on either type of pipe - to_chat(user, "There is already a pipe at that location!") - return TRUE - // no conflicts found - - var/obj/machinery/atmospherics/A = new pipe_type(loc) - build_pipe(A) - A.on_construction(color, piping_layer) - transfer_fingerprints_to(A) - - W.play_tool_sound(src) - user.visible_message( \ - "[user] fastens \the [src].", \ - "You fasten \the [src].", \ - "You hear ratcheting.") - - qdel(src) - -/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A) - A.setDir(fixed_dir()) - A.SetInitDirections() - - if(pipename) - A.name = pipename - if(A.on) - // Certain pre-mapped subtypes are on by default, we want to preserve - // every other aspect of these subtypes (name, pre-set filters, etc.) - // but they shouldn't turn on automatically when wrenched. - A.on = FALSE - -/obj/item/pipe/trinary/flippable/build_pipe(obj/machinery/atmospherics/components/trinary/T) - ..() - T.flipped = flipped - -/obj/item/pipe/directional/suicide_act(mob/user) - user.visible_message("[user] shoves [src] in [user.p_their()] mouth and turns it on! It looks like [user.p_theyre()] trying to commit suicide!") - if(iscarbon(user)) - var/mob/living/carbon/C = user - for(var/i=1 to 20) - C.vomit(0, TRUE, FALSE, 4, FALSE) - if(prob(20)) - C.spew_organ() - sleep(5) - C.blood_volume = 0 - return(OXYLOSS|BRUTELOSS) - -/obj/item/pipe_meter - name = "meter" - desc = "A meter that can be laid on pipes." - icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' - icon_state = "meter" - inhand_icon_state = "buildpipe" - w_class = WEIGHT_CLASS_BULKY - var/piping_layer = PIPING_LAYER_DEFAULT - -/obj/item/pipe_meter/wrench_act(mob/living/user, obj/item/wrench/W) - . = ..() - var/obj/machinery/atmospherics/pipe/pipe - for(var/obj/machinery/atmospherics/pipe/P in loc) - if(P.piping_layer == piping_layer) - pipe = P - break - if(!pipe) - to_chat(user, "You need to fasten it to a pipe!") - return TRUE - new /obj/machinery/meter(loc, piping_layer) - W.play_tool_sound(src) - to_chat(user, "You fasten the meter to the pipe.") - qdel(src) - -/obj/item/pipe_meter/screwdriver_act(mob/living/user, obj/item/S) - . = ..() - if(.) - return TRUE - - if(!isturf(loc)) - to_chat(user, "You need to fasten it to the floor!") - return TRUE - - new /obj/machinery/meter/turf(loc, piping_layer) - S.play_tool_sound(src) - to_chat(user, "You fasten the meter to the [loc.name].") - qdel(src) - -/obj/item/pipe_meter/dropped() - . = ..() - if(loc) - setAttachLayer(piping_layer) - -/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT) - piping_layer = new_layer - PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer) +/*CONTENTS +Buildable pipes +Buildable meters +*/ + +//construction defines are in __defines/pipe_construction.dm +//update those defines ANY TIME an atmos path is changed... +//...otherwise construction will stop working + +/obj/item/pipe + name = "pipe" + desc = "A pipe." + var/pipe_type + var/pipename + force = 7 + throwforce = 7 + icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' + icon_state = "simple" + inhand_icon_state = "buildpipe" + w_class = WEIGHT_CLASS_NORMAL + var/piping_layer = PIPING_LAYER_DEFAULT + var/RPD_type + +/obj/item/pipe/directional + RPD_type = PIPE_UNARY +/obj/item/pipe/binary + RPD_type = PIPE_STRAIGHT +/obj/item/pipe/binary/bendable + RPD_type = PIPE_BENDABLE +/obj/item/pipe/trinary + RPD_type = PIPE_TRINARY +/obj/item/pipe/trinary/flippable + RPD_type = PIPE_TRIN_M + var/flipped = FALSE +/obj/item/pipe/quaternary + RPD_type = PIPE_ONEDIR + +/obj/item/pipe/ComponentInitialize() + //Flipping handled manually due to custom handling for trinary pipes + AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) + +/obj/item/pipe/Initialize(mapload, _pipe_type, _dir, obj/machinery/atmospherics/make_from) + if(make_from) + make_from_existing(make_from) + else + pipe_type = _pipe_type + setDir(_dir) + + update() + pixel_x += rand(-5, 5) + pixel_y += rand(-5, 5) + return ..() + +/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from) + setDir(make_from.dir) + pipename = make_from.name + add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY) + pipe_type = make_from.type + +/obj/item/pipe/trinary/flippable/make_from_existing(obj/machinery/atmospherics/components/trinary/make_from) + ..() + if(make_from.flipped) + do_a_flip() + +/obj/item/pipe/dropped() + if(loc) + setPipingLayer(piping_layer) + return ..() + +/obj/item/pipe/proc/setPipingLayer(new_layer = PIPING_LAYER_DEFAULT) + var/obj/machinery/atmospherics/fakeA = pipe_type + + if(initial(fakeA.pipe_flags) & PIPING_ALL_LAYER) + new_layer = PIPING_LAYER_DEFAULT + piping_layer = new_layer + + PIPING_LAYER_SHIFT(src, piping_layer) + layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) + +/obj/item/pipe/proc/update() + var/obj/machinery/atmospherics/fakeA = pipe_type + name = "[initial(fakeA.name)] fitting" + icon_state = initial(fakeA.pipe_state) + if(ispath(pipe_type,/obj/machinery/atmospherics/pipe/heat_exchanging)) + resistance_flags |= FIRE_PROOF | LAVA_PROOF + +/obj/item/pipe/verb/flip() + set category = "Object" + set name = "Flip Pipe" + set src in view(1) + + if ( usr.incapacitated() ) + return + + do_a_flip() + +/obj/item/pipe/proc/do_a_flip() + setDir(turn(dir, -180)) + +/obj/item/pipe/trinary/flippable/do_a_flip() + setDir(turn(dir, flipped ? 45 : -45)) + flipped = !flipped + +/obj/item/pipe/Move() + var/old_dir = dir + ..() + setDir(old_dir) //pipes changing direction when moved is just annoying and buggy + +// Convert dir of fitting into dir of built component +/obj/item/pipe/proc/fixed_dir() + return dir + +/obj/item/pipe/binary/fixed_dir() + . = dir + if(dir == SOUTH) + . = NORTH + else if(dir == WEST) + . = EAST + +/obj/item/pipe/trinary/flippable/fixed_dir() + . = dir + if(dir in GLOB.diagonals) + . = turn(dir, 45) + +/obj/item/pipe/attack_self(mob/user) + setDir(turn(dir,-90)) + +/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/W) + . = ..() + if(!isturf(loc)) + return TRUE + + add_fingerprint(user) + + var/obj/machinery/atmospherics/fakeA = pipe_type + var/flags = initial(fakeA.pipe_flags) + for(var/obj/machinery/atmospherics/M in loc) + if((M.pipe_flags & flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers. + to_chat(user, "Something is hogging the tile!") + return TRUE + if((M.piping_layer != piping_layer) && !((M.pipe_flags | flags) & PIPING_ALL_LAYER)) //don't continue if either pipe goes across all layers + continue + if(M.GetInitDirections() & SSair.get_init_dirs(pipe_type, fixed_dir())) // matches at least one direction on either type of pipe + to_chat(user, "There is already a pipe at that location!") + return TRUE + // no conflicts found + + var/obj/machinery/atmospherics/A = new pipe_type(loc) + build_pipe(A) + A.on_construction(color, piping_layer) + transfer_fingerprints_to(A) + + W.play_tool_sound(src) + user.visible_message( \ + "[user] fastens \the [src].", \ + "You fasten \the [src].", \ + "You hear ratcheting.") + + qdel(src) + +/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A) + A.setDir(fixed_dir()) + A.SetInitDirections() + + if(pipename) + A.name = pipename + if(A.on) + // Certain pre-mapped subtypes are on by default, we want to preserve + // every other aspect of these subtypes (name, pre-set filters, etc.) + // but they shouldn't turn on automatically when wrenched. + A.on = FALSE + +/obj/item/pipe/trinary/flippable/build_pipe(obj/machinery/atmospherics/components/trinary/T) + ..() + T.flipped = flipped + +/obj/item/pipe/directional/suicide_act(mob/user) + user.visible_message("[user] shoves [src] in [user.p_their()] mouth and turns it on! It looks like [user.p_theyre()] trying to commit suicide!") + if(iscarbon(user)) + var/mob/living/carbon/C = user + for(var/i=1 to 20) + C.vomit(0, TRUE, FALSE, 4, FALSE) + if(prob(20)) + C.spew_organ() + sleep(5) + C.blood_volume = 0 + return(OXYLOSS|BRUTELOSS) + +/obj/item/pipe_meter + name = "meter" + desc = "A meter that can be laid on pipes." + icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' + icon_state = "meter" + inhand_icon_state = "buildpipe" + w_class = WEIGHT_CLASS_BULKY + var/piping_layer = PIPING_LAYER_DEFAULT + +/obj/item/pipe_meter/wrench_act(mob/living/user, obj/item/wrench/W) + . = ..() + var/obj/machinery/atmospherics/pipe/pipe + for(var/obj/machinery/atmospherics/pipe/P in loc) + if(P.piping_layer == piping_layer) + pipe = P + break + if(!pipe) + to_chat(user, "You need to fasten it to a pipe!") + return TRUE + new /obj/machinery/meter(loc, piping_layer) + W.play_tool_sound(src) + to_chat(user, "You fasten the meter to the pipe.") + qdel(src) + +/obj/item/pipe_meter/screwdriver_act(mob/living/user, obj/item/S) + . = ..() + if(.) + return TRUE + + if(!isturf(loc)) + to_chat(user, "You need to fasten it to the floor!") + return TRUE + + new /obj/machinery/meter/turf(loc, piping_layer) + S.play_tool_sound(src) + to_chat(user, "You fasten the meter to the [loc.name].") + qdel(src) + +/obj/item/pipe_meter/dropped() + . = ..() + if(loc) + setAttachLayer(piping_layer) + +/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT) + piping_layer = new_layer + PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer) diff --git a/code/game/machinery/pipe/pipe_dispenser.dm b/code/game/machinery/pipe/pipe_dispenser.dm index 5b72ef26844..93a0f7869b6 100644 --- a/code/game/machinery/pipe/pipe_dispenser.dm +++ b/code/game/machinery/pipe/pipe_dispenser.dm @@ -1,214 +1,214 @@ -/obj/machinery/pipedispenser - name = "pipe dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - desc = "Dispenses countless types of pipes. Very useful if you need pipes." - density = TRUE - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OFFLINE - var/wait = 0 - var/piping_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/pipedispenser/attack_paw(mob/user) - return attack_hand(user) - -/obj/machinery/pipedispenser/ui_interact(mob/user) - . = ..() - var/dat = "PIPING LAYER: --[piping_layer]++
                    " - - var/recipes = GLOB.atmos_pipe_recipes - - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - dat += "[category]:
                      " - - for(var/i in cat_recipes) - var/datum/pipe_info/I = i - dat += I.Render(src) - - dat += "
                    " - - user << browse("[src][dat]", "window=pipedispenser") - onclose(user, "pipedispenser") - return - -/obj/machinery/pipedispenser/Topic(href, href_list) - if(..()) - return 1 - var/mob/living/L = usr - if(!anchored || (istype(L) && !(L.mobility_flags & MOBILITY_UI)) || usr.stat || usr.restrained() || !in_range(loc, usr)) - usr << browse(null, "window=pipedispenser") - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(href_list["makepipe"]) - if(wait < world.time) - var/p_type = text2path(href_list["makepipe"]) - if (!verify_recipe(GLOB.atmos_pipe_recipes, p_type)) - return - var/p_dir = text2num(href_list["dir"]) - var/obj/item/pipe/P = new (loc, p_type, p_dir) - P.setPipingLayer(piping_layer) - P.add_fingerprint(usr) - wait = world.time + 10 - if(href_list["makemeter"]) - if(wait < world.time ) - new /obj/item/pipe_meter(loc) - wait = world.time + 15 - if(href_list["layer_up"]) - piping_layer = clamp(++piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - if(href_list["layer_down"]) - piping_layer = clamp(--piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - return - -/obj/machinery/pipedispenser/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if (istype(W, /obj/item/pipe) || istype(W, /obj/item/pipe_meter)) - to_chat(usr, "You put [W] back into [src].") - qdel(W) - return - else - return ..() - -/obj/machinery/pipedispenser/proc/verify_recipe(recipes, path) - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - for(var/i in cat_recipes) - var/datum/pipe_info/info = i - if (path == info.id) - return TRUE - return FALSE - -/obj/machinery/pipedispenser/wrench_act(mob/living/user, obj/item/I) - ..() - if(default_unfasten_wrench(user, I, 40)) - user << browse(null, "window=pipedispenser") - - return TRUE - - -/obj/machinery/pipedispenser/disposal - name = "disposal pipe dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - desc = "Dispenses pipes that will ultimately be used to move trash around." - density = TRUE - - -//Allow you to drag-drop disposal pipes and transit tubes into it -/obj/machinery/pipedispenser/disposal/MouseDrop_T(obj/structure/pipe, mob/usr) - if(!usr.incapacitated()) - return - - if (!istype(pipe, /obj/structure/disposalconstruct) && !istype(pipe, /obj/structure/c_transit_tube) && !istype(pipe, /obj/structure/c_transit_tube_pod)) - return - - if (get_dist(usr, src) > 1 || get_dist(src,pipe) > 1 ) - return - - if (pipe.anchored) - return - - qdel(pipe) - -/obj/machinery/pipedispenser/disposal/interact(mob/user) - - var/dat = "" - var/recipes = GLOB.disposal_pipe_recipes - - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - dat += "[category]:
                      " - - for(var/i in cat_recipes) - var/datum/pipe_info/I = i - dat += I.Render(src) - - dat += "
                    " - - user << browse("[src][dat]", "window=pipedispenser") - return - - -/obj/machinery/pipedispenser/disposal/Topic(href, href_list) - if(..()) - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(href_list["dmake"]) - if(wait < world.time) - var/p_type = text2path(href_list["dmake"]) - if (!verify_recipe(GLOB.disposal_pipe_recipes, p_type)) - return - var/obj/structure/disposalconstruct/C = new (loc, p_type) - - if(!C.can_place()) - to_chat(usr, "There's not enough room to build that here!") - qdel(C) - return - if(href_list["dir"]) - C.setDir(text2num(href_list["dir"])) - C.add_fingerprint(usr) - C.update_icon() - wait = world.time + 15 - return - -//transit tube dispenser -//inherit disposal for the dragging proc -/obj/machinery/pipedispenser/disposal/transit_tube - name = "transit tube dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - density = TRUE - desc = "Dispenses pipes that will move beings around." - -/obj/machinery/pipedispenser/disposal/transit_tube/interact(mob/user) - - var/dat = {"Transit Tubes:
                    -Straight Tube
                    -Straight Tube with Crossing
                    -Curved Tube
                    -Diagonal Tube
                    -Diagonal Tube with Crossing
                    -Junction
                    -Station Equipment:
                    -Through Tube Station
                    -Terminus Tube Station
                    -Transit Tube Pod
                    -"} - - user << browse("[src][dat]", "window=pipedispenser") - return - - -/obj/machinery/pipedispenser/disposal/transit_tube/Topic(href, href_list) - if(..()) - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(wait < world.time) - if(href_list["tube"]) - var/tube_type = text2num(href_list["tube"]) - var/obj/structure/C - switch(tube_type) - if(TRANSIT_TUBE_STRAIGHT) - C = new /obj/structure/c_transit_tube(loc) - if(TRANSIT_TUBE_STRAIGHT_CROSSING) - C = new /obj/structure/c_transit_tube/crossing(loc) - if(TRANSIT_TUBE_CURVED) - C = new /obj/structure/c_transit_tube/curved(loc) - if(TRANSIT_TUBE_DIAGONAL) - C = new /obj/structure/c_transit_tube/diagonal(loc) - if(TRANSIT_TUBE_DIAGONAL_CROSSING) - C = new /obj/structure/c_transit_tube/diagonal/crossing(loc) - if(TRANSIT_TUBE_JUNCTION) - C = new /obj/structure/c_transit_tube/junction(loc) - if(TRANSIT_TUBE_STATION) - C = new /obj/structure/c_transit_tube/station(loc) - if(TRANSIT_TUBE_TERMINUS) - C = new /obj/structure/c_transit_tube/station/reverse(loc) - if(TRANSIT_TUBE_POD) - C = new /obj/structure/c_transit_tube_pod(loc) - if(C) - C.add_fingerprint(usr) - wait = world.time + 15 - return +/obj/machinery/pipedispenser + name = "pipe dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + desc = "Dispenses countless types of pipes. Very useful if you need pipes." + density = TRUE + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OFFLINE + var/wait = 0 + var/piping_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/pipedispenser/attack_paw(mob/user) + return attack_hand(user) + +/obj/machinery/pipedispenser/ui_interact(mob/user) + . = ..() + var/dat = "PIPING LAYER: --[piping_layer]++
                    " + + var/recipes = GLOB.atmos_pipe_recipes + + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + dat += "[category]:
                      " + + for(var/i in cat_recipes) + var/datum/pipe_info/I = i + dat += I.Render(src) + + dat += "
                    " + + user << browse("[src][dat]", "window=pipedispenser") + onclose(user, "pipedispenser") + return + +/obj/machinery/pipedispenser/Topic(href, href_list) + if(..()) + return 1 + var/mob/living/L = usr + if(!anchored || (istype(L) && !(L.mobility_flags & MOBILITY_UI)) || usr.stat || usr.restrained() || !in_range(loc, usr)) + usr << browse(null, "window=pipedispenser") + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(href_list["makepipe"]) + if(wait < world.time) + var/p_type = text2path(href_list["makepipe"]) + if (!verify_recipe(GLOB.atmos_pipe_recipes, p_type)) + return + var/p_dir = text2num(href_list["dir"]) + var/obj/item/pipe/P = new (loc, p_type, p_dir) + P.setPipingLayer(piping_layer) + P.add_fingerprint(usr) + wait = world.time + 10 + if(href_list["makemeter"]) + if(wait < world.time ) + new /obj/item/pipe_meter(loc) + wait = world.time + 15 + if(href_list["layer_up"]) + piping_layer = clamp(++piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + if(href_list["layer_down"]) + piping_layer = clamp(--piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + return + +/obj/machinery/pipedispenser/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if (istype(W, /obj/item/pipe) || istype(W, /obj/item/pipe_meter)) + to_chat(usr, "You put [W] back into [src].") + qdel(W) + return + else + return ..() + +/obj/machinery/pipedispenser/proc/verify_recipe(recipes, path) + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + for(var/i in cat_recipes) + var/datum/pipe_info/info = i + if (path == info.id) + return TRUE + return FALSE + +/obj/machinery/pipedispenser/wrench_act(mob/living/user, obj/item/I) + ..() + if(default_unfasten_wrench(user, I, 40)) + user << browse(null, "window=pipedispenser") + + return TRUE + + +/obj/machinery/pipedispenser/disposal + name = "disposal pipe dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + desc = "Dispenses pipes that will ultimately be used to move trash around." + density = TRUE + + +//Allow you to drag-drop disposal pipes and transit tubes into it +/obj/machinery/pipedispenser/disposal/MouseDrop_T(obj/structure/pipe, mob/usr) + if(!usr.incapacitated()) + return + + if (!istype(pipe, /obj/structure/disposalconstruct) && !istype(pipe, /obj/structure/c_transit_tube) && !istype(pipe, /obj/structure/c_transit_tube_pod)) + return + + if (get_dist(usr, src) > 1 || get_dist(src,pipe) > 1 ) + return + + if (pipe.anchored) + return + + qdel(pipe) + +/obj/machinery/pipedispenser/disposal/interact(mob/user) + + var/dat = "" + var/recipes = GLOB.disposal_pipe_recipes + + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + dat += "[category]:
                      " + + for(var/i in cat_recipes) + var/datum/pipe_info/I = i + dat += I.Render(src) + + dat += "
                    " + + user << browse("[src][dat]", "window=pipedispenser") + return + + +/obj/machinery/pipedispenser/disposal/Topic(href, href_list) + if(..()) + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(href_list["dmake"]) + if(wait < world.time) + var/p_type = text2path(href_list["dmake"]) + if (!verify_recipe(GLOB.disposal_pipe_recipes, p_type)) + return + var/obj/structure/disposalconstruct/C = new (loc, p_type) + + if(!C.can_place()) + to_chat(usr, "There's not enough room to build that here!") + qdel(C) + return + if(href_list["dir"]) + C.setDir(text2num(href_list["dir"])) + C.add_fingerprint(usr) + C.update_icon() + wait = world.time + 15 + return + +//transit tube dispenser +//inherit disposal for the dragging proc +/obj/machinery/pipedispenser/disposal/transit_tube + name = "transit tube dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + density = TRUE + desc = "Dispenses pipes that will move beings around." + +/obj/machinery/pipedispenser/disposal/transit_tube/interact(mob/user) + + var/dat = {"Transit Tubes:
                    +Straight Tube
                    +Straight Tube with Crossing
                    +Curved Tube
                    +Diagonal Tube
                    +Diagonal Tube with Crossing
                    +Junction
                    +Station Equipment:
                    +Through Tube Station
                    +Terminus Tube Station
                    +Transit Tube Pod
                    +"} + + user << browse("[src][dat]", "window=pipedispenser") + return + + +/obj/machinery/pipedispenser/disposal/transit_tube/Topic(href, href_list) + if(..()) + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(wait < world.time) + if(href_list["tube"]) + var/tube_type = text2num(href_list["tube"]) + var/obj/structure/C + switch(tube_type) + if(TRANSIT_TUBE_STRAIGHT) + C = new /obj/structure/c_transit_tube(loc) + if(TRANSIT_TUBE_STRAIGHT_CROSSING) + C = new /obj/structure/c_transit_tube/crossing(loc) + if(TRANSIT_TUBE_CURVED) + C = new /obj/structure/c_transit_tube/curved(loc) + if(TRANSIT_TUBE_DIAGONAL) + C = new /obj/structure/c_transit_tube/diagonal(loc) + if(TRANSIT_TUBE_DIAGONAL_CROSSING) + C = new /obj/structure/c_transit_tube/diagonal/crossing(loc) + if(TRANSIT_TUBE_JUNCTION) + C = new /obj/structure/c_transit_tube/junction(loc) + if(TRANSIT_TUBE_STATION) + C = new /obj/structure/c_transit_tube/station(loc) + if(TRANSIT_TUBE_TERMINUS) + C = new /obj/structure/c_transit_tube/station/reverse(loc) + if(TRANSIT_TUBE_POD) + C = new /obj/structure/c_transit_tube_pod(loc) + if(C) + C.add_fingerprint(usr) + wait = world.time + 15 + return diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 2a8a0f92014..0cbde19a6df 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -1,1122 +1,1122 @@ -#define TURRET_STUN 0 -#define TURRET_LETHAL 1 - -#define POPUP_ANIM_TIME 5 -#define POPDOWN_ANIM_TIME 5 //Be sure to change the icon animation at the same time or it'll look bad - -#define TURRET_FLAG_SHOOT_ALL_REACT (1<<0) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) -#define TURRET_FLAG_AUTH_WEAPONS (1<<1) // Checks if it can shoot people that have a weapon they aren't authorized to have -#define TURRET_FLAG_SHOOT_CRIMINALS (1<<2) // Checks if it can shoot people that are wanted -#define TURRET_FLAG_SHOOT_ALL (1<<3) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) -#define TURRET_FLAG_SHOOT_ANOMALOUS (1<<4) // Checks if it can shoot at unidentified lifeforms (ie xenos) -#define TURRET_FLAG_SHOOT_UNSHIELDED (1<<5) // Checks if it can shoot people that aren't mindshielded and who arent heads -#define TURRET_FLAG_SHOOT_BORGS (1<<6) // checks if it can shoot cyborgs -#define TURRET_FLAG_SHOOT_HEADS (1<<7) // checks if it can shoot at heads of staff - -/obj/machinery/porta_turret - name = "turret" - icon = 'icons/obj/turrets.dmi' - icon_state = "turretCover" - layer = OBJ_LAYER - invisibility = INVISIBILITY_OBSERVER //the turret is invisible if it's inside its cover - density = TRUE - desc = "A covered turret that shoots at its enemies." - use_power = IDLE_POWER_USE //this turret uses and requires power - idle_power_usage = 50 //when inactive, this turret takes up constant 50 Equipment power - active_power_usage = 300 //when active, this turret takes up constant 300 Equipment power - req_access = list(ACCESS_SECURITY) /// Only people with Security access - power_channel = AREA_USAGE_EQUIP //drains power from the EQUIPMENT channel - max_integrity = 160 //the turret's health - integrity_failure = 0.5 - armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - ui_x = 305 - ui_y = 300 - /// Base turret icon state - var/base_icon_state = "standard" - /// Scan range of the turret for locating targets - var/scan_range = 7 - /// For turrets inside other objects - var/atom/base = null - /// If the turret cover is "open" and the turret is raised - var/raised = FALSE - /// If the turret is currently opening or closing its cover - var/raising = FALSE - /// If the turret's behaviour control access is locked - var/locked = TRUE - /// If the turret responds to control panels - var/controllock = FALSE - /// The type of weapon installed by default - var/installation = /obj/item/gun/energy/e_gun/turret - /// What stored gun is in the turret - var/obj/item/gun/stored_gun = null - /// The charge of the gun when retrieved from wreckage - var/gun_charge = 0 - /// In which mode is turret in, stun or lethal - var/mode = TURRET_STUN - /// Stun mode projectile type - var/stun_projectile = null - /// Sound of stun projectile - var/stun_projectile_sound - /// Lethal mode projectile type - var/lethal_projectile = null - /// Sound of lethal projectile - var/lethal_projectile_sound - /// Power needed per shot - var/reqpower = 500 - /// Will stay active - var/always_up = FALSE - /// Hides the cover - var/has_cover = TRUE - /// The cover that is covering this turret - var/obj/machinery/porta_turret_cover/cover = null - /// World.time the turret last fired - var/last_fired = 0 - /// Ticks until next shot (1.5 ?) - var/shot_delay = 15 - /// Turret flags about who is turret allowed to shoot - var/turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS - /// Determines if the turret is on - var/on = TRUE - /// Same faction mobs will never be shot at, no matter the other settings - var/list/faction = list("turret") - /// The spark system, used for generating... sparks? - var/datum/effect_system/spark_spread/spark_system - /// Linked turret control panel of the turret - var/obj/machinery/turretid/cp = null - /// The turret will try to shoot from a turf in that direction when in a wall - var/wall_turret_direction - /// If the turret is manually controlled - var/manual_control = FALSE - /// Action button holder for quitting manual control - var/datum/action/turret_quit/quit_action - /// Action button holder for switching between turret modes when manually controlling - var/datum/action/turret_toggle/toggle_action - /// Mob that is remotely controlling the turret - var/mob/remote_controller - -/obj/machinery/porta_turret/Initialize() - . = ..() - if(!base) - base = src - update_icon() - //Sets up a spark system - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(5, 0, src) - spark_system.attach(src) - - setup() - if(has_cover) - cover = new /obj/machinery/porta_turret_cover(loc) - cover.parent_turret = src - var/mutable_appearance/base = mutable_appearance('icons/obj/turrets.dmi', "basedark") - base.layer = NOT_HIGH_OBJ_LAYER - underlays += base - if(!has_cover) - INVOKE_ASYNC(src, .proc/popUp) - -/obj/machinery/porta_turret/proc/toggle_on(var/set_to) - var/current = on - if (!isnull(set_to)) - on = set_to - else - on = !on - if (current != on) - check_should_process() - if (!on) - popDown() - -/obj/machinery/porta_turret/proc/check_should_process() - if (datum_flags & DF_ISPROCESSING) - if (!on || !anchored || (machine_stat & BROKEN) || !powered()) - end_processing() - else - if (on && anchored && !(machine_stat & BROKEN) && powered()) - begin_processing() - -/obj/machinery/porta_turret/update_icon_state() - if(!anchored) - icon_state = "turretCover" - return - if(machine_stat & BROKEN) - icon_state = "[base_icon_state]_broken" - else - if(powered()) - if(on && raised) - switch(mode) - if(TURRET_STUN) - icon_state = "[base_icon_state]_stun" - if(TURRET_LETHAL) - icon_state = "[base_icon_state]_lethal" - else - icon_state = "[base_icon_state]_off" - else - icon_state = "[base_icon_state]_unpowered" - -/obj/machinery/porta_turret/proc/setup(obj/item/gun/turret_gun) - if(stored_gun) - qdel(stored_gun) - stored_gun = null - - if(installation && !turret_gun) - stored_gun = new installation(src) - else if (turret_gun) - stored_gun = turret_gun - - var/list/gun_properties = stored_gun.get_turret_properties() - - //required properties - stun_projectile = gun_properties["stun_projectile"] - stun_projectile_sound = gun_properties["stun_projectile_sound"] - lethal_projectile = gun_properties["lethal_projectile"] - lethal_projectile_sound = gun_properties["lethal_projectile_sound"] - base_icon_state = gun_properties["base_icon_state"] - - //optional properties - if(gun_properties["shot_delay"]) - shot_delay = gun_properties["shot_delay"] - if(gun_properties["reqpower"]) - reqpower = gun_properties["reqpower"] - - update_icon() - return gun_properties - -/obj/machinery/porta_turret/Destroy() - //deletes its own cover with it - QDEL_NULL(cover) - base = null - if(cp) - cp.turrets -= src - cp = null - QDEL_NULL(stored_gun) - QDEL_NULL(spark_system) - remove_control() - return ..() - -/obj/machinery/porta_turret/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "PortableTurret", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/porta_turret/ui_data(mob/user) - var/list/data = list( - "locked" = locked, - "on" = on, - "check_weapons" = turret_flags & TURRET_FLAG_AUTH_WEAPONS, - "neutralize_criminals" = turret_flags & TURRET_FLAG_SHOOT_CRIMINALS, - "neutralize_all" = turret_flags & TURRET_FLAG_SHOOT_ALL, - "neutralize_unidentified" = turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS, - "neutralize_nonmindshielded" = turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED, - "neutralize_cyborgs" = turret_flags & TURRET_FLAG_SHOOT_BORGS, - "ignore_heads" = turret_flags & TURRET_FLAG_SHOOT_HEADS, - "manual_control" = manual_control, - "silicon_user" = FALSE, - "allow_manual_control" = FALSE, - "lasertag_turret" = istype(src, /obj/machinery/porta_turret/lasertag), - ) - if(issilicon(user)) - data["silicon_user"] = TRUE - if(!manual_control) - var/mob/living/silicon/S = user - if(S.hack_software) - data["allow_manual_control"] = TRUE - return data - -/obj/machinery/porta_turret/ui_act(action, list/params) - . = ..() - if(.) - return - - switch(action) - if("power") - if(anchored) - toggle_on() - return TRUE - else - to_chat(usr, "It has to be secured first!") - if("authweapon") - turret_flags ^= TURRET_FLAG_AUTH_WEAPONS - return TRUE - if("shootcriminals") - turret_flags ^= TURRET_FLAG_SHOOT_CRIMINALS - return TRUE - if("shootall") - turret_flags ^= TURRET_FLAG_SHOOT_ALL - return TRUE - if("checkxenos") - turret_flags ^= TURRET_FLAG_SHOOT_ANOMALOUS - return TRUE - if("checkloyal") - turret_flags ^= TURRET_FLAG_SHOOT_UNSHIELDED - return TRUE - if("shootborgs") - turret_flags ^= TURRET_FLAG_SHOOT_BORGS - return TRUE - if("shootheads") - turret_flags ^= TURRET_FLAG_SHOOT_HEADS - return TRUE - if("manual") - if(!issilicon(usr)) - return - give_control(usr) - return TRUE - -/obj/machinery/porta_turret/ui_host(mob/user) - if(has_cover && cover) - return cover - if(base) - return base - return src - -/obj/machinery/porta_turret/power_change() - . = ..() - if(!anchored || (machine_stat & BROKEN) || !powered()) - update_icon() - remove_control() - check_should_process() - -/obj/machinery/porta_turret/attackby(obj/item/I, mob/user, params) - if(machine_stat & BROKEN) - if(I.tool_behaviour == TOOL_CROWBAR) - //If the turret is destroyed, you can remove it with a crowbar to - //try and salvage its components - to_chat(user, "You begin prying the metal coverings off...") - if(I.use_tool(src, user, 20)) - if(prob(70)) - if(stored_gun) - stored_gun.forceMove(loc) - stored_gun = null - to_chat(user, "You remove the turret and salvage some components.") - if(prob(50)) - new /obj/item/stack/sheet/metal(loc, rand(1,4)) - if(prob(50)) - new /obj/item/assembly/prox_sensor(loc) - else - to_chat(user, "You remove the turret but did not manage to salvage anything.") - qdel(src) - - else if((I.tool_behaviour == TOOL_WRENCH) && (!on)) - if(raised) - return - - //This code handles moving the turret around. After all, it's a portable turret! - if(!anchored && !isinspace()) - setAnchored(TRUE) - invisibility = INVISIBILITY_MAXIMUM - update_icon() - to_chat(user, "You secure the exterior bolts on the turret.") - if(has_cover) - cover = new /obj/machinery/porta_turret_cover(loc) //create a new turret. While this is handled in process(), this is to workaround a bug where the turret becomes invisible for a split second - cover.parent_turret = src //make the cover's parent src - else if(anchored) - setAnchored(FALSE) - to_chat(user, "You unsecure the exterior bolts on the turret.") - power_change() - invisibility = 0 - qdel(cover) //deletes the cover, and the turret instance itself becomes its own cover. - - else if(I.GetID()) - //Behavior lock/unlock mangement - if(allowed(user)) - locked = !locked - to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") - else - to_chat(user, "Access denied.") - else if(I.tool_behaviour == TOOL_MULTITOOL && !locked) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You add [src] to multitool buffer.") - else - return ..() - -/obj/machinery/porta_turret/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - to_chat(user, "You short out [src]'s threat assessment circuits.") - audible_message("[src] hums oddly...") - obj_flags |= EMAGGED - controllock = TRUE - toggle_on(FALSE) //turns off the turret temporarily - update_icon() - //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit - addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 6 SECONDS) - //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here - -/obj/machinery/porta_turret/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(on) - //if the turret is on, the EMP no matter how severe disables the turret for a while - //and scrambles its settings, with a slight chance of having an emag effect - if(prob(50)) - turret_flags |= TURRET_FLAG_SHOOT_CRIMINALS - if(prob(50)) - turret_flags |= TURRET_FLAG_AUTH_WEAPONS - if(prob(20)) - turret_flags |= TURRET_FLAG_SHOOT_ALL // Shooting everyone is a pretty big deal, so it's least likely to get turned on - - toggle_on(FALSE) - remove_control() - - addtimer(CALLBACK(src, .proc/toggle_on, TRUE), rand(60,600)) - -/obj/machinery/porta_turret/take_damage(damage, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - . = ..() - if(. && obj_integrity > 0) //damage received - if(prob(30)) - spark_system.start() - if(on && !(turret_flags & TURRET_FLAG_SHOOT_ALL_REACT) && !(obj_flags & EMAGGED)) - turret_flags |= TURRET_FLAG_SHOOT_ALL_REACT - addtimer(CALLBACK(src, .proc/reset_attacked), 60) - -/obj/machinery/porta_turret/proc/reset_attacked() - turret_flags &= ~TURRET_FLAG_SHOOT_ALL_REACT - -/obj/machinery/porta_turret/deconstruct(disassembled = TRUE) - qdel(src) - -/obj/machinery/porta_turret/obj_break(damage_flag) - . = ..() - if(.) - power_change() - invisibility = 0 - spark_system.start() //creates some sparks because they look cool - qdel(cover) //deletes the cover - no need on keeping it there! - -/obj/machinery/porta_turret/process() - //the main machinery process - if(cover == null && anchored) //if it has no cover and is anchored - if(machine_stat & BROKEN) //if the turret is borked - qdel(cover) //delete its cover, assuming it has one. Workaround for a pesky little bug - else - if(has_cover) - cover = new /obj/machinery/porta_turret_cover(loc) //if the turret has no cover and is anchored, give it a cover - cover.parent_turret = src //assign the cover its parent_turret, which would be this (src) - - if(!on || (machine_stat & (NOPOWER|BROKEN)) || manual_control) - return PROCESS_KILL - - var/list/targets = list() - for(var/mob/A in view(scan_range, base)) - if(A.invisibility > SEE_INVISIBLE_LIVING) - continue - - if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS)//if it's set to check for simple animals - if(isanimal(A)) - var/mob/living/simple_animal/SA = A - if(SA.stat || in_faction(SA)) //don't target if dead or in faction - continue - targets += SA - continue - - if(issilicon(A)) - var/mob/living/silicon/sillycone = A - - if(ispAI(A)) - continue - - if((turret_flags & TURRET_FLAG_SHOOT_BORGS) && sillycone.stat != DEAD && iscyborg(sillycone)) - targets += sillycone - continue - - if(sillycone.stat || in_faction(sillycone)) - continue - - if(iscyborg(sillycone)) - var/mob/living/silicon/robot/sillyconerobot = A - if(LAZYLEN(faction) && (ROLE_SYNDICATE in faction) && sillyconerobot.emagged == TRUE) - continue - - else if(iscarbon(A)) - var/mob/living/carbon/C = A - //If not emagged, only target carbons that can use items - if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE))) - continue - - //If emagged, target all but dead carbons - if(mode == TURRET_LETHAL && C.stat == DEAD) - continue - - //if the target is a human and not in our faction, analyze threat level - if(ishuman(C) && !in_faction(C)) - - if(assess_perp(C) >= 4) - targets += C - else if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) //non humans who are not simple animals (xenos etc) - if(!in_faction(C)) - targets += C - - for(var/A in GLOB.mechas_list) - if((get_dist(A, base) < scan_range) && can_see(base, A, scan_range)) - var/obj/mecha/Mech = A - if(Mech.occupant && !in_faction(Mech.occupant)) //If there is a user and they're not in our faction - if(assess_perp(Mech.occupant) >= 4) - targets += Mech - - if((turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) && GLOB.blobs.len && (mode == TURRET_LETHAL)) - for(var/obj/structure/blob/B in view(scan_range, base)) - targets += B - - if(targets.len) - tryToShootAt(targets) - else if(!always_up) - popDown() // no valid targets, close the cover - -/obj/machinery/porta_turret/proc/tryToShootAt(list/atom/movable/targets) - while(targets.len > 0) - var/atom/movable/M = pick(targets) - targets -= M - if(target(M)) - return 1 - -/obj/machinery/porta_turret/proc/popUp() //pops the turret up - if(!anchored) - return - if(raising || raised) - return - if(machine_stat & BROKEN) - return - invisibility = 0 - raising = 1 - if(cover) - flick("popup", cover) - sleep(POPUP_ANIM_TIME) - raising = 0 - if(cover) - cover.icon_state = "openTurretCover" - raised = 1 - layer = MOB_LAYER - -/obj/machinery/porta_turret/proc/popDown() //pops the turret down - if(raising || !raised) - return - if(machine_stat & BROKEN) - return - layer = OBJ_LAYER - raising = 1 - if(cover) - flick("popdown", cover) - sleep(POPDOWN_ANIM_TIME) - raising = 0 - if(cover) - cover.icon_state = "turretCover" - raised = 0 - invisibility = 2 - update_icon() - -/obj/machinery/porta_turret/proc/assess_perp(mob/living/carbon/human/perp) - var/threatcount = 0 //the integer returned - - if(obj_flags & EMAGGED) - return 10 //if emagged, always return 10. - - if((turret_flags & (TURRET_FLAG_SHOOT_ALL | TURRET_FLAG_SHOOT_ALL_REACT)) && !allowed(perp)) - //if the turret has been attacked or is angry, target all non-sec people - if(!allowed(perp)) - return 10 - - if(turret_flags & TURRET_FLAG_AUTH_WEAPONS) //check for weapon authorization - if(isnull(perp.wear_id) || istype(perp.wear_id.GetID(), /obj/item/card/id/syndicate)) - - if(allowed(perp)) //if the perp has security access, return 0 - return 0 - if(perp.is_holding_item_of_type(/obj/item/gun) || perp.is_holding_item_of_type(/obj/item/melee/baton)) - threatcount += 4 - - if(istype(perp.belt, /obj/item/gun) || istype(perp.belt, /obj/item/melee/baton)) - threatcount += 2 - - if(turret_flags & TURRET_FLAG_SHOOT_CRIMINALS) //if the turret can check the records, check if they are set to *Arrest* on records - var/perpname = perp.get_face_name(perp.get_id_name()) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) - if(!R || (R.fields["criminal"] == "*Arrest*")) - threatcount += 4 - - if((turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED) && (!HAS_TRAIT(perp, TRAIT_MINDSHIELD))) - threatcount += 4 - - // If we aren't shooting heads then return a threatcount of 0 - if (!(turret_flags & TURRET_FLAG_SHOOT_HEADS) && (perp.get_assignment() in GLOB.command_positions)) - return 0 - - return threatcount - -/obj/machinery/porta_turret/proc/in_faction(mob/target) - for(var/faction1 in faction) - if(faction1 in target.faction) - return TRUE - return FALSE - -/obj/machinery/porta_turret/proc/target(atom/movable/target) - if(target) - popUp() //pop the turret up if it's not already up. - setDir(get_dir(base, target))//even if you can't shoot, follow the target - shootAt(target) - return 1 - return - -/obj/machinery/porta_turret/proc/shootAt(atom/movable/target) - if(!raised) //the turret has to be raised in order to fire - makes sense, right? - return - - if(!(obj_flags & EMAGGED)) //if it hasn't been emagged, cooldown before shooting again - if(last_fired + shot_delay > world.time) - return - last_fired = world.time - - var/turf/T = get_turf(src) - var/turf/U = get_turf(target) - if(!istype(T) || !istype(U)) - return - - //Wall turrets will try to find adjacent empty turf to shoot from to cover full arc - if(T.density) - if(wall_turret_direction) - var/turf/closer = get_step(T,wall_turret_direction) - if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) - T = closer - else - var/target_dir = get_dir(T,target) - for(var/d in list(0,-45,45)) - var/turf/closer = get_step(T,turn(target_dir,d)) - if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) - T = closer - break - - update_icon() - var/obj/projectile/A - //any emagged turrets drains 2x power and uses a different projectile? - if(mode == TURRET_STUN) - use_power(reqpower) - A = new stun_projectile(T) - playsound(loc, stun_projectile_sound, 75, TRUE) - else - use_power(reqpower * 2) - A = new lethal_projectile(T) - playsound(loc, lethal_projectile_sound, 75, TRUE) - - - //Shooting Code: - A.preparePixelProjectile(target, T) - A.firer = src - A.fired_from = src - A.fire() - return A - -/obj/machinery/porta_turret/proc/setState(on, mode, shoot_cyborgs) - if(controllock) - return - - shoot_cyborgs ? (turret_flags |= TURRET_FLAG_SHOOT_BORGS) : (turret_flags &= ~TURRET_FLAG_SHOOT_BORGS) - toggle_on(on) - src.mode = mode - power_change() - -/datum/action/turret_toggle - name = "Toggle Mode" - icon_icon = 'icons/mob/actions/actions_mecha.dmi' - button_icon_state = "mech_cycle_equip_off" - -/datum/action/turret_toggle/Trigger() - var/obj/machinery/porta_turret/P = target - if(!istype(P)) - return - P.setState(P.on,!P.mode) - -/datum/action/turret_quit - name = "Release Control" - icon_icon = 'icons/mob/actions/actions_mecha.dmi' - button_icon_state = "mech_eject" - -/datum/action/turret_quit/Trigger() - var/obj/machinery/porta_turret/P = target - if(!istype(P)) - return - P.remove_control(FALSE) - -/obj/machinery/porta_turret/proc/give_control(mob/A) - if(manual_control || !can_interact(A)) - return FALSE - remote_controller = A - if(!quit_action) - quit_action = new(src) - quit_action.Grant(remote_controller) - if(!toggle_action) - toggle_action = new(src) - toggle_action.Grant(remote_controller) - remote_controller.reset_perspective(src) - remote_controller.click_intercept = src - manual_control = TRUE - always_up = TRUE - popUp() - return TRUE - -/obj/machinery/porta_turret/proc/remove_control(warning_message = TRUE) - if(!manual_control) - return FALSE - if(remote_controller) - if(warning_message) - to_chat(remote_controller, "Your uplink to [src] has been severed!") - quit_action.Remove(remote_controller) - toggle_action.Remove(remote_controller) - remote_controller.click_intercept = null - remote_controller.reset_perspective() - always_up = initial(always_up) - manual_control = FALSE - remote_controller = null - return TRUE - -/obj/machinery/porta_turret/proc/InterceptClickOn(mob/living/caller, params, atom/A) - if(!manual_control) - return FALSE - if(!can_interact(caller)) - remove_control() - return FALSE - log_combat(caller,A,"fired with manual turret control at") - target(A) - return TRUE - -/obj/machinery/porta_turret/syndicate - installation = null - always_up = 1 - use_power = NO_POWER_USE - has_cover = 0 - scan_range = 9 - req_access = list(ACCESS_SYNDICATE) - mode = TURRET_LETHAL - stun_projectile = /obj/projectile/bullet - lethal_projectile = /obj/projectile/bullet - lethal_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' - stun_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' - icon_state = "syndie_off" - base_icon_state = "syndie" - faction = list(ROLE_SYNDICATE) - desc = "A ballistic machine gun auto-turret." - -/obj/machinery/porta_turret/syndicate/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) - -/obj/machinery/porta_turret/syndicate/setup() - return - -/obj/machinery/porta_turret/syndicate/assess_perp(mob/living/carbon/human/perp) - return 10 //Syndicate turrets shoot everything not in their faction - -/obj/machinery/porta_turret/syndicate/energy - icon_state = "standard_lethal" - base_icon_state = "standard" - stun_projectile = /obj/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.ogg' - lethal_projectile = /obj/projectile/beam/laser - lethal_projectile_sound = 'sound/weapons/laser.ogg' - desc = "An energy blaster auto-turret." - -/obj/machinery/porta_turret/syndicate/energy/heavy - icon_state = "standard_lethal" - base_icon_state = "standard" - stun_projectile = /obj/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.ogg' - lethal_projectile = /obj/projectile/beam/laser/heavylaser - lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg' - desc = "An energy blaster auto-turret." - -/obj/machinery/porta_turret/syndicate/energy/raven - stun_projectile = /obj/projectile/beam/laser - stun_projectile_sound = 'sound/weapons/laser.ogg' - faction = list("neutral","silicon","turret") - -/obj/machinery/porta_turret/syndicate/pod - integrity_failure = 0.5 - max_integrity = 40 - stun_projectile = /obj/projectile/bullet/syndicate_turret - lethal_projectile = /obj/projectile/bullet/syndicate_turret - -/obj/machinery/porta_turret/syndicate/shuttle - scan_range = 9 - shot_delay = 3 - stun_projectile = /obj/projectile/bullet/p50/penetrator/shuttle - lethal_projectile = /obj/projectile/bullet/p50/penetrator/shuttle - lethal_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' - stun_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' - armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 80, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - -/obj/machinery/porta_turret/syndicate/shuttle/target(atom/movable/target) - if(target) - setDir(get_dir(base, target))//even if you can't shoot, follow the target - shootAt(target) - addtimer(CALLBACK(src, .proc/shootAt, target), 5) - addtimer(CALLBACK(src, .proc/shootAt, target), 10) - addtimer(CALLBACK(src, .proc/shootAt, target), 15) - return TRUE - -/obj/machinery/porta_turret/ai - faction = list("silicon") - turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS | TURRET_FLAG_SHOOT_HEADS - -/obj/machinery/porta_turret/ai/assess_perp(mob/living/carbon/human/perp) - return 10 //AI turrets shoot at everything not in their faction - -/obj/machinery/porta_turret/aux_base - name = "perimeter defense turret" - desc = "A plasma beam turret calibrated to defend outposts against non-humanoid fauna. It is more effective when exposed to the environment." - installation = null - lethal_projectile = /obj/projectile/plasma/turret - lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' - mode = TURRET_LETHAL //It would be useless in stun mode anyway - faction = list("neutral","silicon","turret") //Minebots, medibots, etc that should not be shot. - -/obj/machinery/porta_turret/aux_base/assess_perp(mob/living/carbon/human/perp) - return 0 //Never shoot humanoids. You are on your own if Ashwalkers or the like attack! - -/obj/machinery/porta_turret/aux_base/setup() - return - -/obj/machinery/porta_turret/aux_base/interact(mob/user) //Controlled solely from the base console. - return - -/obj/machinery/porta_turret/aux_base/Initialize() - . = ..() - cover.name = name - cover.desc = desc - -/obj/machinery/porta_turret/centcom_shuttle - installation = null - max_integrity = 260 - always_up = 1 - use_power = NO_POWER_USE - has_cover = 0 - scan_range = 9 - stun_projectile = /obj/projectile/beam/laser - lethal_projectile = /obj/projectile/beam/laser - lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' - stun_projectile_sound = 'sound/weapons/plasma_cutter.ogg' - icon_state = "syndie_off" - base_icon_state = "syndie" - faction = list("neutral","silicon","turret") - mode = TURRET_LETHAL - -/obj/machinery/porta_turret/centcom_shuttle/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) - -/obj/machinery/porta_turret/centcom_shuttle/assess_perp(mob/living/carbon/human/perp) - return 0 - -/obj/machinery/porta_turret/centcom_shuttle/setup() - return - -/obj/machinery/porta_turret/centcom_shuttle/weak - max_integrity = 120 - integrity_failure = 0.5 - name = "Old Laser Turret" - desc = "A turret built with substandard parts and run down further with age. Still capable of delivering lethal lasers to the odd space carp, but not much else." - stun_projectile = /obj/projectile/beam/weak/penetrator - lethal_projectile = /obj/projectile/beam/weak/penetrator - faction = list("neutral","silicon","turret") - -//////////////////////// -//Turret Control Panel// -//////////////////////// - -/obj/machinery/turretid - name = "turret control panel" - desc = "Used to control a room's automated defenses." - icon = 'icons/obj/machines/turret_control.dmi' - icon_state = "control_standby" - density = FALSE - req_access = list(ACCESS_AI_UPLOAD) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - ui_x = 305 - ui_y = 172 - /// Variable dictating if linked turrets are active and will shoot targets - var/enabled = TRUE - /// Variable dictating if linked turrets will shoot lethal projectiles - var/lethal = FALSE - /// Variable dictating if the panel is locked, preventing changes to turret settings - var/locked = TRUE - /// An area in which linked turrets are located, it can be an area name, path or nothing - var/control_area = null - /// AI is unable to use this machine if set to TRUE - var/ailock = FALSE - /// Variable dictating if linked turrets will shoot cyborgs - var/shoot_cyborgs = FALSE - /// List of all linked turrets - var/list/turrets = list() - -/obj/machinery/turretid/Initialize(mapload, ndir = 0, built = 0) - . = ..() - if(built) - setDir(ndir) - locked = FALSE - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - power_change() //Checks power and initial settings - -/obj/machinery/turretid/Destroy() - turrets.Cut() - return ..() - -/obj/machinery/turretid/Initialize(mapload) //map-placed turrets autolink turrets - . = ..() - if(!mapload) - return - - if(control_area) - control_area = get_area_instance_from_text(control_area) - if(control_area == null) - control_area = get_area(src) - stack_trace("Bad control_area path for [src], [src.control_area]") - else if(!control_area) - control_area = get_area(src) - - for(var/obj/machinery/porta_turret/T in control_area) - turrets |= T - T.cp = src - -/obj/machinery/turretid/examine(mob/user) - . += ..() - if(issilicon(user) && !(machine_stat & BROKEN)) - . += {"Ctrl-click [src] to [ enabled ? "disable" : "enable"] turrets. - Alt-click [src] to set turrets to [ lethal ? "stun" : "kill"]."} - -/obj/machinery/turretid/attackby(obj/item/I, mob/user, params) - if(machine_stat & BROKEN) - return - - if(I.tool_behaviour == TOOL_MULTITOOL) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - if(M.buffer && istype(M.buffer, /obj/machinery/porta_turret)) - turrets |= M.buffer - to_chat(user, "You link \the [M.buffer] with \the [src].") - return - - if (issilicon(user)) - return attack_hand(user) - - if ( get_dist(src, user) == 0 ) // trying to unlock the interface - if (allowed(usr)) - if(obj_flags & EMAGGED) - to_chat(user, "The turret control is unresponsive!") - return - - locked = !locked - to_chat(user, "You [ locked ? "lock" : "unlock"] the panel.") - else - to_chat(user, "Access denied.") - -/obj/machinery/turretid/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - to_chat(user, "You short out the turret controls' access analysis module.") - obj_flags |= EMAGGED - locked = FALSE - -/obj/machinery/turretid/attack_ai(mob/user) - if(!ailock || IsAdminGhost(user)) - return attack_hand(user) - else - to_chat(user, "There seems to be a firewall preventing you from accessing this device!") - -/obj/machinery/turretid/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "TurretControl", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/turretid/ui_data(mob/user) - var/list/data = list() - data["locked"] = locked - data["siliconUser"] = user.has_unlimited_silicon_privilege - data["enabled"] = enabled - data["lethal"] = lethal - data["shootCyborgs"] = shoot_cyborgs - return data - -/obj/machinery/turretid/ui_act(action, list/params) - . = ..() - if(.) - return - - switch(action) - if("lock") - if(!usr.has_unlimited_silicon_privilege) - return - if((obj_flags & EMAGGED) || (machine_stat & BROKEN)) - to_chat(usr, "The turret control is unresponsive!") - return - locked = !locked - return TRUE - if("power") - toggle_on(usr) - return TRUE - if("mode") - toggle_lethal(usr) - return TRUE - if("shoot_silicons") - shoot_silicons(usr) - return TRUE - -/obj/machinery/turretid/proc/toggle_lethal(mob/user) - lethal = !lethal - add_hiddenprint(user) - log_combat(user, src, "[lethal ? "enabled" : "disabled"] lethals on") - updateTurrets() - -/obj/machinery/turretid/proc/toggle_on(mob/user) - enabled = !enabled - add_hiddenprint(user) - log_combat(user, src, "[enabled ? "enabled" : "disabled"]") - updateTurrets() - -/obj/machinery/turretid/proc/shoot_silicons(mob/user) - shoot_cyborgs = !shoot_cyborgs - add_hiddenprint(user) - log_combat(user, src, "[shoot_cyborgs ? "Shooting Borgs" : "Not Shooting Borgs"]") - updateTurrets() - -/obj/machinery/turretid/proc/updateTurrets() - for (var/obj/machinery/porta_turret/aTurret in turrets) - aTurret.setState(enabled, lethal, shoot_cyborgs) - update_icon() - -/obj/machinery/turretid/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "control_off" - else if (enabled) - if (lethal) - icon_state = "control_kill" - else - icon_state = "control_stun" - else - icon_state = "control_standby" - -/obj/item/wallframe/turret_control - name = "turret control frame" - desc = "Used for building turret control panels." - icon_state = "apc" - result_path = /obj/machinery/turretid - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) - -/obj/item/gun/proc/get_turret_properties() - . = list() - .["lethal_projectile"] = null - .["lethal_projectile_sound"] = null - .["stun_projectile"] = null - .["stun_projectile_sound"] = null - .["base_icon_state"] = "standard" - -/obj/item/gun/energy/get_turret_properties() - . = ..() - - var/obj/item/ammo_casing/primary_ammo = ammo_type[1] - - .["stun_projectile"] = initial(primary_ammo.projectile_type) - .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) - - if(ammo_type.len > 1) - var/obj/item/ammo_casing/secondary_ammo = ammo_type[2] - .["lethal_projectile"] = initial(secondary_ammo.projectile_type) - .["lethal_projectile_sound"] = initial(secondary_ammo.fire_sound) - else - .["lethal_projectile"] = .["stun_projectile"] - .["lethal_projectile_sound"] = .["stun_projectile_sound"] - -/obj/item/gun/ballistic/get_turret_properties() - . = ..() - var/obj/item/ammo_box/mag = mag_type - var/obj/item/ammo_casing/primary_ammo = initial(mag.ammo_type) - - .["base_icon_state"] = "syndie" - .["stun_projectile"] = initial(primary_ammo.projectile_type) - .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) - .["lethal_projectile"] = .["stun_projectile"] - .["lethal_projectile_sound"] = .["stun_projectile_sound"] - - -/obj/item/gun/energy/laser/bluetag/get_turret_properties() - . = ..() - .["stun_projectile"] = /obj/projectile/beam/lasertag/bluetag - .["lethal_projectile"] = /obj/projectile/beam/lasertag/bluetag - .["base_icon_state"] = "blue" - .["shot_delay"] = 30 - .["team_color"] = "blue" - -/obj/item/gun/energy/laser/redtag/get_turret_properties() - . = ..() - .["stun_projectile"] = /obj/projectile/beam/lasertag/redtag - .["lethal_projectile"] = /obj/projectile/beam/lasertag/redtag - .["base_icon_state"] = "red" - .["shot_delay"] = 30 - .["team_color"] = "red" - -/obj/item/gun/energy/e_gun/turret/get_turret_properties() - . = ..() - -/obj/machinery/porta_turret/lasertag - req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) - turret_flags = TURRET_FLAG_AUTH_WEAPONS - ui_y = 115 - var/team_color - -/obj/machinery/porta_turret/lasertag/assess_perp(mob/living/carbon/human/perp) - . = 0 - if(team_color == "blue") //Lasertag turrets target the opposing team, how great is that? -Sieve - . = 0 //But does not target anyone else - if(istype(perp.wear_suit, /obj/item/clothing/suit/redtag)) - . += 4 - if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) - . += 4 - if(istype(perp.belt, /obj/item/gun/energy/laser/redtag)) - . += 2 - - if(team_color == "red") - . = 0 - if(istype(perp.wear_suit, /obj/item/clothing/suit/bluetag)) - . += 4 - if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) - . += 4 - if(istype(perp.belt, /obj/item/gun/energy/laser/bluetag)) - . += 2 - -/obj/machinery/porta_turret/lasertag/setup(obj/item/gun/gun) - var/list/properties = ..() - if(properties["team_color"]) - team_color = properties["team_color"] - -/obj/machinery/porta_turret/lasertag/ui_status(mob/user) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(team_color == "blue" && istype(H.wear_suit, /obj/item/clothing/suit/redtag)) - return UI_CLOSE - if(team_color == "red" && istype(H.wear_suit, /obj/item/clothing/suit/bluetag)) - return UI_CLOSE - return ..() - -//lasertag presets -/obj/machinery/porta_turret/lasertag/red - installation = /obj/item/gun/energy/laser/redtag - team_color = "red" - -/obj/machinery/porta_turret/lasertag/blue - installation = /obj/item/gun/energy/laser/bluetag - team_color = "blue" - -/obj/machinery/porta_turret/lasertag/bullet_act(obj/projectile/P) - . = ..() - if(on) - if(team_color == "blue") - if(istype(P, /obj/projectile/beam/lasertag/redtag)) - toggle_on(FALSE) - addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 10 SECONDS) - else if(team_color == "red") - if(istype(P, /obj/projectile/beam/lasertag/bluetag)) - toggle_on(FALSE) - addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 10 SECONDS) +#define TURRET_STUN 0 +#define TURRET_LETHAL 1 + +#define POPUP_ANIM_TIME 5 +#define POPDOWN_ANIM_TIME 5 //Be sure to change the icon animation at the same time or it'll look bad + +#define TURRET_FLAG_SHOOT_ALL_REACT (1<<0) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) +#define TURRET_FLAG_AUTH_WEAPONS (1<<1) // Checks if it can shoot people that have a weapon they aren't authorized to have +#define TURRET_FLAG_SHOOT_CRIMINALS (1<<2) // Checks if it can shoot people that are wanted +#define TURRET_FLAG_SHOOT_ALL (1<<3) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) +#define TURRET_FLAG_SHOOT_ANOMALOUS (1<<4) // Checks if it can shoot at unidentified lifeforms (ie xenos) +#define TURRET_FLAG_SHOOT_UNSHIELDED (1<<5) // Checks if it can shoot people that aren't mindshielded and who arent heads +#define TURRET_FLAG_SHOOT_BORGS (1<<6) // checks if it can shoot cyborgs +#define TURRET_FLAG_SHOOT_HEADS (1<<7) // checks if it can shoot at heads of staff + +/obj/machinery/porta_turret + name = "turret" + icon = 'icons/obj/turrets.dmi' + icon_state = "turretCover" + layer = OBJ_LAYER + invisibility = INVISIBILITY_OBSERVER //the turret is invisible if it's inside its cover + density = TRUE + desc = "A covered turret that shoots at its enemies." + use_power = IDLE_POWER_USE //this turret uses and requires power + idle_power_usage = 50 //when inactive, this turret takes up constant 50 Equipment power + active_power_usage = 300 //when active, this turret takes up constant 300 Equipment power + req_access = list(ACCESS_SECURITY) /// Only people with Security access + power_channel = AREA_USAGE_EQUIP //drains power from the EQUIPMENT channel + max_integrity = 160 //the turret's health + integrity_failure = 0.5 + armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + ui_x = 305 + ui_y = 300 + /// Base turret icon state + var/base_icon_state = "standard" + /// Scan range of the turret for locating targets + var/scan_range = 7 + /// For turrets inside other objects + var/atom/base = null + /// If the turret cover is "open" and the turret is raised + var/raised = FALSE + /// If the turret is currently opening or closing its cover + var/raising = FALSE + /// If the turret's behaviour control access is locked + var/locked = TRUE + /// If the turret responds to control panels + var/controllock = FALSE + /// The type of weapon installed by default + var/installation = /obj/item/gun/energy/e_gun/turret + /// What stored gun is in the turret + var/obj/item/gun/stored_gun = null + /// The charge of the gun when retrieved from wreckage + var/gun_charge = 0 + /// In which mode is turret in, stun or lethal + var/mode = TURRET_STUN + /// Stun mode projectile type + var/stun_projectile = null + /// Sound of stun projectile + var/stun_projectile_sound + /// Lethal mode projectile type + var/lethal_projectile = null + /// Sound of lethal projectile + var/lethal_projectile_sound + /// Power needed per shot + var/reqpower = 500 + /// Will stay active + var/always_up = FALSE + /// Hides the cover + var/has_cover = TRUE + /// The cover that is covering this turret + var/obj/machinery/porta_turret_cover/cover = null + /// World.time the turret last fired + var/last_fired = 0 + /// Ticks until next shot (1.5 ?) + var/shot_delay = 15 + /// Turret flags about who is turret allowed to shoot + var/turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS + /// Determines if the turret is on + var/on = TRUE + /// Same faction mobs will never be shot at, no matter the other settings + var/list/faction = list("turret") + /// The spark system, used for generating... sparks? + var/datum/effect_system/spark_spread/spark_system + /// Linked turret control panel of the turret + var/obj/machinery/turretid/cp = null + /// The turret will try to shoot from a turf in that direction when in a wall + var/wall_turret_direction + /// If the turret is manually controlled + var/manual_control = FALSE + /// Action button holder for quitting manual control + var/datum/action/turret_quit/quit_action + /// Action button holder for switching between turret modes when manually controlling + var/datum/action/turret_toggle/toggle_action + /// Mob that is remotely controlling the turret + var/mob/remote_controller + +/obj/machinery/porta_turret/Initialize() + . = ..() + if(!base) + base = src + update_icon() + //Sets up a spark system + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(5, 0, src) + spark_system.attach(src) + + setup() + if(has_cover) + cover = new /obj/machinery/porta_turret_cover(loc) + cover.parent_turret = src + var/mutable_appearance/base = mutable_appearance('icons/obj/turrets.dmi', "basedark") + base.layer = NOT_HIGH_OBJ_LAYER + underlays += base + if(!has_cover) + INVOKE_ASYNC(src, .proc/popUp) + +/obj/machinery/porta_turret/proc/toggle_on(var/set_to) + var/current = on + if (!isnull(set_to)) + on = set_to + else + on = !on + if (current != on) + check_should_process() + if (!on) + popDown() + +/obj/machinery/porta_turret/proc/check_should_process() + if (datum_flags & DF_ISPROCESSING) + if (!on || !anchored || (machine_stat & BROKEN) || !powered()) + end_processing() + else + if (on && anchored && !(machine_stat & BROKEN) && powered()) + begin_processing() + +/obj/machinery/porta_turret/update_icon_state() + if(!anchored) + icon_state = "turretCover" + return + if(machine_stat & BROKEN) + icon_state = "[base_icon_state]_broken" + else + if(powered()) + if(on && raised) + switch(mode) + if(TURRET_STUN) + icon_state = "[base_icon_state]_stun" + if(TURRET_LETHAL) + icon_state = "[base_icon_state]_lethal" + else + icon_state = "[base_icon_state]_off" + else + icon_state = "[base_icon_state]_unpowered" + +/obj/machinery/porta_turret/proc/setup(obj/item/gun/turret_gun) + if(stored_gun) + qdel(stored_gun) + stored_gun = null + + if(installation && !turret_gun) + stored_gun = new installation(src) + else if (turret_gun) + stored_gun = turret_gun + + var/list/gun_properties = stored_gun.get_turret_properties() + + //required properties + stun_projectile = gun_properties["stun_projectile"] + stun_projectile_sound = gun_properties["stun_projectile_sound"] + lethal_projectile = gun_properties["lethal_projectile"] + lethal_projectile_sound = gun_properties["lethal_projectile_sound"] + base_icon_state = gun_properties["base_icon_state"] + + //optional properties + if(gun_properties["shot_delay"]) + shot_delay = gun_properties["shot_delay"] + if(gun_properties["reqpower"]) + reqpower = gun_properties["reqpower"] + + update_icon() + return gun_properties + +/obj/machinery/porta_turret/Destroy() + //deletes its own cover with it + QDEL_NULL(cover) + base = null + if(cp) + cp.turrets -= src + cp = null + QDEL_NULL(stored_gun) + QDEL_NULL(spark_system) + remove_control() + return ..() + +/obj/machinery/porta_turret/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "PortableTurret", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/porta_turret/ui_data(mob/user) + var/list/data = list( + "locked" = locked, + "on" = on, + "check_weapons" = turret_flags & TURRET_FLAG_AUTH_WEAPONS, + "neutralize_criminals" = turret_flags & TURRET_FLAG_SHOOT_CRIMINALS, + "neutralize_all" = turret_flags & TURRET_FLAG_SHOOT_ALL, + "neutralize_unidentified" = turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS, + "neutralize_nonmindshielded" = turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED, + "neutralize_cyborgs" = turret_flags & TURRET_FLAG_SHOOT_BORGS, + "ignore_heads" = turret_flags & TURRET_FLAG_SHOOT_HEADS, + "manual_control" = manual_control, + "silicon_user" = FALSE, + "allow_manual_control" = FALSE, + "lasertag_turret" = istype(src, /obj/machinery/porta_turret/lasertag), + ) + if(issilicon(user)) + data["silicon_user"] = TRUE + if(!manual_control) + var/mob/living/silicon/S = user + if(S.hack_software) + data["allow_manual_control"] = TRUE + return data + +/obj/machinery/porta_turret/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("power") + if(anchored) + toggle_on() + return TRUE + else + to_chat(usr, "It has to be secured first!") + if("authweapon") + turret_flags ^= TURRET_FLAG_AUTH_WEAPONS + return TRUE + if("shootcriminals") + turret_flags ^= TURRET_FLAG_SHOOT_CRIMINALS + return TRUE + if("shootall") + turret_flags ^= TURRET_FLAG_SHOOT_ALL + return TRUE + if("checkxenos") + turret_flags ^= TURRET_FLAG_SHOOT_ANOMALOUS + return TRUE + if("checkloyal") + turret_flags ^= TURRET_FLAG_SHOOT_UNSHIELDED + return TRUE + if("shootborgs") + turret_flags ^= TURRET_FLAG_SHOOT_BORGS + return TRUE + if("shootheads") + turret_flags ^= TURRET_FLAG_SHOOT_HEADS + return TRUE + if("manual") + if(!issilicon(usr)) + return + give_control(usr) + return TRUE + +/obj/machinery/porta_turret/ui_host(mob/user) + if(has_cover && cover) + return cover + if(base) + return base + return src + +/obj/machinery/porta_turret/power_change() + . = ..() + if(!anchored || (machine_stat & BROKEN) || !powered()) + update_icon() + remove_control() + check_should_process() + +/obj/machinery/porta_turret/attackby(obj/item/I, mob/user, params) + if(machine_stat & BROKEN) + if(I.tool_behaviour == TOOL_CROWBAR) + //If the turret is destroyed, you can remove it with a crowbar to + //try and salvage its components + to_chat(user, "You begin prying the metal coverings off...") + if(I.use_tool(src, user, 20)) + if(prob(70)) + if(stored_gun) + stored_gun.forceMove(loc) + stored_gun = null + to_chat(user, "You remove the turret and salvage some components.") + if(prob(50)) + new /obj/item/stack/sheet/metal(loc, rand(1,4)) + if(prob(50)) + new /obj/item/assembly/prox_sensor(loc) + else + to_chat(user, "You remove the turret but did not manage to salvage anything.") + qdel(src) + + else if((I.tool_behaviour == TOOL_WRENCH) && (!on)) + if(raised) + return + + //This code handles moving the turret around. After all, it's a portable turret! + if(!anchored && !isinspace()) + setAnchored(TRUE) + invisibility = INVISIBILITY_MAXIMUM + update_icon() + to_chat(user, "You secure the exterior bolts on the turret.") + if(has_cover) + cover = new /obj/machinery/porta_turret_cover(loc) //create a new turret. While this is handled in process(), this is to workaround a bug where the turret becomes invisible for a split second + cover.parent_turret = src //make the cover's parent src + else if(anchored) + setAnchored(FALSE) + to_chat(user, "You unsecure the exterior bolts on the turret.") + power_change() + invisibility = 0 + qdel(cover) //deletes the cover, and the turret instance itself becomes its own cover. + + else if(I.GetID()) + //Behavior lock/unlock mangement + if(allowed(user)) + locked = !locked + to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") + else + to_chat(user, "Access denied.") + else if(I.tool_behaviour == TOOL_MULTITOOL && !locked) + if(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You add [src] to multitool buffer.") + else + return ..() + +/obj/machinery/porta_turret/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + to_chat(user, "You short out [src]'s threat assessment circuits.") + audible_message("[src] hums oddly...") + obj_flags |= EMAGGED + controllock = TRUE + toggle_on(FALSE) //turns off the turret temporarily + update_icon() + //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 6 SECONDS) + //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here + +/obj/machinery/porta_turret/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(on) + //if the turret is on, the EMP no matter how severe disables the turret for a while + //and scrambles its settings, with a slight chance of having an emag effect + if(prob(50)) + turret_flags |= TURRET_FLAG_SHOOT_CRIMINALS + if(prob(50)) + turret_flags |= TURRET_FLAG_AUTH_WEAPONS + if(prob(20)) + turret_flags |= TURRET_FLAG_SHOOT_ALL // Shooting everyone is a pretty big deal, so it's least likely to get turned on + + toggle_on(FALSE) + remove_control() + + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), rand(60,600)) + +/obj/machinery/porta_turret/take_damage(damage, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + . = ..() + if(. && obj_integrity > 0) //damage received + if(prob(30)) + spark_system.start() + if(on && !(turret_flags & TURRET_FLAG_SHOOT_ALL_REACT) && !(obj_flags & EMAGGED)) + turret_flags |= TURRET_FLAG_SHOOT_ALL_REACT + addtimer(CALLBACK(src, .proc/reset_attacked), 60) + +/obj/machinery/porta_turret/proc/reset_attacked() + turret_flags &= ~TURRET_FLAG_SHOOT_ALL_REACT + +/obj/machinery/porta_turret/deconstruct(disassembled = TRUE) + qdel(src) + +/obj/machinery/porta_turret/obj_break(damage_flag) + . = ..() + if(.) + power_change() + invisibility = 0 + spark_system.start() //creates some sparks because they look cool + qdel(cover) //deletes the cover - no need on keeping it there! + +/obj/machinery/porta_turret/process() + //the main machinery process + if(cover == null && anchored) //if it has no cover and is anchored + if(machine_stat & BROKEN) //if the turret is borked + qdel(cover) //delete its cover, assuming it has one. Workaround for a pesky little bug + else + if(has_cover) + cover = new /obj/machinery/porta_turret_cover(loc) //if the turret has no cover and is anchored, give it a cover + cover.parent_turret = src //assign the cover its parent_turret, which would be this (src) + + if(!on || (machine_stat & (NOPOWER|BROKEN)) || manual_control) + return PROCESS_KILL + + var/list/targets = list() + for(var/mob/A in view(scan_range, base)) + if(A.invisibility > SEE_INVISIBLE_LIVING) + continue + + if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS)//if it's set to check for simple animals + if(isanimal(A)) + var/mob/living/simple_animal/SA = A + if(SA.stat || in_faction(SA)) //don't target if dead or in faction + continue + targets += SA + continue + + if(issilicon(A)) + var/mob/living/silicon/sillycone = A + + if(ispAI(A)) + continue + + if((turret_flags & TURRET_FLAG_SHOOT_BORGS) && sillycone.stat != DEAD && iscyborg(sillycone)) + targets += sillycone + continue + + if(sillycone.stat || in_faction(sillycone)) + continue + + if(iscyborg(sillycone)) + var/mob/living/silicon/robot/sillyconerobot = A + if(LAZYLEN(faction) && (ROLE_SYNDICATE in faction) && sillyconerobot.emagged == TRUE) + continue + + else if(iscarbon(A)) + var/mob/living/carbon/C = A + //If not emagged, only target carbons that can use items + if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE))) + continue + + //If emagged, target all but dead carbons + if(mode == TURRET_LETHAL && C.stat == DEAD) + continue + + //if the target is a human and not in our faction, analyze threat level + if(ishuman(C) && !in_faction(C)) + + if(assess_perp(C) >= 4) + targets += C + else if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) //non humans who are not simple animals (xenos etc) + if(!in_faction(C)) + targets += C + + for(var/A in GLOB.mechas_list) + if((get_dist(A, base) < scan_range) && can_see(base, A, scan_range)) + var/obj/mecha/Mech = A + if(Mech.occupant && !in_faction(Mech.occupant)) //If there is a user and they're not in our faction + if(assess_perp(Mech.occupant) >= 4) + targets += Mech + + if((turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) && GLOB.blobs.len && (mode == TURRET_LETHAL)) + for(var/obj/structure/blob/B in view(scan_range, base)) + targets += B + + if(targets.len) + tryToShootAt(targets) + else if(!always_up) + popDown() // no valid targets, close the cover + +/obj/machinery/porta_turret/proc/tryToShootAt(list/atom/movable/targets) + while(targets.len > 0) + var/atom/movable/M = pick(targets) + targets -= M + if(target(M)) + return 1 + +/obj/machinery/porta_turret/proc/popUp() //pops the turret up + if(!anchored) + return + if(raising || raised) + return + if(machine_stat & BROKEN) + return + invisibility = 0 + raising = 1 + if(cover) + flick("popup", cover) + sleep(POPUP_ANIM_TIME) + raising = 0 + if(cover) + cover.icon_state = "openTurretCover" + raised = 1 + layer = MOB_LAYER + +/obj/machinery/porta_turret/proc/popDown() //pops the turret down + if(raising || !raised) + return + if(machine_stat & BROKEN) + return + layer = OBJ_LAYER + raising = 1 + if(cover) + flick("popdown", cover) + sleep(POPDOWN_ANIM_TIME) + raising = 0 + if(cover) + cover.icon_state = "turretCover" + raised = 0 + invisibility = 2 + update_icon() + +/obj/machinery/porta_turret/proc/assess_perp(mob/living/carbon/human/perp) + var/threatcount = 0 //the integer returned + + if(obj_flags & EMAGGED) + return 10 //if emagged, always return 10. + + if((turret_flags & (TURRET_FLAG_SHOOT_ALL | TURRET_FLAG_SHOOT_ALL_REACT)) && !allowed(perp)) + //if the turret has been attacked or is angry, target all non-sec people + if(!allowed(perp)) + return 10 + + if(turret_flags & TURRET_FLAG_AUTH_WEAPONS) //check for weapon authorization + if(isnull(perp.wear_id) || istype(perp.wear_id.GetID(), /obj/item/card/id/syndicate)) + + if(allowed(perp)) //if the perp has security access, return 0 + return 0 + if(perp.is_holding_item_of_type(/obj/item/gun) || perp.is_holding_item_of_type(/obj/item/melee/baton)) + threatcount += 4 + + if(istype(perp.belt, /obj/item/gun) || istype(perp.belt, /obj/item/melee/baton)) + threatcount += 2 + + if(turret_flags & TURRET_FLAG_SHOOT_CRIMINALS) //if the turret can check the records, check if they are set to *Arrest* on records + var/perpname = perp.get_face_name(perp.get_id_name()) + var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) + if(!R || (R.fields["criminal"] == "*Arrest*")) + threatcount += 4 + + if((turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED) && (!HAS_TRAIT(perp, TRAIT_MINDSHIELD))) + threatcount += 4 + + // If we aren't shooting heads then return a threatcount of 0 + if (!(turret_flags & TURRET_FLAG_SHOOT_HEADS) && (perp.get_assignment() in GLOB.command_positions)) + return 0 + + return threatcount + +/obj/machinery/porta_turret/proc/in_faction(mob/target) + for(var/faction1 in faction) + if(faction1 in target.faction) + return TRUE + return FALSE + +/obj/machinery/porta_turret/proc/target(atom/movable/target) + if(target) + popUp() //pop the turret up if it's not already up. + setDir(get_dir(base, target))//even if you can't shoot, follow the target + shootAt(target) + return 1 + return + +/obj/machinery/porta_turret/proc/shootAt(atom/movable/target) + if(!raised) //the turret has to be raised in order to fire - makes sense, right? + return + + if(!(obj_flags & EMAGGED)) //if it hasn't been emagged, cooldown before shooting again + if(last_fired + shot_delay > world.time) + return + last_fired = world.time + + var/turf/T = get_turf(src) + var/turf/U = get_turf(target) + if(!istype(T) || !istype(U)) + return + + //Wall turrets will try to find adjacent empty turf to shoot from to cover full arc + if(T.density) + if(wall_turret_direction) + var/turf/closer = get_step(T,wall_turret_direction) + if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) + T = closer + else + var/target_dir = get_dir(T,target) + for(var/d in list(0,-45,45)) + var/turf/closer = get_step(T,turn(target_dir,d)) + if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) + T = closer + break + + update_icon() + var/obj/projectile/A + //any emagged turrets drains 2x power and uses a different projectile? + if(mode == TURRET_STUN) + use_power(reqpower) + A = new stun_projectile(T) + playsound(loc, stun_projectile_sound, 75, TRUE) + else + use_power(reqpower * 2) + A = new lethal_projectile(T) + playsound(loc, lethal_projectile_sound, 75, TRUE) + + + //Shooting Code: + A.preparePixelProjectile(target, T) + A.firer = src + A.fired_from = src + A.fire() + return A + +/obj/machinery/porta_turret/proc/setState(on, mode, shoot_cyborgs) + if(controllock) + return + + shoot_cyborgs ? (turret_flags |= TURRET_FLAG_SHOOT_BORGS) : (turret_flags &= ~TURRET_FLAG_SHOOT_BORGS) + toggle_on(on) + src.mode = mode + power_change() + +/datum/action/turret_toggle + name = "Toggle Mode" + icon_icon = 'icons/mob/actions/actions_mecha.dmi' + button_icon_state = "mech_cycle_equip_off" + +/datum/action/turret_toggle/Trigger() + var/obj/machinery/porta_turret/P = target + if(!istype(P)) + return + P.setState(P.on,!P.mode) + +/datum/action/turret_quit + name = "Release Control" + icon_icon = 'icons/mob/actions/actions_mecha.dmi' + button_icon_state = "mech_eject" + +/datum/action/turret_quit/Trigger() + var/obj/machinery/porta_turret/P = target + if(!istype(P)) + return + P.remove_control(FALSE) + +/obj/machinery/porta_turret/proc/give_control(mob/A) + if(manual_control || !can_interact(A)) + return FALSE + remote_controller = A + if(!quit_action) + quit_action = new(src) + quit_action.Grant(remote_controller) + if(!toggle_action) + toggle_action = new(src) + toggle_action.Grant(remote_controller) + remote_controller.reset_perspective(src) + remote_controller.click_intercept = src + manual_control = TRUE + always_up = TRUE + popUp() + return TRUE + +/obj/machinery/porta_turret/proc/remove_control(warning_message = TRUE) + if(!manual_control) + return FALSE + if(remote_controller) + if(warning_message) + to_chat(remote_controller, "Your uplink to [src] has been severed!") + quit_action.Remove(remote_controller) + toggle_action.Remove(remote_controller) + remote_controller.click_intercept = null + remote_controller.reset_perspective() + always_up = initial(always_up) + manual_control = FALSE + remote_controller = null + return TRUE + +/obj/machinery/porta_turret/proc/InterceptClickOn(mob/living/caller, params, atom/A) + if(!manual_control) + return FALSE + if(!can_interact(caller)) + remove_control() + return FALSE + log_combat(caller,A,"fired with manual turret control at") + target(A) + return TRUE + +/obj/machinery/porta_turret/syndicate + installation = null + always_up = 1 + use_power = NO_POWER_USE + has_cover = 0 + scan_range = 9 + req_access = list(ACCESS_SYNDICATE) + mode = TURRET_LETHAL + stun_projectile = /obj/projectile/bullet + lethal_projectile = /obj/projectile/bullet + lethal_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' + stun_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' + icon_state = "syndie_off" + base_icon_state = "syndie" + faction = list(ROLE_SYNDICATE) + desc = "A ballistic machine gun auto-turret." + +/obj/machinery/porta_turret/syndicate/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + +/obj/machinery/porta_turret/syndicate/setup() + return + +/obj/machinery/porta_turret/syndicate/assess_perp(mob/living/carbon/human/perp) + return 10 //Syndicate turrets shoot everything not in their faction + +/obj/machinery/porta_turret/syndicate/energy + icon_state = "standard_lethal" + base_icon_state = "standard" + stun_projectile = /obj/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.ogg' + lethal_projectile = /obj/projectile/beam/laser + lethal_projectile_sound = 'sound/weapons/laser.ogg' + desc = "An energy blaster auto-turret." + +/obj/machinery/porta_turret/syndicate/energy/heavy + icon_state = "standard_lethal" + base_icon_state = "standard" + stun_projectile = /obj/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.ogg' + lethal_projectile = /obj/projectile/beam/laser/heavylaser + lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg' + desc = "An energy blaster auto-turret." + +/obj/machinery/porta_turret/syndicate/energy/raven + stun_projectile = /obj/projectile/beam/laser + stun_projectile_sound = 'sound/weapons/laser.ogg' + faction = list("neutral","silicon","turret") + +/obj/machinery/porta_turret/syndicate/pod + integrity_failure = 0.5 + max_integrity = 40 + stun_projectile = /obj/projectile/bullet/syndicate_turret + lethal_projectile = /obj/projectile/bullet/syndicate_turret + +/obj/machinery/porta_turret/syndicate/shuttle + scan_range = 9 + shot_delay = 3 + stun_projectile = /obj/projectile/bullet/p50/penetrator/shuttle + lethal_projectile = /obj/projectile/bullet/p50/penetrator/shuttle + lethal_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' + stun_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' + armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 80, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + +/obj/machinery/porta_turret/syndicate/shuttle/target(atom/movable/target) + if(target) + setDir(get_dir(base, target))//even if you can't shoot, follow the target + shootAt(target) + addtimer(CALLBACK(src, .proc/shootAt, target), 5) + addtimer(CALLBACK(src, .proc/shootAt, target), 10) + addtimer(CALLBACK(src, .proc/shootAt, target), 15) + return TRUE + +/obj/machinery/porta_turret/ai + faction = list("silicon") + turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS | TURRET_FLAG_SHOOT_HEADS + +/obj/machinery/porta_turret/ai/assess_perp(mob/living/carbon/human/perp) + return 10 //AI turrets shoot at everything not in their faction + +/obj/machinery/porta_turret/aux_base + name = "perimeter defense turret" + desc = "A plasma beam turret calibrated to defend outposts against non-humanoid fauna. It is more effective when exposed to the environment." + installation = null + lethal_projectile = /obj/projectile/plasma/turret + lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' + mode = TURRET_LETHAL //It would be useless in stun mode anyway + faction = list("neutral","silicon","turret") //Minebots, medibots, etc that should not be shot. + +/obj/machinery/porta_turret/aux_base/assess_perp(mob/living/carbon/human/perp) + return 0 //Never shoot humanoids. You are on your own if Ashwalkers or the like attack! + +/obj/machinery/porta_turret/aux_base/setup() + return + +/obj/machinery/porta_turret/aux_base/interact(mob/user) //Controlled solely from the base console. + return + +/obj/machinery/porta_turret/aux_base/Initialize() + . = ..() + cover.name = name + cover.desc = desc + +/obj/machinery/porta_turret/centcom_shuttle + installation = null + max_integrity = 260 + always_up = 1 + use_power = NO_POWER_USE + has_cover = 0 + scan_range = 9 + stun_projectile = /obj/projectile/beam/laser + lethal_projectile = /obj/projectile/beam/laser + lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' + stun_projectile_sound = 'sound/weapons/plasma_cutter.ogg' + icon_state = "syndie_off" + base_icon_state = "syndie" + faction = list("neutral","silicon","turret") + mode = TURRET_LETHAL + +/obj/machinery/porta_turret/centcom_shuttle/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + +/obj/machinery/porta_turret/centcom_shuttle/assess_perp(mob/living/carbon/human/perp) + return 0 + +/obj/machinery/porta_turret/centcom_shuttle/setup() + return + +/obj/machinery/porta_turret/centcom_shuttle/weak + max_integrity = 120 + integrity_failure = 0.5 + name = "Old Laser Turret" + desc = "A turret built with substandard parts and run down further with age. Still capable of delivering lethal lasers to the odd space carp, but not much else." + stun_projectile = /obj/projectile/beam/weak/penetrator + lethal_projectile = /obj/projectile/beam/weak/penetrator + faction = list("neutral","silicon","turret") + +//////////////////////// +//Turret Control Panel// +//////////////////////// + +/obj/machinery/turretid + name = "turret control panel" + desc = "Used to control a room's automated defenses." + icon = 'icons/obj/machines/turret_control.dmi' + icon_state = "control_standby" + density = FALSE + req_access = list(ACCESS_AI_UPLOAD) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + ui_x = 305 + ui_y = 172 + /// Variable dictating if linked turrets are active and will shoot targets + var/enabled = TRUE + /// Variable dictating if linked turrets will shoot lethal projectiles + var/lethal = FALSE + /// Variable dictating if the panel is locked, preventing changes to turret settings + var/locked = TRUE + /// An area in which linked turrets are located, it can be an area name, path or nothing + var/control_area = null + /// AI is unable to use this machine if set to TRUE + var/ailock = FALSE + /// Variable dictating if linked turrets will shoot cyborgs + var/shoot_cyborgs = FALSE + /// List of all linked turrets + var/list/turrets = list() + +/obj/machinery/turretid/Initialize(mapload, ndir = 0, built = 0) + . = ..() + if(built) + setDir(ndir) + locked = FALSE + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + power_change() //Checks power and initial settings + +/obj/machinery/turretid/Destroy() + turrets.Cut() + return ..() + +/obj/machinery/turretid/Initialize(mapload) //map-placed turrets autolink turrets + . = ..() + if(!mapload) + return + + if(control_area) + control_area = get_area_instance_from_text(control_area) + if(control_area == null) + control_area = get_area(src) + stack_trace("Bad control_area path for [src], [src.control_area]") + else if(!control_area) + control_area = get_area(src) + + for(var/obj/machinery/porta_turret/T in control_area) + turrets |= T + T.cp = src + +/obj/machinery/turretid/examine(mob/user) + . += ..() + if(issilicon(user) && !(machine_stat & BROKEN)) + . += {"Ctrl-click [src] to [ enabled ? "disable" : "enable"] turrets. + Alt-click [src] to set turrets to [ lethal ? "stun" : "kill"]."} + +/obj/machinery/turretid/attackby(obj/item/I, mob/user, params) + if(machine_stat & BROKEN) + return + + if(I.tool_behaviour == TOOL_MULTITOOL) + if(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + if(M.buffer && istype(M.buffer, /obj/machinery/porta_turret)) + turrets |= M.buffer + to_chat(user, "You link \the [M.buffer] with \the [src].") + return + + if (issilicon(user)) + return attack_hand(user) + + if ( get_dist(src, user) == 0 ) // trying to unlock the interface + if (allowed(usr)) + if(obj_flags & EMAGGED) + to_chat(user, "The turret control is unresponsive!") + return + + locked = !locked + to_chat(user, "You [ locked ? "lock" : "unlock"] the panel.") + else + to_chat(user, "Access denied.") + +/obj/machinery/turretid/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + to_chat(user, "You short out the turret controls' access analysis module.") + obj_flags |= EMAGGED + locked = FALSE + +/obj/machinery/turretid/attack_ai(mob/user) + if(!ailock || IsAdminGhost(user)) + return attack_hand(user) + else + to_chat(user, "There seems to be a firewall preventing you from accessing this device!") + +/obj/machinery/turretid/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "TurretControl", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/turretid/ui_data(mob/user) + var/list/data = list() + data["locked"] = locked + data["siliconUser"] = user.has_unlimited_silicon_privilege + data["enabled"] = enabled + data["lethal"] = lethal + data["shootCyborgs"] = shoot_cyborgs + return data + +/obj/machinery/turretid/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("lock") + if(!usr.has_unlimited_silicon_privilege) + return + if((obj_flags & EMAGGED) || (machine_stat & BROKEN)) + to_chat(usr, "The turret control is unresponsive!") + return + locked = !locked + return TRUE + if("power") + toggle_on(usr) + return TRUE + if("mode") + toggle_lethal(usr) + return TRUE + if("shoot_silicons") + shoot_silicons(usr) + return TRUE + +/obj/machinery/turretid/proc/toggle_lethal(mob/user) + lethal = !lethal + add_hiddenprint(user) + log_combat(user, src, "[lethal ? "enabled" : "disabled"] lethals on") + updateTurrets() + +/obj/machinery/turretid/proc/toggle_on(mob/user) + enabled = !enabled + add_hiddenprint(user) + log_combat(user, src, "[enabled ? "enabled" : "disabled"]") + updateTurrets() + +/obj/machinery/turretid/proc/shoot_silicons(mob/user) + shoot_cyborgs = !shoot_cyborgs + add_hiddenprint(user) + log_combat(user, src, "[shoot_cyborgs ? "Shooting Borgs" : "Not Shooting Borgs"]") + updateTurrets() + +/obj/machinery/turretid/proc/updateTurrets() + for (var/obj/machinery/porta_turret/aTurret in turrets) + aTurret.setState(enabled, lethal, shoot_cyborgs) + update_icon() + +/obj/machinery/turretid/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "control_off" + else if (enabled) + if (lethal) + icon_state = "control_kill" + else + icon_state = "control_stun" + else + icon_state = "control_standby" + +/obj/item/wallframe/turret_control + name = "turret control frame" + desc = "Used for building turret control panels." + icon_state = "apc" + result_path = /obj/machinery/turretid + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) + +/obj/item/gun/proc/get_turret_properties() + . = list() + .["lethal_projectile"] = null + .["lethal_projectile_sound"] = null + .["stun_projectile"] = null + .["stun_projectile_sound"] = null + .["base_icon_state"] = "standard" + +/obj/item/gun/energy/get_turret_properties() + . = ..() + + var/obj/item/ammo_casing/primary_ammo = ammo_type[1] + + .["stun_projectile"] = initial(primary_ammo.projectile_type) + .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) + + if(ammo_type.len > 1) + var/obj/item/ammo_casing/secondary_ammo = ammo_type[2] + .["lethal_projectile"] = initial(secondary_ammo.projectile_type) + .["lethal_projectile_sound"] = initial(secondary_ammo.fire_sound) + else + .["lethal_projectile"] = .["stun_projectile"] + .["lethal_projectile_sound"] = .["stun_projectile_sound"] + +/obj/item/gun/ballistic/get_turret_properties() + . = ..() + var/obj/item/ammo_box/mag = mag_type + var/obj/item/ammo_casing/primary_ammo = initial(mag.ammo_type) + + .["base_icon_state"] = "syndie" + .["stun_projectile"] = initial(primary_ammo.projectile_type) + .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) + .["lethal_projectile"] = .["stun_projectile"] + .["lethal_projectile_sound"] = .["stun_projectile_sound"] + + +/obj/item/gun/energy/laser/bluetag/get_turret_properties() + . = ..() + .["stun_projectile"] = /obj/projectile/beam/lasertag/bluetag + .["lethal_projectile"] = /obj/projectile/beam/lasertag/bluetag + .["base_icon_state"] = "blue" + .["shot_delay"] = 30 + .["team_color"] = "blue" + +/obj/item/gun/energy/laser/redtag/get_turret_properties() + . = ..() + .["stun_projectile"] = /obj/projectile/beam/lasertag/redtag + .["lethal_projectile"] = /obj/projectile/beam/lasertag/redtag + .["base_icon_state"] = "red" + .["shot_delay"] = 30 + .["team_color"] = "red" + +/obj/item/gun/energy/e_gun/turret/get_turret_properties() + . = ..() + +/obj/machinery/porta_turret/lasertag + req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) + turret_flags = TURRET_FLAG_AUTH_WEAPONS + ui_y = 115 + var/team_color + +/obj/machinery/porta_turret/lasertag/assess_perp(mob/living/carbon/human/perp) + . = 0 + if(team_color == "blue") //Lasertag turrets target the opposing team, how great is that? -Sieve + . = 0 //But does not target anyone else + if(istype(perp.wear_suit, /obj/item/clothing/suit/redtag)) + . += 4 + if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) + . += 4 + if(istype(perp.belt, /obj/item/gun/energy/laser/redtag)) + . += 2 + + if(team_color == "red") + . = 0 + if(istype(perp.wear_suit, /obj/item/clothing/suit/bluetag)) + . += 4 + if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) + . += 4 + if(istype(perp.belt, /obj/item/gun/energy/laser/bluetag)) + . += 2 + +/obj/machinery/porta_turret/lasertag/setup(obj/item/gun/gun) + var/list/properties = ..() + if(properties["team_color"]) + team_color = properties["team_color"] + +/obj/machinery/porta_turret/lasertag/ui_status(mob/user) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(team_color == "blue" && istype(H.wear_suit, /obj/item/clothing/suit/redtag)) + return UI_CLOSE + if(team_color == "red" && istype(H.wear_suit, /obj/item/clothing/suit/bluetag)) + return UI_CLOSE + return ..() + +//lasertag presets +/obj/machinery/porta_turret/lasertag/red + installation = /obj/item/gun/energy/laser/redtag + team_color = "red" + +/obj/machinery/porta_turret/lasertag/blue + installation = /obj/item/gun/energy/laser/bluetag + team_color = "blue" + +/obj/machinery/porta_turret/lasertag/bullet_act(obj/projectile/P) + . = ..() + if(on) + if(team_color == "blue") + if(istype(P, /obj/projectile/beam/lasertag/redtag)) + toggle_on(FALSE) + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 10 SECONDS) + else if(team_color == "red") + if(istype(P, /obj/projectile/beam/lasertag/bluetag)) + toggle_on(FALSE) + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 10 SECONDS) diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 945c71297bb..06c91991d95 100755 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -1,182 +1,182 @@ -/obj/machinery/recharger - name = "recharger" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "recharger" - desc = "A charging dock for energy based weaponry." - use_power = IDLE_POWER_USE - idle_power_usage = 4 - active_power_usage = 250 - circuit = /obj/item/circuitboard/machine/recharger - pass_flags = PASSTABLE - var/obj/item/charging = null - var/recharge_coeff = 1 - var/using_power = FALSE //Did we put power into "charging" last process()? - - var/static/list/allowed_devices = typecacheof(list( - /obj/item/gun/energy, - /obj/item/melee/baton, - /obj/item/ammo_box/magazine/recharge, - /obj/item/modular_computer)) - -/obj/machinery/recharger/RefreshParts() - for(var/obj/item/stock_parts/capacitor/C in component_parts) - recharge_coeff = C.rating - -/obj/machinery/recharger/examine(mob/user) - . = ..() - if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) - . += "You're too far away to examine [src]'s contents and display!" - return - - if(charging) - . += {"\The [src] contains: - - \A [charging]."} - - if(!(machine_stat & (NOPOWER|BROKEN))) - . += "The status display reads:" - . += "- Recharging [recharge_coeff*10]% cell charge per cycle." - if(charging) - var/obj/item/stock_parts/cell/C = charging.get_cell() - . += "- \The [charging]'s cell is at [C.percent()]%." - - -/obj/machinery/recharger/proc/setCharging(new_charging) - charging = new_charging - if (new_charging) - START_PROCESSING(SSmachines, src) - use_power = ACTIVE_POWER_USE - using_power = TRUE - update_icon() - else - use_power = IDLE_POWER_USE - using_power = FALSE - update_icon() - -/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) - if(G.tool_behaviour == TOOL_WRENCH) - if(charging) - to_chat(user, "Remove the charging item first!") - return - setAnchored(!anchored) - power_change() - to_chat(user, "You [anchored ? "attached" : "detached"] [src].") - G.play_tool_sound(src) - return - - var/allowed = is_type_in_typecache(G, allowed_devices) - - if(allowed) - if(anchored) - if(charging || panel_open) - return 1 - - //Checks to make sure he's not in space doing it, and that the area got proper power. - var/area/a = get_area(src) - if(!isarea(a) || a.power_equip == 0) - to_chat(user, "[src] blinks red as you try to insert [G].") - return 1 - - if (istype(G, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = G - if(!E.can_charge) - to_chat(user, "Your gun has no external power connector.") - return 1 - - if(!user.transferItemToLoc(G, src)) - return 1 - setCharging(G) - - else - to_chat(user, "[src] isn't connected to anything!") - return 1 - - if(anchored && !charging) - if(default_deconstruction_screwdriver(user, "recharger", "recharger", G)) - update_icon() - return - - if(panel_open && G.tool_behaviour == TOOL_CROWBAR) - default_deconstruction_crowbar(G) - return - - return ..() - -/obj/machinery/recharger/attack_hand(mob/user) - . = ..() - if(.) - return - - add_fingerprint(user) - if(charging) - charging.update_icon() - charging.forceMove(drop_location()) - user.put_in_hands(charging) - setCharging(null) - -/obj/machinery/recharger/attack_tk(mob/user) - if(charging) - charging.update_icon() - charging.forceMove(drop_location()) - setCharging(null) - -/obj/machinery/recharger/process() - if(machine_stat & (NOPOWER|BROKEN) || !anchored) - return PROCESS_KILL - - using_power = FALSE - if(charging) - var/obj/item/stock_parts/cell/C = charging.get_cell() - if(C) - if(C.charge < C.maxcharge) - C.give(C.chargerate * recharge_coeff) - use_power(250 * recharge_coeff) - using_power = TRUE - update_icon() - - if(istype(charging, /obj/item/ammo_box/magazine/recharge)) - var/obj/item/ammo_box/magazine/recharge/R = charging - if(R.stored_ammo.len < R.max_ammo) - R.stored_ammo += new R.ammo_type(R) - use_power(200 * recharge_coeff) - using_power = TRUE - update_icon() - return - else - return PROCESS_KILL - -/obj/machinery/recharger/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_CONTENTS) - return - if(!(machine_stat & (NOPOWER|BROKEN)) && anchored) - if(istype(charging, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = charging - if(E.cell) - E.cell.emp_act(severity) - - else if(istype(charging, /obj/item/melee/baton)) - var/obj/item/melee/baton/B = charging - if(B.cell) - B.cell.charge = 0 - -/obj/machinery/recharger/update_overlays() - . = ..() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - luminosity = 0 - if(machine_stat & (NOPOWER|BROKEN) || !anchored) - return - if(panel_open) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-open", layer, plane, dir, alpha) - return - - luminosity = 1 - if (charging) - if(using_power) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", layer, plane, dir, alpha) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - else - SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", layer, plane, dir, alpha) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - else - SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", layer, plane, dir, alpha) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) +/obj/machinery/recharger + name = "recharger" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "recharger" + desc = "A charging dock for energy based weaponry." + use_power = IDLE_POWER_USE + idle_power_usage = 4 + active_power_usage = 250 + circuit = /obj/item/circuitboard/machine/recharger + pass_flags = PASSTABLE + var/obj/item/charging = null + var/recharge_coeff = 1 + var/using_power = FALSE //Did we put power into "charging" last process()? + + var/static/list/allowed_devices = typecacheof(list( + /obj/item/gun/energy, + /obj/item/melee/baton, + /obj/item/ammo_box/magazine/recharge, + /obj/item/modular_computer)) + +/obj/machinery/recharger/RefreshParts() + for(var/obj/item/stock_parts/capacitor/C in component_parts) + recharge_coeff = C.rating + +/obj/machinery/recharger/examine(mob/user) + . = ..() + if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) + . += "You're too far away to examine [src]'s contents and display!" + return + + if(charging) + . += {"\The [src] contains: + - \A [charging]."} + + if(!(machine_stat & (NOPOWER|BROKEN))) + . += "The status display reads:" + . += "- Recharging [recharge_coeff*10]% cell charge per cycle." + if(charging) + var/obj/item/stock_parts/cell/C = charging.get_cell() + . += "- \The [charging]'s cell is at [C.percent()]%." + + +/obj/machinery/recharger/proc/setCharging(new_charging) + charging = new_charging + if (new_charging) + START_PROCESSING(SSmachines, src) + use_power = ACTIVE_POWER_USE + using_power = TRUE + update_icon() + else + use_power = IDLE_POWER_USE + using_power = FALSE + update_icon() + +/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) + if(G.tool_behaviour == TOOL_WRENCH) + if(charging) + to_chat(user, "Remove the charging item first!") + return + setAnchored(!anchored) + power_change() + to_chat(user, "You [anchored ? "attached" : "detached"] [src].") + G.play_tool_sound(src) + return + + var/allowed = is_type_in_typecache(G, allowed_devices) + + if(allowed) + if(anchored) + if(charging || panel_open) + return 1 + + //Checks to make sure he's not in space doing it, and that the area got proper power. + var/area/a = get_area(src) + if(!isarea(a) || a.power_equip == 0) + to_chat(user, "[src] blinks red as you try to insert [G].") + return 1 + + if (istype(G, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = G + if(!E.can_charge) + to_chat(user, "Your gun has no external power connector.") + return 1 + + if(!user.transferItemToLoc(G, src)) + return 1 + setCharging(G) + + else + to_chat(user, "[src] isn't connected to anything!") + return 1 + + if(anchored && !charging) + if(default_deconstruction_screwdriver(user, "recharger", "recharger", G)) + update_icon() + return + + if(panel_open && G.tool_behaviour == TOOL_CROWBAR) + default_deconstruction_crowbar(G) + return + + return ..() + +/obj/machinery/recharger/attack_hand(mob/user) + . = ..() + if(.) + return + + add_fingerprint(user) + if(charging) + charging.update_icon() + charging.forceMove(drop_location()) + user.put_in_hands(charging) + setCharging(null) + +/obj/machinery/recharger/attack_tk(mob/user) + if(charging) + charging.update_icon() + charging.forceMove(drop_location()) + setCharging(null) + +/obj/machinery/recharger/process() + if(machine_stat & (NOPOWER|BROKEN) || !anchored) + return PROCESS_KILL + + using_power = FALSE + if(charging) + var/obj/item/stock_parts/cell/C = charging.get_cell() + if(C) + if(C.charge < C.maxcharge) + C.give(C.chargerate * recharge_coeff) + use_power(250 * recharge_coeff) + using_power = TRUE + update_icon() + + if(istype(charging, /obj/item/ammo_box/magazine/recharge)) + var/obj/item/ammo_box/magazine/recharge/R = charging + if(R.stored_ammo.len < R.max_ammo) + R.stored_ammo += new R.ammo_type(R) + use_power(200 * recharge_coeff) + using_power = TRUE + update_icon() + return + else + return PROCESS_KILL + +/obj/machinery/recharger/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_CONTENTS) + return + if(!(machine_stat & (NOPOWER|BROKEN)) && anchored) + if(istype(charging, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = charging + if(E.cell) + E.cell.emp_act(severity) + + else if(istype(charging, /obj/item/melee/baton)) + var/obj/item/melee/baton/B = charging + if(B.cell) + B.cell.charge = 0 + +/obj/machinery/recharger/update_overlays() + . = ..() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + luminosity = 0 + if(machine_stat & (NOPOWER|BROKEN) || !anchored) + return + if(panel_open) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-open", layer, plane, dir, alpha) + return + + luminosity = 1 + if (charging) + if(using_power) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", layer, plane, dir, alpha) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + else + SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", layer, plane, dir, alpha) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + else + SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", layer, plane, dir, alpha) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm index 02828ef46c9..b3dbaab0a09 100644 --- a/code/game/machinery/rechargestation.dm +++ b/code/game/machinery/rechargestation.dm @@ -1,103 +1,103 @@ -/obj/machinery/recharge_station - name = "cyborg recharging station" - desc = "This device recharges cyborgs and resupplies them with materials." - icon = 'icons/obj/objects.dmi' - icon_state = "borgcharger0" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 1000 - req_access = list(ACCESS_ROBOTICS) - state_open = TRUE - circuit = /obj/item/circuitboard/machine/cyborgrecharger - occupant_typecache = list(/mob/living/silicon/robot, /mob/living/carbon/human) - var/recharge_speed - var/repairs - -/obj/machinery/recharge_station/Initialize() - . = ..() - update_icon() - -/obj/machinery/recharge_station/RefreshParts() - recharge_speed = 0 - repairs = 0 - for(var/obj/item/stock_parts/capacitor/C in component_parts) - recharge_speed += C.rating * 100 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - repairs += M.rating - 1 - for(var/obj/item/stock_parts/cell/C in component_parts) - recharge_speed *= C.maxcharge / 10000 - -/obj/machinery/recharge_station/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Recharging [recharge_speed]J per cycle." - if(repairs) - . += "[src] has been upgraded to support automatic repairs." - -/obj/machinery/recharge_station/process() - if(!is_operational()) - return - - if(occupant) - process_occupant() - return 1 - -/obj/machinery/recharge_station/relaymove(mob/user) - if(user.stat) - return - open_machine() - -/obj/machinery/recharge_station/emp_act(severity) - . = ..() - if(!(machine_stat & (BROKEN|NOPOWER))) - if(occupant && !(. & EMP_PROTECT_CONTENTS)) - occupant.emp_act(severity) - if (!(. & EMP_PROTECT_SELF)) - open_machine() - -/obj/machinery/recharge_station/attackby(obj/item/P, mob/user, params) - if(state_open) - if(default_deconstruction_screwdriver(user, "borgdecon2", "borgcharger0", P)) - return - - if(default_pry_open(P)) - return - - if(default_deconstruction_crowbar(P)) - return - return ..() - -/obj/machinery/recharge_station/interact(mob/user) - toggle_open() - return TRUE - -/obj/machinery/recharge_station/proc/toggle_open() - if(state_open) - close_machine() - else - open_machine() - -/obj/machinery/recharge_station/open_machine() - . = ..() - use_power = IDLE_POWER_USE - -/obj/machinery/recharge_station/close_machine() - . = ..() - if(occupant) - use_power = ACTIVE_POWER_USE //It always tries to charge, even if it can't. - add_fingerprint(occupant) - -/obj/machinery/recharge_station/update_icon_state() - if(is_operational()) - if(state_open) - icon_state = "borgcharger0" - else - icon_state = (occupant ? "borgcharger1" : "borgcharger2") - else - icon_state = (state_open ? "borgcharger-u0" : "borgcharger-u1") - -/obj/machinery/recharge_station/proc/process_occupant() - if(!occupant) - return - SEND_SIGNAL(occupant, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, recharge_speed, repairs) +/obj/machinery/recharge_station + name = "cyborg recharging station" + desc = "This device recharges cyborgs and resupplies them with materials." + icon = 'icons/obj/objects.dmi' + icon_state = "borgcharger0" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 1000 + req_access = list(ACCESS_ROBOTICS) + state_open = TRUE + circuit = /obj/item/circuitboard/machine/cyborgrecharger + occupant_typecache = list(/mob/living/silicon/robot, /mob/living/carbon/human) + var/recharge_speed + var/repairs + +/obj/machinery/recharge_station/Initialize() + . = ..() + update_icon() + +/obj/machinery/recharge_station/RefreshParts() + recharge_speed = 0 + repairs = 0 + for(var/obj/item/stock_parts/capacitor/C in component_parts) + recharge_speed += C.rating * 100 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + repairs += M.rating - 1 + for(var/obj/item/stock_parts/cell/C in component_parts) + recharge_speed *= C.maxcharge / 10000 + +/obj/machinery/recharge_station/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Recharging [recharge_speed]J per cycle." + if(repairs) + . += "[src] has been upgraded to support automatic repairs." + +/obj/machinery/recharge_station/process() + if(!is_operational()) + return + + if(occupant) + process_occupant() + return 1 + +/obj/machinery/recharge_station/relaymove(mob/user) + if(user.stat) + return + open_machine() + +/obj/machinery/recharge_station/emp_act(severity) + . = ..() + if(!(machine_stat & (BROKEN|NOPOWER))) + if(occupant && !(. & EMP_PROTECT_CONTENTS)) + occupant.emp_act(severity) + if (!(. & EMP_PROTECT_SELF)) + open_machine() + +/obj/machinery/recharge_station/attackby(obj/item/P, mob/user, params) + if(state_open) + if(default_deconstruction_screwdriver(user, "borgdecon2", "borgcharger0", P)) + return + + if(default_pry_open(P)) + return + + if(default_deconstruction_crowbar(P)) + return + return ..() + +/obj/machinery/recharge_station/interact(mob/user) + toggle_open() + return TRUE + +/obj/machinery/recharge_station/proc/toggle_open() + if(state_open) + close_machine() + else + open_machine() + +/obj/machinery/recharge_station/open_machine() + . = ..() + use_power = IDLE_POWER_USE + +/obj/machinery/recharge_station/close_machine() + . = ..() + if(occupant) + use_power = ACTIVE_POWER_USE //It always tries to charge, even if it can't. + add_fingerprint(occupant) + +/obj/machinery/recharge_station/update_icon_state() + if(is_operational()) + if(state_open) + icon_state = "borgcharger0" + else + icon_state = (occupant ? "borgcharger1" : "borgcharger2") + else + icon_state = (state_open ? "borgcharger-u0" : "borgcharger-u1") + +/obj/machinery/recharge_station/proc/process_occupant() + if(!occupant) + return + SEND_SIGNAL(occupant, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, recharge_speed, repairs) diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm index 989aa96a132..b041b9aca5c 100644 --- a/code/game/machinery/recycler.dm +++ b/code/game/machinery/recycler.dm @@ -1,202 +1,202 @@ -#define SAFETY_COOLDOWN 100 - -/obj/machinery/recycler - name = "recycler" - desc = "A large crushing machine used to recycle small items inefficiently. There are lights on the side." - icon = 'icons/obj/recycling.dmi' - icon_state = "grinder-o0" - layer = ABOVE_ALL_MOB_LAYER // Overhead - density = TRUE - circuit = /obj/item/circuitboard/machine/recycler - var/safety_mode = FALSE // Temporarily stops machine if it detects a mob - var/icon_name = "grinder-o" - var/blood = 0 - var/eat_dir = WEST - var/amount_produced = 50 - var/crush_damage = 1000 - var/eat_victim_items = TRUE - var/item_recycle_sound = 'sound/items/welder.ogg' - -/obj/machinery/recycler/Initialize() - AddComponent(/datum/component/butchering/recycler, 1, amount_produced,amount_produced/5) - AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/plasma, /datum/material/gold, /datum/material/diamond, /datum/material/plastic, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, FALSE, null, null, null, TRUE) - . = ..() - update_icon() - req_one_access = get_all_accesses() + get_all_centcom_access() - -/obj/machinery/recycler/RefreshParts() - var/amt_made = 0 - var/mat_mod = 0 - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - mat_mod = 2 * B.rating - mat_mod *= 50000 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - amt_made = 12.5 * M.rating //% of materials salvaged - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.max_amount = mat_mod - amount_produced = min(50, amt_made) + 50 - var/datum/component/butchering/butchering = GetComponent(/datum/component/butchering/recycler) - butchering.effectiveness = amount_produced - butchering.bonus_modifier = amount_produced/5 - -/obj/machinery/recycler/examine(mob/user) - . = ..() - . += "Reclaiming [amount_produced]% of materials salvaged." - . += {"The power light is [(machine_stat & NOPOWER) ? "off" : "on"]. - The safety-mode light is [safety_mode ? "on" : "off"]. - The safety-sensors status light is [obj_flags & EMAGGED ? "off" : "on"]."} - - -/obj/machinery/recycler/attackby(obj/item/I, mob/user, params) - if(default_deconstruction_screwdriver(user, "grinder-oOpen", "grinder-o0", I)) - return - - if(default_pry_open(I)) - return - - if(default_unfasten_wrench(user, I)) - return - - if(default_deconstruction_crowbar(I)) - return - return ..() - -/obj/machinery/recycler/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - if(safety_mode) - safety_mode = FALSE - update_icon() - playsound(src, "sparks", 75, TRUE, -1) - to_chat(user, "You use the cryptographic sequencer on [src].") - -/obj/machinery/recycler/update_icon_state() - ..() - var/is_powered = !(machine_stat & (BROKEN|NOPOWER)) - if(safety_mode) - is_powered = FALSE - icon_state = icon_name + "[is_powered]" + "[(blood ? "bld" : "")]" // add the blood tag at the end - -/obj/machinery/recycler/CanAllowThrough(atom/movable/AM) - . = ..() - if(!anchored) - return - var/move_dir = get_dir(loc, AM.loc) - if(move_dir == eat_dir) - return TRUE - -/obj/machinery/recycler/Crossed(atom/movable/AM) - eat(AM) - . = ..() - -/obj/machinery/recycler/proc/eat(atom/movable/AM0, sound=TRUE) - if(machine_stat & (BROKEN|NOPOWER)) - return - if(safety_mode) - return - if(!isturf(AM0.loc)) - return //I don't know how you called Crossed() but stop it. - - var/list/to_eat = AM0.GetAllContents() - - var/living_detected = FALSE //technically includes silicons as well but eh - var/list/nom = list() - var/list/crunchy_nom = list() //Mobs have to be handled differently so they get a different list instead of checking them multiple times. - - for(var/i in to_eat) - var/atom/movable/AM = i - if(istype(AM, /obj/item)) - var/obj/item/bodypart/head/as_head = AM - var/obj/item/mmi/as_mmi = AM - if(istype(AM, /obj/item/organ/brain) || (istype(as_head) && as_head.brain) || (istype(as_mmi) && as_mmi.brain) || istype(AM, /obj/item/dullahan_relay)) - living_detected = TRUE - nom += AM - else if(isliving(AM)) - living_detected = TRUE - crunchy_nom += AM - var/not_eaten = to_eat.len - nom.len - crunchy_nom.len - if(living_detected) // First, check if we have any living beings detected. - if(obj_flags & EMAGGED) - for(var/CRUNCH in crunchy_nom) // Eat them and keep going because we don't care about safety. - if(isliving(CRUNCH)) // MMIs and brains will get eaten like normal items - crush_living(CRUNCH) - else // Stop processing right now without eating anything. - emergency_stop() - return - for(var/nommed in nom) - recycle_item(nommed) - if(nom.len && sound) - playsound(src, item_recycle_sound, (50 + nom.len*5), TRUE, nom.len, ignore_walls = (nom.len - 10)) // As a substitute for playing 50 sounds at once. - if(not_eaten) - playsound(src, 'sound/machines/buzz-sigh.ogg', (50 + not_eaten*5), FALSE, not_eaten, ignore_walls = (not_eaten - 10)) // Ditto. - if(!ismob(AM0)) - AM0.moveToNullspace() - qdel(AM0) - else // Lets not move a mob to nullspace and qdel it, yes? - for(var/i in AM0.contents) - var/atom/movable/content = i - content.moveToNullspace() - qdel(content) - -/obj/machinery/recycler/proc/recycle_item(obj/item/I) - - var/obj/item/grown/log/L = I - if(istype(L)) - var/seed_modifier = 0 - if(L.seed) - seed_modifier = round(L.seed.potency / 25) - new L.plank_type(loc, 1 + seed_modifier) - else - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/material_amount = materials.get_item_material_amount(I) - if(!material_amount) - return - materials.insert_item(I, material_amount, multiplier = (amount_produced / 100)) - materials.retrieve_all() - - -/obj/machinery/recycler/proc/emergency_stop() - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) - safety_mode = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN) - -/obj/machinery/recycler/proc/reboot() - playsound(src, 'sound/machines/ping.ogg', 50, FALSE) - safety_mode = FALSE - update_icon() - -/obj/machinery/recycler/proc/crush_living(mob/living/L) - - L.forceMove(loc) - - if(issilicon(L)) - playsound(src, 'sound/items/welder.ogg', 50, TRUE) - else - playsound(src, 'sound/effects/splat.ogg', 50, TRUE) - - if(iscarbon(L)) - if(L.stat == CONSCIOUS) - L.say("ARRRRRRRRRRRGH!!!", forced="recycler grinding") - add_mob_blood(L) - - if(!blood && !issilicon(L)) - blood = TRUE - update_icon() - - // Instantly lie down, also go unconscious from the pain, before you die. - L.Unconscious(100) - L.adjustBruteLoss(crush_damage) - -/obj/machinery/recycler/deathtrap - name = "dangerous old crusher" - obj_flags = CAN_BE_HIT | EMAGGED - crush_damage = 120 - flags_1 = NODECONSTRUCT_1 - -/obj/item/paper/guides/recycler - name = "paper - 'garbage duty instructions'" - info = "

                    New Assignment

                    You have been assigned to collect garbage from trash bins, located around the station. The crewmembers will put their trash into it and you will collect the said trash.

                    There is a recycling machine near your closet, inside maintenance; use it to recycle the trash for a small chance to get useful minerals. Then deliver these minerals to cargo or engineering. You are our last hope for a clean station, do not screw this up!" - -#undef SAFETY_COOLDOWN +#define SAFETY_COOLDOWN 100 + +/obj/machinery/recycler + name = "recycler" + desc = "A large crushing machine used to recycle small items inefficiently. There are lights on the side." + icon = 'icons/obj/recycling.dmi' + icon_state = "grinder-o0" + layer = ABOVE_ALL_MOB_LAYER // Overhead + density = TRUE + circuit = /obj/item/circuitboard/machine/recycler + var/safety_mode = FALSE // Temporarily stops machine if it detects a mob + var/icon_name = "grinder-o" + var/blood = 0 + var/eat_dir = WEST + var/amount_produced = 50 + var/crush_damage = 1000 + var/eat_victim_items = TRUE + var/item_recycle_sound = 'sound/items/welder.ogg' + +/obj/machinery/recycler/Initialize() + AddComponent(/datum/component/butchering/recycler, 1, amount_produced,amount_produced/5) + AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/plasma, /datum/material/gold, /datum/material/diamond, /datum/material/plastic, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, FALSE, null, null, null, TRUE) + . = ..() + update_icon() + req_one_access = get_all_accesses() + get_all_centcom_access() + +/obj/machinery/recycler/RefreshParts() + var/amt_made = 0 + var/mat_mod = 0 + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + mat_mod = 2 * B.rating + mat_mod *= 50000 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + amt_made = 12.5 * M.rating //% of materials salvaged + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.max_amount = mat_mod + amount_produced = min(50, amt_made) + 50 + var/datum/component/butchering/butchering = GetComponent(/datum/component/butchering/recycler) + butchering.effectiveness = amount_produced + butchering.bonus_modifier = amount_produced/5 + +/obj/machinery/recycler/examine(mob/user) + . = ..() + . += "Reclaiming [amount_produced]% of materials salvaged." + . += {"The power light is [(machine_stat & NOPOWER) ? "off" : "on"]. + The safety-mode light is [safety_mode ? "on" : "off"]. + The safety-sensors status light is [obj_flags & EMAGGED ? "off" : "on"]."} + + +/obj/machinery/recycler/attackby(obj/item/I, mob/user, params) + if(default_deconstruction_screwdriver(user, "grinder-oOpen", "grinder-o0", I)) + return + + if(default_pry_open(I)) + return + + if(default_unfasten_wrench(user, I)) + return + + if(default_deconstruction_crowbar(I)) + return + return ..() + +/obj/machinery/recycler/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + if(safety_mode) + safety_mode = FALSE + update_icon() + playsound(src, "sparks", 75, TRUE, -1) + to_chat(user, "You use the cryptographic sequencer on [src].") + +/obj/machinery/recycler/update_icon_state() + ..() + var/is_powered = !(machine_stat & (BROKEN|NOPOWER)) + if(safety_mode) + is_powered = FALSE + icon_state = icon_name + "[is_powered]" + "[(blood ? "bld" : "")]" // add the blood tag at the end + +/obj/machinery/recycler/CanAllowThrough(atom/movable/AM) + . = ..() + if(!anchored) + return + var/move_dir = get_dir(loc, AM.loc) + if(move_dir == eat_dir) + return TRUE + +/obj/machinery/recycler/Crossed(atom/movable/AM) + eat(AM) + . = ..() + +/obj/machinery/recycler/proc/eat(atom/movable/AM0, sound=TRUE) + if(machine_stat & (BROKEN|NOPOWER)) + return + if(safety_mode) + return + if(!isturf(AM0.loc)) + return //I don't know how you called Crossed() but stop it. + + var/list/to_eat = AM0.GetAllContents() + + var/living_detected = FALSE //technically includes silicons as well but eh + var/list/nom = list() + var/list/crunchy_nom = list() //Mobs have to be handled differently so they get a different list instead of checking them multiple times. + + for(var/i in to_eat) + var/atom/movable/AM = i + if(istype(AM, /obj/item)) + var/obj/item/bodypart/head/as_head = AM + var/obj/item/mmi/as_mmi = AM + if(istype(AM, /obj/item/organ/brain) || (istype(as_head) && as_head.brain) || (istype(as_mmi) && as_mmi.brain) || istype(AM, /obj/item/dullahan_relay)) + living_detected = TRUE + nom += AM + else if(isliving(AM)) + living_detected = TRUE + crunchy_nom += AM + var/not_eaten = to_eat.len - nom.len - crunchy_nom.len + if(living_detected) // First, check if we have any living beings detected. + if(obj_flags & EMAGGED) + for(var/CRUNCH in crunchy_nom) // Eat them and keep going because we don't care about safety. + if(isliving(CRUNCH)) // MMIs and brains will get eaten like normal items + crush_living(CRUNCH) + else // Stop processing right now without eating anything. + emergency_stop() + return + for(var/nommed in nom) + recycle_item(nommed) + if(nom.len && sound) + playsound(src, item_recycle_sound, (50 + nom.len*5), TRUE, nom.len, ignore_walls = (nom.len - 10)) // As a substitute for playing 50 sounds at once. + if(not_eaten) + playsound(src, 'sound/machines/buzz-sigh.ogg', (50 + not_eaten*5), FALSE, not_eaten, ignore_walls = (not_eaten - 10)) // Ditto. + if(!ismob(AM0)) + AM0.moveToNullspace() + qdel(AM0) + else // Lets not move a mob to nullspace and qdel it, yes? + for(var/i in AM0.contents) + var/atom/movable/content = i + content.moveToNullspace() + qdel(content) + +/obj/machinery/recycler/proc/recycle_item(obj/item/I) + + var/obj/item/grown/log/L = I + if(istype(L)) + var/seed_modifier = 0 + if(L.seed) + seed_modifier = round(L.seed.potency / 25) + new L.plank_type(loc, 1 + seed_modifier) + else + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/material_amount = materials.get_item_material_amount(I) + if(!material_amount) + return + materials.insert_item(I, material_amount, multiplier = (amount_produced / 100)) + materials.retrieve_all() + + +/obj/machinery/recycler/proc/emergency_stop() + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + safety_mode = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN) + +/obj/machinery/recycler/proc/reboot() + playsound(src, 'sound/machines/ping.ogg', 50, FALSE) + safety_mode = FALSE + update_icon() + +/obj/machinery/recycler/proc/crush_living(mob/living/L) + + L.forceMove(loc) + + if(issilicon(L)) + playsound(src, 'sound/items/welder.ogg', 50, TRUE) + else + playsound(src, 'sound/effects/splat.ogg', 50, TRUE) + + if(iscarbon(L)) + if(L.stat == CONSCIOUS) + L.say("ARRRRRRRRRRRGH!!!", forced="recycler grinding") + add_mob_blood(L) + + if(!blood && !issilicon(L)) + blood = TRUE + update_icon() + + // Instantly lie down, also go unconscious from the pain, before you die. + L.Unconscious(100) + L.adjustBruteLoss(crush_damage) + +/obj/machinery/recycler/deathtrap + name = "dangerous old crusher" + obj_flags = CAN_BE_HIT | EMAGGED + crush_damage = 120 + flags_1 = NODECONSTRUCT_1 + +/obj/item/paper/guides/recycler + name = "paper - 'garbage duty instructions'" + info = "

                    New Assignment

                    You have been assigned to collect garbage from trash bins, located around the station. The crewmembers will put their trash into it and you will collect the said trash.

                    There is a recycling machine near your closet, inside maintenance; use it to recycle the trash for a small chance to get useful minerals. Then deliver these minerals to cargo or engineering. You are our last hope for a clean station, do not screw this up!" + +#undef SAFETY_COOLDOWN diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm index 783da0c5eaa..0fd660d219d 100644 --- a/code/game/machinery/requests_console.dm +++ b/code/game/machinery/requests_console.dm @@ -1,461 +1,461 @@ -/******************** Requests Console ********************/ -/** Originally written by errorage, updated by: Carn, needs more work though. I just added some security fixes */ - -GLOBAL_LIST_EMPTY(req_console_assistance) -GLOBAL_LIST_EMPTY(req_console_supplies) -GLOBAL_LIST_EMPTY(req_console_information) -GLOBAL_LIST_EMPTY(allConsoles) -GLOBAL_LIST_EMPTY(req_console_ckey_departments) - - -#define REQ_SCREEN_MAIN 0 -#define REQ_SCREEN_REQ_ASSISTANCE 1 -#define REQ_SCREEN_REQ_SUPPLIES 2 -#define REQ_SCREEN_RELAY 3 -#define REQ_SCREEN_WRITE 4 -#define REQ_SCREEN_CHOOSE 5 -#define REQ_SCREEN_SENT 6 -#define REQ_SCREEN_ERR 7 -#define REQ_SCREEN_VIEW_MSGS 8 -#define REQ_SCREEN_AUTHENTICATE 9 -#define REQ_SCREEN_ANNOUNCE 10 - -#define REQ_EMERGENCY_SECURITY 1 -#define REQ_EMERGENCY_ENGINEERING 2 -#define REQ_EMERGENCY_MEDICAL 3 - -/obj/machinery/requests_console - name = "requests console" - desc = "A console intended to send requests to different departments on the station." - icon = 'icons/obj/terminals.dmi' - icon_state = "req_comp0" - var/department = "Unknown" //The list of all departments on the station (Determined from this variable on each unit) Set this to the same thing if you want several consoles in one department - var/list/messages = list() //List of all messages - var/departmentType = 0 //bitflag - // 0 = none (not listed, can only replied to) - // assistance = 1 - // supplies = 2 - // info = 4 - // assistance + supplies = 3 - // assistance + info = 5 - // supplies + info = 6 - // assistance + supplies + info = 7 - var/newmessagepriority = REQ_NO_NEW_MESSAGE - var/screen = REQ_SCREEN_MAIN - // 0 = main menu, - // 1 = req. assistance, - // 2 = req. supplies - // 3 = relay information - // 4 = write msg - not used - // 5 = choose priority - not used - // 6 = sent successfully - // 7 = sent unsuccessfully - // 8 = view messages - // 9 = authentication before sending - // 10 = send announcement - var/silent = FALSE // set to 1 for it not to beep all the time - var/hackState = FALSE - var/announcementConsole = FALSE // FALSE = This console cannot be used to send department announcements, TRUE = This console can send department announcements - var/open = FALSE // TRUE if open - var/announceAuth = FALSE //Will be set to 1 when you authenticate yourself for announcements - var/msgVerified = "" //Will contain the name of the person who verified it - var/msgStamped = "" //If a message is stamped, this will contain the stamp name - var/message = "" - var/to_department = "" //the department which will be receiving the message - var/priority = REQ_NO_NEW_MESSAGE //Priority of the message being sent - var/obj/item/radio/Radio - var/emergency //If an emergency has been called by this device. Acts as both a cooldown and lets the responder know where it the emergency was triggered from - var/receive_ore_updates = FALSE //If ore redemption machines will send an update when it receives new ores. - max_integrity = 300 - armor = list("melee" = 70, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - -/obj/machinery/requests_console/update_icon_state() - if(machine_stat & NOPOWER) - set_light(0) - else - set_light(1.4,0.7,"#34D352")//green light - if(open) - if(!hackState) - icon_state="req_comp_open" - else - icon_state="req_comp_rewired" - else if(machine_stat & NOPOWER) - if(icon_state != "req_comp_off") - icon_state = "req_comp_off" - else - if(emergency || (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY)) - icon_state = "req_comp3" - else if(newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) - icon_state = "req_comp2" - else if(newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) - icon_state = "req_comp1" - else - icon_state = "req_comp0" - -/obj/machinery/requests_console/Initialize() - . = ..() - name = "\improper [department] requests console" - GLOB.allConsoles += src - - if(departmentType) - - if((departmentType & REQ_DEP_TYPE_ASSISTANCE) && !(department in GLOB.req_console_assistance)) - GLOB.req_console_assistance += department - - if((departmentType & REQ_DEP_TYPE_SUPPLIES) && !(department in GLOB.req_console_supplies)) - GLOB.req_console_supplies += department - - if((departmentType & REQ_DEP_TYPE_INFORMATION) && !(department in GLOB.req_console_information)) - GLOB.req_console_information += department - - GLOB.req_console_ckey_departments[ckey(department)] = department - - Radio = new /obj/item/radio(src) - Radio.listening = 0 - -/obj/machinery/requests_console/Destroy() - QDEL_NULL(Radio) - GLOB.allConsoles -= src - return ..() - -/obj/machinery/requests_console/ui_interact(mob/user) - . = ..() - var/dat = "" - if(!open) - switch(screen) - if(REQ_SCREEN_MAIN) - announceAuth = FALSE - if (newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) - dat += "
                    There are new messages

                    " - else if (newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) - dat += "
                    There are new PRIORITY messages

                    " - else if (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY) - dat += "
                    There are new EXTREME PRIORITY messages

                    " - dat += "View Messages

                    " - - dat += "Request Assistance
                    " - dat += "Request Supplies
                    " - dat += "Relay Anonymous Information

                    " - - if(!emergency) - dat += "Emergency: Security
                    " - dat += "Emergency: Engineering
                    " - dat += "Emergency: Medical

                    " - else - dat += "[emergency] has been dispatched to this location.

                    " - - if(announcementConsole) - dat += "Send Station-wide Announcement

                    " - if (silent) - dat += "Speaker OFF" - else - dat += "Speaker ON" - if(REQ_SCREEN_REQ_ASSISTANCE) - dat += "Which department do you need assistance from?

                    " - dat += departments_table(GLOB.req_console_assistance) - - if(REQ_SCREEN_REQ_SUPPLIES) - dat += "Which department do you need supplies from?

                    " - dat += departments_table(GLOB.req_console_supplies) - - if(REQ_SCREEN_RELAY) - dat += "Which department would you like to send information to?

                    " - dat += departments_table(GLOB.req_console_information) - - if(REQ_SCREEN_SENT) - dat += "Message sent.

                    " - dat += "<< Back
                    " - - if(REQ_SCREEN_ERR) - dat += "An error occurred.

                    " - dat += "<< Back
                    " - - if(REQ_SCREEN_VIEW_MSGS) - for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) - if (Console.department == department) - Console.newmessagepriority = REQ_NO_NEW_MESSAGE - Console.update_icon() - - newmessagepriority = REQ_NO_NEW_MESSAGE - update_icon() - var/messageComposite = "" - for(var/msg in messages) // This puts more recent messages at the *top*, where they belong. - messageComposite = "
                    [msg]
                    " + messageComposite - dat += messageComposite - dat += "
                    << Back to Main Menu
                    " - - if(REQ_SCREEN_AUTHENTICATE) - dat += "Message Authentication

                    " - dat += "Message for [to_department]: [message]

                    " - dat += "
                    You may authenticate your message now by scanning your ID or your stamp

                    " - dat += "Validated by: [msgVerified ? msgVerified : "Not Validated"]
                    " - dat += "Stamped by: [msgStamped ? msgStamped : "Not Stamped"]

                    " - dat += "Send Message
                    " - dat += "
                    << Discard Message
                    " - - if(REQ_SCREEN_ANNOUNCE) - dat += "

                    Station-wide Announcement

                    " - if(announceAuth) - dat += "
                    Authentication accepted

                    " - else - dat += "
                    Swipe your card to authenticate yourself

                    " - dat += "Message: [message ? message : "No Message"]
                    " - dat += "[message ? "Edit" : "Write"] Message

                    " - if ((announceAuth || IsAdminGhost(user)) && message) - dat += "Announce Message
                    " - else - dat += "Announce Message
                    " - dat += "
                    << Back
                    " - - if(!dat) - CRASH("No UI for src. Screen var is: [screen]") - var/datum/browser/popup = new(user, "req_console", "[department] Requests Console", 450, 440) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - return - -/obj/machinery/requests_console/proc/departments_table(list/req_consoles) - var/dat = "" - dat += "" - for(var/req_dpt in req_consoles) - if (req_dpt != department) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
                    [req_dpt]Normal High" - if(hackState) - dat += "EXTREME" - dat += "
                    " - dat += "
                    << Back
                    " - return dat - -/obj/machinery/requests_console/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(href_list["write"]) - to_department = ckey(reject_bad_text(href_list["write"])) //write contains the string of the receiving department's name - - var/new_message = (to_department in GLOB.req_console_ckey_departments) && stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN) - if(new_message) - to_department = GLOB.req_console_ckey_departments[to_department] - message = new_message - screen = REQ_SCREEN_AUTHENTICATE - priority = clamp(text2num(href_list["priority"]), REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) - - if(href_list["writeAnnouncement"]) - var/new_message = reject_bad_text(stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN)) - if(new_message) - message = new_message - priority = clamp(text2num(href_list["priority"]) || REQ_NORMAL_MESSAGE_PRIORITY, REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) - else - message = "" - announceAuth = FALSE - screen = REQ_SCREEN_MAIN - - if(href_list["sendAnnouncement"]) - if(!announcementConsole) - return - if(isliving(usr)) - var/mob/living/L = usr - message = L.treat_message(message) - minor_announce(message, "[department] Announcement:") - GLOB.news_network.SubmitArticle(message, department, "Station Announcements", null) - usr.log_talk(message, LOG_SAY, tag="station announcement from [src]") - message_admins("[ADMIN_LOOKUPFLW(usr)] has made a station announcement from [src] at [AREACOORD(usr)].") - deadchat_broadcast(" made a station announcement from [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - announceAuth = FALSE - message = "" - screen = REQ_SCREEN_MAIN - - if(href_list["emergency"]) - if(!emergency) - var/radio_freq - switch(text2num(href_list["emergency"])) - if(REQ_EMERGENCY_SECURITY) //Security - radio_freq = FREQ_SECURITY - emergency = "Security" - if(REQ_EMERGENCY_ENGINEERING) //Engineering - radio_freq = FREQ_ENGINEERING - emergency = "Engineering" - if(REQ_EMERGENCY_MEDICAL) //Medical - radio_freq = FREQ_MEDICAL - emergency = "Medical" - if(radio_freq) - Radio.set_frequency(radio_freq) - Radio.talk_into(src,"[emergency] emergency in [department]!!",radio_freq) - update_icon() - addtimer(CALLBACK(src, .proc/clear_emergency), 5 MINUTES) - - if(href_list["send"] && message && to_department && priority) - - var/radio_freq - switch(ckey(to_department)) - if("bridge") - radio_freq = FREQ_COMMAND - if("medbay") - radio_freq = FREQ_MEDICAL - if("science") - radio_freq = FREQ_SCIENCE - if("engineering") - radio_freq = FREQ_ENGINEERING - if("security") - radio_freq = FREQ_SECURITY - if("cargobay" || "mining") - radio_freq = FREQ_SUPPLY - - var/datum/signal/subspace/messaging/rc/signal = new(src, list( - "sender" = department, - "rec_dpt" = to_department, - "send_dpt" = department, - "message" = message, - "verified" = msgVerified, - "stamped" = msgStamped, - "priority" = priority, - "notify_freq" = radio_freq - )) - signal.send_to_receivers() - - screen = signal.data["done"] ? REQ_SCREEN_SENT : REQ_SCREEN_ERR - - //Handle screen switching - if(href_list["setScreen"]) - var/set_screen = clamp(text2num(href_list["setScreen"]) || 0, REQ_SCREEN_MAIN, REQ_SCREEN_ANNOUNCE) - switch(set_screen) - if(REQ_SCREEN_MAIN) - to_department = "" - msgVerified = "" - msgStamped = "" - message = "" - priority = -1 - if(REQ_SCREEN_ANNOUNCE) - if(!announcementConsole) - return - screen = set_screen - - //Handle silencing the console - if(href_list["setSilent"]) - silent = text2num(href_list["setSilent"]) ? TRUE : FALSE - - updateUsrDialog() - -/obj/machinery/requests_console/say_mod(input, message_mode) - if(spantext_char(input, "!", -3)) - return "blares" - else - . = ..() - -/obj/machinery/requests_console/proc/clear_emergency() - emergency = null - update_icon() - -//from message_server.dm: Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) -/obj/machinery/requests_console/proc/createmessage(source, source_department, message, msgVerified, msgStamped, priority, radio_freq) - var/linkedsender - - var/sending = "[message]
                    " - if(msgVerified) - sending = "[sending][msgVerified]
                    " - if(msgStamped) - sending = "[sending][msgStamped]
                    " - - linkedsender = source_department ? "[source_department]" : (source || "unknown") - - var/authentic = (msgVerified || msgStamped) && " (Authenticated)" - var/alert = "Message from [source][authentic]" - var/silenced = silent - var/header = "From: [linkedsender] Received: [station_time_timestamp()]
                    " - - switch(priority) - if(REQ_NORMAL_MESSAGE_PRIORITY) - if(newmessagepriority < REQ_NORMAL_MESSAGE_PRIORITY) - newmessagepriority = REQ_NORMAL_MESSAGE_PRIORITY - update_icon() - - if(REQ_HIGH_MESSAGE_PRIORITY) - header = "High Priority
                    [header]" - alert = "PRIORITY Alert from [source][authentic]" - if(newmessagepriority < REQ_HIGH_MESSAGE_PRIORITY) - newmessagepriority = REQ_HIGH_MESSAGE_PRIORITY - update_icon() - - if(REQ_EXTREME_MESSAGE_PRIORITY) - header = "!!!Extreme Priority!!!
                    [header]" - alert = "EXTREME PRIORITY Alert from [source][authentic]" - silenced = FALSE - if(newmessagepriority < REQ_EXTREME_MESSAGE_PRIORITY) - newmessagepriority = REQ_EXTREME_MESSAGE_PRIORITY - update_icon() - - messages += "[header][sending]" - - if(!silenced) - playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) - say(alert) - - if(radio_freq) - Radio.set_frequency(radio_freq) - Radio.talk_into(src, "[alert]: [message]", radio_freq) - -/obj/machinery/requests_console/attackby(obj/item/O, mob/user, params) - if(O.tool_behaviour == TOOL_CROWBAR) - if(open) - to_chat(user, "You close the maintenance panel.") - open = FALSE - else - to_chat(user, "You open the maintenance panel.") - open = TRUE - update_icon() - return - if(O.tool_behaviour == TOOL_SCREWDRIVER) - if(open) - hackState = !hackState - if(hackState) - to_chat(user, "You modify the wiring.") - else - to_chat(user, "You reset the wiring.") - update_icon() - else - to_chat(user, "You must open the maintenance panel first!") - return - - var/obj/item/card/id/ID = O.GetID() - if(ID) - if(screen == REQ_SCREEN_AUTHENTICATE) - msgVerified = "Verified by [ID.registered_name] ([ID.assignment])" - updateUsrDialog() - if(screen == REQ_SCREEN_ANNOUNCE) - if (ACCESS_RC_ANNOUNCE in ID.access) - announceAuth = TRUE - else - announceAuth = FALSE - to_chat(user, "You are not authorized to send announcements!") - updateUsrDialog() - return - if (istype(O, /obj/item/stamp)) - if(screen == REQ_SCREEN_AUTHENTICATE) - var/obj/item/stamp/T = O - msgStamped = "Stamped with the [T.name]" - updateUsrDialog() - return - return ..() - -#undef REQ_EMERGENCY_SECURITY -#undef REQ_EMERGENCY_ENGINEERING -#undef REQ_EMERGENCY_MEDICAL - -#undef REQ_SCREEN_MAIN -#undef REQ_SCREEN_REQ_ASSISTANCE -#undef REQ_SCREEN_REQ_SUPPLIES -#undef REQ_SCREEN_RELAY -#undef REQ_SCREEN_WRITE -#undef REQ_SCREEN_CHOOSE -#undef REQ_SCREEN_SENT -#undef REQ_SCREEN_ERR -#undef REQ_SCREEN_VIEW_MSGS -#undef REQ_SCREEN_AUTHENTICATE -#undef REQ_SCREEN_ANNOUNCE +/******************** Requests Console ********************/ +/** Originally written by errorage, updated by: Carn, needs more work though. I just added some security fixes */ + +GLOBAL_LIST_EMPTY(req_console_assistance) +GLOBAL_LIST_EMPTY(req_console_supplies) +GLOBAL_LIST_EMPTY(req_console_information) +GLOBAL_LIST_EMPTY(allConsoles) +GLOBAL_LIST_EMPTY(req_console_ckey_departments) + + +#define REQ_SCREEN_MAIN 0 +#define REQ_SCREEN_REQ_ASSISTANCE 1 +#define REQ_SCREEN_REQ_SUPPLIES 2 +#define REQ_SCREEN_RELAY 3 +#define REQ_SCREEN_WRITE 4 +#define REQ_SCREEN_CHOOSE 5 +#define REQ_SCREEN_SENT 6 +#define REQ_SCREEN_ERR 7 +#define REQ_SCREEN_VIEW_MSGS 8 +#define REQ_SCREEN_AUTHENTICATE 9 +#define REQ_SCREEN_ANNOUNCE 10 + +#define REQ_EMERGENCY_SECURITY 1 +#define REQ_EMERGENCY_ENGINEERING 2 +#define REQ_EMERGENCY_MEDICAL 3 + +/obj/machinery/requests_console + name = "requests console" + desc = "A console intended to send requests to different departments on the station." + icon = 'icons/obj/terminals.dmi' + icon_state = "req_comp0" + var/department = "Unknown" //The list of all departments on the station (Determined from this variable on each unit) Set this to the same thing if you want several consoles in one department + var/list/messages = list() //List of all messages + var/departmentType = 0 //bitflag + // 0 = none (not listed, can only replied to) + // assistance = 1 + // supplies = 2 + // info = 4 + // assistance + supplies = 3 + // assistance + info = 5 + // supplies + info = 6 + // assistance + supplies + info = 7 + var/newmessagepriority = REQ_NO_NEW_MESSAGE + var/screen = REQ_SCREEN_MAIN + // 0 = main menu, + // 1 = req. assistance, + // 2 = req. supplies + // 3 = relay information + // 4 = write msg - not used + // 5 = choose priority - not used + // 6 = sent successfully + // 7 = sent unsuccessfully + // 8 = view messages + // 9 = authentication before sending + // 10 = send announcement + var/silent = FALSE // set to 1 for it not to beep all the time + var/hackState = FALSE + var/announcementConsole = FALSE // FALSE = This console cannot be used to send department announcements, TRUE = This console can send department announcements + var/open = FALSE // TRUE if open + var/announceAuth = FALSE //Will be set to 1 when you authenticate yourself for announcements + var/msgVerified = "" //Will contain the name of the person who verified it + var/msgStamped = "" //If a message is stamped, this will contain the stamp name + var/message = "" + var/to_department = "" //the department which will be receiving the message + var/priority = REQ_NO_NEW_MESSAGE //Priority of the message being sent + var/obj/item/radio/Radio + var/emergency //If an emergency has been called by this device. Acts as both a cooldown and lets the responder know where it the emergency was triggered from + var/receive_ore_updates = FALSE //If ore redemption machines will send an update when it receives new ores. + max_integrity = 300 + armor = list("melee" = 70, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + +/obj/machinery/requests_console/update_icon_state() + if(machine_stat & NOPOWER) + set_light(0) + else + set_light(1.4,0.7,"#34D352")//green light + if(open) + if(!hackState) + icon_state="req_comp_open" + else + icon_state="req_comp_rewired" + else if(machine_stat & NOPOWER) + if(icon_state != "req_comp_off") + icon_state = "req_comp_off" + else + if(emergency || (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY)) + icon_state = "req_comp3" + else if(newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) + icon_state = "req_comp2" + else if(newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) + icon_state = "req_comp1" + else + icon_state = "req_comp0" + +/obj/machinery/requests_console/Initialize() + . = ..() + name = "\improper [department] requests console" + GLOB.allConsoles += src + + if(departmentType) + + if((departmentType & REQ_DEP_TYPE_ASSISTANCE) && !(department in GLOB.req_console_assistance)) + GLOB.req_console_assistance += department + + if((departmentType & REQ_DEP_TYPE_SUPPLIES) && !(department in GLOB.req_console_supplies)) + GLOB.req_console_supplies += department + + if((departmentType & REQ_DEP_TYPE_INFORMATION) && !(department in GLOB.req_console_information)) + GLOB.req_console_information += department + + GLOB.req_console_ckey_departments[ckey(department)] = department + + Radio = new /obj/item/radio(src) + Radio.listening = 0 + +/obj/machinery/requests_console/Destroy() + QDEL_NULL(Radio) + GLOB.allConsoles -= src + return ..() + +/obj/machinery/requests_console/ui_interact(mob/user) + . = ..() + var/dat = "" + if(!open) + switch(screen) + if(REQ_SCREEN_MAIN) + announceAuth = FALSE + if (newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) + dat += "
                    There are new messages

                    " + else if (newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) + dat += "
                    There are new PRIORITY messages

                    " + else if (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY) + dat += "
                    There are new EXTREME PRIORITY messages

                    " + dat += "View Messages

                    " + + dat += "Request Assistance
                    " + dat += "Request Supplies
                    " + dat += "Relay Anonymous Information

                    " + + if(!emergency) + dat += "Emergency: Security
                    " + dat += "Emergency: Engineering
                    " + dat += "Emergency: Medical

                    " + else + dat += "[emergency] has been dispatched to this location.

                    " + + if(announcementConsole) + dat += "Send Station-wide Announcement

                    " + if (silent) + dat += "Speaker OFF" + else + dat += "Speaker ON" + if(REQ_SCREEN_REQ_ASSISTANCE) + dat += "Which department do you need assistance from?

                    " + dat += departments_table(GLOB.req_console_assistance) + + if(REQ_SCREEN_REQ_SUPPLIES) + dat += "Which department do you need supplies from?

                    " + dat += departments_table(GLOB.req_console_supplies) + + if(REQ_SCREEN_RELAY) + dat += "Which department would you like to send information to?

                    " + dat += departments_table(GLOB.req_console_information) + + if(REQ_SCREEN_SENT) + dat += "Message sent.

                    " + dat += "<< Back
                    " + + if(REQ_SCREEN_ERR) + dat += "An error occurred.

                    " + dat += "<< Back
                    " + + if(REQ_SCREEN_VIEW_MSGS) + for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) + if (Console.department == department) + Console.newmessagepriority = REQ_NO_NEW_MESSAGE + Console.update_icon() + + newmessagepriority = REQ_NO_NEW_MESSAGE + update_icon() + var/messageComposite = "" + for(var/msg in messages) // This puts more recent messages at the *top*, where they belong. + messageComposite = "
                    [msg]
                    " + messageComposite + dat += messageComposite + dat += "
                    << Back to Main Menu
                    " + + if(REQ_SCREEN_AUTHENTICATE) + dat += "Message Authentication

                    " + dat += "Message for [to_department]: [message]

                    " + dat += "
                    You may authenticate your message now by scanning your ID or your stamp

                    " + dat += "Validated by: [msgVerified ? msgVerified : "Not Validated"]
                    " + dat += "Stamped by: [msgStamped ? msgStamped : "Not Stamped"]

                    " + dat += "Send Message
                    " + dat += "
                    << Discard Message
                    " + + if(REQ_SCREEN_ANNOUNCE) + dat += "

                    Station-wide Announcement

                    " + if(announceAuth) + dat += "
                    Authentication accepted

                    " + else + dat += "
                    Swipe your card to authenticate yourself

                    " + dat += "Message: [message ? message : "No Message"]
                    " + dat += "[message ? "Edit" : "Write"] Message

                    " + if ((announceAuth || IsAdminGhost(user)) && message) + dat += "Announce Message
                    " + else + dat += "Announce Message
                    " + dat += "
                    << Back
                    " + + if(!dat) + CRASH("No UI for src. Screen var is: [screen]") + var/datum/browser/popup = new(user, "req_console", "[department] Requests Console", 450, 440) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + return + +/obj/machinery/requests_console/proc/departments_table(list/req_consoles) + var/dat = "" + dat += "" + for(var/req_dpt in req_consoles) + if (req_dpt != department) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
                    [req_dpt]Normal High" + if(hackState) + dat += "EXTREME" + dat += "
                    " + dat += "
                    << Back
                    " + return dat + +/obj/machinery/requests_console/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + add_fingerprint(usr) + + if(href_list["write"]) + to_department = ckey(reject_bad_text(href_list["write"])) //write contains the string of the receiving department's name + + var/new_message = (to_department in GLOB.req_console_ckey_departments) && stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN) + if(new_message) + to_department = GLOB.req_console_ckey_departments[to_department] + message = new_message + screen = REQ_SCREEN_AUTHENTICATE + priority = clamp(text2num(href_list["priority"]), REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) + + if(href_list["writeAnnouncement"]) + var/new_message = reject_bad_text(stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN)) + if(new_message) + message = new_message + priority = clamp(text2num(href_list["priority"]) || REQ_NORMAL_MESSAGE_PRIORITY, REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) + else + message = "" + announceAuth = FALSE + screen = REQ_SCREEN_MAIN + + if(href_list["sendAnnouncement"]) + if(!announcementConsole) + return + if(isliving(usr)) + var/mob/living/L = usr + message = L.treat_message(message) + minor_announce(message, "[department] Announcement:") + GLOB.news_network.SubmitArticle(message, department, "Station Announcements", null) + usr.log_talk(message, LOG_SAY, tag="station announcement from [src]") + message_admins("[ADMIN_LOOKUPFLW(usr)] has made a station announcement from [src] at [AREACOORD(usr)].") + deadchat_broadcast(" made a station announcement from [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + announceAuth = FALSE + message = "" + screen = REQ_SCREEN_MAIN + + if(href_list["emergency"]) + if(!emergency) + var/radio_freq + switch(text2num(href_list["emergency"])) + if(REQ_EMERGENCY_SECURITY) //Security + radio_freq = FREQ_SECURITY + emergency = "Security" + if(REQ_EMERGENCY_ENGINEERING) //Engineering + radio_freq = FREQ_ENGINEERING + emergency = "Engineering" + if(REQ_EMERGENCY_MEDICAL) //Medical + radio_freq = FREQ_MEDICAL + emergency = "Medical" + if(radio_freq) + Radio.set_frequency(radio_freq) + Radio.talk_into(src,"[emergency] emergency in [department]!!",radio_freq) + update_icon() + addtimer(CALLBACK(src, .proc/clear_emergency), 5 MINUTES) + + if(href_list["send"] && message && to_department && priority) + + var/radio_freq + switch(ckey(to_department)) + if("bridge") + radio_freq = FREQ_COMMAND + if("medbay") + radio_freq = FREQ_MEDICAL + if("science") + radio_freq = FREQ_SCIENCE + if("engineering") + radio_freq = FREQ_ENGINEERING + if("security") + radio_freq = FREQ_SECURITY + if("cargobay" || "mining") + radio_freq = FREQ_SUPPLY + + var/datum/signal/subspace/messaging/rc/signal = new(src, list( + "sender" = department, + "rec_dpt" = to_department, + "send_dpt" = department, + "message" = message, + "verified" = msgVerified, + "stamped" = msgStamped, + "priority" = priority, + "notify_freq" = radio_freq + )) + signal.send_to_receivers() + + screen = signal.data["done"] ? REQ_SCREEN_SENT : REQ_SCREEN_ERR + + //Handle screen switching + if(href_list["setScreen"]) + var/set_screen = clamp(text2num(href_list["setScreen"]) || 0, REQ_SCREEN_MAIN, REQ_SCREEN_ANNOUNCE) + switch(set_screen) + if(REQ_SCREEN_MAIN) + to_department = "" + msgVerified = "" + msgStamped = "" + message = "" + priority = -1 + if(REQ_SCREEN_ANNOUNCE) + if(!announcementConsole) + return + screen = set_screen + + //Handle silencing the console + if(href_list["setSilent"]) + silent = text2num(href_list["setSilent"]) ? TRUE : FALSE + + updateUsrDialog() + +/obj/machinery/requests_console/say_mod(input, message_mode) + if(spantext_char(input, "!", -3)) + return "blares" + else + . = ..() + +/obj/machinery/requests_console/proc/clear_emergency() + emergency = null + update_icon() + +//from message_server.dm: Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) +/obj/machinery/requests_console/proc/createmessage(source, source_department, message, msgVerified, msgStamped, priority, radio_freq) + var/linkedsender + + var/sending = "[message]
                    " + if(msgVerified) + sending = "[sending][msgVerified]
                    " + if(msgStamped) + sending = "[sending][msgStamped]
                    " + + linkedsender = source_department ? "[source_department]" : (source || "unknown") + + var/authentic = (msgVerified || msgStamped) && " (Authenticated)" + var/alert = "Message from [source][authentic]" + var/silenced = silent + var/header = "From: [linkedsender] Received: [station_time_timestamp()]
                    " + + switch(priority) + if(REQ_NORMAL_MESSAGE_PRIORITY) + if(newmessagepriority < REQ_NORMAL_MESSAGE_PRIORITY) + newmessagepriority = REQ_NORMAL_MESSAGE_PRIORITY + update_icon() + + if(REQ_HIGH_MESSAGE_PRIORITY) + header = "High Priority
                    [header]" + alert = "PRIORITY Alert from [source][authentic]" + if(newmessagepriority < REQ_HIGH_MESSAGE_PRIORITY) + newmessagepriority = REQ_HIGH_MESSAGE_PRIORITY + update_icon() + + if(REQ_EXTREME_MESSAGE_PRIORITY) + header = "!!!Extreme Priority!!!
                    [header]" + alert = "EXTREME PRIORITY Alert from [source][authentic]" + silenced = FALSE + if(newmessagepriority < REQ_EXTREME_MESSAGE_PRIORITY) + newmessagepriority = REQ_EXTREME_MESSAGE_PRIORITY + update_icon() + + messages += "[header][sending]" + + if(!silenced) + playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) + say(alert) + + if(radio_freq) + Radio.set_frequency(radio_freq) + Radio.talk_into(src, "[alert]: [message]", radio_freq) + +/obj/machinery/requests_console/attackby(obj/item/O, mob/user, params) + if(O.tool_behaviour == TOOL_CROWBAR) + if(open) + to_chat(user, "You close the maintenance panel.") + open = FALSE + else + to_chat(user, "You open the maintenance panel.") + open = TRUE + update_icon() + return + if(O.tool_behaviour == TOOL_SCREWDRIVER) + if(open) + hackState = !hackState + if(hackState) + to_chat(user, "You modify the wiring.") + else + to_chat(user, "You reset the wiring.") + update_icon() + else + to_chat(user, "You must open the maintenance panel first!") + return + + var/obj/item/card/id/ID = O.GetID() + if(ID) + if(screen == REQ_SCREEN_AUTHENTICATE) + msgVerified = "Verified by [ID.registered_name] ([ID.assignment])" + updateUsrDialog() + if(screen == REQ_SCREEN_ANNOUNCE) + if (ACCESS_RC_ANNOUNCE in ID.access) + announceAuth = TRUE + else + announceAuth = FALSE + to_chat(user, "You are not authorized to send announcements!") + updateUsrDialog() + return + if (istype(O, /obj/item/stamp)) + if(screen == REQ_SCREEN_AUTHENTICATE) + var/obj/item/stamp/T = O + msgStamped = "Stamped with the [T.name]" + updateUsrDialog() + return + return ..() + +#undef REQ_EMERGENCY_SECURITY +#undef REQ_EMERGENCY_ENGINEERING +#undef REQ_EMERGENCY_MEDICAL + +#undef REQ_SCREEN_MAIN +#undef REQ_SCREEN_REQ_ASSISTANCE +#undef REQ_SCREEN_REQ_SUPPLIES +#undef REQ_SCREEN_RELAY +#undef REQ_SCREEN_WRITE +#undef REQ_SCREEN_CHOOSE +#undef REQ_SCREEN_SENT +#undef REQ_SCREEN_ERR +#undef REQ_SCREEN_VIEW_MSGS +#undef REQ_SCREEN_AUTHENTICATE +#undef REQ_SCREEN_ANNOUNCE diff --git a/code/game/machinery/roulette_machine.dm b/code/game/machinery/roulette_machine.dm index 61bd80508e9..181b92a9ed7 100644 --- a/code/game/machinery/roulette_machine.dm +++ b/code/game/machinery/roulette_machine.dm @@ -1,425 +1,425 @@ -#define ROULETTE_SINGLES_PAYOUT 36 -#define ROULETTE_SIMPLE_PAYOUT 2 -#define ROULETTE_DOZ_COL_PAYOUT 3 - -#define ROULETTE_BET_ODD "odd" -#define ROULETTE_BET_EVEN "even" -#define ROULETTE_BET_1TO18 "s1-18" //adds s to prevent text2num from working -#define ROULETTE_BET_19TO36 "s19-36" //adds s to prevent text2num from working -#define ROULETTE_BET_1TO12 "s1-12" -#define ROULETTE_BET_13TO24 "s13-24" -#define ROULETTE_BET_25TO36 "s25-36" -#define ROULETTE_BET_2TO1_FIRST "s1st col" -#define ROULETTE_BET_2TO1_SECOND "s2nd col" -#define ROULETTE_BET_2TO1_THIRD "s3rd col" -#define ROULETTE_BET_BLACK "black" -#define ROULETTE_BET_RED "red" - -#define ROULETTE_JACKPOT_AMOUNT 1000 - -///Machine that lets you play roulette. Odds are pre-defined to be the same as European Roulette without the "En Prison" rule -/obj/machinery/roulette - name = "Roulette Table" - desc = "A computerized roulette table. Swipe your ID to play or register yourself as owner!" - icon = 'icons/obj/machines/roulette.dmi' - icon_state = "idle" - density = TRUE - use_power = IDLE_POWER_USE - anchored = FALSE - idle_power_usage = 10 - active_power_usage = 100 - max_integrity = 500 - ui_x = 603 - ui_y = 475 - armor = list("melee" = 45, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 10, "bio" = 30, "rad" = 30, "fire" = 30, "acid" = 30) - var/static/list/numbers = list("0" = "green", "1" = "red", "3" = "red", "5" = "red", "7" = "red", "9" = "red", "12" = "red", "14" = "red", "16" = "red",\ - "18" = "red", "19" = "red", "21" = "red", "23" = "red", "25" = "red", "27" = "red", "30" = "red", "32" = "red", "34" = "red", "36" = "red",\ - "2" = "black", "4" = "black", "6" = "black", "8" = "black", "10" = "black", "11" = "black", "13" = "black", "15" = "black", "17" = "black", "20" = "black",\ - "22" = "black", "24" = "black", "26" = "black", "28" = "black", "29" = "black", "31" = "black", "33" = "black", "35" = "black") - - var/chosen_bet_amount = 10 - var/chosen_bet_type = "0" - var/last_anti_spam = 0 - var/anti_spam_cooldown = 20 - var/obj/item/card/id/my_card - var/playing = FALSE - var/locked = FALSE - var/drop_dir = SOUTH - var/static/list/coin_values = list(/obj/item/coin/diamond = 100, /obj/item/coin/gold = 25, /obj/item/coin/silver = 10, /obj/item/coin/iron = 1) //Make sure this is ordered from left to right. - var/list/coins_to_dispense = list() - var/datum/looping_sound/jackpot/jackpot_loop - var/on = TRUE - var/last_spin = 13 - -/obj/machinery/roulette/Initialize() - . = ..() - jackpot_loop = new(list(src), FALSE) - wires = new /datum/wires/roulette(src) - -/obj/machinery/roulette/obj_break(damage_flag) - prize_theft(0.05) - . = ..() - -/obj/machinery/roulette/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - if(machine_stat & MAINT) - return - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Roulette", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/roulette/ui_data(mob/user) - var/list/data = list() - data["IsAnchored"] = anchored - data["BetAmount"] = chosen_bet_amount - data["BetType"] = chosen_bet_type - data["HouseBalance"] = my_card?.registered_account.account_balance - data["LastSpin"] = last_spin - data["Spinning"] = playing - if(ishuman(user)) - var/mob/living/carbon/human/H = user - var/obj/item/card/id/C = H.get_idcard(TRUE) - if(C) - data["AccountBalance"] = C.registered_account.account_balance - else - data["AccountBalance"] = 0 - data["CanUnbolt"] = (H.get_idcard() == my_card) - - return data - -/obj/machinery/roulette/ui_act(action, params) - if(..()) - return - switch(action) - if("anchor") - anchored = !anchored - . = TRUE - if("ChangeBetAmount") - chosen_bet_amount = clamp(text2num(params["amount"]), 10, 500) - . = TRUE - if("ChangeBetType") - chosen_bet_type = params["type"] - . = TRUE - update_icon() // Not applicable to all objects. - -///Handles setting ownership and the betting itself. -/obj/machinery/roulette/attackby(obj/item/W, mob/user, params) - if(machine_stat & MAINT && is_wire_tool(W)) - wires.interact(user) - return - if(playing) - return ..() - if(istype(W, /obj/item/card/id)) - playsound(src, 'sound/machines/card_slide.ogg', 50, TRUE) - - if(machine_stat & MAINT || !on || locked) - to_chat(user, "The machine appears to be disabled.") - return FALSE - - if(my_card) - var/obj/item/card/id/player_card = W - if(player_card.registered_account.account_balance < chosen_bet_amount) //Does the player have enough funds - audible_message("You do not have the funds to play! Lower your bet or get more money.") - playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) - return FALSE - if(!chosen_bet_amount || isnull(chosen_bet_type)) - return FALSE - - //double to nothing bets - var/list/doubles = list( - ROULETTE_BET_2TO1_FIRST, - ROULETTE_BET_2TO1_SECOND, - ROULETTE_BET_2TO1_THIRD, - ROULETTE_BET_1TO12, - ROULETTE_BET_13TO24, - ROULETTE_BET_25TO36 - ) - //result of text2num is null if text starts with a character, meaning it's not a singles bet - var/single = !isnull(text2num(chosen_bet_type)) - var/potential_payout_mult - if (single) - potential_payout_mult = ROULETTE_SINGLES_PAYOUT - else - if (chosen_bet_type in doubles) - potential_payout_mult = ROULETTE_DOZ_COL_PAYOUT - else - potential_payout_mult = ROULETTE_SIMPLE_PAYOUT - var/potential_payout = chosen_bet_amount * potential_payout_mult - - if(!check_bartender_funds(potential_payout)) - return FALSE //bartender is too poor - - if(last_anti_spam > world.time) //do not cheat me - return FALSE - - last_anti_spam = world.time + anti_spam_cooldown - - icon_state = "rolling" //Prepare the new icon state for rolling before hand. - flick("flick_up", src) - playsound(src, 'sound/machines/piston_raise.ogg', 70) - playsound(src, 'sound/machines/chime.ogg', 50) - - addtimer(CALLBACK(src, .proc/play, user, player_card, chosen_bet_type, chosen_bet_amount, potential_payout), 4) //Animation first - return TRUE - else - var/obj/item/card/id/new_card = W - if(new_card.registered_account) - var/msg = stripped_input(user, "Name of your roulette wheel:", "Roulette Naming", "Roulette Machine") - if(!msg) - return - name = msg - desc = "Owned by [new_card.registered_account.account_holder], draws directly from [user.p_their()] account." - my_card = new_card - to_chat(user, "You link the wheel to your account.") - power_change() - return - return ..() - -///Proc called when player is going to try and play -/obj/machinery/roulette/proc/play(mob/user, obj/item/card/id/player_id, bet_type, bet_amount, potential_payout) - - var/payout = potential_payout - - my_card.registered_account.transfer_money(player_id.registered_account, bet_amount) - - playing = TRUE - update_icon() - set_light(0) - - var/rolled_number = rand(0, 36) - - playsound(src, 'sound/machines/roulettewheel.ogg', 50) - addtimer(CALLBACK(src, .proc/finish_play, player_id, bet_type, bet_amount, payout, rolled_number), 34) //4 deciseconds more so the animation can play - addtimer(CALLBACK(src, .proc/finish_play_animation), 30) - -/obj/machinery/roulette/proc/finish_play_animation() - icon_state = "idle" - flick("flick_down", src) - playsound(src, 'sound/machines/piston_lower.ogg', 70) - -///Ran after a while to check if the player won or not. -/obj/machinery/roulette/proc/finish_play(obj/item/card/id/player_id, bet_type, bet_amount, potential_payout, rolled_number) - last_spin = rolled_number - - var/is_winner = check_win(bet_type, bet_amount, rolled_number) //Predetermine if we won - var/color = numbers["[rolled_number]"] //Weird syntax, but dict uses strings. - var/result = "[rolled_number] [color]" //e.g. 31 black - - audible_message("The result is: [result]") - - playing = FALSE - update_icon(potential_payout, color, rolled_number, is_winner) - handle_color_light(color) - - if(!is_winner) - audible_message("You lost! Better luck next time") - playsound(src, 'sound/machines/synth_no.ogg', 50) - return FALSE - - audible_message("You have won [potential_payout] credits! Congratulations!") - playsound(src, 'sound/machines/synth_yes.ogg', 50) - - dispense_prize(potential_payout) - -///Fills a list of coins that should be dropped. -/obj/machinery/roulette/proc/dispense_prize(payout) - - if(payout >= ROULETTE_JACKPOT_AMOUNT) - jackpot_loop.start() - - var/remaining_payout = payout - - my_card.registered_account.adjust_money(-payout) - - for(var/coin_type in coin_values) //Loop through all coins from most valuable to least valuable. Try to give as much of that coin (the iterable) as possible until you can't anymore, then move to the next. - var/value = coin_values[coin_type] //Change this to use initial value once we change to mat datum coins. - var/coin_count = round(remaining_payout / value) - - if(!coin_count) //Cant make coins of this type, as we can't reach it's value. - continue - - remaining_payout -= value * coin_count - coins_to_dispense[coin_type] += coin_count - - drop_coin() //Start recursively dropping coins - -///Recursive function that runs until it runs out of coins to drop. -/obj/machinery/roulette/proc/drop_coin() - var/coin_to_drop - - for(var/i in coins_to_dispense) //Find which coin to drop - if(coins_to_dispense[i] <= 0) //Less than 1? go to next potential coin. - continue - coin_to_drop = i - break - - if(!coin_to_drop) //No more coins, stop recursion. - jackpot_loop.stop() - return FALSE - - coins_to_dispense[coin_to_drop] -= 1 - - var/turf/drop_loc = get_step(loc, drop_dir) - var/obj/item/cash = new coin_to_drop(drop_loc) - playsound(cash, pick(list('sound/machines/coindrop.ogg', 'sound/machines/coindrop2.ogg')), 40, TRUE) - - addtimer(CALLBACK(src, .proc/drop_coin), 3) //Recursion time - - -///Fills a list of coins that should be dropped. -/obj/machinery/roulette/proc/prize_theft(percentage) - if(locked) - return - locked = TRUE - var/stolen_cash = my_card.registered_account.account_balance * percentage - dispense_prize(stolen_cash) - - -///Returns TRUE if the player bet correctly. -/obj/machinery/roulette/proc/check_win(bet_type, bet_amount, rolled_number) - var/actual_bet_number = text2num(bet_type) //Only returns the numeric bet types, AKA singles. - if(actual_bet_number) //This means we're playing singles - return rolled_number == actual_bet_number - - switch(bet_type) //Otherwise, we are playing a "special" game, switch on all the cases so we can check. - if(ROULETTE_BET_ODD) - return ISODD(rolled_number) - if(ROULETTE_BET_EVEN) - return ISEVEN(rolled_number) - if(ROULETTE_BET_1TO18) - return (rolled_number >= 1 && rolled_number <= 18) //between 1 to 18 - if(ROULETTE_BET_19TO36) - return rolled_number > 18 //between 19 to 36, no need to check bounds because we won't go higher anyways - if(ROULETTE_BET_BLACK) - return "black" == numbers["[rolled_number]"]//Check if our number is black in the numbers dict - if(ROULETTE_BET_RED) - return "red" == numbers["[rolled_number]"] //Check if our number is black in the numbers dict - if(ROULETTE_BET_1TO12) - return (rolled_number >= 1 && rolled_number <= 12) - if(ROULETTE_BET_13TO24) - return (rolled_number >= 13 && rolled_number <= 24) - if(ROULETTE_BET_25TO36) - return (rolled_number >= 25 && rolled_number <= 36) - if(ROULETTE_BET_2TO1_FIRST) - //You could do this mathematically but w/e this is easy to understand - //numbers in the first column - var/list/winners = list(1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34) - return (rolled_number in winners) - if(ROULETTE_BET_2TO1_SECOND) - //numbers in the second column - var/list/winners = list(2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35) - return (rolled_number in winners) - if(ROULETTE_BET_2TO1_THIRD) - //numbers in the third column - var/list/winners = list(3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36) - return (rolled_number in winners) - - -///Returns TRUE if the owner has enough funds to payout -/obj/machinery/roulette/proc/check_bartender_funds(payout) - if(my_card.registered_account.account_balance >= payout) - return TRUE //We got the betting amount - audible_message("The bank account of [my_card.registered_account.account_holder] does not have enough funds to pay out the potential prize, contact them to fill up their account or lower your bet!") - playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) - return FALSE - -/obj/machinery/roulette/update_icon(payout, color, rolled_number, is_winner = FALSE) - cut_overlays() - - if(machine_stat & MAINT) - return - - if(playing) - add_overlay("random_numbers") - - if(!payout || !color || isnull(rolled_number)) //Don't fall for tricks. - return - - //Overlay for ring - if(is_winner && payout >= ROULETTE_JACKPOT_AMOUNT) - add_overlay("jackpot") - else - add_overlay(color) - - var/numberright = rolled_number % 10 //Right hand number - var/numberleft = (rolled_number - numberright) / 10 //Left hand number - - var/shift_amount = 2 //How much the icon moves left/right - - if(numberleft != 0) //Don't make the number if we are 0. - var/mutable_appearance/number1 = mutable_appearance(icon, "[numberleft]") - number1.pixel_x = -shift_amount - add_overlay(number1) - else - shift_amount = 0 //We can stay centered. - - var/mutable_appearance/number2 = mutable_appearance(icon, "[numberright]") - number2.pixel_x = shift_amount - add_overlay(number2) - -/obj/machinery/roulette/proc/handle_color_light(color) - switch(color) - if("green") - set_light(2,2, LIGHT_COLOR_GREEN) - if("red") - set_light(2,2, LIGHT_COLOR_RED) - -/obj/machinery/roulette/welder_act(mob/living/user, obj/item/I) - . = ..() - if(machine_stat & MAINT) - to_chat(user, "You start re-attaching the top section of [src]...") - if(I.use_tool(src, user, 30, volume=50)) - to_chat(user, "You re-attach the top section of [src].") - machine_stat &= ~MAINT - icon_state = "idle" - else - to_chat(user, "You start welding the top section from [src]...") - if(I.use_tool(src, user, 30, volume=50)) - to_chat(user, "You removed the top section of [src].") - machine_stat |= MAINT - icon_state = "open" - -/obj/machinery/roulette/proc/shock(mob/user, prb) - if(!on) // unpowered, no shock - return FALSE - if(!prob(prb)) - return FALSE //you lucked out, no shock for you - do_sparks(5, TRUE, src) - if(electrocute_mob(user, get_area(src), src, 1, TRUE)) - return TRUE - else - return FALSE - -/obj/item/roulette_wheel_beacon - name = "roulette wheel beacon" - desc = "N.T. approved roulette wheel beacon, toss it down and you will have a complementary roulette wheel delivered to you." - icon = 'icons/obj/objects.dmi' - icon_state = "floor_beacon" - var/used - -/obj/item/roulette_wheel_beacon/attack_self() - if(used) - return - loc.visible_message("\The [src] begins to beep loudly!") - used = TRUE - addtimer(CALLBACK(src, .proc/launch_payload), 40) - -/obj/item/roulette_wheel_beacon/proc/launch_payload() - var/obj/structure/closet/supplypod/centcompod/toLaunch = new() - - new /obj/machinery/roulette(toLaunch) - - new /obj/effect/pod_landingzone(drop_location(), toLaunch) - qdel(src) - -#undef ROULETTE_SINGLES_PAYOUT -#undef ROULETTE_SIMPLE_PAYOUT - -#undef ROULETTE_BET_ODD -#undef ROULETTE_BET_EVEN -#undef ROULETTE_BET_1TO18 -#undef ROULETTE_BET_19TO36 -#undef ROULETTE_BET_BLACK -#undef ROULETTE_BET_RED - -#undef ROULETTE_JACKPOT_AMOUNT +#define ROULETTE_SINGLES_PAYOUT 36 +#define ROULETTE_SIMPLE_PAYOUT 2 +#define ROULETTE_DOZ_COL_PAYOUT 3 + +#define ROULETTE_BET_ODD "odd" +#define ROULETTE_BET_EVEN "even" +#define ROULETTE_BET_1TO18 "s1-18" //adds s to prevent text2num from working +#define ROULETTE_BET_19TO36 "s19-36" //adds s to prevent text2num from working +#define ROULETTE_BET_1TO12 "s1-12" +#define ROULETTE_BET_13TO24 "s13-24" +#define ROULETTE_BET_25TO36 "s25-36" +#define ROULETTE_BET_2TO1_FIRST "s1st col" +#define ROULETTE_BET_2TO1_SECOND "s2nd col" +#define ROULETTE_BET_2TO1_THIRD "s3rd col" +#define ROULETTE_BET_BLACK "black" +#define ROULETTE_BET_RED "red" + +#define ROULETTE_JACKPOT_AMOUNT 1000 + +///Machine that lets you play roulette. Odds are pre-defined to be the same as European Roulette without the "En Prison" rule +/obj/machinery/roulette + name = "Roulette Table" + desc = "A computerized roulette table. Swipe your ID to play or register yourself as owner!" + icon = 'icons/obj/machines/roulette.dmi' + icon_state = "idle" + density = TRUE + use_power = IDLE_POWER_USE + anchored = FALSE + idle_power_usage = 10 + active_power_usage = 100 + max_integrity = 500 + ui_x = 603 + ui_y = 475 + armor = list("melee" = 45, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 10, "bio" = 30, "rad" = 30, "fire" = 30, "acid" = 30) + var/static/list/numbers = list("0" = "green", "1" = "red", "3" = "red", "5" = "red", "7" = "red", "9" = "red", "12" = "red", "14" = "red", "16" = "red",\ + "18" = "red", "19" = "red", "21" = "red", "23" = "red", "25" = "red", "27" = "red", "30" = "red", "32" = "red", "34" = "red", "36" = "red",\ + "2" = "black", "4" = "black", "6" = "black", "8" = "black", "10" = "black", "11" = "black", "13" = "black", "15" = "black", "17" = "black", "20" = "black",\ + "22" = "black", "24" = "black", "26" = "black", "28" = "black", "29" = "black", "31" = "black", "33" = "black", "35" = "black") + + var/chosen_bet_amount = 10 + var/chosen_bet_type = "0" + var/last_anti_spam = 0 + var/anti_spam_cooldown = 20 + var/obj/item/card/id/my_card + var/playing = FALSE + var/locked = FALSE + var/drop_dir = SOUTH + var/static/list/coin_values = list(/obj/item/coin/diamond = 100, /obj/item/coin/gold = 25, /obj/item/coin/silver = 10, /obj/item/coin/iron = 1) //Make sure this is ordered from left to right. + var/list/coins_to_dispense = list() + var/datum/looping_sound/jackpot/jackpot_loop + var/on = TRUE + var/last_spin = 13 + +/obj/machinery/roulette/Initialize() + . = ..() + jackpot_loop = new(list(src), FALSE) + wires = new /datum/wires/roulette(src) + +/obj/machinery/roulette/obj_break(damage_flag) + prize_theft(0.05) + . = ..() + +/obj/machinery/roulette/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + if(machine_stat & MAINT) + return + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Roulette", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/roulette/ui_data(mob/user) + var/list/data = list() + data["IsAnchored"] = anchored + data["BetAmount"] = chosen_bet_amount + data["BetType"] = chosen_bet_type + data["HouseBalance"] = my_card?.registered_account.account_balance + data["LastSpin"] = last_spin + data["Spinning"] = playing + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/card/id/C = H.get_idcard(TRUE) + if(C) + data["AccountBalance"] = C.registered_account.account_balance + else + data["AccountBalance"] = 0 + data["CanUnbolt"] = (H.get_idcard() == my_card) + + return data + +/obj/machinery/roulette/ui_act(action, params) + if(..()) + return + switch(action) + if("anchor") + anchored = !anchored + . = TRUE + if("ChangeBetAmount") + chosen_bet_amount = clamp(text2num(params["amount"]), 10, 500) + . = TRUE + if("ChangeBetType") + chosen_bet_type = params["type"] + . = TRUE + update_icon() // Not applicable to all objects. + +///Handles setting ownership and the betting itself. +/obj/machinery/roulette/attackby(obj/item/W, mob/user, params) + if(machine_stat & MAINT && is_wire_tool(W)) + wires.interact(user) + return + if(playing) + return ..() + if(istype(W, /obj/item/card/id)) + playsound(src, 'sound/machines/card_slide.ogg', 50, TRUE) + + if(machine_stat & MAINT || !on || locked) + to_chat(user, "The machine appears to be disabled.") + return FALSE + + if(my_card) + var/obj/item/card/id/player_card = W + if(player_card.registered_account.account_balance < chosen_bet_amount) //Does the player have enough funds + audible_message("You do not have the funds to play! Lower your bet or get more money.") + playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) + return FALSE + if(!chosen_bet_amount || isnull(chosen_bet_type)) + return FALSE + + //double to nothing bets + var/list/doubles = list( + ROULETTE_BET_2TO1_FIRST, + ROULETTE_BET_2TO1_SECOND, + ROULETTE_BET_2TO1_THIRD, + ROULETTE_BET_1TO12, + ROULETTE_BET_13TO24, + ROULETTE_BET_25TO36 + ) + //result of text2num is null if text starts with a character, meaning it's not a singles bet + var/single = !isnull(text2num(chosen_bet_type)) + var/potential_payout_mult + if (single) + potential_payout_mult = ROULETTE_SINGLES_PAYOUT + else + if (chosen_bet_type in doubles) + potential_payout_mult = ROULETTE_DOZ_COL_PAYOUT + else + potential_payout_mult = ROULETTE_SIMPLE_PAYOUT + var/potential_payout = chosen_bet_amount * potential_payout_mult + + if(!check_bartender_funds(potential_payout)) + return FALSE //bartender is too poor + + if(last_anti_spam > world.time) //do not cheat me + return FALSE + + last_anti_spam = world.time + anti_spam_cooldown + + icon_state = "rolling" //Prepare the new icon state for rolling before hand. + flick("flick_up", src) + playsound(src, 'sound/machines/piston_raise.ogg', 70) + playsound(src, 'sound/machines/chime.ogg', 50) + + addtimer(CALLBACK(src, .proc/play, user, player_card, chosen_bet_type, chosen_bet_amount, potential_payout), 4) //Animation first + return TRUE + else + var/obj/item/card/id/new_card = W + if(new_card.registered_account) + var/msg = stripped_input(user, "Name of your roulette wheel:", "Roulette Naming", "Roulette Machine") + if(!msg) + return + name = msg + desc = "Owned by [new_card.registered_account.account_holder], draws directly from [user.p_their()] account." + my_card = new_card + to_chat(user, "You link the wheel to your account.") + power_change() + return + return ..() + +///Proc called when player is going to try and play +/obj/machinery/roulette/proc/play(mob/user, obj/item/card/id/player_id, bet_type, bet_amount, potential_payout) + + var/payout = potential_payout + + my_card.registered_account.transfer_money(player_id.registered_account, bet_amount) + + playing = TRUE + update_icon() + set_light(0) + + var/rolled_number = rand(0, 36) + + playsound(src, 'sound/machines/roulettewheel.ogg', 50) + addtimer(CALLBACK(src, .proc/finish_play, player_id, bet_type, bet_amount, payout, rolled_number), 34) //4 deciseconds more so the animation can play + addtimer(CALLBACK(src, .proc/finish_play_animation), 30) + +/obj/machinery/roulette/proc/finish_play_animation() + icon_state = "idle" + flick("flick_down", src) + playsound(src, 'sound/machines/piston_lower.ogg', 70) + +///Ran after a while to check if the player won or not. +/obj/machinery/roulette/proc/finish_play(obj/item/card/id/player_id, bet_type, bet_amount, potential_payout, rolled_number) + last_spin = rolled_number + + var/is_winner = check_win(bet_type, bet_amount, rolled_number) //Predetermine if we won + var/color = numbers["[rolled_number]"] //Weird syntax, but dict uses strings. + var/result = "[rolled_number] [color]" //e.g. 31 black + + audible_message("The result is: [result]") + + playing = FALSE + update_icon(potential_payout, color, rolled_number, is_winner) + handle_color_light(color) + + if(!is_winner) + audible_message("You lost! Better luck next time") + playsound(src, 'sound/machines/synth_no.ogg', 50) + return FALSE + + audible_message("You have won [potential_payout] credits! Congratulations!") + playsound(src, 'sound/machines/synth_yes.ogg', 50) + + dispense_prize(potential_payout) + +///Fills a list of coins that should be dropped. +/obj/machinery/roulette/proc/dispense_prize(payout) + + if(payout >= ROULETTE_JACKPOT_AMOUNT) + jackpot_loop.start() + + var/remaining_payout = payout + + my_card.registered_account.adjust_money(-payout) + + for(var/coin_type in coin_values) //Loop through all coins from most valuable to least valuable. Try to give as much of that coin (the iterable) as possible until you can't anymore, then move to the next. + var/value = coin_values[coin_type] //Change this to use initial value once we change to mat datum coins. + var/coin_count = round(remaining_payout / value) + + if(!coin_count) //Cant make coins of this type, as we can't reach it's value. + continue + + remaining_payout -= value * coin_count + coins_to_dispense[coin_type] += coin_count + + drop_coin() //Start recursively dropping coins + +///Recursive function that runs until it runs out of coins to drop. +/obj/machinery/roulette/proc/drop_coin() + var/coin_to_drop + + for(var/i in coins_to_dispense) //Find which coin to drop + if(coins_to_dispense[i] <= 0) //Less than 1? go to next potential coin. + continue + coin_to_drop = i + break + + if(!coin_to_drop) //No more coins, stop recursion. + jackpot_loop.stop() + return FALSE + + coins_to_dispense[coin_to_drop] -= 1 + + var/turf/drop_loc = get_step(loc, drop_dir) + var/obj/item/cash = new coin_to_drop(drop_loc) + playsound(cash, pick(list('sound/machines/coindrop.ogg', 'sound/machines/coindrop2.ogg')), 40, TRUE) + + addtimer(CALLBACK(src, .proc/drop_coin), 3) //Recursion time + + +///Fills a list of coins that should be dropped. +/obj/machinery/roulette/proc/prize_theft(percentage) + if(locked) + return + locked = TRUE + var/stolen_cash = my_card.registered_account.account_balance * percentage + dispense_prize(stolen_cash) + + +///Returns TRUE if the player bet correctly. +/obj/machinery/roulette/proc/check_win(bet_type, bet_amount, rolled_number) + var/actual_bet_number = text2num(bet_type) //Only returns the numeric bet types, AKA singles. + if(actual_bet_number) //This means we're playing singles + return rolled_number == actual_bet_number + + switch(bet_type) //Otherwise, we are playing a "special" game, switch on all the cases so we can check. + if(ROULETTE_BET_ODD) + return ISODD(rolled_number) + if(ROULETTE_BET_EVEN) + return ISEVEN(rolled_number) + if(ROULETTE_BET_1TO18) + return (rolled_number >= 1 && rolled_number <= 18) //between 1 to 18 + if(ROULETTE_BET_19TO36) + return rolled_number > 18 //between 19 to 36, no need to check bounds because we won't go higher anyways + if(ROULETTE_BET_BLACK) + return "black" == numbers["[rolled_number]"]//Check if our number is black in the numbers dict + if(ROULETTE_BET_RED) + return "red" == numbers["[rolled_number]"] //Check if our number is black in the numbers dict + if(ROULETTE_BET_1TO12) + return (rolled_number >= 1 && rolled_number <= 12) + if(ROULETTE_BET_13TO24) + return (rolled_number >= 13 && rolled_number <= 24) + if(ROULETTE_BET_25TO36) + return (rolled_number >= 25 && rolled_number <= 36) + if(ROULETTE_BET_2TO1_FIRST) + //You could do this mathematically but w/e this is easy to understand + //numbers in the first column + var/list/winners = list(1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34) + return (rolled_number in winners) + if(ROULETTE_BET_2TO1_SECOND) + //numbers in the second column + var/list/winners = list(2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35) + return (rolled_number in winners) + if(ROULETTE_BET_2TO1_THIRD) + //numbers in the third column + var/list/winners = list(3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36) + return (rolled_number in winners) + + +///Returns TRUE if the owner has enough funds to payout +/obj/machinery/roulette/proc/check_bartender_funds(payout) + if(my_card.registered_account.account_balance >= payout) + return TRUE //We got the betting amount + audible_message("The bank account of [my_card.registered_account.account_holder] does not have enough funds to pay out the potential prize, contact them to fill up their account or lower your bet!") + playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) + return FALSE + +/obj/machinery/roulette/update_icon(payout, color, rolled_number, is_winner = FALSE) + cut_overlays() + + if(machine_stat & MAINT) + return + + if(playing) + add_overlay("random_numbers") + + if(!payout || !color || isnull(rolled_number)) //Don't fall for tricks. + return + + //Overlay for ring + if(is_winner && payout >= ROULETTE_JACKPOT_AMOUNT) + add_overlay("jackpot") + else + add_overlay(color) + + var/numberright = rolled_number % 10 //Right hand number + var/numberleft = (rolled_number - numberright) / 10 //Left hand number + + var/shift_amount = 2 //How much the icon moves left/right + + if(numberleft != 0) //Don't make the number if we are 0. + var/mutable_appearance/number1 = mutable_appearance(icon, "[numberleft]") + number1.pixel_x = -shift_amount + add_overlay(number1) + else + shift_amount = 0 //We can stay centered. + + var/mutable_appearance/number2 = mutable_appearance(icon, "[numberright]") + number2.pixel_x = shift_amount + add_overlay(number2) + +/obj/machinery/roulette/proc/handle_color_light(color) + switch(color) + if("green") + set_light(2,2, LIGHT_COLOR_GREEN) + if("red") + set_light(2,2, LIGHT_COLOR_RED) + +/obj/machinery/roulette/welder_act(mob/living/user, obj/item/I) + . = ..() + if(machine_stat & MAINT) + to_chat(user, "You start re-attaching the top section of [src]...") + if(I.use_tool(src, user, 30, volume=50)) + to_chat(user, "You re-attach the top section of [src].") + machine_stat &= ~MAINT + icon_state = "idle" + else + to_chat(user, "You start welding the top section from [src]...") + if(I.use_tool(src, user, 30, volume=50)) + to_chat(user, "You removed the top section of [src].") + machine_stat |= MAINT + icon_state = "open" + +/obj/machinery/roulette/proc/shock(mob/user, prb) + if(!on) // unpowered, no shock + return FALSE + if(!prob(prb)) + return FALSE //you lucked out, no shock for you + do_sparks(5, TRUE, src) + if(electrocute_mob(user, get_area(src), src, 1, TRUE)) + return TRUE + else + return FALSE + +/obj/item/roulette_wheel_beacon + name = "roulette wheel beacon" + desc = "N.T. approved roulette wheel beacon, toss it down and you will have a complementary roulette wheel delivered to you." + icon = 'icons/obj/objects.dmi' + icon_state = "floor_beacon" + var/used + +/obj/item/roulette_wheel_beacon/attack_self() + if(used) + return + loc.visible_message("\The [src] begins to beep loudly!") + used = TRUE + addtimer(CALLBACK(src, .proc/launch_payload), 40) + +/obj/item/roulette_wheel_beacon/proc/launch_payload() + var/obj/structure/closet/supplypod/centcompod/toLaunch = new() + + new /obj/machinery/roulette(toLaunch) + + new /obj/effect/pod_landingzone(drop_location(), toLaunch) + qdel(src) + +#undef ROULETTE_SINGLES_PAYOUT +#undef ROULETTE_SIMPLE_PAYOUT + +#undef ROULETTE_BET_ODD +#undef ROULETTE_BET_EVEN +#undef ROULETTE_BET_1TO18 +#undef ROULETTE_BET_19TO36 +#undef ROULETTE_BET_BLACK +#undef ROULETTE_BET_RED + +#undef ROULETTE_JACKPOT_AMOUNT diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index fa4822d6338..cabbf60f224 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -1,371 +1,371 @@ -// Status display -// (formerly Countdown timer display) - -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Small Fonts" -#define SCROLL_SPEED 2 - -#define SD_BLANK 0 // 0 = Blank -#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer -#define SD_MESSAGE 2 // 2 = Arbitrary message(s) -#define SD_PICTURE 3 // 3 = alert picture - -#define SD_AI_EMOTE 1 // 1 = AI emoticon -#define SD_AI_BSOD 2 // 2 = Blue screen of death - -/// Status display which can show images and scrolling text. -/obj/machinery/status_display - name = "status display" - desc = null - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - - maptext_height = 26 - maptext_width = 32 - maptext_y = -1 - - var/message1 = "" // message line 1 - var/message2 = "" // message line 2 - var/index1 // display index for scrolling messages or 0 if non-scrolling - var/index2 - -/// Immediately blank the display. -/obj/machinery/status_display/proc/remove_display() - cut_overlays() - if(maptext) - maptext = "" - -/// Immediately change the display to the given picture. -/obj/machinery/status_display/proc/set_picture(state) - remove_display() - add_overlay(state) - -/// Immediately change the display to the given two lines. -/obj/machinery/status_display/proc/update_display(line1, line2) - line1 = uppertext(line1) - line2 = uppertext(line2) - var/new_text = {"
                    [line1]
                    [line2]
                    "} - if(maptext != new_text) - maptext = new_text - -/// Prepare the display to marquee the given two lines. -/// -/// Call with no arguments to disable. -/obj/machinery/status_display/proc/set_message(m1, m2) - if(m1) - index1 = (length_char(m1) > CHARS_PER_LINE) - message1 = m1 - else - message1 = "" - index1 = 0 - - if(m2) - index2 = (length_char(m2) > CHARS_PER_LINE) - message2 = m2 - else - message2 = "" - index2 = 0 - -// Timed process - performs default marquee action if so needed. -/obj/machinery/status_display/process() - if(machine_stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - var/line1 = message1 - if(index1) - line1 = copytext_char("[message1]|[message1]", index1, index1 + CHARS_PER_LINE) - var/message1_len = length_char(message1) - index1 += SCROLL_SPEED - if(index1 > message1_len + 1) - index1 -= (message1_len + 1) - - var/line2 = message2 - if(index2) - line2 = copytext_char("[message2]|[message2]", index2, index2 + CHARS_PER_LINE) - var/message2_len = length_char(message2) - index2 += SCROLL_SPEED - if(index2 > message2_len + 1) - index2 -= (message2_len + 1) - - update_display(line1, line2) - if (!index1 && !index2) - // No marquee, no processing. - return PROCESS_KILL - -/// Update the display and, if necessary, re-enable processing. -/obj/machinery/status_display/proc/update() - if (process() != PROCESS_KILL) - START_PROCESSING(SSmachines, src) - -/obj/machinery/status_display/power_change() - . = ..() - update() - -/obj/machinery/status_display/emp_act(severity) - . = ..() - if(machine_stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF) - return - set_picture("ai_bsod") - -/obj/machinery/status_display/examine(mob/user) - . = ..() - if (message1 || message2) - . += "The display says:" - if (message1) - . += "
                    \t[html_encode(message1)]" - if (message2) - . += "
                    \t[html_encode(message2)]" - -// Helper procs for child display types. -/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle) - if(!shuttle) - // the shuttle is missing - no processing - update_display("shutl?","") - return PROCESS_KILL - else if(shuttle.timer) - var/line1 = "-[shuttle.getModeStr()]-" - var/line2 = shuttle.getTimerStr() - - if(length_char(line2) > CHARS_PER_LINE) - line2 = "error" - update_display(line1, line2) - else - // don't kill processing, the timer might turn back on - remove_display() - -/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle) - if (shuttle) - var/modestr = shuttle.getModeStr() - if (modestr) - if (shuttle.timer) - modestr = "
                    \t[modestr]: [shuttle.getTimerStr()]" - else - modestr = "
                    \t[modestr]" - return "The display says:
                    \t[shuttle.name][modestr]" - else - return "The display says:
                    \tShuttle missing!" - - -/// Evac display which shows shuttle timer or message set by Command. -/obj/machinery/status_display/evac - var/frequency = FREQ_STATUS_DISPLAYS - var/mode = SD_EMERGENCY - var/friendc = FALSE // track if Friend Computer mode - var/last_picture // For when Friend Computer mode is undone - -/obj/machinery/status_display/evac/Initialize() - . = ..() - // register for radio system - SSradio.add_object(src, frequency) - -/obj/machinery/status_display/evac/Destroy() - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/status_display/evac/process() - if(machine_stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - if(friendc) //Makes all status displays except supply shuttle timer display the eye -- Urist - set_picture("ai_friend") - return PROCESS_KILL - - switch(mode) - if(SD_BLANK) - remove_display() - return PROCESS_KILL - - if(SD_EMERGENCY) - return display_shuttle_status(SSshuttle.emergency) - - if(SD_MESSAGE) - return ..() - - if(SD_PICTURE) - set_picture(last_picture) - return PROCESS_KILL - -/obj/machinery/status_display/evac/examine(mob/user) - . = ..() - if(mode == SD_EMERGENCY) - . += examine_shuttle(user, SSshuttle.emergency) - else if(!message1 && !message2) - . += "The display is blank." - -/obj/machinery/status_display/evac/receive_signal(datum/signal/signal) - switch(signal.data["command"]) - if("blank") - mode = SD_BLANK - set_message(null, null) - if("shuttle") - mode = SD_EMERGENCY - set_message(null, null) - if("message") - mode = SD_MESSAGE - set_message(signal.data["msg1"], signal.data["msg2"]) - if("alert") - mode = SD_PICTURE - last_picture = signal.data["picture_state"] - set_picture(last_picture) - if("friendcomputer") - friendc = !friendc - update() - - -/// Supply display which shows the status of the supply shuttle. -/obj/machinery/status_display/supply - name = "supply display" - -/obj/machinery/status_display/supply/process() - if(machine_stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - var/line1 - var/line2 - if(!SSshuttle.supply) - // Might be missing in our first update on initialize before shuttles - // have loaded. Cross our fingers that it will soon return. - line1 = "CARGO" - line2 = "shutl?" - else if(SSshuttle.supply.mode == SHUTTLE_IDLE) - if(is_station_level(SSshuttle.supply.z)) - line1 = "CARGO" - line2 = "Docked" - else - line1 = "CARGO" - line2 = SSshuttle.supply.getTimerStr() - if(length_char(line2) > CHARS_PER_LINE) - line2 = "Error" - update_display(line1, line2) - -/obj/machinery/status_display/supply/examine(mob/user) - . = ..() - var/obj/docking_port/mobile/shuttle = SSshuttle.supply - var/shuttleMsg = null - if (shuttle.mode == SHUTTLE_IDLE) - if (is_station_level(shuttle.z)) - shuttleMsg = "Docked" - else - shuttleMsg = "[shuttle.getModeStr()]: [shuttle.getTimerStr()]" - if (shuttleMsg) - . += "The display says:
                    \t[shuttleMsg]" - else - . += "The display is blank." - - -/// General-purpose shuttle status display. -/obj/machinery/status_display/shuttle - name = "shuttle display" - var/shuttle_id - -/obj/machinery/status_display/shuttle/process() - if(!shuttle_id || (machine_stat & NOPOWER)) - // No power, no processing. - remove_display() - return PROCESS_KILL - - return display_shuttle_status(SSshuttle.getShuttle(shuttle_id)) - -/obj/machinery/status_display/shuttle/examine(mob/user) - . = ..() - if(shuttle_id) - . += examine_shuttle(user, SSshuttle.getShuttle(shuttle_id)) - else - . += "The display is blank." - -/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value) - . = ..() - if(!.) - return - switch(var_name) - if(NAMEOF(src, shuttle_id)) - update() - -/obj/machinery/status_display/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) - if (port && (shuttle_id == initial(shuttle_id) || override)) - shuttle_id = port.id - update() - - -/// Pictograph display which the AI can use to emote. -/obj/machinery/status_display/ai - name = "\improper AI display" - desc = "A small screen which the AI can use to present itself." - - var/mode = SD_BLANK - var/emotion = "Neutral" - -/obj/machinery/status_display/ai/Initialize() - . = ..() - GLOB.ai_status_displays.Add(src) - -/obj/machinery/status_display/ai/Destroy() - GLOB.ai_status_displays.Remove(src) - . = ..() - -/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user) - if(isAI(user)) - user.ai_statuschange() - -/obj/machinery/status_display/ai/process() - if(mode == SD_BLANK || (machine_stat & NOPOWER)) - remove_display() - return PROCESS_KILL - - if(mode == SD_AI_EMOTE) - switch(emotion) - if("Very Happy") - set_picture("ai_veryhappy") - if("Happy") - set_picture("ai_happy") - if("Neutral") - set_picture("ai_neutral") - if("Unsure") - set_picture("ai_unsure") - if("Confused") - set_picture("ai_confused") - if("Sad") - set_picture("ai_sad") - if("BSOD") - set_picture("ai_bsod") - if("Blank") - set_picture("ai_off") - if("Problems?") - set_picture("ai_trollface") - if("Awesome") - set_picture("ai_awesome") - if("Dorfy") - set_picture("ai_urist") - if("Thinking") - set_picture("ai_thinking") - if("Facepalm") - set_picture("ai_facepalm") - if("Friend Computer") - set_picture("ai_friend") - if("Blue Glow") - set_picture("ai_sal") - if("Red Glow") - set_picture("ai_hal") - return PROCESS_KILL - - if(mode == SD_AI_BSOD) - set_picture("ai_bsod") - return PROCESS_KILL - - -#undef CHARS_PER_LINE -#undef FONT_SIZE -#undef FONT_COLOR -#undef FONT_STYLE -#undef SCROLL_SPEED +// Status display +// (formerly Countdown timer display) + +#define CHARS_PER_LINE 5 +#define FONT_SIZE "5pt" +#define FONT_COLOR "#09f" +#define FONT_STYLE "Small Fonts" +#define SCROLL_SPEED 2 + +#define SD_BLANK 0 // 0 = Blank +#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer +#define SD_MESSAGE 2 // 2 = Arbitrary message(s) +#define SD_PICTURE 3 // 3 = alert picture + +#define SD_AI_EMOTE 1 // 1 = AI emoticon +#define SD_AI_BSOD 2 // 2 = Blue screen of death + +/// Status display which can show images and scrolling text. +/obj/machinery/status_display + name = "status display" + desc = null + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + + maptext_height = 26 + maptext_width = 32 + maptext_y = -1 + + var/message1 = "" // message line 1 + var/message2 = "" // message line 2 + var/index1 // display index for scrolling messages or 0 if non-scrolling + var/index2 + +/// Immediately blank the display. +/obj/machinery/status_display/proc/remove_display() + cut_overlays() + if(maptext) + maptext = "" + +/// Immediately change the display to the given picture. +/obj/machinery/status_display/proc/set_picture(state) + remove_display() + add_overlay(state) + +/// Immediately change the display to the given two lines. +/obj/machinery/status_display/proc/update_display(line1, line2) + line1 = uppertext(line1) + line2 = uppertext(line2) + var/new_text = {"
                    [line1]
                    [line2]
                    "} + if(maptext != new_text) + maptext = new_text + +/// Prepare the display to marquee the given two lines. +/// +/// Call with no arguments to disable. +/obj/machinery/status_display/proc/set_message(m1, m2) + if(m1) + index1 = (length_char(m1) > CHARS_PER_LINE) + message1 = m1 + else + message1 = "" + index1 = 0 + + if(m2) + index2 = (length_char(m2) > CHARS_PER_LINE) + message2 = m2 + else + message2 = "" + index2 = 0 + +// Timed process - performs default marquee action if so needed. +/obj/machinery/status_display/process() + if(machine_stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + var/line1 = message1 + if(index1) + line1 = copytext_char("[message1]|[message1]", index1, index1 + CHARS_PER_LINE) + var/message1_len = length_char(message1) + index1 += SCROLL_SPEED + if(index1 > message1_len + 1) + index1 -= (message1_len + 1) + + var/line2 = message2 + if(index2) + line2 = copytext_char("[message2]|[message2]", index2, index2 + CHARS_PER_LINE) + var/message2_len = length_char(message2) + index2 += SCROLL_SPEED + if(index2 > message2_len + 1) + index2 -= (message2_len + 1) + + update_display(line1, line2) + if (!index1 && !index2) + // No marquee, no processing. + return PROCESS_KILL + +/// Update the display and, if necessary, re-enable processing. +/obj/machinery/status_display/proc/update() + if (process() != PROCESS_KILL) + START_PROCESSING(SSmachines, src) + +/obj/machinery/status_display/power_change() + . = ..() + update() + +/obj/machinery/status_display/emp_act(severity) + . = ..() + if(machine_stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF) + return + set_picture("ai_bsod") + +/obj/machinery/status_display/examine(mob/user) + . = ..() + if (message1 || message2) + . += "The display says:" + if (message1) + . += "
                    \t[html_encode(message1)]" + if (message2) + . += "
                    \t[html_encode(message2)]" + +// Helper procs for child display types. +/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle) + if(!shuttle) + // the shuttle is missing - no processing + update_display("shutl?","") + return PROCESS_KILL + else if(shuttle.timer) + var/line1 = "-[shuttle.getModeStr()]-" + var/line2 = shuttle.getTimerStr() + + if(length_char(line2) > CHARS_PER_LINE) + line2 = "error" + update_display(line1, line2) + else + // don't kill processing, the timer might turn back on + remove_display() + +/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle) + if (shuttle) + var/modestr = shuttle.getModeStr() + if (modestr) + if (shuttle.timer) + modestr = "
                    \t[modestr]: [shuttle.getTimerStr()]" + else + modestr = "
                    \t[modestr]" + return "The display says:
                    \t[shuttle.name][modestr]" + else + return "The display says:
                    \tShuttle missing!" + + +/// Evac display which shows shuttle timer or message set by Command. +/obj/machinery/status_display/evac + var/frequency = FREQ_STATUS_DISPLAYS + var/mode = SD_EMERGENCY + var/friendc = FALSE // track if Friend Computer mode + var/last_picture // For when Friend Computer mode is undone + +/obj/machinery/status_display/evac/Initialize() + . = ..() + // register for radio system + SSradio.add_object(src, frequency) + +/obj/machinery/status_display/evac/Destroy() + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/status_display/evac/process() + if(machine_stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + if(friendc) //Makes all status displays except supply shuttle timer display the eye -- Urist + set_picture("ai_friend") + return PROCESS_KILL + + switch(mode) + if(SD_BLANK) + remove_display() + return PROCESS_KILL + + if(SD_EMERGENCY) + return display_shuttle_status(SSshuttle.emergency) + + if(SD_MESSAGE) + return ..() + + if(SD_PICTURE) + set_picture(last_picture) + return PROCESS_KILL + +/obj/machinery/status_display/evac/examine(mob/user) + . = ..() + if(mode == SD_EMERGENCY) + . += examine_shuttle(user, SSshuttle.emergency) + else if(!message1 && !message2) + . += "The display is blank." + +/obj/machinery/status_display/evac/receive_signal(datum/signal/signal) + switch(signal.data["command"]) + if("blank") + mode = SD_BLANK + set_message(null, null) + if("shuttle") + mode = SD_EMERGENCY + set_message(null, null) + if("message") + mode = SD_MESSAGE + set_message(signal.data["msg1"], signal.data["msg2"]) + if("alert") + mode = SD_PICTURE + last_picture = signal.data["picture_state"] + set_picture(last_picture) + if("friendcomputer") + friendc = !friendc + update() + + +/// Supply display which shows the status of the supply shuttle. +/obj/machinery/status_display/supply + name = "supply display" + +/obj/machinery/status_display/supply/process() + if(machine_stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + var/line1 + var/line2 + if(!SSshuttle.supply) + // Might be missing in our first update on initialize before shuttles + // have loaded. Cross our fingers that it will soon return. + line1 = "CARGO" + line2 = "shutl?" + else if(SSshuttle.supply.mode == SHUTTLE_IDLE) + if(is_station_level(SSshuttle.supply.z)) + line1 = "CARGO" + line2 = "Docked" + else + line1 = "CARGO" + line2 = SSshuttle.supply.getTimerStr() + if(length_char(line2) > CHARS_PER_LINE) + line2 = "Error" + update_display(line1, line2) + +/obj/machinery/status_display/supply/examine(mob/user) + . = ..() + var/obj/docking_port/mobile/shuttle = SSshuttle.supply + var/shuttleMsg = null + if (shuttle.mode == SHUTTLE_IDLE) + if (is_station_level(shuttle.z)) + shuttleMsg = "Docked" + else + shuttleMsg = "[shuttle.getModeStr()]: [shuttle.getTimerStr()]" + if (shuttleMsg) + . += "The display says:
                    \t[shuttleMsg]" + else + . += "The display is blank." + + +/// General-purpose shuttle status display. +/obj/machinery/status_display/shuttle + name = "shuttle display" + var/shuttle_id + +/obj/machinery/status_display/shuttle/process() + if(!shuttle_id || (machine_stat & NOPOWER)) + // No power, no processing. + remove_display() + return PROCESS_KILL + + return display_shuttle_status(SSshuttle.getShuttle(shuttle_id)) + +/obj/machinery/status_display/shuttle/examine(mob/user) + . = ..() + if(shuttle_id) + . += examine_shuttle(user, SSshuttle.getShuttle(shuttle_id)) + else + . += "The display is blank." + +/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value) + . = ..() + if(!.) + return + switch(var_name) + if(NAMEOF(src, shuttle_id)) + update() + +/obj/machinery/status_display/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) + if (port && (shuttle_id == initial(shuttle_id) || override)) + shuttle_id = port.id + update() + + +/// Pictograph display which the AI can use to emote. +/obj/machinery/status_display/ai + name = "\improper AI display" + desc = "A small screen which the AI can use to present itself." + + var/mode = SD_BLANK + var/emotion = "Neutral" + +/obj/machinery/status_display/ai/Initialize() + . = ..() + GLOB.ai_status_displays.Add(src) + +/obj/machinery/status_display/ai/Destroy() + GLOB.ai_status_displays.Remove(src) + . = ..() + +/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user) + if(isAI(user)) + user.ai_statuschange() + +/obj/machinery/status_display/ai/process() + if(mode == SD_BLANK || (machine_stat & NOPOWER)) + remove_display() + return PROCESS_KILL + + if(mode == SD_AI_EMOTE) + switch(emotion) + if("Very Happy") + set_picture("ai_veryhappy") + if("Happy") + set_picture("ai_happy") + if("Neutral") + set_picture("ai_neutral") + if("Unsure") + set_picture("ai_unsure") + if("Confused") + set_picture("ai_confused") + if("Sad") + set_picture("ai_sad") + if("BSOD") + set_picture("ai_bsod") + if("Blank") + set_picture("ai_off") + if("Problems?") + set_picture("ai_trollface") + if("Awesome") + set_picture("ai_awesome") + if("Dorfy") + set_picture("ai_urist") + if("Thinking") + set_picture("ai_thinking") + if("Facepalm") + set_picture("ai_facepalm") + if("Friend Computer") + set_picture("ai_friend") + if("Blue Glow") + set_picture("ai_sal") + if("Red Glow") + set_picture("ai_hal") + return PROCESS_KILL + + if(mode == SD_AI_BSOD) + set_picture("ai_bsod") + return PROCESS_KILL + + +#undef CHARS_PER_LINE +#undef FONT_SIZE +#undef FONT_COLOR +#undef FONT_STYLE +#undef SCROLL_SPEED diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index 420014ea5e1..79c2c4e04cc 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -1,513 +1,513 @@ -// SUIT STORAGE UNIT ///////////////// -/obj/machinery/suit_storage_unit - name = "suit storage unit" - desc = "An industrial unit made to hold and decontaminate irradiated equipment. It comes with a built-in UV cauterization mechanism. A small warning label advises that organic matter should not be placed into the unit." - icon = 'icons/obj/machines/suit_storage.dmi' - icon_state = "close" - use_power = ACTIVE_POWER_USE - active_power_usage = 60 - power_channel = AREA_USAGE_EQUIP - density = TRUE - max_integrity = 250 - ui_x = 400 - ui_y = 305 - - var/obj/item/clothing/suit/space/suit = null - var/obj/item/clothing/head/helmet/space/helmet = null - var/obj/item/clothing/mask/mask = null - var/obj/item/storage = null - // if you add more storage slots, update cook() to clear their radiation too. - - /// What type of spacesuit the unit starts with when spawned. - var/suit_type = null - /// What type of space helmet the unit starts with when spawned. - var/helmet_type = null - /// What type of breathmask the unit starts with when spawned. - var/mask_type = null - /// What type of additional item the unit starts with when spawned. - var/storage_type = null - - state_open = FALSE - /// If the SSU's doors are locked closed. Can be toggled manually via the UI, but is also locked automatically when the UV decontamination sequence is running. - var/locked = FALSE - panel_open = FALSE - /// If the safety wire is cut/pulsed, the SSU can run the decontamination sequence while occupied by a mob. The mob will be burned during every cycle of cook(). - var/safeties = TRUE - - /// If UV decontamination sequence is running. See cook() - var/uv = FALSE - /** - * If the hack wire is cut/pulsed. - * Modifies effects of cook() - * * If FALSE, decontamination sequence will clear radiation for all atoms (and their contents) contained inside the unit, and burn any mobs inside. - * * If TRUE, decontamination sequence will delete all items contained within, and if occupied by a mob, intensifies burn damage delt. All wires will be cut at the end. - */ - var/uv_super = FALSE - /// How many cycles remain for the decontamination sequence. - var/uv_cycles = 6 - /// Cooldown for occupant breakout messages via relaymove() - var/message_cooldown - /// How long it takes to break out of the SSU. - var/breakout_time = 300 - /// How fast it charges cells in a suit - var/charge_rate = 500 - -/obj/machinery/suit_storage_unit/standard_unit - suit_type = /obj/item/clothing/suit/space/eva - helmet_type = /obj/item/clothing/head/helmet/space/eva - mask_type = /obj/item/clothing/mask/breath - -/obj/machinery/suit_storage_unit/captain - suit_type = /obj/item/clothing/suit/space/hardsuit/swat/captain - mask_type = /obj/item/clothing/mask/gas/atmos/captain - storage_type = /obj/item/tank/jetpack/oxygen/captain - -/obj/machinery/suit_storage_unit/engine - suit_type = /obj/item/clothing/suit/space/hardsuit/engine - mask_type = /obj/item/clothing/mask/breath - -/obj/machinery/suit_storage_unit/atmos - suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos - mask_type = /obj/item/clothing/mask/gas/atmos - storage_type = /obj/item/watertank/atmos - -/obj/machinery/suit_storage_unit/ce - suit_type = /obj/item/clothing/suit/space/hardsuit/engine/elite - mask_type = /obj/item/clothing/mask/breath - storage_type= /obj/item/clothing/shoes/magboots/advance - -/obj/machinery/suit_storage_unit/security - suit_type = /obj/item/clothing/suit/space/hardsuit/security - mask_type = /obj/item/clothing/mask/gas/sechailer - -/obj/machinery/suit_storage_unit/hos - suit_type = /obj/item/clothing/suit/space/hardsuit/security/hos - mask_type = /obj/item/clothing/mask/gas/sechailer - storage_type = /obj/item/tank/internals/oxygen - -/obj/machinery/suit_storage_unit/mining - suit_type = /obj/item/clothing/suit/hooded/explorer - mask_type = /obj/item/clothing/mask/gas/explorer - -/obj/machinery/suit_storage_unit/mining/eva - suit_type = /obj/item/clothing/suit/space/hardsuit/mining - mask_type = /obj/item/clothing/mask/breath - -/obj/machinery/suit_storage_unit/cmo - suit_type = /obj/item/clothing/suit/space/hardsuit/medical - mask_type = /obj/item/clothing/mask/breath/medical - storage_type = /obj/item/tank/internals/oxygen - -/obj/machinery/suit_storage_unit/rd - suit_type = /obj/item/clothing/suit/space/hardsuit/rd - mask_type = /obj/item/clothing/mask/breath - -/obj/machinery/suit_storage_unit/syndicate - suit_type = /obj/item/clothing/suit/space/hardsuit/syndi - mask_type = /obj/item/clothing/mask/gas/syndicate - storage_type = /obj/item/tank/jetpack/oxygen/harness - -/obj/machinery/suit_storage_unit/ert/command - suit_type = /obj/item/clothing/suit/space/hardsuit/ert - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/ert/security - suit_type = /obj/item/clothing/suit/space/hardsuit/ert/sec - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/ert/engineer - suit_type = /obj/item/clothing/suit/space/hardsuit/ert/engi - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/ert/medical - suit_type = /obj/item/clothing/suit/space/hardsuit/ert/med - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/radsuit - name = "radiation suit storage unit" - suit_type = /obj/item/clothing/suit/radiation - helmet_type = /obj/item/clothing/head/radiation - storage_type = /obj/item/geiger_counter - -/obj/machinery/suit_storage_unit/open - state_open = TRUE - density = FALSE - -/obj/machinery/suit_storage_unit/Initialize() - . = ..() - wires = new /datum/wires/suit_storage_unit(src) - if(suit_type) - suit = new suit_type(src) - if(helmet_type) - helmet = new helmet_type(src) - if(mask_type) - mask = new mask_type(src) - if(storage_type) - storage = new storage_type(src) - update_icon() - -/obj/machinery/suit_storage_unit/Destroy() - QDEL_NULL(suit) - QDEL_NULL(helmet) - QDEL_NULL(mask) - QDEL_NULL(storage) - return ..() - -/obj/machinery/suit_storage_unit/update_overlays() - . = ..() - - if(uv) - if(uv_super) - . += "super" - else if(occupant) - . += "uvhuman" - else - . += "uv" - else if(state_open) - if(machine_stat & BROKEN) - . += "broken" - else - . += "open" - if(suit) - . += "suit" - if(helmet) - . += "helm" - if(storage) - . += "storage" - else if(occupant) - . += "human" - -/obj/machinery/suit_storage_unit/power_change() - . = ..() - if(!is_operational() && state_open) - open_machine() - dump_contents() - update_icon() - -/obj/machinery/suit_storage_unit/dump_contents() - dropContents() - helmet = null - suit = null - mask = null - storage = null - occupant = null - -/obj/machinery/suit_storage_unit/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - open_machine() - dump_contents() - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/machinery/suit_storage_unit/MouseDrop_T(atom/A, mob/living/user) - if(!istype(user) || user.stat || !Adjacent(user) || !Adjacent(A) || !isliving(A)) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - var/mob/living/target = A - if(!state_open) - to_chat(user, "The unit's doors are shut!") - return - if(!is_operational()) - to_chat(user, "The unit is not operational!") - return - if(occupant || helmet || suit || storage) - to_chat(user, "It's too cluttered inside to fit in!") - return - - if(target == user) - user.visible_message("[user] starts squeezing into [src]!", "You start working your way into [src]...") - else - target.visible_message("[user] starts shoving [target] into [src]!", "[user] starts shoving you into [src]!") - - if(do_mob(user, target, 30)) - if(occupant || helmet || suit || storage) - return - if(target == user) - user.visible_message("[user] slips into [src] and closes the door behind [user.p_them()]!", "You slip into [src]'s cramped space and shut its door.") - else - target.visible_message("[user] pushes [target] into [src] and shuts its door!", "[user] shoves you into [src] and shuts the door!") - close_machine(target) - add_fingerprint(user) - -/** - * UV decontamination sequence. - * Duration is determined by the uv_cycles var. - * Effects determined by the uv_super var. - * * If FALSE, all atoms (and their contents) contained are cleared of radiation. If a mob is inside, they are burned every cycle. - * * If TRUE, all items contained are destroyed, and burn damage applied to the mob is increased. All wires will be cut at the end. - * All atoms still inside at the end of all cycles are ejected from the unit. -*/ -/obj/machinery/suit_storage_unit/proc/cook() - var/mob/living/mob_occupant = occupant - if(uv_cycles) - uv_cycles-- - uv = TRUE - locked = TRUE - update_icon() - if(occupant) - if(uv_super) - mob_occupant.adjustFireLoss(rand(20, 36)) - else - mob_occupant.adjustFireLoss(rand(10, 16)) - mob_occupant.emote("scream") - addtimer(CALLBACK(src, .proc/cook), 50) - else - uv_cycles = initial(uv_cycles) - uv = FALSE - locked = FALSE - if(uv_super) - visible_message("[src]'s door creaks open with a loud whining noise. A cloud of foul black smoke escapes from its chamber.") - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE) - helmet = null - qdel(helmet) - suit = null - qdel(suit) // Delete everything but the occupant. - mask = null - qdel(mask) - storage = null - qdel(storage) - // The wires get damaged too. - wires.cut_all() - else - if(!occupant) - visible_message("[src]'s door slides open. The glowing yellow lights dim to a gentle green.") - else - visible_message("[src]'s door slides open, barraging you with the nauseating smell of charred flesh.") - mob_occupant.radiation = 0 - playsound(src, 'sound/machines/airlockclose.ogg', 25, TRUE) - var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such. - if(suit) - things_to_clear += suit - things_to_clear += suit.GetAllContents() - if(helmet) - things_to_clear += helmet - things_to_clear += helmet.GetAllContents() - if(mask) - things_to_clear += mask - things_to_clear += mask.GetAllContents() - if(storage) - things_to_clear += storage - things_to_clear += storage.GetAllContents() - if(occupant) - things_to_clear += occupant - things_to_clear += occupant.GetAllContents() - for(var/atom/movable/AM in things_to_clear) //Scorches away blood and forensic evidence, although the SSU itself is unaffected - SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRONG) - var/datum/component/radioactive/contamination = AM.GetComponent(/datum/component/radioactive) - if(contamination) - qdel(contamination) - open_machine(FALSE) - if(occupant) - dump_contents() - -/obj/machinery/suit_storage_unit/process() - if(!suit) - return - if(!istype(suit, /obj/item/clothing/suit/space)) - return - if(!suit.cell) - return - - var/obj/item/stock_parts/cell/C = suit.cell - use_power(charge_rate) - C.give(charge_rate) - -/obj/machinery/suit_storage_unit/proc/shock(mob/user, prb) - if(!prob(prb)) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(5, 1, src) - s.start() - if(electrocute_mob(user, src, src, 1, TRUE)) - return 1 - -/obj/machinery/suit_storage_unit/relaymove(mob/user) - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - open_machine() - dump_contents() - -/obj/machinery/suit_storage_unit/container_resist(mob/living/user) - if(!locked) - open_machine() - dump_contents() - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("You see [user] kicking against the doors of [src]!", \ - "You start kicking against the doors... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a thump from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - dump_contents() - - add_fingerprint(user) - if(locked) - visible_message("You see [user] kicking against the doors of [src]!", \ - "You start kicking against the doors...") - addtimer(CALLBACK(src, .proc/resist_open, user), 300) - else - open_machine() - dump_contents() - -/obj/machinery/suit_storage_unit/proc/resist_open(mob/user) - if(!state_open && occupant && (user in src) && user.stat == 0) // Check they're still here. - visible_message("You see [user] burst out of [src]!", \ - "You escape the cramped confines of [src]!") - open_machine() - -/obj/machinery/suit_storage_unit/attackby(obj/item/I, mob/user, params) - if(state_open && is_operational()) - if(istype(I, /obj/item/clothing/suit)) - if(suit) - to_chat(user, "The unit already contains a suit!.") - return - if(!user.transferItemToLoc(I, src)) - return - suit = I - else if(istype(I, /obj/item/clothing/head)) - if(helmet) - to_chat(user, "The unit already contains a helmet!") - return - if(!user.transferItemToLoc(I, src)) - return - helmet = I - else if(istype(I, /obj/item/clothing/mask)) - if(mask) - to_chat(user, "The unit already contains a mask!") - return - if(!user.transferItemToLoc(I, src)) - return - mask = I - else - if(storage) - to_chat(user, "The auxiliary storage compartment is full!") - return - if(!user.transferItemToLoc(I, src)) - return - storage = I - - visible_message("[user] inserts [I] into [src]", "You load [I] into [src].") - update_icon() - return - - if(panel_open && is_wire_tool(I)) - wires.interact(user) - return - if(!state_open) - if(default_deconstruction_screwdriver(user, "panel", "close", I)) - return - if(default_pry_open(I)) - dump_contents() - return - - return ..() - -/* ref tg-git issue #45036 - screwdriving it open while it's running a decontamination sequence without closing the panel prior to finish - causes the SSU to break due to state_open being set to TRUE at the end, and the panel becoming inaccessible. -*/ -/obj/machinery/suit_storage_unit/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I) - if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_SCREWDRIVER && uv) - to_chat(user, "It might not be wise to fiddle with [src] while it's running...") - return TRUE - return ..() - - -/obj/machinery/suit_storage_unit/default_pry_open(obj/item/I)//needs to check if the storage is locked. - . = !(state_open || panel_open || is_operational() || locked || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR - if(.) - I.play_tool_sound(src, 50) - visible_message("[usr] pries open \the [src].", "You pry open \the [src].") - open_machine() - -/obj/machinery/suit_storage_unit/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "SuitStorageUnit", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/suit_storage_unit/ui_data() - var/list/data = list() - data["locked"] = locked - data["open"] = state_open - data["safeties"] = safeties - data["uv_active"] = uv - data["uv_super"] = uv_super - if(helmet) - data["helmet"] = helmet.name - else - data["helmet"] = null - if(suit) - data["suit"] = suit.name - else - data["suit"] = null - if(mask) - data["mask"] = mask.name - else - data["mask"] = null - if(storage) - data["storage"] = storage.name - else - data["storage"] = null - if(occupant) - data["occupied"] = TRUE - else - data["occupied"] = FALSE - return data - -/obj/machinery/suit_storage_unit/ui_act(action, params) - if(..() || uv) - return - switch(action) - if("door") - if(state_open) - close_machine() - else - open_machine(0) - if(occupant) - dump_contents() // Dump out contents if someone is in there. - . = TRUE - if("lock") - if(state_open) - return - locked = !locked - . = TRUE - if("uv") - if(occupant && safeties) - return - else if(!helmet && !mask && !suit && !storage && !occupant) - return - else - if(occupant) - var/mob/living/mob_occupant = occupant - to_chat(mob_occupant, "[src]'s confines grow warm, then hot, then scorching. You're being burned [!mob_occupant.stat ? "alive" : "away"]!") - cook() - . = TRUE - if("dispense") - if(!state_open) - return - - var/static/list/valid_items = list("helmet", "suit", "mask", "storage") - var/item_name = params["item"] - if(item_name in valid_items) - var/obj/item/I = vars[item_name] - vars[item_name] = null - if(I) - I.forceMove(loc) - . = TRUE - update_icon() +// SUIT STORAGE UNIT ///////////////// +/obj/machinery/suit_storage_unit + name = "suit storage unit" + desc = "An industrial unit made to hold and decontaminate irradiated equipment. It comes with a built-in UV cauterization mechanism. A small warning label advises that organic matter should not be placed into the unit." + icon = 'icons/obj/machines/suit_storage.dmi' + icon_state = "close" + use_power = ACTIVE_POWER_USE + active_power_usage = 60 + power_channel = AREA_USAGE_EQUIP + density = TRUE + max_integrity = 250 + ui_x = 400 + ui_y = 305 + + var/obj/item/clothing/suit/space/suit = null + var/obj/item/clothing/head/helmet/space/helmet = null + var/obj/item/clothing/mask/mask = null + var/obj/item/storage = null + // if you add more storage slots, update cook() to clear their radiation too. + + /// What type of spacesuit the unit starts with when spawned. + var/suit_type = null + /// What type of space helmet the unit starts with when spawned. + var/helmet_type = null + /// What type of breathmask the unit starts with when spawned. + var/mask_type = null + /// What type of additional item the unit starts with when spawned. + var/storage_type = null + + state_open = FALSE + /// If the SSU's doors are locked closed. Can be toggled manually via the UI, but is also locked automatically when the UV decontamination sequence is running. + var/locked = FALSE + panel_open = FALSE + /// If the safety wire is cut/pulsed, the SSU can run the decontamination sequence while occupied by a mob. The mob will be burned during every cycle of cook(). + var/safeties = TRUE + + /// If UV decontamination sequence is running. See cook() + var/uv = FALSE + /** + * If the hack wire is cut/pulsed. + * Modifies effects of cook() + * * If FALSE, decontamination sequence will clear radiation for all atoms (and their contents) contained inside the unit, and burn any mobs inside. + * * If TRUE, decontamination sequence will delete all items contained within, and if occupied by a mob, intensifies burn damage delt. All wires will be cut at the end. + */ + var/uv_super = FALSE + /// How many cycles remain for the decontamination sequence. + var/uv_cycles = 6 + /// Cooldown for occupant breakout messages via relaymove() + var/message_cooldown + /// How long it takes to break out of the SSU. + var/breakout_time = 300 + /// How fast it charges cells in a suit + var/charge_rate = 500 + +/obj/machinery/suit_storage_unit/standard_unit + suit_type = /obj/item/clothing/suit/space/eva + helmet_type = /obj/item/clothing/head/helmet/space/eva + mask_type = /obj/item/clothing/mask/breath + +/obj/machinery/suit_storage_unit/captain + suit_type = /obj/item/clothing/suit/space/hardsuit/swat/captain + mask_type = /obj/item/clothing/mask/gas/atmos/captain + storage_type = /obj/item/tank/jetpack/oxygen/captain + +/obj/machinery/suit_storage_unit/engine + suit_type = /obj/item/clothing/suit/space/hardsuit/engine + mask_type = /obj/item/clothing/mask/breath + +/obj/machinery/suit_storage_unit/atmos + suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos + mask_type = /obj/item/clothing/mask/gas/atmos + storage_type = /obj/item/watertank/atmos + +/obj/machinery/suit_storage_unit/ce + suit_type = /obj/item/clothing/suit/space/hardsuit/engine/elite + mask_type = /obj/item/clothing/mask/breath + storage_type= /obj/item/clothing/shoes/magboots/advance + +/obj/machinery/suit_storage_unit/security + suit_type = /obj/item/clothing/suit/space/hardsuit/security + mask_type = /obj/item/clothing/mask/gas/sechailer + +/obj/machinery/suit_storage_unit/hos + suit_type = /obj/item/clothing/suit/space/hardsuit/security/hos + mask_type = /obj/item/clothing/mask/gas/sechailer + storage_type = /obj/item/tank/internals/oxygen + +/obj/machinery/suit_storage_unit/mining + suit_type = /obj/item/clothing/suit/hooded/explorer + mask_type = /obj/item/clothing/mask/gas/explorer + +/obj/machinery/suit_storage_unit/mining/eva + suit_type = /obj/item/clothing/suit/space/hardsuit/mining + mask_type = /obj/item/clothing/mask/breath + +/obj/machinery/suit_storage_unit/cmo + suit_type = /obj/item/clothing/suit/space/hardsuit/medical + mask_type = /obj/item/clothing/mask/breath/medical + storage_type = /obj/item/tank/internals/oxygen + +/obj/machinery/suit_storage_unit/rd + suit_type = /obj/item/clothing/suit/space/hardsuit/rd + mask_type = /obj/item/clothing/mask/breath + +/obj/machinery/suit_storage_unit/syndicate + suit_type = /obj/item/clothing/suit/space/hardsuit/syndi + mask_type = /obj/item/clothing/mask/gas/syndicate + storage_type = /obj/item/tank/jetpack/oxygen/harness + +/obj/machinery/suit_storage_unit/ert/command + suit_type = /obj/item/clothing/suit/space/hardsuit/ert + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/ert/security + suit_type = /obj/item/clothing/suit/space/hardsuit/ert/sec + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/ert/engineer + suit_type = /obj/item/clothing/suit/space/hardsuit/ert/engi + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/ert/medical + suit_type = /obj/item/clothing/suit/space/hardsuit/ert/med + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/radsuit + name = "radiation suit storage unit" + suit_type = /obj/item/clothing/suit/radiation + helmet_type = /obj/item/clothing/head/radiation + storage_type = /obj/item/geiger_counter + +/obj/machinery/suit_storage_unit/open + state_open = TRUE + density = FALSE + +/obj/machinery/suit_storage_unit/Initialize() + . = ..() + wires = new /datum/wires/suit_storage_unit(src) + if(suit_type) + suit = new suit_type(src) + if(helmet_type) + helmet = new helmet_type(src) + if(mask_type) + mask = new mask_type(src) + if(storage_type) + storage = new storage_type(src) + update_icon() + +/obj/machinery/suit_storage_unit/Destroy() + QDEL_NULL(suit) + QDEL_NULL(helmet) + QDEL_NULL(mask) + QDEL_NULL(storage) + return ..() + +/obj/machinery/suit_storage_unit/update_overlays() + . = ..() + + if(uv) + if(uv_super) + . += "super" + else if(occupant) + . += "uvhuman" + else + . += "uv" + else if(state_open) + if(machine_stat & BROKEN) + . += "broken" + else + . += "open" + if(suit) + . += "suit" + if(helmet) + . += "helm" + if(storage) + . += "storage" + else if(occupant) + . += "human" + +/obj/machinery/suit_storage_unit/power_change() + . = ..() + if(!is_operational() && state_open) + open_machine() + dump_contents() + update_icon() + +/obj/machinery/suit_storage_unit/dump_contents() + dropContents() + helmet = null + suit = null + mask = null + storage = null + occupant = null + +/obj/machinery/suit_storage_unit/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + open_machine() + dump_contents() + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/machinery/suit_storage_unit/MouseDrop_T(atom/A, mob/living/user) + if(!istype(user) || user.stat || !Adjacent(user) || !Adjacent(A) || !isliving(A)) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + var/mob/living/target = A + if(!state_open) + to_chat(user, "The unit's doors are shut!") + return + if(!is_operational()) + to_chat(user, "The unit is not operational!") + return + if(occupant || helmet || suit || storage) + to_chat(user, "It's too cluttered inside to fit in!") + return + + if(target == user) + user.visible_message("[user] starts squeezing into [src]!", "You start working your way into [src]...") + else + target.visible_message("[user] starts shoving [target] into [src]!", "[user] starts shoving you into [src]!") + + if(do_mob(user, target, 30)) + if(occupant || helmet || suit || storage) + return + if(target == user) + user.visible_message("[user] slips into [src] and closes the door behind [user.p_them()]!", "You slip into [src]'s cramped space and shut its door.") + else + target.visible_message("[user] pushes [target] into [src] and shuts its door!", "[user] shoves you into [src] and shuts the door!") + close_machine(target) + add_fingerprint(user) + +/** + * UV decontamination sequence. + * Duration is determined by the uv_cycles var. + * Effects determined by the uv_super var. + * * If FALSE, all atoms (and their contents) contained are cleared of radiation. If a mob is inside, they are burned every cycle. + * * If TRUE, all items contained are destroyed, and burn damage applied to the mob is increased. All wires will be cut at the end. + * All atoms still inside at the end of all cycles are ejected from the unit. +*/ +/obj/machinery/suit_storage_unit/proc/cook() + var/mob/living/mob_occupant = occupant + if(uv_cycles) + uv_cycles-- + uv = TRUE + locked = TRUE + update_icon() + if(occupant) + if(uv_super) + mob_occupant.adjustFireLoss(rand(20, 36)) + else + mob_occupant.adjustFireLoss(rand(10, 16)) + mob_occupant.emote("scream") + addtimer(CALLBACK(src, .proc/cook), 50) + else + uv_cycles = initial(uv_cycles) + uv = FALSE + locked = FALSE + if(uv_super) + visible_message("[src]'s door creaks open with a loud whining noise. A cloud of foul black smoke escapes from its chamber.") + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE) + helmet = null + qdel(helmet) + suit = null + qdel(suit) // Delete everything but the occupant. + mask = null + qdel(mask) + storage = null + qdel(storage) + // The wires get damaged too. + wires.cut_all() + else + if(!occupant) + visible_message("[src]'s door slides open. The glowing yellow lights dim to a gentle green.") + else + visible_message("[src]'s door slides open, barraging you with the nauseating smell of charred flesh.") + mob_occupant.radiation = 0 + playsound(src, 'sound/machines/airlockclose.ogg', 25, TRUE) + var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such. + if(suit) + things_to_clear += suit + things_to_clear += suit.GetAllContents() + if(helmet) + things_to_clear += helmet + things_to_clear += helmet.GetAllContents() + if(mask) + things_to_clear += mask + things_to_clear += mask.GetAllContents() + if(storage) + things_to_clear += storage + things_to_clear += storage.GetAllContents() + if(occupant) + things_to_clear += occupant + things_to_clear += occupant.GetAllContents() + for(var/atom/movable/AM in things_to_clear) //Scorches away blood and forensic evidence, although the SSU itself is unaffected + SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRONG) + var/datum/component/radioactive/contamination = AM.GetComponent(/datum/component/radioactive) + if(contamination) + qdel(contamination) + open_machine(FALSE) + if(occupant) + dump_contents() + +/obj/machinery/suit_storage_unit/process() + if(!suit) + return + if(!istype(suit, /obj/item/clothing/suit/space)) + return + if(!suit.cell) + return + + var/obj/item/stock_parts/cell/C = suit.cell + use_power(charge_rate) + C.give(charge_rate) + +/obj/machinery/suit_storage_unit/proc/shock(mob/user, prb) + if(!prob(prb)) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(5, 1, src) + s.start() + if(electrocute_mob(user, src, src, 1, TRUE)) + return 1 + +/obj/machinery/suit_storage_unit/relaymove(mob/user) + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + open_machine() + dump_contents() + +/obj/machinery/suit_storage_unit/container_resist(mob/living/user) + if(!locked) + open_machine() + dump_contents() + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("You see [user] kicking against the doors of [src]!", \ + "You start kicking against the doors... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a thump from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + dump_contents() + + add_fingerprint(user) + if(locked) + visible_message("You see [user] kicking against the doors of [src]!", \ + "You start kicking against the doors...") + addtimer(CALLBACK(src, .proc/resist_open, user), 300) + else + open_machine() + dump_contents() + +/obj/machinery/suit_storage_unit/proc/resist_open(mob/user) + if(!state_open && occupant && (user in src) && user.stat == 0) // Check they're still here. + visible_message("You see [user] burst out of [src]!", \ + "You escape the cramped confines of [src]!") + open_machine() + +/obj/machinery/suit_storage_unit/attackby(obj/item/I, mob/user, params) + if(state_open && is_operational()) + if(istype(I, /obj/item/clothing/suit)) + if(suit) + to_chat(user, "The unit already contains a suit!.") + return + if(!user.transferItemToLoc(I, src)) + return + suit = I + else if(istype(I, /obj/item/clothing/head)) + if(helmet) + to_chat(user, "The unit already contains a helmet!") + return + if(!user.transferItemToLoc(I, src)) + return + helmet = I + else if(istype(I, /obj/item/clothing/mask)) + if(mask) + to_chat(user, "The unit already contains a mask!") + return + if(!user.transferItemToLoc(I, src)) + return + mask = I + else + if(storage) + to_chat(user, "The auxiliary storage compartment is full!") + return + if(!user.transferItemToLoc(I, src)) + return + storage = I + + visible_message("[user] inserts [I] into [src]", "You load [I] into [src].") + update_icon() + return + + if(panel_open && is_wire_tool(I)) + wires.interact(user) + return + if(!state_open) + if(default_deconstruction_screwdriver(user, "panel", "close", I)) + return + if(default_pry_open(I)) + dump_contents() + return + + return ..() + +/* ref tg-git issue #45036 + screwdriving it open while it's running a decontamination sequence without closing the panel prior to finish + causes the SSU to break due to state_open being set to TRUE at the end, and the panel becoming inaccessible. +*/ +/obj/machinery/suit_storage_unit/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I) + if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_SCREWDRIVER && uv) + to_chat(user, "It might not be wise to fiddle with [src] while it's running...") + return TRUE + return ..() + + +/obj/machinery/suit_storage_unit/default_pry_open(obj/item/I)//needs to check if the storage is locked. + . = !(state_open || panel_open || is_operational() || locked || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR + if(.) + I.play_tool_sound(src, 50) + visible_message("[usr] pries open \the [src].", "You pry open \the [src].") + open_machine() + +/obj/machinery/suit_storage_unit/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "SuitStorageUnit", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/suit_storage_unit/ui_data() + var/list/data = list() + data["locked"] = locked + data["open"] = state_open + data["safeties"] = safeties + data["uv_active"] = uv + data["uv_super"] = uv_super + if(helmet) + data["helmet"] = helmet.name + else + data["helmet"] = null + if(suit) + data["suit"] = suit.name + else + data["suit"] = null + if(mask) + data["mask"] = mask.name + else + data["mask"] = null + if(storage) + data["storage"] = storage.name + else + data["storage"] = null + if(occupant) + data["occupied"] = TRUE + else + data["occupied"] = FALSE + return data + +/obj/machinery/suit_storage_unit/ui_act(action, params) + if(..() || uv) + return + switch(action) + if("door") + if(state_open) + close_machine() + else + open_machine(0) + if(occupant) + dump_contents() // Dump out contents if someone is in there. + . = TRUE + if("lock") + if(state_open) + return + locked = !locked + . = TRUE + if("uv") + if(occupant && safeties) + return + else if(!helmet && !mask && !suit && !storage && !occupant) + return + else + if(occupant) + var/mob/living/mob_occupant = occupant + to_chat(mob_occupant, "[src]'s confines grow warm, then hot, then scorching. You're being burned [!mob_occupant.stat ? "alive" : "away"]!") + cook() + . = TRUE + if("dispense") + if(!state_open) + return + + var/static/list/valid_items = list("helmet", "suit", "mask", "storage") + var/item_name = params["item"] + if(item_name in valid_items) + var/obj/item/I = vars[item_name] + vars[item_name] = null + if(I) + I.forceMove(loc) + . = TRUE + update_icon() diff --git a/code/game/machinery/syndicatebeacon.dm b/code/game/machinery/syndicatebeacon.dm index 189f0658e5a..bb2bb614c53 100644 --- a/code/game/machinery/syndicatebeacon.dm +++ b/code/game/machinery/syndicatebeacon.dm @@ -1,139 +1,139 @@ -//////////////////////////////////////// -//Singularity beacon -//////////////////////////////////////// -/obj/machinery/power/singularity_beacon - name = "ominous beacon" - desc = "This looks suspicious..." - icon = 'icons/obj/singularity.dmi' - icon_state = "beacon0" - - anchored = FALSE - density = TRUE - layer = BELOW_MOB_LAYER //so people can't hide it and it's REALLY OBVIOUS - machine_stat = 0 - verb_say = "states" - var/cooldown = 0 - - var/active = 0 - var/icontype = "beacon" - - -/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) - if(surplus() < 1500) - if(user) - to_chat(user, "The connected wire doesn't have enough current.") - return - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.z == z) - singulo.target = src - icon_state = "[icontype]1" - active = 1 - if(user) - to_chat(user, "You activate the beacon.") - - -/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null) - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.target == src) - singulo.target = null - icon_state = "[icontype]0" - active = 0 - if(user) - to_chat(user, "You deactivate the beacon.") - - -/obj/machinery/power/singularity_beacon/attack_ai(mob/user) - return - - -/obj/machinery/power/singularity_beacon/attack_hand(mob/user) - . = ..() - if(.) - return - if(anchored) - return active ? Deactivate(user) : Activate(user) - else - to_chat(user, "You need to screw \the [src] to the floor first!") - -/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WRENCH) - if(active) - to_chat(user, "You need to deactivate \the [src] first!") - return - - if(anchored) - setAnchored(FALSE) - to_chat(user, "You unbolt \the [src] from the floor and detach it from the cable.") - disconnect_from_network() - return - else - if(!connect_to_network()) - to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") - return - setAnchored(TRUE) - to_chat(user, "You bolt \the [src] to the floor and attach it to the cable.") - return - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message( \ - "[user] messes with \the [src] for a bit.", \ - "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") - else - return ..() - -/obj/machinery/power/singularity_beacon/Destroy() - if(active) - Deactivate() - return ..() - -//stealth direct power usage -/obj/machinery/power/singularity_beacon/process() - if(!active) - return - - if(surplus() >= 1500) - add_load(1500) - if(cooldown <= world.time) - cooldown = world.time + 80 - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.z == z) - say("[singulo] is now [get_dist(src,singulo)] standard lengths away to the [dir2text(get_dir(src,singulo))]") - else - Deactivate() - say("Insufficient charge detected - powering down") - - -/obj/machinery/power/singularity_beacon/syndicate - icontype = "beaconsynd" - icon_state = "beaconsynd0" - -// SINGULO BEACON SPAWNER -/obj/item/sbeacondrop - name = "suspicious beacon" - icon = 'icons/obj/device.dmi' - icon_state = "beacon" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - desc = "A label on it reads: Warning: Activating this device will send a special beacon to your location." - w_class = WEIGHT_CLASS_SMALL - var/droptype = /obj/machinery/power/singularity_beacon/syndicate - - -/obj/item/sbeacondrop/attack_self(mob/user) - if(user) - to_chat(user, "Locked In.") - new droptype( user.loc ) - playsound(src, 'sound/effects/pop.ogg', 100, TRUE, TRUE) - qdel(src) - return - -/obj/item/sbeacondrop/bomb - desc = "A label on it reads: Warning: Activating this device will send a high-ordinance explosive to your location." - droptype = /obj/machinery/syndicatebomb - -/obj/item/sbeacondrop/powersink - desc = "A label on it reads: Warning: Activating this device will send a power draining device to your location." - droptype = /obj/item/powersink - -/obj/item/sbeacondrop/clownbomb - desc = "A label on it reads: Warning: Activating this device will send a silly explosive to your location." - droptype = /obj/machinery/syndicatebomb/badmin/clown +//////////////////////////////////////// +//Singularity beacon +//////////////////////////////////////// +/obj/machinery/power/singularity_beacon + name = "ominous beacon" + desc = "This looks suspicious..." + icon = 'icons/obj/singularity.dmi' + icon_state = "beacon0" + + anchored = FALSE + density = TRUE + layer = BELOW_MOB_LAYER //so people can't hide it and it's REALLY OBVIOUS + machine_stat = 0 + verb_say = "states" + var/cooldown = 0 + + var/active = 0 + var/icontype = "beacon" + + +/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) + if(surplus() < 1500) + if(user) + to_chat(user, "The connected wire doesn't have enough current.") + return + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.z == z) + singulo.target = src + icon_state = "[icontype]1" + active = 1 + if(user) + to_chat(user, "You activate the beacon.") + + +/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null) + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.target == src) + singulo.target = null + icon_state = "[icontype]0" + active = 0 + if(user) + to_chat(user, "You deactivate the beacon.") + + +/obj/machinery/power/singularity_beacon/attack_ai(mob/user) + return + + +/obj/machinery/power/singularity_beacon/attack_hand(mob/user) + . = ..() + if(.) + return + if(anchored) + return active ? Deactivate(user) : Activate(user) + else + to_chat(user, "You need to screw \the [src] to the floor first!") + +/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WRENCH) + if(active) + to_chat(user, "You need to deactivate \the [src] first!") + return + + if(anchored) + setAnchored(FALSE) + to_chat(user, "You unbolt \the [src] from the floor and detach it from the cable.") + disconnect_from_network() + return + else + if(!connect_to_network()) + to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") + return + setAnchored(TRUE) + to_chat(user, "You bolt \the [src] to the floor and attach it to the cable.") + return + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message( \ + "[user] messes with \the [src] for a bit.", \ + "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") + else + return ..() + +/obj/machinery/power/singularity_beacon/Destroy() + if(active) + Deactivate() + return ..() + +//stealth direct power usage +/obj/machinery/power/singularity_beacon/process() + if(!active) + return + + if(surplus() >= 1500) + add_load(1500) + if(cooldown <= world.time) + cooldown = world.time + 80 + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.z == z) + say("[singulo] is now [get_dist(src,singulo)] standard lengths away to the [dir2text(get_dir(src,singulo))]") + else + Deactivate() + say("Insufficient charge detected - powering down") + + +/obj/machinery/power/singularity_beacon/syndicate + icontype = "beaconsynd" + icon_state = "beaconsynd0" + +// SINGULO BEACON SPAWNER +/obj/item/sbeacondrop + name = "suspicious beacon" + icon = 'icons/obj/device.dmi' + icon_state = "beacon" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + desc = "A label on it reads: Warning: Activating this device will send a special beacon to your location." + w_class = WEIGHT_CLASS_SMALL + var/droptype = /obj/machinery/power/singularity_beacon/syndicate + + +/obj/item/sbeacondrop/attack_self(mob/user) + if(user) + to_chat(user, "Locked In.") + new droptype( user.loc ) + playsound(src, 'sound/effects/pop.ogg', 100, TRUE, TRUE) + qdel(src) + return + +/obj/item/sbeacondrop/bomb + desc = "A label on it reads: Warning: Activating this device will send a high-ordinance explosive to your location." + droptype = /obj/machinery/syndicatebomb + +/obj/item/sbeacondrop/powersink + desc = "A label on it reads: Warning: Activating this device will send a power draining device to your location." + droptype = /obj/item/powersink + +/obj/item/sbeacondrop/clownbomb + desc = "A label on it reads: Warning: Activating this device will send a silly explosive to your location." + droptype = /obj/machinery/syndicatebomb/badmin/clown diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm index bf867c6e6d8..f3b77f19695 100644 --- a/code/game/machinery/telecomms/computers/message.dm +++ b/code/game/machinery/telecomms/computers/message.dm @@ -1,480 +1,480 @@ -/* - The monitoring computer for the messaging server. - Lets you read PDA and request console messages. -*/ - -#define LINKED_SERVER_NONRESPONSIVE (!linkedServer || (linkedServer.machine_stat & (NOPOWER|BROKEN))) - -#define MSG_MON_SCREEN_MAIN 0 -#define MSG_MON_SCREEN_LOGS 1 -#define MSG_MON_SCREEN_HACKED 2 -#define MSG_MON_SCREEN_CUSTOM_MSG 3 -#define MSG_MON_SCREEN_REQUEST_LOGS 4 - -// The monitor itself. -/obj/machinery/computer/message_monitor - name = "message monitor console" - desc = "Used to monitor the crew's PDA messages, as well as request console messages." - icon_screen = "comm_logs" - circuit = /obj/item/circuitboard/computer/message_monitor - //Server linked to. - var/obj/machinery/telecomms/message_server/linkedServer = null - //Sparks effect - For emag - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread - //Messages - Saves me time if I want to change something. - var/noserver = "ALERT: No server detected." - var/incorrectkey = "ALERT: Incorrect decryption key!" - var/defaultmsg = "Welcome. Please select an option." - var/rebootmsg = "%$&(£: Critical %$$@ Error // !RestArting! - ?pLeaSe wAit!" - //Computer properties - var/screen = MSG_MON_SCREEN_MAIN // 0 = Main menu, 1 = Message Logs, 2 = Hacked screen, 3 = Custom Message - var/hacking = FALSE // Is it being hacked into by the AI/Cyborg - var/message = "System bootup complete. Please select an option." // The message that shows on the main menu. - var/auth = FALSE // Are they authenticated? - var/optioncount = 7 - // Custom Message Properties - var/customsender = "System Administrator" - var/obj/item/pda/customrecepient = null - var/customjob = "Admin" - var/custommessage = "This is a test, please ignore." - - light_color = LIGHT_COLOR_GREEN - -/obj/machinery/computer/message_monitor/attackby(obj/item/O, mob/living/user, params) - if(O.tool_behaviour == TOOL_SCREWDRIVER && (obj_flags & EMAGGED)) - //Stops people from just unscrewing the monitor and putting it back to get the console working again. - to_chat(user, "It is too hot to mess with!") - else - return ..() - -/obj/machinery/computer/message_monitor/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - if(!isnull(linkedServer)) - obj_flags |= EMAGGED - screen = MSG_MON_SCREEN_HACKED - spark_system.set_up(5, 0, src) - spark_system.start() - var/obj/item/paper/monitorkey/MK = new(loc, linkedServer) - // Will help make emagging the console not so easy to get away with. - MK.info += "

                    £%@%(*$%&(£&?*(%&£/{}" - var/time = 100 * length(linkedServer.decryptkey) - addtimer(CALLBACK(src, .proc/UnmagConsole), time) - message = rebootmsg - else - to_chat(user, "A no server error appears on the screen.") - -/obj/machinery/computer/message_monitor/New() - ..() - GLOB.telecomms_list += src - -/obj/machinery/computer/message_monitor/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/computer/message_monitor/LateInitialize() - //Is the server isn't linked to a server, and there's a server available, default it to the first one in the list. - if(!linkedServer) - for(var/obj/machinery/telecomms/message_server/S in GLOB.telecomms_list) - linkedServer = S - break - -/obj/machinery/computer/message_monitor/Destroy() - GLOB.telecomms_list -= src - return ..() - -/obj/machinery/computer/message_monitor/ui_interact(mob/living/user) - . = ..() - //If the computer is being hacked or is emagged, display the reboot message. - if(hacking || (obj_flags & EMAGGED)) - message = rebootmsg - var/dat = "
                    " - - if(auth) - dat += "

                    \[Authenticated\] /" - dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " - else - dat += "

                    \[Unauthenticated\] /" - dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " - - if(hacking || (obj_flags & EMAGGED)) - screen = MSG_MON_SCREEN_HACKED - else if(!auth || LINKED_SERVER_NONRESPONSIVE) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - screen = MSG_MON_SCREEN_MAIN - - switch(screen) - //Main menu - if(MSG_MON_SCREEN_MAIN) - // = TAB - var/i = 0 - dat += "
                    [++i]. Link To A Server
                    " - if(auth) - if(LINKED_SERVER_NONRESPONSIVE) - dat += "
                    ERROR: Server not found!
                    " - else - dat += "
                    [++i]. View Message Logs
                    " - dat += "
                    [++i]. View Request Console Logs
                    " - dat += "
                    [++i]. Clear Message Logs
                    " - dat += "
                    [++i]. Clear Request Console Logs
                    " - dat += "
                    [++i]. Set Custom Key
                    " - dat += "
                    [++i]. Send Admin Message
                    " - else - for(var/n = ++i; n <= optioncount; n++) - dat += "
                    [n]. ---------------
                    " - var/mob/living/silicon/S = usr - if(istype(S) && S.hack_software) - //Malf/Traitor AIs can bruteforce into the system to gain the Key. - dat += "
                    *&@#. Bruteforce Key
                    " - else - dat += "
                    " - - //Bottom message - if(!auth) - dat += "

                    Please authenticate with the server in order to show additional options." - else - dat += "

                    Reg, #514 forbids sending messages to a Head of Staff containing Erotic Rendering Properties." - - //Message Logs - if(MSG_MON_SCREEN_LOGS) - var/index = 0 - dat += "
                    Back - Refresh

                    " - dat += "" - for(var/datum/data_pda_msg/pda in linkedServer.pda_msgs) - index++ - if(index > 3000) - break - // Del - Sender - Recepient - Message - // X - Al Green - Your Mom - WHAT UP!? - dat += "" - dat += "
                    XSenderRecipientMessage
                    X
                    [pda.sender][pda.recipient][pda.message][pda.picture ? " (Photo)":""]
                    " - //Hacking screen. - if(MSG_MON_SCREEN_HACKED) - if(isAI(user) || iscyborg(user)) - dat += "Brute-forcing for server key.
                    It will take 20 seconds for every character that the password has." - dat += "In the meantime, this console can reveal your true intentions if you let someone access it. Make sure no humans enter the room during that time." - else - //It's the same message as the one above but in binary. Because robots understand binary and humans don't... well I thought it was clever. - dat += {"01000010011100100111010101110100011001010010110
                    - 10110011001101111011100100110001101101001011011100110011
                    - 10010000001100110011011110111001000100000011100110110010
                    - 10111001001110110011001010111001000100000011010110110010
                    - 10111100100101110001000000100100101110100001000000111011
                    - 10110100101101100011011000010000001110100011000010110101
                    - 10110010100100000001100100011000000100000011100110110010
                    - 10110001101101111011011100110010001110011001000000110011
                    - 00110111101110010001000000110010101110110011001010111001
                    - 00111100100100000011000110110100001100001011100100110000
                    - 10110001101110100011001010111001000100000011101000110100
                    - 00110000101110100001000000111010001101000011001010010000
                    - 00111000001100001011100110111001101110111011011110111001
                    - 00110010000100000011010000110000101110011001011100010000
                    - 00100100101101110001000000111010001101000011001010010000
                    - 00110110101100101011000010110111001110100011010010110110
                    - 10110010100101100001000000111010001101000011010010111001
                    - 10010000001100011011011110110111001110011011011110110110
                    - 00110010100100000011000110110000101101110001000000111001
                    - 00110010101110110011001010110000101101100001000000111100
                    - 10110111101110101011100100010000001110100011100100111010
                    - 10110010100100000011010010110111001110100011001010110111
                    - 00111010001101001011011110110111001110011001000000110100
                    - 10110011000100000011110010110111101110101001000000110110
                    - 00110010101110100001000000111001101101111011011010110010
                    - 10110111101101110011001010010000001100001011000110110001
                    - 10110010101110011011100110010000001101001011101000010111
                    - 00010000001001101011000010110101101100101001000000111001
                    - 10111010101110010011001010010000001101110011011110010000
                    - 00110100001110101011011010110000101101110011100110010000
                    - 00110010101101110011101000110010101110010001000000111010
                    - 00110100001100101001000000111001001101111011011110110110
                    - 10010000001100100011101010111001001101001011011100110011
                    - 10010000001110100011010000110000101110100001000000111010
                    - 001101001011011010110010100101110"} - - //Fake messages - if(MSG_MON_SCREEN_CUSTOM_MSG) - dat += "
                    Back - Reset

                    " - - dat += {" - - - - "} - //Sender - Sender's Job - Recepient - Message - //Al Green- Your Dad - Your Mom - WHAT UP!? - - dat += {" - - - "} - dat += "
                    SenderSender's JobRecipientMessage
                    [customsender][customjob][customrecepient ? customrecepient.owner : "NONE"][custommessage]

                    Send" - - //Request Console Logs - if(MSG_MON_SCREEN_REQUEST_LOGS) - - var/index = 0 - /* data_rc_msg - X - 5% - var/rec_dpt = "Unspecified" //name of the person - 15% - var/send_dpt = "Unspecified" //name of the sender- 15% - var/message = "Blank" //transferred message - 300px - var/stamp = "Unstamped" - 15% - var/id_auth = "Unauthenticated" - 15% - var/priority = "Normal" - 10% - */ - dat += "
                    Back - Refresh

                    " - dat += {" - "} - for(var/datum/data_rc_msg/rc in linkedServer.rc_msgs) - index++ - if(index > 3000) - break - // Del - Sender - Recepient - Message - // X - Al Green - Your Mom - WHAT UP!? - dat += {" - "} - dat += "
                    XSending Dep.Receiving Dep.MessageStampID Auth.Priority.
                    X
                    [rc.send_dpt][rc.rec_dpt][rc.message][rc.stamp][rc.id_auth][rc.priority]
                    " - - message = defaultmsg - var/datum/browser/popup = new(user, "hologram_console", name, 700, 700) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/message_monitor/proc/BruteForce(mob/user) - if(isnull(linkedServer)) - to_chat(user, "Could not complete brute-force: Linked Server Disconnected!") - else - var/currentKey = linkedServer.decryptkey - to_chat(user, "Brute-force completed! The key is '[currentKey]'.") - hacking = FALSE - screen = MSG_MON_SCREEN_MAIN // Return the screen back to normal - -/obj/machinery/computer/message_monitor/proc/UnmagConsole() - obj_flags &= ~EMAGGED - -/obj/machinery/computer/message_monitor/proc/ResetMessage() - customsender = "System Administrator" - customrecepient = null - custommessage = "This is a test, please ignore." - customjob = "Admin" - -/obj/machinery/computer/message_monitor/Topic(href, href_list) - if(..()) - return - - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) - //Authenticate - if (href_list["auth"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - auth = FALSE - screen = MSG_MON_SCREEN_MAIN - else - var/dkey = trim(input(usr, "Please enter the decryption key.") as text|null) - if(dkey && dkey != "") - if(linkedServer.decryptkey == dkey) - auth = TRUE - else - message = incorrectkey - - //Turn the server on/off. - if (href_list["active"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - linkedServer.toggled = !linkedServer.toggled - //Find a server - if (href_list["find"]) - var/list/message_servers = list() - for (var/obj/machinery/telecomms/message_server/M in GLOB.telecomms_list) - message_servers += M - - if(message_servers.len > 1) - linkedServer = input(usr, "Please select a server.", "Select a server.", null) as null|anything in message_servers - message = "NOTICE: Server selected." - else if(message_servers.len > 0) - linkedServer = message_servers[1] - message = "NOTICE: Only Single Server Detected - Server selected." - else - message = noserver - - //View the logs - KEY REQUIRED - if (href_list["view_logs"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - screen = MSG_MON_SCREEN_LOGS - - //Clears the logs - KEY REQUIRED - if (href_list["clear_logs"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - linkedServer.pda_msgs = list() - message = "NOTICE: Logs cleared." - //Clears the request console logs - KEY REQUIRED - if (href_list["clear_requests"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - linkedServer.rc_msgs = list() - message = "NOTICE: Logs cleared." - //Change the password - KEY REQUIRED - if (href_list["pass"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - var/dkey = stripped_input(usr, "Please enter the decryption key.") - if(dkey && dkey != "") - if(linkedServer.decryptkey == dkey) - var/newkey = stripped_input(usr,"Please enter the new key (3 - 16 characters max):") - if(length(newkey) <= 3) - message = "NOTICE: Decryption key too short!" - else if(length(newkey) > 16) - message = "NOTICE: Decryption key too long!" - else if(newkey && newkey != "") - linkedServer.decryptkey = newkey - message = "NOTICE: Decryption key set." - else - message = incorrectkey - - //Hack the Console to get the password - if (href_list["hack"]) - var/mob/living/silicon/S = usr - if(istype(S) && S.hack_software) - hacking = TRUE - screen = MSG_MON_SCREEN_HACKED - //Time it takes to bruteforce is dependant on the password length. - addtimer(CALLBACK(src, .proc/finish_bruteforce, usr), 100*length(linkedServer.decryptkey)) - - //Delete the log. - if (href_list["delete_logs"]) - //Are they on the view logs screen? - if(screen == MSG_MON_SCREEN_LOGS) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) - linkedServer.pda_msgs -= locate(href_list["delete_logs"]) in linkedServer.pda_msgs - message = "NOTICE: Log Deleted!" - //Delete the request console log. - if (href_list["delete_requests"]) - //Are they on the view logs screen? - if(screen == MSG_MON_SCREEN_REQUEST_LOGS) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) - linkedServer.rc_msgs -= locate(href_list["delete_requests"]) in linkedServer.rc_msgs - message = "NOTICE: Log Deleted!" - //Create a custom message - if (href_list["msg"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - screen = MSG_MON_SCREEN_CUSTOM_MSG - //Fake messaging selection - KEY REQUIRED - if (href_list["select"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - screen = MSG_MON_SCREEN_MAIN - else - switch(href_list["select"]) - - //Reset - if("Reset") - ResetMessage() - - //Select Your Name - if("Sender") - customsender = stripped_input(usr, "Please enter the sender's name.") || customsender - - //Select Receiver - if("Recepient") - //Get out list of viable PDAs - var/list/obj/item/pda/sendPDAs = get_viewable_pdas() - if(GLOB.PDAs && GLOB.PDAs.len > 0) - customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sendPDAs - else - customrecepient = null - - //Enter custom job - if("RecJob") - customjob = stripped_input(usr, "Please enter the sender's job.") || customjob - - //Enter message - if("Message") - custommessage = stripped_input(usr, "Please enter your message.") || custommessage - - //Send message - if("Send") - if(isnull(customsender) || customsender == "") - customsender = "UNKNOWN" - - if(isnull(customrecepient)) - message = "NOTICE: No recepient selected!" - return attack_hand(usr) - - if(isnull(custommessage) || custommessage == "") - message = "NOTICE: No message entered!" - return attack_hand(usr) - - var/datum/signal/subspace/messaging/pda/signal = new(src, list( - "name" = "[customsender]", - "job" = "[customjob]", - "message" = custommessage, - "targets" = list("[customrecepient.owner] ([customrecepient.ownjob])") - )) - // this will log the signal and transmit it to the target - linkedServer.receive_information(signal, null) - usr.log_message("(PDA: [name] | [usr.real_name]) sent \"[custommessage]\" to [signal.format_target()]", LOG_PDA) - - - //Request Console Logs - KEY REQUIRED - if(href_list["view_requests"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - screen = MSG_MON_SCREEN_REQUEST_LOGS - - if (href_list["back"]) - screen = MSG_MON_SCREEN_MAIN - - return attack_hand(usr) - -/obj/machinery/computer/message_monitor/proc/finish_bruteforce(mob/user) - if(!QDELETED(user)) - BruteForce(user) - return - hacking = FALSE - screen = MSG_MON_SCREEN_MAIN - -#undef MSG_MON_SCREEN_MAIN -#undef MSG_MON_SCREEN_LOGS -#undef MSG_MON_SCREEN_HACKED -#undef MSG_MON_SCREEN_CUSTOM_MSG -#undef MSG_MON_SCREEN_REQUEST_LOGS - -#undef LINKED_SERVER_NONRESPONSIVE - -/obj/item/paper/monitorkey - name = "monitor decryption key" - -/obj/item/paper/monitorkey/Initialize(mapload, obj/machinery/telecomms/message_server/server) - ..() - if (server) - print(server) - return INITIALIZE_HINT_NORMAL - else - return INITIALIZE_HINT_LATELOAD - -/obj/item/paper/monitorkey/proc/print(obj/machinery/telecomms/message_server/server) - info = "

                    Daily Key Reset


                    The new message monitor key is '[server.decryptkey]'.
                    Please keep this a secret and away from the clown.
                    If necessary, change the password to a more secure one." - add_overlay("paper_words") - -/obj/item/paper/monitorkey/LateInitialize() - for (var/obj/machinery/telecomms/message_server/preset/server in GLOB.telecomms_list) - if (server.decryptkey) - print(server) - break +/* + The monitoring computer for the messaging server. + Lets you read PDA and request console messages. +*/ + +#define LINKED_SERVER_NONRESPONSIVE (!linkedServer || (linkedServer.machine_stat & (NOPOWER|BROKEN))) + +#define MSG_MON_SCREEN_MAIN 0 +#define MSG_MON_SCREEN_LOGS 1 +#define MSG_MON_SCREEN_HACKED 2 +#define MSG_MON_SCREEN_CUSTOM_MSG 3 +#define MSG_MON_SCREEN_REQUEST_LOGS 4 + +// The monitor itself. +/obj/machinery/computer/message_monitor + name = "message monitor console" + desc = "Used to monitor the crew's PDA messages, as well as request console messages." + icon_screen = "comm_logs" + circuit = /obj/item/circuitboard/computer/message_monitor + //Server linked to. + var/obj/machinery/telecomms/message_server/linkedServer = null + //Sparks effect - For emag + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread + //Messages - Saves me time if I want to change something. + var/noserver = "ALERT: No server detected." + var/incorrectkey = "ALERT: Incorrect decryption key!" + var/defaultmsg = "Welcome. Please select an option." + var/rebootmsg = "%$&(£: Critical %$$@ Error // !RestArting! - ?pLeaSe wAit!" + //Computer properties + var/screen = MSG_MON_SCREEN_MAIN // 0 = Main menu, 1 = Message Logs, 2 = Hacked screen, 3 = Custom Message + var/hacking = FALSE // Is it being hacked into by the AI/Cyborg + var/message = "System bootup complete. Please select an option." // The message that shows on the main menu. + var/auth = FALSE // Are they authenticated? + var/optioncount = 7 + // Custom Message Properties + var/customsender = "System Administrator" + var/obj/item/pda/customrecepient = null + var/customjob = "Admin" + var/custommessage = "This is a test, please ignore." + + light_color = LIGHT_COLOR_GREEN + +/obj/machinery/computer/message_monitor/attackby(obj/item/O, mob/living/user, params) + if(O.tool_behaviour == TOOL_SCREWDRIVER && (obj_flags & EMAGGED)) + //Stops people from just unscrewing the monitor and putting it back to get the console working again. + to_chat(user, "It is too hot to mess with!") + else + return ..() + +/obj/machinery/computer/message_monitor/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + if(!isnull(linkedServer)) + obj_flags |= EMAGGED + screen = MSG_MON_SCREEN_HACKED + spark_system.set_up(5, 0, src) + spark_system.start() + var/obj/item/paper/monitorkey/MK = new(loc, linkedServer) + // Will help make emagging the console not so easy to get away with. + MK.info += "

                    £%@%(*$%&(£&?*(%&£/{}" + var/time = 100 * length(linkedServer.decryptkey) + addtimer(CALLBACK(src, .proc/UnmagConsole), time) + message = rebootmsg + else + to_chat(user, "A no server error appears on the screen.") + +/obj/machinery/computer/message_monitor/New() + ..() + GLOB.telecomms_list += src + +/obj/machinery/computer/message_monitor/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/computer/message_monitor/LateInitialize() + //Is the server isn't linked to a server, and there's a server available, default it to the first one in the list. + if(!linkedServer) + for(var/obj/machinery/telecomms/message_server/S in GLOB.telecomms_list) + linkedServer = S + break + +/obj/machinery/computer/message_monitor/Destroy() + GLOB.telecomms_list -= src + return ..() + +/obj/machinery/computer/message_monitor/ui_interact(mob/living/user) + . = ..() + //If the computer is being hacked or is emagged, display the reboot message. + if(hacking || (obj_flags & EMAGGED)) + message = rebootmsg + var/dat = "
                    " + + if(auth) + dat += "

                    \[Authenticated\] /" + dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " + else + dat += "

                    \[Unauthenticated\] /" + dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " + + if(hacking || (obj_flags & EMAGGED)) + screen = MSG_MON_SCREEN_HACKED + else if(!auth || LINKED_SERVER_NONRESPONSIVE) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + screen = MSG_MON_SCREEN_MAIN + + switch(screen) + //Main menu + if(MSG_MON_SCREEN_MAIN) + // = TAB + var/i = 0 + dat += "
                    [++i]. Link To A Server
                    " + if(auth) + if(LINKED_SERVER_NONRESPONSIVE) + dat += "
                    ERROR: Server not found!
                    " + else + dat += "
                    [++i]. View Message Logs
                    " + dat += "
                    [++i]. View Request Console Logs
                    " + dat += "
                    [++i]. Clear Message Logs
                    " + dat += "
                    [++i]. Clear Request Console Logs
                    " + dat += "
                    [++i]. Set Custom Key
                    " + dat += "
                    [++i]. Send Admin Message
                    " + else + for(var/n = ++i; n <= optioncount; n++) + dat += "
                    [n]. ---------------
                    " + var/mob/living/silicon/S = usr + if(istype(S) && S.hack_software) + //Malf/Traitor AIs can bruteforce into the system to gain the Key. + dat += "
                    *&@#. Bruteforce Key
                    " + else + dat += "
                    " + + //Bottom message + if(!auth) + dat += "

                    Please authenticate with the server in order to show additional options." + else + dat += "

                    Reg, #514 forbids sending messages to a Head of Staff containing Erotic Rendering Properties." + + //Message Logs + if(MSG_MON_SCREEN_LOGS) + var/index = 0 + dat += "
                    Back - Refresh

                    " + dat += "" + for(var/datum/data_pda_msg/pda in linkedServer.pda_msgs) + index++ + if(index > 3000) + break + // Del - Sender - Recepient - Message + // X - Al Green - Your Mom - WHAT UP!? + dat += "" + dat += "
                    XSenderRecipientMessage
                    X
                    [pda.sender][pda.recipient][pda.message][pda.picture ? " (Photo)":""]
                    " + //Hacking screen. + if(MSG_MON_SCREEN_HACKED) + if(isAI(user) || iscyborg(user)) + dat += "Brute-forcing for server key.
                    It will take 20 seconds for every character that the password has." + dat += "In the meantime, this console can reveal your true intentions if you let someone access it. Make sure no humans enter the room during that time." + else + //It's the same message as the one above but in binary. Because robots understand binary and humans don't... well I thought it was clever. + dat += {"01000010011100100111010101110100011001010010110
                    + 10110011001101111011100100110001101101001011011100110011
                    + 10010000001100110011011110111001000100000011100110110010
                    + 10111001001110110011001010111001000100000011010110110010
                    + 10111100100101110001000000100100101110100001000000111011
                    + 10110100101101100011011000010000001110100011000010110101
                    + 10110010100100000001100100011000000100000011100110110010
                    + 10110001101101111011011100110010001110011001000000110011
                    + 00110111101110010001000000110010101110110011001010111001
                    + 00111100100100000011000110110100001100001011100100110000
                    + 10110001101110100011001010111001000100000011101000110100
                    + 00110000101110100001000000111010001101000011001010010000
                    + 00111000001100001011100110111001101110111011011110111001
                    + 00110010000100000011010000110000101110011001011100010000
                    + 00100100101101110001000000111010001101000011001010010000
                    + 00110110101100101011000010110111001110100011010010110110
                    + 10110010100101100001000000111010001101000011010010111001
                    + 10010000001100011011011110110111001110011011011110110110
                    + 00110010100100000011000110110000101101110001000000111001
                    + 00110010101110110011001010110000101101100001000000111100
                    + 10110111101110101011100100010000001110100011100100111010
                    + 10110010100100000011010010110111001110100011001010110111
                    + 00111010001101001011011110110111001110011001000000110100
                    + 10110011000100000011110010110111101110101001000000110110
                    + 00110010101110100001000000111001101101111011011010110010
                    + 10110111101101110011001010010000001100001011000110110001
                    + 10110010101110011011100110010000001101001011101000010111
                    + 00010000001001101011000010110101101100101001000000111001
                    + 10111010101110010011001010010000001101110011011110010000
                    + 00110100001110101011011010110000101101110011100110010000
                    + 00110010101101110011101000110010101110010001000000111010
                    + 00110100001100101001000000111001001101111011011110110110
                    + 10010000001100100011101010111001001101001011011100110011
                    + 10010000001110100011010000110000101110100001000000111010
                    + 001101001011011010110010100101110"} + + //Fake messages + if(MSG_MON_SCREEN_CUSTOM_MSG) + dat += "
                    Back - Reset

                    " + + dat += {" + + + + "} + //Sender - Sender's Job - Recepient - Message + //Al Green- Your Dad - Your Mom - WHAT UP!? + + dat += {" + + + "} + dat += "
                    SenderSender's JobRecipientMessage
                    [customsender][customjob][customrecepient ? customrecepient.owner : "NONE"][custommessage]

                    Send" + + //Request Console Logs + if(MSG_MON_SCREEN_REQUEST_LOGS) + + var/index = 0 + /* data_rc_msg + X - 5% + var/rec_dpt = "Unspecified" //name of the person - 15% + var/send_dpt = "Unspecified" //name of the sender- 15% + var/message = "Blank" //transferred message - 300px + var/stamp = "Unstamped" - 15% + var/id_auth = "Unauthenticated" - 15% + var/priority = "Normal" - 10% + */ + dat += "
                    Back - Refresh

                    " + dat += {" + "} + for(var/datum/data_rc_msg/rc in linkedServer.rc_msgs) + index++ + if(index > 3000) + break + // Del - Sender - Recepient - Message + // X - Al Green - Your Mom - WHAT UP!? + dat += {" + "} + dat += "
                    XSending Dep.Receiving Dep.MessageStampID Auth.Priority.
                    X
                    [rc.send_dpt][rc.rec_dpt][rc.message][rc.stamp][rc.id_auth][rc.priority]
                    " + + message = defaultmsg + var/datum/browser/popup = new(user, "hologram_console", name, 700, 700) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/message_monitor/proc/BruteForce(mob/user) + if(isnull(linkedServer)) + to_chat(user, "Could not complete brute-force: Linked Server Disconnected!") + else + var/currentKey = linkedServer.decryptkey + to_chat(user, "Brute-force completed! The key is '[currentKey]'.") + hacking = FALSE + screen = MSG_MON_SCREEN_MAIN // Return the screen back to normal + +/obj/machinery/computer/message_monitor/proc/UnmagConsole() + obj_flags &= ~EMAGGED + +/obj/machinery/computer/message_monitor/proc/ResetMessage() + customsender = "System Administrator" + customrecepient = null + custommessage = "This is a test, please ignore." + customjob = "Admin" + +/obj/machinery/computer/message_monitor/Topic(href, href_list) + if(..()) + return + + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) + //Authenticate + if (href_list["auth"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + auth = FALSE + screen = MSG_MON_SCREEN_MAIN + else + var/dkey = trim(input(usr, "Please enter the decryption key.") as text|null) + if(dkey && dkey != "") + if(linkedServer.decryptkey == dkey) + auth = TRUE + else + message = incorrectkey + + //Turn the server on/off. + if (href_list["active"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + linkedServer.toggled = !linkedServer.toggled + //Find a server + if (href_list["find"]) + var/list/message_servers = list() + for (var/obj/machinery/telecomms/message_server/M in GLOB.telecomms_list) + message_servers += M + + if(message_servers.len > 1) + linkedServer = input(usr, "Please select a server.", "Select a server.", null) as null|anything in message_servers + message = "NOTICE: Server selected." + else if(message_servers.len > 0) + linkedServer = message_servers[1] + message = "NOTICE: Only Single Server Detected - Server selected." + else + message = noserver + + //View the logs - KEY REQUIRED + if (href_list["view_logs"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + screen = MSG_MON_SCREEN_LOGS + + //Clears the logs - KEY REQUIRED + if (href_list["clear_logs"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + linkedServer.pda_msgs = list() + message = "NOTICE: Logs cleared." + //Clears the request console logs - KEY REQUIRED + if (href_list["clear_requests"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + linkedServer.rc_msgs = list() + message = "NOTICE: Logs cleared." + //Change the password - KEY REQUIRED + if (href_list["pass"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + var/dkey = stripped_input(usr, "Please enter the decryption key.") + if(dkey && dkey != "") + if(linkedServer.decryptkey == dkey) + var/newkey = stripped_input(usr,"Please enter the new key (3 - 16 characters max):") + if(length(newkey) <= 3) + message = "NOTICE: Decryption key too short!" + else if(length(newkey) > 16) + message = "NOTICE: Decryption key too long!" + else if(newkey && newkey != "") + linkedServer.decryptkey = newkey + message = "NOTICE: Decryption key set." + else + message = incorrectkey + + //Hack the Console to get the password + if (href_list["hack"]) + var/mob/living/silicon/S = usr + if(istype(S) && S.hack_software) + hacking = TRUE + screen = MSG_MON_SCREEN_HACKED + //Time it takes to bruteforce is dependant on the password length. + addtimer(CALLBACK(src, .proc/finish_bruteforce, usr), 100*length(linkedServer.decryptkey)) + + //Delete the log. + if (href_list["delete_logs"]) + //Are they on the view logs screen? + if(screen == MSG_MON_SCREEN_LOGS) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) + linkedServer.pda_msgs -= locate(href_list["delete_logs"]) in linkedServer.pda_msgs + message = "NOTICE: Log Deleted!" + //Delete the request console log. + if (href_list["delete_requests"]) + //Are they on the view logs screen? + if(screen == MSG_MON_SCREEN_REQUEST_LOGS) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) + linkedServer.rc_msgs -= locate(href_list["delete_requests"]) in linkedServer.rc_msgs + message = "NOTICE: Log Deleted!" + //Create a custom message + if (href_list["msg"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + screen = MSG_MON_SCREEN_CUSTOM_MSG + //Fake messaging selection - KEY REQUIRED + if (href_list["select"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + screen = MSG_MON_SCREEN_MAIN + else + switch(href_list["select"]) + + //Reset + if("Reset") + ResetMessage() + + //Select Your Name + if("Sender") + customsender = stripped_input(usr, "Please enter the sender's name.") || customsender + + //Select Receiver + if("Recepient") + //Get out list of viable PDAs + var/list/obj/item/pda/sendPDAs = get_viewable_pdas() + if(GLOB.PDAs && GLOB.PDAs.len > 0) + customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sendPDAs + else + customrecepient = null + + //Enter custom job + if("RecJob") + customjob = stripped_input(usr, "Please enter the sender's job.") || customjob + + //Enter message + if("Message") + custommessage = stripped_input(usr, "Please enter your message.") || custommessage + + //Send message + if("Send") + if(isnull(customsender) || customsender == "") + customsender = "UNKNOWN" + + if(isnull(customrecepient)) + message = "NOTICE: No recepient selected!" + return attack_hand(usr) + + if(isnull(custommessage) || custommessage == "") + message = "NOTICE: No message entered!" + return attack_hand(usr) + + var/datum/signal/subspace/messaging/pda/signal = new(src, list( + "name" = "[customsender]", + "job" = "[customjob]", + "message" = custommessage, + "targets" = list("[customrecepient.owner] ([customrecepient.ownjob])") + )) + // this will log the signal and transmit it to the target + linkedServer.receive_information(signal, null) + usr.log_message("(PDA: [name] | [usr.real_name]) sent \"[custommessage]\" to [signal.format_target()]", LOG_PDA) + + + //Request Console Logs - KEY REQUIRED + if(href_list["view_requests"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + screen = MSG_MON_SCREEN_REQUEST_LOGS + + if (href_list["back"]) + screen = MSG_MON_SCREEN_MAIN + + return attack_hand(usr) + +/obj/machinery/computer/message_monitor/proc/finish_bruteforce(mob/user) + if(!QDELETED(user)) + BruteForce(user) + return + hacking = FALSE + screen = MSG_MON_SCREEN_MAIN + +#undef MSG_MON_SCREEN_MAIN +#undef MSG_MON_SCREEN_LOGS +#undef MSG_MON_SCREEN_HACKED +#undef MSG_MON_SCREEN_CUSTOM_MSG +#undef MSG_MON_SCREEN_REQUEST_LOGS + +#undef LINKED_SERVER_NONRESPONSIVE + +/obj/item/paper/monitorkey + name = "monitor decryption key" + +/obj/item/paper/monitorkey/Initialize(mapload, obj/machinery/telecomms/message_server/server) + ..() + if (server) + print(server) + return INITIALIZE_HINT_NORMAL + else + return INITIALIZE_HINT_LATELOAD + +/obj/item/paper/monitorkey/proc/print(obj/machinery/telecomms/message_server/server) + info = "

                    Daily Key Reset


                    The new message monitor key is '[server.decryptkey]'.
                    Please keep this a secret and away from the clown.
                    If necessary, change the password to a more secure one." + add_overlay("paper_words") + +/obj/item/paper/monitorkey/LateInitialize() + for (var/obj/machinery/telecomms/message_server/preset/server in GLOB.telecomms_list) + if (server.decryptkey) + print(server) + break diff --git a/code/game/machinery/telecomms/machines/message_server.dm b/code/game/machinery/telecomms/machines/message_server.dm index 7137971c374..ed4258dce1f 100644 --- a/code/game/machinery/telecomms/machines/message_server.dm +++ b/code/game/machinery/telecomms/machines/message_server.dm @@ -1,268 +1,268 @@ -/* - The equivalent of the server, for PDA and request console messages. - Without it, PDA and request console messages cannot be transmitted. - PDAs require the rest of the telecomms setup, but request consoles only - require the message server. -*/ - -// A decorational representation of SSblackbox, usually placed alongside the message server. Also contains a traitor theft item. -/obj/machinery/blackbox_recorder - icon = 'icons/obj/stationobjs.dmi' - icon_state = "blackbox" - name = "Blackbox Recorder" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) - var/obj/item/stored - -/obj/machinery/blackbox_recorder/Initialize() - . = ..() - stored = new /obj/item/blackbox(src) - -/obj/machinery/blackbox_recorder/attack_hand(mob/living/user) - . = ..() - if(stored) - user.put_in_hands(stored) - stored = null - to_chat(user, "You remove the blackbox from [src]. The tapes stop spinning.") - update_icon() - return - else - to_chat(user, "It seems that the blackbox is missing...") - return - -/obj/machinery/blackbox_recorder/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/blackbox)) - if(HAS_TRAIT(I, TRAIT_NODROP) || !user.transferItemToLoc(I, src)) - to_chat(user, "[I] is stuck to your hand!") - return - user.visible_message("[user] clicks [I] into [src]!", \ - "You press the device into [src], and it clicks into place. The tapes begin spinning again.") - playsound(src, 'sound/machines/click.ogg', 50, TRUE) - stored = I - update_icon() - return - return ..() - -/obj/machinery/blackbox_recorder/Destroy() - if(stored) - stored.forceMove(loc) - new /obj/effect/decal/cleanable/oil(loc) - return ..() - -/obj/machinery/blackbox_recorder/update_icon() - . = ..() - if(!stored) - icon_state = "blackbox_b" - else - icon_state = "blackbox" - -/obj/item/blackbox - name = "\proper the blackbox" - desc = "A strange relic, capable of recording data on extradimensional vertices. It lives inside the blackbox recorder for safe keeping." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "blackcube" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly." - -// The message server itself. -/obj/machinery/telecomms/message_server - icon_state = "message_server" - name = "Messaging Server" - desc = "A machine that processes and routes PDA and request console messages." - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - circuit = /obj/item/circuitboard/machine/telecomms/message_server - - var/list/datum/data_pda_msg/pda_msgs = list() - var/list/datum/data_rc_msg/rc_msgs = list() - var/decryptkey = "password" - var/calibrating = 15 MINUTES //Init reads this and adds world.time, then becomes 0 when that time has passed and the machine works - -/obj/machinery/telecomms/message_server/Initialize(mapload) - . = ..() - if (!decryptkey) - decryptkey = GenerateKey() - - if (calibrating) - calibrating += world.time - say("Calibrating... Estimated wait time: [rand(3, 9)] minutes.") - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. System calibration started at [station_time_timestamp()]") - else - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) - -/obj/machinery/telecomms/message_server/Destroy() - for(var/obj/machinery/computer/message_monitor/monitor in GLOB.telecomms_list) - if(monitor.linkedServer && monitor.linkedServer == src) - monitor.linkedServer = null - . = ..() - -/obj/machinery/telecomms/message_server/examine(mob/user) - . = ..() - if(calibrating) - . += "It's still calibrating." - -/obj/machinery/telecomms/message_server/proc/GenerateKey() - var/newKey - newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le") - newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai") - newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") - return newKey - -/obj/machinery/telecomms/message_server/process() - . = ..() - if(calibrating && calibrating <= world.time) - calibrating = 0 - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) - -/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/messaging/signal, obj/machinery/telecomms/machine_from) - // can't log non-message signals - if(!istype(signal) || !signal.data["message"] || !on || calibrating) - return - - // log the signal - if(istype(signal, /datum/signal/subspace/messaging/pda)) - var/datum/signal/subspace/messaging/pda/PDAsignal = signal - var/datum/data_pda_msg/M = new(PDAsignal.format_target(), "[PDAsignal.data["name"]] ([PDAsignal.data["job"]])", PDAsignal.data["message"], PDAsignal.data["photo"]) - pda_msgs += M - signal.logged = M - else if(istype(signal, /datum/signal/subspace/messaging/rc)) - var/datum/data_rc_msg/M = new(signal.data["rec_dpt"], signal.data["send_dpt"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"]) - signal.logged = M - if(signal.data["send_dpt"]) // don't log messages not from a department but allow them to work - rc_msgs += M - signal.data["reject"] = FALSE - - // pass it along to either the hub or the broadcaster - if(!relay_information(signal, /obj/machinery/telecomms/hub)) - relay_information(signal, /obj/machinery/telecomms/broadcaster) - -/obj/machinery/telecomms/message_server/update_overlays() - . = ..() - - if(calibrating) - . += "message_server_calibrate" - - -// Root messaging signal datum -/datum/signal/subspace/messaging - frequency = FREQ_COMMON - server_type = /obj/machinery/telecomms/message_server - var/datum/logged - -/datum/signal/subspace/messaging/New(init_source, init_data) - source = init_source - data = init_data - var/turf/T = get_turf(source) - levels = list(T.z) - if(!("reject" in data)) - data["reject"] = TRUE - -/datum/signal/subspace/messaging/copy() - var/datum/signal/subspace/messaging/copy = new type(source, data.Copy()) - copy.original = src - copy.levels = levels - return copy - -// PDA signal datum -/datum/signal/subspace/messaging/pda/proc/format_target() - if (length(data["targets"]) > 1) - return "Everyone" - return data["targets"][1] - -/datum/signal/subspace/messaging/pda/proc/format_message() - if (logged && data["photo"]) - return "\"[data["message"]]\" (Photo)" - return "\"[data["message"]]\"" - -/datum/signal/subspace/messaging/pda/broadcast() - if (!logged) // Can only go through if a message server logs it - return - for (var/obj/item/pda/P in GLOB.PDAs) - if ("[P.owner] ([P.ownjob])" in data["targets"]) - P.receive_message(src) - -// Request Console signal datum -/datum/signal/subspace/messaging/rc/broadcast() - if (!logged) // Like /pda, only if logged - return - var/rec_dpt = ckey(data["rec_dpt"]) - for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) - if(ckey(Console.department) == rec_dpt || (data["ore_update"] && Console.receive_ore_updates)) - Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) - -// Log datums stored by the message server. -/datum/data_pda_msg - var/sender = "Unspecified" - var/recipient = "Unspecified" - var/message = "Blank" // transferred message - var/datum/picture/picture // attached photo - var/automated = 0 //automated message - -/datum/data_pda_msg/New(param_rec, param_sender, param_message, param_photo) - if(param_rec) - recipient = param_rec - if(param_sender) - sender = param_sender - if(param_message) - message = param_message - if(param_photo) - picture = param_photo - -/datum/data_pda_msg/Topic(href,href_list) - ..() - if(href_list["photo"]) - var/mob/M = usr - M << browse_rsc(picture.picture_image, "pda_photo.png") - M << browse("PDA Photo" \ - + "" \ - + "" \ - + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y];can-close=true") - onclose(M, "pdaphoto") - -/datum/data_rc_msg - var/rec_dpt = "Unspecified" // receiving department - var/send_dpt = "Unspecified" // sending department - var/message = "Blank" - var/stamp = "Unstamped" - var/id_auth = "Unauthenticated" - var/priority = "Normal" - -/datum/data_rc_msg/New(param_rec, param_sender, param_message, param_stamp, param_id_auth, param_priority) - if(param_rec) - rec_dpt = param_rec - if(param_sender) - send_dpt = param_sender - if(param_message) - message = param_message - if(param_stamp) - stamp = param_stamp - if(param_id_auth) - id_auth = param_id_auth - if(param_priority) - switch(param_priority) - if(REQ_NORMAL_MESSAGE_PRIORITY) - priority = "Normal" - if(REQ_HIGH_MESSAGE_PRIORITY) - priority = "High" - if(REQ_EXTREME_MESSAGE_PRIORITY) - priority = "Extreme" - else - priority = "Undetermined" - -#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE - -/obj/machinery/telecomms/message_server/preset - id = "Messaging Server" - network = "tcommsat" - autolinkers = list("messaging") - decryptkey = null //random - calibrating = 0 +/* + The equivalent of the server, for PDA and request console messages. + Without it, PDA and request console messages cannot be transmitted. + PDAs require the rest of the telecomms setup, but request consoles only + require the message server. +*/ + +// A decorational representation of SSblackbox, usually placed alongside the message server. Also contains a traitor theft item. +/obj/machinery/blackbox_recorder + icon = 'icons/obj/stationobjs.dmi' + icon_state = "blackbox" + name = "Blackbox Recorder" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) + var/obj/item/stored + +/obj/machinery/blackbox_recorder/Initialize() + . = ..() + stored = new /obj/item/blackbox(src) + +/obj/machinery/blackbox_recorder/attack_hand(mob/living/user) + . = ..() + if(stored) + user.put_in_hands(stored) + stored = null + to_chat(user, "You remove the blackbox from [src]. The tapes stop spinning.") + update_icon() + return + else + to_chat(user, "It seems that the blackbox is missing...") + return + +/obj/machinery/blackbox_recorder/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/blackbox)) + if(HAS_TRAIT(I, TRAIT_NODROP) || !user.transferItemToLoc(I, src)) + to_chat(user, "[I] is stuck to your hand!") + return + user.visible_message("[user] clicks [I] into [src]!", \ + "You press the device into [src], and it clicks into place. The tapes begin spinning again.") + playsound(src, 'sound/machines/click.ogg', 50, TRUE) + stored = I + update_icon() + return + return ..() + +/obj/machinery/blackbox_recorder/Destroy() + if(stored) + stored.forceMove(loc) + new /obj/effect/decal/cleanable/oil(loc) + return ..() + +/obj/machinery/blackbox_recorder/update_icon() + . = ..() + if(!stored) + icon_state = "blackbox_b" + else + icon_state = "blackbox" + +/obj/item/blackbox + name = "\proper the blackbox" + desc = "A strange relic, capable of recording data on extradimensional vertices. It lives inside the blackbox recorder for safe keeping." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "blackcube" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly." + +// The message server itself. +/obj/machinery/telecomms/message_server + icon_state = "message_server" + name = "Messaging Server" + desc = "A machine that processes and routes PDA and request console messages." + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + circuit = /obj/item/circuitboard/machine/telecomms/message_server + + var/list/datum/data_pda_msg/pda_msgs = list() + var/list/datum/data_rc_msg/rc_msgs = list() + var/decryptkey = "password" + var/calibrating = 15 MINUTES //Init reads this and adds world.time, then becomes 0 when that time has passed and the machine works + +/obj/machinery/telecomms/message_server/Initialize(mapload) + . = ..() + if (!decryptkey) + decryptkey = GenerateKey() + + if (calibrating) + calibrating += world.time + say("Calibrating... Estimated wait time: [rand(3, 9)] minutes.") + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. System calibration started at [station_time_timestamp()]") + else + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) + +/obj/machinery/telecomms/message_server/Destroy() + for(var/obj/machinery/computer/message_monitor/monitor in GLOB.telecomms_list) + if(monitor.linkedServer && monitor.linkedServer == src) + monitor.linkedServer = null + . = ..() + +/obj/machinery/telecomms/message_server/examine(mob/user) + . = ..() + if(calibrating) + . += "It's still calibrating." + +/obj/machinery/telecomms/message_server/proc/GenerateKey() + var/newKey + newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le") + newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai") + newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") + return newKey + +/obj/machinery/telecomms/message_server/process() + . = ..() + if(calibrating && calibrating <= world.time) + calibrating = 0 + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) + +/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/messaging/signal, obj/machinery/telecomms/machine_from) + // can't log non-message signals + if(!istype(signal) || !signal.data["message"] || !on || calibrating) + return + + // log the signal + if(istype(signal, /datum/signal/subspace/messaging/pda)) + var/datum/signal/subspace/messaging/pda/PDAsignal = signal + var/datum/data_pda_msg/M = new(PDAsignal.format_target(), "[PDAsignal.data["name"]] ([PDAsignal.data["job"]])", PDAsignal.data["message"], PDAsignal.data["photo"]) + pda_msgs += M + signal.logged = M + else if(istype(signal, /datum/signal/subspace/messaging/rc)) + var/datum/data_rc_msg/M = new(signal.data["rec_dpt"], signal.data["send_dpt"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"]) + signal.logged = M + if(signal.data["send_dpt"]) // don't log messages not from a department but allow them to work + rc_msgs += M + signal.data["reject"] = FALSE + + // pass it along to either the hub or the broadcaster + if(!relay_information(signal, /obj/machinery/telecomms/hub)) + relay_information(signal, /obj/machinery/telecomms/broadcaster) + +/obj/machinery/telecomms/message_server/update_overlays() + . = ..() + + if(calibrating) + . += "message_server_calibrate" + + +// Root messaging signal datum +/datum/signal/subspace/messaging + frequency = FREQ_COMMON + server_type = /obj/machinery/telecomms/message_server + var/datum/logged + +/datum/signal/subspace/messaging/New(init_source, init_data) + source = init_source + data = init_data + var/turf/T = get_turf(source) + levels = list(T.z) + if(!("reject" in data)) + data["reject"] = TRUE + +/datum/signal/subspace/messaging/copy() + var/datum/signal/subspace/messaging/copy = new type(source, data.Copy()) + copy.original = src + copy.levels = levels + return copy + +// PDA signal datum +/datum/signal/subspace/messaging/pda/proc/format_target() + if (length(data["targets"]) > 1) + return "Everyone" + return data["targets"][1] + +/datum/signal/subspace/messaging/pda/proc/format_message() + if (logged && data["photo"]) + return "\"[data["message"]]\" (Photo)" + return "\"[data["message"]]\"" + +/datum/signal/subspace/messaging/pda/broadcast() + if (!logged) // Can only go through if a message server logs it + return + for (var/obj/item/pda/P in GLOB.PDAs) + if ("[P.owner] ([P.ownjob])" in data["targets"]) + P.receive_message(src) + +// Request Console signal datum +/datum/signal/subspace/messaging/rc/broadcast() + if (!logged) // Like /pda, only if logged + return + var/rec_dpt = ckey(data["rec_dpt"]) + for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) + if(ckey(Console.department) == rec_dpt || (data["ore_update"] && Console.receive_ore_updates)) + Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) + +// Log datums stored by the message server. +/datum/data_pda_msg + var/sender = "Unspecified" + var/recipient = "Unspecified" + var/message = "Blank" // transferred message + var/datum/picture/picture // attached photo + var/automated = 0 //automated message + +/datum/data_pda_msg/New(param_rec, param_sender, param_message, param_photo) + if(param_rec) + recipient = param_rec + if(param_sender) + sender = param_sender + if(param_message) + message = param_message + if(param_photo) + picture = param_photo + +/datum/data_pda_msg/Topic(href,href_list) + ..() + if(href_list["photo"]) + var/mob/M = usr + M << browse_rsc(picture.picture_image, "pda_photo.png") + M << browse("PDA Photo" \ + + "" \ + + "" \ + + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y];can-close=true") + onclose(M, "pdaphoto") + +/datum/data_rc_msg + var/rec_dpt = "Unspecified" // receiving department + var/send_dpt = "Unspecified" // sending department + var/message = "Blank" + var/stamp = "Unstamped" + var/id_auth = "Unauthenticated" + var/priority = "Normal" + +/datum/data_rc_msg/New(param_rec, param_sender, param_message, param_stamp, param_id_auth, param_priority) + if(param_rec) + rec_dpt = param_rec + if(param_sender) + send_dpt = param_sender + if(param_message) + message = param_message + if(param_stamp) + stamp = param_stamp + if(param_id_auth) + id_auth = param_id_auth + if(param_priority) + switch(param_priority) + if(REQ_NORMAL_MESSAGE_PRIORITY) + priority = "Normal" + if(REQ_HIGH_MESSAGE_PRIORITY) + priority = "High" + if(REQ_EXTREME_MESSAGE_PRIORITY) + priority = "Extreme" + else + priority = "Undetermined" + +#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE + +/obj/machinery/telecomms/message_server/preset + id = "Messaging Server" + network = "tcommsat" + autolinkers = list("messaging") + decryptkey = null //random + calibrating = 0 diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm index d70d03ac4e2..af687355b2b 100644 --- a/code/game/machinery/telecomms/telecomunications.dm +++ b/code/game/machinery/telecomms/telecomunications.dm @@ -1,154 +1,154 @@ - -/* - Hello, friends, this is Doohl from sexylands. You may be wondering what this - monstrous code file is. Sit down, boys and girls, while I tell you the tale. - - - The telecom machines were designed to be compatible with any radio - signals, provided they use subspace transmission. Currently they are only used for - headsets, but they can eventually be outfitted for real COMPUTER networks. This - is just a skeleton, ladies and gentlemen. - - Look at radio.dm for the prequel to this code. -*/ - -GLOBAL_LIST_EMPTY(telecomms_list) - -/obj/machinery/telecomms - icon = 'icons/obj/machines/telecomms.dmi' - critical_machine = TRUE - var/list/links = list() // list of machines this machine is linked to - var/traffic = 0 // value increases as traffic increases - var/netspeed = 5 // how much traffic to lose per tick (50 gigabytes/second * netspeed) - var/list/autolinkers = list() // list of text/number values to link with - var/id = "NULL" // identification string - var/network = "NULL" // the network of the machinery - - var/list/freq_listening = list() // list of frequencies to tune into: if none, will listen to all - - var/on = TRUE - var/toggled = TRUE // Is it toggled on - var/long_range_link = FALSE // Can you link it across Z levels or on the otherside of the map? (Relay & Hub) - var/hide = FALSE // Is it a hidden machine? - - -/obj/machinery/telecomms/proc/relay_information(datum/signal/subspace/signal, filter, copysig, amount = 20) - // relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending - - if(!on) - return - var/send_count = 0 - - // Apply some lag based on traffic rates - var/netlag = round(traffic / 50) - if(netlag > signal.data["slow"]) - signal.data["slow"] = netlag - - // Loop through all linked machines and send the signal or copy. - for(var/obj/machinery/telecomms/machine in links) - if(filter && !istype( machine, filter )) - continue - if(!machine.on) - continue - if(amount && send_count >= amount) - break - if(z != machine.loc.z && !long_range_link && !machine.long_range_link) - continue - - send_count++ - if(machine.is_freq_listening(signal)) - machine.traffic++ - - if(copysig) - machine.receive_information(signal.copy(), src) - else - machine.receive_information(signal, src) - - if(send_count > 0 && is_freq_listening(signal)) - traffic++ - - return send_count - -/obj/machinery/telecomms/proc/relay_direct_information(datum/signal/signal, obj/machinery/telecomms/machine) - // send signal directly to a machine - machine.receive_information(signal, src) - -/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from) - // receive information from linked machinery - -/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal) - // return TRUE if found, FALSE if not found - return signal && (!freq_listening.len || (signal.frequency in freq_listening)) - -/obj/machinery/telecomms/Initialize(mapload) - . = ..() - GLOB.telecomms_list += src - if(mapload && autolinkers.len) - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/telecomms/LateInitialize() - ..() - for(var/obj/machinery/telecomms/T in (long_range_link ? GLOB.telecomms_list : urange(20, src, 1))) - add_link(T) - -/obj/machinery/telecomms/Destroy() - GLOB.telecomms_list -= src - for(var/obj/machinery/telecomms/comm in GLOB.telecomms_list) - comm.links -= src - links = list() - return ..() - -// Used in auto linking -/obj/machinery/telecomms/proc/add_link(obj/machinery/telecomms/T) - var/turf/position = get_turf(src) - var/turf/T_position = get_turf(T) - if((position.z == T_position.z) || (long_range_link && T.long_range_link)) - if(src != T) - for(var/x in autolinkers) - if(x in T.autolinkers) - links |= T - T.links |= src - - -/obj/machinery/telecomms/update_icon_state() - if(on) - if(panel_open) - icon_state = "[initial(icon_state)]_o" - else - icon_state = initial(icon_state) - else - if(panel_open) - icon_state = "[initial(icon_state)]_o_off" - else - icon_state = "[initial(icon_state)]_off" - -/obj/machinery/telecomms/proc/update_power() - - if(toggled) - if(machine_stat & (BROKEN|NOPOWER|EMPED)) // if powered, on. if not powered, off. if too damaged, off - on = FALSE - else - on = TRUE - else - on = FALSE - -/obj/machinery/telecomms/process() - update_power() - - // Update the icon - update_icon() - - if(traffic > 0) - traffic -= netspeed - -/obj/machinery/telecomms/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(prob(100/severity) && !(machine_stat & EMPED)) - machine_stat |= EMPED - var/duration = (300 * 10)/severity - addtimer(CALLBACK(src, .proc/de_emp), rand(duration - 20, duration + 20)) - -/obj/machinery/telecomms/proc/de_emp() - machine_stat &= ~EMPED + +/* + Hello, friends, this is Doohl from sexylands. You may be wondering what this + monstrous code file is. Sit down, boys and girls, while I tell you the tale. + + + The telecom machines were designed to be compatible with any radio + signals, provided they use subspace transmission. Currently they are only used for + headsets, but they can eventually be outfitted for real COMPUTER networks. This + is just a skeleton, ladies and gentlemen. + + Look at radio.dm for the prequel to this code. +*/ + +GLOBAL_LIST_EMPTY(telecomms_list) + +/obj/machinery/telecomms + icon = 'icons/obj/machines/telecomms.dmi' + critical_machine = TRUE + var/list/links = list() // list of machines this machine is linked to + var/traffic = 0 // value increases as traffic increases + var/netspeed = 5 // how much traffic to lose per tick (50 gigabytes/second * netspeed) + var/list/autolinkers = list() // list of text/number values to link with + var/id = "NULL" // identification string + var/network = "NULL" // the network of the machinery + + var/list/freq_listening = list() // list of frequencies to tune into: if none, will listen to all + + var/on = TRUE + var/toggled = TRUE // Is it toggled on + var/long_range_link = FALSE // Can you link it across Z levels or on the otherside of the map? (Relay & Hub) + var/hide = FALSE // Is it a hidden machine? + + +/obj/machinery/telecomms/proc/relay_information(datum/signal/subspace/signal, filter, copysig, amount = 20) + // relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending + + if(!on) + return + var/send_count = 0 + + // Apply some lag based on traffic rates + var/netlag = round(traffic / 50) + if(netlag > signal.data["slow"]) + signal.data["slow"] = netlag + + // Loop through all linked machines and send the signal or copy. + for(var/obj/machinery/telecomms/machine in links) + if(filter && !istype( machine, filter )) + continue + if(!machine.on) + continue + if(amount && send_count >= amount) + break + if(z != machine.loc.z && !long_range_link && !machine.long_range_link) + continue + + send_count++ + if(machine.is_freq_listening(signal)) + machine.traffic++ + + if(copysig) + machine.receive_information(signal.copy(), src) + else + machine.receive_information(signal, src) + + if(send_count > 0 && is_freq_listening(signal)) + traffic++ + + return send_count + +/obj/machinery/telecomms/proc/relay_direct_information(datum/signal/signal, obj/machinery/telecomms/machine) + // send signal directly to a machine + machine.receive_information(signal, src) + +/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from) + // receive information from linked machinery + +/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal) + // return TRUE if found, FALSE if not found + return signal && (!freq_listening.len || (signal.frequency in freq_listening)) + +/obj/machinery/telecomms/Initialize(mapload) + . = ..() + GLOB.telecomms_list += src + if(mapload && autolinkers.len) + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/telecomms/LateInitialize() + ..() + for(var/obj/machinery/telecomms/T in (long_range_link ? GLOB.telecomms_list : urange(20, src, 1))) + add_link(T) + +/obj/machinery/telecomms/Destroy() + GLOB.telecomms_list -= src + for(var/obj/machinery/telecomms/comm in GLOB.telecomms_list) + comm.links -= src + links = list() + return ..() + +// Used in auto linking +/obj/machinery/telecomms/proc/add_link(obj/machinery/telecomms/T) + var/turf/position = get_turf(src) + var/turf/T_position = get_turf(T) + if((position.z == T_position.z) || (long_range_link && T.long_range_link)) + if(src != T) + for(var/x in autolinkers) + if(x in T.autolinkers) + links |= T + T.links |= src + + +/obj/machinery/telecomms/update_icon_state() + if(on) + if(panel_open) + icon_state = "[initial(icon_state)]_o" + else + icon_state = initial(icon_state) + else + if(panel_open) + icon_state = "[initial(icon_state)]_o_off" + else + icon_state = "[initial(icon_state)]_off" + +/obj/machinery/telecomms/proc/update_power() + + if(toggled) + if(machine_stat & (BROKEN|NOPOWER|EMPED)) // if powered, on. if not powered, off. if too damaged, off + on = FALSE + else + on = TRUE + else + on = FALSE + +/obj/machinery/telecomms/process() + update_power() + + // Update the icon + update_icon() + + if(traffic > 0) + traffic -= netspeed + +/obj/machinery/telecomms/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(prob(100/severity) && !(machine_stat & EMPED)) + machine_stat |= EMPED + var/duration = (300 * 10)/severity + addtimer(CALLBACK(src, .proc/de_emp), rand(duration - 20, duration + 20)) + +/obj/machinery/telecomms/proc/de_emp() + machine_stat &= ~EMPED diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 2e0a656d1a8..512b7ac6887 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -1,359 +1,359 @@ -//dye registry, add dye colors and their resulting output here if you want the sprite to change instead of just the color. -GLOBAL_LIST_INIT(dye_registry, list( - DYE_REGISTRY_UNDER = list( - DYE_RED = /obj/item/clothing/under/color/red, - DYE_ORANGE = /obj/item/clothing/under/color/orange, - DYE_YELLOW = /obj/item/clothing/under/color/yellow, - DYE_GREEN = /obj/item/clothing/under/color/green, - DYE_BLUE = /obj/item/clothing/under/color/blue, - DYE_PURPLE = /obj/item/clothing/under/color/lightpurple, - DYE_BLACK = /obj/item/clothing/under/color/black, - DYE_WHITE = /obj/item/clothing/under/color/white, - DYE_RAINBOW = /obj/item/clothing/under/color/rainbow, - DYE_MIME = /obj/item/clothing/under/rank/civilian/mime, - DYE_CLOWN = /obj/item/clothing/under/rank/civilian/clown, - DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain, - DYE_QM = /obj/item/clothing/under/rank/cargo/qm, - DYE_LAW = /obj/item/clothing/under/suit/black, - DYE_CAPTAIN = /obj/item/clothing/under/rank/captain, - DYE_HOP = /obj/item/clothing/under/rank/civilian/head_of_personnel, - DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security, - DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer, - DYE_RD = /obj/item/clothing/under/rank/rnd/research_director, - DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer, - DYE_REDCOAT = /obj/item/clothing/under/costume/redcoat, - DYE_SYNDICATE = /obj/item/clothing/under/syndicate, - DYE_CENTCOM = /obj/item/clothing/under/rank/centcom/commander - ), - DYE_REGISTRY_JUMPSKIRT = list( - DYE_RED = /obj/item/clothing/under/color/jumpskirt/red, - DYE_ORANGE = /obj/item/clothing/under/color/jumpskirt/orange, - DYE_YELLOW = /obj/item/clothing/under/color/jumpskirt/yellow, - DYE_GREEN = /obj/item/clothing/under/color/jumpskirt/green, - DYE_BLUE = /obj/item/clothing/under/color/jumpskirt/blue, - DYE_PURPLE = /obj/item/clothing/under/color/jumpskirt/lightpurple, - DYE_BLACK = /obj/item/clothing/under/color/jumpskirt/black, - DYE_WHITE = /obj/item/clothing/under/color/jumpskirt/white, - DYE_RAINBOW = /obj/item/clothing/under/color/jumpskirt/rainbow, - DYE_MIME = /obj/item/clothing/under/rank/civilian/mime/skirt, - DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain/skirt, - DYE_QM = /obj/item/clothing/under/rank/cargo/qm/skirt, - DYE_CAPTAIN = /obj/item/clothing/under/rank/captain/skirt, - DYE_HOP = /obj/item/clothing/under/rank/civilian/head_of_personnel/skirt, - DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security/skirt, - DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer/skirt, - DYE_RD = /obj/item/clothing/under/rank/rnd/research_director/skirt, - DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt, - ), - DYE_REGISTRY_GLOVES = list( - DYE_RED = /obj/item/clothing/gloves/color/red, - DYE_ORANGE = /obj/item/clothing/gloves/color/orange, - DYE_YELLOW = /obj/item/clothing/gloves/color/yellow, - DYE_GREEN = /obj/item/clothing/gloves/color/green, - DYE_BLUE = /obj/item/clothing/gloves/color/blue, - DYE_PURPLE = /obj/item/clothing/gloves/color/purple, - DYE_BLACK = /obj/item/clothing/gloves/color/black, - DYE_WHITE = /obj/item/clothing/gloves/color/white, - DYE_RAINBOW = /obj/item/clothing/gloves/color/rainbow, - DYE_MIME = /obj/item/clothing/gloves/color/white, - DYE_CLOWN = /obj/item/clothing/gloves/color/rainbow, - DYE_QM = /obj/item/clothing/gloves/color/brown, - DYE_CAPTAIN = /obj/item/clothing/gloves/color/captain, - DYE_HOP = /obj/item/clothing/gloves/color/grey, - DYE_HOS = /obj/item/clothing/gloves/color/black, - DYE_CE = /obj/item/clothing/gloves/color/black, - DYE_RD = /obj/item/clothing/gloves/color/grey, - DYE_CMO = /obj/item/clothing/gloves/color/latex/nitrile, - DYE_REDCOAT = /obj/item/clothing/gloves/color/white, - DYE_SYNDICATE = /obj/item/clothing/gloves/combat, - DYE_CENTCOM = /obj/item/clothing/gloves/combat - ), - DYE_REGISTRY_SNEAKERS = list( - DYE_RED = /obj/item/clothing/shoes/sneakers/red, - DYE_ORANGE = /obj/item/clothing/shoes/sneakers/orange, - DYE_YELLOW = /obj/item/clothing/shoes/sneakers/yellow, - DYE_GREEN = /obj/item/clothing/shoes/sneakers/green, - DYE_BLUE = /obj/item/clothing/shoes/sneakers/blue, - DYE_PURPLE = /obj/item/clothing/shoes/sneakers/purple, - DYE_BLACK = /obj/item/clothing/shoes/sneakers/black, - DYE_WHITE = /obj/item/clothing/shoes/sneakers/white, - DYE_RAINBOW = /obj/item/clothing/shoes/sneakers/rainbow, - DYE_MIME = /obj/item/clothing/shoes/sneakers/black, - DYE_CLOWN = /obj/item/clothing/shoes/sneakers/rainbow, - DYE_QM = /obj/item/clothing/shoes/sneakers/brown, - DYE_CAPTAIN = /obj/item/clothing/shoes/sneakers/brown, - DYE_HOP = /obj/item/clothing/shoes/sneakers/brown, - DYE_CE = /obj/item/clothing/shoes/sneakers/brown, - DYE_RD = /obj/item/clothing/shoes/sneakers/brown, - DYE_CMO = /obj/item/clothing/shoes/sneakers/brown, - DYE_SYNDICATE = /obj/item/clothing/shoes/combat, - DYE_CENTCOM = /obj/item/clothing/shoes/combat - ), - DYE_REGISTRY_FANNYPACK = list( - DYE_RED = /obj/item/storage/belt/fannypack/red, - DYE_ORANGE = /obj/item/storage/belt/fannypack/orange, - DYE_YELLOW = /obj/item/storage/belt/fannypack/yellow, - DYE_GREEN = /obj/item/storage/belt/fannypack/green, - DYE_BLUE = /obj/item/storage/belt/fannypack/blue, - DYE_PURPLE = /obj/item/storage/belt/fannypack/purple, - DYE_BLACK = /obj/item/storage/belt/fannypack/black, - DYE_WHITE = /obj/item/storage/belt/fannypack/white, - DYE_SYNDICATE = /obj/item/storage/belt/military - ), - DYE_REGISTRY_BEDSHEET = list( - DYE_RED = /obj/item/bedsheet/red, - DYE_ORANGE = /obj/item/bedsheet/orange, - DYE_YELLOW = /obj/item/bedsheet/yellow, - DYE_GREEN = /obj/item/bedsheet/green, - DYE_BLUE = /obj/item/bedsheet/blue, - DYE_PURPLE = /obj/item/bedsheet/purple, - DYE_BLACK = /obj/item/bedsheet/black, - DYE_WHITE = /obj/item/bedsheet, - DYE_RAINBOW = /obj/item/bedsheet/rainbow, - DYE_MIME = /obj/item/bedsheet/mime, - DYE_CLOWN = /obj/item/bedsheet/clown, - DYE_CHAP = /obj/item/bedsheet/chaplain, - DYE_QM = /obj/item/bedsheet/qm, - DYE_LAW = /obj/item/bedsheet/black, - DYE_CAPTAIN = /obj/item/bedsheet/captain, - DYE_HOP = /obj/item/bedsheet/hop, - DYE_HOS = /obj/item/bedsheet/hos, - DYE_CE = /obj/item/bedsheet/ce, - DYE_RD = /obj/item/bedsheet/rd, - DYE_CMO = /obj/item/bedsheet/cmo, - DYE_COSMIC = /obj/item/bedsheet/cosmos, - DYE_SYNDICATE = /obj/item/bedsheet/syndie, - DYE_CENTCOM = /obj/item/bedsheet/centcom - ), - DYE_LAWYER_SPECIAL = list( - DYE_COSMIC = /obj/item/clothing/under/rank/civilian/lawyer/galaxy, - DYE_SYNDICATE = /obj/item/clothing/under/rank/civilian/lawyer/galaxy/red - ) -)) - -/obj/machinery/washing_machine - name = "washing machine" - desc = "Gets rid of those pesky bloodstains, or your money back!" - icon = 'icons/obj/machines/washing_machine.dmi' - icon_state = "wm_1_0" - density = TRUE - state_open = TRUE - var/busy = FALSE - var/bloody_mess = 0 - var/obj/item/color_source - var/max_wash_capacity = 5 - -/obj/machinery/washing_machine/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/machinery/washing_machine/examine(mob/user) - . = ..() - if(!busy) - . += "Alt-click it to start a wash cycle." - -/obj/machinery/washing_machine/AltClick(mob/user) - if(!user.canUseTopic(src, !issilicon(user))) - return - if(busy) - return - if(state_open) - to_chat(user, "Close the door first!") - return - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first!") - return - busy = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/wash_cycle), 200) - - START_PROCESSING(SSfastprocess, src) - -/obj/machinery/washing_machine/process() - if(!busy) - animate(src, transform=matrix(), time=2) - return PROCESS_KILL - if(anchored) - if(prob(5)) - var/matrix/M = new - M.Translate(rand(-1, 1), rand(0, 1)) - animate(src, transform=M, time=1) - animate(transform=matrix(), time=1) - else - if(prob(1)) - step(src, pick(GLOB.cardinals)) - var/matrix/M = new - M.Translate(rand(-3, 3), rand(-1, 3)) - animate(src, transform=M, time=2) - -/obj/machinery/washing_machine/proc/clean_blood() - if(!busy) - bloody_mess = FALSE - update_icon() - -/obj/machinery/washing_machine/proc/wash_cycle() - for(var/X in contents) - var/atom/movable/AM = X - SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) - AM.machine_wash(src) - - busy = FALSE - if(color_source) - qdel(color_source) - color_source = null - update_icon() - -/obj/item/proc/dye_item(dye_color, dye_key_override) - var/dye_key_selector = dye_key_override ? dye_key_override : dying_key - if(undyeable) - return FALSE - if(dye_key_selector) - if(!GLOB.dye_registry[dye_key_selector]) - log_runtime("Item just tried to be dyed with an invalid registry key: [dye_key_selector]") - return FALSE - var/obj/item/target_type = GLOB.dye_registry[dye_key_selector][dye_color] - if(target_type) - icon = initial(target_type.icon) - icon_state = initial(target_type.icon_state) - lefthand_file = initial(target_type.lefthand_file) - righthand_file = initial(target_type.righthand_file) - inhand_icon_state = initial(target_type.inhand_icon_state) - worn_icon = initial(target_type.worn_icon) - worn_icon_state = initial(target_type.worn_icon_state) - inhand_x_dimension = initial(target_type.inhand_x_dimension) - inhand_y_dimension = initial(target_type.inhand_y_dimension) - name = initial(target_type.name) - desc = "[initial(target_type.desc)] The colors look a little dodgy." - return target_type //successfully "appearance copy" dyed something; returns the target type as a hacky way of extending - add_atom_colour(dye_color, FIXED_COLOUR_PRIORITY) - return FALSE - -//what happens to this object when washed inside a washing machine -/atom/movable/proc/machine_wash(obj/machinery/washing_machine/WM) - return - -/obj/item/stack/sheet/hairlesshide/machine_wash(obj/machinery/washing_machine/WM) - new /obj/item/stack/sheet/wethide(drop_location(), amount) - qdel(src) - -/obj/item/clothing/suit/hooded/ian_costume/machine_wash(obj/machinery/washing_machine/WM) - new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(loc) - qdel(src) - -/mob/living/simple_animal/pet/machine_wash(obj/machinery/washing_machine/WM) - WM.bloody_mess = TRUE - gib() - -/obj/item/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - dye_item(WM.color_source.dye_color) - -/obj/item/clothing/under/dye_item(dye_color, dye_key) - . = ..() - if(.) - var/obj/item/clothing/under/U = . - can_adjust = initial(U.can_adjust) - if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable - toggle_jumpsuit_adjust() - -/obj/item/clothing/under/machine_wash(obj/machinery/washing_machine/WM) - freshly_laundered = TRUE - addtimer(VARSET_CALLBACK(src, freshly_laundered, FALSE), 5 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE) - ..() - -/obj/item/clothing/head/mob_holder/machine_wash(obj/machinery/washing_machine/WM) - ..() - held_mob.machine_wash(WM) - -/obj/item/clothing/shoes/sneakers/machine_wash(obj/machinery/washing_machine/WM) - if(chained) - chained = 0 - slowdown = SHOES_SLOWDOWN - new /obj/item/restraints/handcuffs(loc) - ..() - -/obj/machinery/washing_machine/relaymove(mob/user) - container_resist(user) - -/obj/machinery/washing_machine/container_resist(mob/living/user) - if(!busy) - add_fingerprint(user) - open_machine() - -/obj/machinery/washing_machine/update_icon_state() - if(busy) - icon_state = "wm_running_[bloody_mess]" - else if(bloody_mess) - icon_state = "wm_[state_open]_blood" - else - var/full = contents.len ? 1 : 0 - icon_state = "wm_[state_open]_[full]" - -/obj/machinery/washing_machine/update_overlays() - . = ..() - if(panel_open) - . += "wm_panel" - -/obj/machinery/washing_machine/attackby(obj/item/W, mob/user, params) - if(panel_open && !busy && default_unfasten_wrench(user, W)) - return - - if(default_deconstruction_screwdriver(user, null, null, W)) - update_icon() - return - - else if(user.a_intent != INTENT_HARM) - if (!state_open) - to_chat(user, "Open the door first!") - return TRUE - - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first!") - return TRUE - - if(contents.len >= max_wash_capacity) - to_chat(user, "The washing machine is full!") - return TRUE - - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") - return TRUE - if(W.dye_color) - color_source = W - update_icon() - - else - return ..() - -/obj/machinery/washing_machine/attack_hand(mob/user) - . = ..() - if(.) - return - if(busy) - to_chat(user, "[src] is busy!") - return - - if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(L.buckled || L.has_buckled_mobs()) - return - if(state_open) - if(istype(L, /mob/living/simple_animal/pet)) - L.forceMove(src) - update_icon() - return - - if(!state_open) - open_machine() - else - state_open = FALSE //close the door - update_icon() - -/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal(drop_location(), 2) - qdel(src) - -/obj/machinery/washing_machine/open_machine(drop = 1) - ..() - density = TRUE //because machinery/open_machine() sets it to 0 - color_source = null +//dye registry, add dye colors and their resulting output here if you want the sprite to change instead of just the color. +GLOBAL_LIST_INIT(dye_registry, list( + DYE_REGISTRY_UNDER = list( + DYE_RED = /obj/item/clothing/under/color/red, + DYE_ORANGE = /obj/item/clothing/under/color/orange, + DYE_YELLOW = /obj/item/clothing/under/color/yellow, + DYE_GREEN = /obj/item/clothing/under/color/green, + DYE_BLUE = /obj/item/clothing/under/color/blue, + DYE_PURPLE = /obj/item/clothing/under/color/lightpurple, + DYE_BLACK = /obj/item/clothing/under/color/black, + DYE_WHITE = /obj/item/clothing/under/color/white, + DYE_RAINBOW = /obj/item/clothing/under/color/rainbow, + DYE_MIME = /obj/item/clothing/under/rank/civilian/mime, + DYE_CLOWN = /obj/item/clothing/under/rank/civilian/clown, + DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain, + DYE_QM = /obj/item/clothing/under/rank/cargo/qm, + DYE_LAW = /obj/item/clothing/under/suit/black, + DYE_CAPTAIN = /obj/item/clothing/under/rank/captain, + DYE_HOP = /obj/item/clothing/under/rank/civilian/head_of_personnel, + DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security, + DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer, + DYE_RD = /obj/item/clothing/under/rank/rnd/research_director, + DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer, + DYE_REDCOAT = /obj/item/clothing/under/costume/redcoat, + DYE_SYNDICATE = /obj/item/clothing/under/syndicate, + DYE_CENTCOM = /obj/item/clothing/under/rank/centcom/commander + ), + DYE_REGISTRY_JUMPSKIRT = list( + DYE_RED = /obj/item/clothing/under/color/jumpskirt/red, + DYE_ORANGE = /obj/item/clothing/under/color/jumpskirt/orange, + DYE_YELLOW = /obj/item/clothing/under/color/jumpskirt/yellow, + DYE_GREEN = /obj/item/clothing/under/color/jumpskirt/green, + DYE_BLUE = /obj/item/clothing/under/color/jumpskirt/blue, + DYE_PURPLE = /obj/item/clothing/under/color/jumpskirt/lightpurple, + DYE_BLACK = /obj/item/clothing/under/color/jumpskirt/black, + DYE_WHITE = /obj/item/clothing/under/color/jumpskirt/white, + DYE_RAINBOW = /obj/item/clothing/under/color/jumpskirt/rainbow, + DYE_MIME = /obj/item/clothing/under/rank/civilian/mime/skirt, + DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain/skirt, + DYE_QM = /obj/item/clothing/under/rank/cargo/qm/skirt, + DYE_CAPTAIN = /obj/item/clothing/under/rank/captain/skirt, + DYE_HOP = /obj/item/clothing/under/rank/civilian/head_of_personnel/skirt, + DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security/skirt, + DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer/skirt, + DYE_RD = /obj/item/clothing/under/rank/rnd/research_director/skirt, + DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt, + ), + DYE_REGISTRY_GLOVES = list( + DYE_RED = /obj/item/clothing/gloves/color/red, + DYE_ORANGE = /obj/item/clothing/gloves/color/orange, + DYE_YELLOW = /obj/item/clothing/gloves/color/yellow, + DYE_GREEN = /obj/item/clothing/gloves/color/green, + DYE_BLUE = /obj/item/clothing/gloves/color/blue, + DYE_PURPLE = /obj/item/clothing/gloves/color/purple, + DYE_BLACK = /obj/item/clothing/gloves/color/black, + DYE_WHITE = /obj/item/clothing/gloves/color/white, + DYE_RAINBOW = /obj/item/clothing/gloves/color/rainbow, + DYE_MIME = /obj/item/clothing/gloves/color/white, + DYE_CLOWN = /obj/item/clothing/gloves/color/rainbow, + DYE_QM = /obj/item/clothing/gloves/color/brown, + DYE_CAPTAIN = /obj/item/clothing/gloves/color/captain, + DYE_HOP = /obj/item/clothing/gloves/color/grey, + DYE_HOS = /obj/item/clothing/gloves/color/black, + DYE_CE = /obj/item/clothing/gloves/color/black, + DYE_RD = /obj/item/clothing/gloves/color/grey, + DYE_CMO = /obj/item/clothing/gloves/color/latex/nitrile, + DYE_REDCOAT = /obj/item/clothing/gloves/color/white, + DYE_SYNDICATE = /obj/item/clothing/gloves/combat, + DYE_CENTCOM = /obj/item/clothing/gloves/combat + ), + DYE_REGISTRY_SNEAKERS = list( + DYE_RED = /obj/item/clothing/shoes/sneakers/red, + DYE_ORANGE = /obj/item/clothing/shoes/sneakers/orange, + DYE_YELLOW = /obj/item/clothing/shoes/sneakers/yellow, + DYE_GREEN = /obj/item/clothing/shoes/sneakers/green, + DYE_BLUE = /obj/item/clothing/shoes/sneakers/blue, + DYE_PURPLE = /obj/item/clothing/shoes/sneakers/purple, + DYE_BLACK = /obj/item/clothing/shoes/sneakers/black, + DYE_WHITE = /obj/item/clothing/shoes/sneakers/white, + DYE_RAINBOW = /obj/item/clothing/shoes/sneakers/rainbow, + DYE_MIME = /obj/item/clothing/shoes/sneakers/black, + DYE_CLOWN = /obj/item/clothing/shoes/sneakers/rainbow, + DYE_QM = /obj/item/clothing/shoes/sneakers/brown, + DYE_CAPTAIN = /obj/item/clothing/shoes/sneakers/brown, + DYE_HOP = /obj/item/clothing/shoes/sneakers/brown, + DYE_CE = /obj/item/clothing/shoes/sneakers/brown, + DYE_RD = /obj/item/clothing/shoes/sneakers/brown, + DYE_CMO = /obj/item/clothing/shoes/sneakers/brown, + DYE_SYNDICATE = /obj/item/clothing/shoes/combat, + DYE_CENTCOM = /obj/item/clothing/shoes/combat + ), + DYE_REGISTRY_FANNYPACK = list( + DYE_RED = /obj/item/storage/belt/fannypack/red, + DYE_ORANGE = /obj/item/storage/belt/fannypack/orange, + DYE_YELLOW = /obj/item/storage/belt/fannypack/yellow, + DYE_GREEN = /obj/item/storage/belt/fannypack/green, + DYE_BLUE = /obj/item/storage/belt/fannypack/blue, + DYE_PURPLE = /obj/item/storage/belt/fannypack/purple, + DYE_BLACK = /obj/item/storage/belt/fannypack/black, + DYE_WHITE = /obj/item/storage/belt/fannypack/white, + DYE_SYNDICATE = /obj/item/storage/belt/military + ), + DYE_REGISTRY_BEDSHEET = list( + DYE_RED = /obj/item/bedsheet/red, + DYE_ORANGE = /obj/item/bedsheet/orange, + DYE_YELLOW = /obj/item/bedsheet/yellow, + DYE_GREEN = /obj/item/bedsheet/green, + DYE_BLUE = /obj/item/bedsheet/blue, + DYE_PURPLE = /obj/item/bedsheet/purple, + DYE_BLACK = /obj/item/bedsheet/black, + DYE_WHITE = /obj/item/bedsheet, + DYE_RAINBOW = /obj/item/bedsheet/rainbow, + DYE_MIME = /obj/item/bedsheet/mime, + DYE_CLOWN = /obj/item/bedsheet/clown, + DYE_CHAP = /obj/item/bedsheet/chaplain, + DYE_QM = /obj/item/bedsheet/qm, + DYE_LAW = /obj/item/bedsheet/black, + DYE_CAPTAIN = /obj/item/bedsheet/captain, + DYE_HOP = /obj/item/bedsheet/hop, + DYE_HOS = /obj/item/bedsheet/hos, + DYE_CE = /obj/item/bedsheet/ce, + DYE_RD = /obj/item/bedsheet/rd, + DYE_CMO = /obj/item/bedsheet/cmo, + DYE_COSMIC = /obj/item/bedsheet/cosmos, + DYE_SYNDICATE = /obj/item/bedsheet/syndie, + DYE_CENTCOM = /obj/item/bedsheet/centcom + ), + DYE_LAWYER_SPECIAL = list( + DYE_COSMIC = /obj/item/clothing/under/rank/civilian/lawyer/galaxy, + DYE_SYNDICATE = /obj/item/clothing/under/rank/civilian/lawyer/galaxy/red + ) +)) + +/obj/machinery/washing_machine + name = "washing machine" + desc = "Gets rid of those pesky bloodstains, or your money back!" + icon = 'icons/obj/machines/washing_machine.dmi' + icon_state = "wm_1_0" + density = TRUE + state_open = TRUE + var/busy = FALSE + var/bloody_mess = 0 + var/obj/item/color_source + var/max_wash_capacity = 5 + +/obj/machinery/washing_machine/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/machinery/washing_machine/examine(mob/user) + . = ..() + if(!busy) + . += "Alt-click it to start a wash cycle." + +/obj/machinery/washing_machine/AltClick(mob/user) + if(!user.canUseTopic(src, !issilicon(user))) + return + if(busy) + return + if(state_open) + to_chat(user, "Close the door first!") + return + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first!") + return + busy = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/wash_cycle), 200) + + START_PROCESSING(SSfastprocess, src) + +/obj/machinery/washing_machine/process() + if(!busy) + animate(src, transform=matrix(), time=2) + return PROCESS_KILL + if(anchored) + if(prob(5)) + var/matrix/M = new + M.Translate(rand(-1, 1), rand(0, 1)) + animate(src, transform=M, time=1) + animate(transform=matrix(), time=1) + else + if(prob(1)) + step(src, pick(GLOB.cardinals)) + var/matrix/M = new + M.Translate(rand(-3, 3), rand(-1, 3)) + animate(src, transform=M, time=2) + +/obj/machinery/washing_machine/proc/clean_blood() + if(!busy) + bloody_mess = FALSE + update_icon() + +/obj/machinery/washing_machine/proc/wash_cycle() + for(var/X in contents) + var/atom/movable/AM = X + SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) + AM.machine_wash(src) + + busy = FALSE + if(color_source) + qdel(color_source) + color_source = null + update_icon() + +/obj/item/proc/dye_item(dye_color, dye_key_override) + var/dye_key_selector = dye_key_override ? dye_key_override : dying_key + if(undyeable) + return FALSE + if(dye_key_selector) + if(!GLOB.dye_registry[dye_key_selector]) + log_runtime("Item just tried to be dyed with an invalid registry key: [dye_key_selector]") + return FALSE + var/obj/item/target_type = GLOB.dye_registry[dye_key_selector][dye_color] + if(target_type) + icon = initial(target_type.icon) + icon_state = initial(target_type.icon_state) + lefthand_file = initial(target_type.lefthand_file) + righthand_file = initial(target_type.righthand_file) + inhand_icon_state = initial(target_type.inhand_icon_state) + worn_icon = initial(target_type.worn_icon) + worn_icon_state = initial(target_type.worn_icon_state) + inhand_x_dimension = initial(target_type.inhand_x_dimension) + inhand_y_dimension = initial(target_type.inhand_y_dimension) + name = initial(target_type.name) + desc = "[initial(target_type.desc)] The colors look a little dodgy." + return target_type //successfully "appearance copy" dyed something; returns the target type as a hacky way of extending + add_atom_colour(dye_color, FIXED_COLOUR_PRIORITY) + return FALSE + +//what happens to this object when washed inside a washing machine +/atom/movable/proc/machine_wash(obj/machinery/washing_machine/WM) + return + +/obj/item/stack/sheet/hairlesshide/machine_wash(obj/machinery/washing_machine/WM) + new /obj/item/stack/sheet/wethide(drop_location(), amount) + qdel(src) + +/obj/item/clothing/suit/hooded/ian_costume/machine_wash(obj/machinery/washing_machine/WM) + new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(loc) + qdel(src) + +/mob/living/simple_animal/pet/machine_wash(obj/machinery/washing_machine/WM) + WM.bloody_mess = TRUE + gib() + +/obj/item/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + dye_item(WM.color_source.dye_color) + +/obj/item/clothing/under/dye_item(dye_color, dye_key) + . = ..() + if(.) + var/obj/item/clothing/under/U = . + can_adjust = initial(U.can_adjust) + if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable + toggle_jumpsuit_adjust() + +/obj/item/clothing/under/machine_wash(obj/machinery/washing_machine/WM) + freshly_laundered = TRUE + addtimer(VARSET_CALLBACK(src, freshly_laundered, FALSE), 5 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE) + ..() + +/obj/item/clothing/head/mob_holder/machine_wash(obj/machinery/washing_machine/WM) + ..() + held_mob.machine_wash(WM) + +/obj/item/clothing/shoes/sneakers/machine_wash(obj/machinery/washing_machine/WM) + if(chained) + chained = 0 + slowdown = SHOES_SLOWDOWN + new /obj/item/restraints/handcuffs(loc) + ..() + +/obj/machinery/washing_machine/relaymove(mob/user) + container_resist(user) + +/obj/machinery/washing_machine/container_resist(mob/living/user) + if(!busy) + add_fingerprint(user) + open_machine() + +/obj/machinery/washing_machine/update_icon_state() + if(busy) + icon_state = "wm_running_[bloody_mess]" + else if(bloody_mess) + icon_state = "wm_[state_open]_blood" + else + var/full = contents.len ? 1 : 0 + icon_state = "wm_[state_open]_[full]" + +/obj/machinery/washing_machine/update_overlays() + . = ..() + if(panel_open) + . += "wm_panel" + +/obj/machinery/washing_machine/attackby(obj/item/W, mob/user, params) + if(panel_open && !busy && default_unfasten_wrench(user, W)) + return + + if(default_deconstruction_screwdriver(user, null, null, W)) + update_icon() + return + + else if(user.a_intent != INTENT_HARM) + if (!state_open) + to_chat(user, "Open the door first!") + return TRUE + + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first!") + return TRUE + + if(contents.len >= max_wash_capacity) + to_chat(user, "The washing machine is full!") + return TRUE + + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") + return TRUE + if(W.dye_color) + color_source = W + update_icon() + + else + return ..() + +/obj/machinery/washing_machine/attack_hand(mob/user) + . = ..() + if(.) + return + if(busy) + to_chat(user, "[src] is busy!") + return + + if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) + var/mob/living/L = user.pulling + if(L.buckled || L.has_buckled_mobs()) + return + if(state_open) + if(istype(L, /mob/living/simple_animal/pet)) + L.forceMove(src) + update_icon() + return + + if(!state_open) + open_machine() + else + state_open = FALSE //close the door + update_icon() + +/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal(drop_location(), 2) + qdel(src) + +/obj/machinery/washing_machine/open_machine(drop = 1) + ..() + density = TRUE //because machinery/open_machine() sets it to 0 + color_source = null diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm index a16db8db30f..d79d5554104 100644 --- a/code/game/machinery/wishgranter.dm +++ b/code/game/machinery/wishgranter.dm @@ -1,43 +1,43 @@ -/obj/machinery/wish_granter - name = "wish granter" - desc = "You're not so sure about this, anymore..." - icon = 'icons/obj/device.dmi' - icon_state = "syndbeacon" - - use_power = NO_POWER_USE - density = TRUE - - var/charges = 1 - var/insisting = 0 - -/obj/machinery/wish_granter/attack_hand(mob/living/carbon/user) - . = ..() - if(.) - return - if(charges <= 0) - to_chat(user, "The Wish Granter lies silent.") - return - - else if(!ishuman(user)) - to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") - return - - else if(is_special_character(user)) - to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") - - else if (!insisting) - to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") - insisting++ - - else - to_chat(user, "You speak. [pick("I want the station to disappear","Humanity is corrupt, mankind must be destroyed","I want to be rich", "I want to rule the world","I want immortality.")]. The Wish Granter answers.") - to_chat(user, "Your head pounds for a moment, before your vision clears. You are the avatar of the Wish Granter, and your power is LIMITLESS! And it's all yours. You need to make sure no one can take it from you. No one can know, first.") - - charges-- - insisting = 0 - - user.mind.add_antag_datum(/datum/antagonist/wishgranter) - - to_chat(user, "You have a very bad feeling about this.") - - return +/obj/machinery/wish_granter + name = "wish granter" + desc = "You're not so sure about this, anymore..." + icon = 'icons/obj/device.dmi' + icon_state = "syndbeacon" + + use_power = NO_POWER_USE + density = TRUE + + var/charges = 1 + var/insisting = 0 + +/obj/machinery/wish_granter/attack_hand(mob/living/carbon/user) + . = ..() + if(.) + return + if(charges <= 0) + to_chat(user, "The Wish Granter lies silent.") + return + + else if(!ishuman(user)) + to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") + return + + else if(is_special_character(user)) + to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") + + else if (!insisting) + to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") + insisting++ + + else + to_chat(user, "You speak. [pick("I want the station to disappear","Humanity is corrupt, mankind must be destroyed","I want to be rich", "I want to rule the world","I want immortality.")]. The Wish Granter answers.") + to_chat(user, "Your head pounds for a moment, before your vision clears. You are the avatar of the Wish Granter, and your power is LIMITLESS! And it's all yours. You need to make sure no one can take it from you. No one can know, first.") + + charges-- + insisting = 0 + + user.mind.add_antag_datum(/datum/antagonist/wishgranter) + + to_chat(user, "You have a very bad feeling about this.") + + return diff --git a/code/game/mecha/combat/combat.dm b/code/game/mecha/combat/combat.dm index 0834f28d008..2e26096eacf 100644 --- a/code/game/mecha/combat/combat.dm +++ b/code/game/mecha/combat/combat.dm @@ -1,18 +1,18 @@ -/obj/mecha/combat - force = 30 - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY) - internal_damage_threshold = 50 - armor = list("melee" = 30, "bullet" = 30, "laser" = 15, "energy" = 20, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' - destruction_sleep_duration = 40 - exit_delay = 40 - -/obj/mecha/combat/restore_equipment() - mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' - . = ..() - -/obj/mecha/combat/proc/max_ammo() //Max the ammo stored for Nuke Ops mechs, or anyone else that calls this - for(var/obj/item/I in equipment) - if(istype(I, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/)) - var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun = I - gun.projectiles_cache = gun.projectiles_cache_max +/obj/mecha/combat + force = 30 + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY) + internal_damage_threshold = 50 + armor = list("melee" = 30, "bullet" = 30, "laser" = 15, "energy" = 20, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' + destruction_sleep_duration = 40 + exit_delay = 40 + +/obj/mecha/combat/restore_equipment() + mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' + . = ..() + +/obj/mecha/combat/proc/max_ammo() //Max the ammo stored for Nuke Ops mechs, or anyone else that calls this + for(var/obj/item/I in equipment) + if(istype(I, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/)) + var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun = I + gun.projectiles_cache = gun.projectiles_cache_max diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm index 07369c90998..efafa478429 100644 --- a/code/game/mecha/combat/durand.dm +++ b/code/game/mecha/combat/durand.dm @@ -1,210 +1,210 @@ -/obj/mecha/combat/durand - desc = "An aging combat exosuit utilized by the Nanotrasen corporation. Originally developed to combat hostile alien lifeforms." - name = "\improper Durand" - icon_state = "durand" - step_in = 4 - dir_in = 1 //Facing North. - max_integrity = 400 - deflect_chance = 20 - armor = list("melee" = 40, "bullet" = 35, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) - max_temperature = 30000 - infra_luminosity = 8 - force = 40 - wreckage = /obj/structure/mecha_wreckage/durand - var/obj/durand_shield/shield - -/obj/mecha/combat/durand/Initialize() - shield = new/obj/durand_shield - shield.chassis = src - shield.layer = layer - RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/relay) - RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, .proc/prehit) - . = ..() - -/obj/mecha/combat/durand/Destroy() - if(shield) - qdel(shield) - . = ..() - -/obj/mecha/combat/durand/GrantActions(mob/living/user, human_occupant = 0) - ..() - defense_action.Grant(user, src) - -/obj/mecha/combat/durand/RemoveActions(mob/living/user, human_occupant = 0) - ..() - defense_action.Remove(user) - -/obj/mecha/combat/durand/process() - . = ..() - if(defense_mode && !use_power(100)) - defense_action.Activate(forced_state = TRUE) - -/obj/mecha/combat/durand/domove(direction) - . = ..() - if(shield) - shield.forceMove(loc) - shield.dir = dir - -/obj/mecha/combat/durand/forceMove(var/turf/T) - . = ..() - shield.forceMove(T) - -/obj/mecha/combat/durand/go_out(forced, atom/newloc = loc) - if(defense_mode) - defense_action.Activate(forced_state = TRUE) - . = ..() - -///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA. -/obj/mecha/combat/durand/proc/relay(datum/source, list/signal_args) - if(!shield) //if the shield somehow got deleted - shield = new/obj/durand_shield - shield.chassis = src - shield.layer = layer - shield.forceMove(loc) - shield.dir = dir - SEND_SIGNAL(shield, COMSIG_MECHA_ACTION_ACTIVATE, source, signal_args) - -//Redirects projectiles to the shield if defense_check decides they should be blocked and returns true. -/obj/mecha/combat/durand/proc/prehit(obj/projectile/source, list/signal_args) - if(defense_check(source.loc) && shield) - signal_args[2] = shield - - -/**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield. -Expects a turf. Returns true if the attack should be blocked, false if not.*/ -/obj/mecha/combat/durand/proc/defense_check(var/turf/aloc) - if (!defense_mode || !shield || shield.switching) - return FALSE - . = FALSE - switch(dir) - if (1) - if(abs(x - aloc.x) <= (y - aloc.y) * -2) - . = TRUE - if (2) - if(abs(x - aloc.x) <= (y - aloc.y) * 2) - . = TRUE - if (4) - if(abs(y - aloc.y) <= (x - aloc.x) * -2) - . = TRUE - if (8) - if(abs(y - aloc.y) <= (x - aloc.x) * 2) - . = TRUE - return - -obj/mecha/combat/durand/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) - if(defense_check(user.loc)) - log_message("Attack absorbed by defense field. Attacker - [user].", LOG_MECHA, color="orange") - shield.attack_generic(user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) - else - . = ..() - -/obj/mecha/combat/durand/blob_act(obj/structure/blob/B) - if(defense_check(B.loc)) - log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red") - log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange") - shield.blob_act(B) - else - . = ..() - -/obj/mecha/combat/durand/attackby(obj/item/W as obj, mob/user as mob, params) - if(defense_check(user.loc)) - log_message("Attack absorbed by defense field. Attacker - [user], with [W]", LOG_MECHA, color="orange") - shield.attackby(W, user, params) - else - . = ..() - -/obj/mecha/combat/durand/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(defense_check(AM.loc)) - log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange") - shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) - else - . = ..() - -//////////////////////////// -///// Shield processing //// -//////////////////////////// - -/**An object to take the hit for us when using the Durand's defense mode. -It is spawned in during the durand's initilization, and always stays on the same tile. -Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the -attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its -own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out.*/ - -/obj/durand_shield //projectiles get passed to this when defense mode is enabled - name = "defense grid" - icon = 'icons/mecha/durand_shield.dmi' - icon_state = "shield_null" - invisibility = INVISIBILITY_MAXIMUM //no showing on right-click - pixel_y = 4 - max_integrity = 10000 - obj_integrity = 10000 - anchored = TRUE - var/obj/mecha/combat/durand/chassis ///Our link back to the durand - var/switching = FALSE ///To keep track of things during the animation - -/obj/durand_shield/Initialize() - . = ..() - RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/activate) - -/obj/durand_shield/Destroy() - if(chassis) - chassis.shield = null - . = ..() - -/**Handles activating and deactivating the shield. This proc is called by a signal sent from the mech's action button -and relayed by the mech itself. The "forced" variabe, signal_args[1], will skip the to-pilot text and is meant for when -the shield is disabled by means other than the action button (like running out of power)*/ - -/obj/durand_shield/proc/activate(datum/source, datum/action/innate/mecha/mech_defense_mode/button, list/signal_args) - if(!chassis || !chassis.occupant) - return - if(switching && !signal_args[1]) - return - if(!chassis.defense_mode && (!chassis.cell || chassis.cell.charge < 100)) //If it's off, and we have less than 100 units of power - chassis.occupant_message("Insufficient power; cannot activate defense mode.") - return - switching = TRUE - chassis.defense_mode = !chassis.defense_mode - chassis.defense_action.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" //This is backwards because we haven't changed the var yet - if(!signal_args[1]) - chassis.occupant_message("Defense mode [chassis.defense_mode?"enabled":"disabled"].") - chassis.log_message("User has toggled defense mode -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) - else - chassis.log_message("defense mode state changed -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) - chassis.defense_action.UpdateButtonIcon() - - if(chassis.defense_mode) - invisibility = 0 - flick("shield_raise", src) - playsound(src, 'sound/mecha/mech_shield_raise.ogg', 50, FALSE) - set_light(l_range = MINIMUM_USEFUL_LIGHT_RANGE , l_power = 5, l_color = "#00FFFF") - sleep(3) - icon_state = "shield" - else - flick("shield_drop", src) - playsound(src, 'sound/mecha/mech_shield_drop.ogg', 50, FALSE) - sleep(5) - set_light(0) - icon_state = "shield_null" - invisibility = INVISIBILITY_MAXIMUM //no showing on right-click - switching = FALSE - -/obj/durand_shield/take_damage() - if(!chassis) - qdel(src) - return - if(!chassis.defense_mode) //if defense mode is disabled, we're taking damage that we shouldn't be taking - return - . = ..() - flick("shield_impact", src) - if(!chassis.use_power((max_integrity - obj_integrity) * 100)) - chassis.cell?.charge = 0 - chassis.defense_action.Activate(forced_state = TRUE) - obj_integrity = 10000 - -/obj/durand_shield/play_attack_sound() - playsound(src, 'sound/mecha/mech_shield_deflect.ogg', 100, TRUE) - -/obj/durand_shield/bullet_act() - play_attack_sound() - . = ..() +/obj/mecha/combat/durand + desc = "An aging combat exosuit utilized by the Nanotrasen corporation. Originally developed to combat hostile alien lifeforms." + name = "\improper Durand" + icon_state = "durand" + step_in = 4 + dir_in = 1 //Facing North. + max_integrity = 400 + deflect_chance = 20 + armor = list("melee" = 40, "bullet" = 35, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) + max_temperature = 30000 + infra_luminosity = 8 + force = 40 + wreckage = /obj/structure/mecha_wreckage/durand + var/obj/durand_shield/shield + +/obj/mecha/combat/durand/Initialize() + shield = new/obj/durand_shield + shield.chassis = src + shield.layer = layer + RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/relay) + RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, .proc/prehit) + . = ..() + +/obj/mecha/combat/durand/Destroy() + if(shield) + qdel(shield) + . = ..() + +/obj/mecha/combat/durand/GrantActions(mob/living/user, human_occupant = 0) + ..() + defense_action.Grant(user, src) + +/obj/mecha/combat/durand/RemoveActions(mob/living/user, human_occupant = 0) + ..() + defense_action.Remove(user) + +/obj/mecha/combat/durand/process() + . = ..() + if(defense_mode && !use_power(100)) + defense_action.Activate(forced_state = TRUE) + +/obj/mecha/combat/durand/domove(direction) + . = ..() + if(shield) + shield.forceMove(loc) + shield.dir = dir + +/obj/mecha/combat/durand/forceMove(var/turf/T) + . = ..() + shield.forceMove(T) + +/obj/mecha/combat/durand/go_out(forced, atom/newloc = loc) + if(defense_mode) + defense_action.Activate(forced_state = TRUE) + . = ..() + +///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA. +/obj/mecha/combat/durand/proc/relay(datum/source, list/signal_args) + if(!shield) //if the shield somehow got deleted + shield = new/obj/durand_shield + shield.chassis = src + shield.layer = layer + shield.forceMove(loc) + shield.dir = dir + SEND_SIGNAL(shield, COMSIG_MECHA_ACTION_ACTIVATE, source, signal_args) + +//Redirects projectiles to the shield if defense_check decides they should be blocked and returns true. +/obj/mecha/combat/durand/proc/prehit(obj/projectile/source, list/signal_args) + if(defense_check(source.loc) && shield) + signal_args[2] = shield + + +/**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield. +Expects a turf. Returns true if the attack should be blocked, false if not.*/ +/obj/mecha/combat/durand/proc/defense_check(var/turf/aloc) + if (!defense_mode || !shield || shield.switching) + return FALSE + . = FALSE + switch(dir) + if (1) + if(abs(x - aloc.x) <= (y - aloc.y) * -2) + . = TRUE + if (2) + if(abs(x - aloc.x) <= (y - aloc.y) * 2) + . = TRUE + if (4) + if(abs(y - aloc.y) <= (x - aloc.x) * -2) + . = TRUE + if (8) + if(abs(y - aloc.y) <= (x - aloc.x) * 2) + . = TRUE + return + +obj/mecha/combat/durand/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) + if(defense_check(user.loc)) + log_message("Attack absorbed by defense field. Attacker - [user].", LOG_MECHA, color="orange") + shield.attack_generic(user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) + else + . = ..() + +/obj/mecha/combat/durand/blob_act(obj/structure/blob/B) + if(defense_check(B.loc)) + log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red") + log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange") + shield.blob_act(B) + else + . = ..() + +/obj/mecha/combat/durand/attackby(obj/item/W as obj, mob/user as mob, params) + if(defense_check(user.loc)) + log_message("Attack absorbed by defense field. Attacker - [user], with [W]", LOG_MECHA, color="orange") + shield.attackby(W, user, params) + else + . = ..() + +/obj/mecha/combat/durand/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(defense_check(AM.loc)) + log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange") + shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) + else + . = ..() + +//////////////////////////// +///// Shield processing //// +//////////////////////////// + +/**An object to take the hit for us when using the Durand's defense mode. +It is spawned in during the durand's initilization, and always stays on the same tile. +Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the +attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its +own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out.*/ + +/obj/durand_shield //projectiles get passed to this when defense mode is enabled + name = "defense grid" + icon = 'icons/mecha/durand_shield.dmi' + icon_state = "shield_null" + invisibility = INVISIBILITY_MAXIMUM //no showing on right-click + pixel_y = 4 + max_integrity = 10000 + obj_integrity = 10000 + anchored = TRUE + var/obj/mecha/combat/durand/chassis ///Our link back to the durand + var/switching = FALSE ///To keep track of things during the animation + +/obj/durand_shield/Initialize() + . = ..() + RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/activate) + +/obj/durand_shield/Destroy() + if(chassis) + chassis.shield = null + . = ..() + +/**Handles activating and deactivating the shield. This proc is called by a signal sent from the mech's action button +and relayed by the mech itself. The "forced" variabe, signal_args[1], will skip the to-pilot text and is meant for when +the shield is disabled by means other than the action button (like running out of power)*/ + +/obj/durand_shield/proc/activate(datum/source, datum/action/innate/mecha/mech_defense_mode/button, list/signal_args) + if(!chassis || !chassis.occupant) + return + if(switching && !signal_args[1]) + return + if(!chassis.defense_mode && (!chassis.cell || chassis.cell.charge < 100)) //If it's off, and we have less than 100 units of power + chassis.occupant_message("Insufficient power; cannot activate defense mode.") + return + switching = TRUE + chassis.defense_mode = !chassis.defense_mode + chassis.defense_action.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" //This is backwards because we haven't changed the var yet + if(!signal_args[1]) + chassis.occupant_message("Defense mode [chassis.defense_mode?"enabled":"disabled"].") + chassis.log_message("User has toggled defense mode -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) + else + chassis.log_message("defense mode state changed -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) + chassis.defense_action.UpdateButtonIcon() + + if(chassis.defense_mode) + invisibility = 0 + flick("shield_raise", src) + playsound(src, 'sound/mecha/mech_shield_raise.ogg', 50, FALSE) + set_light(l_range = MINIMUM_USEFUL_LIGHT_RANGE , l_power = 5, l_color = "#00FFFF") + sleep(3) + icon_state = "shield" + else + flick("shield_drop", src) + playsound(src, 'sound/mecha/mech_shield_drop.ogg', 50, FALSE) + sleep(5) + set_light(0) + icon_state = "shield_null" + invisibility = INVISIBILITY_MAXIMUM //no showing on right-click + switching = FALSE + +/obj/durand_shield/take_damage() + if(!chassis) + qdel(src) + return + if(!chassis.defense_mode) //if defense mode is disabled, we're taking damage that we shouldn't be taking + return + . = ..() + flick("shield_impact", src) + if(!chassis.use_power((max_integrity - obj_integrity) * 100)) + chassis.cell?.charge = 0 + chassis.defense_action.Activate(forced_state = TRUE) + obj_integrity = 10000 + +/obj/durand_shield/play_attack_sound() + playsound(src, 'sound/mecha/mech_shield_deflect.ogg', 100, TRUE) + +/obj/durand_shield/bullet_act() + play_attack_sound() + . = ..() diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm index 24197a34f45..5774716fca7 100644 --- a/code/game/mecha/combat/gygax.dm +++ b/code/game/mecha/combat/gygax.dm @@ -1,69 +1,69 @@ -/obj/mecha/combat/gygax - desc = "A lightweight, security exosuit. Popular among private and corporate security." - name = "\improper Gygax" - icon_state = "gygax" - step_in = 3 - dir_in = 1 //Facing North. - max_integrity = 250 - deflect_chance = 5 - armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 25000 - leg_overload_coeff = 80 - infra_luminosity = 6 - force = 25 - wreckage = /obj/structure/mecha_wreckage/gygax - internal_damage_threshold = 35 - max_equip = 3 - step_energy_drain = 3 - -/obj/mecha/combat/gygax/mechturn(direction) - . = ..() - if(!strafe && !occupant.client.keys_held["Alt"]) - mechstep(direction) //agile mechs get to move and turn in the same step - -/obj/mecha/combat/gygax/dark - desc = "A lightweight exosuit, painted in a dark scheme. This model appears to have some modifications." - name = "\improper Dark Gygax" - icon_state = "darkgygax" - max_integrity = 300 - deflect_chance = 20 - armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" =20, "fire" = 100, "acid" = 100) - max_temperature = 35000 - leg_overload_coeff = 70 - force = 30 - operation_req_access = list(ACCESS_SYNDICATE) - internals_req_access = list(ACCESS_SYNDICATE) - wreckage = /obj/structure/mecha_wreckage/gygax/dark - max_equip = 5 - destruction_sleep_duration = 20 - -/obj/mecha/combat/gygax/dark/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay - ME.attach(src) - max_ammo() - -/obj/mecha/combat/gygax/dark/add_cell(obj/item/stock_parts/cell/C=null) - if(C) - C.forceMove(src) - cell = C - return - cell = new /obj/item/stock_parts/cell/bluespace(src) - - -/obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0) - ..() - overload_action.Grant(user, src) - - -/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0) - ..() - overload_action.Remove(user) +/obj/mecha/combat/gygax + desc = "A lightweight, security exosuit. Popular among private and corporate security." + name = "\improper Gygax" + icon_state = "gygax" + step_in = 3 + dir_in = 1 //Facing North. + max_integrity = 250 + deflect_chance = 5 + armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 25000 + leg_overload_coeff = 80 + infra_luminosity = 6 + force = 25 + wreckage = /obj/structure/mecha_wreckage/gygax + internal_damage_threshold = 35 + max_equip = 3 + step_energy_drain = 3 + +/obj/mecha/combat/gygax/mechturn(direction) + . = ..() + if(!strafe && !occupant.client.keys_held["Alt"]) + mechstep(direction) //agile mechs get to move and turn in the same step + +/obj/mecha/combat/gygax/dark + desc = "A lightweight exosuit, painted in a dark scheme. This model appears to have some modifications." + name = "\improper Dark Gygax" + icon_state = "darkgygax" + max_integrity = 300 + deflect_chance = 20 + armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" =20, "fire" = 100, "acid" = 100) + max_temperature = 35000 + leg_overload_coeff = 70 + force = 30 + operation_req_access = list(ACCESS_SYNDICATE) + internals_req_access = list(ACCESS_SYNDICATE) + wreckage = /obj/structure/mecha_wreckage/gygax/dark + max_equip = 5 + destruction_sleep_duration = 20 + +/obj/mecha/combat/gygax/dark/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay + ME.attach(src) + max_ammo() + +/obj/mecha/combat/gygax/dark/add_cell(obj/item/stock_parts/cell/C=null) + if(C) + C.forceMove(src) + cell = C + return + cell = new /obj/item/stock_parts/cell/bluespace(src) + + +/obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0) + ..() + overload_action.Grant(user, src) + + +/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0) + ..() + overload_action.Remove(user) diff --git a/code/game/mecha/combat/honker.dm b/code/game/mecha/combat/honker.dm index ee25d6a8dc6..3f6715b4c2a 100644 --- a/code/game/mecha/combat/honker.dm +++ b/code/game/mecha/combat/honker.dm @@ -1,168 +1,168 @@ -/obj/mecha/combat/honker - desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. Used to spread the fun and joy of life. HONK!" - name = "\improper H.O.N.K" - icon_state = "honker" - step_in = 3 - max_integrity = 140 - deflect_chance = 60 - internal_damage_threshold = 60 - armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 25000 - infra_luminosity = 5 - operation_req_access = list(ACCESS_THEATRE) - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) - wreckage = /obj/structure/mecha_wreckage/honker - add_req_access = 0 - max_equip = 3 - var/squeak = TRUE - -/obj/mecha/combat/honker/get_stats_part() - var/integrity = obj_integrity/max_integrity*100 - var/cell_charge = get_charge() - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - var/tank_pressure = internal_tank ? round(int_tank_air.return_pressure(),0.01) : "None" - var/tank_temperature = internal_tank ? int_tank_air.temperature : "Unknown" - var/cabin_pressure = round(return_pressure(),0.01) - var/output = {"[report_internal_damage()] - [integrity<30?"DAMAGE LEVEL CRITICAL
                    ":null] - [internal_damage&MECHA_INT_TEMP_CONTROL?"CLOWN SUPPORT SYSTEM MALFUNCTION
                    ":null] - [internal_damage&MECHA_INT_TANK_BREACH?"GAS TANK HONK
                    ":null] - [internal_damage&MECHA_INT_CONTROL_LOST?"HONK-A-DOODLE - Recalibrate
                    ":null] - IntegriHONK: [integrity]%
                    - PowerHONK charge: [isnull(cell_charge)?"No power cell installed":"[cell.percent()]%"]
                    - Air source: [use_internal_tank?"Internal Airtank":"Environment"]
                    - AirHONK pressure: [tank_pressure]kPa
                    - AirHONK temperature: [tank_temperature]°K|[tank_temperature - T0C]°C
                    - HONK pressure: [cabin_pressure>WARNING_HIGH_PRESSURE ? "[cabin_pressure]": cabin_pressure]kPa
                    - HONK temperature: [return_temperature()]°K|[return_temperature() - T0C]°C
                    - Lights: [lights?"on":"off"]
                    - [dna_lock?"DNA-locked:
                    [dna_lock] \[Reset\]
                    ":null] - "} - return output - -/obj/mecha/combat/honker/get_stats_html() - var/output = {" - - - [src.name] data - - - - -
                    - [src.get_stats_part()] -
                    -
                    - [src.get_equipment_list()] -
                    -
                    -
                    - [src.get_commands()] -
                    - - - "} - return output - -/obj/mecha/combat/honker/get_commands() - var/output = {" - "} - output += ..() - return output - - -/obj/mecha/combat/honker/get_equipment_list() - if(!equipment.len) - return - var/output = "Honk-ON-Systems:
                    " - for(var/obj/item/mecha_parts/mecha_equipment/MT in equipment) - output += "
                    [MT.get_equip_info()]
                    " - output += "
                    " - return output - -/obj/mecha/combat/honker/play_stepsound() - if(squeak) - playsound(src, "clownstep", 70, 1) - squeak = !squeak - -/obj/mecha/combat/honker/Topic(href, href_list) - ..() - if (href_list["play_sound"]) - switch(href_list["play_sound"]) - if("sadtrombone") - playsound(src, 'sound/misc/sadtrombone.ogg', 50) - if("bikehorn") - playsound(src, 'sound/items/bikehorn.ogg', 50) - if("airhorn2") - playsound(src, 'sound/items/airhorn2.ogg', 40) //soundfile has higher than average volume - if("carhorn") - playsound(src, 'sound/items/carhorn.ogg', 80) //soundfile has lower than average volume - if("party_horn") - playsound(src, 'sound/items/party_horn.ogg', 50) - if("reee") - playsound(src, 'sound/effects/reee.ogg', 50) - if("weeoo1") - playsound(src, 'sound/items/weeoo1.ogg', 50) - if("hiss1") - playsound(src, 'sound/voice/hiss1.ogg', 50) - if("armbomb") - playsound(src, 'sound/weapons/armbomb.ogg', 50) - if("saberon") - playsound(src, 'sound/weapons/saberon.ogg', 50) - if("airlock_alien_prying") - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50) - if("lightningbolt") - playsound(src, 'sound/magic/lightningbolt.ogg', 50) - if("explosionfar") - playsound(src, 'sound/effects/explosionfar.ogg', 50) - return +/obj/mecha/combat/honker + desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. Used to spread the fun and joy of life. HONK!" + name = "\improper H.O.N.K" + icon_state = "honker" + step_in = 3 + max_integrity = 140 + deflect_chance = 60 + internal_damage_threshold = 60 + armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 25000 + infra_luminosity = 5 + operation_req_access = list(ACCESS_THEATRE) + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) + wreckage = /obj/structure/mecha_wreckage/honker + add_req_access = 0 + max_equip = 3 + var/squeak = TRUE + +/obj/mecha/combat/honker/get_stats_part() + var/integrity = obj_integrity/max_integrity*100 + var/cell_charge = get_charge() + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + var/tank_pressure = internal_tank ? round(int_tank_air.return_pressure(),0.01) : "None" + var/tank_temperature = internal_tank ? int_tank_air.temperature : "Unknown" + var/cabin_pressure = round(return_pressure(),0.01) + var/output = {"[report_internal_damage()] + [integrity<30?"DAMAGE LEVEL CRITICAL
                    ":null] + [internal_damage&MECHA_INT_TEMP_CONTROL?"CLOWN SUPPORT SYSTEM MALFUNCTION
                    ":null] + [internal_damage&MECHA_INT_TANK_BREACH?"GAS TANK HONK
                    ":null] + [internal_damage&MECHA_INT_CONTROL_LOST?"HONK-A-DOODLE - Recalibrate
                    ":null] + IntegriHONK: [integrity]%
                    + PowerHONK charge: [isnull(cell_charge)?"No power cell installed":"[cell.percent()]%"]
                    + Air source: [use_internal_tank?"Internal Airtank":"Environment"]
                    + AirHONK pressure: [tank_pressure]kPa
                    + AirHONK temperature: [tank_temperature]°K|[tank_temperature - T0C]°C
                    + HONK pressure: [cabin_pressure>WARNING_HIGH_PRESSURE ? "[cabin_pressure]": cabin_pressure]kPa
                    + HONK temperature: [return_temperature()]°K|[return_temperature() - T0C]°C
                    + Lights: [lights?"on":"off"]
                    + [dna_lock?"DNA-locked:
                    [dna_lock] \[Reset\]
                    ":null] + "} + return output + +/obj/mecha/combat/honker/get_stats_html() + var/output = {" + + + [src.name] data + + + + +
                    + [src.get_stats_part()] +
                    +
                    + [src.get_equipment_list()] +
                    +
                    +
                    + [src.get_commands()] +
                    + + + "} + return output + +/obj/mecha/combat/honker/get_commands() + var/output = {" + "} + output += ..() + return output + + +/obj/mecha/combat/honker/get_equipment_list() + if(!equipment.len) + return + var/output = "Honk-ON-Systems:
                    " + for(var/obj/item/mecha_parts/mecha_equipment/MT in equipment) + output += "
                    [MT.get_equip_info()]
                    " + output += "
                    " + return output + +/obj/mecha/combat/honker/play_stepsound() + if(squeak) + playsound(src, "clownstep", 70, 1) + squeak = !squeak + +/obj/mecha/combat/honker/Topic(href, href_list) + ..() + if (href_list["play_sound"]) + switch(href_list["play_sound"]) + if("sadtrombone") + playsound(src, 'sound/misc/sadtrombone.ogg', 50) + if("bikehorn") + playsound(src, 'sound/items/bikehorn.ogg', 50) + if("airhorn2") + playsound(src, 'sound/items/airhorn2.ogg', 40) //soundfile has higher than average volume + if("carhorn") + playsound(src, 'sound/items/carhorn.ogg', 80) //soundfile has lower than average volume + if("party_horn") + playsound(src, 'sound/items/party_horn.ogg', 50) + if("reee") + playsound(src, 'sound/effects/reee.ogg', 50) + if("weeoo1") + playsound(src, 'sound/items/weeoo1.ogg', 50) + if("hiss1") + playsound(src, 'sound/voice/hiss1.ogg', 50) + if("armbomb") + playsound(src, 'sound/weapons/armbomb.ogg', 50) + if("saberon") + playsound(src, 'sound/weapons/saberon.ogg', 50) + if("airlock_alien_prying") + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50) + if("lightningbolt") + playsound(src, 'sound/magic/lightningbolt.ogg', 50) + if("explosionfar") + playsound(src, 'sound/effects/explosionfar.ogg', 50) + return diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm index 8c6e2b194ca..d1d4bfc1ea4 100644 --- a/code/game/mecha/combat/marauder.dm +++ b/code/game/mecha/combat/marauder.dm @@ -1,103 +1,103 @@ -/obj/mecha/combat/marauder - desc = "Heavy-duty, combat exosuit, developed after the Durand model. Rarely found among civilian populations." - name = "\improper Marauder" - icon_state = "marauder" - step_in = 5 - max_integrity = 500 - deflect_chance = 25 - armor = list("melee" = 50, "bullet" = 55, "laser" = 40, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 60, "fire" = 100, "acid" = 100) - max_temperature = 60000 - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - infra_luminosity = 3 - operation_req_access = list(ACCESS_CENT_SPECOPS) - internals_req_access = list(ACCESS_CENT_SPECOPS) - wreckage = /obj/structure/mecha_wreckage/marauder - add_req_access = 0 - internal_damage_threshold = 25 - force = 45 - max_equip = 5 - bumpsmash = 1 - -/obj/mecha/combat/marauder/GrantActions(mob/living/user, human_occupant = 0) - ..() - smoke_action.Grant(user, src) - zoom_action.Grant(user, src) - -/obj/mecha/combat/marauder/RemoveActions(mob/living/user, human_occupant = 0) - ..() - smoke_action.Remove(user) - zoom_action.Remove(user) - -/obj/mecha/combat/marauder/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - -/obj/mecha/combat/marauder/seraph - desc = "Heavy-duty, command-type exosuit. This is a custom model, utilized only by high-ranking military personnel." - name = "\improper Seraph" - icon_state = "seraph" - operation_req_access = list(ACCESS_CENT_SPECOPS) - internals_req_access = list(ACCESS_CENT_SPECOPS) - step_in = 3 - max_integrity = 550 - wreckage = /obj/structure/mecha_wreckage/seraph - internal_damage_threshold = 20 - force = 55 - max_equip = 6 - -/obj/mecha/combat/marauder/seraph/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/teleporter(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - -/obj/mecha/combat/marauder/mauler - desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model." - name = "\improper Mauler" - icon_state = "mauler" - operation_req_access = list(ACCESS_SYNDICATE) - internals_req_access = list(ACCESS_SYNDICATE) - wreckage = /obj/structure/mecha_wreckage/mauler - max_equip = 6 - destruction_sleep_duration = 20 - -/obj/mecha/combat/marauder/mauler/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - -/obj/mecha/combat/marauder/mauler/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - - +/obj/mecha/combat/marauder + desc = "Heavy-duty, combat exosuit, developed after the Durand model. Rarely found among civilian populations." + name = "\improper Marauder" + icon_state = "marauder" + step_in = 5 + max_integrity = 500 + deflect_chance = 25 + armor = list("melee" = 50, "bullet" = 55, "laser" = 40, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 60, "fire" = 100, "acid" = 100) + max_temperature = 60000 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + infra_luminosity = 3 + operation_req_access = list(ACCESS_CENT_SPECOPS) + internals_req_access = list(ACCESS_CENT_SPECOPS) + wreckage = /obj/structure/mecha_wreckage/marauder + add_req_access = 0 + internal_damage_threshold = 25 + force = 45 + max_equip = 5 + bumpsmash = 1 + +/obj/mecha/combat/marauder/GrantActions(mob/living/user, human_occupant = 0) + ..() + smoke_action.Grant(user, src) + zoom_action.Grant(user, src) + +/obj/mecha/combat/marauder/RemoveActions(mob/living/user, human_occupant = 0) + ..() + smoke_action.Remove(user) + zoom_action.Remove(user) + +/obj/mecha/combat/marauder/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + +/obj/mecha/combat/marauder/seraph + desc = "Heavy-duty, command-type exosuit. This is a custom model, utilized only by high-ranking military personnel." + name = "\improper Seraph" + icon_state = "seraph" + operation_req_access = list(ACCESS_CENT_SPECOPS) + internals_req_access = list(ACCESS_CENT_SPECOPS) + step_in = 3 + max_integrity = 550 + wreckage = /obj/structure/mecha_wreckage/seraph + internal_damage_threshold = 20 + force = 55 + max_equip = 6 + +/obj/mecha/combat/marauder/seraph/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/teleporter(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + +/obj/mecha/combat/marauder/mauler + desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model." + name = "\improper Mauler" + icon_state = "mauler" + operation_req_access = list(ACCESS_SYNDICATE) + internals_req_access = list(ACCESS_SYNDICATE) + wreckage = /obj/structure/mecha_wreckage/mauler + max_equip = 6 + destruction_sleep_duration = 20 + +/obj/mecha/combat/marauder/mauler/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + +/obj/mecha/combat/marauder/mauler/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + + diff --git a/code/game/mecha/combat/phazon.dm b/code/game/mecha/combat/phazon.dm index 4f37ac7f1b9..95b60eba68d 100644 --- a/code/game/mecha/combat/phazon.dm +++ b/code/game/mecha/combat/phazon.dm @@ -1,30 +1,30 @@ -/obj/mecha/combat/phazon - desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge bluespace technology and expensive materials." - name = "\improper Phazon" - icon_state = "phazon" - step_in = 2 - dir_in = 2 //Facing South. - step_energy_drain = 3 - max_integrity = 200 - deflect_chance = 30 - armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) - max_temperature = 25000 - infra_luminosity = 3 - wreckage = /obj/structure/mecha_wreckage/phazon - add_req_access = 1 - internal_damage_threshold = 25 - force = 15 - max_equip = 3 - phase_state = "phazon-phase" - -/obj/mecha/combat/phazon/GrantActions(mob/living/user, human_occupant = 0) - ..() - switch_damtype_action.Grant(user, src) - phasing_action.Grant(user, src) - - -/obj/mecha/combat/phazon/RemoveActions(mob/living/user, human_occupant = 0) - ..() - switch_damtype_action.Remove(user) - phasing_action.Remove(user) - +/obj/mecha/combat/phazon + desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge bluespace technology and expensive materials." + name = "\improper Phazon" + icon_state = "phazon" + step_in = 2 + dir_in = 2 //Facing South. + step_energy_drain = 3 + max_integrity = 200 + deflect_chance = 30 + armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) + max_temperature = 25000 + infra_luminosity = 3 + wreckage = /obj/structure/mecha_wreckage/phazon + add_req_access = 1 + internal_damage_threshold = 25 + force = 15 + max_equip = 3 + phase_state = "phazon-phase" + +/obj/mecha/combat/phazon/GrantActions(mob/living/user, human_occupant = 0) + ..() + switch_damtype_action.Grant(user, src) + phasing_action.Grant(user, src) + + +/obj/mecha/combat/phazon/RemoveActions(mob/living/user, human_occupant = 0) + ..() + switch_damtype_action.Remove(user) + phasing_action.Remove(user) + diff --git a/code/game/mecha/combat/reticence.dm b/code/game/mecha/combat/reticence.dm index ad628f2df95..d1beff7265b 100644 --- a/code/game/mecha/combat/reticence.dm +++ b/code/game/mecha/combat/reticence.dm @@ -1,28 +1,28 @@ -/obj/mecha/combat/reticence - desc = "A silent, fast, and nigh-invisible miming exosuit. Popular among mimes and mime assassins." - name = "\improper reticence" - icon_state = "reticence" - step_in = 2 - dir_in = 1 //Facing North. - max_integrity = 100 - deflect_chance = 3 - armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 15000 - wreckage = /obj/structure/mecha_wreckage/reticence - operation_req_access = list(ACCESS_THEATRE) - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) - add_req_access = 0 - internal_damage_threshold = 25 - max_equip = 2 - step_energy_drain = 3 - color = "#87878715" - stepsound = null - turnsound = null - opacity = 0 - -/obj/mecha/combat/reticence/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/rcd //HAHA IT MAKES WALLS GET IT - ME.attach(src) +/obj/mecha/combat/reticence + desc = "A silent, fast, and nigh-invisible miming exosuit. Popular among mimes and mime assassins." + name = "\improper reticence" + icon_state = "reticence" + step_in = 2 + dir_in = 1 //Facing North. + max_integrity = 100 + deflect_chance = 3 + armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 15000 + wreckage = /obj/structure/mecha_wreckage/reticence + operation_req_access = list(ACCESS_THEATRE) + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) + add_req_access = 0 + internal_damage_threshold = 25 + max_equip = 2 + step_energy_drain = 3 + color = "#87878715" + stepsound = null + turnsound = null + opacity = 0 + +/obj/mecha/combat/reticence/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/rcd //HAHA IT MAKES WALLS GET IT + ME.attach(src) diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm index 3a0e5bf5293..e9e3b335ffc 100644 --- a/code/game/mecha/equipment/mecha_equipment.dm +++ b/code/game/mecha/equipment/mecha_equipment.dm @@ -1,176 +1,176 @@ -//DO NOT ADD MECHA PARTS TO THE GAME WITH THE DEFAULT "SPRITE ME" SPRITE! -//I'm annoyed I even have to tell you this! SPRITE FIRST, then commit. - -/obj/item/mecha_parts/mecha_equipment - name = "mecha equipment" - icon = 'icons/mecha/mecha_equipment.dmi' - icon_state = "mecha_equip" - force = 5 - max_integrity = 300 - var/equip_cooldown = 0 // cooldown after use - var/equip_ready = 1 //whether the equipment is ready for use. (or deactivated/activated for static stuff) - var/energy_drain = 0 - var/obj/mecha/chassis = null - ///Bitflag. Determines the range of the equipment. - var/range = MECHA_MELEE - var/salvageable = 1 - var/detachable = TRUE // Set to FALSE for built-in equipment that cannot be removed - var/selectable = 1 // Set to 0 for passive equipment such as mining scanner or armor plates - var/harmful = FALSE //Controls if equipment can be used to attack by a pacifist. - var/destroy_sound = 'sound/mecha/critdestr.ogg' - -/obj/item/mecha_parts/mecha_equipment/proc/update_chassis_page() - if(chassis) - send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list()) - send_byjax(chassis.occupant,"exosuit.browser","equipment_menu",chassis.get_equipment_menu(),"dropdowns") - return 1 - return - -/obj/item/mecha_parts/mecha_equipment/proc/update_equip_info() - if(chassis) - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",get_equip_info()) - return 1 - return - -/obj/item/mecha_parts/mecha_equipment/Destroy() - if(chassis) - chassis.equipment -= src - if(chassis.selected == src) - chassis.selected = null - src.update_chassis_page() - log_message("[src] is destroyed.", LOG_MECHA) - if(chassis.occupant) - chassis.occupant_message("[src] is destroyed!") - chassis.occupant.playsound_local(chassis, destroy_sound, 50) - if(!detachable) //If we're a built-in nondetachable equipment, let's lock up the slot that we were in. - chassis.max_equip-- - chassis = null - return ..() - -/obj/item/mecha_parts/mecha_equipment/try_attach_part(mob/user, obj/mecha/M) - if(can_attach(M)) - if(!user.temporarilyRemoveItemFromInventory(src)) - return FALSE - attach(M) - user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") - return TRUE - to_chat(user, "You are unable to attach [src] to [M]!") - return FALSE - -/obj/item/mecha_parts/mecha_equipment/proc/get_equip_info() - if(!chassis) - return - var/txt = "* " - if(chassis.selected == src) - txt += "[src.name]" - else if(selectable) - txt += "[src.name]" - else - txt += "[src.name]" - - return txt - -/obj/item/mecha_parts/mecha_equipment/proc/is_ranged()//add a distance restricted equipment. Why not? - return range&MECHA_RANGED - -/obj/item/mecha_parts/mecha_equipment/proc/is_melee() - return range&MECHA_MELEE - - -/obj/item/mecha_parts/mecha_equipment/proc/action_checks(atom/target) - if(!target) - return 0 - if(!chassis) - return 0 - if(!equip_ready) - return 0 - if(energy_drain && !chassis.has_charge(energy_drain)) - return 0 - if(chassis.is_currently_ejecting) - return 0 - if(chassis.equipment_disabled) - to_chat(chassis.occupant, "Error -- Equipment control unit is unresponsive.") - return 0 - return 1 - -/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown() - set_ready_state(0) - chassis.use_power(energy_drain) - addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) - -/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target) - if(!chassis) - return - var/C = chassis.loc - set_ready_state(0) - chassis.use_power(energy_drain) - . = do_after(chassis.occupant, equip_cooldown, target=target) - set_ready_state(1) - if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/do_after_mecha(atom/target, delay) - if(!chassis) - return - var/C = chassis.loc - . = do_after(chassis.occupant, delay, target=target) - if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/mecha/M) - if(M.equipment.len[src] is destroyed!") + chassis.occupant.playsound_local(chassis, destroy_sound, 50) + if(!detachable) //If we're a built-in nondetachable equipment, let's lock up the slot that we were in. + chassis.max_equip-- + chassis = null + return ..() + +/obj/item/mecha_parts/mecha_equipment/try_attach_part(mob/user, obj/mecha/M) + if(can_attach(M)) + if(!user.temporarilyRemoveItemFromInventory(src)) + return FALSE + attach(M) + user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") + return TRUE + to_chat(user, "You are unable to attach [src] to [M]!") + return FALSE + +/obj/item/mecha_parts/mecha_equipment/proc/get_equip_info() + if(!chassis) + return + var/txt = "* " + if(chassis.selected == src) + txt += "[src.name]" + else if(selectable) + txt += "[src.name]" + else + txt += "[src.name]" + + return txt + +/obj/item/mecha_parts/mecha_equipment/proc/is_ranged()//add a distance restricted equipment. Why not? + return range&MECHA_RANGED + +/obj/item/mecha_parts/mecha_equipment/proc/is_melee() + return range&MECHA_MELEE + + +/obj/item/mecha_parts/mecha_equipment/proc/action_checks(atom/target) + if(!target) + return 0 + if(!chassis) + return 0 + if(!equip_ready) + return 0 + if(energy_drain && !chassis.has_charge(energy_drain)) + return 0 + if(chassis.is_currently_ejecting) + return 0 + if(chassis.equipment_disabled) + to_chat(chassis.occupant, "Error -- Equipment control unit is unresponsive.") + return 0 + return 1 + +/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown() + set_ready_state(0) + chassis.use_power(energy_drain) + addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) + +/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target) + if(!chassis) + return + var/C = chassis.loc + set_ready_state(0) + chassis.use_power(energy_drain) + . = do_after(chassis.occupant, equip_cooldown, target=target) + set_ready_state(1) + if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/do_after_mecha(atom/target, delay) + if(!chassis) + return + var/C = chassis.loc + . = do_after(chassis.occupant, delay, target=target) + if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/mecha/M) + if(M.equipment.lenYou start putting [target] into [src]...") - chassis.visible_message("[chassis] starts putting [target] into \the [src].") - if(do_after_cooldown(target)) - if(!patient_insertion_check(target)) - return - target.forceMove(src) - patient = target - START_PROCESSING(SSobj, src) - update_equip_info() - occupant_message("[target] successfully loaded into [src]. Life support functions engaged.") - chassis.visible_message("[chassis] loads [target] into [src].") - log_message("[target] loaded. Life support functions engaged.", LOG_MECHA) - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/patient_insertion_check(mob/living/carbon/target) - if(target.buckled) - occupant_message("[target] will not fit into the sleeper because [target.p_theyre()] buckled to [target.buckled]!") - return - if(target.has_buckled_mobs()) - occupant_message("[target] will not fit into the sleeper because of the creatures attached to it!") - return - if(patient) - occupant_message("The sleeper is already occupied!") - return - return 1 - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/go_out() - if(!patient) - return - patient.forceMove(get_turf(src)) - occupant_message("[patient] ejected. Life support functions disabled.") - log_message("[patient] ejected. Life support functions disabled.", LOG_MECHA) - STOP_PROCESSING(SSobj, src) - patient = null - update_equip_info() - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/detach() - if(patient) - occupant_message("Unable to detach [src] - equipment occupied!") - return - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/get_equip_info() - var/output = ..() - if(output) - var/temp = "" - if(patient) - temp = "
                    \[Occupant: [patient] ([patient.stat > 1 ? "*DECEASED*" : "Health: [patient.health]%"])\]
                    View stats|Eject" - return "[output] [temp]" - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Topic(href,href_list) - ..() - if(href_list["eject"]) - go_out() - if(href_list["view_stats"]) - chassis.occupant << browse(get_patient_stats(),"window=msleeper") - onclose(chassis.occupant, "msleeper") - return - if(href_list["inject"]) - var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate() in chassis - var/datum/reagent/R = locate(href_list["inject"]) in SG.reagents.reagent_list - if (istype(R)) - inject_reagent(R, SG) - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_stats() - if(!patient) - return - return {" - - - [patient] statistics - - - - -

                    Health statistics

                    -
                    - [get_patient_dam()] -
                    -

                    Reagents in bloodstream

                    -
                    - [get_patient_reagents()] -
                    -
                    - [get_available_reagents()] -
                    - - "} - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_dam() - var/t1 - switch(patient.stat) - if(0) - t1 = "Conscious" - if(1) - t1 = "Unconscious" - if(2) - t1 = "*dead*" - else - t1 = "Unknown" - return {"Health: [patient.stat > 1 ? "[t1]" : "[patient.health]% ([t1])"]
                    - Core Temperature: [patient.bodytemperature-T0C]°C ([patient.bodytemperature*1.8-459.67]°F)
                    - Brute Damage: [patient.getBruteLoss()]%
                    - Respiratory Damage: [patient.getOxyLoss()]%
                    - Toxin Content: [patient.getToxLoss()]%
                    - Burn Severity: [patient.getFireLoss()]%
                    - [patient.getCloneLoss() ? "Subject appears to have cellular damage." : ""]
                    - [patient.getOrganLoss(ORGAN_SLOT_BRAIN) ? "Significant brain damage detected." : ""]
                    - [length(patient.get_traumas()) ? "Brain Traumas detected." : ""]
                    - "} - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_reagents() - if(patient.reagents) - for(var/datum/reagent/R in patient.reagents.reagent_list) - if(R.volume > 0) - . += "[R]: [round(R.volume,0.01)]
                    " - return . || "None" - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_available_reagents() - var/output - var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate(/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun) in chassis - if(SG && SG.reagents && islist(SG.reagents.reagent_list)) - for(var/datum/reagent/R in SG.reagents.reagent_list) - if(R.volume > 0) - output += "Inject [R.name]
                    " - return output - - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/inject_reagent(datum/reagent/R,obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG) - if(!R || !patient || !SG || !(SG in chassis.equipment)) - return 0 - var/to_inject = min(R.volume, inject_amount) - if(to_inject && patient.reagents.get_reagent_amount(R.type) + to_inject <= inject_amount*2) - occupant_message("Injecting [patient] with [to_inject] units of [R.name].") - log_message("Injecting [patient] with [to_inject] units of [R.name].", LOG_MECHA) - log_combat(chassis.occupant, patient, "injected", "[name] ([R] - [to_inject] units)") - SG.reagents.trans_id_to(patient,R.type,to_inject) - update_equip_info() - return - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/update_equip_info() - if(..()) - if(patient) - send_byjax(chassis.occupant,"msleeper.browser","lossinfo",get_patient_dam()) - send_byjax(chassis.occupant,"msleeper.browser","reagents",get_patient_reagents()) - send_byjax(chassis.occupant,"msleeper.browser","injectwith",get_available_reagents()) - return 1 - return - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/container_resist(mob/living/user) - go_out() - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/process() - if(..()) - return - if(!chassis.has_charge(energy_drain)) - set_ready_state(1) - log_message("Deactivated.", LOG_MECHA) - occupant_message("[src] deactivated - no power.") - STOP_PROCESSING(SSobj, src) - return - var/mob/living/carbon/M = patient - if(!M) - return - if(M.health > 0) - M.adjustOxyLoss(-1) - M.AdjustStun(-80) - M.AdjustKnockdown(-80) - M.AdjustParalyzed(-80) - M.AdjustImmobilized(-80) - M.AdjustUnconscious(-80) - if(M.reagents.get_reagent_amount(/datum/reagent/medicine/epinephrine) < 5) - M.reagents.add_reagent(/datum/reagent/medicine/epinephrine, 5) - chassis.use_power(energy_drain) - update_equip_info() - - - - -///////////////////////////////// Syringe Gun /////////////////////////////////////////////////////////////// - - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun - name = "exosuit syringe gun" - desc = "Equipment for medical exosuits. A chem synthesizer with syringe gun. Reagents inside are held in stasis, so no reactions will occur." - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "syringegun" - var/list/syringes - var/list/known_reagents - var/list/processed_reagents - var/max_syringes = 10 - var/max_volume = 75 //max reagent volume - var/synth_speed = 5 //[num] reagent units per cycle - energy_drain = 10 - var/mode = 0 //0 - fire syringe, 1 - analyze reagents. - range = MECHA_MELEE|MECHA_RANGED - equip_cooldown = 10 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Initialize() - . = ..() - create_reagents(max_volume, NO_REACT) - syringes = new - known_reagents = list(/datum/reagent/medicine/epinephrine="Epinephrine",/datum/reagent/medicine/c2/multiver="Multiver") - processed_reagents = new - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/detach() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/can_attach(obj/mecha/medical/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/get_equip_info() - var/output = ..() - if(output) - return "[output] \[[mode? "Analyze" : "Launch"]\]
                    \[Syringes: [syringes.len]/[max_syringes] | Reagents: [reagents.total_volume]/[reagents.maximum_volume]\]
                    Reagents list" - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/action(atom/movable/target) - if(!action_checks(target)) - return - if(istype(target, /obj/item/reagent_containers/syringe)) - return load_syringe(target) - if(istype(target, /obj/item/storage))//Loads syringes from boxes - for(var/obj/item/reagent_containers/syringe/S in target.contents) - load_syringe(S) - return - if(mode) - return analyze_reagents(target) - if(!syringes.len) - occupant_message("No syringes loaded.") - return - if(reagents.total_volume<=0) - occupant_message("No available reagents to load syringe with.") - return - var/turf/trg = get_turf(target) - var/obj/item/reagent_containers/syringe/mechsyringe = syringes[1] - mechsyringe.forceMove(get_turf(chassis)) - reagents.trans_to(mechsyringe, min(mechsyringe.volume, reagents.total_volume), transfered_by = chassis.occupant) - syringes -= mechsyringe - mechsyringe.icon = 'icons/obj/chemical.dmi' - mechsyringe.icon_state = "syringeproj" - playsound(chassis, 'sound/items/syringeproj.ogg', 50, TRUE) - log_message("Launched [mechsyringe] from [src], targeting [target].", LOG_MECHA) - var/mob/originaloccupant = chassis.occupant - spawn(0) - src = null //if src is deleted, still process the syringe - for(var/i=0, i<6, i++) - if(!mechsyringe) - break - if(step_towards(mechsyringe,trg)) - var/list/mobs = new - for(var/mob/living/carbon/M in mechsyringe.loc) - mobs += M - if(length(mobs)) - var/mob/living/carbon/M = pick(mobs) - var/R - mechsyringe.visible_message(" [M] is hit by the syringe!") - if(M.can_inject(null, 1)) - if(mechsyringe.reagents) - for(var/datum/reagent/A in mechsyringe.reagents.reagent_list) - R += "[A.name] ([num2text(A.volume)]" - mechsyringe.icon_state = initial(mechsyringe.icon_state) - mechsyringe.icon = initial(mechsyringe.icon) - mechsyringe.reagents.expose(M, INJECT) - mechsyringe.reagents.trans_to(M, mechsyringe.reagents.total_volume, transfered_by = originaloccupant) - M.take_bodypart_damage(2) - log_combat(originaloccupant, M, "shot", "syringegun") - break - else if(mechsyringe.loc == trg) - mechsyringe.icon_state = initial(mechsyringe.icon_state) - mechsyringe.icon = initial(mechsyringe.icon) - mechsyringe.update_icon() - break - else - mechsyringe.icon_state = initial(mechsyringe.icon_state) - mechsyringe.icon = initial(mechsyringe.icon) - mechsyringe.update_icon() - break - sleep(1) - return 1 - - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Topic(href,href_list) - ..() - if (href_list["toggle_mode"]) - mode = !mode - update_equip_info() - return - if (href_list["select_reagents"]) - processed_reagents.len = 0 - var/m = 0 - var/message - for(var/i=1 to known_reagents.len) - if(m>=synth_speed) - break - var/reagent = text2path(href_list["reagent_[i]"]) - if(reagent && (reagent in known_reagents)) - message = "[m ? ", " : null][known_reagents[reagent]]" - processed_reagents += reagent - m++ - if(processed_reagents.len) - message += " added to production" - START_PROCESSING(SSobj, src) - occupant_message(message) - occupant_message("Reagent processing started.") - log_message("Reagent processing started.", LOG_MECHA) - return - if (href_list["show_reagents"]) - chassis.occupant << browse(get_reagents_page(),"window=msyringegun") - if (href_list["purge_reagent"]) - var/reagent = href_list["purge_reagent"] - if(reagent) - reagents.del_reagent(reagent) - return - if (href_list["purge_all"]) - reagents.clear_reagents() - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_page() - var/output = {" - - - Reagent Synthesizer - - - - -

                    Current reagents:

                    -
                    - [get_current_reagents()] -
                    -

                    Reagents production:

                    -
                    - [get_reagents_form()] -
                    - - - "} - return output - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_form() - var/r_list = get_reagents_list() - var/inputs - if(r_list) - inputs += "" - inputs += "" - inputs += "" - var/output = {" - [r_list || "No known reagents"] - [inputs] - - [r_list? "Only the first [synth_speed] selected reagent\s will be added to production" : null] - "} - return output - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_list() - var/output - for(var/i=1 to known_reagents.len) - var/reagent_id = known_reagents[i] - output += {" [known_reagents[reagent_id]]
                    "} - return output - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_current_reagents() - var/output - for(var/datum/reagent/R in reagents.reagent_list) - if(R.volume > 0) - output += "[R]: [round(R.volume,0.001)] - Purge Reagent
                    " - if(output) - output += "Total: [round(reagents.total_volume,0.001)]/[reagents.maximum_volume] - Purge All" - return output || "None" - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/load_syringe(obj/item/reagent_containers/syringe/S) - if(syringes.len= 2) - occupant_message("The syringe is too far away!") - return 0 - for(var/obj/structure/D in S.loc)//Basic level check for structures in the way (Like grilles and windows) - if(!(D.CanPass(S,src.loc))) - occupant_message("Unable to load syringe!") - return 0 - for(var/obj/machinery/door/D in S.loc)//Checks for doors - if(!(D.CanPass(S,src.loc))) - occupant_message("Unable to load syringe!") - return 0 - S.reagents.trans_to(src, S.reagents.total_volume, transfered_by = chassis.occupant) - S.forceMove(src) - syringes += S - occupant_message("Syringe loaded.") - update_equip_info() - return 1 - occupant_message("[src]'s syringe chamber is full!") - return 0 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/analyze_reagents(atom/A) - if(get_dist(src,A) >= 4) - occupant_message("The object is too far away!") - return 0 - if(!A.reagents || ismob(A)) - occupant_message("No reagent info gained from [A].") - return 0 - occupant_message("Analyzing reagents...") - for(var/datum/reagent/R in A.reagents.reagent_list) - if(R.can_synth && add_known_reagent(R.type,R.name)) - occupant_message("Reagent analyzed, identified as [R.name] and added to database.") - send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) - occupant_message("Analysis complete.") - return 1 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/add_known_reagent(r_id,r_name) - if(!(r_id in known_reagents)) - known_reagents += r_id - known_reagents[r_id] = r_name - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/update_equip_info() - if(..()) - send_byjax(chassis.occupant,"msyringegun.browser","reagents",get_current_reagents()) - send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) - return 1 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/on_reagent_change(changetype) - ..() - update_equip_info() - - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/process() - if(..()) - return - if(!processed_reagents.len || reagents.total_volume >= reagents.maximum_volume || !chassis.has_charge(energy_drain)) - occupant_message("Reagent processing stopped.") - log_message("Reagent processing stopped.", LOG_MECHA) - STOP_PROCESSING(SSobj, src) - return - var/amount = synth_speed / processed_reagents.len - for(var/reagent in processed_reagents) - reagents.add_reagent(reagent,amount) - chassis.use_power(energy_drain) - -///////////////////////////////// Medical Beam /////////////////////////////////////////////////////////////// - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam - name = "exosuit medical beamgun" - desc = "Equipment for medical exosuits. Generates a focused beam of medical nanites." - icon_state = "mecha_medigun" - energy_drain = 10 - range = MECHA_MELEE|MECHA_RANGED - equip_cooldown = 0 - var/obj/item/gun/medbeam/mech/medigun - custom_materials = list(/datum/material/iron = 15000, /datum/material/glass = 8000, /datum/material/plasma = 3000, /datum/material/gold = 8000, /datum/material/diamond = 2000) - material_flags = MATERIAL_NO_EFFECTS - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Initialize() - . = ..() - medigun = new(src) - - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Destroy() - qdel(medigun) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/process() - if(..()) - return - medigun.process() - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/action(atom/target) - medigun.process_fire(target, loc) - - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/detach() - STOP_PROCESSING(SSobj, src) - medigun.LoseTarget() - return ..() +// Sleeper, Medical Beam, and Syringe gun + +/obj/item/mecha_parts/mecha_equipment/medical + +/obj/item/mecha_parts/mecha_equipment/medical/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/mecha_parts/mecha_equipment/medical/can_attach(obj/mecha/medical/M) + if(..() && istype(M)) + return 1 + + +/obj/item/mecha_parts/mecha_equipment/medical/attach(obj/mecha/M) + ..() + START_PROCESSING(SSobj, src) + +/obj/item/mecha_parts/mecha_equipment/medical/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/process() + if(!chassis) + STOP_PROCESSING(SSobj, src) + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/detach() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper + name = "mounted sleeper" + desc = "Equipment for medical exosuits. A mounted sleeper that stabilizes patients and can inject reagents in the exosuit's reserves." + icon = 'icons/obj/machines/sleeper.dmi' + icon_state = "sleeper" + energy_drain = 20 + range = MECHA_MELEE + equip_cooldown = 20 + var/mob/living/carbon/patient = null + var/inject_amount = 10 + salvageable = 0 + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Destroy() + for(var/atom/movable/AM in src) + AM.forceMove(get_turf(src)) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Exit(atom/movable/O) + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/action(mob/living/carbon/target) + if(!action_checks(target)) + return + if(!istype(target)) + return + if(!patient_insertion_check(target)) + return + occupant_message("You start putting [target] into [src]...") + chassis.visible_message("[chassis] starts putting [target] into \the [src].") + if(do_after_cooldown(target)) + if(!patient_insertion_check(target)) + return + target.forceMove(src) + patient = target + START_PROCESSING(SSobj, src) + update_equip_info() + occupant_message("[target] successfully loaded into [src]. Life support functions engaged.") + chassis.visible_message("[chassis] loads [target] into [src].") + log_message("[target] loaded. Life support functions engaged.", LOG_MECHA) + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/patient_insertion_check(mob/living/carbon/target) + if(target.buckled) + occupant_message("[target] will not fit into the sleeper because [target.p_theyre()] buckled to [target.buckled]!") + return + if(target.has_buckled_mobs()) + occupant_message("[target] will not fit into the sleeper because of the creatures attached to it!") + return + if(patient) + occupant_message("The sleeper is already occupied!") + return + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/go_out() + if(!patient) + return + patient.forceMove(get_turf(src)) + occupant_message("[patient] ejected. Life support functions disabled.") + log_message("[patient] ejected. Life support functions disabled.", LOG_MECHA) + STOP_PROCESSING(SSobj, src) + patient = null + update_equip_info() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/detach() + if(patient) + occupant_message("Unable to detach [src] - equipment occupied!") + return + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/get_equip_info() + var/output = ..() + if(output) + var/temp = "" + if(patient) + temp = "
                    \[Occupant: [patient] ([patient.stat > 1 ? "*DECEASED*" : "Health: [patient.health]%"])\]
                    View stats|Eject" + return "[output] [temp]" + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Topic(href,href_list) + ..() + if(href_list["eject"]) + go_out() + if(href_list["view_stats"]) + chassis.occupant << browse(get_patient_stats(),"window=msleeper") + onclose(chassis.occupant, "msleeper") + return + if(href_list["inject"]) + var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate() in chassis + var/datum/reagent/R = locate(href_list["inject"]) in SG.reagents.reagent_list + if (istype(R)) + inject_reagent(R, SG) + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_stats() + if(!patient) + return + return {" + + + [patient] statistics + + + + +

                    Health statistics

                    +
                    + [get_patient_dam()] +
                    +

                    Reagents in bloodstream

                    +
                    + [get_patient_reagents()] +
                    +
                    + [get_available_reagents()] +
                    + + "} + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_dam() + var/t1 + switch(patient.stat) + if(0) + t1 = "Conscious" + if(1) + t1 = "Unconscious" + if(2) + t1 = "*dead*" + else + t1 = "Unknown" + return {"Health: [patient.stat > 1 ? "[t1]" : "[patient.health]% ([t1])"]
                    + Core Temperature: [patient.bodytemperature-T0C]°C ([patient.bodytemperature*1.8-459.67]°F)
                    + Brute Damage: [patient.getBruteLoss()]%
                    + Respiratory Damage: [patient.getOxyLoss()]%
                    + Toxin Content: [patient.getToxLoss()]%
                    + Burn Severity: [patient.getFireLoss()]%
                    + [patient.getCloneLoss() ? "Subject appears to have cellular damage." : ""]
                    + [patient.getOrganLoss(ORGAN_SLOT_BRAIN) ? "Significant brain damage detected." : ""]
                    + [length(patient.get_traumas()) ? "Brain Traumas detected." : ""]
                    + "} + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_reagents() + if(patient.reagents) + for(var/datum/reagent/R in patient.reagents.reagent_list) + if(R.volume > 0) + . += "[R]: [round(R.volume,0.01)]
                    " + return . || "None" + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_available_reagents() + var/output + var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate(/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun) in chassis + if(SG && SG.reagents && islist(SG.reagents.reagent_list)) + for(var/datum/reagent/R in SG.reagents.reagent_list) + if(R.volume > 0) + output += "Inject [R.name]
                    " + return output + + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/inject_reagent(datum/reagent/R,obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG) + if(!R || !patient || !SG || !(SG in chassis.equipment)) + return 0 + var/to_inject = min(R.volume, inject_amount) + if(to_inject && patient.reagents.get_reagent_amount(R.type) + to_inject <= inject_amount*2) + occupant_message("Injecting [patient] with [to_inject] units of [R.name].") + log_message("Injecting [patient] with [to_inject] units of [R.name].", LOG_MECHA) + log_combat(chassis.occupant, patient, "injected", "[name] ([R] - [to_inject] units)") + SG.reagents.trans_id_to(patient,R.type,to_inject) + update_equip_info() + return + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/update_equip_info() + if(..()) + if(patient) + send_byjax(chassis.occupant,"msleeper.browser","lossinfo",get_patient_dam()) + send_byjax(chassis.occupant,"msleeper.browser","reagents",get_patient_reagents()) + send_byjax(chassis.occupant,"msleeper.browser","injectwith",get_available_reagents()) + return 1 + return + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/container_resist(mob/living/user) + go_out() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/process() + if(..()) + return + if(!chassis.has_charge(energy_drain)) + set_ready_state(1) + log_message("Deactivated.", LOG_MECHA) + occupant_message("[src] deactivated - no power.") + STOP_PROCESSING(SSobj, src) + return + var/mob/living/carbon/M = patient + if(!M) + return + if(M.health > 0) + M.adjustOxyLoss(-1) + M.AdjustStun(-80) + M.AdjustKnockdown(-80) + M.AdjustParalyzed(-80) + M.AdjustImmobilized(-80) + M.AdjustUnconscious(-80) + if(M.reagents.get_reagent_amount(/datum/reagent/medicine/epinephrine) < 5) + M.reagents.add_reagent(/datum/reagent/medicine/epinephrine, 5) + chassis.use_power(energy_drain) + update_equip_info() + + + + +///////////////////////////////// Syringe Gun /////////////////////////////////////////////////////////////// + + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun + name = "exosuit syringe gun" + desc = "Equipment for medical exosuits. A chem synthesizer with syringe gun. Reagents inside are held in stasis, so no reactions will occur." + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "syringegun" + var/list/syringes + var/list/known_reagents + var/list/processed_reagents + var/max_syringes = 10 + var/max_volume = 75 //max reagent volume + var/synth_speed = 5 //[num] reagent units per cycle + energy_drain = 10 + var/mode = 0 //0 - fire syringe, 1 - analyze reagents. + range = MECHA_MELEE|MECHA_RANGED + equip_cooldown = 10 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Initialize() + . = ..() + create_reagents(max_volume, NO_REACT) + syringes = new + known_reagents = list(/datum/reagent/medicine/epinephrine="Epinephrine",/datum/reagent/medicine/c2/multiver="Multiver") + processed_reagents = new + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/detach() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/can_attach(obj/mecha/medical/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/get_equip_info() + var/output = ..() + if(output) + return "[output] \[[mode? "Analyze" : "Launch"]\]
                    \[Syringes: [syringes.len]/[max_syringes] | Reagents: [reagents.total_volume]/[reagents.maximum_volume]\]
                    Reagents list" + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/action(atom/movable/target) + if(!action_checks(target)) + return + if(istype(target, /obj/item/reagent_containers/syringe)) + return load_syringe(target) + if(istype(target, /obj/item/storage))//Loads syringes from boxes + for(var/obj/item/reagent_containers/syringe/S in target.contents) + load_syringe(S) + return + if(mode) + return analyze_reagents(target) + if(!syringes.len) + occupant_message("No syringes loaded.") + return + if(reagents.total_volume<=0) + occupant_message("No available reagents to load syringe with.") + return + var/turf/trg = get_turf(target) + var/obj/item/reagent_containers/syringe/mechsyringe = syringes[1] + mechsyringe.forceMove(get_turf(chassis)) + reagents.trans_to(mechsyringe, min(mechsyringe.volume, reagents.total_volume), transfered_by = chassis.occupant) + syringes -= mechsyringe + mechsyringe.icon = 'icons/obj/chemical.dmi' + mechsyringe.icon_state = "syringeproj" + playsound(chassis, 'sound/items/syringeproj.ogg', 50, TRUE) + log_message("Launched [mechsyringe] from [src], targeting [target].", LOG_MECHA) + var/mob/originaloccupant = chassis.occupant + spawn(0) + src = null //if src is deleted, still process the syringe + for(var/i=0, i<6, i++) + if(!mechsyringe) + break + if(step_towards(mechsyringe,trg)) + var/list/mobs = new + for(var/mob/living/carbon/M in mechsyringe.loc) + mobs += M + if(length(mobs)) + var/mob/living/carbon/M = pick(mobs) + var/R + mechsyringe.visible_message(" [M] is hit by the syringe!") + if(M.can_inject(null, 1)) + if(mechsyringe.reagents) + for(var/datum/reagent/A in mechsyringe.reagents.reagent_list) + R += "[A.name] ([num2text(A.volume)]" + mechsyringe.icon_state = initial(mechsyringe.icon_state) + mechsyringe.icon = initial(mechsyringe.icon) + mechsyringe.reagents.expose(M, INJECT) + mechsyringe.reagents.trans_to(M, mechsyringe.reagents.total_volume, transfered_by = originaloccupant) + M.take_bodypart_damage(2) + log_combat(originaloccupant, M, "shot", "syringegun") + break + else if(mechsyringe.loc == trg) + mechsyringe.icon_state = initial(mechsyringe.icon_state) + mechsyringe.icon = initial(mechsyringe.icon) + mechsyringe.update_icon() + break + else + mechsyringe.icon_state = initial(mechsyringe.icon_state) + mechsyringe.icon = initial(mechsyringe.icon) + mechsyringe.update_icon() + break + sleep(1) + return 1 + + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Topic(href,href_list) + ..() + if (href_list["toggle_mode"]) + mode = !mode + update_equip_info() + return + if (href_list["select_reagents"]) + processed_reagents.len = 0 + var/m = 0 + var/message + for(var/i=1 to known_reagents.len) + if(m>=synth_speed) + break + var/reagent = text2path(href_list["reagent_[i]"]) + if(reagent && (reagent in known_reagents)) + message = "[m ? ", " : null][known_reagents[reagent]]" + processed_reagents += reagent + m++ + if(processed_reagents.len) + message += " added to production" + START_PROCESSING(SSobj, src) + occupant_message(message) + occupant_message("Reagent processing started.") + log_message("Reagent processing started.", LOG_MECHA) + return + if (href_list["show_reagents"]) + chassis.occupant << browse(get_reagents_page(),"window=msyringegun") + if (href_list["purge_reagent"]) + var/reagent = href_list["purge_reagent"] + if(reagent) + reagents.del_reagent(reagent) + return + if (href_list["purge_all"]) + reagents.clear_reagents() + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_page() + var/output = {" + + + Reagent Synthesizer + + + + +

                    Current reagents:

                    +
                    + [get_current_reagents()] +
                    +

                    Reagents production:

                    +
                    + [get_reagents_form()] +
                    + + + "} + return output + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_form() + var/r_list = get_reagents_list() + var/inputs + if(r_list) + inputs += "" + inputs += "" + inputs += "" + var/output = {"
                    + [r_list || "No known reagents"] + [inputs] +
                    + [r_list? "Only the first [synth_speed] selected reagent\s will be added to production" : null] + "} + return output + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_list() + var/output + for(var/i=1 to known_reagents.len) + var/reagent_id = known_reagents[i] + output += {" [known_reagents[reagent_id]]
                    "} + return output + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_current_reagents() + var/output + for(var/datum/reagent/R in reagents.reagent_list) + if(R.volume > 0) + output += "[R]: [round(R.volume,0.001)] - Purge Reagent
                    " + if(output) + output += "Total: [round(reagents.total_volume,0.001)]/[reagents.maximum_volume] - Purge All" + return output || "None" + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/load_syringe(obj/item/reagent_containers/syringe/S) + if(syringes.len= 2) + occupant_message("The syringe is too far away!") + return 0 + for(var/obj/structure/D in S.loc)//Basic level check for structures in the way (Like grilles and windows) + if(!(D.CanPass(S,src.loc))) + occupant_message("Unable to load syringe!") + return 0 + for(var/obj/machinery/door/D in S.loc)//Checks for doors + if(!(D.CanPass(S,src.loc))) + occupant_message("Unable to load syringe!") + return 0 + S.reagents.trans_to(src, S.reagents.total_volume, transfered_by = chassis.occupant) + S.forceMove(src) + syringes += S + occupant_message("Syringe loaded.") + update_equip_info() + return 1 + occupant_message("[src]'s syringe chamber is full!") + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/analyze_reagents(atom/A) + if(get_dist(src,A) >= 4) + occupant_message("The object is too far away!") + return 0 + if(!A.reagents || ismob(A)) + occupant_message("No reagent info gained from [A].") + return 0 + occupant_message("Analyzing reagents...") + for(var/datum/reagent/R in A.reagents.reagent_list) + if(R.can_synth && add_known_reagent(R.type,R.name)) + occupant_message("Reagent analyzed, identified as [R.name] and added to database.") + send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) + occupant_message("Analysis complete.") + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/add_known_reagent(r_id,r_name) + if(!(r_id in known_reagents)) + known_reagents += r_id + known_reagents[r_id] = r_name + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/update_equip_info() + if(..()) + send_byjax(chassis.occupant,"msyringegun.browser","reagents",get_current_reagents()) + send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/on_reagent_change(changetype) + ..() + update_equip_info() + + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/process() + if(..()) + return + if(!processed_reagents.len || reagents.total_volume >= reagents.maximum_volume || !chassis.has_charge(energy_drain)) + occupant_message("Reagent processing stopped.") + log_message("Reagent processing stopped.", LOG_MECHA) + STOP_PROCESSING(SSobj, src) + return + var/amount = synth_speed / processed_reagents.len + for(var/reagent in processed_reagents) + reagents.add_reagent(reagent,amount) + chassis.use_power(energy_drain) + +///////////////////////////////// Medical Beam /////////////////////////////////////////////////////////////// + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam + name = "exosuit medical beamgun" + desc = "Equipment for medical exosuits. Generates a focused beam of medical nanites." + icon_state = "mecha_medigun" + energy_drain = 10 + range = MECHA_MELEE|MECHA_RANGED + equip_cooldown = 0 + var/obj/item/gun/medbeam/mech/medigun + custom_materials = list(/datum/material/iron = 15000, /datum/material/glass = 8000, /datum/material/plasma = 3000, /datum/material/gold = 8000, /datum/material/diamond = 2000) + material_flags = MATERIAL_NO_EFFECTS + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Initialize() + . = ..() + medigun = new(src) + + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Destroy() + qdel(medigun) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/process() + if(..()) + return + medigun.process() + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/action(atom/target) + medigun.process_fire(target, loc) + + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/detach() + STOP_PROCESSING(SSobj, src) + medigun.LoseTarget() + return ..() diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm index 336329209f1..af0bea78834 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -1,525 +1,525 @@ -/obj/item/mecha_parts/mecha_equipment/weapon - name = "mecha weapon" - range = MECHA_RANGED - destroy_sound = 'sound/mecha/weapdestr.ogg' - var/projectile - var/fire_sound - var/projectiles_per_shot = 1 - var/variance = 0 - var/randomspread = 0 //use random spread for machineguns, instead of shotgun scatter - var/projectile_delay = 0 - var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the weapon is fired. - var/kickback = TRUE //Will using this weapon in no grav push mecha back. - -/obj/item/mecha_parts/mecha_equipment/weapon/can_attach(obj/mecha/M) - if(!..()) - return FALSE - if(istype(M, /obj/mecha/combat)) - return TRUE - if((locate(/obj/item/mecha_parts/concealed_weapon_bay) in M.contents) && !(locate(/obj/item/mecha_parts/mecha_equipment/weapon) in M.equipment)) - return TRUE - return FALSE - -/obj/item/mecha_parts/mecha_equipment/weapon/proc/get_shot_amount() - return projectiles_per_shot - -/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, params) - if(!action_checks(target)) - return 0 - - var/turf/curloc = get_turf(chassis) - var/turf/targloc = get_turf(target) - if (!targloc || !istype(targloc) || !curloc) - return 0 - if (targloc == curloc) - return 0 - - set_ready_state(0) - for(var/i=1 to get_shot_amount()) - var/obj/projectile/A = new projectile(curloc) - A.firer = chassis.occupant - A.original = target - if(!A.suppressed && firing_effect_type) - new firing_effect_type(get_turf(src), chassis.dir) - - var/spread = 0 - if(variance) - if(randomspread) - spread = round((rand() - 0.5) * variance) - else - spread = round((i / projectiles_per_shot - 0.5) * variance) - A.preparePixelProjectile(target, chassis.occupant, params, spread) - - A.fire() - playsound(chassis, fire_sound, 50, TRUE) - - sleep(max(0, projectile_delay)) - - if(kickback) - chassis.newtonian_move(turn(chassis.dir,180)) - chassis.log_message("Fired from [src.name], targeting [target].", LOG_MECHA) - return 1 - - -//Base energy weapon type -/obj/item/mecha_parts/mecha_equipment/weapon/energy - name = "general energy weapon" - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/get_shot_amount() - return min(round(chassis.cell.charge / energy_drain), projectiles_per_shot) - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/start_cooldown() - set_ready_state(0) - chassis.use_power(energy_drain*get_shot_amount()) - addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser - equip_cooldown = 8 - name = "\improper CH-PS \"Immolator\" laser" - desc = "A weapon for combat exosuits. Shoots basic lasers." - icon_state = "mecha_laser" - energy_drain = 30 - projectile = /obj/projectile/beam/laser - fire_sound = 'sound/weapons/laser.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/disabler - equip_cooldown = 8 - name = "\improper CH-DS \"Peacemaker\" disabler" - desc = "A weapon for combat exosuits. Shoots basic disablers." - icon_state = "mecha_disabler" - energy_drain = 30 - projectile = /obj/projectile/beam/disabler - fire_sound = 'sound/weapons/taser2.ogg' - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy - equip_cooldown = 15 - name = "\improper CH-LC \"Solaris\" laser cannon" - desc = "A weapon for combat exosuits. Shoots heavy lasers." - icon_state = "mecha_laser" - energy_drain = 60 - projectile = /obj/projectile/beam/laser/heavylaser - fire_sound = 'sound/weapons/lasercannonfire.ogg' - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/ion - equip_cooldown = 20 - name = "\improper MKIV ion heavy cannon" - desc = "A weapon for combat exosuits. Shoots technology-disabling ion beams. Don't catch yourself in the blast!" - icon_state = "mecha_ion" - energy_drain = 120 - projectile = /obj/projectile/ion - fire_sound = 'sound/weapons/laser.ogg' - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/tesla - equip_cooldown = 35 - name = "\improper MKI Tesla Cannon" - desc = "A weapon for combat exosuits. Fires bolts of electricity similar to the experimental tesla engine." - icon_state = "mecha_ion" - energy_drain = 500 - projectile = /obj/projectile/energy/tesla/cannon - fire_sound = 'sound/magic/lightningbolt.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse - equip_cooldown = 30 - name = "eZ-13 MK2 heavy pulse rifle" - desc = "A weapon for combat exosuits. Shoots powerful destructive blasts capable of demolishing obstacles." - icon_state = "mecha_pulse" - energy_drain = 120 - projectile = /obj/projectile/beam/pulse/heavy - fire_sound = 'sound/weapons/marauder.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma - equip_cooldown = 10 - name = "217-D Heavy Plasma Cutter" - desc = "A device that shoots resonant plasma bursts at extreme velocity. The blasts are capable of crushing rock and demolishing solid obstacles." - icon_state = "mecha_plasmacutter" - inhand_icon_state = "plasmacutter" - lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - energy_drain = 30 - projectile = /obj/projectile/plasma/adv/mech - fire_sound = 'sound/weapons/plasma_cutter.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/can_attach(obj/mecha/working/M) - if(..()) //combat mech - return 1 - else if(M.equipment.len < M.max_equip && istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/taser - name = "\improper PBT \"Pacifier\" mounted taser" - desc = "A weapon for combat exosuits. Shoots non-lethal stunning electrodes." - icon_state = "mecha_taser" - energy_drain = 20 - equip_cooldown = 8 - projectile = /obj/projectile/energy/electrode - fire_sound = 'sound/weapons/taser.ogg' - - -/obj/item/mecha_parts/mecha_equipment/weapon/honker - name = "\improper HoNkER BlAsT 5000" - desc = "Equipment for clown exosuits. Spreads fun and joy to everyone around. Honk!" - icon_state = "mecha_honker" - energy_drain = 200 - equip_cooldown = 150 - range = MECHA_MELEE|MECHA_RANGED - kickback = FALSE - -/obj/item/mecha_parts/mecha_equipment/weapon/honker/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/honker/action(target, params) - if(!action_checks(target)) - return - playsound(chassis, 'sound/items/airhorn.ogg', 100, TRUE) - chassis.occupant_message("HONK") - for(var/mob/living/carbon/M in ohearers(6, chassis)) - if(!M.can_hear()) - continue - var/turf/turf_check = get_turf(M) - if(isspaceturf(turf_check) && !turf_check.Adjacent(src)) //in space nobody can hear you honk. - continue - to_chat(M, "HONK") - M.SetSleeping(0) - M.stuttering += 20 - var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) - if(ears) - ears.adjustEarDamage(0, 30) - M.Paralyze(60) - if(prob(30)) - M.Stun(200) - M.Unconscious(80) - else - M.Jitter(500) - - log_message("Honked from [src.name]. HONK!", LOG_MECHA) - var/turf/T = get_turf(src) - message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Mecha Honker in [ADMIN_VERBOSEJMP(T)]") - log_game("[key_name(chassis.occupant)] used a Mecha Honker in [AREACOORD(T)]") - return 1 - - -//Base ballistic weapon type -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic - name = "general ballistic weapon" - fire_sound = 'sound/weapons/gun/smg/shot.ogg' - var/projectiles - var/projectiles_cache //ammo to be loaded in, if possible. - var/projectiles_cache_max - var/projectile_energy_cost - var/disabledreload //For weapons with no cache (like the rockets) which are reloaded by hand - var/ammo_type - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_shot_amount() - return min(projectiles, projectiles_per_shot) - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action_checks(target) - if(!..()) - return 0 - if(projectiles <= 0) - return 0 - return 1 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_equip_info() - return "[..()] \[[src.projectiles][projectiles_cache_max &&!projectile_energy_cost?"/[projectiles_cache]":""]\][!disabledreload &&(src.projectiles < initial(src.projectiles))?" - Rearm":null]" - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/rearm() - if(projectiles < initial(projectiles)) - var/projectiles_to_add = initial(projectiles) - projectiles - - if(projectile_energy_cost) - while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) - projectiles++ - projectiles_to_add-- - chassis.use_power(projectile_energy_cost) - - else - if(!projectiles_cache) - return FALSE - if(projectiles_to_add <= projectiles_cache) - projectiles = projectiles + projectiles_to_add - projectiles_cache = projectiles_cache - projectiles_to_add - else - projectiles = projectiles + projectiles_cache - projectiles_cache = 0 - - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) - log_message("Rearmed [src.name].", LOG_MECHA) - return TRUE - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/needs_rearm() - . = !(projectiles > 0) - - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/Topic(href, href_list) - ..() - if (href_list["rearm"]) - src.rearm() - return - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target) - if(..()) - projectiles -= get_shot_amount() - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) - return 1 - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine - name = "\improper FNX-99 \"Hades\" Carbine" - desc = "A weapon for combat exosuits. Shoots incendiary bullets." - icon_state = "mecha_carbine" - equip_cooldown = 10 - projectile = /obj/projectile/bullet/incendiary/fnx99 - projectiles = 24 - projectiles_cache = 24 - projectiles_cache_max = 96 - harmful = TRUE - ammo_type = "incendiary" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced - name = "\improper S.H.H. \"Quietus\" Carbine" - desc = "A weapon for combat exosuits. A mime invention, field tests have shown that targets cannot even scream before going down." - fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' - icon_state = "mecha_mime" - equip_cooldown = 30 - projectile = /obj/projectile/bullet/mime - projectiles = 6 - projectile_energy_cost = 50 - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot - name = "\improper LBX AC 10 \"Scattershot\"" - desc = "A weapon for combat exosuits. Shoots a spread of pellets." - icon_state = "mecha_scatter" - equip_cooldown = 20 - projectile = /obj/projectile/bullet/scattershot - projectiles = 40 - projectiles_cache = 40 - projectiles_cache_max = 160 - projectiles_per_shot = 4 - variance = 25 - harmful = TRUE - ammo_type = "scattershot" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg - name = "\improper Ultra AC 2" - desc = "A weapon for combat exosuits. Shoots a rapid, three shot burst." - icon_state = "mecha_uac2" - equip_cooldown = 10 - projectile = /obj/projectile/bullet/lmg - projectiles = 300 - projectiles_cache = 300 - projectiles_cache_max = 1200 - projectiles_per_shot = 3 - variance = 6 - randomspread = 1 - projectile_delay = 2 - harmful = TRUE - ammo_type = "lmg" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack - name = "\improper SRM-8 missile rack" - desc = "A weapon for combat exosuits. Launches light explosive missiles." - icon_state = "mecha_missilerack" - projectile = /obj/projectile/bullet/a84mm_he - fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' - projectiles = 8 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - equip_cooldown = 60 - harmful = TRUE - ammo_type = "missiles_he" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/spacecops - projectiles = 420 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching - name = "\improper BRM-6 missile rack" - desc = "A weapon for combat exosuits. Launches low-explosive breaching missiles designed to explode only when striking a sturdy target." - icon_state = "mecha_missilerack_six" - projectile = /obj/projectile/bullet/a84mm_br - fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' - projectiles = 6 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - equip_cooldown = 60 - harmful = TRUE - ammo_type = "missiles_br" - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher - var/missile_speed = 2 - var/missile_range = 30 - var/diags_first = FALSE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/action(target) - if(!action_checks(target)) - return - var/obj/O = new projectile(chassis.loc) - playsound(chassis, fire_sound, 50, TRUE) - log_message("Launched a [O.name] from [name], targeting [target].", LOG_MECHA) - projectiles-- - proj_init(O) - O.throw_at(target, missile_range, missile_speed, chassis.occupant, FALSE, diagonals_first = diags_first) - return 1 - -//used for projectile initilisation (priming flashbang) and additional logging -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/proc/proj_init(var/obj/O) - return - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang - name = "\improper SGL-6 grenade launcher" - desc = "A weapon for combat exosuits. Launches primed flashbangs." - icon_state = "mecha_grenadelnchr" - projectile = /obj/item/grenade/flashbang - fire_sound = 'sound/weapons/gun/general/grenade_launch.ogg' - projectiles = 6 - projectiles_cache = 6 - projectiles_cache_max = 24 - missile_speed = 1.5 - equip_cooldown = 60 - var/det_time = 20 - ammo_type = "flashbang" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/proj_init(var/obj/item/grenade/flashbang/F) - var/turf/T = get_turf(src) - message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] fired a [src] in [ADMIN_VERBOSEJMP(T)]") - log_game("[key_name(chassis.occupant)] fired a [src] in [AREACOORD(T)]") - addtimer(CALLBACK(F, /obj/item/grenade/flashbang.proc/prime), det_time) - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/clusterbang //Because I am a heartless bastard -Sieve //Heartless? for making the poor man's honkblast? - Kaze - name = "\improper SOB-3 grenade launcher" - desc = "A weapon for combat exosuits. Launches primed clusterbangs. You monster." - projectiles = 3 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - projectile = /obj/item/grenade/clusterbuster - equip_cooldown = 90 - ammo_type = "clusterbang" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar - name = "banana mortar" - desc = "Equipment for clown exosuits. Launches banana peels." - icon_state = "mecha_bananamrtr" - projectile = /obj/item/grown/bananapeel - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 15 - missile_speed = 1.5 - projectile_energy_cost = 100 - equip_cooldown = 20 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar - name = "mousetrap mortar" - desc = "Equipment for clown exosuits. Launches armed mousetraps." - icon_state = "mecha_mousetrapmrtr" - projectile = /obj/item/assembly/mousetrap/armed - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 15 - missile_speed = 1.5 - projectile_energy_cost = 100 - equip_cooldown = 10 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/proj_init(var/obj/item/assembly/mousetrap/armed/M) - M.secured = 1 - - -//Classic extending punching glove, but weaponised! -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove - name = "\improper Oingo Boingo Punch-face" - desc = "Equipment for clown exosuits. Delivers fun right to your face!" - icon_state = "mecha_punching_glove" - energy_drain = 250 - equip_cooldown = 20 - range = MECHA_MELEE|MECHA_RANGED - missile_range = 5 - projectile = /obj/item/punching_glove - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 10 - projectile_energy_cost = 500 - harmful = TRUE - diags_first = TRUE - /// Damage done by the glove on contact. Also used to determine throw distance (damage / 5) - var/punch_damage = 35 - /// TRUE - Can toggle between lethal and non-lethal || FALSE - Cannot toggle - var/can_toggle_lethal = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/get_equip_info() - if(!chassis) - return - - if(can_toggle_lethal) - return "[..()]   [harmful?"Punch":"Pat"] mode" - else - return ..() - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/Topic(href, href_list) - ..() - if(href_list["lethalPunch"]) - harmful = !harmful - if(harmful) - chassis?.occupant_message("Lethal Fisting Enabled.") - else - chassis?.occupant_message("Lethal Fisting Disabled.") - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/action(target) - . = ..() - if(.) - chassis.occupant_message("HONK") - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/proj_init(obj/item/punching_glove/PG) - if(!istype(PG)) - return - - if(harmful) - PG.throwforce = punch_damage - else - PG.throwforce = 0 - - //has to be low sleep or it looks weird, the beam doesn't exist for very long so it's a non-issue - chassis.Beam(PG, icon_state = "chain", time = missile_range * 20, maxdistance = missile_range + 2, beam_sleep_time = 1) - -/obj/item/punching_glove - name = "punching glove" - desc = "INCOMING HONKS" - throwforce = 35 - icon_state = "punching_glove" - -/obj/item/punching_glove/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) - if(ismovable(hit_atom)) - var/atom/movable/AM = hit_atom - AM.safe_throw_at(get_edge_target_turf(AM,get_dir(src, AM)), clamp(round(throwforce/5), 2, 20), 2) //Throws them equal to damage/5, with a min range of 2 and max range of 20 - qdel(src) +/obj/item/mecha_parts/mecha_equipment/weapon + name = "mecha weapon" + range = MECHA_RANGED + destroy_sound = 'sound/mecha/weapdestr.ogg' + var/projectile + var/fire_sound + var/projectiles_per_shot = 1 + var/variance = 0 + var/randomspread = 0 //use random spread for machineguns, instead of shotgun scatter + var/projectile_delay = 0 + var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the weapon is fired. + var/kickback = TRUE //Will using this weapon in no grav push mecha back. + +/obj/item/mecha_parts/mecha_equipment/weapon/can_attach(obj/mecha/M) + if(!..()) + return FALSE + if(istype(M, /obj/mecha/combat)) + return TRUE + if((locate(/obj/item/mecha_parts/concealed_weapon_bay) in M.contents) && !(locate(/obj/item/mecha_parts/mecha_equipment/weapon) in M.equipment)) + return TRUE + return FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/proc/get_shot_amount() + return projectiles_per_shot + +/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, params) + if(!action_checks(target)) + return 0 + + var/turf/curloc = get_turf(chassis) + var/turf/targloc = get_turf(target) + if (!targloc || !istype(targloc) || !curloc) + return 0 + if (targloc == curloc) + return 0 + + set_ready_state(0) + for(var/i=1 to get_shot_amount()) + var/obj/projectile/A = new projectile(curloc) + A.firer = chassis.occupant + A.original = target + if(!A.suppressed && firing_effect_type) + new firing_effect_type(get_turf(src), chassis.dir) + + var/spread = 0 + if(variance) + if(randomspread) + spread = round((rand() - 0.5) * variance) + else + spread = round((i / projectiles_per_shot - 0.5) * variance) + A.preparePixelProjectile(target, chassis.occupant, params, spread) + + A.fire() + playsound(chassis, fire_sound, 50, TRUE) + + sleep(max(0, projectile_delay)) + + if(kickback) + chassis.newtonian_move(turn(chassis.dir,180)) + chassis.log_message("Fired from [src.name], targeting [target].", LOG_MECHA) + return 1 + + +//Base energy weapon type +/obj/item/mecha_parts/mecha_equipment/weapon/energy + name = "general energy weapon" + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/get_shot_amount() + return min(round(chassis.cell.charge / energy_drain), projectiles_per_shot) + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/start_cooldown() + set_ready_state(0) + chassis.use_power(energy_drain*get_shot_amount()) + addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser + equip_cooldown = 8 + name = "\improper CH-PS \"Immolator\" laser" + desc = "A weapon for combat exosuits. Shoots basic lasers." + icon_state = "mecha_laser" + energy_drain = 30 + projectile = /obj/projectile/beam/laser + fire_sound = 'sound/weapons/laser.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/disabler + equip_cooldown = 8 + name = "\improper CH-DS \"Peacemaker\" disabler" + desc = "A weapon for combat exosuits. Shoots basic disablers." + icon_state = "mecha_disabler" + energy_drain = 30 + projectile = /obj/projectile/beam/disabler + fire_sound = 'sound/weapons/taser2.ogg' + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy + equip_cooldown = 15 + name = "\improper CH-LC \"Solaris\" laser cannon" + desc = "A weapon for combat exosuits. Shoots heavy lasers." + icon_state = "mecha_laser" + energy_drain = 60 + projectile = /obj/projectile/beam/laser/heavylaser + fire_sound = 'sound/weapons/lasercannonfire.ogg' + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/ion + equip_cooldown = 20 + name = "\improper MKIV ion heavy cannon" + desc = "A weapon for combat exosuits. Shoots technology-disabling ion beams. Don't catch yourself in the blast!" + icon_state = "mecha_ion" + energy_drain = 120 + projectile = /obj/projectile/ion + fire_sound = 'sound/weapons/laser.ogg' + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/tesla + equip_cooldown = 35 + name = "\improper MKI Tesla Cannon" + desc = "A weapon for combat exosuits. Fires bolts of electricity similar to the experimental tesla engine." + icon_state = "mecha_ion" + energy_drain = 500 + projectile = /obj/projectile/energy/tesla/cannon + fire_sound = 'sound/magic/lightningbolt.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse + equip_cooldown = 30 + name = "eZ-13 MK2 heavy pulse rifle" + desc = "A weapon for combat exosuits. Shoots powerful destructive blasts capable of demolishing obstacles." + icon_state = "mecha_pulse" + energy_drain = 120 + projectile = /obj/projectile/beam/pulse/heavy + fire_sound = 'sound/weapons/marauder.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma + equip_cooldown = 10 + name = "217-D Heavy Plasma Cutter" + desc = "A device that shoots resonant plasma bursts at extreme velocity. The blasts are capable of crushing rock and demolishing solid obstacles." + icon_state = "mecha_plasmacutter" + inhand_icon_state = "plasmacutter" + lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + energy_drain = 30 + projectile = /obj/projectile/plasma/adv/mech + fire_sound = 'sound/weapons/plasma_cutter.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/can_attach(obj/mecha/working/M) + if(..()) //combat mech + return 1 + else if(M.equipment.len < M.max_equip && istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/taser + name = "\improper PBT \"Pacifier\" mounted taser" + desc = "A weapon for combat exosuits. Shoots non-lethal stunning electrodes." + icon_state = "mecha_taser" + energy_drain = 20 + equip_cooldown = 8 + projectile = /obj/projectile/energy/electrode + fire_sound = 'sound/weapons/taser.ogg' + + +/obj/item/mecha_parts/mecha_equipment/weapon/honker + name = "\improper HoNkER BlAsT 5000" + desc = "Equipment for clown exosuits. Spreads fun and joy to everyone around. Honk!" + icon_state = "mecha_honker" + energy_drain = 200 + equip_cooldown = 150 + range = MECHA_MELEE|MECHA_RANGED + kickback = FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/honker/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/honker/action(target, params) + if(!action_checks(target)) + return + playsound(chassis, 'sound/items/airhorn.ogg', 100, TRUE) + chassis.occupant_message("HONK") + for(var/mob/living/carbon/M in ohearers(6, chassis)) + if(!M.can_hear()) + continue + var/turf/turf_check = get_turf(M) + if(isspaceturf(turf_check) && !turf_check.Adjacent(src)) //in space nobody can hear you honk. + continue + to_chat(M, "HONK") + M.SetSleeping(0) + M.stuttering += 20 + var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) + if(ears) + ears.adjustEarDamage(0, 30) + M.Paralyze(60) + if(prob(30)) + M.Stun(200) + M.Unconscious(80) + else + M.Jitter(500) + + log_message("Honked from [src.name]. HONK!", LOG_MECHA) + var/turf/T = get_turf(src) + message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Mecha Honker in [ADMIN_VERBOSEJMP(T)]") + log_game("[key_name(chassis.occupant)] used a Mecha Honker in [AREACOORD(T)]") + return 1 + + +//Base ballistic weapon type +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic + name = "general ballistic weapon" + fire_sound = 'sound/weapons/gun/smg/shot.ogg' + var/projectiles + var/projectiles_cache //ammo to be loaded in, if possible. + var/projectiles_cache_max + var/projectile_energy_cost + var/disabledreload //For weapons with no cache (like the rockets) which are reloaded by hand + var/ammo_type + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_shot_amount() + return min(projectiles, projectiles_per_shot) + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action_checks(target) + if(!..()) + return 0 + if(projectiles <= 0) + return 0 + return 1 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_equip_info() + return "[..()] \[[src.projectiles][projectiles_cache_max &&!projectile_energy_cost?"/[projectiles_cache]":""]\][!disabledreload &&(src.projectiles < initial(src.projectiles))?" - Rearm":null]" + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/rearm() + if(projectiles < initial(projectiles)) + var/projectiles_to_add = initial(projectiles) - projectiles + + if(projectile_energy_cost) + while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) + projectiles++ + projectiles_to_add-- + chassis.use_power(projectile_energy_cost) + + else + if(!projectiles_cache) + return FALSE + if(projectiles_to_add <= projectiles_cache) + projectiles = projectiles + projectiles_to_add + projectiles_cache = projectiles_cache - projectiles_to_add + else + projectiles = projectiles + projectiles_cache + projectiles_cache = 0 + + send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) + log_message("Rearmed [src.name].", LOG_MECHA) + return TRUE + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/needs_rearm() + . = !(projectiles > 0) + + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/Topic(href, href_list) + ..() + if (href_list["rearm"]) + src.rearm() + return + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target) + if(..()) + projectiles -= get_shot_amount() + send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) + return 1 + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine + name = "\improper FNX-99 \"Hades\" Carbine" + desc = "A weapon for combat exosuits. Shoots incendiary bullets." + icon_state = "mecha_carbine" + equip_cooldown = 10 + projectile = /obj/projectile/bullet/incendiary/fnx99 + projectiles = 24 + projectiles_cache = 24 + projectiles_cache_max = 96 + harmful = TRUE + ammo_type = "incendiary" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced + name = "\improper S.H.H. \"Quietus\" Carbine" + desc = "A weapon for combat exosuits. A mime invention, field tests have shown that targets cannot even scream before going down." + fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' + icon_state = "mecha_mime" + equip_cooldown = 30 + projectile = /obj/projectile/bullet/mime + projectiles = 6 + projectile_energy_cost = 50 + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot + name = "\improper LBX AC 10 \"Scattershot\"" + desc = "A weapon for combat exosuits. Shoots a spread of pellets." + icon_state = "mecha_scatter" + equip_cooldown = 20 + projectile = /obj/projectile/bullet/scattershot + projectiles = 40 + projectiles_cache = 40 + projectiles_cache_max = 160 + projectiles_per_shot = 4 + variance = 25 + harmful = TRUE + ammo_type = "scattershot" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg + name = "\improper Ultra AC 2" + desc = "A weapon for combat exosuits. Shoots a rapid, three shot burst." + icon_state = "mecha_uac2" + equip_cooldown = 10 + projectile = /obj/projectile/bullet/lmg + projectiles = 300 + projectiles_cache = 300 + projectiles_cache_max = 1200 + projectiles_per_shot = 3 + variance = 6 + randomspread = 1 + projectile_delay = 2 + harmful = TRUE + ammo_type = "lmg" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack + name = "\improper SRM-8 missile rack" + desc = "A weapon for combat exosuits. Launches light explosive missiles." + icon_state = "mecha_missilerack" + projectile = /obj/projectile/bullet/a84mm_he + fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' + projectiles = 8 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + equip_cooldown = 60 + harmful = TRUE + ammo_type = "missiles_he" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/spacecops + projectiles = 420 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching + name = "\improper BRM-6 missile rack" + desc = "A weapon for combat exosuits. Launches low-explosive breaching missiles designed to explode only when striking a sturdy target." + icon_state = "mecha_missilerack_six" + projectile = /obj/projectile/bullet/a84mm_br + fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' + projectiles = 6 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + equip_cooldown = 60 + harmful = TRUE + ammo_type = "missiles_br" + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher + var/missile_speed = 2 + var/missile_range = 30 + var/diags_first = FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/action(target) + if(!action_checks(target)) + return + var/obj/O = new projectile(chassis.loc) + playsound(chassis, fire_sound, 50, TRUE) + log_message("Launched a [O.name] from [name], targeting [target].", LOG_MECHA) + projectiles-- + proj_init(O) + O.throw_at(target, missile_range, missile_speed, chassis.occupant, FALSE, diagonals_first = diags_first) + return 1 + +//used for projectile initilisation (priming flashbang) and additional logging +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/proc/proj_init(var/obj/O) + return + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang + name = "\improper SGL-6 grenade launcher" + desc = "A weapon for combat exosuits. Launches primed flashbangs." + icon_state = "mecha_grenadelnchr" + projectile = /obj/item/grenade/flashbang + fire_sound = 'sound/weapons/gun/general/grenade_launch.ogg' + projectiles = 6 + projectiles_cache = 6 + projectiles_cache_max = 24 + missile_speed = 1.5 + equip_cooldown = 60 + var/det_time = 20 + ammo_type = "flashbang" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/proj_init(var/obj/item/grenade/flashbang/F) + var/turf/T = get_turf(src) + message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] fired a [src] in [ADMIN_VERBOSEJMP(T)]") + log_game("[key_name(chassis.occupant)] fired a [src] in [AREACOORD(T)]") + addtimer(CALLBACK(F, /obj/item/grenade/flashbang.proc/prime), det_time) + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/clusterbang //Because I am a heartless bastard -Sieve //Heartless? for making the poor man's honkblast? - Kaze + name = "\improper SOB-3 grenade launcher" + desc = "A weapon for combat exosuits. Launches primed clusterbangs. You monster." + projectiles = 3 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + projectile = /obj/item/grenade/clusterbuster + equip_cooldown = 90 + ammo_type = "clusterbang" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar + name = "banana mortar" + desc = "Equipment for clown exosuits. Launches banana peels." + icon_state = "mecha_bananamrtr" + projectile = /obj/item/grown/bananapeel + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 15 + missile_speed = 1.5 + projectile_energy_cost = 100 + equip_cooldown = 20 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar + name = "mousetrap mortar" + desc = "Equipment for clown exosuits. Launches armed mousetraps." + icon_state = "mecha_mousetrapmrtr" + projectile = /obj/item/assembly/mousetrap/armed + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 15 + missile_speed = 1.5 + projectile_energy_cost = 100 + equip_cooldown = 10 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/proj_init(var/obj/item/assembly/mousetrap/armed/M) + M.secured = 1 + + +//Classic extending punching glove, but weaponised! +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove + name = "\improper Oingo Boingo Punch-face" + desc = "Equipment for clown exosuits. Delivers fun right to your face!" + icon_state = "mecha_punching_glove" + energy_drain = 250 + equip_cooldown = 20 + range = MECHA_MELEE|MECHA_RANGED + missile_range = 5 + projectile = /obj/item/punching_glove + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 10 + projectile_energy_cost = 500 + harmful = TRUE + diags_first = TRUE + /// Damage done by the glove on contact. Also used to determine throw distance (damage / 5) + var/punch_damage = 35 + /// TRUE - Can toggle between lethal and non-lethal || FALSE - Cannot toggle + var/can_toggle_lethal = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/get_equip_info() + if(!chassis) + return + + if(can_toggle_lethal) + return "[..()]   [harmful?"Punch":"Pat"] mode" + else + return ..() + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/Topic(href, href_list) + ..() + if(href_list["lethalPunch"]) + harmful = !harmful + if(harmful) + chassis?.occupant_message("Lethal Fisting Enabled.") + else + chassis?.occupant_message("Lethal Fisting Disabled.") + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/action(target) + . = ..() + if(.) + chassis.occupant_message("HONK") + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/proj_init(obj/item/punching_glove/PG) + if(!istype(PG)) + return + + if(harmful) + PG.throwforce = punch_damage + else + PG.throwforce = 0 + + //has to be low sleep or it looks weird, the beam doesn't exist for very long so it's a non-issue + chassis.Beam(PG, icon_state = "chain", time = missile_range * 20, maxdistance = missile_range + 2, beam_sleep_time = 1) + +/obj/item/punching_glove + name = "punching glove" + desc = "INCOMING HONKS" + throwforce = 35 + icon_state = "punching_glove" + +/obj/item/punching_glove/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) + if(ismovable(hit_atom)) + var/atom/movable/AM = hit_atom + AM.safe_throw_at(get_edge_target_turf(AM,get_dir(src, AM)), clamp(round(throwforce/5), 2, 20), 2) //Throws them equal to damage/5, with a min range of 2 and max range of 20 + qdel(src) diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm index 9feb2e8a3e6..231b649be2e 100644 --- a/code/game/mecha/mech_fabricator.dm +++ b/code/game/mecha/mech_fabricator.dm @@ -1,443 +1,443 @@ -/obj/machinery/mecha_part_fabricator - icon = 'icons/obj/robotics.dmi' - icon_state = "fab-idle" - name = "exosuit fabricator" - desc = "Nothing is being built." - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 20 - active_power_usage = 5000 - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/machine/mechfab - var/time_coeff = 1 - var/component_coeff = 1 - var/datum/techweb/specialized/autounlocking/exofab/stored_research - var/sync = 0 - var/part_set - var/datum/design/being_built - var/list/queue = list() - var/processing_queue = 0 - var/screen = "main" - var/link_on_init = TRUE - var/temp - var/datum/component/remote_materials/rmat - var/list/part_sets = list( - "Cyborg", - "Ripley", - "Odysseus", - "Clarke", - "Gygax", - "Durand", - "H.O.N.K", - "Phazon", - "Exosuit Equipment", - "Exosuit Ammunition", - "Cyborg Upgrade Modules", - "Misc" - ) - -/obj/machinery/mecha_part_fabricator/Initialize(mapload) - stored_research = new - rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init) - RefreshParts() //Recalculating local material sizes if the fab isn't linked - return ..() - -/obj/machinery/mecha_part_fabricator/RefreshParts() - var/T = 0 - - //maximum stocking amount (default 300000, 600000 at T4) - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - T += M.rating - rmat.set_local_size((200000 + (T*50000))) - - //resources adjustment coefficient (1 -> 0.85 -> 0.7 -> 0.55) - T = 1.15 - for(var/obj/item/stock_parts/micro_laser/Ma in component_parts) - T -= Ma.rating*0.15 - component_coeff = T - - //building time adjustment coefficient (1 -> 0.8 -> 0.6) - T = -1 - for(var/obj/item/stock_parts/manipulator/Ml in component_parts) - T += Ml.rating - time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01) - -/obj/machinery/mecha_part_fabricator/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [rmat.local_size] material units.
                    Material consumption at [component_coeff*100]%.
                    Build time reduced by [100-time_coeff*100]%.
                    " - -/obj/machinery/mecha_part_fabricator/emag_act() - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - req_access = list() - say("DB error \[Code 0x00F1\]") - sleep(10) - say("Attempting auto-repair...") - sleep(15) - say("User DB corrupted \[Code 0x00FA\]. Truncating data structure...") - sleep(30) - say("User DB truncated. Please contact your Nanotrasen system operator for future assistance.") - -/obj/machinery/mecha_part_fabricator/proc/output_parts_list(set_name) - var/output = "" - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(!(set_name in D.category)) - continue - output += "
                    [output_part_info(D)]
                    \[" - if(check_resources(D)) - output += "Build | " - output += "Add to queue\]\[?\]
                    " - return output - -/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D) - var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [get_construction_time_w_coeff(D)/10]sec" - return output - -/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D) - var/i = 0 - var/output - for(var/c in D.materials) - var/datum/material/M = c - output += "[i?" | ":null][get_resource_cost_w_coeff(D, M)] [M.name]" - i++ - return output - -/obj/machinery/mecha_part_fabricator/proc/output_available_resources() - var/output - var/datum/component/material_container/materials = rmat.mat_container - - if(materials) - for(var/mat_id in materials.materials) - var/datum/material/M = mat_id - var/amount = materials.materials[mat_id] - var/ref = REF(M) - output += "[M.name]: [amount] cm³" - if(amount >= MINERAL_MATERIAL_AMOUNT) - output += "- Remove \[1\]" - if(amount >= (MINERAL_MATERIAL_AMOUNT * 10)) - output += " | \[10\]" - output += " | \[50\]" - output += "
                    " - else - output += "No material storage connected, please contact the quartermaster.
                    " - return output - -/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D) - var/list/resources = list() - for(var/R in D.materials) - var/datum/material/M = R - resources[M] = get_resource_cost_w_coeff(D, M) - return resources - -/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D) - if(D.reagents_list.len) // No reagents storage - no reagent designs. - return FALSE - var/datum/component/material_container/materials = rmat.mat_container - if(materials.has_materials(get_resources_w_coeff(D))) - return TRUE - return FALSE - -/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D) - var/list/res_coef = get_resources_w_coeff(D) - - var/datum/component/material_container/materials = rmat.mat_container - if (!materials) - say("No access to material storage, please contact the quartermaster.") - return FALSE - if (rmat.on_hold()) - say("Mineral access is on hold, please contact the quartermaster.") - return FALSE - if(!check_resources(D)) - say("Not enough resources. Queue processing stopped.") - return FALSE - being_built = D - desc = "It's building \a [initial(D.name)]." - materials.use_materials(res_coef) - rmat.silo_log(src, "built", -1, "[D.name]", res_coef) - - add_overlay("fab-active") - use_power = ACTIVE_POWER_USE - updateUsrDialog() - sleep(get_construction_time_w_coeff(D)) - use_power = IDLE_POWER_USE - cut_overlay("fab-active") - desc = initial(desc) - - var/location = get_step(src,(dir)) - var/obj/item/I = new D.build_path(location) - I.material_flags |= MATERIAL_NO_EFFECTS //Find a better way to do this. - I.set_custom_materials(res_coef) - say("\The [I] is complete.") - being_built = null - - updateUsrDialog() - return TRUE - -/obj/machinery/mecha_part_fabricator/proc/update_queue_on_page() - send_byjax(usr,"mecha_fabricator.browser","queue",list_queue()) - return - -/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(set_name) - if(set_name in part_sets) - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(set_name in D.category) - add_to_queue(D) - -/obj/machinery/mecha_part_fabricator/proc/add_to_queue(D) - if(!istype(queue)) - queue = list() - if(D) - queue[++queue.len] = D - return queue.len - -/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index) - if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>queue.len)) - return FALSE - queue.Cut(index,++index) - return TRUE - -/obj/machinery/mecha_part_fabricator/proc/process_queue() - var/datum/design/D = queue[1] - if(!D) - remove_from_queue(1) - if(queue.len) - return process_queue() - else - return - temp = null - while(D) - if(machine_stat&(NOPOWER|BROKEN)) - return FALSE - if(build_part(D)) - remove_from_queue(1) - else - return FALSE - D = LAZYACCESS(queue, 1) - say("Queue processing finished successfully.") - -/obj/machinery/mecha_part_fabricator/proc/list_queue() - var/output = "Queue contains:" - if(!istype(queue) || !queue.len) - output += "
                    Nothing" - else - output += "
                      " - var/i = 0 - for(var/datum/design/D in queue) - i++ - var/obj/part = D.build_path - output += "" - output += initial(part.name) + " - " - output += "[i>1?"":null] " - output += "[i↓":null] " - output += "Remove" - - output += "
                    " - output += "\[Process queue | Clear queue\]" - return output - -/obj/machinery/mecha_part_fabricator/proc/sync() - for(var/obj/machinery/computer/rdconsole/RDC in oview(7,src)) - RDC.stored_research.copy_research_to(stored_research) - updateUsrDialog() - say("Successfully synchronized with R&D server.") - return - - temp = "Unable to connect to local R&D Database.
                    Please check your connections and try again.
                    Return" - updateUsrDialog() - return - -/obj/machinery/mecha_part_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, var/datum/material/resource, roundto = 1) - return round(D.materials[resource]*component_coeff, roundto) - -/obj/machinery/mecha_part_fabricator/proc/get_construction_time_w_coeff(datum/design/D, roundto = 1) //aran - return round(initial(D.construction_time)*time_coeff, roundto) - -/obj/machinery/mecha_part_fabricator/ui_interact(mob/user as mob) - . = ..() - var/dat, left_part - user.set_machine(src) - var/turf/exit = get_step(src,(dir)) - if(exit.density) - say("Error! Part outlet is obstructed.") - return - if(temp) - left_part = temp - else if(being_built) - var/obj/I = being_built.build_path - left_part = {"Building [initial(I.name)].
                    - Please wait until completion...
                    "} - else - switch(screen) - if("main") - left_part = output_available_resources()+"
                    " - left_part += "Sync with R&D servers
                    " - for(var/part_set in part_sets) - left_part += "[part_set] - \[Add all parts to queue\]
                    " - if("parts") - left_part += output_parts_list(part_set) - left_part += "
                    Return" - dat = {" - - - [name] - - - - - - - - - -
                    - [left_part] - - [list_queue()] -
                    - - "} - user << browse(dat, "window=mecha_fabricator;size=1000x430") - onclose(user, "mecha_fabricator") - return - -/obj/machinery/mecha_part_fabricator/Topic(href, href_list) - if(..()) - return - if(href_list["part_set"]) - var/tpart_set = href_list["part_set"] - if(tpart_set) - if(tpart_set=="clear") - part_set = null - else - part_set = tpart_set - screen = "parts" - if(href_list["part"]) - var/T = href_list["part"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - if(!processing_queue) - build_part(D) - else - add_to_queue(D) - break - if(href_list["add_to_queue"]) - var/T = href_list["add_to_queue"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - add_to_queue(D) - break - return update_queue_on_page() - if(href_list["remove_from_queue"]) - remove_from_queue(text2num(href_list["remove_from_queue"])) - return update_queue_on_page() - if(href_list["partset_to_queue"]) - add_part_set_to_queue(href_list["partset_to_queue"]) - return update_queue_on_page() - if(href_list["process_queue"]) - INVOKE_ASYNC(src, .proc/do_process_queue) - if(href_list["clear_temp"]) - temp = null - if(href_list["screen"]) - screen = href_list["screen"] - if(href_list["queue_move"] && href_list["index"]) - var/index = text2num(href_list["index"]) - var/new_index = index + text2num(href_list["queue_move"]) - if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index)) - if(ISINRANGE(new_index,1,queue.len)) - queue.Swap(index,new_index) - return update_queue_on_page() - if(href_list["clear_queue"]) - queue = list() - return update_queue_on_page() - if(href_list["sync"]) - sync() - if(href_list["part_desc"]) - var/T = href_list["part_desc"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - var/obj/part = D.build_path - temp = {"

                    [initial(part.name)] description:

                    - [initial(part.desc)]
                    - Return - "} - break - - if(href_list["remove_mat"] && href_list["material"]) - var/datum/material/Mat = locate(href_list["material"]) - eject_sheets(Mat, text2num(href_list["remove_mat"])) - - updateUsrDialog() - return - -/obj/machinery/mecha_part_fabricator/proc/do_process_queue() - if(processing_queue || being_built) - return FALSE - processing_queue = 1 - process_queue() - processing_queue = 0 - -/obj/machinery/mecha_part_fabricator/proc/eject_sheets(eject_sheet, eject_amt) - var/datum/component/material_container/mat_container = rmat.mat_container - if (!mat_container) - say("No access to material storage, please contact the quartermaster.") - return 0 - if (rmat.on_hold()) - say("Mineral access is on hold, please contact the quartermaster.") - return 0 - var/count = mat_container.retrieve_sheets(text2num(eject_amt), eject_sheet, drop_location()) - var/list/matlist = list() - matlist[eject_sheet] = text2num(eject_amt) - rmat.silo_log(src, "ejected", -count, "sheets", matlist) - return count - -/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) - var/datum/material/M = id_inserted - add_overlay("fab-load-[M.name]") - addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[M.name]"), 10) - updateUsrDialog() - -/obj/machinery/mecha_part_fabricator/attackby(obj/item/W, mob/user, params) - if(default_deconstruction_screwdriver(user, "fab-o", "fab-idle", W)) - return TRUE - - if(default_deconstruction_crowbar(W)) - return TRUE - - return ..() - - -/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) - if(panel_open) - to_chat(user, "You can't load [src] while it's opened!") - return FALSE - if(being_built) - to_chat(user, "\The [src] is currently processing! Please wait until completion.") - return FALSE - - return TRUE - -/obj/machinery/mecha_part_fabricator/maint - link_on_init = FALSE +/obj/machinery/mecha_part_fabricator + icon = 'icons/obj/robotics.dmi' + icon_state = "fab-idle" + name = "exosuit fabricator" + desc = "Nothing is being built." + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 20 + active_power_usage = 5000 + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/machine/mechfab + var/time_coeff = 1 + var/component_coeff = 1 + var/datum/techweb/specialized/autounlocking/exofab/stored_research + var/sync = 0 + var/part_set + var/datum/design/being_built + var/list/queue = list() + var/processing_queue = 0 + var/screen = "main" + var/link_on_init = TRUE + var/temp + var/datum/component/remote_materials/rmat + var/list/part_sets = list( + "Cyborg", + "Ripley", + "Odysseus", + "Clarke", + "Gygax", + "Durand", + "H.O.N.K", + "Phazon", + "Exosuit Equipment", + "Exosuit Ammunition", + "Cyborg Upgrade Modules", + "Misc" + ) + +/obj/machinery/mecha_part_fabricator/Initialize(mapload) + stored_research = new + rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init) + RefreshParts() //Recalculating local material sizes if the fab isn't linked + return ..() + +/obj/machinery/mecha_part_fabricator/RefreshParts() + var/T = 0 + + //maximum stocking amount (default 300000, 600000 at T4) + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + T += M.rating + rmat.set_local_size((200000 + (T*50000))) + + //resources adjustment coefficient (1 -> 0.85 -> 0.7 -> 0.55) + T = 1.15 + for(var/obj/item/stock_parts/micro_laser/Ma in component_parts) + T -= Ma.rating*0.15 + component_coeff = T + + //building time adjustment coefficient (1 -> 0.8 -> 0.6) + T = -1 + for(var/obj/item/stock_parts/manipulator/Ml in component_parts) + T += Ml.rating + time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01) + +/obj/machinery/mecha_part_fabricator/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Storing up to [rmat.local_size] material units.
                    Material consumption at [component_coeff*100]%.
                    Build time reduced by [100-time_coeff*100]%.
                    " + +/obj/machinery/mecha_part_fabricator/emag_act() + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + req_access = list() + say("DB error \[Code 0x00F1\]") + sleep(10) + say("Attempting auto-repair...") + sleep(15) + say("User DB corrupted \[Code 0x00FA\]. Truncating data structure...") + sleep(30) + say("User DB truncated. Please contact your Nanotrasen system operator for future assistance.") + +/obj/machinery/mecha_part_fabricator/proc/output_parts_list(set_name) + var/output = "" + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(!(set_name in D.category)) + continue + output += "
                    [output_part_info(D)]
                    \[" + if(check_resources(D)) + output += "Build | " + output += "Add to queue\]\[?\]
                    " + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D) + var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [get_construction_time_w_coeff(D)/10]sec" + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D) + var/i = 0 + var/output + for(var/c in D.materials) + var/datum/material/M = c + output += "[i?" | ":null][get_resource_cost_w_coeff(D, M)] [M.name]" + i++ + return output + +/obj/machinery/mecha_part_fabricator/proc/output_available_resources() + var/output + var/datum/component/material_container/materials = rmat.mat_container + + if(materials) + for(var/mat_id in materials.materials) + var/datum/material/M = mat_id + var/amount = materials.materials[mat_id] + var/ref = REF(M) + output += "[M.name]: [amount] cm³" + if(amount >= MINERAL_MATERIAL_AMOUNT) + output += "- Remove \[1\]" + if(amount >= (MINERAL_MATERIAL_AMOUNT * 10)) + output += " | \[10\]" + output += " | \[50\]" + output += "
                    " + else + output += "No material storage connected, please contact the quartermaster.
                    " + return output + +/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D) + var/list/resources = list() + for(var/R in D.materials) + var/datum/material/M = R + resources[M] = get_resource_cost_w_coeff(D, M) + return resources + +/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D) + if(D.reagents_list.len) // No reagents storage - no reagent designs. + return FALSE + var/datum/component/material_container/materials = rmat.mat_container + if(materials.has_materials(get_resources_w_coeff(D))) + return TRUE + return FALSE + +/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D) + var/list/res_coef = get_resources_w_coeff(D) + + var/datum/component/material_container/materials = rmat.mat_container + if (!materials) + say("No access to material storage, please contact the quartermaster.") + return FALSE + if (rmat.on_hold()) + say("Mineral access is on hold, please contact the quartermaster.") + return FALSE + if(!check_resources(D)) + say("Not enough resources. Queue processing stopped.") + return FALSE + being_built = D + desc = "It's building \a [initial(D.name)]." + materials.use_materials(res_coef) + rmat.silo_log(src, "built", -1, "[D.name]", res_coef) + + add_overlay("fab-active") + use_power = ACTIVE_POWER_USE + updateUsrDialog() + sleep(get_construction_time_w_coeff(D)) + use_power = IDLE_POWER_USE + cut_overlay("fab-active") + desc = initial(desc) + + var/location = get_step(src,(dir)) + var/obj/item/I = new D.build_path(location) + I.material_flags |= MATERIAL_NO_EFFECTS //Find a better way to do this. + I.set_custom_materials(res_coef) + say("\The [I] is complete.") + being_built = null + + updateUsrDialog() + return TRUE + +/obj/machinery/mecha_part_fabricator/proc/update_queue_on_page() + send_byjax(usr,"mecha_fabricator.browser","queue",list_queue()) + return + +/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(set_name) + if(set_name in part_sets) + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(set_name in D.category) + add_to_queue(D) + +/obj/machinery/mecha_part_fabricator/proc/add_to_queue(D) + if(!istype(queue)) + queue = list() + if(D) + queue[++queue.len] = D + return queue.len + +/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index) + if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>queue.len)) + return FALSE + queue.Cut(index,++index) + return TRUE + +/obj/machinery/mecha_part_fabricator/proc/process_queue() + var/datum/design/D = queue[1] + if(!D) + remove_from_queue(1) + if(queue.len) + return process_queue() + else + return + temp = null + while(D) + if(machine_stat&(NOPOWER|BROKEN)) + return FALSE + if(build_part(D)) + remove_from_queue(1) + else + return FALSE + D = LAZYACCESS(queue, 1) + say("Queue processing finished successfully.") + +/obj/machinery/mecha_part_fabricator/proc/list_queue() + var/output = "Queue contains:" + if(!istype(queue) || !queue.len) + output += "
                    Nothing" + else + output += "
                      " + var/i = 0 + for(var/datum/design/D in queue) + i++ + var/obj/part = D.build_path + output += "" + output += initial(part.name) + " - " + output += "[i>1?"":null] " + output += "[i↓":null] " + output += "Remove" + + output += "
                    " + output += "\[Process queue | Clear queue\]" + return output + +/obj/machinery/mecha_part_fabricator/proc/sync() + for(var/obj/machinery/computer/rdconsole/RDC in oview(7,src)) + RDC.stored_research.copy_research_to(stored_research) + updateUsrDialog() + say("Successfully synchronized with R&D server.") + return + + temp = "Unable to connect to local R&D Database.
                    Please check your connections and try again.
                    Return" + updateUsrDialog() + return + +/obj/machinery/mecha_part_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, var/datum/material/resource, roundto = 1) + return round(D.materials[resource]*component_coeff, roundto) + +/obj/machinery/mecha_part_fabricator/proc/get_construction_time_w_coeff(datum/design/D, roundto = 1) //aran + return round(initial(D.construction_time)*time_coeff, roundto) + +/obj/machinery/mecha_part_fabricator/ui_interact(mob/user as mob) + . = ..() + var/dat, left_part + user.set_machine(src) + var/turf/exit = get_step(src,(dir)) + if(exit.density) + say("Error! Part outlet is obstructed.") + return + if(temp) + left_part = temp + else if(being_built) + var/obj/I = being_built.build_path + left_part = {"Building [initial(I.name)].
                    + Please wait until completion...
                    "} + else + switch(screen) + if("main") + left_part = output_available_resources()+"
                    " + left_part += "Sync with R&D servers
                    " + for(var/part_set in part_sets) + left_part += "[part_set] - \[Add all parts to queue\]
                    " + if("parts") + left_part += output_parts_list(part_set) + left_part += "
                    Return" + dat = {" + + + [name] + + + + + + + + + +
                    + [left_part] + + [list_queue()] +
                    + + "} + user << browse(dat, "window=mecha_fabricator;size=1000x430") + onclose(user, "mecha_fabricator") + return + +/obj/machinery/mecha_part_fabricator/Topic(href, href_list) + if(..()) + return + if(href_list["part_set"]) + var/tpart_set = href_list["part_set"] + if(tpart_set) + if(tpart_set=="clear") + part_set = null + else + part_set = tpart_set + screen = "parts" + if(href_list["part"]) + var/T = href_list["part"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + if(!processing_queue) + build_part(D) + else + add_to_queue(D) + break + if(href_list["add_to_queue"]) + var/T = href_list["add_to_queue"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + add_to_queue(D) + break + return update_queue_on_page() + if(href_list["remove_from_queue"]) + remove_from_queue(text2num(href_list["remove_from_queue"])) + return update_queue_on_page() + if(href_list["partset_to_queue"]) + add_part_set_to_queue(href_list["partset_to_queue"]) + return update_queue_on_page() + if(href_list["process_queue"]) + INVOKE_ASYNC(src, .proc/do_process_queue) + if(href_list["clear_temp"]) + temp = null + if(href_list["screen"]) + screen = href_list["screen"] + if(href_list["queue_move"] && href_list["index"]) + var/index = text2num(href_list["index"]) + var/new_index = index + text2num(href_list["queue_move"]) + if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index)) + if(ISINRANGE(new_index,1,queue.len)) + queue.Swap(index,new_index) + return update_queue_on_page() + if(href_list["clear_queue"]) + queue = list() + return update_queue_on_page() + if(href_list["sync"]) + sync() + if(href_list["part_desc"]) + var/T = href_list["part_desc"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + var/obj/part = D.build_path + temp = {"

                    [initial(part.name)] description:

                    + [initial(part.desc)]
                    + Return + "} + break + + if(href_list["remove_mat"] && href_list["material"]) + var/datum/material/Mat = locate(href_list["material"]) + eject_sheets(Mat, text2num(href_list["remove_mat"])) + + updateUsrDialog() + return + +/obj/machinery/mecha_part_fabricator/proc/do_process_queue() + if(processing_queue || being_built) + return FALSE + processing_queue = 1 + process_queue() + processing_queue = 0 + +/obj/machinery/mecha_part_fabricator/proc/eject_sheets(eject_sheet, eject_amt) + var/datum/component/material_container/mat_container = rmat.mat_container + if (!mat_container) + say("No access to material storage, please contact the quartermaster.") + return 0 + if (rmat.on_hold()) + say("Mineral access is on hold, please contact the quartermaster.") + return 0 + var/count = mat_container.retrieve_sheets(text2num(eject_amt), eject_sheet, drop_location()) + var/list/matlist = list() + matlist[eject_sheet] = text2num(eject_amt) + rmat.silo_log(src, "ejected", -count, "sheets", matlist) + return count + +/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) + var/datum/material/M = id_inserted + add_overlay("fab-load-[M.name]") + addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[M.name]"), 10) + updateUsrDialog() + +/obj/machinery/mecha_part_fabricator/attackby(obj/item/W, mob/user, params) + if(default_deconstruction_screwdriver(user, "fab-o", "fab-idle", W)) + return TRUE + + if(default_deconstruction_crowbar(W)) + return TRUE + + return ..() + + +/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) + if(panel_open) + to_chat(user, "You can't load [src] while it's opened!") + return FALSE + if(being_built) + to_chat(user, "\The [src] is currently processing! Please wait until completion.") + return FALSE + + return TRUE + +/obj/machinery/mecha_part_fabricator/maint + link_on_init = FALSE diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 8778c7ae408..499594fa82e 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -1,1196 +1,1196 @@ -/obj/mecha - name = "mecha" - desc = "Exosuit" - icon = 'icons/mecha/mecha.dmi' - density = TRUE //Dense. To raise the heat. - opacity = TRUE //opaque. Menacing. - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_EXTREMELY_STRONG - resistance_flags = FIRE_PROOF | ACID_PROOF - layer = BELOW_MOB_LAYER//icon draw layer - infra_luminosity = 15 //byond implementation is bugged. - force = 5 - flags_1 = HEAR_1 - var/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. - var/can_move = 0 //time of next allowed movement - var/mob/living/carbon/occupant = null - var/step_in = 10 //make a step in step_in/10 sec. - var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. - var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. - var/step_energy_drain = 10 - var/melee_energy_drain = 15 - var/overload_step_energy_drain_min = 100 - max_integrity = 300 //max_integrity is base health - var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. - armor = list("melee" = 20, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - var/list/facing_modifiers = list(MECHA_FRONT_ARMOUR = 1.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 0.5) - var/equipment_disabled = 0 //disabled due to EMP - /// Keeps track of the mech's cell - var/obj/item/stock_parts/cell/cell - /// Keeps track of the mech's scanning module - var/obj/item/stock_parts/scanning_module/scanmod - /// Keeps track of the mech's capacitor - var/obj/item/stock_parts/capacitor/capacitor - var/construction_state = MECHA_LOCKED - var/last_message = 0 - var/add_req_access = 1 - var/maint_access = 0 - var/dna_lock //dna-locking the mech - var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference - var/datum/effect_system/spark_spread/spark_system = new - var/lights = FALSE - var/lights_power = 6 - var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference - var/completely_disabled = FALSE //stops the mech from doing anything - - var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. - //inner atmos - var/use_internal_tank = 0 - var/internal_tank_valve = ONE_ATMOSPHERE - var/obj/machinery/portable_atmospherics/canister/internal_tank - var/datum/gas_mixture/cabin_air - var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port = null - - var/obj/item/radio/mech/radio - var/list/trackers = list() - - var/max_temperature = 25000 - var/internal_damage_threshold = 50 //health percentage below which internal damage is possible - var/internal_damage = 0 //contains bitflags - - var/list/operation_req_access = list()//required access level for mecha operation - var/list/internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT - - var/wreckage - - var/list/equipment = new - var/obj/item/mecha_parts/mecha_equipment/selected - var/max_equip = 3 - - var/step_silent = FALSE //Used for disabling mech step sounds while using thrusters or pushing off lockers - var/stepsound = 'sound/mecha/mechstep.ogg' - var/turnsound = 'sound/mecha/mechturn.ogg' - - var/melee_cooldown = 10 - var/melee_can_hit = 1 - - var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. - - var/enter_delay = 40 //Time taken to enter the mech - var/exit_delay = 20 //Time to exit mech - var/destruction_sleep_duration = 20 //Time that mech pilot is put to sleep for if mech is destroyed - var/enclosed = TRUE //Set to false for open-cockpit mechs - var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI - var/is_currently_ejecting = FALSE //Mech cannot use equiptment when true, set to true if pilot is trying to exit mech - - //Action datums - var/datum/action/innate/mecha/mech_eject/eject_action = new - var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new - var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new - var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new - var/datum/action/innate/mecha/mech_view_stats/stats_action = new - var/datum/action/innate/mecha/mech_defense_mode/defense_action = new - var/datum/action/innate/mecha/mech_overload_mode/overload_action = new - var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one - var/datum/action/innate/mecha/mech_smoke/smoke_action = new - var/datum/action/innate/mecha/mech_zoom/zoom_action = new - var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new - var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new - var/datum/action/innate/mecha/strafe/strafing_action = new - - //Action vars - var/obj/item/mecha_parts/mecha_equipment/thrusters/active_thrusters - var/defense_mode = FALSE - var/leg_overload_mode = FALSE - var/leg_overload_coeff = 100 - var/zoom_mode = FALSE - var/smoke = 5 - var/smoke_ready = 1 - var/smoke_cooldown = 100 - var/phasing = FALSE - var/phasing_energy_drain = 200 - var/phase_state = "" //icon_state when phasing - var/strafe = FALSE //If we are strafing - var/canstrafe = TRUE //if we can turn on strafing - var/haslights = TRUE //if we can turn on lights - - var/nextsmash = 0 - var/smashcooldown = 3 //deciseconds - - var/occupant_sight_flags = 0 //sight flags to give to the occupant (e.g. mech mining scanner gives meson-like vision) - var/mouse_pointer - - hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) - -/obj/item/radio/mech //this has to go somewhere - -/obj/mecha/Initialize() - . = ..() - icon_state += "-open" - add_radio() - add_cabin() - if (enclosed) - add_airtank() - spark_system.set_up(2, 0, src) - spark_system.attach(src) - smoke_system.set_up(3, src) - smoke_system.attach(src) - add_cell() - add_scanmod() - add_capacitor() - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - log_message("[src.name] created.", LOG_MECHA) - GLOB.mechas_list += src //global mech list - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/update_icon_state() - if(silicon_pilot && silicon_icon_state) - icon_state = silicon_icon_state - -/obj/mecha/get_cell() - return cell - -/obj/mecha/rust_heretic_act() - take_damage(500, BRUTE) - -/obj/mecha/Destroy() - if(occupant) - occupant.SetSleeping(destruction_sleep_duration) - go_out() - var/mob/living/silicon/ai/AI - for(var/mob/M in src) //Let's just be ultra sure - if(isAI(M)) - occupant = null - AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. - else - M.forceMove(loc) - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach(loc) - qdel(E) - if(cell) - qdel(cell) - if(scanmod) - qdel(scanmod) - if(capacitor) - qdel(capacitor) - if(internal_tank) - qdel(internal_tank) - if(AI) - AI.gib() //No wreck, no AI to recover - STOP_PROCESSING(SSobj, src) - GLOB.poi_list.Remove(src) - equipment.Cut() - cell = null - scanmod = null - capacitor = null - internal_tank = null - if(loc) - loc.assume_air(cabin_air) - air_update_turf() - else - qdel(cabin_air) - cabin_air = null - qdel(spark_system) - spark_system = null - qdel(smoke_system) - smoke_system = null - - GLOB.mechas_list -= src //global mech list - return ..() - -/obj/mecha/proc/restore_equipment() - equipment_disabled = 0 - if(occupant) - SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) - to_chat(occupant, "Equipment control unit has been rebooted successfully.") - occupant.update_mouse_pointer() - -/obj/mecha/CheckParts(list/parts_list) - ..() - cell = locate(/obj/item/stock_parts/cell) in contents - scanmod = locate(/obj/item/stock_parts/scanning_module) in contents - capacitor = locate(/obj/item/stock_parts/capacitor) in contents - update_part_values() - -/obj/mecha/proc/update_part_values() ///Updates the values given by scanning module and capacitor tier, called when a part is removed or inserted. - if(scanmod) - normal_step_energy_drain = 20 - (5 * scanmod.rating) //10 is normal, so on lowest part its worse, on second its ok and on higher its real good up to 0 on best - step_energy_drain = normal_step_energy_drain - else - normal_step_energy_drain = 500 - step_energy_drain = normal_step_energy_drain - if(capacitor) - armor = armor.modifyRating(energy = (capacitor.rating * 5)) //Each level of capacitor protects the mech against emp by 5% - else //because we can still be hit without a cap, even if we can't move - armor = armor.setRating(energy = 0) - - -//////////////////////// -////// Helpers ///////// -//////////////////////// - -/obj/mecha/proc/add_airtank() - internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) - return internal_tank - -///Adds a cell, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. -/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) - QDEL_NULL(cell) - if(C) - C.forceMove(src) - cell = C - return - cell = new /obj/item/stock_parts/cell/high/plus(src) - -///Adds a scanning module, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. -/obj/mecha/proc/add_scanmod(var/obj/item/stock_parts/scanning_module/sm=null) - QDEL_NULL(scanmod) - if(sm) - sm.forceMove(src) - scanmod = sm - return - scanmod = new /obj/item/stock_parts/scanning_module(src) - -///Adds a capacitor, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. -/obj/mecha/proc/add_capacitor(var/obj/item/stock_parts/capacitor/cap=null) - QDEL_NULL(capacitor) - if(cap) - cap.forceMove(src) - capacitor = cap - else - capacitor = new /obj/item/stock_parts/capacitor(src) - -/obj/mecha/proc/add_cabin() - cabin_air = new - cabin_air.temperature = T20C - cabin_air.volume = 200 - cabin_air.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - cabin_air.gases[/datum/gas/oxygen][MOLES] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - cabin_air.gases[/datum/gas/nitrogen][MOLES] = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - return cabin_air - -/obj/mecha/proc/add_radio() - radio = new(src) - radio.name = "[src] radio" - radio.icon = icon - radio.icon_state = icon_state - radio.subspace_transmission = TRUE - -/obj/mecha/proc/can_use(mob/user) - if(user != occupant) - return 0 - if(user && ismob(user)) - if(!user.incapacitated()) - return 1 - return 0 - -//////////////////////////////////////////////////////////////////////////////// - -/obj/mecha/examine(mob/user) - . = ..() - var/integrity = obj_integrity*100/max_integrity - switch(integrity) - if(85 to 100) - . += "It's fully intact." - if(65 to 85) - . += "It's slightly damaged." - if(45 to 65) - . += "It's badly damaged." - if(25 to 45) - . += "It's heavily damaged." - else - . += "It's falling apart." - var/hide_weapon = locate(/obj/item/mecha_parts/concealed_weapon_bay) in contents - var/hidden_weapon = hide_weapon ? (locate(/obj/item/mecha_parts/mecha_equipment/weapon) in equipment) : null - var/list/visible_equipment = equipment - hidden_weapon - if(visible_equipment.len) - . += "It's equipped with:" - for(var/obj/item/mecha_parts/mecha_equipment/ME in visible_equipment) - . += "[icon2html(ME, user)] \A [ME]." - if(!enclosed) - if(silicon_pilot) - . += "[src] appears to be piloting itself..." - else if(occupant && occupant != user) //!silicon_pilot implied - . += "You can see [occupant] inside." - if(ishuman(user)) - var/mob/living/carbon/human/H = user - for(var/O in H.held_items) - if(istype(O, /obj/item/gun)) - . += "It looks like you can hit the pilot directly if you target the center or above." - break //in case user is holding two guns - -//processing internal damage, temperature, air regulation, alert updates, lights power use. -/obj/mecha/process() - var/internal_temp_regulation = 1 - - if(internal_damage) - if(internal_damage & MECHA_INT_FIRE) - if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) - clearInternalDamage(MECHA_INT_FIRE) - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) - setInternalDamage(MECHA_INT_TANK_BREACH) - if(int_tank_air && int_tank_air.return_volume() > 0) //heat the air_contents - int_tank_air.temperature = min(6000+T0C, int_tank_air.temperature+rand(10,15)) - if(cabin_air && cabin_air.return_volume()>0) - cabin_air.temperature = min(6000+T0C, cabin_air.return_temperature()+rand(10,15)) - if(cabin_air.return_temperature() > max_temperature/2) - take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) - - if(internal_damage & MECHA_INT_TEMP_CONTROL) - internal_temp_regulation = 0 - - if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.1) - if(loc) - loc.assume_air(leaked_gas) - air_update_turf() - else - qdel(leaked_gas) - - if(internal_damage & MECHA_INT_SHORT_CIRCUIT) - if(get_charge()) - spark_system.start() - cell.charge -= min(20,cell.charge) - cell.maxcharge -= min(20,cell.maxcharge) - - if(internal_temp_regulation) - if(cabin_air && cabin_air.return_volume() > 0) - var/delta = cabin_air.temperature - T20C - cabin_air.temperature -= max(-10, min(10, round(delta/4,0.1))) - - if(internal_tank) - var/datum/gas_mixture/tank_air = internal_tank.return_air() - - var/release_pressure = internal_tank_valve - var/cabin_pressure = cabin_air.return_pressure() - var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) - var/transfer_moles = 0 - if(pressure_delta > 0) //cabin pressure lower than release pressure - if(tank_air.return_temperature() > 0) - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) - cabin_air.merge(removed) - else if(pressure_delta < 0) //cabin pressure higher than release pressure - var/datum/gas_mixture/t_air = return_air() - pressure_delta = cabin_pressure - release_pressure - if(t_air) - pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) - if(pressure_delta > 0) //if location pressure is lower than cabin pressure - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) - if(t_air) - t_air.merge(removed) - else //just delete the cabin gas, we're in space or some shit - qdel(removed) - - if(occupant) - if(cell) - var/cellcharge = cell.charge/cell.maxcharge - switch(cellcharge) - if(0.75 to INFINITY) - occupant.clear_alert("charge") - if(0.5 to 0.75) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 1) - if(0.25 to 0.5) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) - if(0.01 to 0.25) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) - else - occupant.throw_alert("charge", /obj/screen/alert/emptycell) - - var/integrity = obj_integrity/max_integrity*100 - switch(integrity) - if(30 to 45) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) - if(15 to 35) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) - if(-INFINITY to 15) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) - else - occupant.clear_alert("mech damage") - var/atom/checking = occupant.loc - // recursive check to handle all cases regarding very nested occupants, - // such as brainmob inside brainitem inside MMI inside mecha - while (!isnull(checking)) - if (isturf(checking)) - // hit a turf before hitting the mecha, seems like they have - // been moved out - occupant.clear_alert("charge") - occupant.clear_alert("mech damage") - RemoveActions(occupant, human_occupant=1) - occupant = null - break - else if (checking == src) - break // all good - checking = checking.loc - - if(lights) - var/lights_energy_drain = 2 - use_power(lights_energy_drain) - - if(!enclosed && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. - visible_message("[occupant] tumbles out of the cockpit!") - go_out() //Maybe we should install seat belts? - -//Diagnostic HUD updates - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech - . = ..() - if (occupant && !enclosed && !silicon_pilot) - if (occupant.fire_stacks < 5) - occupant.fire_stacks += 1 - occupant.IgniteMob() - -/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. - return - -/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker == occupant) - if(radio?.broadcasting) - radio.talk_into(speaker, text, , spans, message_language) - //flick speech bubble - var/list/speech_bubble_recipients = list() - for(var/mob/M in get_hearers_in_view(7,src)) - if(M.client) - speech_bubble_recipients.Add(M.client) - INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) - -//////////////////////////// -///// Action processing //// -//////////////////////////// - - -/obj/mecha/proc/click_action(atom/target,mob/user,params) - if(!occupant || occupant != user ) - return - if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled - return - if(completely_disabled) - return - if(is_currently_ejecting) - return - if(phasing) - occupant_message("Unable to interact with objects while phasing.") - return - if(user.incapacitated()) - return - if(construction_state) - occupant_message("Maintenance protocols in effect.") - return - if(!get_charge()) - return - if(src == target) - return - var/dir_to_target = get_dir(src,target) - if(dir_to_target && !(dir_to_target & dir))//wrong direction - return - if(internal_damage & MECHA_INT_CONTROL_LOST) - if (!target) - return - target = pick(view(3,target)) - - var/mob/living/L = user - if(!Adjacent(target)) - if(selected && selected.is_ranged()) - if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) - to_chat(user, "You don't want to harm other living beings!") - return - if(selected.action(target,params)) - selected.start_cooldown() - else if(selected && selected.is_melee()) - if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm other living beings!") - return - if(selected.action(target,params)) - selected.start_cooldown() - else - if(internal_damage & MECHA_INT_CONTROL_LOST) - var/list/possible_targets = oview(1,src) - if (!length(possible_targets)) - return - target = pick(possible_targets) - if(!melee_can_hit || !istype(target, /atom)) - return - target.mech_melee_attack(src) - melee_can_hit = FALSE - addtimer(VARSET_CALLBACK(src, melee_can_hit, TRUE), melee_cooldown) - - -/obj/mecha/proc/range_action(atom/target) - return - - -////////////////////////////////// -//////// Movement procs //////// -////////////////////////////////// - -///Plays the mech step sound effect. Split from movement procs so that other mechs (HONK) can override this one specific part. -/obj/mecha/proc/play_stepsound() - if(stepsound) - playsound(src,stepsound,40,1) - -/obj/mecha/Move(atom/newloc, direct) - . = ..() - if (internal_tank?.disconnect()) // Something moved us and broke connection - occupant_message("Air port connection has been severed!") - log_message("Lost connection to gas port.", LOG_MECHA) - -/obj/mecha/Process_Spacemove(var/movement_dir = 0) - . = ..() - if(.) - return TRUE - - var/atom/movable/backup = get_spacemove_backup() - if(backup) - if(istype(backup) && movement_dir && !backup.anchored) - if(backup.newtonian_move(turn(movement_dir, 180))) - step_silent = TRUE - if(occupant) - to_chat(occupant, "You push off [backup] to propel yourself.") - return TRUE - - if(can_move <= world.time && active_thrusters && movement_dir && active_thrusters.thrust(movement_dir)) - step_silent = TRUE - return TRUE - - return FALSE - -/obj/mecha/relaymove(mob/user,direction) - if(completely_disabled) - return - if(!direction) - return - if(user != occupant) //While not "realistic", this piece is player friendly. - user.forceMove(get_turf(src)) - to_chat(user, "You climb out from [src].") - return 0 - if(internal_tank?.connected_port) - if(world.time - last_message > 20) - occupant_message("Unable to move while connected to the air system port!") - last_message = world.time - return 0 - if(construction_state) - if(world.time - last_message > 20) - occupant_message("Maintenance protocols in effect.") - last_message = world.time - return - return domove(direction) - -/obj/mecha/proc/domove(direction) - if(can_move >= world.time) - return 0 - if(!Process_Spacemove(direction)) - return 0 - if(!has_charge(step_energy_drain)) - return 0 - if(zoom_mode) - if(world.time - last_message > 20) - occupant_message("Unable to move while in zoom mode!") - last_message = world.time - return 0 - if(!cell) - if(world.time - last_message > 20) - occupant_message("Missing power cell.") - last_message = world.time - return 0 - if(!scanmod || !capacitor) - if(world.time - last_message > 20) - occupant_message("Missing [scanmod? "capacitor" : "scanning module"].") - last_message = world.time - return 0 - - var/move_result = 0 - var/oldloc = loc - if(internal_damage & MECHA_INT_CONTROL_LOST) - move_result = mechsteprand() - else if(dir != direction && (!strafe || occupant.client.keys_held["Alt"])) - move_result = mechturn(direction) - else - move_result = mechstep(direction) - if(move_result || loc != oldloc)// halfway done diagonal move still returns false - use_power(step_energy_drain) - can_move = world.time + step_in - return 1 - return 0 - -/obj/mecha/proc/mechturn(direction) - setDir(direction) - if(turnsound) - playsound(src,turnsound,40,TRUE) - return 1 - -/obj/mecha/proc/mechstep(direction) - var/current_dir = dir - . = step(src,direction) - if(strafe) - setDir(current_dir) - if(. && !step_silent) - play_stepsound() - step_silent = FALSE - -/obj/mecha/proc/mechsteprand() - . = step_rand(src) - if(. && !step_silent) - play_stepsound() - step_silent = FALSE - -/obj/mecha/Bump(var/atom/obstacle) - if(phasing && get_charge() >= phasing_energy_drain && !throwing) - if(!can_move) - return - can_move = 0 - if(phase_state) - flick(phase_state, src) - forceMove(get_step(src,dir)) - use_power(phasing_energy_drain) - addtimer(VARSET_CALLBACK(src, can_move, TRUE), step_in*3) - else - if(..()) //mech was thrown - return - if(bumpsmash && occupant) //Need a pilot to push the PUNCH button. - if(nextsmash < world.time) - obstacle.mech_melee_attack(src) - nextsmash = world.time + smashcooldown - if(!obstacle || obstacle.CanPass(src,get_step(src,dir))) - step(src,dir) - if(isobj(obstacle)) - var/obj/O = obstacle - if(!O.anchored && O.move_resist <= move_force) - step(obstacle, dir) - else if(ismob(obstacle)) - var/mob/M = obstacle - if(M.move_resist <= move_force) - step(obstacle, dir) - - - - - -/////////////////////////////////// -//////// Internal damage //////// -/////////////////////////////////// - -/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) - if(!islist(possible_int_damage) || !length(possible_int_damage)) - return - if(prob(20)) - if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) - for(var/T in possible_int_damage) - if(internal_damage & T) - possible_int_damage -= T - if (length(possible_int_damage)) - var/int_dam_flag = pick(possible_int_damage) - if(int_dam_flag) - setInternalDamage(int_dam_flag) - if(prob(5)) - if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) - if (length(equipment)) - var/obj/item/mecha_parts/mecha_equipment/ME = pick(equipment) - qdel(ME) - -/obj/mecha/proc/setInternalDamage(int_dam_flag) - internal_damage |= int_dam_flag - log_message("Internal damage of type [int_dam_flag].", LOG_MECHA) - SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) - diag_hud_set_mechstat() - -/obj/mecha/proc/clearInternalDamage(int_dam_flag) - if(internal_damage & int_dam_flag) - switch(int_dam_flag) - if(MECHA_INT_TEMP_CONTROL) - occupant_message("Life support system reactivated.") - if(MECHA_INT_FIRE) - occupant_message("Internal fire extinguished.") - if(MECHA_INT_TANK_BREACH) - occupant_message("Damaged internal tank has been sealed.") - internal_damage &= ~int_dam_flag - diag_hud_set_mechstat() - -///////////////////////////////////// -//////////// AI piloting //////////// -///////////////////////////////////// - -/obj/mecha/attack_ai(mob/living/silicon/ai/user) - if(!isAI(user)) - return - //Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot. - if(user.can_dominate_mechs) - examine(user) //Get diagnostic information! - for(var/obj/item/mecha_parts/mecha_tracking/B in trackers) - to_chat(user, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:") - to_chat(user, "[B.get_mecha_info()]") - break - //Nothing like a big, red link to make the player feel powerful! - to_chat(user, "ASSUME DIRECT CONTROL?
                    ") - else - examine(user) - if(occupant) - to_chat(user, "This exosuit has a pilot and cannot be controlled.") - return - var/can_control_mech = 0 - for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) - can_control_mech = 1 - to_chat(user, "[icon2html(src, user)] Status of [name]:\n[A.get_mecha_info()]") - break - if(!can_control_mech) - to_chat(user, "You cannot control exosuits without AI control beacons installed.") - return - to_chat(user, "Take control of exosuit?
                    ") - -/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(!..()) - return - - //Transfer from core or card to mech. Proc is called by mech. - switch(interaction) - if(AI_TRANS_TO_CARD) //Upload AI from mech to AI card. - if(!construction_state) //Mech must be in maint mode to allow carding. - to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.") - return - AI = occupant - if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot - to_chat(user, "No AI detected in the [name] onboard computer.") - return - AI.ai_restore_power()//So the AI initially has power. - AI.control_disabled = TRUE - AI.radio_enabled = FALSE - AI.disconnect_shell() - RemoveActions(AI, TRUE) - occupant = null - silicon_pilot = FALSE - AI.forceMove(card) - card.AI = AI - AI.controlled_mech = null - AI.remote_control = null - icon_state = initial(icon_state)+"-open" - to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") - - if(AI_MECH_HACK) //Called by AIs on the mech - AI.linked_core = new /obj/structure/ai_core/deactivated(AI.loc) - if(AI.can_dominate_mechs) - if(occupant) //Oh, I am sorry, were you using that? - to_chat(AI, "Pilot detected! Forced ejection initiated!") - to_chat(occupant, "You have been forcibly ejected!") - go_out(1) //IT IS MINE, NOW. SUCK IT, RD! - ai_enter_mech(AI, interaction) - - if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech. - AI = card.AI - if(!AI) - to_chat(user, "There is no AI currently installed on this device.") - return - if(AI.deployed_shell) //Recall AI if shelled so it can be checked for a client - AI.disconnect_shell() - if(AI.stat || !AI.client) - to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") - return - if(occupant || dna_lock) //Normal AIs cannot steal mechs! - to_chat(user, "Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].") - return - AI.control_disabled = FALSE - AI.radio_enabled = TRUE - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - ai_enter_mech(AI, interaction) - -//Hack and From Card interactions share some code, so leave that here for both to use. -/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) - AI.ai_restore_power() - AI.forceMove(src) - occupant = AI - silicon_pilot = TRUE - icon_state = initial(icon_state) - update_icon() - playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - AI.cancel_camera() - AI.controlled_mech = src - AI.remote_control = src - AI.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. - AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. - to_chat(AI, AI.can_dominate_mechs ? "Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!" :\ - "You have been uploaded to a mech's onboard computer.") - to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") - if(interaction == AI_TRANS_FROM_CARD) - GrantActions(AI, FALSE) //No eject/return to core action for AI uploaded by card - else - GrantActions(AI, !AI.can_dominate_mechs) - - -//An actual AI (simple_animal mecha pilot) entering the mech -/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(pilot_mob && pilot_mob.Adjacent(src)) - if(occupant) - return - icon_state = initial(icon_state) - occupant = pilot_mob - pilot_mob.mecha = src - pilot_mob.forceMove(src) - GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob - -/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(occupant == pilot_mob) - occupant = null - if(pilot_mob.mecha == src) - pilot_mob.mecha = null - icon_state = "[initial(icon_state)]-open" - pilot_mob.forceMove(get_turf(src)) - RemoveActions(pilot_mob) - - -///////////////////////////////////// -//////// Atmospheric stuff //////// -///////////////////////////////////// - -/obj/mecha/remove_air(amount) - if(use_internal_tank) - return cabin_air.remove(amount) - return ..() - -/obj/mecha/return_air() - if(use_internal_tank) - return cabin_air - return ..() - -/obj/mecha/return_analyzable_air() - return cabin_air - -/obj/mecha/proc/return_pressure() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_pressure() - -/obj/mecha/return_temperature() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_temperature() - -/obj/mecha/MouseDrop_T(mob/M, mob/user) - if((user != M) || user.incapacitated() || !Adjacent(user)) - return - if(!ishuman(user)) // no silicons or drones in mechas. - return - if(HAS_TRAIT(user, TRAIT_PRIMITIVE)) //no lavalizards either. - to_chat(user, "The knowledge to use this device eludes you!") - return - log_message("[user] tries to move in.", LOG_MECHA) - if (occupant) - to_chat(usr, "The [name] is already occupied!") - log_message("Permission denied (Occupied).", LOG_MECHA) - return - if(dna_lock) - var/passed = FALSE - if(user.has_dna()) - var/mob/living/carbon/C = user - if(C.dna.unique_enzymes==dna_lock) - passed = TRUE - if (!passed) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - log_message("Permission denied (DNA LOCK).", LOG_MECHA) - return - if(!operation_allowed(user)) - to_chat(user, "Access denied. Insufficient operation keycodes.") - log_message("Permission denied (No keycode).", LOG_MECHA) - return - if(user.buckled) - to_chat(user, "You are currently buckled and cannot move.") - log_message("Permission denied (Buckled).", LOG_MECHA) - return - if(user.has_buckled_mobs()) //mob attached to us - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - log_message("Permission denied (Attached mobs).", LOG_MECHA) - return - - visible_message("[user] starts to climb into [name].") - - if(do_after(user, enter_delay, target = src)) - if(obj_integrity <= 0) - to_chat(user, "You cannot get in the [name], it has been destroyed!") - else if(occupant) - to_chat(user, "[occupant] was faster! Try better next time, loser.") - else if(user.buckled) - to_chat(user, "You can't enter the exosuit while buckled.") - else if(user.has_buckled_mobs()) - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - else - moved_inside(user) - else - to_chat(user, "You stop entering the exosuit!") - -/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) - . = FALSE - if(H && H.client && (H in range(1))) - occupant = H - H.forceMove(src) - H.update_mouse_pointer() - add_fingerprint(H) - GrantActions(H, human_occupant=1) - forceMove(loc) - log_message("[H] moved in as pilot.", LOG_MECHA) - icon_state = initial(icon_state) - setDir(dir_in) - playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - return TRUE - -/obj/mecha/proc/mmi_move_inside(obj/item/mmi/M, mob/user) - . = FALSE - if(!M.brain_check(user)) - return - - var/mob/living/brain/B = M.brainmob - if(occupant) - to_chat(user, "Occupant detected!") - return - if(dna_lock && (!B.stored_dna || (dna_lock != B.stored_dna.unique_enzymes))) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - return - - visible_message("[user] starts to insert an MMI into [name].") - - if(do_after(user, 40, target = src)) - if(!occupant) - return mmi_moved_inside(M, user) - else - to_chat(user, "Occupant detected!") - else - to_chat(user, "You stop inserting the MMI.") - -/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/M, mob/user) - if(!(Adjacent(M) && Adjacent(user))) - return FALSE - if(!M.brain_check(user)) - return FALSE - - var/mob/living/brain/B = M.brainmob - if(!user.transferItemToLoc(M, src)) - to_chat(user, "\the [M] is stuck to your hand, you cannot put it in \the [src]!") - return FALSE - - M.mecha = src - occupant = B - silicon_pilot = TRUE - B.forceMove(src) //should allow relaymove - B.reset_perspective(src) - B.remote_control = src - B.update_mobility() - B.update_mouse_pointer() - icon_state = initial(icon_state) - update_icon() - setDir(dir_in) - log_message("[M] moved in as pilot.", LOG_MECHA) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - GrantActions(B) - log_game("[key_name(user)] has put the MMI/posibrain of [key_name(B)] into [src] at [AREACOORD(src)]") - return TRUE - -/obj/mecha/container_resist(mob/living/user) - is_currently_ejecting = TRUE - to_chat(occupant, "You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.") - if(do_after(occupant, has_gravity() ? exit_delay : 0 , target = src)) - to_chat(occupant, "You exit the mech.") - go_out() - else - to_chat(occupant, "You stop exiting the mech. Weapons are enabled again.") - is_currently_ejecting = FALSE - -/obj/mecha/Exited(atom/movable/M, atom/newloc) - if(occupant && occupant == M) // The occupant exited the mech without calling go_out() - go_out(TRUE, newloc) - - if(cell && cell == M) - cell = null - return - if(scanmod && scanmod == M) - scanmod = null - update_part_values() - return - if(capacitor && capacitor == M) - armor = armor.modifyRating(energy = (capacitor.rating * -5)) //lose the energy armor if we lose this cap - capacitor = null - update_part_values() - -/obj/mecha/proc/go_out(forced, atom/newloc = loc) - if(!occupant) - return - var/atom/movable/mob_container - occupant.clear_alert("charge") - occupant.clear_alert("mech damage") - if(ishuman(occupant)) - mob_container = occupant - RemoveActions(occupant, human_occupant=1) - else if(isbrain(occupant)) - var/mob/living/brain/brain = occupant - RemoveActions(brain) - mob_container = brain.container - else if(isAI(occupant)) - var/mob/living/silicon/ai/AI = occupant - if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. - RemoveActions(occupant) - occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. - occupant = null - silicon_pilot = FALSE - return - else - if(!AI.linked_core) - to_chat(AI, "Inactive core destroyed. Unable to return.") - AI.linked_core = null - return - to_chat(AI, "Returning to core...") - AI.controlled_mech = null - AI.remote_control = null - RemoveActions(occupant, 1) - mob_container = AI - newloc = get_turf(AI.linked_core) - qdel(AI.linked_core) - else - return - var/mob/living/L = occupant - occupant = null //we need it null when forceMove calls Exited(). - silicon_pilot = FALSE - if(mob_container.forceMove(newloc))//ejecting mob container - log_message("[mob_container] moved out.", LOG_MECHA) - L << browse(null, "window=exosuit") - - if(istype(mob_container, /obj/item/mmi)) - var/obj/item/mmi/mmi = mob_container - if(mmi.brainmob) - L.forceMove(mmi) - L.reset_perspective() - mmi.mecha = null - mmi.update_icon() - L.mobility_flags = NONE - icon_state = initial(icon_state)+"-open" - setDir(dir_in) - - if(L && L.client) - L.update_mouse_pointer() - L.client.view_size.resetToDefault() - zoom_mode = 0 - -///////////////////////// -////// Access stuff ///// -///////////////////////// - -/obj/mecha/proc/operation_allowed(mob/M) - req_access = operation_req_access - req_one_access = list() - return allowed(M) - -/obj/mecha/proc/internals_access_allowed(mob/M) - req_one_access = internals_req_access - req_access = list() - return allowed(M) - - - -//////////////////////////////// -/////// Messages and Log /////// -//////////////////////////////// - -/obj/mecha/proc/occupant_message(message as text) - if(message) - if(occupant && occupant.client) - to_chat(occupant, "[icon2html(src, occupant)] [message]") - -GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) -GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? - -/////////////////////// -///// Power stuff ///// -/////////////////////// - -/obj/mecha/proc/has_charge(amount) - return (get_charge()>=amount) - -/obj/mecha/proc/get_charge() - for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) - var/relay_charge = R.get_charge() - if(relay_charge) - return relay_charge - if(cell) - return max(0, cell.charge) - -/obj/mecha/proc/use_power(amount) - if(get_charge() && cell.use(amount)) - return 1 - return 0 - -/obj/mecha/proc/give_power(amount) - if(!isnull(get_charge())) - cell.give(amount) - return 1 - return 0 - -/obj/mecha/update_remote_sight(mob/living/user) - if(occupant_sight_flags) - if(user == occupant) - user.sight |= occupant_sight_flags - -/////////////////////// -////// Ammo stuff ///// -/////////////////////// - -/obj/mecha/proc/ammo_resupply(var/obj/item/mecha_ammo/A, mob/user,var/fail_chat_override = FALSE) - if(!A.rounds) - if(!fail_chat_override) - to_chat(user, "This box of ammo is empty!") - return FALSE - var/ammo_needed - var/found_gun - for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in equipment) - ammo_needed = 0 - - if(istype(gun, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic) && gun.ammo_type == A.ammo_type) - found_gun = TRUE - if(A.direct_load) - ammo_needed = initial(gun.projectiles) - gun.projectiles - else - ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache - - if(ammo_needed) - if(ammo_needed < A.rounds) - if(A.direct_load) - gun.projectiles = gun.projectiles + ammo_needed - else - gun.projectiles_cache = gun.projectiles_cache + ammo_needed - playsound(get_turf(user),A.load_audio,50,TRUE) - to_chat(user, "You add [ammo_needed] [A.round_term][ammo_needed > 1?"s":""] to the [gun.name]") - A.rounds = A.rounds - ammo_needed - A.update_name() - return TRUE - - else - if(A.direct_load) - gun.projectiles = gun.projectiles + A.rounds - else - gun.projectiles_cache = gun.projectiles_cache + A.rounds - playsound(get_turf(user),A.load_audio,50,TRUE) - to_chat(user, "You add [A.rounds] [A.round_term][A.rounds > 1?"s":""] to the [gun.name]") - A.rounds = 0 - A.update_name() - return TRUE - if(!fail_chat_override) - if(found_gun) - to_chat(user, "You can't fit any more ammo of this type!") - else - to_chat(user, "None of the equipment on this exosuit can use this ammo!") - return FALSE +/obj/mecha + name = "mecha" + desc = "Exosuit" + icon = 'icons/mecha/mecha.dmi' + density = TRUE //Dense. To raise the heat. + opacity = TRUE //opaque. Menacing. + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_EXTREMELY_STRONG + resistance_flags = FIRE_PROOF | ACID_PROOF + layer = BELOW_MOB_LAYER//icon draw layer + infra_luminosity = 15 //byond implementation is bugged. + force = 5 + flags_1 = HEAR_1 + var/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. + var/can_move = 0 //time of next allowed movement + var/mob/living/carbon/occupant = null + var/step_in = 10 //make a step in step_in/10 sec. + var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. + var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. + var/step_energy_drain = 10 + var/melee_energy_drain = 15 + var/overload_step_energy_drain_min = 100 + max_integrity = 300 //max_integrity is base health + var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. + armor = list("melee" = 20, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + var/list/facing_modifiers = list(MECHA_FRONT_ARMOUR = 1.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 0.5) + var/equipment_disabled = 0 //disabled due to EMP + /// Keeps track of the mech's cell + var/obj/item/stock_parts/cell/cell + /// Keeps track of the mech's scanning module + var/obj/item/stock_parts/scanning_module/scanmod + /// Keeps track of the mech's capacitor + var/obj/item/stock_parts/capacitor/capacitor + var/construction_state = MECHA_LOCKED + var/last_message = 0 + var/add_req_access = 1 + var/maint_access = 0 + var/dna_lock //dna-locking the mech + var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference + var/datum/effect_system/spark_spread/spark_system = new + var/lights = FALSE + var/lights_power = 6 + var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference + var/completely_disabled = FALSE //stops the mech from doing anything + + var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. + //inner atmos + var/use_internal_tank = 0 + var/internal_tank_valve = ONE_ATMOSPHERE + var/obj/machinery/portable_atmospherics/canister/internal_tank + var/datum/gas_mixture/cabin_air + var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port = null + + var/obj/item/radio/mech/radio + var/list/trackers = list() + + var/max_temperature = 25000 + var/internal_damage_threshold = 50 //health percentage below which internal damage is possible + var/internal_damage = 0 //contains bitflags + + var/list/operation_req_access = list()//required access level for mecha operation + var/list/internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT + + var/wreckage + + var/list/equipment = new + var/obj/item/mecha_parts/mecha_equipment/selected + var/max_equip = 3 + + var/step_silent = FALSE //Used for disabling mech step sounds while using thrusters or pushing off lockers + var/stepsound = 'sound/mecha/mechstep.ogg' + var/turnsound = 'sound/mecha/mechturn.ogg' + + var/melee_cooldown = 10 + var/melee_can_hit = 1 + + var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. + + var/enter_delay = 40 //Time taken to enter the mech + var/exit_delay = 20 //Time to exit mech + var/destruction_sleep_duration = 20 //Time that mech pilot is put to sleep for if mech is destroyed + var/enclosed = TRUE //Set to false for open-cockpit mechs + var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI + var/is_currently_ejecting = FALSE //Mech cannot use equiptment when true, set to true if pilot is trying to exit mech + + //Action datums + var/datum/action/innate/mecha/mech_eject/eject_action = new + var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new + var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new + var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new + var/datum/action/innate/mecha/mech_view_stats/stats_action = new + var/datum/action/innate/mecha/mech_defense_mode/defense_action = new + var/datum/action/innate/mecha/mech_overload_mode/overload_action = new + var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one + var/datum/action/innate/mecha/mech_smoke/smoke_action = new + var/datum/action/innate/mecha/mech_zoom/zoom_action = new + var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new + var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new + var/datum/action/innate/mecha/strafe/strafing_action = new + + //Action vars + var/obj/item/mecha_parts/mecha_equipment/thrusters/active_thrusters + var/defense_mode = FALSE + var/leg_overload_mode = FALSE + var/leg_overload_coeff = 100 + var/zoom_mode = FALSE + var/smoke = 5 + var/smoke_ready = 1 + var/smoke_cooldown = 100 + var/phasing = FALSE + var/phasing_energy_drain = 200 + var/phase_state = "" //icon_state when phasing + var/strafe = FALSE //If we are strafing + var/canstrafe = TRUE //if we can turn on strafing + var/haslights = TRUE //if we can turn on lights + + var/nextsmash = 0 + var/smashcooldown = 3 //deciseconds + + var/occupant_sight_flags = 0 //sight flags to give to the occupant (e.g. mech mining scanner gives meson-like vision) + var/mouse_pointer + + hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) + +/obj/item/radio/mech //this has to go somewhere + +/obj/mecha/Initialize() + . = ..() + icon_state += "-open" + add_radio() + add_cabin() + if (enclosed) + add_airtank() + spark_system.set_up(2, 0, src) + spark_system.attach(src) + smoke_system.set_up(3, src) + smoke_system.attach(src) + add_cell() + add_scanmod() + add_capacitor() + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + log_message("[src.name] created.", LOG_MECHA) + GLOB.mechas_list += src //global mech list + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_to_hud(src) + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/update_icon_state() + if(silicon_pilot && silicon_icon_state) + icon_state = silicon_icon_state + +/obj/mecha/get_cell() + return cell + +/obj/mecha/rust_heretic_act() + take_damage(500, BRUTE) + +/obj/mecha/Destroy() + if(occupant) + occupant.SetSleeping(destruction_sleep_duration) + go_out() + var/mob/living/silicon/ai/AI + for(var/mob/M in src) //Let's just be ultra sure + if(isAI(M)) + occupant = null + AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. + else + M.forceMove(loc) + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach(loc) + qdel(E) + if(cell) + qdel(cell) + if(scanmod) + qdel(scanmod) + if(capacitor) + qdel(capacitor) + if(internal_tank) + qdel(internal_tank) + if(AI) + AI.gib() //No wreck, no AI to recover + STOP_PROCESSING(SSobj, src) + GLOB.poi_list.Remove(src) + equipment.Cut() + cell = null + scanmod = null + capacitor = null + internal_tank = null + if(loc) + loc.assume_air(cabin_air) + air_update_turf() + else + qdel(cabin_air) + cabin_air = null + qdel(spark_system) + spark_system = null + qdel(smoke_system) + smoke_system = null + + GLOB.mechas_list -= src //global mech list + return ..() + +/obj/mecha/proc/restore_equipment() + equipment_disabled = 0 + if(occupant) + SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) + to_chat(occupant, "Equipment control unit has been rebooted successfully.") + occupant.update_mouse_pointer() + +/obj/mecha/CheckParts(list/parts_list) + ..() + cell = locate(/obj/item/stock_parts/cell) in contents + scanmod = locate(/obj/item/stock_parts/scanning_module) in contents + capacitor = locate(/obj/item/stock_parts/capacitor) in contents + update_part_values() + +/obj/mecha/proc/update_part_values() ///Updates the values given by scanning module and capacitor tier, called when a part is removed or inserted. + if(scanmod) + normal_step_energy_drain = 20 - (5 * scanmod.rating) //10 is normal, so on lowest part its worse, on second its ok and on higher its real good up to 0 on best + step_energy_drain = normal_step_energy_drain + else + normal_step_energy_drain = 500 + step_energy_drain = normal_step_energy_drain + if(capacitor) + armor = armor.modifyRating(energy = (capacitor.rating * 5)) //Each level of capacitor protects the mech against emp by 5% + else //because we can still be hit without a cap, even if we can't move + armor = armor.setRating(energy = 0) + + +//////////////////////// +////// Helpers ///////// +//////////////////////// + +/obj/mecha/proc/add_airtank() + internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) + return internal_tank + +///Adds a cell, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. +/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) + QDEL_NULL(cell) + if(C) + C.forceMove(src) + cell = C + return + cell = new /obj/item/stock_parts/cell/high/plus(src) + +///Adds a scanning module, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. +/obj/mecha/proc/add_scanmod(var/obj/item/stock_parts/scanning_module/sm=null) + QDEL_NULL(scanmod) + if(sm) + sm.forceMove(src) + scanmod = sm + return + scanmod = new /obj/item/stock_parts/scanning_module(src) + +///Adds a capacitor, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. +/obj/mecha/proc/add_capacitor(var/obj/item/stock_parts/capacitor/cap=null) + QDEL_NULL(capacitor) + if(cap) + cap.forceMove(src) + capacitor = cap + else + capacitor = new /obj/item/stock_parts/capacitor(src) + +/obj/mecha/proc/add_cabin() + cabin_air = new + cabin_air.temperature = T20C + cabin_air.volume = 200 + cabin_air.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) + cabin_air.gases[/datum/gas/oxygen][MOLES] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.gases[/datum/gas/nitrogen][MOLES] = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + return cabin_air + +/obj/mecha/proc/add_radio() + radio = new(src) + radio.name = "[src] radio" + radio.icon = icon + radio.icon_state = icon_state + radio.subspace_transmission = TRUE + +/obj/mecha/proc/can_use(mob/user) + if(user != occupant) + return 0 + if(user && ismob(user)) + if(!user.incapacitated()) + return 1 + return 0 + +//////////////////////////////////////////////////////////////////////////////// + +/obj/mecha/examine(mob/user) + . = ..() + var/integrity = obj_integrity*100/max_integrity + switch(integrity) + if(85 to 100) + . += "It's fully intact." + if(65 to 85) + . += "It's slightly damaged." + if(45 to 65) + . += "It's badly damaged." + if(25 to 45) + . += "It's heavily damaged." + else + . += "It's falling apart." + var/hide_weapon = locate(/obj/item/mecha_parts/concealed_weapon_bay) in contents + var/hidden_weapon = hide_weapon ? (locate(/obj/item/mecha_parts/mecha_equipment/weapon) in equipment) : null + var/list/visible_equipment = equipment - hidden_weapon + if(visible_equipment.len) + . += "It's equipped with:" + for(var/obj/item/mecha_parts/mecha_equipment/ME in visible_equipment) + . += "[icon2html(ME, user)] \A [ME]." + if(!enclosed) + if(silicon_pilot) + . += "[src] appears to be piloting itself..." + else if(occupant && occupant != user) //!silicon_pilot implied + . += "You can see [occupant] inside." + if(ishuman(user)) + var/mob/living/carbon/human/H = user + for(var/O in H.held_items) + if(istype(O, /obj/item/gun)) + . += "It looks like you can hit the pilot directly if you target the center or above." + break //in case user is holding two guns + +//processing internal damage, temperature, air regulation, alert updates, lights power use. +/obj/mecha/process() + var/internal_temp_regulation = 1 + + if(internal_damage) + if(internal_damage & MECHA_INT_FIRE) + if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) + clearInternalDamage(MECHA_INT_FIRE) + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) + setInternalDamage(MECHA_INT_TANK_BREACH) + if(int_tank_air && int_tank_air.return_volume() > 0) //heat the air_contents + int_tank_air.temperature = min(6000+T0C, int_tank_air.temperature+rand(10,15)) + if(cabin_air && cabin_air.return_volume()>0) + cabin_air.temperature = min(6000+T0C, cabin_air.return_temperature()+rand(10,15)) + if(cabin_air.return_temperature() > max_temperature/2) + take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) + + if(internal_damage & MECHA_INT_TEMP_CONTROL) + internal_temp_regulation = 0 + + if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.1) + if(loc) + loc.assume_air(leaked_gas) + air_update_turf() + else + qdel(leaked_gas) + + if(internal_damage & MECHA_INT_SHORT_CIRCUIT) + if(get_charge()) + spark_system.start() + cell.charge -= min(20,cell.charge) + cell.maxcharge -= min(20,cell.maxcharge) + + if(internal_temp_regulation) + if(cabin_air && cabin_air.return_volume() > 0) + var/delta = cabin_air.temperature - T20C + cabin_air.temperature -= max(-10, min(10, round(delta/4,0.1))) + + if(internal_tank) + var/datum/gas_mixture/tank_air = internal_tank.return_air() + + var/release_pressure = internal_tank_valve + var/cabin_pressure = cabin_air.return_pressure() + var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) + var/transfer_moles = 0 + if(pressure_delta > 0) //cabin pressure lower than release pressure + if(tank_air.return_temperature() > 0) + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) + cabin_air.merge(removed) + else if(pressure_delta < 0) //cabin pressure higher than release pressure + var/datum/gas_mixture/t_air = return_air() + pressure_delta = cabin_pressure - release_pressure + if(t_air) + pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) + if(pressure_delta > 0) //if location pressure is lower than cabin pressure + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) + if(t_air) + t_air.merge(removed) + else //just delete the cabin gas, we're in space or some shit + qdel(removed) + + if(occupant) + if(cell) + var/cellcharge = cell.charge/cell.maxcharge + switch(cellcharge) + if(0.75 to INFINITY) + occupant.clear_alert("charge") + if(0.5 to 0.75) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 1) + if(0.25 to 0.5) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) + if(0.01 to 0.25) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) + else + occupant.throw_alert("charge", /obj/screen/alert/emptycell) + + var/integrity = obj_integrity/max_integrity*100 + switch(integrity) + if(30 to 45) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) + if(15 to 35) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) + if(-INFINITY to 15) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) + else + occupant.clear_alert("mech damage") + var/atom/checking = occupant.loc + // recursive check to handle all cases regarding very nested occupants, + // such as brainmob inside brainitem inside MMI inside mecha + while (!isnull(checking)) + if (isturf(checking)) + // hit a turf before hitting the mecha, seems like they have + // been moved out + occupant.clear_alert("charge") + occupant.clear_alert("mech damage") + RemoveActions(occupant, human_occupant=1) + occupant = null + break + else if (checking == src) + break // all good + checking = checking.loc + + if(lights) + var/lights_energy_drain = 2 + use_power(lights_energy_drain) + + if(!enclosed && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. + visible_message("[occupant] tumbles out of the cockpit!") + go_out() //Maybe we should install seat belts? + +//Diagnostic HUD updates + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech + . = ..() + if (occupant && !enclosed && !silicon_pilot) + if (occupant.fire_stacks < 5) + occupant.fire_stacks += 1 + occupant.IgniteMob() + +/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. + return + +/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(speaker == occupant) + if(radio?.broadcasting) + radio.talk_into(speaker, text, , spans, message_language) + //flick speech bubble + var/list/speech_bubble_recipients = list() + for(var/mob/M in get_hearers_in_view(7,src)) + if(M.client) + speech_bubble_recipients.Add(M.client) + INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) + +//////////////////////////// +///// Action processing //// +//////////////////////////// + + +/obj/mecha/proc/click_action(atom/target,mob/user,params) + if(!occupant || occupant != user ) + return + if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled + return + if(completely_disabled) + return + if(is_currently_ejecting) + return + if(phasing) + occupant_message("Unable to interact with objects while phasing.") + return + if(user.incapacitated()) + return + if(construction_state) + occupant_message("Maintenance protocols in effect.") + return + if(!get_charge()) + return + if(src == target) + return + var/dir_to_target = get_dir(src,target) + if(dir_to_target && !(dir_to_target & dir))//wrong direction + return + if(internal_damage & MECHA_INT_CONTROL_LOST) + if (!target) + return + target = pick(view(3,target)) + + var/mob/living/L = user + if(!Adjacent(target)) + if(selected && selected.is_ranged()) + if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) + to_chat(user, "You don't want to harm other living beings!") + return + if(selected.action(target,params)) + selected.start_cooldown() + else if(selected && selected.is_melee()) + if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm other living beings!") + return + if(selected.action(target,params)) + selected.start_cooldown() + else + if(internal_damage & MECHA_INT_CONTROL_LOST) + var/list/possible_targets = oview(1,src) + if (!length(possible_targets)) + return + target = pick(possible_targets) + if(!melee_can_hit || !istype(target, /atom)) + return + target.mech_melee_attack(src) + melee_can_hit = FALSE + addtimer(VARSET_CALLBACK(src, melee_can_hit, TRUE), melee_cooldown) + + +/obj/mecha/proc/range_action(atom/target) + return + + +////////////////////////////////// +//////// Movement procs //////// +////////////////////////////////// + +///Plays the mech step sound effect. Split from movement procs so that other mechs (HONK) can override this one specific part. +/obj/mecha/proc/play_stepsound() + if(stepsound) + playsound(src,stepsound,40,1) + +/obj/mecha/Move(atom/newloc, direct) + . = ..() + if (internal_tank?.disconnect()) // Something moved us and broke connection + occupant_message("Air port connection has been severed!") + log_message("Lost connection to gas port.", LOG_MECHA) + +/obj/mecha/Process_Spacemove(var/movement_dir = 0) + . = ..() + if(.) + return TRUE + + var/atom/movable/backup = get_spacemove_backup() + if(backup) + if(istype(backup) && movement_dir && !backup.anchored) + if(backup.newtonian_move(turn(movement_dir, 180))) + step_silent = TRUE + if(occupant) + to_chat(occupant, "You push off [backup] to propel yourself.") + return TRUE + + if(can_move <= world.time && active_thrusters && movement_dir && active_thrusters.thrust(movement_dir)) + step_silent = TRUE + return TRUE + + return FALSE + +/obj/mecha/relaymove(mob/user,direction) + if(completely_disabled) + return + if(!direction) + return + if(user != occupant) //While not "realistic", this piece is player friendly. + user.forceMove(get_turf(src)) + to_chat(user, "You climb out from [src].") + return 0 + if(internal_tank?.connected_port) + if(world.time - last_message > 20) + occupant_message("Unable to move while connected to the air system port!") + last_message = world.time + return 0 + if(construction_state) + if(world.time - last_message > 20) + occupant_message("Maintenance protocols in effect.") + last_message = world.time + return + return domove(direction) + +/obj/mecha/proc/domove(direction) + if(can_move >= world.time) + return 0 + if(!Process_Spacemove(direction)) + return 0 + if(!has_charge(step_energy_drain)) + return 0 + if(zoom_mode) + if(world.time - last_message > 20) + occupant_message("Unable to move while in zoom mode!") + last_message = world.time + return 0 + if(!cell) + if(world.time - last_message > 20) + occupant_message("Missing power cell.") + last_message = world.time + return 0 + if(!scanmod || !capacitor) + if(world.time - last_message > 20) + occupant_message("Missing [scanmod? "capacitor" : "scanning module"].") + last_message = world.time + return 0 + + var/move_result = 0 + var/oldloc = loc + if(internal_damage & MECHA_INT_CONTROL_LOST) + move_result = mechsteprand() + else if(dir != direction && (!strafe || occupant.client.keys_held["Alt"])) + move_result = mechturn(direction) + else + move_result = mechstep(direction) + if(move_result || loc != oldloc)// halfway done diagonal move still returns false + use_power(step_energy_drain) + can_move = world.time + step_in + return 1 + return 0 + +/obj/mecha/proc/mechturn(direction) + setDir(direction) + if(turnsound) + playsound(src,turnsound,40,TRUE) + return 1 + +/obj/mecha/proc/mechstep(direction) + var/current_dir = dir + . = step(src,direction) + if(strafe) + setDir(current_dir) + if(. && !step_silent) + play_stepsound() + step_silent = FALSE + +/obj/mecha/proc/mechsteprand() + . = step_rand(src) + if(. && !step_silent) + play_stepsound() + step_silent = FALSE + +/obj/mecha/Bump(var/atom/obstacle) + if(phasing && get_charge() >= phasing_energy_drain && !throwing) + if(!can_move) + return + can_move = 0 + if(phase_state) + flick(phase_state, src) + forceMove(get_step(src,dir)) + use_power(phasing_energy_drain) + addtimer(VARSET_CALLBACK(src, can_move, TRUE), step_in*3) + else + if(..()) //mech was thrown + return + if(bumpsmash && occupant) //Need a pilot to push the PUNCH button. + if(nextsmash < world.time) + obstacle.mech_melee_attack(src) + nextsmash = world.time + smashcooldown + if(!obstacle || obstacle.CanPass(src,get_step(src,dir))) + step(src,dir) + if(isobj(obstacle)) + var/obj/O = obstacle + if(!O.anchored && O.move_resist <= move_force) + step(obstacle, dir) + else if(ismob(obstacle)) + var/mob/M = obstacle + if(M.move_resist <= move_force) + step(obstacle, dir) + + + + + +/////////////////////////////////// +//////// Internal damage //////// +/////////////////////////////////// + +/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) + if(!islist(possible_int_damage) || !length(possible_int_damage)) + return + if(prob(20)) + if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) + for(var/T in possible_int_damage) + if(internal_damage & T) + possible_int_damage -= T + if (length(possible_int_damage)) + var/int_dam_flag = pick(possible_int_damage) + if(int_dam_flag) + setInternalDamage(int_dam_flag) + if(prob(5)) + if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) + if (length(equipment)) + var/obj/item/mecha_parts/mecha_equipment/ME = pick(equipment) + qdel(ME) + +/obj/mecha/proc/setInternalDamage(int_dam_flag) + internal_damage |= int_dam_flag + log_message("Internal damage of type [int_dam_flag].", LOG_MECHA) + SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) + diag_hud_set_mechstat() + +/obj/mecha/proc/clearInternalDamage(int_dam_flag) + if(internal_damage & int_dam_flag) + switch(int_dam_flag) + if(MECHA_INT_TEMP_CONTROL) + occupant_message("Life support system reactivated.") + if(MECHA_INT_FIRE) + occupant_message("Internal fire extinguished.") + if(MECHA_INT_TANK_BREACH) + occupant_message("Damaged internal tank has been sealed.") + internal_damage &= ~int_dam_flag + diag_hud_set_mechstat() + +///////////////////////////////////// +//////////// AI piloting //////////// +///////////////////////////////////// + +/obj/mecha/attack_ai(mob/living/silicon/ai/user) + if(!isAI(user)) + return + //Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot. + if(user.can_dominate_mechs) + examine(user) //Get diagnostic information! + for(var/obj/item/mecha_parts/mecha_tracking/B in trackers) + to_chat(user, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:") + to_chat(user, "[B.get_mecha_info()]") + break + //Nothing like a big, red link to make the player feel powerful! + to_chat(user, "ASSUME DIRECT CONTROL?
                    ") + else + examine(user) + if(occupant) + to_chat(user, "This exosuit has a pilot and cannot be controlled.") + return + var/can_control_mech = 0 + for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) + can_control_mech = 1 + to_chat(user, "[icon2html(src, user)] Status of [name]:\n[A.get_mecha_info()]") + break + if(!can_control_mech) + to_chat(user, "You cannot control exosuits without AI control beacons installed.") + return + to_chat(user, "Take control of exosuit?
                    ") + +/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(!..()) + return + + //Transfer from core or card to mech. Proc is called by mech. + switch(interaction) + if(AI_TRANS_TO_CARD) //Upload AI from mech to AI card. + if(!construction_state) //Mech must be in maint mode to allow carding. + to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.") + return + AI = occupant + if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot + to_chat(user, "No AI detected in the [name] onboard computer.") + return + AI.ai_restore_power()//So the AI initially has power. + AI.control_disabled = TRUE + AI.radio_enabled = FALSE + AI.disconnect_shell() + RemoveActions(AI, TRUE) + occupant = null + silicon_pilot = FALSE + AI.forceMove(card) + card.AI = AI + AI.controlled_mech = null + AI.remote_control = null + icon_state = initial(icon_state)+"-open" + to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") + + if(AI_MECH_HACK) //Called by AIs on the mech + AI.linked_core = new /obj/structure/ai_core/deactivated(AI.loc) + if(AI.can_dominate_mechs) + if(occupant) //Oh, I am sorry, were you using that? + to_chat(AI, "Pilot detected! Forced ejection initiated!") + to_chat(occupant, "You have been forcibly ejected!") + go_out(1) //IT IS MINE, NOW. SUCK IT, RD! + ai_enter_mech(AI, interaction) + + if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech. + AI = card.AI + if(!AI) + to_chat(user, "There is no AI currently installed on this device.") + return + if(AI.deployed_shell) //Recall AI if shelled so it can be checked for a client + AI.disconnect_shell() + if(AI.stat || !AI.client) + to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") + return + if(occupant || dna_lock) //Normal AIs cannot steal mechs! + to_chat(user, "Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].") + return + AI.control_disabled = FALSE + AI.radio_enabled = TRUE + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + ai_enter_mech(AI, interaction) + +//Hack and From Card interactions share some code, so leave that here for both to use. +/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) + AI.ai_restore_power() + AI.forceMove(src) + occupant = AI + silicon_pilot = TRUE + icon_state = initial(icon_state) + update_icon() + playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + AI.cancel_camera() + AI.controlled_mech = src + AI.remote_control = src + AI.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. + AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. + to_chat(AI, AI.can_dominate_mechs ? "Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!" :\ + "You have been uploaded to a mech's onboard computer.") + to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") + if(interaction == AI_TRANS_FROM_CARD) + GrantActions(AI, FALSE) //No eject/return to core action for AI uploaded by card + else + GrantActions(AI, !AI.can_dominate_mechs) + + +//An actual AI (simple_animal mecha pilot) entering the mech +/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(pilot_mob && pilot_mob.Adjacent(src)) + if(occupant) + return + icon_state = initial(icon_state) + occupant = pilot_mob + pilot_mob.mecha = src + pilot_mob.forceMove(src) + GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob + +/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(occupant == pilot_mob) + occupant = null + if(pilot_mob.mecha == src) + pilot_mob.mecha = null + icon_state = "[initial(icon_state)]-open" + pilot_mob.forceMove(get_turf(src)) + RemoveActions(pilot_mob) + + +///////////////////////////////////// +//////// Atmospheric stuff //////// +///////////////////////////////////// + +/obj/mecha/remove_air(amount) + if(use_internal_tank) + return cabin_air.remove(amount) + return ..() + +/obj/mecha/return_air() + if(use_internal_tank) + return cabin_air + return ..() + +/obj/mecha/return_analyzable_air() + return cabin_air + +/obj/mecha/proc/return_pressure() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_pressure() + +/obj/mecha/return_temperature() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_temperature() + +/obj/mecha/MouseDrop_T(mob/M, mob/user) + if((user != M) || user.incapacitated() || !Adjacent(user)) + return + if(!ishuman(user)) // no silicons or drones in mechas. + return + if(HAS_TRAIT(user, TRAIT_PRIMITIVE)) //no lavalizards either. + to_chat(user, "The knowledge to use this device eludes you!") + return + log_message("[user] tries to move in.", LOG_MECHA) + if (occupant) + to_chat(usr, "The [name] is already occupied!") + log_message("Permission denied (Occupied).", LOG_MECHA) + return + if(dna_lock) + var/passed = FALSE + if(user.has_dna()) + var/mob/living/carbon/C = user + if(C.dna.unique_enzymes==dna_lock) + passed = TRUE + if (!passed) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + log_message("Permission denied (DNA LOCK).", LOG_MECHA) + return + if(!operation_allowed(user)) + to_chat(user, "Access denied. Insufficient operation keycodes.") + log_message("Permission denied (No keycode).", LOG_MECHA) + return + if(user.buckled) + to_chat(user, "You are currently buckled and cannot move.") + log_message("Permission denied (Buckled).", LOG_MECHA) + return + if(user.has_buckled_mobs()) //mob attached to us + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + log_message("Permission denied (Attached mobs).", LOG_MECHA) + return + + visible_message("[user] starts to climb into [name].") + + if(do_after(user, enter_delay, target = src)) + if(obj_integrity <= 0) + to_chat(user, "You cannot get in the [name], it has been destroyed!") + else if(occupant) + to_chat(user, "[occupant] was faster! Try better next time, loser.") + else if(user.buckled) + to_chat(user, "You can't enter the exosuit while buckled.") + else if(user.has_buckled_mobs()) + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + else + moved_inside(user) + else + to_chat(user, "You stop entering the exosuit!") + +/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) + . = FALSE + if(H && H.client && (H in range(1))) + occupant = H + H.forceMove(src) + H.update_mouse_pointer() + add_fingerprint(H) + GrantActions(H, human_occupant=1) + forceMove(loc) + log_message("[H] moved in as pilot.", LOG_MECHA) + icon_state = initial(icon_state) + setDir(dir_in) + playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + return TRUE + +/obj/mecha/proc/mmi_move_inside(obj/item/mmi/M, mob/user) + . = FALSE + if(!M.brain_check(user)) + return + + var/mob/living/brain/B = M.brainmob + if(occupant) + to_chat(user, "Occupant detected!") + return + if(dna_lock && (!B.stored_dna || (dna_lock != B.stored_dna.unique_enzymes))) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + return + + visible_message("[user] starts to insert an MMI into [name].") + + if(do_after(user, 40, target = src)) + if(!occupant) + return mmi_moved_inside(M, user) + else + to_chat(user, "Occupant detected!") + else + to_chat(user, "You stop inserting the MMI.") + +/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/M, mob/user) + if(!(Adjacent(M) && Adjacent(user))) + return FALSE + if(!M.brain_check(user)) + return FALSE + + var/mob/living/brain/B = M.brainmob + if(!user.transferItemToLoc(M, src)) + to_chat(user, "\the [M] is stuck to your hand, you cannot put it in \the [src]!") + return FALSE + + M.mecha = src + occupant = B + silicon_pilot = TRUE + B.forceMove(src) //should allow relaymove + B.reset_perspective(src) + B.remote_control = src + B.update_mobility() + B.update_mouse_pointer() + icon_state = initial(icon_state) + update_icon() + setDir(dir_in) + log_message("[M] moved in as pilot.", LOG_MECHA) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + GrantActions(B) + log_game("[key_name(user)] has put the MMI/posibrain of [key_name(B)] into [src] at [AREACOORD(src)]") + return TRUE + +/obj/mecha/container_resist(mob/living/user) + is_currently_ejecting = TRUE + to_chat(occupant, "You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.") + if(do_after(occupant, has_gravity() ? exit_delay : 0 , target = src)) + to_chat(occupant, "You exit the mech.") + go_out() + else + to_chat(occupant, "You stop exiting the mech. Weapons are enabled again.") + is_currently_ejecting = FALSE + +/obj/mecha/Exited(atom/movable/M, atom/newloc) + if(occupant && occupant == M) // The occupant exited the mech without calling go_out() + go_out(TRUE, newloc) + + if(cell && cell == M) + cell = null + return + if(scanmod && scanmod == M) + scanmod = null + update_part_values() + return + if(capacitor && capacitor == M) + armor = armor.modifyRating(energy = (capacitor.rating * -5)) //lose the energy armor if we lose this cap + capacitor = null + update_part_values() + +/obj/mecha/proc/go_out(forced, atom/newloc = loc) + if(!occupant) + return + var/atom/movable/mob_container + occupant.clear_alert("charge") + occupant.clear_alert("mech damage") + if(ishuman(occupant)) + mob_container = occupant + RemoveActions(occupant, human_occupant=1) + else if(isbrain(occupant)) + var/mob/living/brain/brain = occupant + RemoveActions(brain) + mob_container = brain.container + else if(isAI(occupant)) + var/mob/living/silicon/ai/AI = occupant + if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. + RemoveActions(occupant) + occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. + occupant = null + silicon_pilot = FALSE + return + else + if(!AI.linked_core) + to_chat(AI, "Inactive core destroyed. Unable to return.") + AI.linked_core = null + return + to_chat(AI, "Returning to core...") + AI.controlled_mech = null + AI.remote_control = null + RemoveActions(occupant, 1) + mob_container = AI + newloc = get_turf(AI.linked_core) + qdel(AI.linked_core) + else + return + var/mob/living/L = occupant + occupant = null //we need it null when forceMove calls Exited(). + silicon_pilot = FALSE + if(mob_container.forceMove(newloc))//ejecting mob container + log_message("[mob_container] moved out.", LOG_MECHA) + L << browse(null, "window=exosuit") + + if(istype(mob_container, /obj/item/mmi)) + var/obj/item/mmi/mmi = mob_container + if(mmi.brainmob) + L.forceMove(mmi) + L.reset_perspective() + mmi.mecha = null + mmi.update_icon() + L.mobility_flags = NONE + icon_state = initial(icon_state)+"-open" + setDir(dir_in) + + if(L && L.client) + L.update_mouse_pointer() + L.client.view_size.resetToDefault() + zoom_mode = 0 + +///////////////////////// +////// Access stuff ///// +///////////////////////// + +/obj/mecha/proc/operation_allowed(mob/M) + req_access = operation_req_access + req_one_access = list() + return allowed(M) + +/obj/mecha/proc/internals_access_allowed(mob/M) + req_one_access = internals_req_access + req_access = list() + return allowed(M) + + + +//////////////////////////////// +/////// Messages and Log /////// +//////////////////////////////// + +/obj/mecha/proc/occupant_message(message as text) + if(message) + if(occupant && occupant.client) + to_chat(occupant, "[icon2html(src, occupant)] [message]") + +GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) +GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? + +/////////////////////// +///// Power stuff ///// +/////////////////////// + +/obj/mecha/proc/has_charge(amount) + return (get_charge()>=amount) + +/obj/mecha/proc/get_charge() + for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) + var/relay_charge = R.get_charge() + if(relay_charge) + return relay_charge + if(cell) + return max(0, cell.charge) + +/obj/mecha/proc/use_power(amount) + if(get_charge() && cell.use(amount)) + return 1 + return 0 + +/obj/mecha/proc/give_power(amount) + if(!isnull(get_charge())) + cell.give(amount) + return 1 + return 0 + +/obj/mecha/update_remote_sight(mob/living/user) + if(occupant_sight_flags) + if(user == occupant) + user.sight |= occupant_sight_flags + +/////////////////////// +////// Ammo stuff ///// +/////////////////////// + +/obj/mecha/proc/ammo_resupply(var/obj/item/mecha_ammo/A, mob/user,var/fail_chat_override = FALSE) + if(!A.rounds) + if(!fail_chat_override) + to_chat(user, "This box of ammo is empty!") + return FALSE + var/ammo_needed + var/found_gun + for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in equipment) + ammo_needed = 0 + + if(istype(gun, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic) && gun.ammo_type == A.ammo_type) + found_gun = TRUE + if(A.direct_load) + ammo_needed = initial(gun.projectiles) - gun.projectiles + else + ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache + + if(ammo_needed) + if(ammo_needed < A.rounds) + if(A.direct_load) + gun.projectiles = gun.projectiles + ammo_needed + else + gun.projectiles_cache = gun.projectiles_cache + ammo_needed + playsound(get_turf(user),A.load_audio,50,TRUE) + to_chat(user, "You add [ammo_needed] [A.round_term][ammo_needed > 1?"s":""] to the [gun.name]") + A.rounds = A.rounds - ammo_needed + A.update_name() + return TRUE + + else + if(A.direct_load) + gun.projectiles = gun.projectiles + A.rounds + else + gun.projectiles_cache = gun.projectiles_cache + A.rounds + playsound(get_turf(user),A.load_audio,50,TRUE) + to_chat(user, "You add [A.rounds] [A.round_term][A.rounds > 1?"s":""] to the [gun.name]") + A.rounds = 0 + A.update_name() + return TRUE + if(!fail_chat_override) + if(found_gun) + to_chat(user, "You can't fit any more ammo of this type!") + else + to_chat(user, "None of the equipment on this exosuit can use this ammo!") + return FALSE diff --git a/code/game/mecha/mecha_construction_paths.dm b/code/game/mecha/mecha_construction_paths.dm index d6265da7615..c23dd70a8c9 100644 --- a/code/game/mecha/mecha_construction_paths.dm +++ b/code/game/mecha/mecha_construction_paths.dm @@ -1,1334 +1,1334 @@ -//////////////////////////////// -///// Construction datums ////// -//////////////////////////////// -/datum/component/construction/mecha - var/base_icon - - // Component typepaths. - // most must be defined unless - // get_steps is overriden. - - // Circuit board typepaths. - // circuit_control and circuit_periph must be defined - // unless get_circuit_steps is overriden. - var/circuit_control - var/circuit_periph - var/circuit_weapon - - // Armor plating typepaths. both must be defined - // unless relevant step procs are overriden. amounts - // must be defined if using /obj/item/stack/sheet types - var/inner_plating - var/inner_plating_amount - - var/outer_plating - var/outer_plating_amount - -/datum/component/construction/mecha/spawn_result() - if(!result) - return - // Remove default mech power cell, as we replace it with a new one. - var/obj/mecha/M = new result(drop_location()) - QDEL_NULL(M.cell) - QDEL_NULL(M.scanmod) - QDEL_NULL(M.capacitor) - - var/obj/item/mecha_parts/chassis/parent_chassis = parent - M.CheckParts(parent_chassis.contents) - - SSblackbox.record_feedback("tally", "mechas_created", 1, M.name) - QDEL_NULL(parent) - -// Default proc to generate mech steps. -// Override if the mech needs an entirely custom process (See HONK mech) -// Otherwise override specific steps as needed (Ripley, firefighter, Phazon) -/datum/component/construction/mecha/proc/get_steps() - return get_frame_steps() + get_circuit_steps() + (circuit_weapon ? get_circuit_weapon_steps() : list()) + get_stockpart_steps() + get_inner_plating_steps() + get_outer_plating_steps() - -/datum/component/construction/mecha/update_parent(step_index) - steps = get_steps() - ..() - // By default, each step in mech construction has a single icon_state: - // "[base_icon][index - 1]" - // For example, Ripley's step 1 icon_state is "ripley0". - var/atom/parent_atom = parent - if(!steps[index]["icon_state"] && base_icon) - parent_atom.icon_state = "[base_icon][index - 1]" - -/datum/component/construction/unordered/mecha_chassis/custom_action(obj/item/I, mob/living/user, typepath) - . = user.transferItemToLoc(I, parent) - if(.) - var/atom/parent_atom = parent - user.visible_message("[user] connects [I] to [parent].", "You connect [I] to [parent].") - parent_atom.add_overlay(I.icon_state+"+o") - qdel(I) - -/datum/component/construction/unordered/mecha_chassis/spawn_result() - var/atom/parent_atom = parent - parent_atom.icon = 'icons/mecha/mech_construction.dmi' - parent_atom.density = TRUE - parent_atom.cut_overlays() - ..() - -// Default proc for the first steps of mech construction. -/datum/component/construction/mecha/proc/get_frame_steps() - return list( - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ) - ) - -// Default proc for the circuit board steps of a mech. -// Second set of steps by default. -/datum/component/construction/mecha/proc/get_circuit_steps() - return list( - list( - "key" = circuit_control, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - list( - "key" = circuit_periph, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed." - ) - ) - -// Default proc for weapon circuitboard steps -// Used by combat mechs -/datum/component/construction/mecha/proc/get_circuit_weapon_steps() - return list( - list( - "key" = circuit_weapon, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Weapons control module is installed." - ) - ) - -// Default proc for stock part installation -// Third set of steps by default -/datum/component/construction/mecha/proc/get_stockpart_steps() - var/prevstep_text = circuit_weapon ? "Weapons control module is secured." : "Peripherals control module is secured." - return list( - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = prevstep_text - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ) - ) - -// Default proc for inner armor plating -// Fourth set of steps by default -/datum/component/construction/mecha/proc/get_inner_plating_steps() - var/list/first_step - if(ispath(inner_plating, /obj/item/stack/sheet)) - first_step = list( - list( - "key" = inner_plating, - "amount" = inner_plating_amount, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ) - ) - else - first_step = list( - list( - "key" = inner_plating, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ) - ) - - return first_step + list( - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Inner plating is installed." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Inner Plating is wrenched." - ) - ) - -// Default proc for outer armor plating -// Fifth set of steps by default -/datum/component/construction/mecha/proc/get_outer_plating_steps() - var/list/first_step - if(ispath(outer_plating, /obj/item/stack/sheet)) - first_step = list( - list( - "key" = outer_plating, - "amount" = outer_plating_amount, - "back_key" = TOOL_WELDER, - "desc" = "Inner plating is welded." - ) - ) - else - first_step = list( - list( - "key" = outer_plating, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Inner plating is welded." - ) - ) - - return first_step + list( - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ) - ) - - -/datum/component/construction/unordered/mecha_chassis/ripley - result = /datum/component/construction/mecha/ripley - steps = list( - /obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg - ) - -/datum/component/construction/mecha/ripley - result = /obj/mecha/working/ripley - base_icon = "ripley" - - circuit_control = /obj/item/circuitboard/mecha/ripley/main - circuit_periph = /obj/item/circuitboard/mecha/ripley/peripherals - - inner_plating=/obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating=/obj/item/stack/rods - outer_plating_amount = 10 - -/datum/component/construction/mecha/ripley/get_outer_plating_steps() - return list( - list( - "key" = /obj/item/stack/rods, - "amount" = 10, - "back_key" = TOOL_WELDER, - "desc" = "Outer Plating is welded." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WIRECUTTER, - "desc" = "Cockpit wire screen is installed." - ), - ) - -/datum/component/construction/mecha/ripley/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures [I].", "You secure [I].") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I].", "You install [I].") - else - user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/gygax - result = /datum/component/construction/mecha/gygax - steps = list( - /obj/item/mecha_parts/part/gygax_torso, - /obj/item/mecha_parts/part/gygax_left_arm, - /obj/item/mecha_parts/part/gygax_right_arm, - /obj/item/mecha_parts/part/gygax_left_leg, - /obj/item/mecha_parts/part/gygax_right_leg, - /obj/item/mecha_parts/part/gygax_head - ) - -/datum/component/construction/mecha/gygax - result = /obj/mecha/combat/gygax - base_icon = "gygax" - - circuit_control = /obj/item/circuitboard/mecha/gygax/main - circuit_periph = /obj/item/circuitboard/mecha/gygax/peripherals - circuit_weapon = /obj/item/circuitboard/mecha/gygax/targeting - - inner_plating = /obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating=/obj/item/mecha_parts/part/gygax_armor - outer_plating_amount=1 - -/datum/component/construction/mecha/gygax/action(datum/source, atom/used_atom, mob/user) - return check_step(used_atom,user) - -/datum/component/construction/mecha/gygax/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - else - user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(21) - if(diff==FORWARD) - user.visible_message("[user] secures Gygax Armor Plates.", "You secure Gygax Armor Plates.") - else - user.visible_message("[user] pries Gygax Armor Plates from [parent].", "You pry Gygax Armor Plates from [parent].") - if(22) - if(diff==FORWARD) - user.visible_message("[user] welds Gygax Armor Plates to [parent].", "You weld Gygax Armor Plates to [parent].") - else - user.visible_message("[user] unfastens Gygax Armor Plates.", "You unfasten Gygax Armor Plates.") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/clarke - result = /datum/component/construction/mecha/clarke - steps = list( - /obj/item/mecha_parts/part/clarke_torso, - /obj/item/mecha_parts/part/clarke_left_arm, - /obj/item/mecha_parts/part/clarke_right_arm, - /obj/item/mecha_parts/part/clarke_head - ) - -/datum/component/construction/mecha/clarke - result = /obj/mecha/working/clarke - base_icon = "clarke" - - circuit_control = /obj/item/circuitboard/mecha/clarke/main - circuit_periph = /obj/item/circuitboard/mecha/clarke/peripherals - - inner_plating = /obj/item/stack/sheet/plasteel - inner_plating_amount = 5 - - outer_plating = /obj/item/stack/sheet/mineral/gold - outer_plating_amount = 5 - -/datum/component/construction/mecha/clarke/get_frame_steps() - return list( - list( - "key" = /obj/item/stack/conveyor, - "amount" = 4, - "desc" = "The treads are added." - ), - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "The hydraulic systems are disconnected." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ) - ) - - - -/datum/component/construction/mecha/clarke/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] adds the tread systems.", "You add the tread systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - else - user.visible_message("[user] removes the tread systems.", "You remove the tread systems.") - - if(3) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(5) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(8) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(9) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(10) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(11) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(12) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(13) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(14) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(15) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(16) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(17) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(18) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(19) - if(diff==FORWARD) - user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") - if(21) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE - - -/datum/component/construction/unordered/mecha_chassis/honker - result = /datum/component/construction/mecha/honker - steps = list( - /obj/item/mecha_parts/part/honker_torso, - /obj/item/mecha_parts/part/honker_left_arm, - /obj/item/mecha_parts/part/honker_right_arm, - /obj/item/mecha_parts/part/honker_left_leg, - /obj/item/mecha_parts/part/honker_right_leg, - /obj/item/mecha_parts/part/honker_head - ) - -/datum/component/construction/mecha/honker - result = /obj/mecha/combat/honker - steps = list( - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/circuitboard/mecha/honker/main, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/circuitboard/mecha/honker/peripherals, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/circuitboard/mecha/honker/targeting, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/clothing/mask/gas/clown_hat, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/clothing/shoes/clown_shoes, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - ) - -/datum/component/construction/mecha/honker/get_steps() - return steps - -// HONK doesn't have any construction step icons, so we just set an icon once. -/datum/component/construction/mecha/honker/update_parent(step_index) - if(step_index == 1) - var/atom/parent_atom = parent - parent_atom.icon = 'icons/mecha/mech_construct.dmi' - parent_atom.icon_state = "honker_chassis" - ..() - -/datum/component/construction/mecha/honker/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - if(istype(I, /obj/item/bikehorn)) - playsound(parent, 'sound/items/bikehorn.ogg', 50, TRUE) - user.visible_message("HONK!") - - //TODO: better messages. - switch(index) - if(2) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(4) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(6) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(8) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(10) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(12) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(14) - user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") - if(16) - user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/durand - result = /datum/component/construction/mecha/durand - steps = list( - /obj/item/mecha_parts/part/durand_torso, - /obj/item/mecha_parts/part/durand_left_arm, - /obj/item/mecha_parts/part/durand_right_arm, - /obj/item/mecha_parts/part/durand_left_leg, - /obj/item/mecha_parts/part/durand_right_leg, - /obj/item/mecha_parts/part/durand_head - ) - -/datum/component/construction/mecha/durand - result = /obj/mecha/combat/durand - base_icon = "durand" - - circuit_control = /obj/item/circuitboard/mecha/durand/main - circuit_periph = /obj/item/circuitboard/mecha/durand/peripherals - circuit_weapon = /obj/item/circuitboard/mecha/durand/targeting - - inner_plating = /obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating = /obj/item/mecha_parts/part/durand_armor - outer_plating_amount = 1 - -/datum/component/construction/mecha/durand/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - else - user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(21) - if(diff==FORWARD) - user.visible_message("[user] secures Durand Armor Plates.", "You secure Durand Armor Plates.") - else - user.visible_message("[user] pries Durand Armor Plates from [parent].", "You pry Durand Armor Plates from [parent].") - if(22) - if(diff==FORWARD) - user.visible_message("[user] welds Durand Armor Plates to [parent].", "You weld Durand Armor Plates to [parent].") - else - user.visible_message("[user] unfastens Durand Armor Plates.", "You unfasten Durand Armor Plates.") - return TRUE - -//PHAZON - -/datum/component/construction/unordered/mecha_chassis/phazon - result = /datum/component/construction/mecha/phazon - steps = list( - /obj/item/mecha_parts/part/phazon_torso, - /obj/item/mecha_parts/part/phazon_left_arm, - /obj/item/mecha_parts/part/phazon_right_arm, - /obj/item/mecha_parts/part/phazon_left_leg, - /obj/item/mecha_parts/part/phazon_right_leg, - /obj/item/mecha_parts/part/phazon_head - ) - -/datum/component/construction/mecha/phazon - result = /obj/mecha/combat/phazon - base_icon = "phazon" - - circuit_control = /obj/item/circuitboard/mecha/phazon/main - circuit_periph = /obj/item/circuitboard/mecha/phazon/peripherals - circuit_weapon = /obj/item/circuitboard/mecha/phazon/targeting - - inner_plating = /obj/item/stack/sheet/plasteel - inner_plating_amount = 5 - - outer_plating = /obj/item/mecha_parts/part/phazon_armor - outer_plating_amount = 1 - -/datum/component/construction/mecha/phazon/get_stockpart_steps() - return list( - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Weapon control module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - list( - "key" = /obj/item/stack/ore/bluespace_crystal, - "amount" = 1, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_CROWBAR, - "desc" = "The bluespace crystal is installed." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WIRECUTTER, - "desc" = "The bluespace crystal is connected." - ), - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The bluespace crystal is engaged." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed.", - "icon_state" = "phazon17" - // This is the point where a step icon is skipped, so "icon_state" had to be set manually starting from here. - ) - ) - -/datum/component/construction/mecha/phazon/get_outer_plating_steps() - return list( - list( - "key" = outer_plating, - "amount" = 1, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - list( - "key" = /obj/item/assembly/signaler/anomaly/bluespace, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Bluespace anomaly core socket is open.", - "icon_state" = "phazon24" - ) - ) - -/datum/component/construction/mecha/phazon/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - else - user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs [I].", "You install [I].") - else - user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") - if(16) - if(diff==FORWARD) - user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystal.") - else - user.visible_message("[user] removes the bluespace crystal from [parent].", "You remove the bluespace crystal from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystal.") - else - user.visible_message("[user] disconnects the bluespace crystal from [parent].", "You disconnect the bluespace crystal from [parent].") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystal.") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs the phase armor layer to [parent].", "You install the phase armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(21) - if(diff==FORWARD) - user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") - else - user.visible_message("[user] pries the phase armor layer from [parent].", "You pry the phase armor layer from [parent].") - if(22) - if(diff==FORWARD) - user.visible_message("[user] welds the phase armor layer to [parent].", "You weld the phase armor layer to [parent].") - else - user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") - if(23) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] cuts phase armor layer from [parent].", "You cut the phase armor layer from [parent].") - if(24) - if(diff==FORWARD) - user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") - else - user.visible_message("[user] pries Phazon Armor Plates from [parent].", "You pry Phazon Armor Plates from [parent].") - if(25) - if(diff==FORWARD) - user.visible_message("[user] welds Phazon Armor Plates to [parent].", "You weld Phazon Armor Plates to [parent].") - else - user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") - if(26) - if(diff==FORWARD) - user.visible_message("[user] carefully inserts the bluespace anomaly core into [parent] and secures it.", - "You slowly place the bluespace anomaly core into its socket and close its chamber.") - return TRUE - -//ODYSSEUS - -/datum/component/construction/unordered/mecha_chassis/odysseus - result = /datum/component/construction/mecha/odysseus - steps = list( - /obj/item/mecha_parts/part/odysseus_torso, - /obj/item/mecha_parts/part/odysseus_head, - /obj/item/mecha_parts/part/odysseus_left_arm, - /obj/item/mecha_parts/part/odysseus_right_arm, - /obj/item/mecha_parts/part/odysseus_left_leg, - /obj/item/mecha_parts/part/odysseus_right_leg - ) - -/datum/component/construction/mecha/odysseus - result = /obj/mecha/medical/odysseus - base_icon = "odysseus" - - circuit_control = /obj/item/circuitboard/mecha/odysseus/main - circuit_periph = /obj/item/circuitboard/mecha/odysseus/peripherals - - inner_plating = /obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating = /obj/item/stack/sheet/plasteel - outer_plating_amount = 5 - -/datum/component/construction/mecha/odysseus/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE +//////////////////////////////// +///// Construction datums ////// +//////////////////////////////// +/datum/component/construction/mecha + var/base_icon + + // Component typepaths. + // most must be defined unless + // get_steps is overriden. + + // Circuit board typepaths. + // circuit_control and circuit_periph must be defined + // unless get_circuit_steps is overriden. + var/circuit_control + var/circuit_periph + var/circuit_weapon + + // Armor plating typepaths. both must be defined + // unless relevant step procs are overriden. amounts + // must be defined if using /obj/item/stack/sheet types + var/inner_plating + var/inner_plating_amount + + var/outer_plating + var/outer_plating_amount + +/datum/component/construction/mecha/spawn_result() + if(!result) + return + // Remove default mech power cell, as we replace it with a new one. + var/obj/mecha/M = new result(drop_location()) + QDEL_NULL(M.cell) + QDEL_NULL(M.scanmod) + QDEL_NULL(M.capacitor) + + var/obj/item/mecha_parts/chassis/parent_chassis = parent + M.CheckParts(parent_chassis.contents) + + SSblackbox.record_feedback("tally", "mechas_created", 1, M.name) + QDEL_NULL(parent) + +// Default proc to generate mech steps. +// Override if the mech needs an entirely custom process (See HONK mech) +// Otherwise override specific steps as needed (Ripley, firefighter, Phazon) +/datum/component/construction/mecha/proc/get_steps() + return get_frame_steps() + get_circuit_steps() + (circuit_weapon ? get_circuit_weapon_steps() : list()) + get_stockpart_steps() + get_inner_plating_steps() + get_outer_plating_steps() + +/datum/component/construction/mecha/update_parent(step_index) + steps = get_steps() + ..() + // By default, each step in mech construction has a single icon_state: + // "[base_icon][index - 1]" + // For example, Ripley's step 1 icon_state is "ripley0". + var/atom/parent_atom = parent + if(!steps[index]["icon_state"] && base_icon) + parent_atom.icon_state = "[base_icon][index - 1]" + +/datum/component/construction/unordered/mecha_chassis/custom_action(obj/item/I, mob/living/user, typepath) + . = user.transferItemToLoc(I, parent) + if(.) + var/atom/parent_atom = parent + user.visible_message("[user] connects [I] to [parent].", "You connect [I] to [parent].") + parent_atom.add_overlay(I.icon_state+"+o") + qdel(I) + +/datum/component/construction/unordered/mecha_chassis/spawn_result() + var/atom/parent_atom = parent + parent_atom.icon = 'icons/mecha/mech_construction.dmi' + parent_atom.density = TRUE + parent_atom.cut_overlays() + ..() + +// Default proc for the first steps of mech construction. +/datum/component/construction/mecha/proc/get_frame_steps() + return list( + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ) + ) + +// Default proc for the circuit board steps of a mech. +// Second set of steps by default. +/datum/component/construction/mecha/proc/get_circuit_steps() + return list( + list( + "key" = circuit_control, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + list( + "key" = circuit_periph, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed." + ) + ) + +// Default proc for weapon circuitboard steps +// Used by combat mechs +/datum/component/construction/mecha/proc/get_circuit_weapon_steps() + return list( + list( + "key" = circuit_weapon, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Weapons control module is installed." + ) + ) + +// Default proc for stock part installation +// Third set of steps by default +/datum/component/construction/mecha/proc/get_stockpart_steps() + var/prevstep_text = circuit_weapon ? "Weapons control module is secured." : "Peripherals control module is secured." + return list( + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = prevstep_text + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ) + ) + +// Default proc for inner armor plating +// Fourth set of steps by default +/datum/component/construction/mecha/proc/get_inner_plating_steps() + var/list/first_step + if(ispath(inner_plating, /obj/item/stack/sheet)) + first_step = list( + list( + "key" = inner_plating, + "amount" = inner_plating_amount, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ) + ) + else + first_step = list( + list( + "key" = inner_plating, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ) + ) + + return first_step + list( + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Inner plating is installed." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Inner Plating is wrenched." + ) + ) + +// Default proc for outer armor plating +// Fifth set of steps by default +/datum/component/construction/mecha/proc/get_outer_plating_steps() + var/list/first_step + if(ispath(outer_plating, /obj/item/stack/sheet)) + first_step = list( + list( + "key" = outer_plating, + "amount" = outer_plating_amount, + "back_key" = TOOL_WELDER, + "desc" = "Inner plating is welded." + ) + ) + else + first_step = list( + list( + "key" = outer_plating, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Inner plating is welded." + ) + ) + + return first_step + list( + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ) + ) + + +/datum/component/construction/unordered/mecha_chassis/ripley + result = /datum/component/construction/mecha/ripley + steps = list( + /obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg + ) + +/datum/component/construction/mecha/ripley + result = /obj/mecha/working/ripley + base_icon = "ripley" + + circuit_control = /obj/item/circuitboard/mecha/ripley/main + circuit_periph = /obj/item/circuitboard/mecha/ripley/peripherals + + inner_plating=/obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating=/obj/item/stack/rods + outer_plating_amount = 10 + +/datum/component/construction/mecha/ripley/get_outer_plating_steps() + return list( + list( + "key" = /obj/item/stack/rods, + "amount" = 10, + "back_key" = TOOL_WELDER, + "desc" = "Outer Plating is welded." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WIRECUTTER, + "desc" = "Cockpit wire screen is installed." + ), + ) + +/datum/component/construction/mecha/ripley/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures [I].", "You secure [I].") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I].", "You install [I].") + else + user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/gygax + result = /datum/component/construction/mecha/gygax + steps = list( + /obj/item/mecha_parts/part/gygax_torso, + /obj/item/mecha_parts/part/gygax_left_arm, + /obj/item/mecha_parts/part/gygax_right_arm, + /obj/item/mecha_parts/part/gygax_left_leg, + /obj/item/mecha_parts/part/gygax_right_leg, + /obj/item/mecha_parts/part/gygax_head + ) + +/datum/component/construction/mecha/gygax + result = /obj/mecha/combat/gygax + base_icon = "gygax" + + circuit_control = /obj/item/circuitboard/mecha/gygax/main + circuit_periph = /obj/item/circuitboard/mecha/gygax/peripherals + circuit_weapon = /obj/item/circuitboard/mecha/gygax/targeting + + inner_plating = /obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating=/obj/item/mecha_parts/part/gygax_armor + outer_plating_amount=1 + +/datum/component/construction/mecha/gygax/action(datum/source, atom/used_atom, mob/user) + return check_step(used_atom,user) + +/datum/component/construction/mecha/gygax/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + else + user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures Gygax Armor Plates.", "You secure Gygax Armor Plates.") + else + user.visible_message("[user] pries Gygax Armor Plates from [parent].", "You pry Gygax Armor Plates from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds Gygax Armor Plates to [parent].", "You weld Gygax Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Gygax Armor Plates.", "You unfasten Gygax Armor Plates.") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/clarke + result = /datum/component/construction/mecha/clarke + steps = list( + /obj/item/mecha_parts/part/clarke_torso, + /obj/item/mecha_parts/part/clarke_left_arm, + /obj/item/mecha_parts/part/clarke_right_arm, + /obj/item/mecha_parts/part/clarke_head + ) + +/datum/component/construction/mecha/clarke + result = /obj/mecha/working/clarke + base_icon = "clarke" + + circuit_control = /obj/item/circuitboard/mecha/clarke/main + circuit_periph = /obj/item/circuitboard/mecha/clarke/peripherals + + inner_plating = /obj/item/stack/sheet/plasteel + inner_plating_amount = 5 + + outer_plating = /obj/item/stack/sheet/mineral/gold + outer_plating_amount = 5 + +/datum/component/construction/mecha/clarke/get_frame_steps() + return list( + list( + "key" = /obj/item/stack/conveyor, + "amount" = 4, + "desc" = "The treads are added." + ), + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "The hydraulic systems are disconnected." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ) + ) + + + +/datum/component/construction/mecha/clarke/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] adds the tread systems.", "You add the tread systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + else + user.visible_message("[user] removes the tread systems.", "You remove the tread systems.") + + if(3) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(5) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(8) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(9) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(10) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(11) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(12) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(13) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(14) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(15) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(16) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(17) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(18) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(19) + if(diff==FORWARD) + user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE + + +/datum/component/construction/unordered/mecha_chassis/honker + result = /datum/component/construction/mecha/honker + steps = list( + /obj/item/mecha_parts/part/honker_torso, + /obj/item/mecha_parts/part/honker_left_arm, + /obj/item/mecha_parts/part/honker_right_arm, + /obj/item/mecha_parts/part/honker_left_leg, + /obj/item/mecha_parts/part/honker_right_leg, + /obj/item/mecha_parts/part/honker_head + ) + +/datum/component/construction/mecha/honker + result = /obj/mecha/combat/honker + steps = list( + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/circuitboard/mecha/honker/main, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/circuitboard/mecha/honker/peripherals, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/circuitboard/mecha/honker/targeting, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/clothing/mask/gas/clown_hat, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/clothing/shoes/clown_shoes, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + ) + +/datum/component/construction/mecha/honker/get_steps() + return steps + +// HONK doesn't have any construction step icons, so we just set an icon once. +/datum/component/construction/mecha/honker/update_parent(step_index) + if(step_index == 1) + var/atom/parent_atom = parent + parent_atom.icon = 'icons/mecha/mech_construct.dmi' + parent_atom.icon_state = "honker_chassis" + ..() + +/datum/component/construction/mecha/honker/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + if(istype(I, /obj/item/bikehorn)) + playsound(parent, 'sound/items/bikehorn.ogg', 50, TRUE) + user.visible_message("HONK!") + + //TODO: better messages. + switch(index) + if(2) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(4) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(6) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(8) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(10) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(12) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(14) + user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") + if(16) + user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/durand + result = /datum/component/construction/mecha/durand + steps = list( + /obj/item/mecha_parts/part/durand_torso, + /obj/item/mecha_parts/part/durand_left_arm, + /obj/item/mecha_parts/part/durand_right_arm, + /obj/item/mecha_parts/part/durand_left_leg, + /obj/item/mecha_parts/part/durand_right_leg, + /obj/item/mecha_parts/part/durand_head + ) + +/datum/component/construction/mecha/durand + result = /obj/mecha/combat/durand + base_icon = "durand" + + circuit_control = /obj/item/circuitboard/mecha/durand/main + circuit_periph = /obj/item/circuitboard/mecha/durand/peripherals + circuit_weapon = /obj/item/circuitboard/mecha/durand/targeting + + inner_plating = /obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating = /obj/item/mecha_parts/part/durand_armor + outer_plating_amount = 1 + +/datum/component/construction/mecha/durand/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + else + user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures Durand Armor Plates.", "You secure Durand Armor Plates.") + else + user.visible_message("[user] pries Durand Armor Plates from [parent].", "You pry Durand Armor Plates from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds Durand Armor Plates to [parent].", "You weld Durand Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Durand Armor Plates.", "You unfasten Durand Armor Plates.") + return TRUE + +//PHAZON + +/datum/component/construction/unordered/mecha_chassis/phazon + result = /datum/component/construction/mecha/phazon + steps = list( + /obj/item/mecha_parts/part/phazon_torso, + /obj/item/mecha_parts/part/phazon_left_arm, + /obj/item/mecha_parts/part/phazon_right_arm, + /obj/item/mecha_parts/part/phazon_left_leg, + /obj/item/mecha_parts/part/phazon_right_leg, + /obj/item/mecha_parts/part/phazon_head + ) + +/datum/component/construction/mecha/phazon + result = /obj/mecha/combat/phazon + base_icon = "phazon" + + circuit_control = /obj/item/circuitboard/mecha/phazon/main + circuit_periph = /obj/item/circuitboard/mecha/phazon/peripherals + circuit_weapon = /obj/item/circuitboard/mecha/phazon/targeting + + inner_plating = /obj/item/stack/sheet/plasteel + inner_plating_amount = 5 + + outer_plating = /obj/item/mecha_parts/part/phazon_armor + outer_plating_amount = 1 + +/datum/component/construction/mecha/phazon/get_stockpart_steps() + return list( + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Weapon control module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + list( + "key" = /obj/item/stack/ore/bluespace_crystal, + "amount" = 1, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_CROWBAR, + "desc" = "The bluespace crystal is installed." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WIRECUTTER, + "desc" = "The bluespace crystal is connected." + ), + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The bluespace crystal is engaged." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed.", + "icon_state" = "phazon17" + // This is the point where a step icon is skipped, so "icon_state" had to be set manually starting from here. + ) + ) + +/datum/component/construction/mecha/phazon/get_outer_plating_steps() + return list( + list( + "key" = outer_plating, + "amount" = 1, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + list( + "key" = /obj/item/assembly/signaler/anomaly/bluespace, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Bluespace anomaly core socket is open.", + "icon_state" = "phazon24" + ) + ) + +/datum/component/construction/mecha/phazon/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + else + user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I].", "You install [I].") + else + user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") + if(16) + if(diff==FORWARD) + user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystal.") + else + user.visible_message("[user] removes the bluespace crystal from [parent].", "You remove the bluespace crystal from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystal.") + else + user.visible_message("[user] disconnects the bluespace crystal from [parent].", "You disconnect the bluespace crystal from [parent].") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystal.") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs the phase armor layer to [parent].", "You install the phase armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") + else + user.visible_message("[user] pries the phase armor layer from [parent].", "You pry the phase armor layer from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds the phase armor layer to [parent].", "You weld the phase armor layer to [parent].") + else + user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") + if(23) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts phase armor layer from [parent].", "You cut the phase armor layer from [parent].") + if(24) + if(diff==FORWARD) + user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") + else + user.visible_message("[user] pries Phazon Armor Plates from [parent].", "You pry Phazon Armor Plates from [parent].") + if(25) + if(diff==FORWARD) + user.visible_message("[user] welds Phazon Armor Plates to [parent].", "You weld Phazon Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") + if(26) + if(diff==FORWARD) + user.visible_message("[user] carefully inserts the bluespace anomaly core into [parent] and secures it.", + "You slowly place the bluespace anomaly core into its socket and close its chamber.") + return TRUE + +//ODYSSEUS + +/datum/component/construction/unordered/mecha_chassis/odysseus + result = /datum/component/construction/mecha/odysseus + steps = list( + /obj/item/mecha_parts/part/odysseus_torso, + /obj/item/mecha_parts/part/odysseus_head, + /obj/item/mecha_parts/part/odysseus_left_arm, + /obj/item/mecha_parts/part/odysseus_right_arm, + /obj/item/mecha_parts/part/odysseus_left_leg, + /obj/item/mecha_parts/part/odysseus_right_leg + ) + +/datum/component/construction/mecha/odysseus + result = /obj/mecha/medical/odysseus + base_icon = "odysseus" + + circuit_control = /obj/item/circuitboard/mecha/odysseus/main + circuit_periph = /obj/item/circuitboard/mecha/odysseus/peripherals + + inner_plating = /obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating = /obj/item/stack/sheet/plasteel + outer_plating_amount = 5 + +/datum/component/construction/mecha/odysseus/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm index 9c52797129e..c7db051331b 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -1,163 +1,163 @@ -/obj/machinery/computer/mecha - name = "exosuit control console" - desc = "Used to remotely locate or lockdown exosuits." - icon_screen = "mecha" - icon_keyboard = "tech_key" - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/computer/mecha_control - ui_x = 500 - ui_y = 500 - -/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "ExosuitControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/mecha/ui_data(mob/user) - var/list/data = list() - - var/list/trackerlist = list() - for(var/obj/mecha/MC in GLOB.mechas_list) - trackerlist += MC.trackers - - data["mechs"] = list() - for(var/obj/item/mecha_parts/mecha_tracking/MT in trackerlist) - if(!MT.chassis) - continue - var/obj/mecha/M = MT.chassis - var/list/mech_data = list( - name = M.name, - integrity = round((M.obj_integrity / M.max_integrity) * 100), - charge = M.cell ? round(M.cell.percent()) : null, - airtank = M.internal_tank ? M.return_pressure() : null, - pilot = M.occupant, - location = get_area_name(M, TRUE), - active_equipment = M.selected, - emp_recharging = MT.recharging, - tracker_ref = REF(MT) - ) - if(istype(M, /obj/mecha/working/ripley)) - var/obj/mecha/working/ripley/RM = M - mech_data += list( - cargo_space = round((RM.cargo.len / RM.cargo_capacity) * 100) - ) - - data["mechs"] += list(mech_data) - - return data - -/obj/machinery/computer/mecha/ui_act(action, params) - if(..()) - return - - switch(action) - if("send_message") - var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) - if(!istype(MT)) - return - var/message = stripped_input(usr, "Input message", "Transmit message") - var/obj/mecha/M = MT.chassis - if(trim(message) && M) - M.occupant_message(message) - to_chat(usr, "Message sent.") - . = TRUE - if("shock") - var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) - if(!istype(MT)) - return - var/obj/mecha/M = MT.chassis - if(M) - MT.shock() - log_game("[key_name(usr)] has activated remote EMP on exosuit [M], located at [loc_name(M)], which is currently [M.occupant? "being piloted by [key_name(M.occupant)]." : "without a pilot."] ") - message_admins("[key_name_admin(usr)][ADMIN_FLW(usr)] has activated remote EMP on exosuit [M][ADMIN_JMP(M)], which is currently [M.occupant ? "being piloted by [key_name_admin(M.occupant)][ADMIN_FLW(M.occupant)]." : "without a pilot."] ") - . = TRUE - -/obj/item/mecha_parts/mecha_tracking - name = "exosuit tracking beacon" - desc = "Device used to transmit exosuit data." - icon = 'icons/obj/device.dmi' - icon_state = "motion2" - w_class = WEIGHT_CLASS_SMALL - /// If this beacon allows for AI control. Exists to avoid using istype() on checking - var/ai_beacon = FALSE - /// Cooldown variable for EMP pulsing - var/recharging = FALSE - /// The Mecha that this tracking beacon is attached to - var/obj/mecha/chassis - -/** - * Returns a html formatted string describing attached mech status - */ -/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() - if(!chassis) - return FALSE - - var/cell_charge = chassis.get_charge() - var/answer = {"Name: [chassis.name]
                    - Integrity: [round((chassis.obj_integrity/chassis.max_integrity * 100), 0.01)]%
                    - Cell Charge: [isnull(cell_charge) ? "Not Found":"[chassis.cell.percent()]%"]
                    - Airtank: [chassis.internal_tank ? "[round(chassis.return_pressure(), 0.01)]" : "Not Equipped"] kPa
                    - Pilot: [chassis.occupant || "None"]
                    - Location: [get_area_name(chassis, TRUE) || "Unknown"]
                    - Active Equipment: [chassis.selected || "None"]"} - if(istype(chassis, /obj/mecha/working/ripley)) - var/obj/mecha/working/ripley/RM = chassis - answer += "
                    Used Cargo Space: [round((RM.cargo.len / RM.cargo_capacity * 100), 0.01)]%" - - return answer - -/obj/item/mecha_parts/mecha_tracking/emp_act() - . = ..() - if(!(. & EMP_PROTECT_SELF)) - qdel(src) - -/obj/item/mecha_parts/mecha_tracking/Destroy() - if(chassis) - if(src in chassis.trackers) - chassis.trackers -= src - chassis = null - return ..() - -/obj/item/mecha_parts/mecha_tracking/try_attach_part(mob/user, obj/mecha/M) - if(!..()) - return - M.trackers += src - M.diag_hud_set_mechtracking() - chassis = M - -/** - * Attempts to EMP mech that the tracker is attached to, if there is one and tracker is not on cooldown - */ -/obj/item/mecha_parts/mecha_tracking/proc/shock() - if(recharging) - return - if(chassis) - chassis.emp_act(EMP_HEAVY) - addtimer(CALLBACK(src, /obj/item/mecha_parts/mecha_tracking/proc/recharge), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) - recharging = TRUE - -/** - * Resets recharge variable, allowing tracker to be EMP pulsed again - */ -/obj/item/mecha_parts/mecha_tracking/proc/recharge() - recharging = FALSE - -/obj/item/mecha_parts/mecha_tracking/ai_control - name = "exosuit AI control beacon" - desc = "A device used to transmit exosuit data. Also allows active AI units to take control of said exosuit." - ai_beacon = TRUE - -/obj/item/storage/box/mechabeacons - name = "exosuit tracking beacons" - -/obj/item/storage/box/mechabeacons/PopulateContents() - ..() - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) +/obj/machinery/computer/mecha + name = "exosuit control console" + desc = "Used to remotely locate or lockdown exosuits." + icon_screen = "mecha" + icon_keyboard = "tech_key" + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/computer/mecha_control + ui_x = 500 + ui_y = 500 + +/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "ExosuitControlConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/mecha/ui_data(mob/user) + var/list/data = list() + + var/list/trackerlist = list() + for(var/obj/mecha/MC in GLOB.mechas_list) + trackerlist += MC.trackers + + data["mechs"] = list() + for(var/obj/item/mecha_parts/mecha_tracking/MT in trackerlist) + if(!MT.chassis) + continue + var/obj/mecha/M = MT.chassis + var/list/mech_data = list( + name = M.name, + integrity = round((M.obj_integrity / M.max_integrity) * 100), + charge = M.cell ? round(M.cell.percent()) : null, + airtank = M.internal_tank ? M.return_pressure() : null, + pilot = M.occupant, + location = get_area_name(M, TRUE), + active_equipment = M.selected, + emp_recharging = MT.recharging, + tracker_ref = REF(MT) + ) + if(istype(M, /obj/mecha/working/ripley)) + var/obj/mecha/working/ripley/RM = M + mech_data += list( + cargo_space = round((RM.cargo.len / RM.cargo_capacity) * 100) + ) + + data["mechs"] += list(mech_data) + + return data + +/obj/machinery/computer/mecha/ui_act(action, params) + if(..()) + return + + switch(action) + if("send_message") + var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) + if(!istype(MT)) + return + var/message = stripped_input(usr, "Input message", "Transmit message") + var/obj/mecha/M = MT.chassis + if(trim(message) && M) + M.occupant_message(message) + to_chat(usr, "Message sent.") + . = TRUE + if("shock") + var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) + if(!istype(MT)) + return + var/obj/mecha/M = MT.chassis + if(M) + MT.shock() + log_game("[key_name(usr)] has activated remote EMP on exosuit [M], located at [loc_name(M)], which is currently [M.occupant? "being piloted by [key_name(M.occupant)]." : "without a pilot."] ") + message_admins("[key_name_admin(usr)][ADMIN_FLW(usr)] has activated remote EMP on exosuit [M][ADMIN_JMP(M)], which is currently [M.occupant ? "being piloted by [key_name_admin(M.occupant)][ADMIN_FLW(M.occupant)]." : "without a pilot."] ") + . = TRUE + +/obj/item/mecha_parts/mecha_tracking + name = "exosuit tracking beacon" + desc = "Device used to transmit exosuit data." + icon = 'icons/obj/device.dmi' + icon_state = "motion2" + w_class = WEIGHT_CLASS_SMALL + /// If this beacon allows for AI control. Exists to avoid using istype() on checking + var/ai_beacon = FALSE + /// Cooldown variable for EMP pulsing + var/recharging = FALSE + /// The Mecha that this tracking beacon is attached to + var/obj/mecha/chassis + +/** + * Returns a html formatted string describing attached mech status + */ +/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() + if(!chassis) + return FALSE + + var/cell_charge = chassis.get_charge() + var/answer = {"Name: [chassis.name]
                    + Integrity: [round((chassis.obj_integrity/chassis.max_integrity * 100), 0.01)]%
                    + Cell Charge: [isnull(cell_charge) ? "Not Found":"[chassis.cell.percent()]%"]
                    + Airtank: [chassis.internal_tank ? "[round(chassis.return_pressure(), 0.01)]" : "Not Equipped"] kPa
                    + Pilot: [chassis.occupant || "None"]
                    + Location: [get_area_name(chassis, TRUE) || "Unknown"]
                    + Active Equipment: [chassis.selected || "None"]"} + if(istype(chassis, /obj/mecha/working/ripley)) + var/obj/mecha/working/ripley/RM = chassis + answer += "
                    Used Cargo Space: [round((RM.cargo.len / RM.cargo_capacity * 100), 0.01)]%" + + return answer + +/obj/item/mecha_parts/mecha_tracking/emp_act() + . = ..() + if(!(. & EMP_PROTECT_SELF)) + qdel(src) + +/obj/item/mecha_parts/mecha_tracking/Destroy() + if(chassis) + if(src in chassis.trackers) + chassis.trackers -= src + chassis = null + return ..() + +/obj/item/mecha_parts/mecha_tracking/try_attach_part(mob/user, obj/mecha/M) + if(!..()) + return + M.trackers += src + M.diag_hud_set_mechtracking() + chassis = M + +/** + * Attempts to EMP mech that the tracker is attached to, if there is one and tracker is not on cooldown + */ +/obj/item/mecha_parts/mecha_tracking/proc/shock() + if(recharging) + return + if(chassis) + chassis.emp_act(EMP_HEAVY) + addtimer(CALLBACK(src, /obj/item/mecha_parts/mecha_tracking/proc/recharge), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + recharging = TRUE + +/** + * Resets recharge variable, allowing tracker to be EMP pulsed again + */ +/obj/item/mecha_parts/mecha_tracking/proc/recharge() + recharging = FALSE + +/obj/item/mecha_parts/mecha_tracking/ai_control + name = "exosuit AI control beacon" + desc = "A device used to transmit exosuit data. Also allows active AI units to take control of said exosuit." + ai_beacon = TRUE + +/obj/item/storage/box/mechabeacons + name = "exosuit tracking beacons" + +/obj/item/storage/box/mechabeacons/PopulateContents() + ..() + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) diff --git a/code/game/mecha/mecha_parts.dm b/code/game/mecha/mecha_parts.dm index e454699271e..a79be7c6a0f 100644 --- a/code/game/mecha/mecha_parts.dm +++ b/code/game/mecha/mecha_parts.dm @@ -1,381 +1,381 @@ -///////////////////////// -////// Mecha Parts ////// -///////////////////////// - -/obj/item/mecha_parts - name = "mecha part" - icon = 'icons/mecha/mech_construct.dmi' - icon_state = "blank" - w_class = WEIGHT_CLASS_GIGANTIC - flags_1 = CONDUCT_1 - -/obj/item/mecha_parts/proc/try_attach_part(mob/user, obj/mecha/M) //For attaching parts to a finished mech - if(!user.transferItemToLoc(src, M)) - to_chat(user, "\The [src] is stuck to your hand, you cannot put it in \the [M]!") - return FALSE - user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") - return TRUE - -/obj/item/mecha_parts/part/try_attach_part(mob/user, obj/mecha/M) - return - -/obj/item/mecha_parts/chassis - name = "Mecha Chassis" - icon_state = "backbone" - interaction_flags_item = NONE //Don't pick us up!! - var/construct_type - -/obj/item/mecha_parts/chassis/Initialize() - . = ..() - if(construct_type) - AddComponent(construct_type) - -/////////// Ripley - -/obj/item/mecha_parts/chassis/ripley - name = "\improper Ripley chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/ripley - -/obj/item/mecha_parts/part/ripley_torso - name = "\improper Ripley torso" - desc = "A torso part of Ripley APLU. Contains power unit, processing core and life support systems." - icon_state = "ripley_harness" - -/obj/item/mecha_parts/part/ripley_left_arm - name = "\improper Ripley left arm" - desc = "A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_l_arm" - -/obj/item/mecha_parts/part/ripley_right_arm - name = "\improper Ripley right arm" - desc = "A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_r_arm" - -/obj/item/mecha_parts/part/ripley_left_leg - name = "\improper Ripley left leg" - desc = "A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_l_leg" - -/obj/item/mecha_parts/part/ripley_right_leg - name = "\improper Ripley right leg" - desc = "A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_r_leg" - -///////// Odysseus - -/obj/item/mecha_parts/chassis/odysseus - name = "\improper Odysseus chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/odysseus - -/obj/item/mecha_parts/part/odysseus_head - name = "\improper Odysseus head" - desc = "An Odysseus head. Contains an integrated medical HUD scanner." - icon_state = "odysseus_head" - -/obj/item/mecha_parts/part/odysseus_torso - name = "\improper Odysseus torso" - desc="A torso part of Odysseus. Contains power unit, processing core and life support systems along with an attachment port for a mounted sleeper." - icon_state = "odysseus_torso" - -/obj/item/mecha_parts/part/odysseus_left_arm - name = "\improper Odysseus left arm" - desc = "An Odysseus left arm. Data and power sockets are compatible with specialized medical equipment." - icon_state = "odysseus_l_arm" - -/obj/item/mecha_parts/part/odysseus_right_arm - name = "\improper Odysseus right arm" - desc = "An Odysseus right arm. Data and power sockets are compatible with specialized medical equipment." - icon_state = "odysseus_r_arm" - -/obj/item/mecha_parts/part/odysseus_left_leg - name = "\improper Odysseus left leg" - desc = "An Odysseus left leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." - icon_state = "odysseus_l_leg" - -/obj/item/mecha_parts/part/odysseus_right_leg - name = "\improper Odysseus right leg" - desc = "An odysseus right leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." - icon_state = "odysseus_r_leg" - -///////// Gygax - -/obj/item/mecha_parts/chassis/gygax - name = "\improper Gygax chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/gygax - -/obj/item/mecha_parts/part/gygax_torso - name = "\improper Gygax torso" - desc = "A torso part of Gygax. Contains power unit, processing core and life support systems." - icon_state = "gygax_harness" - -/obj/item/mecha_parts/part/gygax_head - name = "\improper Gygax head" - desc = "A Gygax head. Houses advanced surveillance and targeting sensors." - icon_state = "gygax_head" - -/obj/item/mecha_parts/part/gygax_left_arm - name = "\improper Gygax left arm" - desc = "A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_l_arm" - -/obj/item/mecha_parts/part/gygax_right_arm - name = "\improper Gygax right arm" - desc = "A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_r_arm" - -/obj/item/mecha_parts/part/gygax_left_leg - name = "\improper Gygax left leg" - desc = "A Gygax left leg. Constructed with advanced servomechanisms and actuators to enable faster speed." - icon_state = "gygax_l_leg" - -/obj/item/mecha_parts/part/gygax_right_leg - name = "\improper Gygax right leg" - desc = "A Gygax right leg. Constructed with advanced servomechanisms and actuators to enable faster speed." - icon_state = "gygax_r_leg" - -/obj/item/mecha_parts/part/gygax_armor - gender = PLURAL - name = "\improper Gygax armor plates" - desc = "A set of armor plates designed for the Gygax. Designed to effectively deflect damage with a lightweight construction." - icon_state = "gygax_armor" - - -//////////// Durand - -/obj/item/mecha_parts/chassis/durand - name = "\improper Durand chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/durand - -/obj/item/mecha_parts/part/durand_torso - name = "\improper Durand torso" - desc = "A torso part of Durand. Contains power unit, processing core and life support systems within a robust protective frame." - icon_state = "durand_harness" - -/obj/item/mecha_parts/part/durand_head - name = "\improper Durand head" - desc = "A Durand head. Houses advanced surveillance and targeting sensors." - icon_state = "durand_head" - -/obj/item/mecha_parts/part/durand_left_arm - name = "\improper Durand left arm" - desc = "A Durand left arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." - icon_state = "durand_l_arm" - -/obj/item/mecha_parts/part/durand_right_arm - name = "\improper Durand right arm" - desc = "A Durand right arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." - icon_state = "durand_r_arm" - -/obj/item/mecha_parts/part/durand_left_leg - name = "\improper Durand left leg" - desc = "A Durand left leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." - icon_state = "durand_l_leg" - -/obj/item/mecha_parts/part/durand_right_leg - name = "\improper Durand right leg" - desc = "A Durand right leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." - icon_state = "durand_r_leg" - -/obj/item/mecha_parts/part/durand_armor - gender = PLURAL - name = "\improper Durand armor plates" - desc = "A set of armor plates for the Durand. Built heavy to resist an incredible amount of brute force." - icon_state = "durand_armor" - -////////// Clarke - -/obj/item/mecha_parts/chassis/clarke - name = "\improper Clarke chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/clarke - -/obj/item/mecha_parts/part/clarke_torso - name = "\improper Clarke torso" - desc = "A torso part of Clarke. Contains power unit, processing core and life support systems." - icon_state = "clarke_harness" - -/obj/item/mecha_parts/part/clarke_head - name = "\improper Clarke head" - desc = "A Clarke head. Contains an integrated diagnostic HUD scanner." - icon_state = "clarke_head" - -/obj/item/mecha_parts/part/clarke_left_arm - name = "\improper Clarke left arm" - desc = "A Clarke left arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "clarke_l_arm" - -/obj/item/mecha_parts/part/clarke_right_arm - name = "\improper Clarke right arm" - desc = "A Clarke right arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "clarke_r_arm" - -////////// HONK - -/obj/item/mecha_parts/chassis/honker - name = "\improper H.O.N.K chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/honker - -/obj/item/mecha_parts/part/honker_torso - name = "\improper H.O.N.K torso" - desc = "A torso part of H.O.N.K. Contains chuckle unit, bananium core and honk support systems." - icon_state = "honker_harness" - -/obj/item/mecha_parts/part/honker_head - name = "\improper H.O.N.K head" - desc = "A H.O.N.K head. Appears to lack a face plate." - icon_state = "honker_head" - -/obj/item/mecha_parts/part/honker_left_arm - name = "\improper H.O.N.K left arm" - desc = "A H.O.N.K left arm. With unique sockets that accept odd weaponry designed by clown scientists." - icon_state = "honker_l_arm" - -/obj/item/mecha_parts/part/honker_right_arm - name = "\improper H.O.N.K right arm" - desc = "A H.O.N.K right arm. With unique sockets that accept odd weaponry designed by clown scientists." - icon_state = "honker_r_arm" - -/obj/item/mecha_parts/part/honker_left_leg - name = "\improper H.O.N.K left leg" - desc = "A H.O.N.K left leg. The foot appears just large enough to fully accommodate a clown shoe." - icon_state = "honker_l_leg" - -/obj/item/mecha_parts/part/honker_right_leg - name = "\improper H.O.N.K right leg" - desc = "A H.O.N.K right leg. The foot appears just large enough to fully accommodate a clown shoe." - icon_state = "honker_r_leg" - - -////////// Phazon - -/obj/item/mecha_parts/chassis/phazon - name = "\improper Phazon chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/phazon - -/obj/item/mecha_parts/chassis/phazon/attackby(obj/item/I, mob/user, params) - . = ..() - if(istype(I, /obj/item/assembly/signaler/anomaly) && !istype(I, /obj/item/assembly/signaler/anomaly/bluespace)) - to_chat(user, "The anomaly core socket only accepts bluespace anomaly cores!") - -/obj/item/mecha_parts/part/phazon_torso - name="\improper Phazon torso" - desc="A Phazon torso part. The socket for the bluespace core that powers the exosuit's unique phase drives is located in the middle." - icon_state = "phazon_harness" - -/obj/item/mecha_parts/part/phazon_head - name="\improper Phazon head" - desc="A Phazon head. Its sensors are carefully calibrated to provide vision and data even when the exosuit is phasing." - icon_state = "phazon_head" - -/obj/item/mecha_parts/part/phazon_left_arm - name="\improper Phazon left arm" - desc="A Phazon left arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." - icon_state = "phazon_l_arm" - -/obj/item/mecha_parts/part/phazon_right_arm - name="\improper Phazon right arm" - desc="A Phazon right arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." - icon_state = "phazon_r_arm" - -/obj/item/mecha_parts/part/phazon_left_leg - name="\improper Phazon left leg" - desc="A Phazon left leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." - icon_state = "phazon_l_leg" - -/obj/item/mecha_parts/part/phazon_right_leg - name="\improper Phazon right leg" - desc="A Phazon right leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." - icon_state = "phazon_r_leg" - -/obj/item/mecha_parts/part/phazon_armor - name="Phazon armor" - desc="Phazon armor plates. They are layered with plasma to protect the pilot from the stress of phasing and have unusual properties." - icon_state = "phazon_armor" - - -///////// Circuitboards - -/obj/item/circuitboard/mecha - name = "exosuit circuit board" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - force = 5 - w_class = WEIGHT_CLASS_SMALL - throwforce = 0 - throw_speed = 3 - throw_range = 7 - -/obj/item/circuitboard/mecha/ripley/peripherals - name = "Ripley Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/ripley/main - name = "Ripley Central Control module (Exosuit Board)" - icon_state = "mainboard" - - -/obj/item/circuitboard/mecha/gygax/peripherals - name = "Gygax Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/gygax/targeting - name = "Gygax Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/gygax/main - name = "Gygax Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/durand/peripherals - name = "Durand Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/durand/targeting - name = "Durand Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/durand/main - name = "Durand Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/honker/peripherals - name = "H.O.N.K Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/honker/targeting - name = "H.O.N.K Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/honker/main - name = "H.O.N.K Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/odysseus/peripherals - name = "Odysseus Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/odysseus/main - name = "Odysseus Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/phazon/peripherals - name = "Phazon Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/phazon/targeting - name = "Phazon Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/phazon/main - name = "Phazon Central Control module (Exosuit Board)" - -/obj/item/circuitboard/mecha/clarke/peripherals - name = "Clarke Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/clarke/main - name = "Clarke Central Control module (Exosuit Board)" - icon_state = "mainboard" +///////////////////////// +////// Mecha Parts ////// +///////////////////////// + +/obj/item/mecha_parts + name = "mecha part" + icon = 'icons/mecha/mech_construct.dmi' + icon_state = "blank" + w_class = WEIGHT_CLASS_GIGANTIC + flags_1 = CONDUCT_1 + +/obj/item/mecha_parts/proc/try_attach_part(mob/user, obj/mecha/M) //For attaching parts to a finished mech + if(!user.transferItemToLoc(src, M)) + to_chat(user, "\The [src] is stuck to your hand, you cannot put it in \the [M]!") + return FALSE + user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") + return TRUE + +/obj/item/mecha_parts/part/try_attach_part(mob/user, obj/mecha/M) + return + +/obj/item/mecha_parts/chassis + name = "Mecha Chassis" + icon_state = "backbone" + interaction_flags_item = NONE //Don't pick us up!! + var/construct_type + +/obj/item/mecha_parts/chassis/Initialize() + . = ..() + if(construct_type) + AddComponent(construct_type) + +/////////// Ripley + +/obj/item/mecha_parts/chassis/ripley + name = "\improper Ripley chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/ripley + +/obj/item/mecha_parts/part/ripley_torso + name = "\improper Ripley torso" + desc = "A torso part of Ripley APLU. Contains power unit, processing core and life support systems." + icon_state = "ripley_harness" + +/obj/item/mecha_parts/part/ripley_left_arm + name = "\improper Ripley left arm" + desc = "A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_l_arm" + +/obj/item/mecha_parts/part/ripley_right_arm + name = "\improper Ripley right arm" + desc = "A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_r_arm" + +/obj/item/mecha_parts/part/ripley_left_leg + name = "\improper Ripley left leg" + desc = "A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_l_leg" + +/obj/item/mecha_parts/part/ripley_right_leg + name = "\improper Ripley right leg" + desc = "A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_r_leg" + +///////// Odysseus + +/obj/item/mecha_parts/chassis/odysseus + name = "\improper Odysseus chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/odysseus + +/obj/item/mecha_parts/part/odysseus_head + name = "\improper Odysseus head" + desc = "An Odysseus head. Contains an integrated medical HUD scanner." + icon_state = "odysseus_head" + +/obj/item/mecha_parts/part/odysseus_torso + name = "\improper Odysseus torso" + desc="A torso part of Odysseus. Contains power unit, processing core and life support systems along with an attachment port for a mounted sleeper." + icon_state = "odysseus_torso" + +/obj/item/mecha_parts/part/odysseus_left_arm + name = "\improper Odysseus left arm" + desc = "An Odysseus left arm. Data and power sockets are compatible with specialized medical equipment." + icon_state = "odysseus_l_arm" + +/obj/item/mecha_parts/part/odysseus_right_arm + name = "\improper Odysseus right arm" + desc = "An Odysseus right arm. Data and power sockets are compatible with specialized medical equipment." + icon_state = "odysseus_r_arm" + +/obj/item/mecha_parts/part/odysseus_left_leg + name = "\improper Odysseus left leg" + desc = "An Odysseus left leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." + icon_state = "odysseus_l_leg" + +/obj/item/mecha_parts/part/odysseus_right_leg + name = "\improper Odysseus right leg" + desc = "An odysseus right leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." + icon_state = "odysseus_r_leg" + +///////// Gygax + +/obj/item/mecha_parts/chassis/gygax + name = "\improper Gygax chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/gygax + +/obj/item/mecha_parts/part/gygax_torso + name = "\improper Gygax torso" + desc = "A torso part of Gygax. Contains power unit, processing core and life support systems." + icon_state = "gygax_harness" + +/obj/item/mecha_parts/part/gygax_head + name = "\improper Gygax head" + desc = "A Gygax head. Houses advanced surveillance and targeting sensors." + icon_state = "gygax_head" + +/obj/item/mecha_parts/part/gygax_left_arm + name = "\improper Gygax left arm" + desc = "A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_l_arm" + +/obj/item/mecha_parts/part/gygax_right_arm + name = "\improper Gygax right arm" + desc = "A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_r_arm" + +/obj/item/mecha_parts/part/gygax_left_leg + name = "\improper Gygax left leg" + desc = "A Gygax left leg. Constructed with advanced servomechanisms and actuators to enable faster speed." + icon_state = "gygax_l_leg" + +/obj/item/mecha_parts/part/gygax_right_leg + name = "\improper Gygax right leg" + desc = "A Gygax right leg. Constructed with advanced servomechanisms and actuators to enable faster speed." + icon_state = "gygax_r_leg" + +/obj/item/mecha_parts/part/gygax_armor + gender = PLURAL + name = "\improper Gygax armor plates" + desc = "A set of armor plates designed for the Gygax. Designed to effectively deflect damage with a lightweight construction." + icon_state = "gygax_armor" + + +//////////// Durand + +/obj/item/mecha_parts/chassis/durand + name = "\improper Durand chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/durand + +/obj/item/mecha_parts/part/durand_torso + name = "\improper Durand torso" + desc = "A torso part of Durand. Contains power unit, processing core and life support systems within a robust protective frame." + icon_state = "durand_harness" + +/obj/item/mecha_parts/part/durand_head + name = "\improper Durand head" + desc = "A Durand head. Houses advanced surveillance and targeting sensors." + icon_state = "durand_head" + +/obj/item/mecha_parts/part/durand_left_arm + name = "\improper Durand left arm" + desc = "A Durand left arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." + icon_state = "durand_l_arm" + +/obj/item/mecha_parts/part/durand_right_arm + name = "\improper Durand right arm" + desc = "A Durand right arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." + icon_state = "durand_r_arm" + +/obj/item/mecha_parts/part/durand_left_leg + name = "\improper Durand left leg" + desc = "A Durand left leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." + icon_state = "durand_l_leg" + +/obj/item/mecha_parts/part/durand_right_leg + name = "\improper Durand right leg" + desc = "A Durand right leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." + icon_state = "durand_r_leg" + +/obj/item/mecha_parts/part/durand_armor + gender = PLURAL + name = "\improper Durand armor plates" + desc = "A set of armor plates for the Durand. Built heavy to resist an incredible amount of brute force." + icon_state = "durand_armor" + +////////// Clarke + +/obj/item/mecha_parts/chassis/clarke + name = "\improper Clarke chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/clarke + +/obj/item/mecha_parts/part/clarke_torso + name = "\improper Clarke torso" + desc = "A torso part of Clarke. Contains power unit, processing core and life support systems." + icon_state = "clarke_harness" + +/obj/item/mecha_parts/part/clarke_head + name = "\improper Clarke head" + desc = "A Clarke head. Contains an integrated diagnostic HUD scanner." + icon_state = "clarke_head" + +/obj/item/mecha_parts/part/clarke_left_arm + name = "\improper Clarke left arm" + desc = "A Clarke left arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "clarke_l_arm" + +/obj/item/mecha_parts/part/clarke_right_arm + name = "\improper Clarke right arm" + desc = "A Clarke right arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "clarke_r_arm" + +////////// HONK + +/obj/item/mecha_parts/chassis/honker + name = "\improper H.O.N.K chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/honker + +/obj/item/mecha_parts/part/honker_torso + name = "\improper H.O.N.K torso" + desc = "A torso part of H.O.N.K. Contains chuckle unit, bananium core and honk support systems." + icon_state = "honker_harness" + +/obj/item/mecha_parts/part/honker_head + name = "\improper H.O.N.K head" + desc = "A H.O.N.K head. Appears to lack a face plate." + icon_state = "honker_head" + +/obj/item/mecha_parts/part/honker_left_arm + name = "\improper H.O.N.K left arm" + desc = "A H.O.N.K left arm. With unique sockets that accept odd weaponry designed by clown scientists." + icon_state = "honker_l_arm" + +/obj/item/mecha_parts/part/honker_right_arm + name = "\improper H.O.N.K right arm" + desc = "A H.O.N.K right arm. With unique sockets that accept odd weaponry designed by clown scientists." + icon_state = "honker_r_arm" + +/obj/item/mecha_parts/part/honker_left_leg + name = "\improper H.O.N.K left leg" + desc = "A H.O.N.K left leg. The foot appears just large enough to fully accommodate a clown shoe." + icon_state = "honker_l_leg" + +/obj/item/mecha_parts/part/honker_right_leg + name = "\improper H.O.N.K right leg" + desc = "A H.O.N.K right leg. The foot appears just large enough to fully accommodate a clown shoe." + icon_state = "honker_r_leg" + + +////////// Phazon + +/obj/item/mecha_parts/chassis/phazon + name = "\improper Phazon chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/phazon + +/obj/item/mecha_parts/chassis/phazon/attackby(obj/item/I, mob/user, params) + . = ..() + if(istype(I, /obj/item/assembly/signaler/anomaly) && !istype(I, /obj/item/assembly/signaler/anomaly/bluespace)) + to_chat(user, "The anomaly core socket only accepts bluespace anomaly cores!") + +/obj/item/mecha_parts/part/phazon_torso + name="\improper Phazon torso" + desc="A Phazon torso part. The socket for the bluespace core that powers the exosuit's unique phase drives is located in the middle." + icon_state = "phazon_harness" + +/obj/item/mecha_parts/part/phazon_head + name="\improper Phazon head" + desc="A Phazon head. Its sensors are carefully calibrated to provide vision and data even when the exosuit is phasing." + icon_state = "phazon_head" + +/obj/item/mecha_parts/part/phazon_left_arm + name="\improper Phazon left arm" + desc="A Phazon left arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." + icon_state = "phazon_l_arm" + +/obj/item/mecha_parts/part/phazon_right_arm + name="\improper Phazon right arm" + desc="A Phazon right arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." + icon_state = "phazon_r_arm" + +/obj/item/mecha_parts/part/phazon_left_leg + name="\improper Phazon left leg" + desc="A Phazon left leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." + icon_state = "phazon_l_leg" + +/obj/item/mecha_parts/part/phazon_right_leg + name="\improper Phazon right leg" + desc="A Phazon right leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." + icon_state = "phazon_r_leg" + +/obj/item/mecha_parts/part/phazon_armor + name="Phazon armor" + desc="Phazon armor plates. They are layered with plasma to protect the pilot from the stress of phasing and have unusual properties." + icon_state = "phazon_armor" + + +///////// Circuitboards + +/obj/item/circuitboard/mecha + name = "exosuit circuit board" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + force = 5 + w_class = WEIGHT_CLASS_SMALL + throwforce = 0 + throw_speed = 3 + throw_range = 7 + +/obj/item/circuitboard/mecha/ripley/peripherals + name = "Ripley Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/ripley/main + name = "Ripley Central Control module (Exosuit Board)" + icon_state = "mainboard" + + +/obj/item/circuitboard/mecha/gygax/peripherals + name = "Gygax Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/gygax/targeting + name = "Gygax Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/gygax/main + name = "Gygax Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/durand/peripherals + name = "Durand Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/durand/targeting + name = "Durand Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/durand/main + name = "Durand Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/honker/peripherals + name = "H.O.N.K Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/honker/targeting + name = "H.O.N.K Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/honker/main + name = "H.O.N.K Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/odysseus/peripherals + name = "Odysseus Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/odysseus/main + name = "Odysseus Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/phazon/peripherals + name = "Phazon Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/phazon/targeting + name = "Phazon Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/phazon/main + name = "Phazon Central Control module (Exosuit Board)" + +/obj/item/circuitboard/mecha/clarke/peripherals + name = "Clarke Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/clarke/main + name = "Clarke Central Control module (Exosuit Board)" + icon_state = "mainboard" diff --git a/code/game/mecha/mecha_wreckage.dm b/code/game/mecha/mecha_wreckage.dm index 88d72f037f9..61b4b3c092a 100644 --- a/code/game/mecha/mecha_wreckage.dm +++ b/code/game/mecha/mecha_wreckage.dm @@ -1,221 +1,221 @@ -/////////////////////////////////// -//////// Mecha wreckage //////// -/////////////////////////////////// - - -/obj/structure/mecha_wreckage - name = "exosuit wreckage" - desc = "Remains of some unfortunate mecha. Completely irreparable, but perhaps something can be salvaged." - icon = 'icons/mecha/mecha.dmi' - density = TRUE - anchored = FALSE - opacity = 0 - var/list/welder_salvage = list(/obj/item/stack/sheet/plasteel, /obj/item/stack/sheet/metal, /obj/item/stack/rods) - var/salvage_num = 5 - var/list/crowbar_salvage = list() - var/wires_removed = FALSE - var/mob/living/silicon/ai/AI //AIs to be salvaged - var/list/parts - -/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) - . = ..() - if(parts) - for(var/i in 1 to 2) - if(!parts.len) - break - if(prob(60)) - continue - var/part = pick(parts) - welder_salvage += part - parts = null - if(!AI_pilot) //Type-checking for this is already done in mecha/Destroy() - return - AI = AI_pilot - AI.apply_damage(150, BURN) //Give the AI a bit of damage from the "shock" of being suddenly shut down - AI.death() //The damage is not enough to kill the AI, but to be 'corrupted files' in need of repair. - AI.forceMove(src) //Put the dead AI inside the wreckage for recovery - add_overlay(mutable_appearance('icons/obj/projectiles.dmi', "green_laser")) //Overlay for the recovery beacon - AI.controlled_mech = null - AI.remote_control = null - -/obj/structure/mecha_wreckage/Destroy() - if(AI) - QDEL_NULL(AI) - QDEL_LIST(crowbar_salvage) - return ..() - -/obj/structure/mecha_wreckage/examine(mob/user) - . = ..() - if(!AI) - return - . += "The AI recovery beacon is active." - -/obj/structure/mecha_wreckage/welder_act(mob/living/user, obj/item/I) - ..() - . = TRUE - if(salvage_num <= 0 || !length(welder_salvage)) - to_chat(user, "You don't see anything that can be cut with [I]!") - return - if(!I.use_tool(src, user, 0, volume=50)) - return - if(prob(30)) - to_chat(user, "You fail to salvage anything valuable from [src]!") - return - var/type = pick(welder_salvage) - var/N = new type(get_turf(user)) - user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") - if(!istype(N, /obj/item/stack)) - welder_salvage -= type - salvage_num-- - -/obj/structure/mecha_wreckage/wirecutter_act(mob/living/user, obj/item/I) - ..() - . = TRUE - if(wires_removed) - to_chat(user, "You don't see anything that can be cut with [I]!") - return - var/N = new /obj/item/stack/cable_coil(get_turf(user), rand(1,3)) - user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") - wires_removed = TRUE - -/obj/structure/mecha_wreckage/crowbar_act(mob/living/user, obj/item/I) - ..() - . = TRUE - if(crowbar_salvage.len) - var/obj/S = pick(crowbar_salvage) - S.forceMove(user.drop_location()) - user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") - crowbar_salvage -= S - return - to_chat(user, "You don't see anything that can be cut with [I]!") - -/obj/structure/mecha_wreckage/transfer_ai(interaction, mob/user, null, obj/item/aicard/card) - if(!..()) - return - - //Proc called on the wreck by the AI card. - if(interaction != AI_TRANS_TO_CARD) //AIs can only be transferred in one direction, from the wreck to the card. - return - if(!AI) //No AI in the wreck - to_chat(user, "No AI backups found.") - return - cut_overlays() //Remove the recovery beacon overlay - AI.forceMove(card) //Move the dead AI to the card. - card.AI = AI - if(AI.client) //AI player is still in the dead AI and is connected - to_chat(AI, "The remains of your file system have been recovered on a mobile storage device.") - else //Give the AI a heads-up that it is probably going to get fixed. - AI.notify_ghost_cloning("You have been recovered from the wreckage!", source = card) - to_chat(user, "Backup files recovered: [AI.name] ([rand(1000,9999)].exe) salvaged from [name] and stored within local memory.") - AI = null - -/obj/structure/mecha_wreckage/gygax - name = "\improper Gygax wreckage" - icon_state = "gygax-broken" - parts = list( - /obj/item/mecha_parts/part/gygax_torso, - /obj/item/mecha_parts/part/gygax_head, - /obj/item/mecha_parts/part/gygax_left_arm, - /obj/item/mecha_parts/part/gygax_right_arm, - /obj/item/mecha_parts/part/gygax_left_leg, - /obj/item/mecha_parts/part/gygax_right_leg - ) - -/obj/structure/mecha_wreckage/gygax/dark - name = "\improper Dark Gygax wreckage" - icon_state = "darkgygax-broken" - -/obj/structure/mecha_wreckage/marauder - name = "\improper Marauder wreckage" - icon_state = "marauder-broken" - -/obj/structure/mecha_wreckage/mauler - name = "\improper Mauler wreckage" - icon_state = "mauler-broken" - desc = "The syndicate won't be very happy about this..." - -/obj/structure/mecha_wreckage/seraph - name = "\improper Seraph wreckage" - icon_state = "seraph-broken" - -/obj/structure/mecha_wreckage/reticence - name = "\improper Reticence wreckage" - icon_state = "reticence-broken" - color = "#87878715" - desc = "..." - -/obj/structure/mecha_wreckage/ripley - name = "\improper Ripley wreckage" - icon_state = "ripley-broken" - parts = list( - /obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg) - -/obj/structure/mecha_wreckage/ripley/mkii - name = "\improper Ripley MK-II wreckage" - icon_state = "ripleymkii-broken" - -/obj/structure/mecha_wreckage/clarke - name = "\improper Clarke wreckage" - icon_state = "clarke-broken" - parts = list( - /obj/item/mecha_parts/part/clarke_torso, - /obj/item/mecha_parts/part/clarke_head, - /obj/item/mecha_parts/part/clarke_left_arm, - /obj/item/mecha_parts/part/clarke_right_arm, - /obj/item/stack/conveyor) - -/obj/structure/mecha_wreckage/ripley/deathripley - name = "\improper Death-Ripley wreckage" - icon_state = "deathripley-broken" - parts = null - -/obj/structure/mecha_wreckage/honker - name = "\improper H.O.N.K wreckage" - icon_state = "honker-broken" - desc = "All is right in the universe." - parts = list( - /obj/item/mecha_parts/part/honker_torso, - /obj/item/mecha_parts/part/honker_head, - /obj/item/mecha_parts/part/honker_left_arm, - /obj/item/mecha_parts/part/honker_right_arm, - /obj/item/mecha_parts/part/honker_left_leg, - /obj/item/mecha_parts/part/honker_right_leg) - -/obj/structure/mecha_wreckage/durand - name = "\improper Durand wreckage" - icon_state = "durand-broken" - parts = list( - /obj/item/mecha_parts/part/durand_torso, - /obj/item/mecha_parts/part/durand_head, - /obj/item/mecha_parts/part/durand_left_arm, - /obj/item/mecha_parts/part/durand_right_arm, - /obj/item/mecha_parts/part/durand_left_leg, - /obj/item/mecha_parts/part/durand_right_leg) - -/obj/structure/mecha_wreckage/phazon - name = "\improper Phazon wreckage" - icon_state = "phazon-broken" - parts = list( - /obj/item/mecha_parts/part/phazon_torso, - /obj/item/mecha_parts/part/phazon_head, - /obj/item/mecha_parts/part/phazon_left_arm, - /obj/item/mecha_parts/part/phazon_right_arm, - /obj/item/mecha_parts/part/phazon_left_leg, - /obj/item/mecha_parts/part/phazon_right_leg) - - - -/obj/structure/mecha_wreckage/odysseus - name = "\improper Odysseus wreckage" - icon_state = "odysseus-broken" - parts = list( - /obj/item/mecha_parts/part/odysseus_torso, - /obj/item/mecha_parts/part/odysseus_head, - /obj/item/mecha_parts/part/odysseus_left_arm, - /obj/item/mecha_parts/part/odysseus_right_arm, - /obj/item/mecha_parts/part/odysseus_left_leg, - /obj/item/mecha_parts/part/odysseus_right_leg) +/////////////////////////////////// +//////// Mecha wreckage //////// +/////////////////////////////////// + + +/obj/structure/mecha_wreckage + name = "exosuit wreckage" + desc = "Remains of some unfortunate mecha. Completely irreparable, but perhaps something can be salvaged." + icon = 'icons/mecha/mecha.dmi' + density = TRUE + anchored = FALSE + opacity = 0 + var/list/welder_salvage = list(/obj/item/stack/sheet/plasteel, /obj/item/stack/sheet/metal, /obj/item/stack/rods) + var/salvage_num = 5 + var/list/crowbar_salvage = list() + var/wires_removed = FALSE + var/mob/living/silicon/ai/AI //AIs to be salvaged + var/list/parts + +/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) + . = ..() + if(parts) + for(var/i in 1 to 2) + if(!parts.len) + break + if(prob(60)) + continue + var/part = pick(parts) + welder_salvage += part + parts = null + if(!AI_pilot) //Type-checking for this is already done in mecha/Destroy() + return + AI = AI_pilot + AI.apply_damage(150, BURN) //Give the AI a bit of damage from the "shock" of being suddenly shut down + AI.death() //The damage is not enough to kill the AI, but to be 'corrupted files' in need of repair. + AI.forceMove(src) //Put the dead AI inside the wreckage for recovery + add_overlay(mutable_appearance('icons/obj/projectiles.dmi', "green_laser")) //Overlay for the recovery beacon + AI.controlled_mech = null + AI.remote_control = null + +/obj/structure/mecha_wreckage/Destroy() + if(AI) + QDEL_NULL(AI) + QDEL_LIST(crowbar_salvage) + return ..() + +/obj/structure/mecha_wreckage/examine(mob/user) + . = ..() + if(!AI) + return + . += "The AI recovery beacon is active." + +/obj/structure/mecha_wreckage/welder_act(mob/living/user, obj/item/I) + ..() + . = TRUE + if(salvage_num <= 0 || !length(welder_salvage)) + to_chat(user, "You don't see anything that can be cut with [I]!") + return + if(!I.use_tool(src, user, 0, volume=50)) + return + if(prob(30)) + to_chat(user, "You fail to salvage anything valuable from [src]!") + return + var/type = pick(welder_salvage) + var/N = new type(get_turf(user)) + user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") + if(!istype(N, /obj/item/stack)) + welder_salvage -= type + salvage_num-- + +/obj/structure/mecha_wreckage/wirecutter_act(mob/living/user, obj/item/I) + ..() + . = TRUE + if(wires_removed) + to_chat(user, "You don't see anything that can be cut with [I]!") + return + var/N = new /obj/item/stack/cable_coil(get_turf(user), rand(1,3)) + user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") + wires_removed = TRUE + +/obj/structure/mecha_wreckage/crowbar_act(mob/living/user, obj/item/I) + ..() + . = TRUE + if(crowbar_salvage.len) + var/obj/S = pick(crowbar_salvage) + S.forceMove(user.drop_location()) + user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") + crowbar_salvage -= S + return + to_chat(user, "You don't see anything that can be cut with [I]!") + +/obj/structure/mecha_wreckage/transfer_ai(interaction, mob/user, null, obj/item/aicard/card) + if(!..()) + return + + //Proc called on the wreck by the AI card. + if(interaction != AI_TRANS_TO_CARD) //AIs can only be transferred in one direction, from the wreck to the card. + return + if(!AI) //No AI in the wreck + to_chat(user, "No AI backups found.") + return + cut_overlays() //Remove the recovery beacon overlay + AI.forceMove(card) //Move the dead AI to the card. + card.AI = AI + if(AI.client) //AI player is still in the dead AI and is connected + to_chat(AI, "The remains of your file system have been recovered on a mobile storage device.") + else //Give the AI a heads-up that it is probably going to get fixed. + AI.notify_ghost_cloning("You have been recovered from the wreckage!", source = card) + to_chat(user, "Backup files recovered: [AI.name] ([rand(1000,9999)].exe) salvaged from [name] and stored within local memory.") + AI = null + +/obj/structure/mecha_wreckage/gygax + name = "\improper Gygax wreckage" + icon_state = "gygax-broken" + parts = list( + /obj/item/mecha_parts/part/gygax_torso, + /obj/item/mecha_parts/part/gygax_head, + /obj/item/mecha_parts/part/gygax_left_arm, + /obj/item/mecha_parts/part/gygax_right_arm, + /obj/item/mecha_parts/part/gygax_left_leg, + /obj/item/mecha_parts/part/gygax_right_leg + ) + +/obj/structure/mecha_wreckage/gygax/dark + name = "\improper Dark Gygax wreckage" + icon_state = "darkgygax-broken" + +/obj/structure/mecha_wreckage/marauder + name = "\improper Marauder wreckage" + icon_state = "marauder-broken" + +/obj/structure/mecha_wreckage/mauler + name = "\improper Mauler wreckage" + icon_state = "mauler-broken" + desc = "The syndicate won't be very happy about this..." + +/obj/structure/mecha_wreckage/seraph + name = "\improper Seraph wreckage" + icon_state = "seraph-broken" + +/obj/structure/mecha_wreckage/reticence + name = "\improper Reticence wreckage" + icon_state = "reticence-broken" + color = "#87878715" + desc = "..." + +/obj/structure/mecha_wreckage/ripley + name = "\improper Ripley wreckage" + icon_state = "ripley-broken" + parts = list( + /obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg) + +/obj/structure/mecha_wreckage/ripley/mkii + name = "\improper Ripley MK-II wreckage" + icon_state = "ripleymkii-broken" + +/obj/structure/mecha_wreckage/clarke + name = "\improper Clarke wreckage" + icon_state = "clarke-broken" + parts = list( + /obj/item/mecha_parts/part/clarke_torso, + /obj/item/mecha_parts/part/clarke_head, + /obj/item/mecha_parts/part/clarke_left_arm, + /obj/item/mecha_parts/part/clarke_right_arm, + /obj/item/stack/conveyor) + +/obj/structure/mecha_wreckage/ripley/deathripley + name = "\improper Death-Ripley wreckage" + icon_state = "deathripley-broken" + parts = null + +/obj/structure/mecha_wreckage/honker + name = "\improper H.O.N.K wreckage" + icon_state = "honker-broken" + desc = "All is right in the universe." + parts = list( + /obj/item/mecha_parts/part/honker_torso, + /obj/item/mecha_parts/part/honker_head, + /obj/item/mecha_parts/part/honker_left_arm, + /obj/item/mecha_parts/part/honker_right_arm, + /obj/item/mecha_parts/part/honker_left_leg, + /obj/item/mecha_parts/part/honker_right_leg) + +/obj/structure/mecha_wreckage/durand + name = "\improper Durand wreckage" + icon_state = "durand-broken" + parts = list( + /obj/item/mecha_parts/part/durand_torso, + /obj/item/mecha_parts/part/durand_head, + /obj/item/mecha_parts/part/durand_left_arm, + /obj/item/mecha_parts/part/durand_right_arm, + /obj/item/mecha_parts/part/durand_left_leg, + /obj/item/mecha_parts/part/durand_right_leg) + +/obj/structure/mecha_wreckage/phazon + name = "\improper Phazon wreckage" + icon_state = "phazon-broken" + parts = list( + /obj/item/mecha_parts/part/phazon_torso, + /obj/item/mecha_parts/part/phazon_head, + /obj/item/mecha_parts/part/phazon_left_arm, + /obj/item/mecha_parts/part/phazon_right_arm, + /obj/item/mecha_parts/part/phazon_left_leg, + /obj/item/mecha_parts/part/phazon_right_leg) + + + +/obj/structure/mecha_wreckage/odysseus + name = "\improper Odysseus wreckage" + icon_state = "odysseus-broken" + parts = list( + /obj/item/mecha_parts/part/odysseus_torso, + /obj/item/mecha_parts/part/odysseus_head, + /obj/item/mecha_parts/part/odysseus_left_arm, + /obj/item/mecha_parts/part/odysseus_right_arm, + /obj/item/mecha_parts/part/odysseus_left_leg, + /obj/item/mecha_parts/part/odysseus_right_leg) diff --git a/code/game/mecha/medical/medical.dm b/code/game/mecha/medical/medical.dm index bbb84507572..1541ff1b7cd 100644 --- a/code/game/mecha/medical/medical.dm +++ b/code/game/mecha/medical/medical.dm @@ -1,7 +1,7 @@ -/obj/mecha/medical - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_MEDICAL) - -/obj/mecha/medical/mechturn(direction) - . = ..() - if(!strafe && !occupant.client.keys_held["Alt"]) - mechstep(direction) //agile mechs get to move and turn in the same step +/obj/mecha/medical + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_MEDICAL) + +/obj/mecha/medical/mechturn(direction) + . = ..() + if(!strafe && !occupant.client.keys_held["Alt"]) + mechstep(direction) //agile mechs get to move and turn in the same step diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index 015b3514676..27e139c84ab 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -1,31 +1,31 @@ -/obj/mecha/medical/odysseus - desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." - name = "\improper Odysseus" - icon_state = "odysseus" - step_in = 2 - max_temperature = 15000 - max_integrity = 120 - wreckage = /obj/structure/mecha_wreckage/odysseus - internal_damage_threshold = 35 - deflect_chance = 15 - step_energy_drain = 6 - -/obj/mecha/medical/odysseus/moved_inside(mob/living/carbon/human/H) - . = ..() - if(.) - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.add_hud_to(H) - -/obj/mecha/medical/odysseus/go_out() - if(isliving(occupant)) - var/mob/living/L = occupant - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.remove_hud_from(L) - ..() - -/obj/mecha/medical/odysseus/mmi_moved_inside(obj/item/mmi/M, mob/user) - . = ..() - if(.) - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - var/mob/living/brain/B = M.brainmob - hud.add_hud_to(B) +/obj/mecha/medical/odysseus + desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." + name = "\improper Odysseus" + icon_state = "odysseus" + step_in = 2 + max_temperature = 15000 + max_integrity = 120 + wreckage = /obj/structure/mecha_wreckage/odysseus + internal_damage_threshold = 35 + deflect_chance = 15 + step_energy_drain = 6 + +/obj/mecha/medical/odysseus/moved_inside(mob/living/carbon/human/H) + . = ..() + if(.) + var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + hud.add_hud_to(H) + +/obj/mecha/medical/odysseus/go_out() + if(isliving(occupant)) + var/mob/living/L = occupant + var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + hud.remove_hud_from(L) + ..() + +/obj/mecha/medical/odysseus/mmi_moved_inside(obj/item/mmi/M, mob/user) + . = ..() + if(.) + var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + var/mob/living/brain/B = M.brainmob + hud.add_hud_to(B) diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm index f23fb7d0a94..d9253a493db 100644 --- a/code/game/mecha/working/ripley.dm +++ b/code/game/mecha/working/ripley.dm @@ -1,204 +1,204 @@ -/obj/mecha/working/ripley - desc = "Autonomous Power Loader Unit MK-I. Designed primarily around heavy lifting, the Ripley can be outfitted with utility equipment to fill a number of roles." - name = "\improper APLU MK-I \"Ripley\"" - icon_state = "ripley" - silicon_icon_state = "ripley-empty" - step_in = 1.5 //Move speed, lower is faster. - /// How fast the mech is in low pressure - var/fast_pressure_step_in = 1.5 - /// How fast the mech is in normal pressure - var/slow_pressure_step_in = 2 - max_temperature = 20000 - max_integrity = 200 - lights_power = 7 - deflect_chance = 15 - armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 20, "fire" = 100, "acid" = 100) - max_equip = 6 - wreckage = /obj/structure/mecha_wreckage/ripley - internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_MINING) - enclosed = FALSE //Normal ripley has an open cockpit design - enter_delay = 10 //can enter in a quarter of the time of other mechs - exit_delay = 10 - opacity = FALSE //Ripley has a window - /// Amount of Goliath hides attached to the mech - var/hides = 0 - /// List of all things in Ripley's Cargo Compartment - var/list/cargo = new - /// How much things Ripley can carry in their Cargo Compartment - var/cargo_capacity = 15 - -/obj/mecha/working/ripley/Move() - . = ..() - update_pressure() - -/obj/mecha/working/ripley/go_out() - ..() - update_icon() - -/obj/mecha/working/ripley/moved_inside(mob/living/carbon/human/H) - ..() - update_icon() - -/obj/mecha/working/ripley/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) - if (!enclosed) - possible_int_damage -= (MECHA_INT_TEMP_CONTROL + MECHA_INT_TANK_BREACH) //if we don't even have an air tank, these two doesn't make a ton of sense. - . = ..() - -/obj/mecha/working/ripley/Initialize() - . = ..() - AddComponent(/datum/component/armor_plate,3,/obj/item/stack/sheet/animalhide/goliath_hide,list("melee" = 10, "bullet" = 5, "laser" = 5)) - - -/obj/mecha/working/ripley/Destroy() - for(var/atom/movable/A in cargo) - A.forceMove(drop_location()) - step_rand(A) - cargo.Cut() - return ..() - -/obj/mecha/working/ripley/mkii - desc = "Autonomous Power Loader Unit MK-II. This prototype Ripley is refitted with a pressurized cabin, trading its prior speed for atmospheric protection and armor." - name = "\improper APLU MK-II \"Ripley\"" - icon_state = "ripleymkii" - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 4 //step_in while in normal pressure conditions - step_in = 4 - max_temperature = 30000 - max_integrity = 250 - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 70, "fire" = 100, "acid" = 100) - wreckage = /obj/structure/mecha_wreckage/ripley/mkii - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - - -/obj/mecha/working/ripley/deathripley - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" - name = "\improper DEATH-RIPLEY" - icon_state = "deathripley" - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 3 //step_in while in normal pressure conditions - step_in = 4 - lights_power = 7 - wreckage = /obj/structure/mecha_wreckage/ripley/deathripley - step_energy_drain = 0 - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - -/obj/mecha/working/ripley/deathripley/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill - ME.attach(src) - -/obj/mecha/working/ripley/deathripley/real - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE. FOR REAL" - -/obj/mecha/working/ripley/deathripley/real/Initialize() - . = ..() - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach() - qdel(E) - equipment.Cut() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill/real - ME.attach(src) - -/obj/mecha/working/ripley/mining - desc = "An old, dusty mining Ripley." - name = "\improper APLU \"Miner\"" - obj_integrity = 75 //Low starting health - -/obj/mecha/working/ripley/mining/Initialize() - . = ..() - if(cell) - cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge - if(prob(70)) //Maybe add a drill - if(prob(15)) //Possible diamond drill... Feeling lucky? - var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new - D.attach(src) - else - var/obj/item/mecha_parts/mecha_equipment/drill/D = new - D.attach(src) - - else //Add plasma cutter if no drill - var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new - P.attach(src) - - //Add ore box to cargo - cargo.Add(new /obj/structure/ore_box(src)) - - //Attach hydraulic clamp - var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new - HC.attach(src) - for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily - qdel(B) - - var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new - scanner.attach(src) - -/obj/mecha/working/ripley/Exit(atom/movable/O) - if(O in cargo) - return 0 - return ..() - -/obj/mecha/working/ripley/Topic(href, href_list) - ..() - if(href_list["drop_from_cargo"]) - var/obj/O = locate(href_list["drop_from_cargo"]) in cargo - if(O) - occupant_message("You unload [O].") - O.forceMove(drop_location()) - cargo -= O - log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]", LOG_MECHA) - return - - -/obj/mecha/working/ripley/contents_explosion(severity, target) - for(var/X in cargo) - var/obj/O = X - if(prob(30/severity)) - cargo -= O - O.forceMove(drop_location()) - . = ..() - -/obj/mecha/working/ripley/get_stats_part() - var/output = ..() - output += "Cargo Compartment Contents:
                    " - if(cargo.len) - for(var/obj/O in cargo) - output += "Unload : [O]
                    " - else - output += "Nothing" - output += "
                    " - return output - -/obj/mecha/working/ripley/relay_container_resist(mob/living/user, obj/O) - to_chat(user, "You lean on the back of [O] and start pushing so it falls out of [src].") - if(do_after(user, 300, target = O)) - if(!user || user.stat != CONSCIOUS || user.loc != src || O.loc != src ) - return - to_chat(user, "You successfully pushed [O] out of [src]!") - O.forceMove(drop_location()) - cargo -= O - else - if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. - to_chat(user, "You fail to push [O] out of [src]!") -/** - * Makes the mecha go faster and halves the mecha drill cooldown if in Lavaland pressure. - * - * Checks for Lavaland pressure, if that works out the mech's speed is equal to fast_pressure_step_in and the cooldown for the mecha drill is halved. If not it uses slow_pressure_step_in and drill cooldown is normal. - */ -/obj/mecha/working/ripley/proc/update_pressure() - var/turf/T = get_turf(loc) - - if(lavaland_equipment_pressure_check(T)) - step_in = fast_pressure_step_in - for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) - drill.equip_cooldown = initial(drill.equip_cooldown) * 0.5 - - else - step_in = slow_pressure_step_in - for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) - drill.equip_cooldown = initial(drill.equip_cooldown) +/obj/mecha/working/ripley + desc = "Autonomous Power Loader Unit MK-I. Designed primarily around heavy lifting, the Ripley can be outfitted with utility equipment to fill a number of roles." + name = "\improper APLU MK-I \"Ripley\"" + icon_state = "ripley" + silicon_icon_state = "ripley-empty" + step_in = 1.5 //Move speed, lower is faster. + /// How fast the mech is in low pressure + var/fast_pressure_step_in = 1.5 + /// How fast the mech is in normal pressure + var/slow_pressure_step_in = 2 + max_temperature = 20000 + max_integrity = 200 + lights_power = 7 + deflect_chance = 15 + armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 20, "fire" = 100, "acid" = 100) + max_equip = 6 + wreckage = /obj/structure/mecha_wreckage/ripley + internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_MINING) + enclosed = FALSE //Normal ripley has an open cockpit design + enter_delay = 10 //can enter in a quarter of the time of other mechs + exit_delay = 10 + opacity = FALSE //Ripley has a window + /// Amount of Goliath hides attached to the mech + var/hides = 0 + /// List of all things in Ripley's Cargo Compartment + var/list/cargo = new + /// How much things Ripley can carry in their Cargo Compartment + var/cargo_capacity = 15 + +/obj/mecha/working/ripley/Move() + . = ..() + update_pressure() + +/obj/mecha/working/ripley/go_out() + ..() + update_icon() + +/obj/mecha/working/ripley/moved_inside(mob/living/carbon/human/H) + ..() + update_icon() + +/obj/mecha/working/ripley/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) + if (!enclosed) + possible_int_damage -= (MECHA_INT_TEMP_CONTROL + MECHA_INT_TANK_BREACH) //if we don't even have an air tank, these two doesn't make a ton of sense. + . = ..() + +/obj/mecha/working/ripley/Initialize() + . = ..() + AddComponent(/datum/component/armor_plate,3,/obj/item/stack/sheet/animalhide/goliath_hide,list("melee" = 10, "bullet" = 5, "laser" = 5)) + + +/obj/mecha/working/ripley/Destroy() + for(var/atom/movable/A in cargo) + A.forceMove(drop_location()) + step_rand(A) + cargo.Cut() + return ..() + +/obj/mecha/working/ripley/mkii + desc = "Autonomous Power Loader Unit MK-II. This prototype Ripley is refitted with a pressurized cabin, trading its prior speed for atmospheric protection and armor." + name = "\improper APLU MK-II \"Ripley\"" + icon_state = "ripleymkii" + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 4 //step_in while in normal pressure conditions + step_in = 4 + max_temperature = 30000 + max_integrity = 250 + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 70, "fire" = 100, "acid" = 100) + wreckage = /obj/structure/mecha_wreckage/ripley/mkii + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + + +/obj/mecha/working/ripley/deathripley + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" + name = "\improper DEATH-RIPLEY" + icon_state = "deathripley" + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 3 //step_in while in normal pressure conditions + step_in = 4 + lights_power = 7 + wreckage = /obj/structure/mecha_wreckage/ripley/deathripley + step_energy_drain = 0 + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + +/obj/mecha/working/ripley/deathripley/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill + ME.attach(src) + +/obj/mecha/working/ripley/deathripley/real + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE. FOR REAL" + +/obj/mecha/working/ripley/deathripley/real/Initialize() + . = ..() + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach() + qdel(E) + equipment.Cut() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill/real + ME.attach(src) + +/obj/mecha/working/ripley/mining + desc = "An old, dusty mining Ripley." + name = "\improper APLU \"Miner\"" + obj_integrity = 75 //Low starting health + +/obj/mecha/working/ripley/mining/Initialize() + . = ..() + if(cell) + cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge + if(prob(70)) //Maybe add a drill + if(prob(15)) //Possible diamond drill... Feeling lucky? + var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new + D.attach(src) + else + var/obj/item/mecha_parts/mecha_equipment/drill/D = new + D.attach(src) + + else //Add plasma cutter if no drill + var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new + P.attach(src) + + //Add ore box to cargo + cargo.Add(new /obj/structure/ore_box(src)) + + //Attach hydraulic clamp + var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new + HC.attach(src) + for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily + qdel(B) + + var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new + scanner.attach(src) + +/obj/mecha/working/ripley/Exit(atom/movable/O) + if(O in cargo) + return 0 + return ..() + +/obj/mecha/working/ripley/Topic(href, href_list) + ..() + if(href_list["drop_from_cargo"]) + var/obj/O = locate(href_list["drop_from_cargo"]) in cargo + if(O) + occupant_message("You unload [O].") + O.forceMove(drop_location()) + cargo -= O + log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]", LOG_MECHA) + return + + +/obj/mecha/working/ripley/contents_explosion(severity, target) + for(var/X in cargo) + var/obj/O = X + if(prob(30/severity)) + cargo -= O + O.forceMove(drop_location()) + . = ..() + +/obj/mecha/working/ripley/get_stats_part() + var/output = ..() + output += "Cargo Compartment Contents:
                    " + if(cargo.len) + for(var/obj/O in cargo) + output += "Unload : [O]
                    " + else + output += "Nothing" + output += "
                    " + return output + +/obj/mecha/working/ripley/relay_container_resist(mob/living/user, obj/O) + to_chat(user, "You lean on the back of [O] and start pushing so it falls out of [src].") + if(do_after(user, 300, target = O)) + if(!user || user.stat != CONSCIOUS || user.loc != src || O.loc != src ) + return + to_chat(user, "You successfully pushed [O] out of [src]!") + O.forceMove(drop_location()) + cargo -= O + else + if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. + to_chat(user, "You fail to push [O] out of [src]!") +/** + * Makes the mecha go faster and halves the mecha drill cooldown if in Lavaland pressure. + * + * Checks for Lavaland pressure, if that works out the mech's speed is equal to fast_pressure_step_in and the cooldown for the mecha drill is halved. If not it uses slow_pressure_step_in and drill cooldown is normal. + */ +/obj/mecha/working/ripley/proc/update_pressure() + var/turf/T = get_turf(loc) + + if(lavaland_equipment_pressure_check(T)) + step_in = fast_pressure_step_in + for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) + drill.equip_cooldown = initial(drill.equip_cooldown) * 0.5 + + else + step_in = slow_pressure_step_in + for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) + drill.equip_cooldown = initial(drill.equip_cooldown) diff --git a/code/game/mecha/working/working.dm b/code/game/mecha/working/working.dm index 874313b0666..51213bd110b 100644 --- a/code/game/mecha/working/working.dm +++ b/code/game/mecha/working/working.dm @@ -1,20 +1,20 @@ -/obj/mecha/working - internal_damage_threshold = 60 - -/obj/mecha/working/Move() - . = ..() - if(.) - collect_ore() - -/** - * Handles collecting ore. - * - * Checks for a hydraulic clamp or ore box manager and if it finds an ore box inside them puts ore in the ore box. - */ -/obj/mecha/working/proc/collect_ore() - if((locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) || (locate(/obj/item/mecha_parts/mecha_equipment/orebox_manager) in equipment)) - var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in contents - if(ore_box) - for(var/obj/item/stack/ore/ore in range(1, src)) - if(ore.Adjacent(src) && ((get_dir(src, ore) & dir) || ore.loc == loc)) //we can reach it and it's in front of us? grab it! - ore.forceMove(ore_box) +/obj/mecha/working + internal_damage_threshold = 60 + +/obj/mecha/working/Move() + . = ..() + if(.) + collect_ore() + +/** + * Handles collecting ore. + * + * Checks for a hydraulic clamp or ore box manager and if it finds an ore box inside them puts ore in the ore box. + */ +/obj/mecha/working/proc/collect_ore() + if((locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) || (locate(/obj/item/mecha_parts/mecha_equipment/orebox_manager) in equipment)) + var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in contents + if(ore_box) + for(var/obj/item/stack/ore/ore in range(1, src)) + if(ore.Adjacent(src) && ((get_dir(src, ore) & dir) || ore.loc == loc)) //we can reach it and it's in front of us? grab it! + ore.forceMove(ore_box) diff --git a/code/game/objects/effects/bump_teleporter.dm b/code/game/objects/effects/bump_teleporter.dm index 23b186c0094..0337b076ff7 100644 --- a/code/game/objects/effects/bump_teleporter.dm +++ b/code/game/objects/effects/bump_teleporter.dm @@ -1,37 +1,37 @@ -/obj/effect/bump_teleporter - name = "bump-teleporter" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - var/id = null //id of this bump_teleporter. - var/id_target = null //id of bump_teleporter which this moves you to. - invisibility = INVISIBILITY_ABSTRACT //nope, can't see this - anchored = TRUE - density = TRUE - opacity = 0 - - var/static/list/AllTeleporters - -/obj/effect/bump_teleporter/Initialize() - . = ..() - LAZYADD(AllTeleporters, src) - -/obj/effect/bump_teleporter/Destroy() - LAZYREMOVE(AllTeleporters, src) - return ..() - - -/obj/effect/bump_teleporter/singularity_act() - return - -/obj/effect/bump_teleporter/singularity_pull() - return - -/obj/effect/bump_teleporter/Bumped(atom/movable/AM) - if(!ismob(AM)) - return - if(!id_target) - return - - for(var/obj/effect/bump_teleporter/BT in AllTeleporters) - if(BT.id == src.id_target) - AM.forceMove(BT.loc) //Teleport to location with correct id. +/obj/effect/bump_teleporter + name = "bump-teleporter" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + var/id = null //id of this bump_teleporter. + var/id_target = null //id of bump_teleporter which this moves you to. + invisibility = INVISIBILITY_ABSTRACT //nope, can't see this + anchored = TRUE + density = TRUE + opacity = 0 + + var/static/list/AllTeleporters + +/obj/effect/bump_teleporter/Initialize() + . = ..() + LAZYADD(AllTeleporters, src) + +/obj/effect/bump_teleporter/Destroy() + LAZYREMOVE(AllTeleporters, src) + return ..() + + +/obj/effect/bump_teleporter/singularity_act() + return + +/obj/effect/bump_teleporter/singularity_pull() + return + +/obj/effect/bump_teleporter/Bumped(atom/movable/AM) + if(!ismob(AM)) + return + if(!id_target) + return + + for(var/obj/effect/bump_teleporter/BT in AllTeleporters) + if(BT.id == src.id_target) + AM.forceMove(BT.loc) //Teleport to location with correct id. diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index c6f8422c6cf..47ac77ccc9e 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,111 +1,111 @@ -/obj/effect/decal/cleanable - gender = PLURAL - layer = ABOVE_NORMAL_TURF_LAYER - var/list/random_icon_states = null - var/blood_state = "" //I'm sorry but cleanable/blood code is ass, and so is blood_DNA - var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints - var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one? - var/beauty = 0 - -/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) - . = ..() - if (random_icon_states && (icon_state == initial(icon_state)) && length(random_icon_states) > 0) - icon_state = pick(random_icon_states) - create_reagents(300) - if(loc && isturf(loc)) - for(var/obj/effect/decal/cleanable/C in loc) - if(C != src && C.type == type && !QDELETED(C)) - if (replace_decal(C)) - return INITIALIZE_HINT_QDEL - - if(LAZYLEN(diseases)) - var/list/datum/disease/diseases_to_add = list() - for(var/datum/disease/D in diseases) - if(D.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS) - diseases_to_add += D - if(LAZYLEN(diseases_to_add)) - AddComponent(/datum/component/infective, diseases_to_add) - - addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, beauty)), 0) - - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_created", 1, name) - -/obj/effect/decal/cleanable/Destroy() - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) - return ..() - -/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal - if(mergeable_decal) - return TRUE - -/obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/reagent_containers/glass) || istype(W, /obj/item/reagent_containers/food/drinks)) - if(src.reagents && W.reagents) - . = 1 //so the containers don't splash their content on the src while scooping. - if(!src.reagents.total_volume) - to_chat(user, "[src] isn't thick enough to scoop up!") - return - if(W.reagents.total_volume >= W.reagents.maximum_volume) - to_chat(user, "[W] is full!") - return - to_chat(user, "You scoop up [src] into [W]!") - reagents.trans_to(W, reagents.total_volume, transfered_by = user) - if(!reagents.total_volume) //scooped up all of it - qdel(src) - return - if(W.get_temperature()) //todo: make heating a reagent holder proc - if(istype(W, /obj/item/clothing/mask/cigarette)) - return - else - var/hotness = W.get_temperature() - reagents.expose_temperature(hotness) - to_chat(user, "You heat [name] with [W]!") - else - return ..() - -/obj/effect/decal/cleanable/ex_act() - if(reagents) - for(var/datum/reagent/R in reagents.reagent_list) - R.on_ex_act() - ..() - -/obj/effect/decal/cleanable/fire_act(exposed_temperature, exposed_volume) - if(reagents) - reagents.expose_temperature(exposed_temperature) - ..() - - -//Add "bloodiness" of this blood's type, to the human's shoes -//This is on /cleanable because fuck this ancient mess -/obj/effect/decal/cleanable/Crossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - if(H.shoes && blood_state && bloodiness) - var/obj/item/clothing/shoes/S = H.shoes - if(!S.can_be_bloody) - return - var/add_blood = 0 - if(bloodiness >= BLOOD_GAIN_PER_STEP) - add_blood = BLOOD_GAIN_PER_STEP - else - add_blood = bloodiness - bloodiness -= add_blood - S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS,S.bloody_shoes[blood_state]+add_blood) - S.add_blood_DNA(return_blood_DNA()) - S.blood_state = blood_state - update_icon() - H.update_inv_shoes() -/atom/effect/decal/cleanable/washed(atom/washer) - . = ..() - qdel(src) - -/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return bloodiness - else - return 0 +/obj/effect/decal/cleanable + gender = PLURAL + layer = ABOVE_NORMAL_TURF_LAYER + var/list/random_icon_states = null + var/blood_state = "" //I'm sorry but cleanable/blood code is ass, and so is blood_DNA + var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints + var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one? + var/beauty = 0 + +/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) + . = ..() + if (random_icon_states && (icon_state == initial(icon_state)) && length(random_icon_states) > 0) + icon_state = pick(random_icon_states) + create_reagents(300) + if(loc && isturf(loc)) + for(var/obj/effect/decal/cleanable/C in loc) + if(C != src && C.type == type && !QDELETED(C)) + if (replace_decal(C)) + return INITIALIZE_HINT_QDEL + + if(LAZYLEN(diseases)) + var/list/datum/disease/diseases_to_add = list() + for(var/datum/disease/D in diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS) + diseases_to_add += D + if(LAZYLEN(diseases_to_add)) + AddComponent(/datum/component/infective, diseases_to_add) + + addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, beauty)), 0) + + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_created", 1, name) + +/obj/effect/decal/cleanable/Destroy() + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) + return ..() + +/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal + if(mergeable_decal) + return TRUE + +/obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/reagent_containers/glass) || istype(W, /obj/item/reagent_containers/food/drinks)) + if(src.reagents && W.reagents) + . = 1 //so the containers don't splash their content on the src while scooping. + if(!src.reagents.total_volume) + to_chat(user, "[src] isn't thick enough to scoop up!") + return + if(W.reagents.total_volume >= W.reagents.maximum_volume) + to_chat(user, "[W] is full!") + return + to_chat(user, "You scoop up [src] into [W]!") + reagents.trans_to(W, reagents.total_volume, transfered_by = user) + if(!reagents.total_volume) //scooped up all of it + qdel(src) + return + if(W.get_temperature()) //todo: make heating a reagent holder proc + if(istype(W, /obj/item/clothing/mask/cigarette)) + return + else + var/hotness = W.get_temperature() + reagents.expose_temperature(hotness) + to_chat(user, "You heat [name] with [W]!") + else + return ..() + +/obj/effect/decal/cleanable/ex_act() + if(reagents) + for(var/datum/reagent/R in reagents.reagent_list) + R.on_ex_act() + ..() + +/obj/effect/decal/cleanable/fire_act(exposed_temperature, exposed_volume) + if(reagents) + reagents.expose_temperature(exposed_temperature) + ..() + + +//Add "bloodiness" of this blood's type, to the human's shoes +//This is on /cleanable because fuck this ancient mess +/obj/effect/decal/cleanable/Crossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + if(H.shoes && blood_state && bloodiness) + var/obj/item/clothing/shoes/S = H.shoes + if(!S.can_be_bloody) + return + var/add_blood = 0 + if(bloodiness >= BLOOD_GAIN_PER_STEP) + add_blood = BLOOD_GAIN_PER_STEP + else + add_blood = bloodiness + bloodiness -= add_blood + S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS,S.bloody_shoes[blood_state]+add_blood) + S.add_blood_DNA(return_blood_DNA()) + S.blood_state = blood_state + update_icon() + H.update_inv_shoes() +/atom/effect/decal/cleanable/washed(atom/washer) + . = ..() + qdel(src) + +/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return bloodiness + else + return 0 diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm index 3e36332aa87..6c3a455edde 100644 --- a/code/game/objects/effects/decals/cleanable/aliens.dm +++ b/code/game/objects/effects/decals/cleanable/aliens.dm @@ -1,80 +1,80 @@ -// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. - -/obj/effect/decal/cleanable/xenoblood - name = "xeno blood" - desc = "It's green and acidic. It looks like... blood?" - icon = 'icons/effects/blood.dmi' - icon_state = "xfloor1" - random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") - bloodiness = BLOOD_AMOUNT_PER_DECAL - blood_state = BLOOD_STATE_XENO - beauty = -250 - -/obj/effect/decal/cleanable/xenoblood/Initialize() - . = ..() - add_blood_DNA(list("UNKNOWN DNA" = "X*")) - -/obj/effect/decal/cleanable/xenoblood/xsplatter - random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5") - -/obj/effect/decal/cleanable/xenoblood/xgibs - name = "xeno gibs" - desc = "Gnarly..." - icon = 'icons/effects/blood.dmi' - icon_state = "xgib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6") - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions) - set waitfor = 0 - var/direction = pick(directions) - for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) - sleep(2) - if(i > 0) - new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc) - if(!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/xenoblood/xgibs/ex_act() - return - -/obj/effect/decal/cleanable/xenoblood/xgibs/up - icon_state = "xgibup1" - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibup1","xgibup1","xgibup1") - -/obj/effect/decal/cleanable/xenoblood/xgibs/down - icon_state = "xgibdown1" - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibdown1","xgibdown1","xgibdown1") - -/obj/effect/decal/cleanable/xenoblood/xgibs/body - icon_state = "xgibtorso" - random_icon_states = list("xgibhead", "xgibtorso") - -/obj/effect/decal/cleanable/xenoblood/xgibs/torso - icon_state = "xgibtorso" - random_icon_states = list("xgibtorso") - -/obj/effect/decal/cleanable/xenoblood/xgibs/limb - icon_state = "xgibleg" - random_icon_states = list("xgibleg", "xgibarm") - -/obj/effect/decal/cleanable/xenoblood/xgibs/core - icon_state = "xgibmid1" - random_icon_states = list("xgibmid1", "xgibmid2", "xgibmid3") - -/obj/effect/decal/cleanable/xenoblood/xgibs/larva - icon_state = "xgiblarva1" - random_icon_states = list("xgiblarva1", "xgiblarva2") - -/obj/effect/decal/cleanable/xenoblood/xgibs/larva/body - icon_state = "xgiblarvatorso" - random_icon_states = list("xgiblarvahead", "xgiblarvatorso") - -/obj/effect/decal/cleanable/blood/xtracks - icon_state = "xtracks" - random_icon_states = null - -/obj/effect/decal/cleanable/blood/xtracks/Initialize() - . = ..() - add_blood_DNA(list("Unknown DNA" = "X*")) +// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. + +/obj/effect/decal/cleanable/xenoblood + name = "xeno blood" + desc = "It's green and acidic. It looks like... blood?" + icon = 'icons/effects/blood.dmi' + icon_state = "xfloor1" + random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") + bloodiness = BLOOD_AMOUNT_PER_DECAL + blood_state = BLOOD_STATE_XENO + beauty = -250 + +/obj/effect/decal/cleanable/xenoblood/Initialize() + . = ..() + add_blood_DNA(list("UNKNOWN DNA" = "X*")) + +/obj/effect/decal/cleanable/xenoblood/xsplatter + random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5") + +/obj/effect/decal/cleanable/xenoblood/xgibs + name = "xeno gibs" + desc = "Gnarly..." + icon = 'icons/effects/blood.dmi' + icon_state = "xgib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6") + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions) + set waitfor = 0 + var/direction = pick(directions) + for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) + sleep(2) + if(i > 0) + new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc) + if(!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/xenoblood/xgibs/ex_act() + return + +/obj/effect/decal/cleanable/xenoblood/xgibs/up + icon_state = "xgibup1" + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibup1","xgibup1","xgibup1") + +/obj/effect/decal/cleanable/xenoblood/xgibs/down + icon_state = "xgibdown1" + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibdown1","xgibdown1","xgibdown1") + +/obj/effect/decal/cleanable/xenoblood/xgibs/body + icon_state = "xgibtorso" + random_icon_states = list("xgibhead", "xgibtorso") + +/obj/effect/decal/cleanable/xenoblood/xgibs/torso + icon_state = "xgibtorso" + random_icon_states = list("xgibtorso") + +/obj/effect/decal/cleanable/xenoblood/xgibs/limb + icon_state = "xgibleg" + random_icon_states = list("xgibleg", "xgibarm") + +/obj/effect/decal/cleanable/xenoblood/xgibs/core + icon_state = "xgibmid1" + random_icon_states = list("xgibmid1", "xgibmid2", "xgibmid3") + +/obj/effect/decal/cleanable/xenoblood/xgibs/larva + icon_state = "xgiblarva1" + random_icon_states = list("xgiblarva1", "xgiblarva2") + +/obj/effect/decal/cleanable/xenoblood/xgibs/larva/body + icon_state = "xgiblarvatorso" + random_icon_states = list("xgiblarvahead", "xgiblarvatorso") + +/obj/effect/decal/cleanable/blood/xtracks + icon_state = "xtracks" + random_icon_states = null + +/obj/effect/decal/cleanable/blood/xtracks/Initialize() + . = ..() + add_blood_DNA(list("Unknown DNA" = "X*")) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index edafa39828a..95e17007b98 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -1,239 +1,239 @@ -/obj/effect/decal/cleanable/blood - name = "blood" - desc = "It's red and gooey. Perhaps it's the chef's cooking?" - icon = 'icons/effects/blood.dmi' - icon_state = "floor1" - random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") - blood_state = BLOOD_STATE_HUMAN - bloodiness = BLOOD_AMOUNT_PER_DECAL - beauty = -100 - -/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) - C.add_blood_DNA(return_blood_DNA()) - if (bloodiness) - C.bloodiness = min((C.bloodiness + bloodiness),MAX_SHOE_BLOODINESS) - return ..() - -/obj/effect/decal/cleanable/blood/old - name = "dried blood" - desc = "Looks like it's been here a while. Eew." - bloodiness = 0 - icon_state = "floor1-old" - -/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) - add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..() - . = ..() - icon_state = "[icon_state]-old" //change from the normal blood icon selected from random_icon_states in the parent's Initialize to the old dried up blood. - -/obj/effect/decal/cleanable/blood/splatter - icon_state = "gibbl1" - random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") - -/obj/effect/decal/cleanable/blood/tracks - icon_state = "tracks" - desc = "They look like tracks left by wheels." - random_icon_states = null - beauty = -50 - -/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose - name = "blood" - icon = 'icons/effects/blood.dmi' - desc = "Your instincts say you shouldn't be following these." - beauty = -50 - var/list/existing_dirs = list() - -/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() - return TRUE - -/obj/effect/decal/cleanable/blood/gibs - name = "gibs" - desc = "They look bloody and gruesome." - icon = 'icons/effects/blood.dmi' - icon_state = "gib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") - mergeable_decal = FALSE - - var/already_rotting = FALSE - -/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases) - . = ..() - reagents.add_reagent(/datum/reagent/liquidgibs, 5) - if(already_rotting) - start_rotting(rename=FALSE) - else - addtimer(CALLBACK(src, .proc/start_rotting), 2 MINUTES) - -/obj/effect/decal/cleanable/blood/gibs/proc/start_rotting(rename=TRUE) - if(rename) - name = "rotting [initial(name)]" - desc += " They smell terrible." - AddComponent(/datum/component/rot/gibs) - -/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target) - return - -/obj/effect/decal/cleanable/blood/gibs/Crossed(atom/movable/L) - if(isliving(L) && has_gravity(loc)) - playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, TRUE) - . = ..() - -/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions) - set waitfor = FALSE - var/list/diseases = list() - SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases) - var/direction = pick(directions) - for(var/i in 0 to pick(0, 200; 1, 150; 2, 50)) - sleep(2) - if(i > 0) - new /obj/effect/decal/cleanable/blood/splatter(loc, diseases) - if(!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/blood/gibs/up - icon_state = "gibup1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") - -/obj/effect/decal/cleanable/blood/gibs/down - icon_state = "gibdown1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") - -/obj/effect/decal/cleanable/blood/gibs/body - icon_state = "gibtorso" - random_icon_states = list("gibhead", "gibtorso") - -/obj/effect/decal/cleanable/blood/gibs/torso - icon_state = "gibtorso" - random_icon_states = null - -/obj/effect/decal/cleanable/blood/gibs/limb - icon_state = "gibleg" - random_icon_states = list("gibleg", "gibarm") - -/obj/effect/decal/cleanable/blood/gibs/core - icon_state = "gibmid1" - random_icon_states = list("gibmid1", "gibmid2", "gibmid3") - -/obj/effect/decal/cleanable/blood/gibs/old - name = "old rotting gibs" - desc = "Space Jesus, why didn't anyone clean this up? They smell terrible." - bloodiness = 0 - already_rotting = TRUE - -/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases) - . = ..() - setDir(pick(1,2,4,8)) - icon_state += "-old" - add_blood_DNA(list("Non-human DNA" = random_blood_type())) - -/obj/effect/decal/cleanable/blood/drip - name = "drips of blood" - desc = "It's red." - icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires. - random_icon_states = list("drip1","drip2","drip3","drip4","drip5") - bloodiness = 0 - var/drips = 1 - -/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in() - return TRUE - - -//BLOODY FOOTPRINTS -/obj/effect/decal/cleanable/blood/footprints - name = "footprints" - desc = "WHOSE FOOTPRINTS ARE THESE?" - icon = 'icons/effects/footprints.dmi' - icon_state = "blood1" - random_icon_states = null - blood_state = BLOOD_STATE_HUMAN //the icon state to load images from - var/entered_dirs = 0 - var/exited_dirs = 0 - var/list/shoe_types = list() - -/obj/effect/decal/cleanable/blood/footprints/Initialize(mapload) - . = ..() - icon_state = "" //All of the footprint visuals come from overlays - if(mapload) - entered_dirs |= dir //Keep the same appearance as in the map editor - update_icon() - -//Rotate all of the footprint directions too -/obj/effect/decal/cleanable/blood/footprints/setDir(newdir) - if(dir == newdir) - return ..() - - var/ang_change = dir2angle(newdir) - dir2angle(dir) - var/old_entered_dirs = entered_dirs - var/old_exited_dirs = exited_dirs - entered_dirs = 0 - exited_dirs = 0 - - for(var/Ddir in GLOB.cardinals) - if(old_entered_dirs & Ddir) - entered_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) - if(old_exited_dirs & Ddir) - exited_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) - - update_icon() - return ..() - -/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - var/obj/item/clothing/shoes/S = H.shoes - if(S && S.bloody_shoes[blood_state]) - S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) - shoe_types |= S.type - if (!(entered_dirs & H.dir)) - entered_dirs |= H.dir - update_icon() - -/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - var/obj/item/clothing/shoes/S = H.shoes - if(S && S.bloody_shoes[blood_state]) - S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) - shoe_types |= S.type - if (!(exited_dirs & H.dir)) - exited_dirs |= H.dir - update_icon() - - -/obj/effect/decal/cleanable/blood/footprints/update_icon() - cut_overlays() - - for(var/Ddir in GLOB.cardinals) - if(entered_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] - if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]1", dir = Ddir) - add_overlay(bloodstep_overlay) - if(exited_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] - if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir) - add_overlay(bloodstep_overlay) - - alpha = BLOODY_FOOTPRINT_BASE_ALPHA+bloodiness - - -/obj/effect/decal/cleanable/blood/footprints/examine(mob/user) - . = ..() - if(shoe_types.len) - . += "You recognise the footprints as belonging to:\n" - for(var/shoe in shoe_types) - var/obj/item/clothing/shoes/S = shoe - . += "[icon2html(initial(S.icon), user)] Some [initial(S.name)].\n" - -/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C) - if(blood_state != C.blood_state) //We only replace footprints of the same type as us - return - ..() - -/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return 1 - return 0 +/obj/effect/decal/cleanable/blood + name = "blood" + desc = "It's red and gooey. Perhaps it's the chef's cooking?" + icon = 'icons/effects/blood.dmi' + icon_state = "floor1" + random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") + blood_state = BLOOD_STATE_HUMAN + bloodiness = BLOOD_AMOUNT_PER_DECAL + beauty = -100 + +/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) + C.add_blood_DNA(return_blood_DNA()) + if (bloodiness) + C.bloodiness = min((C.bloodiness + bloodiness),MAX_SHOE_BLOODINESS) + return ..() + +/obj/effect/decal/cleanable/blood/old + name = "dried blood" + desc = "Looks like it's been here a while. Eew." + bloodiness = 0 + icon_state = "floor1-old" + +/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) + add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..() + . = ..() + icon_state = "[icon_state]-old" //change from the normal blood icon selected from random_icon_states in the parent's Initialize to the old dried up blood. + +/obj/effect/decal/cleanable/blood/splatter + icon_state = "gibbl1" + random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") + +/obj/effect/decal/cleanable/blood/tracks + icon_state = "tracks" + desc = "They look like tracks left by wheels." + random_icon_states = null + beauty = -50 + +/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose + name = "blood" + icon = 'icons/effects/blood.dmi' + desc = "Your instincts say you shouldn't be following these." + beauty = -50 + var/list/existing_dirs = list() + +/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() + return TRUE + +/obj/effect/decal/cleanable/blood/gibs + name = "gibs" + desc = "They look bloody and gruesome." + icon = 'icons/effects/blood.dmi' + icon_state = "gib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") + mergeable_decal = FALSE + + var/already_rotting = FALSE + +/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases) + . = ..() + reagents.add_reagent(/datum/reagent/liquidgibs, 5) + if(already_rotting) + start_rotting(rename=FALSE) + else + addtimer(CALLBACK(src, .proc/start_rotting), 2 MINUTES) + +/obj/effect/decal/cleanable/blood/gibs/proc/start_rotting(rename=TRUE) + if(rename) + name = "rotting [initial(name)]" + desc += " They smell terrible." + AddComponent(/datum/component/rot/gibs) + +/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target) + return + +/obj/effect/decal/cleanable/blood/gibs/Crossed(atom/movable/L) + if(isliving(L) && has_gravity(loc)) + playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, TRUE) + . = ..() + +/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions) + set waitfor = FALSE + var/list/diseases = list() + SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases) + var/direction = pick(directions) + for(var/i in 0 to pick(0, 200; 1, 150; 2, 50)) + sleep(2) + if(i > 0) + new /obj/effect/decal/cleanable/blood/splatter(loc, diseases) + if(!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/blood/gibs/up + icon_state = "gibup1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") + +/obj/effect/decal/cleanable/blood/gibs/down + icon_state = "gibdown1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") + +/obj/effect/decal/cleanable/blood/gibs/body + icon_state = "gibtorso" + random_icon_states = list("gibhead", "gibtorso") + +/obj/effect/decal/cleanable/blood/gibs/torso + icon_state = "gibtorso" + random_icon_states = null + +/obj/effect/decal/cleanable/blood/gibs/limb + icon_state = "gibleg" + random_icon_states = list("gibleg", "gibarm") + +/obj/effect/decal/cleanable/blood/gibs/core + icon_state = "gibmid1" + random_icon_states = list("gibmid1", "gibmid2", "gibmid3") + +/obj/effect/decal/cleanable/blood/gibs/old + name = "old rotting gibs" + desc = "Space Jesus, why didn't anyone clean this up? They smell terrible." + bloodiness = 0 + already_rotting = TRUE + +/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases) + . = ..() + setDir(pick(1,2,4,8)) + icon_state += "-old" + add_blood_DNA(list("Non-human DNA" = random_blood_type())) + +/obj/effect/decal/cleanable/blood/drip + name = "drips of blood" + desc = "It's red." + icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires. + random_icon_states = list("drip1","drip2","drip3","drip4","drip5") + bloodiness = 0 + var/drips = 1 + +/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in() + return TRUE + + +//BLOODY FOOTPRINTS +/obj/effect/decal/cleanable/blood/footprints + name = "footprints" + desc = "WHOSE FOOTPRINTS ARE THESE?" + icon = 'icons/effects/footprints.dmi' + icon_state = "blood1" + random_icon_states = null + blood_state = BLOOD_STATE_HUMAN //the icon state to load images from + var/entered_dirs = 0 + var/exited_dirs = 0 + var/list/shoe_types = list() + +/obj/effect/decal/cleanable/blood/footprints/Initialize(mapload) + . = ..() + icon_state = "" //All of the footprint visuals come from overlays + if(mapload) + entered_dirs |= dir //Keep the same appearance as in the map editor + update_icon() + +//Rotate all of the footprint directions too +/obj/effect/decal/cleanable/blood/footprints/setDir(newdir) + if(dir == newdir) + return ..() + + var/ang_change = dir2angle(newdir) - dir2angle(dir) + var/old_entered_dirs = entered_dirs + var/old_exited_dirs = exited_dirs + entered_dirs = 0 + exited_dirs = 0 + + for(var/Ddir in GLOB.cardinals) + if(old_entered_dirs & Ddir) + entered_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) + if(old_exited_dirs & Ddir) + exited_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) + + update_icon() + return ..() + +/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + var/obj/item/clothing/shoes/S = H.shoes + if(S && S.bloody_shoes[blood_state]) + S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) + shoe_types |= S.type + if (!(entered_dirs & H.dir)) + entered_dirs |= H.dir + update_icon() + +/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + var/obj/item/clothing/shoes/S = H.shoes + if(S && S.bloody_shoes[blood_state]) + S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) + shoe_types |= S.type + if (!(exited_dirs & H.dir)) + exited_dirs |= H.dir + update_icon() + + +/obj/effect/decal/cleanable/blood/footprints/update_icon() + cut_overlays() + + for(var/Ddir in GLOB.cardinals) + if(entered_dirs & Ddir) + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] + if(!bloodstep_overlay) + GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]1", dir = Ddir) + add_overlay(bloodstep_overlay) + if(exited_dirs & Ddir) + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] + if(!bloodstep_overlay) + GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir) + add_overlay(bloodstep_overlay) + + alpha = BLOODY_FOOTPRINT_BASE_ALPHA+bloodiness + + +/obj/effect/decal/cleanable/blood/footprints/examine(mob/user) + . = ..() + if(shoe_types.len) + . += "You recognise the footprints as belonging to:\n" + for(var/shoe in shoe_types) + var/obj/item/clothing/shoes/S = shoe + . += "[icon2html(initial(S.icon), user)] Some [initial(S.name)].\n" + +/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C) + if(blood_state != C.blood_state) //We only replace footprints of the same type as us + return + ..() + +/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return 1 + return 0 diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 7ba12e24770..ac28e5f731c 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -1,239 +1,239 @@ -/obj/effect/decal/cleanable/generic - name = "clutter" - desc = "Someone should clean that up." - icon = 'icons/obj/objects.dmi' - icon_state = "shards" - beauty = -50 - -/obj/effect/decal/cleanable/ash - name = "ashes" - desc = "Ashes to ashes, dust to dust, and into space." - icon = 'icons/obj/objects.dmi' - icon_state = "ash" - mergeable_decal = FALSE - beauty = -50 - -/obj/effect/decal/cleanable/ash/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/ash, 30) - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/effect/decal/cleanable/ash/crematorium -//crematoriums need their own ash cause default ash deletes itself if created in an obj - turf_loc_check = FALSE - -/obj/effect/decal/cleanable/ash/large - name = "large pile of ashes" - icon_state = "big_ash" - beauty = -100 - -/obj/effect/decal/cleanable/ash/large/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/ash, 30) //double the amount of ash. - -/obj/effect/decal/cleanable/glass - name = "tiny shards" - desc = "Back to sand." - icon = 'icons/obj/shards.dmi' - icon_state = "tiny" - beauty = -100 - -/obj/effect/decal/cleanable/glass/Initialize() - . = ..() - setDir(pick(GLOB.cardinals)) - -/obj/effect/decal/cleanable/glass/ex_act() - qdel(src) - -/obj/effect/decal/cleanable/glass/plasma - icon_state = "plasmatiny" - -/obj/effect/decal/cleanable/dirt - name = "dirt" - desc = "Someone should clean that up." - icon_state = "dirt" - canSmoothWith = list(/obj/effect/decal/cleanable/dirt, /turf/closed/wall, /obj/structure/falsewall) - smooth = SMOOTH_FALSE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - beauty = -75 - -/obj/effect/decal/cleanable/dirt/Initialize() - . = ..() - var/turf/T = get_turf(src) - if(T.tiled_dirt) - smooth = SMOOTH_MORE - icon = 'icons/effects/dirt.dmi' - icon_state = "" - queue_smooth(src) - queue_smooth_neighbors(src) - -/obj/effect/decal/cleanable/dirt/Destroy() - queue_smooth_neighbors(src) - return ..() - -/obj/effect/decal/cleanable/dirt/dust - name = "dust" - desc = "A thin layer of dust coating the floor." - -/obj/effect/decal/cleanable/greenglow - name = "glowing goo" - desc = "Jeez. I hope that's not for lunch." - icon_state = "greenglow" - light_power = 3 - light_range = 2 - light_color = LIGHT_COLOR_GREEN - beauty = -300 - -/obj/effect/decal/cleanable/greenglow/ex_act() - return - -/obj/effect/decal/cleanable/greenglow/filled/Initialize() - . = ..() - reagents.add_reagent(pick(/datum/reagent/uranium, /datum/reagent/uranium/radium), 5) - -/obj/effect/decal/cleanable/greenglow/ecto - name = "ectoplasmic puddle" - desc = "You know who to call." - light_power = 2 - -/obj/effect/decal/cleanable/cobweb - name = "cobweb" - desc = "Somebody should remove that." - gender = NEUTER - layer = WALL_OBJ_LAYER - icon_state = "cobweb1" - resistance_flags = FLAMMABLE - beauty = -100 - -/obj/effect/decal/cleanable/cobweb/cobweb2 - icon_state = "cobweb2" - -/obj/effect/decal/cleanable/molten_object - name = "gooey grey mass" - desc = "It looks like a melted... something." - gender = NEUTER - icon = 'icons/effects/effects.dmi' - icon_state = "molten" - mergeable_decal = FALSE - beauty = -150 - -/obj/effect/decal/cleanable/molten_object/large - name = "big gooey grey mass" - icon_state = "big_molten" - beauty = -300 - -//Vomit (sorry) -/obj/effect/decal/cleanable/vomit - name = "vomit" - desc = "Gosh, how unpleasant." - icon = 'icons/effects/blood.dmi' - icon_state = "vomit_1" - random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4") - beauty = -150 - -/obj/effect/decal/cleanable/vomit/attack_hand(mob/user) - . = ..() - if(.) - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(isflyperson(H)) - playsound(get_turf(src), 'sound/items/drink.ogg', 50, TRUE) //slurp - H.visible_message("[H] extends a small proboscis into the vomit pool, sucking it with a slurping sound.") - if(reagents) - for(var/datum/reagent/R in reagents.reagent_list) - if (istype(R, /datum/reagent/consumable)) - var/datum/reagent/consumable/nutri_check = R - if(nutri_check.nutriment_factor >0) - H.adjust_nutrition(nutri_check.nutriment_factor * nutri_check.volume) - reagents.remove_reagent(nutri_check.type,nutri_check.volume) - reagents.trans_to(H, reagents.total_volume, transfered_by = user) - qdel(src) - -/obj/effect/decal/cleanable/vomit/old - name = "crusty dried vomit" - desc = "You try not to look at the chunks, and fail." - -/obj/effect/decal/cleanable/vomit/old/Initialize(mapload, list/datum/disease/diseases) - . = ..() - icon_state += "-old" - -/obj/effect/decal/cleanable/chem_pile - name = "chemical pile" - desc = "A pile of chemicals. You can't quite tell what's inside it." - gender = NEUTER - icon = 'icons/obj/objects.dmi' - icon_state = "ash" - -/obj/effect/decal/cleanable/shreds - name = "shreds" - desc = "The shredded remains of what appears to be clothing." - icon_state = "shreds" - gender = PLURAL - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/shreds/ex_act(severity, target) - if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion. - qdel(src) - -/obj/effect/decal/cleanable/shreds/Initialize(mapload, oldname) - pixel_x = rand(-10, 10) - pixel_y = rand(-10, 10) - if(!isnull(oldname)) - desc = "The sad remains of what used to be [oldname]" - . = ..() - -/obj/effect/decal/cleanable/glitter - name = "generic glitter pile" - desc = "The herpes of arts and crafts." - icon = 'icons/effects/atmospherics.dmi' - icon_state = "plasma_old" - gender = NEUTER - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/decal/cleanable/glitter/pink - name = "pink glitter" - icon_state = "plasma" - -/obj/effect/decal/cleanable/glitter/white - name = "white glitter" - icon_state = "nitrous_oxide" - -/obj/effect/decal/cleanable/glitter/blue - name = "blue glitter" - icon_state = "freon" - -/obj/effect/decal/cleanable/plasma - name = "stabilized plasma" - desc = "A puddle of stabilized plasma." - icon_state = "flour" - icon = 'icons/effects/tomatodecal.dmi' - color = "#2D2D2D" - -/obj/effect/decal/cleanable/insectguts - name = "insect guts" - desc = "One bug squashed. Four more will rise in its place." - icon = 'icons/effects/blood.dmi' - icon_state = "xfloor1" - random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") - -/obj/effect/decal/cleanable/confetti - name = "confetti" - desc = "Tiny bits of colored paper thrown about for the janitor to enjoy!" - icon = 'icons/effects/confetti_and_decor.dmi' - icon_state = "confetti" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT //the confetti itself might be annoying enough - -/obj/effect/decal/cleanable/plastic - name = "plastic shreds" - desc = "Bits of torn, broken, worthless plastic." - icon = 'icons/obj/objects.dmi' - icon_state = "shards" - color = "#c6f4ff" - -/obj/effect/decal/cleanable/wrapping - name = "wrapping shreds" - desc = "Torn pieces of cardboard and paper, left over from a package." - icon = 'icons/obj/objects.dmi' - icon_state = "paper_shreds" +/obj/effect/decal/cleanable/generic + name = "clutter" + desc = "Someone should clean that up." + icon = 'icons/obj/objects.dmi' + icon_state = "shards" + beauty = -50 + +/obj/effect/decal/cleanable/ash + name = "ashes" + desc = "Ashes to ashes, dust to dust, and into space." + icon = 'icons/obj/objects.dmi' + icon_state = "ash" + mergeable_decal = FALSE + beauty = -50 + +/obj/effect/decal/cleanable/ash/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/ash, 30) + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/effect/decal/cleanable/ash/crematorium +//crematoriums need their own ash cause default ash deletes itself if created in an obj + turf_loc_check = FALSE + +/obj/effect/decal/cleanable/ash/large + name = "large pile of ashes" + icon_state = "big_ash" + beauty = -100 + +/obj/effect/decal/cleanable/ash/large/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/ash, 30) //double the amount of ash. + +/obj/effect/decal/cleanable/glass + name = "tiny shards" + desc = "Back to sand." + icon = 'icons/obj/shards.dmi' + icon_state = "tiny" + beauty = -100 + +/obj/effect/decal/cleanable/glass/Initialize() + . = ..() + setDir(pick(GLOB.cardinals)) + +/obj/effect/decal/cleanable/glass/ex_act() + qdel(src) + +/obj/effect/decal/cleanable/glass/plasma + icon_state = "plasmatiny" + +/obj/effect/decal/cleanable/dirt + name = "dirt" + desc = "Someone should clean that up." + icon_state = "dirt" + canSmoothWith = list(/obj/effect/decal/cleanable/dirt, /turf/closed/wall, /obj/structure/falsewall) + smooth = SMOOTH_FALSE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + beauty = -75 + +/obj/effect/decal/cleanable/dirt/Initialize() + . = ..() + var/turf/T = get_turf(src) + if(T.tiled_dirt) + smooth = SMOOTH_MORE + icon = 'icons/effects/dirt.dmi' + icon_state = "" + queue_smooth(src) + queue_smooth_neighbors(src) + +/obj/effect/decal/cleanable/dirt/Destroy() + queue_smooth_neighbors(src) + return ..() + +/obj/effect/decal/cleanable/dirt/dust + name = "dust" + desc = "A thin layer of dust coating the floor." + +/obj/effect/decal/cleanable/greenglow + name = "glowing goo" + desc = "Jeez. I hope that's not for lunch." + icon_state = "greenglow" + light_power = 3 + light_range = 2 + light_color = LIGHT_COLOR_GREEN + beauty = -300 + +/obj/effect/decal/cleanable/greenglow/ex_act() + return + +/obj/effect/decal/cleanable/greenglow/filled/Initialize() + . = ..() + reagents.add_reagent(pick(/datum/reagent/uranium, /datum/reagent/uranium/radium), 5) + +/obj/effect/decal/cleanable/greenglow/ecto + name = "ectoplasmic puddle" + desc = "You know who to call." + light_power = 2 + +/obj/effect/decal/cleanable/cobweb + name = "cobweb" + desc = "Somebody should remove that." + gender = NEUTER + layer = WALL_OBJ_LAYER + icon_state = "cobweb1" + resistance_flags = FLAMMABLE + beauty = -100 + +/obj/effect/decal/cleanable/cobweb/cobweb2 + icon_state = "cobweb2" + +/obj/effect/decal/cleanable/molten_object + name = "gooey grey mass" + desc = "It looks like a melted... something." + gender = NEUTER + icon = 'icons/effects/effects.dmi' + icon_state = "molten" + mergeable_decal = FALSE + beauty = -150 + +/obj/effect/decal/cleanable/molten_object/large + name = "big gooey grey mass" + icon_state = "big_molten" + beauty = -300 + +//Vomit (sorry) +/obj/effect/decal/cleanable/vomit + name = "vomit" + desc = "Gosh, how unpleasant." + icon = 'icons/effects/blood.dmi' + icon_state = "vomit_1" + random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4") + beauty = -150 + +/obj/effect/decal/cleanable/vomit/attack_hand(mob/user) + . = ..() + if(.) + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(isflyperson(H)) + playsound(get_turf(src), 'sound/items/drink.ogg', 50, TRUE) //slurp + H.visible_message("[H] extends a small proboscis into the vomit pool, sucking it with a slurping sound.") + if(reagents) + for(var/datum/reagent/R in reagents.reagent_list) + if (istype(R, /datum/reagent/consumable)) + var/datum/reagent/consumable/nutri_check = R + if(nutri_check.nutriment_factor >0) + H.adjust_nutrition(nutri_check.nutriment_factor * nutri_check.volume) + reagents.remove_reagent(nutri_check.type,nutri_check.volume) + reagents.trans_to(H, reagents.total_volume, transfered_by = user) + qdel(src) + +/obj/effect/decal/cleanable/vomit/old + name = "crusty dried vomit" + desc = "You try not to look at the chunks, and fail." + +/obj/effect/decal/cleanable/vomit/old/Initialize(mapload, list/datum/disease/diseases) + . = ..() + icon_state += "-old" + +/obj/effect/decal/cleanable/chem_pile + name = "chemical pile" + desc = "A pile of chemicals. You can't quite tell what's inside it." + gender = NEUTER + icon = 'icons/obj/objects.dmi' + icon_state = "ash" + +/obj/effect/decal/cleanable/shreds + name = "shreds" + desc = "The shredded remains of what appears to be clothing." + icon_state = "shreds" + gender = PLURAL + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/shreds/ex_act(severity, target) + if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion. + qdel(src) + +/obj/effect/decal/cleanable/shreds/Initialize(mapload, oldname) + pixel_x = rand(-10, 10) + pixel_y = rand(-10, 10) + if(!isnull(oldname)) + desc = "The sad remains of what used to be [oldname]" + . = ..() + +/obj/effect/decal/cleanable/glitter + name = "generic glitter pile" + desc = "The herpes of arts and crafts." + icon = 'icons/effects/atmospherics.dmi' + icon_state = "plasma_old" + gender = NEUTER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/decal/cleanable/glitter/pink + name = "pink glitter" + icon_state = "plasma" + +/obj/effect/decal/cleanable/glitter/white + name = "white glitter" + icon_state = "nitrous_oxide" + +/obj/effect/decal/cleanable/glitter/blue + name = "blue glitter" + icon_state = "freon" + +/obj/effect/decal/cleanable/plasma + name = "stabilized plasma" + desc = "A puddle of stabilized plasma." + icon_state = "flour" + icon = 'icons/effects/tomatodecal.dmi' + color = "#2D2D2D" + +/obj/effect/decal/cleanable/insectguts + name = "insect guts" + desc = "One bug squashed. Four more will rise in its place." + icon = 'icons/effects/blood.dmi' + icon_state = "xfloor1" + random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") + +/obj/effect/decal/cleanable/confetti + name = "confetti" + desc = "Tiny bits of colored paper thrown about for the janitor to enjoy!" + icon = 'icons/effects/confetti_and_decor.dmi' + icon_state = "confetti" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT //the confetti itself might be annoying enough + +/obj/effect/decal/cleanable/plastic + name = "plastic shreds" + desc = "Bits of torn, broken, worthless plastic." + icon = 'icons/obj/objects.dmi' + icon_state = "shards" + color = "#c6f4ff" + +/obj/effect/decal/cleanable/wrapping + name = "wrapping shreds" + desc = "Torn pieces of cardboard and paper, left over from a package." + icon = 'icons/obj/objects.dmi' + icon_state = "paper_shreds" diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm index 937c620a4ae..79059b51f35 100644 --- a/code/game/objects/effects/decals/cleanable/robots.dm +++ b/code/game/objects/effects/decals/cleanable/robots.dm @@ -1,83 +1,83 @@ -// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. - -/obj/effect/decal/cleanable/robot_debris - name = "robot debris" - desc = "It's a useless heap of junk... or is it?" - icon = 'icons/mob/robots.dmi' - icon_state = "gib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") - blood_state = BLOOD_STATE_OIL - bloodiness = BLOOD_AMOUNT_PER_DECAL - mergeable_decal = FALSE - beauty = -50 - -/obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions) - set waitfor = 0 - var/direction = pick(directions) - for (var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) - sleep(2) - if (i > 0) - if (prob(40)) - new /obj/effect/decal/cleanable/oil/streak(src.loc) - else if (prob(10)) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - if (!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/robot_debris/ex_act() - return - -/obj/effect/decal/cleanable/robot_debris/limb - icon_state = "gibarm" - random_icon_states = list("gibarm", "gibleg") - -/obj/effect/decal/cleanable/robot_debris/up - icon_state = "gibup1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibup1","gibup1") - -/obj/effect/decal/cleanable/robot_debris/down - icon_state = "gibdown1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibdown1","gibdown1") - -/obj/effect/decal/cleanable/oil - name = "motor oil" - desc = "It's black and greasy. Looks like Beepsky made another mess." - icon = 'icons/mob/robots.dmi' - icon_state = "floor1" - random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") - blood_state = BLOOD_STATE_OIL - bloodiness = BLOOD_AMOUNT_PER_DECAL - beauty = -100 - -/obj/effect/decal/cleanable/oil/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/fuel/oil, 30) - -/obj/effect/decal/cleanable/oil/attackby(obj/item/I, mob/living/user) - var/attacked_by_hot_thing = I.get_temperature() - if(attacked_by_hot_thing) - visible_message("[user] tries to ignite [src] with [I]!", "You try to ignite [src] with [I].") - log_combat(user, src, (attacked_by_hot_thing < 480) ? "tried to ignite" : "ignited", I) - fire_act(attacked_by_hot_thing) - return - return ..() - -/obj/effect/decal/cleanable/oil/fire_act(exposed_temperature, exposed_volume) - if(exposed_temperature < 480) - return - visible_message("[src] catches fire!") - var/turf/T = get_turf(src) - qdel(src) - new /obj/effect/hotspot(T) - -/obj/effect/decal/cleanable/oil/streak - icon_state = "streak1" - random_icon_states = list("streak1", "streak2", "streak3", "streak4", "streak5") - beauty = -50 - -/obj/effect/decal/cleanable/oil/slippery/ComponentInitialize() - . = ..() - AddComponent(/datum/component/slippery, 80, (NO_SLIP_WHEN_WALKING | SLIDE)) +// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. + +/obj/effect/decal/cleanable/robot_debris + name = "robot debris" + desc = "It's a useless heap of junk... or is it?" + icon = 'icons/mob/robots.dmi' + icon_state = "gib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") + blood_state = BLOOD_STATE_OIL + bloodiness = BLOOD_AMOUNT_PER_DECAL + mergeable_decal = FALSE + beauty = -50 + +/obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions) + set waitfor = 0 + var/direction = pick(directions) + for (var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) + sleep(2) + if (i > 0) + if (prob(40)) + new /obj/effect/decal/cleanable/oil/streak(src.loc) + else if (prob(10)) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, src) + s.start() + if (!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/robot_debris/ex_act() + return + +/obj/effect/decal/cleanable/robot_debris/limb + icon_state = "gibarm" + random_icon_states = list("gibarm", "gibleg") + +/obj/effect/decal/cleanable/robot_debris/up + icon_state = "gibup1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibup1","gibup1") + +/obj/effect/decal/cleanable/robot_debris/down + icon_state = "gibdown1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibdown1","gibdown1") + +/obj/effect/decal/cleanable/oil + name = "motor oil" + desc = "It's black and greasy. Looks like Beepsky made another mess." + icon = 'icons/mob/robots.dmi' + icon_state = "floor1" + random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") + blood_state = BLOOD_STATE_OIL + bloodiness = BLOOD_AMOUNT_PER_DECAL + beauty = -100 + +/obj/effect/decal/cleanable/oil/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/fuel/oil, 30) + +/obj/effect/decal/cleanable/oil/attackby(obj/item/I, mob/living/user) + var/attacked_by_hot_thing = I.get_temperature() + if(attacked_by_hot_thing) + visible_message("[user] tries to ignite [src] with [I]!", "You try to ignite [src] with [I].") + log_combat(user, src, (attacked_by_hot_thing < 480) ? "tried to ignite" : "ignited", I) + fire_act(attacked_by_hot_thing) + return + return ..() + +/obj/effect/decal/cleanable/oil/fire_act(exposed_temperature, exposed_volume) + if(exposed_temperature < 480) + return + visible_message("[src] catches fire!") + var/turf/T = get_turf(src) + qdel(src) + new /obj/effect/hotspot(T) + +/obj/effect/decal/cleanable/oil/streak + icon_state = "streak1" + random_icon_states = list("streak1", "streak2", "streak3", "streak4", "streak5") + beauty = -50 + +/obj/effect/decal/cleanable/oil/slippery/ComponentInitialize() + . = ..() + AddComponent(/datum/component/slippery, 80, (NO_SLIP_WHEN_WALKING | SLIDE)) diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm index f1d777b8dab..da5366350cd 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -1,49 +1,49 @@ -GLOBAL_LIST(gang_tags) - -/obj/effect/decal/cleanable/crayon - name = "rune" - desc = "Graffiti. Damn kids." - icon = 'icons/effects/crayondecal.dmi' - icon_state = "rune1" - gender = NEUTER - plane = GAME_PLANE //makes the graffiti visible over a wall. - mergeable_decal = FALSE - var/do_icon_rotate = TRUE - var/rotation = 0 - var/paint_colour = "#FFFFFF" - -/obj/effect/decal/cleanable/crayon/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) - . = ..() - if(e_name) - name = e_name - desc = "A [name] vandalizing the station." - if(alt_icon) - icon = alt_icon - if(type) - icon_state = type - if(graf_rot) - rotation = graf_rot - if(rotation && do_icon_rotate) - var/matrix/M = matrix() - M.Turn(rotation) - src.transform = M - if(main) - paint_colour = main - add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) -/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T) - return isgroundlessturf(T) - -/obj/effect/decal/cleanable/crayon/gang - name = "Leet Like Jeff K gang tag" - desc = "Looks like someone's claimed this area for Leet Like Jeff K." - icon = 'icons/obj/gang/tags.dmi' - layer = BELOW_MOB_LAYER - var/datum/team/gang/my_gang - -/obj/effect/decal/cleanable/crayon/gang/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) - . = ..() - LAZYADD(GLOB.gang_tags, src) - -/obj/effect/decal/cleanable/crayon/gang/Destroy() - LAZYREMOVE(GLOB.gang_tags, src) - ..() +GLOBAL_LIST(gang_tags) + +/obj/effect/decal/cleanable/crayon + name = "rune" + desc = "Graffiti. Damn kids." + icon = 'icons/effects/crayondecal.dmi' + icon_state = "rune1" + gender = NEUTER + plane = GAME_PLANE //makes the graffiti visible over a wall. + mergeable_decal = FALSE + var/do_icon_rotate = TRUE + var/rotation = 0 + var/paint_colour = "#FFFFFF" + +/obj/effect/decal/cleanable/crayon/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) + . = ..() + if(e_name) + name = e_name + desc = "A [name] vandalizing the station." + if(alt_icon) + icon = alt_icon + if(type) + icon_state = type + if(graf_rot) + rotation = graf_rot + if(rotation && do_icon_rotate) + var/matrix/M = matrix() + M.Turn(rotation) + src.transform = M + if(main) + paint_colour = main + add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) +/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T) + return isgroundlessturf(T) + +/obj/effect/decal/cleanable/crayon/gang + name = "Leet Like Jeff K gang tag" + desc = "Looks like someone's claimed this area for Leet Like Jeff K." + icon = 'icons/obj/gang/tags.dmi' + layer = BELOW_MOB_LAYER + var/datum/team/gang/my_gang + +/obj/effect/decal/cleanable/crayon/gang/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) + . = ..() + LAZYADD(GLOB.gang_tags, src) + +/obj/effect/decal/cleanable/crayon/gang/Destroy() + LAZYREMOVE(GLOB.gang_tags, src) + ..() diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 96d719cc341..803b7250801 100644 --- a/code/game/objects/effects/decals/decal.dm +++ b/code/game/objects/effects/decals/decal.dm @@ -1,48 +1,48 @@ -/obj/effect/decal - name = "decal" - plane = FLOOR_PLANE - anchored = TRUE - resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/turf_loc_check = TRUE - -/obj/effect/decal/Initialize() - . = ..() - if(turf_loc_check && (!isturf(loc) || NeverShouldHaveComeHere(loc))) - return INITIALIZE_HINT_QDEL - -/obj/effect/decal/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - qdel(src) - -/obj/effect/decal/proc/NeverShouldHaveComeHere(turf/T) - return isclosedturf(T) || isgroundlessturf(T) - -/obj/effect/decal/ex_act(severity, target) - qdel(src) - -/obj/effect/decal/fire_act(exposed_temperature, exposed_volume) - if(!(resistance_flags & FIRE_PROOF)) //non fire proof decal or being burned by lava - qdel(src) - -/obj/effect/decal/HandleTurfChange(turf/T) - ..() - if(T == loc && NeverShouldHaveComeHere(T)) - qdel(src) - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/obj/effect/turf_decal - icon = 'icons/turf/decals.dmi' - icon_state = "warningline" - layer = TURF_DECAL_LAYER - -/obj/effect/turf_decal/Initialize() - ..() - return INITIALIZE_HINT_QDEL - -/obj/effect/turf_decal/ComponentInitialize() - . = ..() - var/turf/T = loc - if(!istype(T)) //you know this will happen somehow - CRASH("Turf decal initialized in an object/nullspace") - T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_NEVER, color, null, null, alpha) +/obj/effect/decal + name = "decal" + plane = FLOOR_PLANE + anchored = TRUE + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/turf_loc_check = TRUE + +/obj/effect/decal/Initialize() + . = ..() + if(turf_loc_check && (!isturf(loc) || NeverShouldHaveComeHere(loc))) + return INITIALIZE_HINT_QDEL + +/obj/effect/decal/blob_act(obj/structure/blob/B) + if(B && B.loc == loc) + qdel(src) + +/obj/effect/decal/proc/NeverShouldHaveComeHere(turf/T) + return isclosedturf(T) || isgroundlessturf(T) + +/obj/effect/decal/ex_act(severity, target) + qdel(src) + +/obj/effect/decal/fire_act(exposed_temperature, exposed_volume) + if(!(resistance_flags & FIRE_PROOF)) //non fire proof decal or being burned by lava + qdel(src) + +/obj/effect/decal/HandleTurfChange(turf/T) + ..() + if(T == loc && NeverShouldHaveComeHere(T)) + qdel(src) + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/obj/effect/turf_decal + icon = 'icons/turf/decals.dmi' + icon_state = "warningline" + layer = TURF_DECAL_LAYER + +/obj/effect/turf_decal/Initialize() + ..() + return INITIALIZE_HINT_QDEL + +/obj/effect/turf_decal/ComponentInitialize() + . = ..() + var/turf/T = loc + if(!istype(T)) //you know this will happen somehow + CRASH("Turf decal initialized in an object/nullspace") + T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_NEVER, color, null, null, alpha) diff --git a/code/game/objects/effects/decals/misc.dm b/code/game/objects/effects/decals/misc.dm index 05ff9cb863c..8f46d5cc463 100644 --- a/code/game/objects/effects/decals/misc.dm +++ b/code/game/objects/effects/decals/misc.dm @@ -1,31 +1,31 @@ -/obj/effect/temp_visual/point - name = "pointer" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "arrow" - layer = POINT_LAYER - duration = 25 - -/obj/effect/temp_visual/point/Initialize(mapload, set_invis = 0) - . = ..() - var/atom/old_loc = loc - loc = get_turf(src) // We don't want to actualy trigger anything when it moves - pixel_x = old_loc.pixel_x - pixel_y = old_loc.pixel_y - invisibility = set_invis - -//Used by spraybottles. -/obj/effect/decal/chempuff - name = "chemicals" - icon = 'icons/obj/chempuff.dmi' - pass_flags = PASSTABLE | PASSGRILLE - layer = FLY_LAYER - -/obj/effect/decal/chempuff/blob_act(obj/structure/blob/B) - return - -/obj/effect/decal/fakelattice - name = "lattice" - desc = "A lightweight support lattice." - icon = 'icons/obj/smooth_structures/lattice.dmi' - icon_state = "lattice" - density = TRUE +/obj/effect/temp_visual/point + name = "pointer" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "arrow" + layer = POINT_LAYER + duration = 25 + +/obj/effect/temp_visual/point/Initialize(mapload, set_invis = 0) + . = ..() + var/atom/old_loc = loc + loc = get_turf(src) // We don't want to actualy trigger anything when it moves + pixel_x = old_loc.pixel_x + pixel_y = old_loc.pixel_y + invisibility = set_invis + +//Used by spraybottles. +/obj/effect/decal/chempuff + name = "chemicals" + icon = 'icons/obj/chempuff.dmi' + pass_flags = PASSTABLE | PASSGRILLE + layer = FLY_LAYER + +/obj/effect/decal/chempuff/blob_act(obj/structure/blob/B) + return + +/obj/effect/decal/fakelattice + name = "lattice" + desc = "A lightweight support lattice." + icon = 'icons/obj/smooth_structures/lattice.dmi' + icon_state = "lattice" + density = TRUE diff --git a/code/game/objects/effects/decals/remains.dm b/code/game/objects/effects/decals/remains.dm index 8c2e76b168d..790b6c5a592 100644 --- a/code/game/objects/effects/decals/remains.dm +++ b/code/game/objects/effects/decals/remains.dm @@ -1,33 +1,33 @@ -/obj/effect/decal/remains - name = "remains" - gender = PLURAL - icon = 'icons/effects/blood.dmi' - -/obj/effect/decal/remains/acid_act() - visible_message("[src] dissolve[gender==PLURAL?"":"s"] into a puddle of sizzling goop!") - playsound(src, 'sound/items/welder.ogg', 150, TRUE) - new /obj/effect/decal/cleanable/greenglow(drop_location()) - qdel(src) - -/obj/effect/decal/remains/human - desc = "They look like human remains. They have a strange aura about them." - icon_state = "remains" - -/obj/effect/decal/remains/plasma - icon_state = "remainsplasma" - -/obj/effect/decal/remains/xeno - desc = "They look like the remains of something... alien. They have a strange aura about them." - icon_state = "remainsxeno" - -/obj/effect/decal/remains/xeno/larva - icon_state = "remainslarva" - -/obj/effect/decal/remains/robot - desc = "They look like the remains of something mechanical. They have a strange aura about them." - icon = 'icons/mob/robots.dmi' - icon_state = "remainsrobot" - -/obj/effect/decal/cleanable/robot_debris/old - name = "dusty robot debris" - desc = "Looks like nobody has touched this in a while." +/obj/effect/decal/remains + name = "remains" + gender = PLURAL + icon = 'icons/effects/blood.dmi' + +/obj/effect/decal/remains/acid_act() + visible_message("[src] dissolve[gender==PLURAL?"":"s"] into a puddle of sizzling goop!") + playsound(src, 'sound/items/welder.ogg', 150, TRUE) + new /obj/effect/decal/cleanable/greenglow(drop_location()) + qdel(src) + +/obj/effect/decal/remains/human + desc = "They look like human remains. They have a strange aura about them." + icon_state = "remains" + +/obj/effect/decal/remains/plasma + icon_state = "remainsplasma" + +/obj/effect/decal/remains/xeno + desc = "They look like the remains of something... alien. They have a strange aura about them." + icon_state = "remainsxeno" + +/obj/effect/decal/remains/xeno/larva + icon_state = "remainslarva" + +/obj/effect/decal/remains/robot + desc = "They look like the remains of something mechanical. They have a strange aura about them." + icon = 'icons/mob/robots.dmi' + icon_state = "remainsrobot" + +/obj/effect/decal/cleanable/robot_debris/old + name = "dusty robot debris" + desc = "Looks like nobody has touched this in a while." diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm index 865e2fa8691..d3fa9ce2c12 100644 --- a/code/game/objects/effects/forcefields.dm +++ b/code/game/objects/effects/forcefields.dm @@ -1,38 +1,38 @@ -/obj/effect/forcefield - desc = "A space wizard's magic wall." - name = "FORCEWALL" - icon_state = "m_shield" - anchored = TRUE - opacity = 0 - density = TRUE - CanAtmosPass = ATMOS_PASS_DENSITY - var/timeleft = 300 //Set to 0 for permanent forcefields (ugh) - -/obj/effect/forcefield/Initialize() - . = ..() - if(timeleft) - QDEL_IN(src, timeleft) - -/obj/effect/forcefield/singularity_pull() - return - -/obj/effect/forcefield/cult - desc = "An unholy shield that blocks all attacks." - name = "glowing wall" - icon = 'icons/effects/cult_effects.dmi' - icon_state = "cultshield" - CanAtmosPass = ATMOS_PASS_NO - timeleft = 200 - -///////////Mimewalls/////////// - -/obj/effect/forcefield/mime - icon_state = "nothing" - name = "invisible wall" - desc = "You have a bad feeling about this." - alpha = 0 - -/obj/effect/forcefield/mime/advanced - name = "invisible blockade" - desc = "You're gonna be here awhile." - timeleft = 600 +/obj/effect/forcefield + desc = "A space wizard's magic wall." + name = "FORCEWALL" + icon_state = "m_shield" + anchored = TRUE + opacity = 0 + density = TRUE + CanAtmosPass = ATMOS_PASS_DENSITY + var/timeleft = 300 //Set to 0 for permanent forcefields (ugh) + +/obj/effect/forcefield/Initialize() + . = ..() + if(timeleft) + QDEL_IN(src, timeleft) + +/obj/effect/forcefield/singularity_pull() + return + +/obj/effect/forcefield/cult + desc = "An unholy shield that blocks all attacks." + name = "glowing wall" + icon = 'icons/effects/cult_effects.dmi' + icon_state = "cultshield" + CanAtmosPass = ATMOS_PASS_NO + timeleft = 200 + +///////////Mimewalls/////////// + +/obj/effect/forcefield/mime + icon_state = "nothing" + name = "invisible wall" + desc = "You have a bad feeling about this." + alpha = 0 + +/obj/effect/forcefield/mime/advanced + name = "invisible blockade" + desc = "You're gonna be here awhile." + timeleft = 600 diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index 21bdab05673..85c1ce7f129 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -1,435 +1,435 @@ -/obj/effect/landmark - name = "landmark" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "x2" - anchored = TRUE - layer = MID_LANDMARK_LAYER - invisibility = INVISIBILITY_ABSTRACT - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/landmark/singularity_act() - return - -// Please stop bombing the Observer-Start landmark. -/obj/effect/landmark/ex_act() - return - -/obj/effect/landmark/singularity_pull() - return - -INITIALIZE_IMMEDIATE(/obj/effect/landmark) - -/obj/effect/landmark/Initialize() - . = ..() - GLOB.landmarks_list += src - -/obj/effect/landmark/Destroy() - GLOB.landmarks_list -= src - return ..() - -/obj/effect/landmark/start - name = "start" - icon = 'icons/mob/landmarks.dmi' - icon_state = "x" - anchored = TRUE - layer = MOB_LAYER - var/jobspawn_override = FALSE - var/delete_after_roundstart = TRUE - var/used = FALSE - -/obj/effect/landmark/start/proc/after_round_start() - if(delete_after_roundstart) - qdel(src) - -/obj/effect/landmark/start/Initialize() - . = ..() - GLOB.start_landmarks_list += src - if(jobspawn_override) - LAZYADDASSOC(GLOB.jobspawn_overrides, name, src) - if(name != "start") - tag = "start*[name]" - -/obj/effect/landmark/start/Destroy() - GLOB.start_landmarks_list -= src - if(jobspawn_override) - LAZYREMOVEASSOC(GLOB.jobspawn_overrides, name, src) - return ..() - -// START LANDMARKS FOLLOW. Don't change the names unless -// you are refactoring shitty landmark code. -/obj/effect/landmark/start/assistant - name = "Assistant" - icon_state = "Assistant" //icon_state is case sensitive. why are all of these capitalized? because fuck you that's why - -/obj/effect/landmark/start/assistant/override - jobspawn_override = TRUE - delete_after_roundstart = FALSE - -/obj/effect/landmark/start/prisoner - name = "Prisoner" - icon_state = "Prisoner" - -/obj/effect/landmark/start/janitor - name = "Janitor" - icon_state = "Janitor" - -/obj/effect/landmark/start/cargo_technician - name = "Cargo Technician" - icon_state = "Cargo Technician" - -/obj/effect/landmark/start/bartender - name = "Bartender" - icon_state = "Bartender" - -/obj/effect/landmark/start/clown - name = "Clown" - icon_state = "Clown" - -/obj/effect/landmark/start/mime - name = "Mime" - icon_state = "Mime" - -/obj/effect/landmark/start/quartermaster - name = "Quartermaster" - icon_state = "Quartermaster" - -/obj/effect/landmark/start/atmospheric_technician - name = "Atmospheric Technician" - icon_state = "Atmospheric Technician" - -/obj/effect/landmark/start/cook - name = "Cook" - icon_state = "Cook" - -/obj/effect/landmark/start/shaft_miner - name = "Shaft Miner" - icon_state = "Shaft Miner" - -/obj/effect/landmark/start/security_officer - name = "Security Officer" - icon_state = "Security Officer" - -/obj/effect/landmark/start/botanist - name = "Botanist" - icon_state = "Botanist" - -/obj/effect/landmark/start/head_of_security - name = "Head of Security" - icon_state = "Head of Security" - -/obj/effect/landmark/start/captain - name = "Captain" - icon_state = "Captain" - -/obj/effect/landmark/start/detective - name = "Detective" - icon_state = "Detective" - -/obj/effect/landmark/start/warden - name = "Warden" - icon_state = "Warden" - -/obj/effect/landmark/start/chief_engineer - name = "Chief Engineer" - icon_state = "Chief Engineer" - -/obj/effect/landmark/start/head_of_personnel - name = "Head of Personnel" - icon_state = "Head of Personnel" - -/obj/effect/landmark/start/librarian - name = "Curator" - icon_state = "Curator" - -/obj/effect/landmark/start/lawyer - name = "Lawyer" - icon_state = "Lawyer" - -/obj/effect/landmark/start/station_engineer - name = "Station Engineer" - icon_state = "Station Engineer" - -/obj/effect/landmark/start/medical_doctor - name = "Medical Doctor" - icon_state = "Medical Doctor" - -/obj/effect/landmark/start/paramedic - name = "Paramedic" - icon_state = "Paramedic" - -/obj/effect/landmark/start/scientist - name = "Scientist" - icon_state = "Scientist" - -/obj/effect/landmark/start/chemist - name = "Chemist" - icon_state = "Chemist" - -/obj/effect/landmark/start/roboticist - name = "Roboticist" - icon_state = "Roboticist" - -/obj/effect/landmark/start/research_director - name = "Research Director" - icon_state = "Research Director" - -/obj/effect/landmark/start/geneticist - name = "Geneticist" - icon_state = "Geneticist" - -/obj/effect/landmark/start/chief_medical_officer - name = "Chief Medical Officer" - icon_state = "Chief Medical Officer" - -/obj/effect/landmark/start/virologist - name = "Virologist" - icon_state = "Virologist" - -/obj/effect/landmark/start/psychologist - name = "Psychologist" - icon_state = "Psychologist" - -/obj/effect/landmark/start/chaplain - name = "Chaplain" - icon_state = "Chaplain" - -/obj/effect/landmark/start/cyborg - name = "Cyborg" - icon_state = "Cyborg" - -/obj/effect/landmark/start/ai - name = "AI" - icon_state = "AI" - delete_after_roundstart = FALSE - var/primary_ai = TRUE - var/latejoin_active = TRUE - -/obj/effect/landmark/start/ai/after_round_start() - if(latejoin_active && !used) - new /obj/structure/ai_core/latejoin_inactive(loc) - return ..() - -/obj/effect/landmark/start/ai/secondary - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "ai_spawn" - primary_ai = FALSE - latejoin_active = FALSE - -//Department Security spawns - -/obj/effect/landmark/start/depsec - name = "department_sec" - icon_state = "Security Officer" - -/obj/effect/landmark/start/depsec/New() - ..() - GLOB.department_security_spawns += src - -/obj/effect/landmark/start/depsec/Destroy() - GLOB.department_security_spawns -= src - return ..() - -/obj/effect/landmark/start/depsec/supply - name = "supply_sec" - -/obj/effect/landmark/start/depsec/medical - name = "medical_sec" - -/obj/effect/landmark/start/depsec/engineering - name = "engineering_sec" - -/obj/effect/landmark/start/depsec/science - name = "science_sec" - -//Antagonist spawns - -/obj/effect/landmark/start/wizard - name = "wizard" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "wiznerd_spawn" - -/obj/effect/landmark/start/wizard/Initialize() - ..() - GLOB.wizardstart += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/start/nukeop - name = "nukeop" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "snukeop_spawn" - -/obj/effect/landmark/start/nukeop/Initialize() - ..() - GLOB.nukeop_start += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/start/nukeop_leader - name = "nukeop leader" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "snukeop_leader_spawn" - -/obj/effect/landmark/start/nukeop_leader/Initialize() - ..() - GLOB.nukeop_leader_start += loc - return INITIALIZE_HINT_QDEL - -// Must be immediate because players will -// join before SSatom initializes everything. -INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) - -/obj/effect/landmark/start/new_player - name = "New Player" - -/obj/effect/landmark/start/new_player/Initialize() - ..() - GLOB.newplayer_start += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/latejoin - name = "JoinLate" - -/obj/effect/landmark/latejoin/Initialize(mapload) - ..() - SSjob.latejoin_trackers += loc - return INITIALIZE_HINT_QDEL - -//space carps, magicarps, lone ops, slaughter demons, possibly revenants spawn here -/obj/effect/landmark/carpspawn - name = "carpspawn" - icon_state = "carp_spawn" - -//observer start -/obj/effect/landmark/observer_start - name = "Observer-Start" - icon_state = "observer_start" - -//xenos, morphs and nightmares spawn here -/obj/effect/landmark/xeno_spawn - name = "xeno_spawn" - icon_state = "xeno_spawn" - -/obj/effect/landmark/xeno_spawn/Initialize(mapload) - ..() - GLOB.xeno_spawn += loc - return INITIALIZE_HINT_QDEL - -//objects with the stationloving component (nuke disk) respawn here. -//also blobs that have their spawn forcemoved (running out of time when picking their spawn spot), santa and respawning devils -/obj/effect/landmark/blobstart - name = "blobstart" - icon_state = "blob_start" - -/obj/effect/landmark/blobstart/Initialize(mapload) - ..() - GLOB.blobstart += loc - return INITIALIZE_HINT_QDEL - -//spawns sec equipment lockers depending on the number of sec officers -/obj/effect/landmark/secequipment - name = "secequipment" - icon_state = "secequipment" - -/obj/effect/landmark/secequipment/Initialize(mapload) - ..() - GLOB.secequipment += loc - return INITIALIZE_HINT_QDEL - -//players that get put in admin jail show up here -/obj/effect/landmark/prisonwarp - name = "prisonwarp" - icon_state = "prisonwarp" - -/obj/effect/landmark/prisonwarp/Initialize(mapload) - ..() - GLOB.prisonwarp += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/ert_spawn - name = "Emergencyresponseteam" - icon_state = "ert_spawn" - -/obj/effect/landmark/ert_spawn/Initialize(mapload) - ..() - GLOB.emergencyresponseteamspawn += loc - return INITIALIZE_HINT_QDEL - -//ninja energy nets teleport victims here -/obj/effect/landmark/holding_facility - name = "Holding Facility" - icon_state = "holding_facility" - -/obj/effect/landmark/holding_facility/Initialize(mapload) - ..() - GLOB.holdingfacility += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/observe - name = "tdomeobserve" - icon_state = "tdome_observer" - -/obj/effect/landmark/thunderdome/observe/Initialize(mapload) - ..() - GLOB.tdomeobserve += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/one - name = "tdome1" - icon_state = "tdome_t1" - -/obj/effect/landmark/thunderdome/one/Initialize(mapload) - ..() - GLOB.tdome1 += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/two - name = "tdome2" - icon_state = "tdome_t2" - -/obj/effect/landmark/thunderdome/two/Initialize(mapload) - ..() - GLOB.tdome2 += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/admin - name = "tdomeadmin" - icon_state = "tdome_admin" - -/obj/effect/landmark/thunderdome/admin/Initialize(mapload) - ..() - GLOB.tdomeadmin += loc - return INITIALIZE_HINT_QDEL - -//generic event spawns -/obj/effect/landmark/event_spawn - name = "generic event spawn" - icon_state = "generic_event" - layer = HIGH_LANDMARK_LAYER - - -/obj/effect/landmark/event_spawn/New() - ..() - GLOB.generic_event_spawns += src - -/obj/effect/landmark/event_spawn/Destroy() - GLOB.generic_event_spawns -= src - return ..() - -/obj/effect/landmark/ruin - var/datum/map_template/ruin/ruin_template - -/obj/effect/landmark/ruin/New(loc, my_ruin_template) - name = "ruin_[GLOB.ruin_landmarks.len + 1]" - ..(loc) - ruin_template = my_ruin_template - GLOB.ruin_landmarks |= src - -/obj/effect/landmark/ruin/Destroy() - GLOB.ruin_landmarks -= src - ruin_template = null - . = ..() - -// handled in portals.dm, id connected to one-way portal -/obj/effect/landmark/portal_exit - name = "portal exit" - icon_state = "portal_exit" - var/id +/obj/effect/landmark + name = "landmark" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "x2" + anchored = TRUE + layer = MID_LANDMARK_LAYER + invisibility = INVISIBILITY_ABSTRACT + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/effect/landmark/singularity_act() + return + +// Please stop bombing the Observer-Start landmark. +/obj/effect/landmark/ex_act() + return + +/obj/effect/landmark/singularity_pull() + return + +INITIALIZE_IMMEDIATE(/obj/effect/landmark) + +/obj/effect/landmark/Initialize() + . = ..() + GLOB.landmarks_list += src + +/obj/effect/landmark/Destroy() + GLOB.landmarks_list -= src + return ..() + +/obj/effect/landmark/start + name = "start" + icon = 'icons/mob/landmarks.dmi' + icon_state = "x" + anchored = TRUE + layer = MOB_LAYER + var/jobspawn_override = FALSE + var/delete_after_roundstart = TRUE + var/used = FALSE + +/obj/effect/landmark/start/proc/after_round_start() + if(delete_after_roundstart) + qdel(src) + +/obj/effect/landmark/start/Initialize() + . = ..() + GLOB.start_landmarks_list += src + if(jobspawn_override) + LAZYADDASSOC(GLOB.jobspawn_overrides, name, src) + if(name != "start") + tag = "start*[name]" + +/obj/effect/landmark/start/Destroy() + GLOB.start_landmarks_list -= src + if(jobspawn_override) + LAZYREMOVEASSOC(GLOB.jobspawn_overrides, name, src) + return ..() + +// START LANDMARKS FOLLOW. Don't change the names unless +// you are refactoring shitty landmark code. +/obj/effect/landmark/start/assistant + name = "Assistant" + icon_state = "Assistant" //icon_state is case sensitive. why are all of these capitalized? because fuck you that's why + +/obj/effect/landmark/start/assistant/override + jobspawn_override = TRUE + delete_after_roundstart = FALSE + +/obj/effect/landmark/start/prisoner + name = "Prisoner" + icon_state = "Prisoner" + +/obj/effect/landmark/start/janitor + name = "Janitor" + icon_state = "Janitor" + +/obj/effect/landmark/start/cargo_technician + name = "Cargo Technician" + icon_state = "Cargo Technician" + +/obj/effect/landmark/start/bartender + name = "Bartender" + icon_state = "Bartender" + +/obj/effect/landmark/start/clown + name = "Clown" + icon_state = "Clown" + +/obj/effect/landmark/start/mime + name = "Mime" + icon_state = "Mime" + +/obj/effect/landmark/start/quartermaster + name = "Quartermaster" + icon_state = "Quartermaster" + +/obj/effect/landmark/start/atmospheric_technician + name = "Atmospheric Technician" + icon_state = "Atmospheric Technician" + +/obj/effect/landmark/start/cook + name = "Cook" + icon_state = "Cook" + +/obj/effect/landmark/start/shaft_miner + name = "Shaft Miner" + icon_state = "Shaft Miner" + +/obj/effect/landmark/start/security_officer + name = "Security Officer" + icon_state = "Security Officer" + +/obj/effect/landmark/start/botanist + name = "Botanist" + icon_state = "Botanist" + +/obj/effect/landmark/start/head_of_security + name = "Head of Security" + icon_state = "Head of Security" + +/obj/effect/landmark/start/captain + name = "Captain" + icon_state = "Captain" + +/obj/effect/landmark/start/detective + name = "Detective" + icon_state = "Detective" + +/obj/effect/landmark/start/warden + name = "Warden" + icon_state = "Warden" + +/obj/effect/landmark/start/chief_engineer + name = "Chief Engineer" + icon_state = "Chief Engineer" + +/obj/effect/landmark/start/head_of_personnel + name = "Head of Personnel" + icon_state = "Head of Personnel" + +/obj/effect/landmark/start/librarian + name = "Curator" + icon_state = "Curator" + +/obj/effect/landmark/start/lawyer + name = "Lawyer" + icon_state = "Lawyer" + +/obj/effect/landmark/start/station_engineer + name = "Station Engineer" + icon_state = "Station Engineer" + +/obj/effect/landmark/start/medical_doctor + name = "Medical Doctor" + icon_state = "Medical Doctor" + +/obj/effect/landmark/start/paramedic + name = "Paramedic" + icon_state = "Paramedic" + +/obj/effect/landmark/start/scientist + name = "Scientist" + icon_state = "Scientist" + +/obj/effect/landmark/start/chemist + name = "Chemist" + icon_state = "Chemist" + +/obj/effect/landmark/start/roboticist + name = "Roboticist" + icon_state = "Roboticist" + +/obj/effect/landmark/start/research_director + name = "Research Director" + icon_state = "Research Director" + +/obj/effect/landmark/start/geneticist + name = "Geneticist" + icon_state = "Geneticist" + +/obj/effect/landmark/start/chief_medical_officer + name = "Chief Medical Officer" + icon_state = "Chief Medical Officer" + +/obj/effect/landmark/start/virologist + name = "Virologist" + icon_state = "Virologist" + +/obj/effect/landmark/start/psychologist + name = "Psychologist" + icon_state = "Psychologist" + +/obj/effect/landmark/start/chaplain + name = "Chaplain" + icon_state = "Chaplain" + +/obj/effect/landmark/start/cyborg + name = "Cyborg" + icon_state = "Cyborg" + +/obj/effect/landmark/start/ai + name = "AI" + icon_state = "AI" + delete_after_roundstart = FALSE + var/primary_ai = TRUE + var/latejoin_active = TRUE + +/obj/effect/landmark/start/ai/after_round_start() + if(latejoin_active && !used) + new /obj/structure/ai_core/latejoin_inactive(loc) + return ..() + +/obj/effect/landmark/start/ai/secondary + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "ai_spawn" + primary_ai = FALSE + latejoin_active = FALSE + +//Department Security spawns + +/obj/effect/landmark/start/depsec + name = "department_sec" + icon_state = "Security Officer" + +/obj/effect/landmark/start/depsec/New() + ..() + GLOB.department_security_spawns += src + +/obj/effect/landmark/start/depsec/Destroy() + GLOB.department_security_spawns -= src + return ..() + +/obj/effect/landmark/start/depsec/supply + name = "supply_sec" + +/obj/effect/landmark/start/depsec/medical + name = "medical_sec" + +/obj/effect/landmark/start/depsec/engineering + name = "engineering_sec" + +/obj/effect/landmark/start/depsec/science + name = "science_sec" + +//Antagonist spawns + +/obj/effect/landmark/start/wizard + name = "wizard" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "wiznerd_spawn" + +/obj/effect/landmark/start/wizard/Initialize() + ..() + GLOB.wizardstart += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/start/nukeop + name = "nukeop" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "snukeop_spawn" + +/obj/effect/landmark/start/nukeop/Initialize() + ..() + GLOB.nukeop_start += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/start/nukeop_leader + name = "nukeop leader" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "snukeop_leader_spawn" + +/obj/effect/landmark/start/nukeop_leader/Initialize() + ..() + GLOB.nukeop_leader_start += loc + return INITIALIZE_HINT_QDEL + +// Must be immediate because players will +// join before SSatom initializes everything. +INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) + +/obj/effect/landmark/start/new_player + name = "New Player" + +/obj/effect/landmark/start/new_player/Initialize() + ..() + GLOB.newplayer_start += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/latejoin + name = "JoinLate" + +/obj/effect/landmark/latejoin/Initialize(mapload) + ..() + SSjob.latejoin_trackers += loc + return INITIALIZE_HINT_QDEL + +//space carps, magicarps, lone ops, slaughter demons, possibly revenants spawn here +/obj/effect/landmark/carpspawn + name = "carpspawn" + icon_state = "carp_spawn" + +//observer start +/obj/effect/landmark/observer_start + name = "Observer-Start" + icon_state = "observer_start" + +//xenos, morphs and nightmares spawn here +/obj/effect/landmark/xeno_spawn + name = "xeno_spawn" + icon_state = "xeno_spawn" + +/obj/effect/landmark/xeno_spawn/Initialize(mapload) + ..() + GLOB.xeno_spawn += loc + return INITIALIZE_HINT_QDEL + +//objects with the stationloving component (nuke disk) respawn here. +//also blobs that have their spawn forcemoved (running out of time when picking their spawn spot), santa and respawning devils +/obj/effect/landmark/blobstart + name = "blobstart" + icon_state = "blob_start" + +/obj/effect/landmark/blobstart/Initialize(mapload) + ..() + GLOB.blobstart += loc + return INITIALIZE_HINT_QDEL + +//spawns sec equipment lockers depending on the number of sec officers +/obj/effect/landmark/secequipment + name = "secequipment" + icon_state = "secequipment" + +/obj/effect/landmark/secequipment/Initialize(mapload) + ..() + GLOB.secequipment += loc + return INITIALIZE_HINT_QDEL + +//players that get put in admin jail show up here +/obj/effect/landmark/prisonwarp + name = "prisonwarp" + icon_state = "prisonwarp" + +/obj/effect/landmark/prisonwarp/Initialize(mapload) + ..() + GLOB.prisonwarp += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/ert_spawn + name = "Emergencyresponseteam" + icon_state = "ert_spawn" + +/obj/effect/landmark/ert_spawn/Initialize(mapload) + ..() + GLOB.emergencyresponseteamspawn += loc + return INITIALIZE_HINT_QDEL + +//ninja energy nets teleport victims here +/obj/effect/landmark/holding_facility + name = "Holding Facility" + icon_state = "holding_facility" + +/obj/effect/landmark/holding_facility/Initialize(mapload) + ..() + GLOB.holdingfacility += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/observe + name = "tdomeobserve" + icon_state = "tdome_observer" + +/obj/effect/landmark/thunderdome/observe/Initialize(mapload) + ..() + GLOB.tdomeobserve += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/one + name = "tdome1" + icon_state = "tdome_t1" + +/obj/effect/landmark/thunderdome/one/Initialize(mapload) + ..() + GLOB.tdome1 += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/two + name = "tdome2" + icon_state = "tdome_t2" + +/obj/effect/landmark/thunderdome/two/Initialize(mapload) + ..() + GLOB.tdome2 += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/admin + name = "tdomeadmin" + icon_state = "tdome_admin" + +/obj/effect/landmark/thunderdome/admin/Initialize(mapload) + ..() + GLOB.tdomeadmin += loc + return INITIALIZE_HINT_QDEL + +//generic event spawns +/obj/effect/landmark/event_spawn + name = "generic event spawn" + icon_state = "generic_event" + layer = HIGH_LANDMARK_LAYER + + +/obj/effect/landmark/event_spawn/New() + ..() + GLOB.generic_event_spawns += src + +/obj/effect/landmark/event_spawn/Destroy() + GLOB.generic_event_spawns -= src + return ..() + +/obj/effect/landmark/ruin + var/datum/map_template/ruin/ruin_template + +/obj/effect/landmark/ruin/New(loc, my_ruin_template) + name = "ruin_[GLOB.ruin_landmarks.len + 1]" + ..(loc) + ruin_template = my_ruin_template + GLOB.ruin_landmarks |= src + +/obj/effect/landmark/ruin/Destroy() + GLOB.ruin_landmarks -= src + ruin_template = null + . = ..() + +// handled in portals.dm, id connected to one-way portal +/obj/effect/landmark/portal_exit + name = "portal exit" + icon_state = "portal_exit" + var/id diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 05fca65f70f..d162481e66e 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -1,199 +1,199 @@ -/obj/effect/mine - name = "dummy mine" - desc = "Better stay away from that thing." - density = FALSE - anchored = TRUE - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "uglymine" - /// We manually check to see if we've been triggered in case multiple atoms cross us in the time between the mine being triggered and it actually deleting, to avoid a race condition with multiple detonations - var/triggered = FALSE - -/obj/effect/mine/proc/mineEffect(mob/victim) - to_chat(victim, "*click*") - -/obj/effect/mine/Crossed(atom/movable/AM) - if(triggered || !isturf(loc)) - return - . = ..() - - if(AM.movement_type & FLYING) - return - - triggermine(AM) - -/obj/effect/mine/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir) - . = ..() - triggermine() - -/obj/effect/mine/proc/triggermine(mob/victim) - if(triggered) - return - visible_message("[victim] sets off [icon2html(src, viewers(src))] [src]!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - mineEffect(victim) - triggered = TRUE - SEND_SIGNAL(src, COMSIG_MINE_TRIGGERED) - qdel(src) - -/obj/effect/mine/explosive - name = "explosive mine" - var/range_devastation = 0 - var/range_heavy = 1 - var/range_light = 2 - var/range_flash = 3 - -/obj/effect/mine/explosive/mineEffect(mob/victim) - explosion(loc, range_devastation, range_heavy, range_light, range_flash) - -/obj/effect/mine/stun - name = "stun mine" - var/stun_time = 80 - -/obj/effect/mine/shrapnel - name = "shrapnel mine" - var/shrapnel_type = /obj/projectile/bullet/shrapnel - var/shrapnel_magnitude = 3 - -/obj/effect/mine/shrapnel/mineEffect(mob/victim) - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) - -/obj/effect/mine/shrapnel/sting - name = "stinger mine" - shrapnel_type = /obj/projectile/bullet/pellet/stingball - -/obj/effect/mine/stun/mineEffect(mob/living/victim) - if(isliving(victim)) - victim.Paralyze(stun_time) - -/obj/effect/mine/kickmine - name = "kick mine" - -/obj/effect/mine/kickmine/mineEffect(mob/victim) - if(isliving(victim) && victim.client) - to_chat(victim, "You have been kicked FOR NO REISIN!") - qdel(victim.client) - - -/obj/effect/mine/gas - name = "oxygen mine" - var/gas_amount = 360 - var/gas_type = "o2" - -/obj/effect/mine/gas/mineEffect(mob/victim) - atmos_spawn_air("[gas_type]=[gas_amount]") - - -/obj/effect/mine/gas/plasma - name = "plasma mine" - gas_type = "plasma" - - -/obj/effect/mine/gas/n2o - name = "\improper N2O mine" - gas_type = "n2o" - - -/obj/effect/mine/gas/water_vapor - name = "chilled vapor mine" - gas_amount = 500 - gas_type = "water_vapor" - -/obj/effect/mine/sound - name = "honkblaster 1000" - var/sound = 'sound/items/bikehorn.ogg' - -/obj/effect/mine/sound/mineEffect(mob/victim) - playsound(loc, sound, 100, TRUE) - - -/obj/effect/mine/sound/bwoink - name = "bwoink mine" - sound = 'sound/effects/adminhelp.ogg' - -/obj/effect/mine/pickup - name = "pickup" - desc = "pick me up" - icon = 'icons/effects/effects.dmi' - icon_state = "electricity2" - density = FALSE - var/duration = 0 - -/obj/effect/mine/pickup/Initialize() - . = ..() - animate(src, pixel_y = 4, time = 20, loop = -1) - -/obj/effect/mine/pickup/triggermine(mob/victim) - if(triggered) - return - triggered = 1 - invisibility = INVISIBILITY_ABSTRACT - mineEffect(victim) - qdel(src) - - -/obj/effect/mine/pickup/bloodbath - name = "Red Orb" - desc = "You feel angry just looking at it." - duration = 1200 //2min - color = "#FF0000" - -/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "RIP AND TEAR") - var/old_color = victim.client.color - var/static/list/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0) - var/static/list/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0) - - INVOKE_ASYNC(src, .proc/blood_delusion, victim) - - var/obj/item/chainsaw/doomslayer/chainsaw = new(victim.loc) - victim.log_message("entered a blood frenzy", LOG_ATTACK) - - ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) - victim.drop_all_held_items() - victim.put_in_hands(chainsaw, forced = TRUE) - chainsaw.attack_self(victim) - victim.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25) - to_chat(victim, "KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!") - - victim.client.color = pure_red - animate(victim.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT) - sleep(10) - animate(victim.client,color = old_color, time = duration)//, easing = SINE_EASING|EASE_OUT) - sleep(duration) - to_chat(victim, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.") - qdel(chainsaw) - victim.log_message("exited a blood frenzy", LOG_ATTACK) - qdel(src) - -/obj/effect/mine/pickup/bloodbath/proc/blood_delusion(mob/living/carbon/victim) - new /datum/hallucination/delusion(victim, TRUE, "demon", duration, 0) - -/obj/effect/mine/pickup/healing - name = "Blue Orb" - desc = "You feel better just looking at it." - color = "#0000FF" - -/obj/effect/mine/pickup/healing/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "You feel great!") - victim.revive(full_heal = TRUE, admin_revive = TRUE) - -/obj/effect/mine/pickup/speed - name = "Yellow Orb" - desc = "You feel faster just looking at it." - color = "#FFFF00" - duration = 300 - -/obj/effect/mine/pickup/speed/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "You feel fast!") - victim.add_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) - sleep(duration) - victim.remove_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) - to_chat(victim, "You slow down.") +/obj/effect/mine + name = "dummy mine" + desc = "Better stay away from that thing." + density = FALSE + anchored = TRUE + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "uglymine" + /// We manually check to see if we've been triggered in case multiple atoms cross us in the time between the mine being triggered and it actually deleting, to avoid a race condition with multiple detonations + var/triggered = FALSE + +/obj/effect/mine/proc/mineEffect(mob/victim) + to_chat(victim, "*click*") + +/obj/effect/mine/Crossed(atom/movable/AM) + if(triggered || !isturf(loc)) + return + . = ..() + + if(AM.movement_type & FLYING) + return + + triggermine(AM) + +/obj/effect/mine/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir) + . = ..() + triggermine() + +/obj/effect/mine/proc/triggermine(mob/victim) + if(triggered) + return + visible_message("[victim] sets off [icon2html(src, viewers(src))] [src]!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, src) + s.start() + mineEffect(victim) + triggered = TRUE + SEND_SIGNAL(src, COMSIG_MINE_TRIGGERED) + qdel(src) + +/obj/effect/mine/explosive + name = "explosive mine" + var/range_devastation = 0 + var/range_heavy = 1 + var/range_light = 2 + var/range_flash = 3 + +/obj/effect/mine/explosive/mineEffect(mob/victim) + explosion(loc, range_devastation, range_heavy, range_light, range_flash) + +/obj/effect/mine/stun + name = "stun mine" + var/stun_time = 80 + +/obj/effect/mine/shrapnel + name = "shrapnel mine" + var/shrapnel_type = /obj/projectile/bullet/shrapnel + var/shrapnel_magnitude = 3 + +/obj/effect/mine/shrapnel/mineEffect(mob/victim) + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) + +/obj/effect/mine/shrapnel/sting + name = "stinger mine" + shrapnel_type = /obj/projectile/bullet/pellet/stingball + +/obj/effect/mine/stun/mineEffect(mob/living/victim) + if(isliving(victim)) + victim.Paralyze(stun_time) + +/obj/effect/mine/kickmine + name = "kick mine" + +/obj/effect/mine/kickmine/mineEffect(mob/victim) + if(isliving(victim) && victim.client) + to_chat(victim, "You have been kicked FOR NO REISIN!") + qdel(victim.client) + + +/obj/effect/mine/gas + name = "oxygen mine" + var/gas_amount = 360 + var/gas_type = "o2" + +/obj/effect/mine/gas/mineEffect(mob/victim) + atmos_spawn_air("[gas_type]=[gas_amount]") + + +/obj/effect/mine/gas/plasma + name = "plasma mine" + gas_type = "plasma" + + +/obj/effect/mine/gas/n2o + name = "\improper N2O mine" + gas_type = "n2o" + + +/obj/effect/mine/gas/water_vapor + name = "chilled vapor mine" + gas_amount = 500 + gas_type = "water_vapor" + +/obj/effect/mine/sound + name = "honkblaster 1000" + var/sound = 'sound/items/bikehorn.ogg' + +/obj/effect/mine/sound/mineEffect(mob/victim) + playsound(loc, sound, 100, TRUE) + + +/obj/effect/mine/sound/bwoink + name = "bwoink mine" + sound = 'sound/effects/adminhelp.ogg' + +/obj/effect/mine/pickup + name = "pickup" + desc = "pick me up" + icon = 'icons/effects/effects.dmi' + icon_state = "electricity2" + density = FALSE + var/duration = 0 + +/obj/effect/mine/pickup/Initialize() + . = ..() + animate(src, pixel_y = 4, time = 20, loop = -1) + +/obj/effect/mine/pickup/triggermine(mob/victim) + if(triggered) + return + triggered = 1 + invisibility = INVISIBILITY_ABSTRACT + mineEffect(victim) + qdel(src) + + +/obj/effect/mine/pickup/bloodbath + name = "Red Orb" + desc = "You feel angry just looking at it." + duration = 1200 //2min + color = "#FF0000" + +/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "RIP AND TEAR") + var/old_color = victim.client.color + var/static/list/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0) + var/static/list/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0) + + INVOKE_ASYNC(src, .proc/blood_delusion, victim) + + var/obj/item/chainsaw/doomslayer/chainsaw = new(victim.loc) + victim.log_message("entered a blood frenzy", LOG_ATTACK) + + ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) + victim.drop_all_held_items() + victim.put_in_hands(chainsaw, forced = TRUE) + chainsaw.attack_self(victim) + victim.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25) + to_chat(victim, "KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!") + + victim.client.color = pure_red + animate(victim.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT) + sleep(10) + animate(victim.client,color = old_color, time = duration)//, easing = SINE_EASING|EASE_OUT) + sleep(duration) + to_chat(victim, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.") + qdel(chainsaw) + victim.log_message("exited a blood frenzy", LOG_ATTACK) + qdel(src) + +/obj/effect/mine/pickup/bloodbath/proc/blood_delusion(mob/living/carbon/victim) + new /datum/hallucination/delusion(victim, TRUE, "demon", duration, 0) + +/obj/effect/mine/pickup/healing + name = "Blue Orb" + desc = "You feel better just looking at it." + color = "#0000FF" + +/obj/effect/mine/pickup/healing/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "You feel great!") + victim.revive(full_heal = TRUE, admin_revive = TRUE) + +/obj/effect/mine/pickup/speed + name = "Yellow Orb" + desc = "You feel faster just looking at it." + color = "#FFFF00" + duration = 300 + +/obj/effect/mine/pickup/speed/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "You feel fast!") + victim.add_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) + sleep(duration) + victim.remove_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) + to_chat(victim, "You slow down.") diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm index fdf580fb6c7..b7ded01d5a2 100644 --- a/code/game/objects/effects/misc.dm +++ b/code/game/objects/effects/misc.dm @@ -1,94 +1,94 @@ -//The effect when you wrap a dead body in gift wrap -/obj/effect/spresent - name = "strange present" - desc = "It's a ... present?" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "strangepresent" - density = TRUE - anchored = FALSE - -/obj/effect/beam - name = "beam" - var/def_zone - pass_flags = PASSTABLE - -/obj/effect/beam/singularity_act() - return - -/obj/effect/beam/singularity_pull() - return - -/obj/effect/spawner - name = "object spawner" - -/obj/effect/list_container - name = "list container" - -/obj/effect/list_container/mobl - name = "mobl" - var/master = null - - var/list/container = list( ) - -/obj/effect/overlay/thermite - name = "thermite" - desc = "Looks hot." - icon = 'icons/effects/fire.dmi' - icon_state = "2" //what? - anchored = TRUE - opacity = TRUE - density = TRUE - layer = FLY_LAYER - -/obj/effect/supplypod_selector - icon_state = "supplypod_selector" - layer = FLY_LAYER - -//Makes a tile fully lit no matter what -/obj/effect/fullbright - icon = 'icons/effects/alphacolors.dmi' - icon_state = "white" - plane = LIGHTING_PLANE - layer = LIGHTING_LAYER - blend_mode = BLEND_ADD - -/obj/effect/abstract/marker - name = "marker" - icon = 'icons/effects/effects.dmi' - anchored = TRUE - icon_state = "wave3" - layer = RIPPLE_LAYER - -/obj/effect/abstract/marker/Initialize(mapload) - . = ..() - GLOB.all_abstract_markers += src - -/obj/effect/abstract/marker/Destroy() - GLOB.all_abstract_markers -= src - . = ..() - -/obj/effect/abstract/marker/at - name = "active turf marker" - - -/obj/effect/dummy/lighting_obj - name = "lighting fx obj" - desc = "Tell a coder if you're seeing this." - icon_state = "nothing" - light_color = "#FFFFFF" - light_range = MINIMUM_USEFUL_LIGHT_RANGE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) - if(_duration) - QDEL_IN(src, _duration) - -/obj/effect/dummy/lighting_obj/moblight - name = "mob lighting fx" - -/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - if(!ismob(loc)) - return INITIALIZE_HINT_QDEL +//The effect when you wrap a dead body in gift wrap +/obj/effect/spresent + name = "strange present" + desc = "It's a ... present?" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "strangepresent" + density = TRUE + anchored = FALSE + +/obj/effect/beam + name = "beam" + var/def_zone + pass_flags = PASSTABLE + +/obj/effect/beam/singularity_act() + return + +/obj/effect/beam/singularity_pull() + return + +/obj/effect/spawner + name = "object spawner" + +/obj/effect/list_container + name = "list container" + +/obj/effect/list_container/mobl + name = "mobl" + var/master = null + + var/list/container = list( ) + +/obj/effect/overlay/thermite + name = "thermite" + desc = "Looks hot." + icon = 'icons/effects/fire.dmi' + icon_state = "2" //what? + anchored = TRUE + opacity = TRUE + density = TRUE + layer = FLY_LAYER + +/obj/effect/supplypod_selector + icon_state = "supplypod_selector" + layer = FLY_LAYER + +//Makes a tile fully lit no matter what +/obj/effect/fullbright + icon = 'icons/effects/alphacolors.dmi' + icon_state = "white" + plane = LIGHTING_PLANE + layer = LIGHTING_LAYER + blend_mode = BLEND_ADD + +/obj/effect/abstract/marker + name = "marker" + icon = 'icons/effects/effects.dmi' + anchored = TRUE + icon_state = "wave3" + layer = RIPPLE_LAYER + +/obj/effect/abstract/marker/Initialize(mapload) + . = ..() + GLOB.all_abstract_markers += src + +/obj/effect/abstract/marker/Destroy() + GLOB.all_abstract_markers -= src + . = ..() + +/obj/effect/abstract/marker/at + name = "active turf marker" + + +/obj/effect/dummy/lighting_obj + name = "lighting fx obj" + desc = "Tell a coder if you're seeing this." + icon_state = "nothing" + light_color = "#FFFFFF" + light_range = MINIMUM_USEFUL_LIGHT_RANGE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) + if(_duration) + QDEL_IN(src, _duration) + +/obj/effect/dummy/lighting_obj/moblight + name = "mob lighting fx" + +/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + if(!ismob(loc)) + return INITIALIZE_HINT_QDEL diff --git a/code/game/objects/effects/proximity.dm b/code/game/objects/effects/proximity.dm index 677e2c3b256..5a38b29613b 100644 --- a/code/game/objects/effects/proximity.dm +++ b/code/game/objects/effects/proximity.dm @@ -1,116 +1,116 @@ -/datum/proximity_monitor - var/atom/host //the atom we are tracking - var/atom/hasprox_receiver //the atom that will receive HasProximity calls. - var/atom/last_host_loc - var/list/checkers //list of /obj/effect/abstract/proximity_checkers - var/current_range - var/ignore_if_not_on_turf //don't check turfs in range if the host's loc isn't a turf - var/wire = FALSE - -/datum/proximity_monitor/New(atom/_host, range, _ignore_if_not_on_turf = TRUE) - checkers = list() - last_host_loc = _host.loc - ignore_if_not_on_turf = _ignore_if_not_on_turf - current_range = range - SetHost(_host) - -/datum/proximity_monitor/proc/SetHost(atom/H,atom/R) - if(H == host) - return - if(host) - UnregisterSignal(host, COMSIG_MOVABLE_MOVED) - if(R) - hasprox_receiver = R - else if(hasprox_receiver == host) //Default case - hasprox_receiver = H - host = H - RegisterSignal(host, COMSIG_MOVABLE_MOVED, .proc/HandleMove) - last_host_loc = host.loc - SetRange(current_range,TRUE) - -/datum/proximity_monitor/Destroy() - host = null - last_host_loc = null - hasprox_receiver = null - QDEL_LIST(checkers) - return ..() - -/datum/proximity_monitor/proc/HandleMove() - var/atom/_host = host - var/atom/new_host_loc = _host.loc - if(last_host_loc != new_host_loc) - last_host_loc = new_host_loc //hopefully this won't cause GC issues with containers - var/curr_range = current_range - SetRange(curr_range, TRUE) - if(curr_range) - testing("HasProx: [host] -> [host]") - hasprox_receiver.HasProximity(host) //if we are processing, we're guaranteed to be a movable - -/datum/proximity_monitor/proc/SetRange(range, force_rebuild = FALSE) - if(!force_rebuild && range == current_range) - return FALSE - . = TRUE - - current_range = range - - var/list/checkers_local = checkers - var/old_checkers_len = checkers_local.len - - var/atom/_host = host - - var/atom/loc_to_use = ignore_if_not_on_turf ? _host.loc : get_turf(_host) - if(wire && !isturf(loc_to_use)) //it makes assemblies attached on wires work - loc_to_use = get_turf(loc_to_use) - if(!isturf(loc_to_use)) //only check the host's loc - if(range) - var/obj/effect/abstract/proximity_checker/pc - if(old_checkers_len) - pc = checkers_local[old_checkers_len] - --checkers_local.len - QDEL_LIST(checkers_local) - else - pc = new(loc_to_use, src) - - checkers_local += pc //only check the host's loc - return - - var/list/turfs = RANGE_TURFS(range, loc_to_use) - var/turfs_len = turfs.len - var/old_checkers_used = min(turfs_len, old_checkers_len) - - //reuse what we can - for(var/I in 1 to old_checkers_len) - if(I <= old_checkers_used) - var/obj/effect/abstract/proximity_checker/pc = checkers_local[I] - pc.forceMove(turfs[I]) - else - qdel(checkers_local[I]) //delete the leftovers - - if(old_checkers_len < turfs_len) - //create what we lack - for(var/I in (old_checkers_used + 1) to turfs_len) - checkers_local += new /obj/effect/abstract/proximity_checker(turfs[I], src) - else - checkers_local.Cut(old_checkers_used + 1, old_checkers_len) - -/obj/effect/abstract/proximity_checker - invisibility = INVISIBILITY_ABSTRACT - anchored = TRUE - var/datum/proximity_monitor/monitor - -/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/proximity_monitor/_monitor) - . = ..() - if(_monitor) - monitor = _monitor - else - stack_trace("proximity_checker created without host") - return INITIALIZE_HINT_QDEL - -/obj/effect/abstract/proximity_checker/Destroy() - monitor = null - return ..() - -/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM) - set waitfor = FALSE - . = ..() - monitor.hasprox_receiver.HasProximity(AM) +/datum/proximity_monitor + var/atom/host //the atom we are tracking + var/atom/hasprox_receiver //the atom that will receive HasProximity calls. + var/atom/last_host_loc + var/list/checkers //list of /obj/effect/abstract/proximity_checkers + var/current_range + var/ignore_if_not_on_turf //don't check turfs in range if the host's loc isn't a turf + var/wire = FALSE + +/datum/proximity_monitor/New(atom/_host, range, _ignore_if_not_on_turf = TRUE) + checkers = list() + last_host_loc = _host.loc + ignore_if_not_on_turf = _ignore_if_not_on_turf + current_range = range + SetHost(_host) + +/datum/proximity_monitor/proc/SetHost(atom/H,atom/R) + if(H == host) + return + if(host) + UnregisterSignal(host, COMSIG_MOVABLE_MOVED) + if(R) + hasprox_receiver = R + else if(hasprox_receiver == host) //Default case + hasprox_receiver = H + host = H + RegisterSignal(host, COMSIG_MOVABLE_MOVED, .proc/HandleMove) + last_host_loc = host.loc + SetRange(current_range,TRUE) + +/datum/proximity_monitor/Destroy() + host = null + last_host_loc = null + hasprox_receiver = null + QDEL_LIST(checkers) + return ..() + +/datum/proximity_monitor/proc/HandleMove() + var/atom/_host = host + var/atom/new_host_loc = _host.loc + if(last_host_loc != new_host_loc) + last_host_loc = new_host_loc //hopefully this won't cause GC issues with containers + var/curr_range = current_range + SetRange(curr_range, TRUE) + if(curr_range) + testing("HasProx: [host] -> [host]") + hasprox_receiver.HasProximity(host) //if we are processing, we're guaranteed to be a movable + +/datum/proximity_monitor/proc/SetRange(range, force_rebuild = FALSE) + if(!force_rebuild && range == current_range) + return FALSE + . = TRUE + + current_range = range + + var/list/checkers_local = checkers + var/old_checkers_len = checkers_local.len + + var/atom/_host = host + + var/atom/loc_to_use = ignore_if_not_on_turf ? _host.loc : get_turf(_host) + if(wire && !isturf(loc_to_use)) //it makes assemblies attached on wires work + loc_to_use = get_turf(loc_to_use) + if(!isturf(loc_to_use)) //only check the host's loc + if(range) + var/obj/effect/abstract/proximity_checker/pc + if(old_checkers_len) + pc = checkers_local[old_checkers_len] + --checkers_local.len + QDEL_LIST(checkers_local) + else + pc = new(loc_to_use, src) + + checkers_local += pc //only check the host's loc + return + + var/list/turfs = RANGE_TURFS(range, loc_to_use) + var/turfs_len = turfs.len + var/old_checkers_used = min(turfs_len, old_checkers_len) + + //reuse what we can + for(var/I in 1 to old_checkers_len) + if(I <= old_checkers_used) + var/obj/effect/abstract/proximity_checker/pc = checkers_local[I] + pc.forceMove(turfs[I]) + else + qdel(checkers_local[I]) //delete the leftovers + + if(old_checkers_len < turfs_len) + //create what we lack + for(var/I in (old_checkers_used + 1) to turfs_len) + checkers_local += new /obj/effect/abstract/proximity_checker(turfs[I], src) + else + checkers_local.Cut(old_checkers_used + 1, old_checkers_len) + +/obj/effect/abstract/proximity_checker + invisibility = INVISIBILITY_ABSTRACT + anchored = TRUE + var/datum/proximity_monitor/monitor + +/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/proximity_monitor/_monitor) + . = ..() + if(_monitor) + monitor = _monitor + else + stack_trace("proximity_checker created without host") + return INITIALIZE_HINT_QDEL + +/obj/effect/abstract/proximity_checker/Destroy() + monitor = null + return ..() + +/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM) + set waitfor = FALSE + . = ..() + monitor.hasprox_receiver.HasProximity(AM) diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index b1bb3e6b4d8..c96a047d6f7 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -1,67 +1,67 @@ -#define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) - -#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) -#define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) - -/obj/effect/spawner/newbomb - name = "bomb" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x" - var/temp_p = 1500 - var/temp_o = 1000 // tank temperatures - var/pressure_p = 10 * ONE_ATMOSPHERE - var/pressure_o = 10 * ONE_ATMOSPHERE //tank pressures - var/assembly_type - -/obj/effect/spawner/newbomb/Initialize() - . = ..() - var/obj/item/transfer_valve/V = new(src.loc) - var/obj/item/tank/internals/plasma/PT = new(V) - var/obj/item/tank/internals/oxygen/OT = new(V) - - PT.air_contents.assert_gas(/datum/gas/plasma) - PT.air_contents.gases[/datum/gas/plasma][MOLES] = pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p)) - PT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_p) - - OT.air_contents.assert_gas(/datum/gas/oxygen) - OT.air_contents.gases[/datum/gas/oxygen][MOLES] = pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o)) - OT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_o) - - V.tank_one = PT - V.tank_two = OT - PT.master = V - OT.master = V - - if(assembly_type) - var/obj/item/assembly/A = new assembly_type(V) - V.attached_device = A - A.holder = V - - V.update_icon() - - return INITIALIZE_HINT_QDEL - -/obj/effect/spawner/newbomb/timer/syndicate/Initialize() - temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C - . = ..() - -/obj/effect/spawner/newbomb/timer - assembly_type = /obj/item/assembly/timer - -/obj/effect/spawner/newbomb/timer/syndicate - pressure_o = TANK_LEAK_PRESSURE - 1 - temp_o = 20 - - pressure_p = TANK_LEAK_PRESSURE - 1 - -/obj/effect/spawner/newbomb/proximity - assembly_type = /obj/item/assembly/prox_sensor - -/obj/effect/spawner/newbomb/radio - assembly_type = /obj/item/assembly/signaler - - -#undef CELSIUS_TO_KELVIN - -#undef OPTIMAL_TEMP_K_PLA_BURN_SCALE -#undef OPTIMAL_TEMP_K_PLA_BURN_RATIO +#define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) + +#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) +#define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) + +/obj/effect/spawner/newbomb + name = "bomb" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x" + var/temp_p = 1500 + var/temp_o = 1000 // tank temperatures + var/pressure_p = 10 * ONE_ATMOSPHERE + var/pressure_o = 10 * ONE_ATMOSPHERE //tank pressures + var/assembly_type + +/obj/effect/spawner/newbomb/Initialize() + . = ..() + var/obj/item/transfer_valve/V = new(src.loc) + var/obj/item/tank/internals/plasma/PT = new(V) + var/obj/item/tank/internals/oxygen/OT = new(V) + + PT.air_contents.assert_gas(/datum/gas/plasma) + PT.air_contents.gases[/datum/gas/plasma][MOLES] = pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p)) + PT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_p) + + OT.air_contents.assert_gas(/datum/gas/oxygen) + OT.air_contents.gases[/datum/gas/oxygen][MOLES] = pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o)) + OT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_o) + + V.tank_one = PT + V.tank_two = OT + PT.master = V + OT.master = V + + if(assembly_type) + var/obj/item/assembly/A = new assembly_type(V) + V.attached_device = A + A.holder = V + + V.update_icon() + + return INITIALIZE_HINT_QDEL + +/obj/effect/spawner/newbomb/timer/syndicate/Initialize() + temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C + . = ..() + +/obj/effect/spawner/newbomb/timer + assembly_type = /obj/item/assembly/timer + +/obj/effect/spawner/newbomb/timer/syndicate + pressure_o = TANK_LEAK_PRESSURE - 1 + temp_o = 20 + + pressure_p = TANK_LEAK_PRESSURE - 1 + +/obj/effect/spawner/newbomb/proximity + assembly_type = /obj/item/assembly/prox_sensor + +/obj/effect/spawner/newbomb/radio + assembly_type = /obj/item/assembly/signaler + + +#undef CELSIUS_TO_KELVIN + +#undef OPTIMAL_TEMP_K_PLA_BURN_SCALE +#undef OPTIMAL_TEMP_K_PLA_BURN_RATIO diff --git a/code/game/objects/effects/spawners/gibspawner.dm b/code/game/objects/effects/spawners/gibspawner.dm index e2481487164..b05730bdc43 100644 --- a/code/game/objects/effects/spawners/gibspawner.dm +++ b/code/game/objects/effects/spawners/gibspawner.dm @@ -1,153 +1,153 @@ - -/obj/effect/gibspawner - icon_state = "gibspawner"// For the map editor - var/sparks = 0 //whether sparks spread - var/virusProb = 20 //the chance for viruses to spread on the gibs - var/gib_mob_type //generate a fake mob to transfer DNA from if we weren't passed a mob. - var/sound_to_play = 'sound/effects/blobattack.ogg' - var/sound_vol = 60 - var/list/gibtypes = list() //typepaths of the gib decals to spawn - var/list/gibamounts = list() //amount to spawn for each gib decal type we'll spawn. - var/list/gibdirections = list() //of lists of possible directions to spread each gib decal type towards. - -/obj/effect/gibspawner/Initialize(mapload, mob/living/source_mob, list/datum/disease/diseases) - . = ..() - - if(gibtypes.len != gibamounts.len) - stack_trace("Gib list amount length mismatch!") - return - if(gibamounts.len != gibdirections.len) - stack_trace("Gib list dir length mismatch!") - return - - var/obj/effect/decal/cleanable/blood/gibs/gib = null - - if(sound_to_play && isnum(sound_vol)) - playsound(src, sound_to_play, sound_vol, TRUE) - - if(sparks) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(2, 1, loc) - s.start() - - - var/list/dna_to_add //find the dna to pass to the spawned gibs. do note this can be null if the mob doesn't have blood. add_blood_DNA() has built in null handling. - if(source_mob) - dna_to_add = source_mob.get_blood_dna_list() //ez pz - else if(gib_mob_type) - var/mob/living/temp_mob = new gib_mob_type(src) //generate a fake mob so that we pull the right type of DNA for the gibs. - dna_to_add = temp_mob.get_blood_dna_list() - qdel(temp_mob) - else - dna_to_add = list("Non-human DNA" = random_blood_type()) //else, generate a random bloodtype for it. - - - for(var/i = 1, i<= gibtypes.len, i++) - if(gibamounts[i]) - for(var/j = 1, j<= gibamounts[i], j++) - var/gibType = gibtypes[i] - gib = new gibType(loc, diseases) - - gib.add_blood_DNA(dna_to_add) - - var/list/directions = gibdirections[i] - if(isturf(loc)) - if(directions.len) - gib.streak(directions) - - return INITIALIZE_HINT_QDEL - - -/obj/effect/gibspawner/generic - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core) - gibamounts = list(2, 2, 1) - sound_vol = 40 - -/obj/effect/gibspawner/generic/Initialize() - if(!gibdirections.len) - gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH),list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list()) - return ..() - -/obj/effect/gibspawner/generic/animal - gib_mob_type = /mob/living/simple_animal/pet - - - -/obj/effect/gibspawner/human - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/up, /obj/effect/decal/cleanable/blood/gibs/down, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/body, /obj/effect/decal/cleanable/blood/gibs/limb, /obj/effect/decal/cleanable/blood/gibs/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/human - sound_vol = 50 - -/obj/effect/gibspawner/human/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) - return ..() - - -/obj/effect/gibspawner/human/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/torso) - gibamounts = list(1, 1, 1, 1, 1, 1) - -/obj/effect/gibspawner/human/bodypartless/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) - return ..() - - - -/obj/effect/gibspawner/xeno - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/up, /obj/effect/decal/cleanable/xenoblood/xgibs/down, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/body, /obj/effect/decal/cleanable/xenoblood/xgibs/limb, /obj/effect/decal/cleanable/xenoblood/xgibs/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/alien - -/obj/effect/gibspawner/xeno/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) - return ..() - - -/obj/effect/gibspawner/xeno/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/torso) - gibamounts = list(1, 1, 1, 1, 1, 1) - - -/obj/effect/gibspawner/xeno/bodypartless/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) - return ..() - - - -/obj/effect/gibspawner/larva - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body) - gibamounts = list(1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/alien/larva - -/obj/effect/gibspawner/larva/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list(), GLOB.alldirs) - return ..() - -/obj/effect/gibspawner/larva/bodypartless - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva) - gibamounts = list(1, 1, 1) - -/obj/effect/gibspawner/larva/bodypartless/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list()) - return ..() - - - -/obj/effect/gibspawner/robot - sparks = 1 - gibtypes = list(/obj/effect/decal/cleanable/robot_debris/up, /obj/effect/decal/cleanable/robot_debris/down, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris/limb) - gibamounts = list(1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/silicon - -/obj/effect/gibspawner/robot/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs) - gibamounts[6] = pick(0, 1, 2) - return ..() + +/obj/effect/gibspawner + icon_state = "gibspawner"// For the map editor + var/sparks = 0 //whether sparks spread + var/virusProb = 20 //the chance for viruses to spread on the gibs + var/gib_mob_type //generate a fake mob to transfer DNA from if we weren't passed a mob. + var/sound_to_play = 'sound/effects/blobattack.ogg' + var/sound_vol = 60 + var/list/gibtypes = list() //typepaths of the gib decals to spawn + var/list/gibamounts = list() //amount to spawn for each gib decal type we'll spawn. + var/list/gibdirections = list() //of lists of possible directions to spread each gib decal type towards. + +/obj/effect/gibspawner/Initialize(mapload, mob/living/source_mob, list/datum/disease/diseases) + . = ..() + + if(gibtypes.len != gibamounts.len) + stack_trace("Gib list amount length mismatch!") + return + if(gibamounts.len != gibdirections.len) + stack_trace("Gib list dir length mismatch!") + return + + var/obj/effect/decal/cleanable/blood/gibs/gib = null + + if(sound_to_play && isnum(sound_vol)) + playsound(src, sound_to_play, sound_vol, TRUE) + + if(sparks) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(2, 1, loc) + s.start() + + + var/list/dna_to_add //find the dna to pass to the spawned gibs. do note this can be null if the mob doesn't have blood. add_blood_DNA() has built in null handling. + if(source_mob) + dna_to_add = source_mob.get_blood_dna_list() //ez pz + else if(gib_mob_type) + var/mob/living/temp_mob = new gib_mob_type(src) //generate a fake mob so that we pull the right type of DNA for the gibs. + dna_to_add = temp_mob.get_blood_dna_list() + qdel(temp_mob) + else + dna_to_add = list("Non-human DNA" = random_blood_type()) //else, generate a random bloodtype for it. + + + for(var/i = 1, i<= gibtypes.len, i++) + if(gibamounts[i]) + for(var/j = 1, j<= gibamounts[i], j++) + var/gibType = gibtypes[i] + gib = new gibType(loc, diseases) + + gib.add_blood_DNA(dna_to_add) + + var/list/directions = gibdirections[i] + if(isturf(loc)) + if(directions.len) + gib.streak(directions) + + return INITIALIZE_HINT_QDEL + + +/obj/effect/gibspawner/generic + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core) + gibamounts = list(2, 2, 1) + sound_vol = 40 + +/obj/effect/gibspawner/generic/Initialize() + if(!gibdirections.len) + gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH),list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list()) + return ..() + +/obj/effect/gibspawner/generic/animal + gib_mob_type = /mob/living/simple_animal/pet + + + +/obj/effect/gibspawner/human + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/up, /obj/effect/decal/cleanable/blood/gibs/down, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/body, /obj/effect/decal/cleanable/blood/gibs/limb, /obj/effect/decal/cleanable/blood/gibs/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/human + sound_vol = 50 + +/obj/effect/gibspawner/human/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) + return ..() + + +/obj/effect/gibspawner/human/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/torso) + gibamounts = list(1, 1, 1, 1, 1, 1) + +/obj/effect/gibspawner/human/bodypartless/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) + return ..() + + + +/obj/effect/gibspawner/xeno + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/up, /obj/effect/decal/cleanable/xenoblood/xgibs/down, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/body, /obj/effect/decal/cleanable/xenoblood/xgibs/limb, /obj/effect/decal/cleanable/xenoblood/xgibs/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/alien + +/obj/effect/gibspawner/xeno/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) + return ..() + + +/obj/effect/gibspawner/xeno/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/torso) + gibamounts = list(1, 1, 1, 1, 1, 1) + + +/obj/effect/gibspawner/xeno/bodypartless/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) + return ..() + + + +/obj/effect/gibspawner/larva + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body) + gibamounts = list(1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/alien/larva + +/obj/effect/gibspawner/larva/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list(), GLOB.alldirs) + return ..() + +/obj/effect/gibspawner/larva/bodypartless + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva) + gibamounts = list(1, 1, 1) + +/obj/effect/gibspawner/larva/bodypartless/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list()) + return ..() + + + +/obj/effect/gibspawner/robot + sparks = 1 + gibtypes = list(/obj/effect/decal/cleanable/robot_debris/up, /obj/effect/decal/cleanable/robot_debris/down, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris/limb) + gibamounts = list(1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/silicon + +/obj/effect/gibspawner/robot/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs) + gibamounts[6] = pick(0, 1, 2) + return ..() diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm index a7fca2dd437..dbdfffbc798 100644 --- a/code/game/objects/effects/spawners/lootdrop.dm +++ b/code/game/objects/effects/spawners/lootdrop.dm @@ -1,487 +1,487 @@ -/obj/effect/spawner/lootdrop - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "random_loot" - layer = OBJ_LAYER - var/lootcount = 1 //how many items will be spawned - var/lootdoubles = TRUE //if the same item can be spawned twice - var/list/loot //a list of possible items to spawn e.g. list(/obj/item, /obj/structure, /obj/effect) - var/fan_out_items = FALSE //Whether the items should be distributed to offsets 0,1,-1,2,-2,3,-3.. This overrides pixel_x/y on the spawner itself - -/obj/effect/spawner/lootdrop/Initialize(mapload) - ..() - if(loot && loot.len) - var/loot_spawned = 0 - while((lootcount-loot_spawned) && loot.len) - var/lootspawn = pickweight(loot) - while(islist(lootspawn)) - lootspawn = pickweight(lootspawn) - if(!lootdoubles) - loot.Remove(lootspawn) - - if(lootspawn) - var/atom/movable/spawned_loot = new lootspawn(loc) - if (!fan_out_items) - if (pixel_x != 0) - spawned_loot.pixel_x = pixel_x - if (pixel_y != 0) - spawned_loot.pixel_y = pixel_y - else - if (loot_spawned) - spawned_loot.pixel_x = spawned_loot.pixel_y = ((!(loot_spawned%2)*loot_spawned/2)*-1)+((loot_spawned%2)*(loot_spawned+1)/2*1) - loot_spawned++ - return INITIALIZE_HINT_QDEL - -/obj/effect/spawner/lootdrop/donkpockets - name = "donk pocket box spawner" - lootdoubles = FALSE - - loot = list( - /obj/item/storage/box/donkpockets/donkpocketspicy = 1, - /obj/item/storage/box/donkpockets/donkpocketteriyaki = 1, - /obj/item/storage/box/donkpockets/donkpocketpizza = 1, - /obj/item/storage/box/donkpockets/donkpocketberry = 1, - /obj/item/storage/box/donkpockets/donkpockethonk = 1, - ) - - -/obj/effect/spawner/lootdrop/armory_contraband - name = "armory contraband gun spawner" - lootdoubles = FALSE - - loot = list( - /obj/item/gun/ballistic/automatic/pistol = 8, - /obj/item/gun/ballistic/shotgun/automatic/combat = 5, - /obj/item/gun/ballistic/automatic/pistol/deagle, - /obj/item/gun/ballistic/revolver/mateba - ) - -/obj/effect/spawner/lootdrop/armory_contraband/metastation - loot = list(/obj/item/gun/ballistic/automatic/pistol = 5, - /obj/item/gun/ballistic/shotgun/automatic/combat = 5, - /obj/item/gun/ballistic/automatic/pistol/deagle, - /obj/item/storage/box/syndie_kit/throwing_weapons = 3, - /obj/item/gun/ballistic/revolver/mateba) - -/obj/effect/spawner/lootdrop/armory_contraband/donutstation - loot = list(/obj/item/grenade/clusterbuster/teargas = 5, - /obj/item/gun/ballistic/shotgun/automatic/combat = 5, - /obj/item/bikehorn/golden, - /obj/item/grenade/clusterbuster, - /obj/item/storage/box/syndie_kit/throwing_weapons = 3, - /obj/item/gun/ballistic/revolver/mateba) - -/obj/effect/spawner/lootdrop/prison_contraband - name = "prison contraband loot spawner" - loot = list(/obj/item/clothing/mask/cigarette/space_cigarette = 4, - /obj/item/clothing/mask/cigarette/robust = 2, - /obj/item/clothing/mask/cigarette/carp = 3, - /obj/item/clothing/mask/cigarette/uplift = 2, - /obj/item/clothing/mask/cigarette/dromedary = 3, - /obj/item/clothing/mask/cigarette/robustgold = 1, - /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3, - /obj/item/storage/fancy/cigarettes = 3, - /obj/item/clothing/mask/cigarette/rollie/cannabis = 4, - /obj/item/toy/crayon/spraycan = 2, - /obj/item/crowbar = 1, - /obj/item/assembly/flash/handheld = 1, - /obj/item/restraints/handcuffs/cable/zipties = 1, - /obj/item/restraints/handcuffs = 1, - /obj/item/radio/off = 1, - /obj/item/lighter = 3, - /obj/item/storage/box/matches = 3, - /obj/item/reagent_containers/syringe/contraband/space_drugs = 1, - /obj/item/reagent_containers/syringe/contraband/krokodil = 1, - /obj/item/reagent_containers/syringe/contraband/crank = 1, - /obj/item/reagent_containers/syringe/contraband/methamphetamine = 1, - /obj/item/reagent_containers/syringe/contraband/bath_salts = 1, - /obj/item/reagent_containers/syringe/contraband/fentanyl = 1, - /obj/item/reagent_containers/syringe/contraband/morphine = 1, - /obj/item/storage/pill_bottle/happy = 1, - /obj/item/storage/pill_bottle/lsd = 1, - /obj/item/storage/pill_bottle/psicodine = 1, - /obj/item/reagent_containers/food/drinks/beer = 4, - /obj/item/reagent_containers/food/drinks/bottle/whiskey = 1, - /obj/item/paper/fluff/jobs/prisoner/letter = 1, - /obj/item/grenade/smokebomb = 1, - /obj/item/flashlight/seclite = 1, - /obj/item/tailclub = 1, //want to buy makeshift wooden club sprite - /obj/item/kitchen/knife/shiv = 4, - /obj/item/kitchen/knife/shiv/carrot = 1, - /obj/item/kitchen/knife = 1, - /obj/item/storage/wallet/random = 1, - /obj/item/pda = 1 - ) - -/obj/effect/spawner/lootdrop/gambling - name = "gambling valuables spawner" - loot = list( - /obj/item/gun/ballistic/revolver/russian = 5, - /obj/item/clothing/head/ushanka = 3, - /obj/item/storage/box/syndie_kit/throwing_weapons, - /obj/item/coin/gold, - /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka, - ) - -/obj/effect/spawner/lootdrop/grille_or_trash - name = "maint grille or trash spawner" - loot = list(/obj/structure/grille = 5, - /obj/item/cigbutt = 1, - /obj/item/trash/cheesie = 1, - /obj/item/trash/candy = 1, - /obj/item/trash/chips = 1, - /obj/item/reagent_containers/food/snacks/deadmouse = 1, - /obj/item/trash/pistachios = 1, - /obj/item/trash/plate = 1, - /obj/item/trash/popcorn = 1, - /obj/item/trash/raisins = 1, - /obj/item/trash/sosjerky = 1, - /obj/item/trash/syndi_cakes = 1) - -/obj/effect/spawner/lootdrop/three_course_meal - name = "three course meal spawner" - lootcount = 3 - lootdoubles = FALSE - var/soups = list( - /obj/item/reagent_containers/food/snacks/soup/beet, - /obj/item/reagent_containers/food/snacks/soup/sweetpotato, - /obj/item/reagent_containers/food/snacks/soup/stew, - /obj/item/reagent_containers/food/snacks/soup/hotchili, - /obj/item/reagent_containers/food/snacks/soup/nettle, - /obj/item/reagent_containers/food/snacks/soup/meatball) - var/salads = list( - /obj/item/reagent_containers/food/snacks/salad/herbsalad, - /obj/item/reagent_containers/food/snacks/salad/validsalad, - /obj/item/reagent_containers/food/snacks/salad/fruit, - /obj/item/reagent_containers/food/snacks/salad/jungle, - /obj/item/reagent_containers/food/snacks/salad/aesirsalad) - var/mains = list( - /obj/item/reagent_containers/food/snacks/bearsteak, - /obj/item/reagent_containers/food/snacks/enchiladas, - /obj/item/reagent_containers/food/snacks/stewedsoymeat, - /obj/item/reagent_containers/food/snacks/burger/bigbite, - /obj/item/reagent_containers/food/snacks/burger/superbite, - /obj/item/reagent_containers/food/snacks/burger/fivealarm) - -/obj/effect/spawner/lootdrop/three_course_meal/Initialize(mapload) - loot = list(pick(soups) = 1,pick(salads) = 1,pick(mains) = 1) - . = ..() - -/obj/effect/spawner/lootdrop/maintenance - name = "maintenance loot spawner" - // see code/_globalvars/lists/maintenance_loot.dm for loot table - -/obj/effect/spawner/lootdrop/maintenance/Initialize(mapload) - loot = GLOB.maintenance_loot - . = ..() - -/obj/effect/spawner/lootdrop/maintenance/two - name = "2 x maintenance loot spawner" - lootcount = 2 - -/obj/effect/spawner/lootdrop/maintenance/three - name = "3 x maintenance loot spawner" - lootcount = 3 - -/obj/effect/spawner/lootdrop/maintenance/four - name = "4 x maintenance loot spawner" - lootcount = 4 - -/obj/effect/spawner/lootdrop/maintenance/five - name = "5 x maintenance loot spawner" - lootcount = 5 - -/obj/effect/spawner/lootdrop/maintenance/six - name = "6 x maintenance loot spawner" - lootcount = 6 - -/obj/effect/spawner/lootdrop/maintenance/seven - name = "7 x maintenance loot spawner" - lootcount = 7 - -/obj/effect/spawner/lootdrop/maintenance/eight - name = "8 x maintenance loot spawner" - lootcount = 8 - -/obj/effect/spawner/lootdrop/crate_spawner - name = "lootcrate spawner" //USE PROMO CODE "SELLOUT" FOR 20% OFF! - lootdoubles = FALSE - - loot = list( - /obj/structure/closet/crate/secure/loot = 20, - "" = 80 - ) - -/obj/effect/spawner/lootdrop/organ_spawner - name = "ayylien organ spawner" - loot = list( - /obj/item/organ/heart/gland/electric = 3, - /obj/item/organ/heart/gland/trauma = 4, - /obj/item/organ/heart/gland/egg = 7, - /obj/item/organ/heart/gland/chem = 5, - /obj/item/organ/heart/gland/mindshock = 5, - /obj/item/organ/heart/gland/plasma = 7, - /obj/item/organ/heart/gland/transform = 5, - /obj/item/organ/heart/gland/slime = 4, - /obj/item/organ/heart/gland/spiderman = 5, - /obj/item/organ/heart/gland/ventcrawling = 1, - /obj/item/organ/body_egg/alien_embryo = 1, - /obj/item/organ/regenerative_core = 2) - lootcount = 3 - -/obj/effect/spawner/lootdrop/memeorgans - name = "meme organ spawner" - loot = list( - /obj/item/organ/ears/penguin, - /obj/item/organ/ears/cat, - /obj/item/organ/eyes/moth, - /obj/item/organ/eyes/snail, - /obj/item/organ/tongue/bone, - /obj/item/organ/tongue/fly, - /obj/item/organ/tongue/snail, - /obj/item/organ/tongue/lizard, - /obj/item/organ/tongue/alien, - /obj/item/organ/tongue/ethereal, - /obj/item/organ/tongue/robot, - /obj/item/organ/tongue/zombie, - /obj/item/organ/appendix, - /obj/item/organ/liver/fly, - /obj/item/organ/lungs/plasmaman, - /obj/item/organ/tail/cat, - /obj/item/organ/tail/lizard) - lootcount = 5 - -/obj/effect/spawner/lootdrop/two_percent_xeno_egg_spawner - name = "2% chance xeno egg spawner" - loot = list( - /obj/effect/decal/remains/xeno = 49, - /obj/effect/spawner/xeno_egg_delivery = 1) - -/obj/effect/spawner/lootdrop/costume - name = "random costume spawner" - -/obj/effect/spawner/lootdrop/costume/Initialize() - loot = list() - for(var/path in subtypesof(/obj/effect/spawner/bundle/costume)) - loot[path] = TRUE - . = ..() - -// Minor lootdrops follow - -/obj/effect/spawner/lootdrop/minor/beret_or_rabbitears - name = "beret or rabbit ears spawner" - loot = list( - /obj/item/clothing/head/beret = 1, - /obj/item/clothing/head/rabbitears = 1) - -/obj/effect/spawner/lootdrop/minor/bowler_or_that - name = "bowler or top hat spawner" - loot = list( - /obj/item/clothing/head/bowler = 1, - /obj/item/clothing/head/that = 1) - -/obj/effect/spawner/lootdrop/minor/kittyears_or_rabbitears - name = "kitty ears or rabbit ears spawner" - loot = list( - /obj/item/clothing/head/kitty = 1, - /obj/item/clothing/head/rabbitears = 1) - -/obj/effect/spawner/lootdrop/minor/pirate_or_bandana - name = "pirate hat or bandana spawner" - loot = list( - /obj/item/clothing/head/pirate = 1, - /obj/item/clothing/head/bandana = 1) - -/obj/effect/spawner/lootdrop/minor/twentyfive_percent_cyborg_mask - name = "25% cyborg mask spawner" - loot = list( - /obj/item/clothing/mask/gas/cyborg = 25, - "" = 75) - -/obj/effect/spawner/lootdrop/aimodule_harmless // These shouldn't allow the AI to start butchering people - name = "harmless AI module spawner" - loot = list( - /obj/item/ai_module/core/full/asimov, - /obj/item/ai_module/core/full/asimovpp, - /obj/item/ai_module/core/full/hippocratic, - /obj/item/ai_module/core/full/paladin_devotion, - /obj/item/ai_module/core/full/paladin - ) - -/obj/effect/spawner/lootdrop/aimodule_neutral // These shouldn't allow the AI to start butchering people without reason - name = "neutral AI module spawner" - loot = list( - /obj/item/ai_module/core/full/corp, - /obj/item/ai_module/core/full/maintain, - /obj/item/ai_module/core/full/drone, - /obj/item/ai_module/core/full/peacekeeper, - /obj/item/ai_module/core/full/reporter, - /obj/item/ai_module/core/full/robocop, - /obj/item/ai_module/core/full/liveandletlive, - /obj/item/ai_module/core/full/hulkamania - ) - -/obj/effect/spawner/lootdrop/aimodule_harmful // These will get the shuttle called - name = "harmful AI module spawner" - loot = list( - /obj/item/ai_module/core/full/antimov, - /obj/item/ai_module/core/full/balance, - /obj/item/ai_module/core/full/tyrant, - /obj/item/ai_module/core/full/thermurderdynamic, - /obj/item/ai_module/core/full/damaged, - /obj/item/ai_module/reset/purge - ) - -// Tech storage circuit board spawners - -/obj/effect/spawner/lootdrop/techstorage - name = "generic circuit board spawner" - lootdoubles = FALSE - fan_out_items = TRUE - lootcount = INFINITY - -/obj/effect/spawner/lootdrop/techstorage/service - name = "service circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/arcade/battle, - /obj/item/circuitboard/computer/arcade/orion_trail, - /obj/item/circuitboard/machine/autolathe, - /obj/item/circuitboard/computer/mining, - /obj/item/circuitboard/machine/ore_redemption, - /obj/item/circuitboard/machine/mining_equipment_vendor, - /obj/item/circuitboard/machine/microwave, - /obj/item/circuitboard/machine/chem_dispenser/drinks, - /obj/item/circuitboard/machine/chem_dispenser/drinks/beer, - /obj/item/circuitboard/computer/slot_machine - ) - -/obj/effect/spawner/lootdrop/techstorage/rnd - name = "RnD circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/aifixer, - /obj/item/circuitboard/machine/rdserver, - /obj/item/circuitboard/machine/mechfab, - /obj/item/circuitboard/machine/circuit_imprinter/department, - /obj/item/circuitboard/computer/teleporter, - /obj/item/circuitboard/machine/destructive_analyzer, - /obj/item/circuitboard/computer/rdconsole, - /obj/item/circuitboard/computer/nanite_chamber_control, - /obj/item/circuitboard/computer/nanite_cloud_controller, - /obj/item/circuitboard/machine/nanite_chamber, - /obj/item/circuitboard/machine/nanite_programmer, - /obj/item/circuitboard/machine/nanite_program_hub, - /obj/item/circuitboard/computer/scan_consolenew, - /obj/item/circuitboard/machine/dnascanner - ) - -/obj/effect/spawner/lootdrop/techstorage/security - name = "security circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/secure_data, - /obj/item/circuitboard/computer/security, - /obj/item/circuitboard/computer/prisoner - ) - -/obj/effect/spawner/lootdrop/techstorage/engineering - name = "engineering circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/atmos_alert, - /obj/item/circuitboard/computer/stationalert, - /obj/item/circuitboard/computer/powermonitor - ) - -/obj/effect/spawner/lootdrop/techstorage/tcomms - name = "tcomms circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/message_monitor, - /obj/item/circuitboard/machine/telecomms/broadcaster, - /obj/item/circuitboard/machine/telecomms/bus, - /obj/item/circuitboard/machine/telecomms/server, - /obj/item/circuitboard/machine/telecomms/receiver, - /obj/item/circuitboard/machine/telecomms/processor, - /obj/item/circuitboard/machine/announcement_system, - /obj/item/circuitboard/computer/comm_server, - /obj/item/circuitboard/computer/comm_monitor - ) - -/obj/effect/spawner/lootdrop/techstorage/medical - name = "medical circuit board spawner" - loot = list( - /obj/item/circuitboard/machine/chem_dispenser, - /obj/item/circuitboard/computer/med_data, - /obj/item/circuitboard/machine/smoke_machine, - /obj/item/circuitboard/machine/chem_master, - /obj/item/circuitboard/computer/pandemic - ) - -/obj/effect/spawner/lootdrop/techstorage/ai - name = "secure AI circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/aiupload, - /obj/item/circuitboard/computer/borgupload, - /obj/item/circuitboard/aicore - ) - -/obj/effect/spawner/lootdrop/techstorage/command - name = "secure command circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/crew, - /obj/item/circuitboard/computer/communications, - /obj/item/circuitboard/computer/card - ) - -/obj/effect/spawner/lootdrop/techstorage/rnd_secure - name = "secure RnD circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/mecha_control, - /obj/item/circuitboard/computer/apc_control, - /obj/item/circuitboard/computer/robotics - ) - -/obj/effect/spawner/lootdrop/mafia_outfit - name = "mafia outfit spawner" - loot = list( - /obj/effect/spawner/bundle/costume/mafia = 20, - /obj/effect/spawner/bundle/costume/mafia/white = 5, - /obj/effect/spawner/bundle/costume/mafia/checkered = 2, - /obj/effect/spawner/bundle/costume/mafia/beige = 5 - ) - -//finds the probabilities of items spawning from a loot spawner's loot pool -/obj/item/loot_table_maker - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "random_loot" - var/spawner_to_test = /obj/effect/spawner/lootdrop/maintenance //what lootdrop spawner to use the loot pool of - var/loot_count = 180 //180 is about how much maint loot spawns per map as of 11/14/2019 - //result outputs - var/list/spawned_table //list of all items "spawned" and how many - var/list/stat_table //list of all items "spawned" and their occurrance probability - -/obj/item/loot_table_maker/Initialize() - . = ..() - make_table() - -/obj/item/loot_table_maker/attack_self(mob/user) - to_chat(user, "Loot pool re-rolled.") - make_table() - -/obj/item/loot_table_maker/proc/make_table() - spawned_table = list() - stat_table = list() - var/obj/effect/spawner/lootdrop/spawner_to_table = new spawner_to_test - var/lootpool = spawner_to_table.loot - qdel(spawner_to_table) - for(var/i in 1 to loot_count) - var/loot_spawn = pick_loot(lootpool) - if(!(loot_spawn in spawned_table)) - spawned_table[loot_spawn] = 1 - else - spawned_table[loot_spawn] += 1 - stat_table += spawned_table - for(var/item in stat_table) - stat_table[item] /= loot_count - -/obj/item/loot_table_maker/proc/pick_loot(lootpool) //selects path from loot table and returns it - var/lootspawn = pickweight(lootpool) - while(islist(lootspawn)) - lootspawn = pickweight(lootspawn) - return lootspawn +/obj/effect/spawner/lootdrop + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "random_loot" + layer = OBJ_LAYER + var/lootcount = 1 //how many items will be spawned + var/lootdoubles = TRUE //if the same item can be spawned twice + var/list/loot //a list of possible items to spawn e.g. list(/obj/item, /obj/structure, /obj/effect) + var/fan_out_items = FALSE //Whether the items should be distributed to offsets 0,1,-1,2,-2,3,-3.. This overrides pixel_x/y on the spawner itself + +/obj/effect/spawner/lootdrop/Initialize(mapload) + ..() + if(loot && loot.len) + var/loot_spawned = 0 + while((lootcount-loot_spawned) && loot.len) + var/lootspawn = pickweight(loot) + while(islist(lootspawn)) + lootspawn = pickweight(lootspawn) + if(!lootdoubles) + loot.Remove(lootspawn) + + if(lootspawn) + var/atom/movable/spawned_loot = new lootspawn(loc) + if (!fan_out_items) + if (pixel_x != 0) + spawned_loot.pixel_x = pixel_x + if (pixel_y != 0) + spawned_loot.pixel_y = pixel_y + else + if (loot_spawned) + spawned_loot.pixel_x = spawned_loot.pixel_y = ((!(loot_spawned%2)*loot_spawned/2)*-1)+((loot_spawned%2)*(loot_spawned+1)/2*1) + loot_spawned++ + return INITIALIZE_HINT_QDEL + +/obj/effect/spawner/lootdrop/donkpockets + name = "donk pocket box spawner" + lootdoubles = FALSE + + loot = list( + /obj/item/storage/box/donkpockets/donkpocketspicy = 1, + /obj/item/storage/box/donkpockets/donkpocketteriyaki = 1, + /obj/item/storage/box/donkpockets/donkpocketpizza = 1, + /obj/item/storage/box/donkpockets/donkpocketberry = 1, + /obj/item/storage/box/donkpockets/donkpockethonk = 1, + ) + + +/obj/effect/spawner/lootdrop/armory_contraband + name = "armory contraband gun spawner" + lootdoubles = FALSE + + loot = list( + /obj/item/gun/ballistic/automatic/pistol = 8, + /obj/item/gun/ballistic/shotgun/automatic/combat = 5, + /obj/item/gun/ballistic/automatic/pistol/deagle, + /obj/item/gun/ballistic/revolver/mateba + ) + +/obj/effect/spawner/lootdrop/armory_contraband/metastation + loot = list(/obj/item/gun/ballistic/automatic/pistol = 5, + /obj/item/gun/ballistic/shotgun/automatic/combat = 5, + /obj/item/gun/ballistic/automatic/pistol/deagle, + /obj/item/storage/box/syndie_kit/throwing_weapons = 3, + /obj/item/gun/ballistic/revolver/mateba) + +/obj/effect/spawner/lootdrop/armory_contraband/donutstation + loot = list(/obj/item/grenade/clusterbuster/teargas = 5, + /obj/item/gun/ballistic/shotgun/automatic/combat = 5, + /obj/item/bikehorn/golden, + /obj/item/grenade/clusterbuster, + /obj/item/storage/box/syndie_kit/throwing_weapons = 3, + /obj/item/gun/ballistic/revolver/mateba) + +/obj/effect/spawner/lootdrop/prison_contraband + name = "prison contraband loot spawner" + loot = list(/obj/item/clothing/mask/cigarette/space_cigarette = 4, + /obj/item/clothing/mask/cigarette/robust = 2, + /obj/item/clothing/mask/cigarette/carp = 3, + /obj/item/clothing/mask/cigarette/uplift = 2, + /obj/item/clothing/mask/cigarette/dromedary = 3, + /obj/item/clothing/mask/cigarette/robustgold = 1, + /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3, + /obj/item/storage/fancy/cigarettes = 3, + /obj/item/clothing/mask/cigarette/rollie/cannabis = 4, + /obj/item/toy/crayon/spraycan = 2, + /obj/item/crowbar = 1, + /obj/item/assembly/flash/handheld = 1, + /obj/item/restraints/handcuffs/cable/zipties = 1, + /obj/item/restraints/handcuffs = 1, + /obj/item/radio/off = 1, + /obj/item/lighter = 3, + /obj/item/storage/box/matches = 3, + /obj/item/reagent_containers/syringe/contraband/space_drugs = 1, + /obj/item/reagent_containers/syringe/contraband/krokodil = 1, + /obj/item/reagent_containers/syringe/contraband/crank = 1, + /obj/item/reagent_containers/syringe/contraband/methamphetamine = 1, + /obj/item/reagent_containers/syringe/contraband/bath_salts = 1, + /obj/item/reagent_containers/syringe/contraband/fentanyl = 1, + /obj/item/reagent_containers/syringe/contraband/morphine = 1, + /obj/item/storage/pill_bottle/happy = 1, + /obj/item/storage/pill_bottle/lsd = 1, + /obj/item/storage/pill_bottle/psicodine = 1, + /obj/item/reagent_containers/food/drinks/beer = 4, + /obj/item/reagent_containers/food/drinks/bottle/whiskey = 1, + /obj/item/paper/fluff/jobs/prisoner/letter = 1, + /obj/item/grenade/smokebomb = 1, + /obj/item/flashlight/seclite = 1, + /obj/item/tailclub = 1, //want to buy makeshift wooden club sprite + /obj/item/kitchen/knife/shiv = 4, + /obj/item/kitchen/knife/shiv/carrot = 1, + /obj/item/kitchen/knife = 1, + /obj/item/storage/wallet/random = 1, + /obj/item/pda = 1 + ) + +/obj/effect/spawner/lootdrop/gambling + name = "gambling valuables spawner" + loot = list( + /obj/item/gun/ballistic/revolver/russian = 5, + /obj/item/clothing/head/ushanka = 3, + /obj/item/storage/box/syndie_kit/throwing_weapons, + /obj/item/coin/gold, + /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka, + ) + +/obj/effect/spawner/lootdrop/grille_or_trash + name = "maint grille or trash spawner" + loot = list(/obj/structure/grille = 5, + /obj/item/cigbutt = 1, + /obj/item/trash/cheesie = 1, + /obj/item/trash/candy = 1, + /obj/item/trash/chips = 1, + /obj/item/reagent_containers/food/snacks/deadmouse = 1, + /obj/item/trash/pistachios = 1, + /obj/item/trash/plate = 1, + /obj/item/trash/popcorn = 1, + /obj/item/trash/raisins = 1, + /obj/item/trash/sosjerky = 1, + /obj/item/trash/syndi_cakes = 1) + +/obj/effect/spawner/lootdrop/three_course_meal + name = "three course meal spawner" + lootcount = 3 + lootdoubles = FALSE + var/soups = list( + /obj/item/reagent_containers/food/snacks/soup/beet, + /obj/item/reagent_containers/food/snacks/soup/sweetpotato, + /obj/item/reagent_containers/food/snacks/soup/stew, + /obj/item/reagent_containers/food/snacks/soup/hotchili, + /obj/item/reagent_containers/food/snacks/soup/nettle, + /obj/item/reagent_containers/food/snacks/soup/meatball) + var/salads = list( + /obj/item/reagent_containers/food/snacks/salad/herbsalad, + /obj/item/reagent_containers/food/snacks/salad/validsalad, + /obj/item/reagent_containers/food/snacks/salad/fruit, + /obj/item/reagent_containers/food/snacks/salad/jungle, + /obj/item/reagent_containers/food/snacks/salad/aesirsalad) + var/mains = list( + /obj/item/reagent_containers/food/snacks/bearsteak, + /obj/item/reagent_containers/food/snacks/enchiladas, + /obj/item/reagent_containers/food/snacks/stewedsoymeat, + /obj/item/reagent_containers/food/snacks/burger/bigbite, + /obj/item/reagent_containers/food/snacks/burger/superbite, + /obj/item/reagent_containers/food/snacks/burger/fivealarm) + +/obj/effect/spawner/lootdrop/three_course_meal/Initialize(mapload) + loot = list(pick(soups) = 1,pick(salads) = 1,pick(mains) = 1) + . = ..() + +/obj/effect/spawner/lootdrop/maintenance + name = "maintenance loot spawner" + // see code/_globalvars/lists/maintenance_loot.dm for loot table + +/obj/effect/spawner/lootdrop/maintenance/Initialize(mapload) + loot = GLOB.maintenance_loot + . = ..() + +/obj/effect/spawner/lootdrop/maintenance/two + name = "2 x maintenance loot spawner" + lootcount = 2 + +/obj/effect/spawner/lootdrop/maintenance/three + name = "3 x maintenance loot spawner" + lootcount = 3 + +/obj/effect/spawner/lootdrop/maintenance/four + name = "4 x maintenance loot spawner" + lootcount = 4 + +/obj/effect/spawner/lootdrop/maintenance/five + name = "5 x maintenance loot spawner" + lootcount = 5 + +/obj/effect/spawner/lootdrop/maintenance/six + name = "6 x maintenance loot spawner" + lootcount = 6 + +/obj/effect/spawner/lootdrop/maintenance/seven + name = "7 x maintenance loot spawner" + lootcount = 7 + +/obj/effect/spawner/lootdrop/maintenance/eight + name = "8 x maintenance loot spawner" + lootcount = 8 + +/obj/effect/spawner/lootdrop/crate_spawner + name = "lootcrate spawner" //USE PROMO CODE "SELLOUT" FOR 20% OFF! + lootdoubles = FALSE + + loot = list( + /obj/structure/closet/crate/secure/loot = 20, + "" = 80 + ) + +/obj/effect/spawner/lootdrop/organ_spawner + name = "ayylien organ spawner" + loot = list( + /obj/item/organ/heart/gland/electric = 3, + /obj/item/organ/heart/gland/trauma = 4, + /obj/item/organ/heart/gland/egg = 7, + /obj/item/organ/heart/gland/chem = 5, + /obj/item/organ/heart/gland/mindshock = 5, + /obj/item/organ/heart/gland/plasma = 7, + /obj/item/organ/heart/gland/transform = 5, + /obj/item/organ/heart/gland/slime = 4, + /obj/item/organ/heart/gland/spiderman = 5, + /obj/item/organ/heart/gland/ventcrawling = 1, + /obj/item/organ/body_egg/alien_embryo = 1, + /obj/item/organ/regenerative_core = 2) + lootcount = 3 + +/obj/effect/spawner/lootdrop/memeorgans + name = "meme organ spawner" + loot = list( + /obj/item/organ/ears/penguin, + /obj/item/organ/ears/cat, + /obj/item/organ/eyes/moth, + /obj/item/organ/eyes/snail, + /obj/item/organ/tongue/bone, + /obj/item/organ/tongue/fly, + /obj/item/organ/tongue/snail, + /obj/item/organ/tongue/lizard, + /obj/item/organ/tongue/alien, + /obj/item/organ/tongue/ethereal, + /obj/item/organ/tongue/robot, + /obj/item/organ/tongue/zombie, + /obj/item/organ/appendix, + /obj/item/organ/liver/fly, + /obj/item/organ/lungs/plasmaman, + /obj/item/organ/tail/cat, + /obj/item/organ/tail/lizard) + lootcount = 5 + +/obj/effect/spawner/lootdrop/two_percent_xeno_egg_spawner + name = "2% chance xeno egg spawner" + loot = list( + /obj/effect/decal/remains/xeno = 49, + /obj/effect/spawner/xeno_egg_delivery = 1) + +/obj/effect/spawner/lootdrop/costume + name = "random costume spawner" + +/obj/effect/spawner/lootdrop/costume/Initialize() + loot = list() + for(var/path in subtypesof(/obj/effect/spawner/bundle/costume)) + loot[path] = TRUE + . = ..() + +// Minor lootdrops follow + +/obj/effect/spawner/lootdrop/minor/beret_or_rabbitears + name = "beret or rabbit ears spawner" + loot = list( + /obj/item/clothing/head/beret = 1, + /obj/item/clothing/head/rabbitears = 1) + +/obj/effect/spawner/lootdrop/minor/bowler_or_that + name = "bowler or top hat spawner" + loot = list( + /obj/item/clothing/head/bowler = 1, + /obj/item/clothing/head/that = 1) + +/obj/effect/spawner/lootdrop/minor/kittyears_or_rabbitears + name = "kitty ears or rabbit ears spawner" + loot = list( + /obj/item/clothing/head/kitty = 1, + /obj/item/clothing/head/rabbitears = 1) + +/obj/effect/spawner/lootdrop/minor/pirate_or_bandana + name = "pirate hat or bandana spawner" + loot = list( + /obj/item/clothing/head/pirate = 1, + /obj/item/clothing/head/bandana = 1) + +/obj/effect/spawner/lootdrop/minor/twentyfive_percent_cyborg_mask + name = "25% cyborg mask spawner" + loot = list( + /obj/item/clothing/mask/gas/cyborg = 25, + "" = 75) + +/obj/effect/spawner/lootdrop/aimodule_harmless // These shouldn't allow the AI to start butchering people + name = "harmless AI module spawner" + loot = list( + /obj/item/ai_module/core/full/asimov, + /obj/item/ai_module/core/full/asimovpp, + /obj/item/ai_module/core/full/hippocratic, + /obj/item/ai_module/core/full/paladin_devotion, + /obj/item/ai_module/core/full/paladin + ) + +/obj/effect/spawner/lootdrop/aimodule_neutral // These shouldn't allow the AI to start butchering people without reason + name = "neutral AI module spawner" + loot = list( + /obj/item/ai_module/core/full/corp, + /obj/item/ai_module/core/full/maintain, + /obj/item/ai_module/core/full/drone, + /obj/item/ai_module/core/full/peacekeeper, + /obj/item/ai_module/core/full/reporter, + /obj/item/ai_module/core/full/robocop, + /obj/item/ai_module/core/full/liveandletlive, + /obj/item/ai_module/core/full/hulkamania + ) + +/obj/effect/spawner/lootdrop/aimodule_harmful // These will get the shuttle called + name = "harmful AI module spawner" + loot = list( + /obj/item/ai_module/core/full/antimov, + /obj/item/ai_module/core/full/balance, + /obj/item/ai_module/core/full/tyrant, + /obj/item/ai_module/core/full/thermurderdynamic, + /obj/item/ai_module/core/full/damaged, + /obj/item/ai_module/reset/purge + ) + +// Tech storage circuit board spawners + +/obj/effect/spawner/lootdrop/techstorage + name = "generic circuit board spawner" + lootdoubles = FALSE + fan_out_items = TRUE + lootcount = INFINITY + +/obj/effect/spawner/lootdrop/techstorage/service + name = "service circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/arcade/battle, + /obj/item/circuitboard/computer/arcade/orion_trail, + /obj/item/circuitboard/machine/autolathe, + /obj/item/circuitboard/computer/mining, + /obj/item/circuitboard/machine/ore_redemption, + /obj/item/circuitboard/machine/mining_equipment_vendor, + /obj/item/circuitboard/machine/microwave, + /obj/item/circuitboard/machine/chem_dispenser/drinks, + /obj/item/circuitboard/machine/chem_dispenser/drinks/beer, + /obj/item/circuitboard/computer/slot_machine + ) + +/obj/effect/spawner/lootdrop/techstorage/rnd + name = "RnD circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/aifixer, + /obj/item/circuitboard/machine/rdserver, + /obj/item/circuitboard/machine/mechfab, + /obj/item/circuitboard/machine/circuit_imprinter/department, + /obj/item/circuitboard/computer/teleporter, + /obj/item/circuitboard/machine/destructive_analyzer, + /obj/item/circuitboard/computer/rdconsole, + /obj/item/circuitboard/computer/nanite_chamber_control, + /obj/item/circuitboard/computer/nanite_cloud_controller, + /obj/item/circuitboard/machine/nanite_chamber, + /obj/item/circuitboard/machine/nanite_programmer, + /obj/item/circuitboard/machine/nanite_program_hub, + /obj/item/circuitboard/computer/scan_consolenew, + /obj/item/circuitboard/machine/dnascanner + ) + +/obj/effect/spawner/lootdrop/techstorage/security + name = "security circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/secure_data, + /obj/item/circuitboard/computer/security, + /obj/item/circuitboard/computer/prisoner + ) + +/obj/effect/spawner/lootdrop/techstorage/engineering + name = "engineering circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/atmos_alert, + /obj/item/circuitboard/computer/stationalert, + /obj/item/circuitboard/computer/powermonitor + ) + +/obj/effect/spawner/lootdrop/techstorage/tcomms + name = "tcomms circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/message_monitor, + /obj/item/circuitboard/machine/telecomms/broadcaster, + /obj/item/circuitboard/machine/telecomms/bus, + /obj/item/circuitboard/machine/telecomms/server, + /obj/item/circuitboard/machine/telecomms/receiver, + /obj/item/circuitboard/machine/telecomms/processor, + /obj/item/circuitboard/machine/announcement_system, + /obj/item/circuitboard/computer/comm_server, + /obj/item/circuitboard/computer/comm_monitor + ) + +/obj/effect/spawner/lootdrop/techstorage/medical + name = "medical circuit board spawner" + loot = list( + /obj/item/circuitboard/machine/chem_dispenser, + /obj/item/circuitboard/computer/med_data, + /obj/item/circuitboard/machine/smoke_machine, + /obj/item/circuitboard/machine/chem_master, + /obj/item/circuitboard/computer/pandemic + ) + +/obj/effect/spawner/lootdrop/techstorage/ai + name = "secure AI circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/aiupload, + /obj/item/circuitboard/computer/borgupload, + /obj/item/circuitboard/aicore + ) + +/obj/effect/spawner/lootdrop/techstorage/command + name = "secure command circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/crew, + /obj/item/circuitboard/computer/communications, + /obj/item/circuitboard/computer/card + ) + +/obj/effect/spawner/lootdrop/techstorage/rnd_secure + name = "secure RnD circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/mecha_control, + /obj/item/circuitboard/computer/apc_control, + /obj/item/circuitboard/computer/robotics + ) + +/obj/effect/spawner/lootdrop/mafia_outfit + name = "mafia outfit spawner" + loot = list( + /obj/effect/spawner/bundle/costume/mafia = 20, + /obj/effect/spawner/bundle/costume/mafia/white = 5, + /obj/effect/spawner/bundle/costume/mafia/checkered = 2, + /obj/effect/spawner/bundle/costume/mafia/beige = 5 + ) + +//finds the probabilities of items spawning from a loot spawner's loot pool +/obj/item/loot_table_maker + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "random_loot" + var/spawner_to_test = /obj/effect/spawner/lootdrop/maintenance //what lootdrop spawner to use the loot pool of + var/loot_count = 180 //180 is about how much maint loot spawns per map as of 11/14/2019 + //result outputs + var/list/spawned_table //list of all items "spawned" and how many + var/list/stat_table //list of all items "spawned" and their occurrance probability + +/obj/item/loot_table_maker/Initialize() + . = ..() + make_table() + +/obj/item/loot_table_maker/attack_self(mob/user) + to_chat(user, "Loot pool re-rolled.") + make_table() + +/obj/item/loot_table_maker/proc/make_table() + spawned_table = list() + stat_table = list() + var/obj/effect/spawner/lootdrop/spawner_to_table = new spawner_to_test + var/lootpool = spawner_to_table.loot + qdel(spawner_to_table) + for(var/i in 1 to loot_count) + var/loot_spawn = pick_loot(lootpool) + if(!(loot_spawn in spawned_table)) + spawned_table[loot_spawn] = 1 + else + spawned_table[loot_spawn] += 1 + stat_table += spawned_table + for(var/item in stat_table) + stat_table[item] /= loot_count + +/obj/item/loot_table_maker/proc/pick_loot(lootpool) //selects path from loot table and returns it + var/lootspawn = pickweight(lootpool) + while(islist(lootspawn)) + lootspawn = pickweight(lootspawn) + return lootspawn diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 3ddcdd9730d..4f672691273 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -1,264 +1,264 @@ -//generic procs copied from obj/effect/alien -/obj/structure/spider - name = "web" - icon = 'icons/effects/effects.dmi' - desc = "It's stringy and sticky." - anchored = TRUE - density = FALSE - max_integrity = 15 - - - -/obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type - playsound(loc, 'sound/items/welder.ogg', 100, TRUE) - - -/obj/structure/spider/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee") - switch(damage_type) - if(BURN) - damage_amount *= 2 - if(BRUTE) - damage_amount *= 0.25 - . = ..() - -/obj/structure/spider/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > 300) - take_damage(5, BURN, 0, 0) - -/obj/structure/spider/stickyweb - var/genetic = FALSE - icon_state = "stickyweb1" - -/obj/structure/spider/stickyweb/Initialize() - if(prob(50)) - icon_state = "stickyweb2" - . = ..() - -/obj/structure/spider/stickyweb/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(genetic) - return - if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider)) - return TRUE - else if(isliving(mover)) - if(istype(mover.pulledby, /mob/living/simple_animal/hostile/poison/giant_spider)) - return TRUE - if(prob(50)) - to_chat(mover, "You get stuck in \the [src] for a moment.") - return FALSE - else if(istype(mover, /obj/projectile)) - return prob(30) - -/obj/structure/spider/stickyweb/genetic //for the spider genes in genetics - genetic = TRUE - var/mob/living/allowed_mob - -/obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob) - allowed_mob = allowedmob - . = ..() - -/obj/structure/spider/stickyweb/genetic/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() //this is the normal spider web return aka a spider would make this TRUE - if(mover == allowed_mob) - return TRUE - else if(isliving(mover)) //we change the spider to not be able to go through here - if(mover.pulledby == allowed_mob) - return TRUE - if(prob(50)) - to_chat(mover, "You get stuck in \the [src] for a moment.") - return FALSE - else if(istype(mover, /obj/projectile)) - return prob(30) - -/obj/structure/spider/eggcluster - name = "egg cluster" - desc = "They seem to pulse slightly with an inner life." - icon_state = "eggs" - var/amount_grown = 0 - var/player_spiders = 0 - var/directive = "" //Message from the mother - var/poison_type = /datum/reagent/toxin - var/poison_per_bite = 5 - var/list/faction = list("spiders") - -/obj/structure/spider/eggcluster/Initialize() - pixel_x = rand(3,-3) - pixel_y = rand(3,-3) - START_PROCESSING(SSobj, src) - . = ..() - -/obj/structure/spider/eggcluster/process() - amount_grown += rand(0,2) - if(amount_grown >= 100) - var/num = rand(3,12) - for(var/i=0, iYou hear something scampering through the ventilation ducts.") - - addtimer(CALLBACK(src, .proc/finish_vent_move, exit_vent), travel_time) - -/obj/structure/spider/spiderling/proc/finish_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent) - if(QDELETED(exit_vent) || exit_vent.welded) - cancel_vent_move() - return - forceMove(exit_vent.loc) - entry_vent = null - -/obj/structure/spider/spiderling/process() - if(travelling_in_vent) - if(isturf(loc)) - travelling_in_vent = 0 - entry_vent = null - else if(entry_vent) - if(get_dist(src, entry_vent) <= 1) - var/list/vents = list() - var/datum/pipeline/entry_vent_parent = entry_vent.parents[1] - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmosmch) - vents.Add(temp_vent) - if(!vents.len) - entry_vent = null - return - var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = pick(vents) - if(prob(50)) - visible_message("[src] scrambles into the ventilation ducts!", \ - "You hear something scampering through the ventilation ducts.") - - addtimer(CALLBACK(src, .proc/vent_move, exit_vent), rand(20,60)) - - //================= - - else if(prob(33)) - var/list/nearby = oview(10, src) - if(nearby.len) - var/target_atom = pick(nearby) - walk_to(src, target_atom) - if(prob(40)) - src.visible_message("\The [src] skitters[pick(" away"," around","")].") - else if(prob(10)) - //ventcrawl! - for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src)) - if(!v.welded) - entry_vent = v - walk_to(src, entry_vent, 1) - break - if(isturf(loc)) - amount_grown += rand(0,2) - if(amount_grown >= 100) - if(!grow_as) - if(prob(3)) - grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider/tarantula, /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife) - else - grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider, /mob/living/simple_animal/hostile/poison/giant_spider/hunter, /mob/living/simple_animal/hostile/poison/giant_spider/nurse) - var/mob/living/simple_animal/hostile/poison/giant_spider/S = new grow_as(src.loc) - S.faction = faction.Copy() - S.directive = directive - if(player_spiders) - S.playable_spider = TRUE - notify_ghosts("Spider [S.name] can be controlled", null, enter_link="(Click to play)", source=S, action=NOTIFY_ATTACK, ignore_key = POLL_IGNORE_SPIDER) - qdel(src) - - - -/obj/structure/spider/cocoon - name = "cocoon" - desc = "Something wrapped in silky spider web." - icon_state = "cocoon1" - max_integrity = 60 - -/obj/structure/spider/cocoon/Initialize() - icon_state = pick("cocoon1","cocoon2","cocoon3") - . = ..() - -/obj/structure/spider/cocoon/container_resist(mob/living/user) - var/breakout_time = 600 - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(user, "You struggle against the tight bonds... (This will take about [DisplayTimeText(breakout_time)].)") - visible_message("You see something struggling and writhing in \the [src]!") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src) - return - qdel(src) - - - -/obj/structure/spider/cocoon/Destroy() - var/turf/T = get_turf(src) - src.visible_message("\The [src] splits open.") - for(var/atom/movable/A in contents) - A.forceMove(T) - return ..() +//generic procs copied from obj/effect/alien +/obj/structure/spider + name = "web" + icon = 'icons/effects/effects.dmi' + desc = "It's stringy and sticky." + anchored = TRUE + density = FALSE + max_integrity = 15 + + + +/obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + + +/obj/structure/spider/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee") + switch(damage_type) + if(BURN) + damage_amount *= 2 + if(BRUTE) + damage_amount *= 0.25 + . = ..() + +/obj/structure/spider/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > 300) + take_damage(5, BURN, 0, 0) + +/obj/structure/spider/stickyweb + var/genetic = FALSE + icon_state = "stickyweb1" + +/obj/structure/spider/stickyweb/Initialize() + if(prob(50)) + icon_state = "stickyweb2" + . = ..() + +/obj/structure/spider/stickyweb/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(genetic) + return + if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider)) + return TRUE + else if(isliving(mover)) + if(istype(mover.pulledby, /mob/living/simple_animal/hostile/poison/giant_spider)) + return TRUE + if(prob(50)) + to_chat(mover, "You get stuck in \the [src] for a moment.") + return FALSE + else if(istype(mover, /obj/projectile)) + return prob(30) + +/obj/structure/spider/stickyweb/genetic //for the spider genes in genetics + genetic = TRUE + var/mob/living/allowed_mob + +/obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob) + allowed_mob = allowedmob + . = ..() + +/obj/structure/spider/stickyweb/genetic/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() //this is the normal spider web return aka a spider would make this TRUE + if(mover == allowed_mob) + return TRUE + else if(isliving(mover)) //we change the spider to not be able to go through here + if(mover.pulledby == allowed_mob) + return TRUE + if(prob(50)) + to_chat(mover, "You get stuck in \the [src] for a moment.") + return FALSE + else if(istype(mover, /obj/projectile)) + return prob(30) + +/obj/structure/spider/eggcluster + name = "egg cluster" + desc = "They seem to pulse slightly with an inner life." + icon_state = "eggs" + var/amount_grown = 0 + var/player_spiders = 0 + var/directive = "" //Message from the mother + var/poison_type = /datum/reagent/toxin + var/poison_per_bite = 5 + var/list/faction = list("spiders") + +/obj/structure/spider/eggcluster/Initialize() + pixel_x = rand(3,-3) + pixel_y = rand(3,-3) + START_PROCESSING(SSobj, src) + . = ..() + +/obj/structure/spider/eggcluster/process() + amount_grown += rand(0,2) + if(amount_grown >= 100) + var/num = rand(3,12) + for(var/i=0, iYou hear something scampering through the ventilation ducts.") + + addtimer(CALLBACK(src, .proc/finish_vent_move, exit_vent), travel_time) + +/obj/structure/spider/spiderling/proc/finish_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent) + if(QDELETED(exit_vent) || exit_vent.welded) + cancel_vent_move() + return + forceMove(exit_vent.loc) + entry_vent = null + +/obj/structure/spider/spiderling/process() + if(travelling_in_vent) + if(isturf(loc)) + travelling_in_vent = 0 + entry_vent = null + else if(entry_vent) + if(get_dist(src, entry_vent) <= 1) + var/list/vents = list() + var/datum/pipeline/entry_vent_parent = entry_vent.parents[1] + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmosmch) + vents.Add(temp_vent) + if(!vents.len) + entry_vent = null + return + var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = pick(vents) + if(prob(50)) + visible_message("[src] scrambles into the ventilation ducts!", \ + "You hear something scampering through the ventilation ducts.") + + addtimer(CALLBACK(src, .proc/vent_move, exit_vent), rand(20,60)) + + //================= + + else if(prob(33)) + var/list/nearby = oview(10, src) + if(nearby.len) + var/target_atom = pick(nearby) + walk_to(src, target_atom) + if(prob(40)) + src.visible_message("\The [src] skitters[pick(" away"," around","")].") + else if(prob(10)) + //ventcrawl! + for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src)) + if(!v.welded) + entry_vent = v + walk_to(src, entry_vent, 1) + break + if(isturf(loc)) + amount_grown += rand(0,2) + if(amount_grown >= 100) + if(!grow_as) + if(prob(3)) + grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider/tarantula, /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife) + else + grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider, /mob/living/simple_animal/hostile/poison/giant_spider/hunter, /mob/living/simple_animal/hostile/poison/giant_spider/nurse) + var/mob/living/simple_animal/hostile/poison/giant_spider/S = new grow_as(src.loc) + S.faction = faction.Copy() + S.directive = directive + if(player_spiders) + S.playable_spider = TRUE + notify_ghosts("Spider [S.name] can be controlled", null, enter_link="(Click to play)", source=S, action=NOTIFY_ATTACK, ignore_key = POLL_IGNORE_SPIDER) + qdel(src) + + + +/obj/structure/spider/cocoon + name = "cocoon" + desc = "Something wrapped in silky spider web." + icon_state = "cocoon1" + max_integrity = 60 + +/obj/structure/spider/cocoon/Initialize() + icon_state = pick("cocoon1","cocoon2","cocoon3") + . = ..() + +/obj/structure/spider/cocoon/container_resist(mob/living/user) + var/breakout_time = 600 + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(user, "You struggle against the tight bonds... (This will take about [DisplayTimeText(breakout_time)].)") + visible_message("You see something struggling and writhing in \the [src]!") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src) + return + qdel(src) + + + +/obj/structure/spider/cocoon/Destroy() + var/turf/T = get_turf(src) + src.visible_message("\The [src] splits open.") + for(var/atom/movable/A in contents) + A.forceMove(T) + return ..() diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm index a91b66a7c52..f8da95fee32 100644 --- a/code/game/objects/effects/step_triggers.dm +++ b/code/game/objects/effects/step_triggers.dm @@ -1,200 +1,200 @@ -/* Simple object type, calls a proc when "stepped" on by something */ - -/obj/effect/step_trigger - var/affect_ghosts = 0 - var/stopper = 1 // stops throwers - var/mobs_only = FALSE - invisibility = INVISIBILITY_ABSTRACT // nope cant see this shit - anchored = TRUE - -/obj/effect/step_trigger/proc/Trigger(atom/movable/A) - return 0 - -/obj/effect/step_trigger/Crossed(H as mob|obj) - ..() - if(!H) - return - if(isobserver(H) && !affect_ghosts) - return - if(!ismob(H) && mobs_only) - return - Trigger(H) - - -/obj/effect/step_trigger/singularity_act() - return - -/obj/effect/step_trigger/singularity_pull() - return - -/* Sends a message to mob when triggered*/ - -/obj/effect/step_trigger/message - var/message //the message to give to the mob - var/once = 1 - mobs_only = TRUE - -/obj/effect/step_trigger/message/Trigger(mob/M) - if(M.client) - to_chat(M, "[message]") - if(once) - qdel(src) - -/* Tosses things in a certain direction */ - -/obj/effect/step_trigger/thrower - var/direction = SOUTH // the direction of throw - var/tiles = 3 // if 0: forever until atom hits a stopper - var/immobilize = 1 // if nonzero: prevents mobs from moving while they're being flung - var/speed = 1 // delay of movement - var/facedir = 0 // if 1: atom faces the direction of movement - var/nostop = 0 // if 1: will only be stopped by teleporters - var/list/affecting = list() - -/obj/effect/step_trigger/thrower/Trigger(atom/A) - if(!A || !ismovable(A)) - return - var/atom/movable/AM = A - var/curtiles = 0 - var/stopthrow = 0 - for(var/obj/effect/step_trigger/thrower/T in orange(2, src)) - if(AM in T.affecting) - return - - if(isliving(AM)) - var/mob/living/M = AM - if(immobilize) - M.mobility_flags &= ~MOBILITY_MOVE - - affecting.Add(AM) - while(AM && !stopthrow) - if(tiles) - if(curtiles >= tiles) - break - if(AM.z != src.z) - break - - curtiles++ - - sleep(speed) - - // Calculate if we should stop the process - if(!nostop) - for(var/obj/effect/step_trigger/T in get_step(AM, direction)) - if(T.stopper && T != src) - stopthrow = 1 - else - for(var/obj/effect/step_trigger/teleporter/T in get_step(AM, direction)) - if(T.stopper) - stopthrow = 1 - - if(AM) - var/predir = AM.dir - step(AM, direction) - if(!facedir) - AM.setDir(predir) - - - - affecting.Remove(AM) - - if(isliving(AM)) - var/mob/living/M = AM - if(immobilize) - M.mobility_flags |= MOBILITY_MOVE - M.update_mobility() - -/* Stops things thrown by a thrower, doesn't do anything */ - -/obj/effect/step_trigger/stopper - -/* Instant teleporter */ - -/obj/effect/step_trigger/teleporter - var/teleport_x = 0 // teleportation coordinates (if one is null, then no teleport!) - var/teleport_y = 0 - var/teleport_z = 0 - -/obj/effect/step_trigger/teleporter/Trigger(atom/movable/A) - if(teleport_x && teleport_y && teleport_z) - - var/turf/T = locate(teleport_x, teleport_y, teleport_z) - A.forceMove(T) - -/* Random teleporter, teleports atoms to locations ranging from teleport_x - teleport_x_offset, etc */ - -/obj/effect/step_trigger/teleporter/random - var/teleport_x_offset = 0 - var/teleport_y_offset = 0 - var/teleport_z_offset = 0 - -/obj/effect/step_trigger/teleporter/random/Trigger(atom/movable/A) - if(teleport_x && teleport_y && teleport_z) - if(teleport_x_offset && teleport_y_offset && teleport_z_offset) - - var/turf/T = locate(rand(teleport_x, teleport_x_offset), rand(teleport_y, teleport_y_offset), rand(teleport_z, teleport_z_offset)) - if (T) - A.forceMove(T) - -/* Fancy teleporter, creates sparks and smokes when used */ - -/obj/effect/step_trigger/teleport_fancy - var/locationx - var/locationy - var/uses = 1 //0 for infinite uses - var/entersparks = 0 - var/exitsparks = 0 - var/entersmoke = 0 - var/exitsmoke = 0 - -/obj/effect/step_trigger/teleport_fancy/Trigger(mob/M) - var/dest = locate(locationx, locationy, z) - M.Move(dest) - - if(entersparks) - var/datum/effect_system/spark_spread/s = new - s.set_up(4, 1, src) - s.start() - if(exitsparks) - var/datum/effect_system/spark_spread/s = new - s.set_up(4, 1, dest) - s.start() - - if(entersmoke) - var/datum/effect_system/smoke_spread/s = new - s.set_up(4, 1, src, 0) - s.start() - if(exitsmoke) - var/datum/effect_system/smoke_spread/s = new - s.set_up(4, 1, dest, 0) - s.start() - - uses-- - if(uses == 0) - qdel(src) - -/* Simple sound player, Mapper friendly! */ - -/obj/effect/step_trigger/sound_effect - var/sound //eg. path to the sound, inside '' eg: 'growl.ogg' - var/volume = 100 - var/freq_vary = 1 //Should the frequency of the sound vary? - var/extra_range = 0 // eg World.view = 7, extra_range = 1, 7+1 = 8, 8 turfs radius - var/happens_once = 0 - var/triggerer_only = 0 //Whether the triggerer is the only person who hears this - - -/obj/effect/step_trigger/sound_effect/Trigger(atom/movable/A) - var/turf/T = get_turf(A) - - if(!T) - return - - if(triggerer_only && ismob(A)) - var/mob/B = A - B.playsound_local(T, sound, volume, freq_vary) - else - playsound(T, sound, volume, freq_vary, extra_range) - - if(happens_once) - qdel(src) +/* Simple object type, calls a proc when "stepped" on by something */ + +/obj/effect/step_trigger + var/affect_ghosts = 0 + var/stopper = 1 // stops throwers + var/mobs_only = FALSE + invisibility = INVISIBILITY_ABSTRACT // nope cant see this shit + anchored = TRUE + +/obj/effect/step_trigger/proc/Trigger(atom/movable/A) + return 0 + +/obj/effect/step_trigger/Crossed(H as mob|obj) + ..() + if(!H) + return + if(isobserver(H) && !affect_ghosts) + return + if(!ismob(H) && mobs_only) + return + Trigger(H) + + +/obj/effect/step_trigger/singularity_act() + return + +/obj/effect/step_trigger/singularity_pull() + return + +/* Sends a message to mob when triggered*/ + +/obj/effect/step_trigger/message + var/message //the message to give to the mob + var/once = 1 + mobs_only = TRUE + +/obj/effect/step_trigger/message/Trigger(mob/M) + if(M.client) + to_chat(M, "[message]") + if(once) + qdel(src) + +/* Tosses things in a certain direction */ + +/obj/effect/step_trigger/thrower + var/direction = SOUTH // the direction of throw + var/tiles = 3 // if 0: forever until atom hits a stopper + var/immobilize = 1 // if nonzero: prevents mobs from moving while they're being flung + var/speed = 1 // delay of movement + var/facedir = 0 // if 1: atom faces the direction of movement + var/nostop = 0 // if 1: will only be stopped by teleporters + var/list/affecting = list() + +/obj/effect/step_trigger/thrower/Trigger(atom/A) + if(!A || !ismovable(A)) + return + var/atom/movable/AM = A + var/curtiles = 0 + var/stopthrow = 0 + for(var/obj/effect/step_trigger/thrower/T in orange(2, src)) + if(AM in T.affecting) + return + + if(isliving(AM)) + var/mob/living/M = AM + if(immobilize) + M.mobility_flags &= ~MOBILITY_MOVE + + affecting.Add(AM) + while(AM && !stopthrow) + if(tiles) + if(curtiles >= tiles) + break + if(AM.z != src.z) + break + + curtiles++ + + sleep(speed) + + // Calculate if we should stop the process + if(!nostop) + for(var/obj/effect/step_trigger/T in get_step(AM, direction)) + if(T.stopper && T != src) + stopthrow = 1 + else + for(var/obj/effect/step_trigger/teleporter/T in get_step(AM, direction)) + if(T.stopper) + stopthrow = 1 + + if(AM) + var/predir = AM.dir + step(AM, direction) + if(!facedir) + AM.setDir(predir) + + + + affecting.Remove(AM) + + if(isliving(AM)) + var/mob/living/M = AM + if(immobilize) + M.mobility_flags |= MOBILITY_MOVE + M.update_mobility() + +/* Stops things thrown by a thrower, doesn't do anything */ + +/obj/effect/step_trigger/stopper + +/* Instant teleporter */ + +/obj/effect/step_trigger/teleporter + var/teleport_x = 0 // teleportation coordinates (if one is null, then no teleport!) + var/teleport_y = 0 + var/teleport_z = 0 + +/obj/effect/step_trigger/teleporter/Trigger(atom/movable/A) + if(teleport_x && teleport_y && teleport_z) + + var/turf/T = locate(teleport_x, teleport_y, teleport_z) + A.forceMove(T) + +/* Random teleporter, teleports atoms to locations ranging from teleport_x - teleport_x_offset, etc */ + +/obj/effect/step_trigger/teleporter/random + var/teleport_x_offset = 0 + var/teleport_y_offset = 0 + var/teleport_z_offset = 0 + +/obj/effect/step_trigger/teleporter/random/Trigger(atom/movable/A) + if(teleport_x && teleport_y && teleport_z) + if(teleport_x_offset && teleport_y_offset && teleport_z_offset) + + var/turf/T = locate(rand(teleport_x, teleport_x_offset), rand(teleport_y, teleport_y_offset), rand(teleport_z, teleport_z_offset)) + if (T) + A.forceMove(T) + +/* Fancy teleporter, creates sparks and smokes when used */ + +/obj/effect/step_trigger/teleport_fancy + var/locationx + var/locationy + var/uses = 1 //0 for infinite uses + var/entersparks = 0 + var/exitsparks = 0 + var/entersmoke = 0 + var/exitsmoke = 0 + +/obj/effect/step_trigger/teleport_fancy/Trigger(mob/M) + var/dest = locate(locationx, locationy, z) + M.Move(dest) + + if(entersparks) + var/datum/effect_system/spark_spread/s = new + s.set_up(4, 1, src) + s.start() + if(exitsparks) + var/datum/effect_system/spark_spread/s = new + s.set_up(4, 1, dest) + s.start() + + if(entersmoke) + var/datum/effect_system/smoke_spread/s = new + s.set_up(4, 1, src, 0) + s.start() + if(exitsmoke) + var/datum/effect_system/smoke_spread/s = new + s.set_up(4, 1, dest, 0) + s.start() + + uses-- + if(uses == 0) + qdel(src) + +/* Simple sound player, Mapper friendly! */ + +/obj/effect/step_trigger/sound_effect + var/sound //eg. path to the sound, inside '' eg: 'growl.ogg' + var/volume = 100 + var/freq_vary = 1 //Should the frequency of the sound vary? + var/extra_range = 0 // eg World.view = 7, extra_range = 1, 7+1 = 8, 8 turfs radius + var/happens_once = 0 + var/triggerer_only = 0 //Whether the triggerer is the only person who hears this + + +/obj/effect/step_trigger/sound_effect/Trigger(atom/movable/A) + var/turf/T = get_turf(A) + + if(!T) + return + + if(triggerer_only && ismob(A)) + var/mob/B = A + B.playsound_local(T, sound, volume, freq_vary) + else + playsound(T, sound, volume, freq_vary, extra_range) + + if(happens_once) + qdel(src) diff --git a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm index a7c3d270e78..875eaf5e60a 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm @@ -1,38 +1,38 @@ -/obj/effect/projectile/impact - name = "beam impact" - icon = 'icons/obj/projectiles_impact.dmi' - -/obj/effect/projectile/impact/laser - name = "laser impact" - icon_state = "impact_laser" - -/obj/effect/projectile/impact/laser/blue - name = "laser impact" - icon_state = "impact_blue" - -/obj/effect/projectile/impact/disabler - name = "disabler impact" - icon_state = "impact_omni" - -/obj/effect/projectile/impact/xray - name = "\improper X-ray impact" - icon_state = "impact_xray" - -/obj/effect/projectile/impact/pulse - name = "pulse impact" - icon_state = "impact_u_laser" - -/obj/effect/projectile/impact/plasma_cutter - name = "plasma impact" - icon_state = "impact_plasmacutter" - -/obj/effect/projectile/impact/stun - name = "stun impact" - icon_state = "impact_stun" - -/obj/effect/projectile/impact/heavy_laser - name = "heavy laser impact" - icon_state = "impact_beam_heavy" - -/obj/effect/projectile/impact/wormhole - icon_state = "wormhole_g" +/obj/effect/projectile/impact + name = "beam impact" + icon = 'icons/obj/projectiles_impact.dmi' + +/obj/effect/projectile/impact/laser + name = "laser impact" + icon_state = "impact_laser" + +/obj/effect/projectile/impact/laser/blue + name = "laser impact" + icon_state = "impact_blue" + +/obj/effect/projectile/impact/disabler + name = "disabler impact" + icon_state = "impact_omni" + +/obj/effect/projectile/impact/xray + name = "\improper X-ray impact" + icon_state = "impact_xray" + +/obj/effect/projectile/impact/pulse + name = "pulse impact" + icon_state = "impact_u_laser" + +/obj/effect/projectile/impact/plasma_cutter + name = "plasma impact" + icon_state = "impact_plasmacutter" + +/obj/effect/projectile/impact/stun + name = "stun impact" + icon_state = "impact_stun" + +/obj/effect/projectile/impact/heavy_laser + name = "heavy laser impact" + icon_state = "impact_beam_heavy" + +/obj/effect/projectile/impact/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm index 133d06b4b68..ad6b23f5041 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm @@ -1,30 +1,30 @@ -/obj/effect/projectile/muzzle - name = "muzzle flash" - icon = 'icons/obj/projectiles_muzzle.dmi' - -/obj/effect/projectile/muzzle/laser - icon_state = "muzzle_laser" - -/obj/effect/projectile/muzzle/laser/blue - icon_state = "muzzle_laser_blue" - -/obj/effect/projectile/muzzle/disabler - icon_state = "muzzle_omni" - -/obj/effect/projectile/muzzle/xray - icon_state = "muzzle_xray" - -/obj/effect/projectile/muzzle/pulse - icon_state = "muzzle_u_laser" - -/obj/effect/projectile/muzzle/plasma_cutter - icon_state = "muzzle_plasmacutter" - -/obj/effect/projectile/muzzle/stun - icon_state = "muzzle_stun" - -/obj/effect/projectile/muzzle/heavy_laser - icon_state = "muzzle_beam_heavy" - -/obj/effect/projectile/muzzle/wormhole - icon_state = "wormhole_g" +/obj/effect/projectile/muzzle + name = "muzzle flash" + icon = 'icons/obj/projectiles_muzzle.dmi' + +/obj/effect/projectile/muzzle/laser + icon_state = "muzzle_laser" + +/obj/effect/projectile/muzzle/laser/blue + icon_state = "muzzle_laser_blue" + +/obj/effect/projectile/muzzle/disabler + icon_state = "muzzle_omni" + +/obj/effect/projectile/muzzle/xray + icon_state = "muzzle_xray" + +/obj/effect/projectile/muzzle/pulse + icon_state = "muzzle_u_laser" + +/obj/effect/projectile/muzzle/plasma_cutter + icon_state = "muzzle_plasmacutter" + +/obj/effect/projectile/muzzle/stun + icon_state = "muzzle_stun" + +/obj/effect/projectile/muzzle/heavy_laser + icon_state = "muzzle_beam_heavy" + +/obj/effect/projectile/muzzle/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm index 103cee137d3..49cdc3667cf 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -1,60 +1,60 @@ -/obj/effect/projectile - name = "pew" - icon = 'icons/obj/projectiles.dmi' - icon_state = "nothing" - layer = ABOVE_MOB_LAYER - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - appearance_flags = 0 - -/obj/effect/projectile/singularity_pull() - return - -/obj/effect/projectile/singularity_act() - return - -/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE) - var/matrix/M - if(!override) - M = transform - else - M = new - M.Scale(nx,ny) - transform = M - -/obj/effect/projectile/proc/turn_to(angle,override=TRUE) - var/matrix/M - if(!override) - M = transform - else - M = new - M.Turn(angle) - transform = M - -/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1) - if(angle_override && p_x && p_y && color_override && scaling) - apply_vars(angle_override, p_x, p_y, color_override, scaling) - return ..() - -/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0) - var/mutable_appearance/look = new(src) - look.pixel_x = p_x - look.pixel_y = p_y - if(color_override) - look.color = color_override - appearance = look - scale_to(1,scaling, FALSE) - turn_to(angle_override, FALSE) - if(!isnull(new_loc)) //If you want to null it just delete it... - forceMove(new_loc) - for(var/i in 1 to increment) - pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) - pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) - -/obj/effect/projectile_lighting - var/owner - -/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) - . = ..() - set_light(range, intensity, color) - owner = owner_key +/obj/effect/projectile + name = "pew" + icon = 'icons/obj/projectiles.dmi' + icon_state = "nothing" + layer = ABOVE_MOB_LAYER + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + appearance_flags = 0 + +/obj/effect/projectile/singularity_pull() + return + +/obj/effect/projectile/singularity_act() + return + +/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Scale(nx,ny) + transform = M + +/obj/effect/projectile/proc/turn_to(angle,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Turn(angle) + transform = M + +/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1) + if(angle_override && p_x && p_y && color_override && scaling) + apply_vars(angle_override, p_x, p_y, color_override, scaling) + return ..() + +/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0) + var/mutable_appearance/look = new(src) + look.pixel_x = p_x + look.pixel_y = p_y + if(color_override) + look.color = color_override + appearance = look + scale_to(1,scaling, FALSE) + turn_to(angle_override, FALSE) + if(!isnull(new_loc)) //If you want to null it just delete it... + forceMove(new_loc) + for(var/i in 1 to increment) + pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) + pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) + +/obj/effect/projectile_lighting + var/owner + +/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) + . = ..() + set_light(range, intensity, color) + owner = owner_key diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm index 4111b2ec6f5..23ecf438c4f 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -1,68 +1,68 @@ -/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! - if(!istype(starting) || !istype(ending) || !ispath(beam_type)) - return - var/datum/point/midpoint = point_midpoint_points(starting, ending) - var/obj/effect/projectile/tracer/PB = new beam_type - if(isnull(light_color_override)) - light_color_override = color - PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0) - . = PB - if(light_range > 0 && light_intensity > 0) - var/list/turf/line = getline(starting.return_turf(), ending.return_turf()) - tracing_line: - for(var/i in line) - var/turf/T = i - for(var/obj/effect/projectile_lighting/PL in T) - if(PL.owner == instance_key) - continue tracing_line - QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) - line = null - if(qdel_in) - QDEL_IN(PB, qdel_in) - -/obj/effect/projectile/tracer - name = "beam" - icon = 'icons/obj/projectiles_tracer.dmi' - -/obj/effect/projectile/tracer/laser - name = "laser" - icon_state = "beam" - -/obj/effect/projectile/tracer/laser/blue - icon_state = "beam_blue" - -/obj/effect/projectile/tracer/disabler - name = "disabler" - icon_state = "beam_omni" - -/obj/effect/projectile/tracer/xray - name = "\improper X-ray laser" - icon_state = "xray" - -/obj/effect/projectile/tracer/pulse - name = "pulse laser" - icon_state = "u_laser" - -/obj/effect/projectile/tracer/plasma_cutter - name = "plasma blast" - icon_state = "plasmacutter" - -/obj/effect/projectile/tracer/stun - name = "stun beam" - icon_state = "stun" - -/obj/effect/projectile/tracer/heavy_laser - name = "heavy laser" - icon_state = "beam_heavy" - -//BEAM RIFLE -/obj/effect/projectile/tracer/tracer/beam_rifle - icon_state = "tracer_beam" - -/obj/effect/projectile/tracer/tracer/aiming - icon_state = "pixelbeam_greyscale" - layer = ABOVE_LIGHTING_LAYER - plane = ABOVE_LIGHTING_PLANE - -/obj/effect/projectile/tracer/wormhole - icon_state = "wormhole_g" +/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! + if(!istype(starting) || !istype(ending) || !ispath(beam_type)) + return + var/datum/point/midpoint = point_midpoint_points(starting, ending) + var/obj/effect/projectile/tracer/PB = new beam_type + if(isnull(light_color_override)) + light_color_override = color + PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0) + . = PB + if(light_range > 0 && light_intensity > 0) + var/list/turf/line = getline(starting.return_turf(), ending.return_turf()) + tracing_line: + for(var/i in line) + var/turf/T = i + for(var/obj/effect/projectile_lighting/PL in T) + if(PL.owner == instance_key) + continue tracing_line + QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) + line = null + if(qdel_in) + QDEL_IN(PB, qdel_in) + +/obj/effect/projectile/tracer + name = "beam" + icon = 'icons/obj/projectiles_tracer.dmi' + +/obj/effect/projectile/tracer/laser + name = "laser" + icon_state = "beam" + +/obj/effect/projectile/tracer/laser/blue + icon_state = "beam_blue" + +/obj/effect/projectile/tracer/disabler + name = "disabler" + icon_state = "beam_omni" + +/obj/effect/projectile/tracer/xray + name = "\improper X-ray laser" + icon_state = "xray" + +/obj/effect/projectile/tracer/pulse + name = "pulse laser" + icon_state = "u_laser" + +/obj/effect/projectile/tracer/plasma_cutter + name = "plasma blast" + icon_state = "plasmacutter" + +/obj/effect/projectile/tracer/stun + name = "stun beam" + icon_state = "stun" + +/obj/effect/projectile/tracer/heavy_laser + name = "heavy laser" + icon_state = "beam_heavy" + +//BEAM RIFLE +/obj/effect/projectile/tracer/tracer/beam_rifle + icon_state = "tracer_beam" + +/obj/effect/projectile/tracer/tracer/aiming + icon_state = "pixelbeam_greyscale" + layer = ABOVE_LIGHTING_LAYER + plane = ABOVE_LIGHTING_PLANE + +/obj/effect/projectile/tracer/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm index f81ba58f435..e7246fd04ef 100644 --- a/code/game/objects/empulse.dm +++ b/code/game/objects/empulse.dm @@ -1,32 +1,32 @@ -/proc/empulse(turf/epicenter, heavy_range, light_range, log=0) - if(!epicenter) - return - - if(!isturf(epicenter)) - epicenter = get_turf(epicenter.loc) - - if(log) - message_admins("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") - log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") - - if(heavy_range > 1) - new /obj/effect/temp_visual/emp/pulse(epicenter) - - if(heavy_range > light_range) - light_range = heavy_range - - for(var/A in spiral_range(light_range, epicenter)) - var/atom/T = A - var/distance = get_dist(epicenter, T) - if(distance < 0) - distance = 0 - if(distance < heavy_range) - T.emp_act(EMP_HEAVY) - else if(distance == heavy_range) - if(prob(50)) - T.emp_act(EMP_HEAVY) - else - T.emp_act(EMP_LIGHT) - else if(distance <= light_range) - T.emp_act(EMP_LIGHT) - return 1 +/proc/empulse(turf/epicenter, heavy_range, light_range, log=0) + if(!epicenter) + return + + if(!isturf(epicenter)) + epicenter = get_turf(epicenter.loc) + + if(log) + message_admins("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") + log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") + + if(heavy_range > 1) + new /obj/effect/temp_visual/emp/pulse(epicenter) + + if(heavy_range > light_range) + light_range = heavy_range + + for(var/A in spiral_range(light_range, epicenter)) + var/atom/T = A + var/distance = get_dist(epicenter, T) + if(distance < 0) + distance = 0 + if(distance < heavy_range) + T.emp_act(EMP_HEAVY) + else if(distance == heavy_range) + if(prob(50)) + T.emp_act(EMP_HEAVY) + else + T.emp_act(EMP_LIGHT) + else if(distance <= light_range) + T.emp_act(EMP_LIGHT) + return 1 diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 26058ee6274..c1cdb8a06f7 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1,1013 +1,1013 @@ -GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/effects/fire.dmi', "fire")) - -GLOBAL_VAR_INIT(rpg_loot_items, FALSE) -// if true, everyone item when created will have its name changed to be -// more... RPG-like. - -GLOBAL_VAR_INIT(stickpocalypse, FALSE) // if true, all non-embeddable items will be able to harmlessly stick to people when thrown -GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to embed in people, takes precedence over stickpocalypse - -/obj/item - name = "item" - icon = 'icons/obj/items_and_weapons.dmi' - blocks_emissive = EMISSIVE_BLOCK_GENERIC - - /* !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! - - IF YOU ADD MORE ICON CRAP TO THIS - ENSURE YOU ALSO ADD THE NEW VARS TO CHAMELEON ITEM_ACTION'S update_item() PROC (/datum/action/item_action/chameleon/change/proc/update_item()) - WASHING MASHINE'S dye_item() PROC (/obj/item/proc/dye_item()) - AND ALSO TO THE CHANGELING PROFILE DISGUISE SYSTEMS (/datum/changelingprofile / /datum/antagonist/changeling/proc/create_profile() / /proc/changeling_transform()) - - !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! */ - - ///icon state for inhand overlays, if null the normal icon_state will be used. - var/inhand_icon_state = null - ///Icon file for left hand inhand overlays - var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - ///Icon file for right inhand overlays - var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' - - ///Icon file for mob worn overlays. - var/icon/worn_icon - ///icon state for mob worn overlays, if null the normal icon_state will be used. - var/worn_icon_state - ///Forced mob worn layer instead of the standard preferred ssize. - var/alternate_worn_layer - - /* !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! - - IF YOU ADD MORE ICON CRAP TO THIS - ENSURE YOU ALSO ADD THE NEW VARS TO CHAMELEON ITEM_ACTION'S update_item() PROC (/datum/action/item_action/chameleon/change/proc/update_item()) - WASHING MASHINE'S dye_item() PROC (/obj/item/proc/dye_item()) - AND ALSO TO THE CHANGELING PROFILE DISGUISE SYSTEMS (/datum/changelingprofile / /datum/antagonist/changeling/proc/create_profile() / /proc/changeling_transform()) - - !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! */ - - ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly - var/worn_x_dimension = 32 - ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly - var/worn_y_dimension = 32 - ///Same as for [worn_x_dimension][/obj/item/var/worn_x_dimension] but for inhands, uses the lefthand_ and righthand_ file vars - var/inhand_x_dimension = 32 - ///Same as for [worn_y_dimension][/obj/item/var/worn_y_dimension] but for inhands, uses the lefthand_ and righthand_ file vars - var/inhand_y_dimension = 32 - - - max_integrity = 200 - - obj_flags = NONE - ///Item flags for the item - var/item_flags = NONE - - ///Sound played when you hit something with the item - var/hitsound - ///Played when the item is used, for example tools - var/usesound - ///Used when yate into a mob - var/mob_throw_hit_sound - ///Sound used when equipping the item into a valid slot - var/equip_sound - ///Sound uses when picking the item up (into your hands) - var/pickup_sound - ///Sound uses when dropping the item, or when its thrown. - var/drop_sound - - ///How large is the object, used for stuff like whether it can fit in backpacks or not - var/w_class = WEIGHT_CLASS_NORMAL - ///This is used to determine on which slots an item can fit. - var/slot_flags = 0 - pass_flags = PASSTABLE - pressure_resistance = 4 - var/obj/item/master = null - - ///flags which determine which body parts are protected from heat. [See here][HEAD] - var/heat_protection = 0 - ///flags which determine which body parts are protected from cold. [See here][HEAD] - var/cold_protection = 0 - ///Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags - var/max_heat_protection_temperature - ///Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags - var/min_cold_protection_temperature - - ///list of /datum/action's that this item has. - var/list/actions - ///list of paths of action datums to give to the item on New(). - var/list/actions_types - - //Since any item can now be a piece of clothing, this has to be put here so all items share it. - ///This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc. - var/flags_inv - ///you can see someone's mask through their transparent visor, but you can't reach it - var/transparent_protection = NONE - - ///flags for what should be done when you click on the item, default is picking it up - var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP - - ///What body parts are covered by the clothing when you wear it - var/body_parts_covered = 0 - ///Literally does nothing right now - var/gas_transfer_coefficient = 1 - /// How likely a disease or chemical is to get through a piece of clothing - var/permeability_coefficient = 1 - /// for electrical admittance/conductance (electrocution checks and shit) - var/siemens_coefficient = 1 - /// How much clothing is slowing you down. Negative values speeds you up - var/slowdown = 0 - ///percentage of armour effectiveness to remove - var/armour_penetration = 0 - ///What objects the suit storage can store - var/list/allowed = null - ///In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc. - var/equip_delay_self = 0 - ///In deciseconds, how long an item takes to put on another person - var/equip_delay_other = 20 - ///In deciseconds, how long an item takes to remove from another person - var/strip_delay = 40 - ///How long it takes to resist out of the item (cuffs and such) - var/breakouttime = 0 - - ///Used in [atom/proc/attackby] to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" - var/list/attack_verb - ///list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item - var/list/species_exception = null - - ///Who threw the item - var/mob/thrownby = null - - ///the icon to indicate this object is being dragged - mouse_drag_pointer = MOUSE_ACTIVE_POINTER - - ///Does it embed and if yes, what kind of embed - var/list/embedding = NONE - - ///for flags such as [GLASSESCOVERSEYES] - var/flags_cover = 0 - var/heat = 0 - ///All items with sharpness of IS_SHARP or higher will automatically get the butchering component. - var/sharpness = IS_BLUNT - - ///How a tool acts when you use it on something, such as wirecutters cutting wires while multitools measure power - var/tool_behaviour = NONE - ///How fast does the tool work - var/toolspeed = 1 - - var/block_chance = 0 - var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom - ///In tiles, how far this weapon can reach; 1 for adjacent, which is default - var/reach = 1 - - ///The list of slots by priority. equip_to_appropriate_slot() uses this list. Doesn't matter if a mob type doesn't have a slot. For default list, see [/mob/proc/equip_to_appropriate_slot] - var/list/slot_equipment_priority = null - - ///Reference to the datum that determines whether dogs can wear the item: Needs to be in /obj/item because corgis can wear a lot of non-clothing items - var/datum/dog_fashion/dog_fashion = null - - //Tooltip vars - ///string form of an item's force. Edit this var only to set a custom force string - var/force_string - var/last_force_string_check = 0 - var/tip_timer - - ///Determines who can shoot this - var/trigger_guard = TRIGGER_GUARD_NONE - - ///Used as the dye color source in the washing machine only (at the moment). Can be a hex color or a key corresponding to a registry entry, see washing_machine.dm - var/dye_color - ///Whether the item is unaffected by standard dying. - var/undyeable = FALSE - ///What dye registry should be looked at when dying this item; see washing_machine.dm - var/dying_key - - ///Grinder var:A reagent list containing the reagents this item produces when ground up in a grinder - this can be an empty list to allow for reagent transferring only - var/list/grind_results - //Grinder var:A reagent list containing blah blah... but when JUICED in a grinder! - var/list/juice_results - - var/canMouseDown = FALSE - - -/obj/item/Initialize() - - if(attack_verb) - attack_verb = typelist("attack_verb", attack_verb) - - . = ..() - for(var/path in actions_types) - new path(src) - actions_types = null - - if(force_string) - item_flags |= FORCE_STRING_OVERRIDE - - if(!hitsound) - if(damtype == "fire") - hitsound = 'sound/items/welder.ogg' - if(damtype == "brute") - hitsound = "swing_hit" - -/obj/item/Destroy() - item_flags &= ~DROPDEL //prevent reqdels - if(ismob(loc)) - var/mob/m = loc - m.temporarilyRemoveItemFromInventory(src, TRUE) - for(var/X in actions) - qdel(X) - return ..() - -/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) - if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside)) - return 0 - else - return 1 - -/obj/item/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - qdel(src) - -/obj/item/ComponentInitialize() - . = ..() - // this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:) - if(!LAZYLEN(embedding)) - if(GLOB.embedpocalypse) - embedding = EMBED_POINTY - name = "pointy [name]" - else if(GLOB.stickpocalypse) - embedding = EMBED_HARMLESS - name = "sticky [name]" - - updateEmbedding() - - if(GLOB.rpg_loot_items) - AddComponent(/datum/component/fantasy) - - if(sharpness) //give sharp objects butchering functionality, for consistency - AddComponent(/datum/component/butchering, 80 * toolspeed) - -/**Makes cool stuff happen when you suicide with an item - * - *Outputs a creative message and then return the damagetype done - * Arguments: - * * user: The mob that is suiciding - */ -/obj/item/proc/suicide_act(mob/user) - return - -/obj/item/verb/move_to_top() - set name = "Move To Top" - set category = "Object" - set src in oview(1) - - if(!isturf(loc) || usr.stat || usr.restrained()) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - - var/turf/T = loc - loc = null - loc = T - -/obj/item/examine(mob/user) //This might be spammy. Remove? - . = ..() - - . += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item." - - if(resistance_flags & INDESTRUCTIBLE) - . += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!" - else - if(resistance_flags & LAVA_PROOF) - . += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!" - if(resistance_flags & (ACID_PROOF | UNACIDABLE)) - . += "[src] looks pretty robust! It'd probably be able to withstand acid!" - if(resistance_flags & FREEZE_PROOF) - . += "[src] is made of cold-resistant materials." - if(resistance_flags & FIRE_PROOF) - . += "[src] is made of fire-retardant materials." - - if(!user.research_scanner) - return - - /// Research prospects, including boostable nodes and point values. Deliver to a console to know whether the boosts have already been used. - var/list/research_msg = list("Research prospects: ") - ///Separator between the items on the list - var/sep = "" - ///Nodes that can be boosted - var/list/boostable_nodes = techweb_item_boost_check(src) - if (boostable_nodes) - for(var/id in boostable_nodes) - var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id) - if(!node) - continue - research_msg += sep - research_msg += node.display_name - sep = ", " - var/list/points = techweb_item_point_check(src) - if (length(points)) - sep = ", " - research_msg += techweb_point_display_generic(points) - - if (!sep) // nothing was shown - research_msg += "None" - - // Extractable materials. Only shows the names, not the amounts. - research_msg += ".
                    Extractable materials: " - if (length(custom_materials)) - sep = "" - for(var/mat in custom_materials) - research_msg += sep - research_msg += CallMaterialName(mat) - sep = ", " - else - research_msg += "None" - research_msg += "." - . += research_msg.Join() - -/obj/item/interact(mob/user) - add_fingerprint(user) - ui_interact(user) - -/obj/item/ui_act(action, list/params) - add_fingerprint(usr) - return ..() - -/obj/item/attack_hand(mob/user) - . = ..() - if(.) - return - if(!user) - return - if(anchored) - return - - if(resistance_flags & ON_FIRE) - var/mob/living/carbon/C = user - var/can_handle_hot = FALSE - if(!istype(C)) - can_handle_hot = TRUE - else if(C.gloves && (C.gloves.max_heat_protection_temperature > 360)) - can_handle_hot = TRUE - else if(HAS_TRAIT(C, TRAIT_RESISTHEAT) || HAS_TRAIT(C, TRAIT_RESISTHEATHANDS)) - can_handle_hot = TRUE - - if(can_handle_hot) - extinguish() - to_chat(user, "You put out the fire on [src].") - else - to_chat(user, "You burn your hand on [src]!") - var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") - if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage - C.update_damage_overlays() - return - - if(acid_level > 20 && !ismob(loc))// so we can still remove the clothes on us that have acid. - var/mob/living/carbon/C = user - if(istype(C)) - if(!C.gloves || (!(C.gloves.resistance_flags & (UNACIDABLE|ACID_PROOF)))) - to_chat(user, "The acid on [src] burns your hand!") - var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") - if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage - C.update_damage_overlays() - - if(!(interaction_flags_item & INTERACT_ITEM_ATTACK_HAND_PICKUP)) //See if we're supposed to auto pickup. - return - - //Heavy gravity makes picking up things very slow. - var/grav = user.has_gravity() - if(grav > STANDARD_GRAVITY) - var/grav_power = min(3,grav - STANDARD_GRAVITY) - to_chat(user,"You start picking up [src]...") - if(!do_mob(user,src,30*grav_power)) - return - - - //If the item is in a storage item, take it out - SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) - if(QDELETED(src)) //moving it out of the storage to the floor destroyed it. - return - - if(throwing) - throwing.finalize(FALSE) - if(loc == user) - if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) - return - - pickup(user) - add_fingerprint(user) - if(!user.put_in_active_hand(src, FALSE, FALSE)) - user.dropItemToGround(src) - -/obj/item/proc/allow_attack_hand_drop(mob/user) - return TRUE - -/obj/item/attack_paw(mob/user) - if(!user) - return - if(anchored) - return - - SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) - - if(throwing) - throwing.finalize(FALSE) - if(loc == user) - if(!user.temporarilyRemoveItemFromInventory(src)) - return - - pickup(user) - add_fingerprint(user) - if(!user.put_in_active_hand(src, FALSE, FALSE)) - user.dropItemToGround(src) - -/obj/item/attack_alien(mob/user) - var/mob/living/carbon/alien/A = user - - if(!A.has_fine_manipulation) - if(src in A.contents) // To stop Aliens having items stuck in their pockets - A.dropItemToGround(src) - to_chat(user, "Your claws aren't capable of such fine manipulation!") - return - attack_paw(A) - -/obj/item/attack_ai(mob/user) - if(istype(src.loc, /obj/item/robot_module)) - //If the item is part of a cyborg module, equip it - if(!iscyborg(user)) - return - var/mob/living/silicon/robot/R = user - if(!R.low_power_mode) //can't equip modules with an empty cell. - R.activate_module(src) - R.hud_used.update_robot_modules_display() - -/obj/item/proc/GetDeconstructableContents() - return GetAllContents() - src - -// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency - -/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) - if(prob(final_block_chance)) - owner.visible_message("[owner] blocks [attack_text] with [src]!") - return 1 - return 0 - -/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language) - return ITALICS | REDUCE_RANGE - -/obj/item/proc/dropped(mob/user, silent = FALSE) - SHOULD_CALL_PARENT(1) - for(var/X in actions) - var/datum/action/A = X - A.Remove(user) - if(item_flags & DROPDEL) - qdel(src) - item_flags &= ~IN_INVENTORY - SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) - if(!silent) - playsound(src, drop_sound, DROP_SOUND_VOLUME, ignore_walls = FALSE) - user?.update_equipment_speed_mods() - -/// called just as an item is picked up (loc is not yet changed) -/obj/item/proc/pickup(mob/user) - SHOULD_CALL_PARENT(1) - SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) - item_flags |= IN_INVENTORY - -/// called when "found" in pockets and storage items. Returns 1 if the search should end. -/obj/item/proc/on_found(mob/finder) - return - -/** - *called after an item is placed in an equipment slot - - * Arguments: - * * user is mob that equipped it - * * slot uses the slot_X defines found in setup.dm for items that can be placed in multiple slots - * * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it - */ -/obj/item/proc/equipped(mob/user, slot, initial = FALSE) - SHOULD_CALL_PARENT(1) - SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) - for(var/X in actions) - var/datum/action/A = X - if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. - A.Grant(user) - item_flags |= IN_INVENTORY - if(!initial) - if(equip_sound && (slot_flags & slot)) - playsound(src, equip_sound, EQUIP_SOUND_VOLUME, TRUE, ignore_walls = FALSE) - else if(slot == ITEM_SLOT_HANDS) - playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE) - user.update_equipment_speed_mods() - -///sometimes we only want to grant the item's action if it's equipped in a specific slot. -/obj/item/proc/item_action_slot_check(slot, mob/user) - if(slot == ITEM_SLOT_BACKPACK || slot == ITEM_SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there - return FALSE - return TRUE - -/** - *the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't. - *if this is being done by a mob other than M, it will include the mob equipper, who is trying to equip the item to mob M. equipper will be null otherwise. - *If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen. - * Arguments: - * * disable_warning to TRUE if you wish it to not give you text outputs. - * * slot is the slot we are trying to equip to - * * equipper is the mob trying to equip the item - * * bypass_equip_delay_self for whether we want to bypass the equip delay - */ -/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, swap = FALSE) - if(!M) - return FALSE - - return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self, swap) - -/obj/item/verb/verb_pickup() - set src in oview(1) - set category = "Object" - set name = "Pick up" - - if(usr.incapacitated() || !Adjacent(usr)) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - - if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota - usr.UnarmedAttack(src) - -/** - *This proc is executed when someone clicks the on-screen UI button. - *The default action is attack_self(). - *Checks before we get to here are: mob is alive, mob is not restrained, stunned, asleep, resting, laying, item is on the mob. - */ -/obj/item/proc/ui_action_click(mob/user, actiontype) - attack_self(user) - -///This proc determines if and at what an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit -/obj/item/proc/IsReflect(var/def_zone) - return FALSE - -/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user) - - var/is_human_victim - var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) - if(ishuman(M)) - if(!affecting) //no head! - return - is_human_victim = TRUE - - if(M.is_eyes_covered()) - // you can't stab someone in the eyes wearing a mask! - to_chat(user, "You failed to stab [M.p_their()] eyes, you need to remove [M.p_their()] eye protection first!") - return - - if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes! - to_chat(user, "You cannot locate any eyes on this creature!") - return - - if(isbrain(M)) - to_chat(user, "You cannot locate any organic eyes on this brain!") - return - - src.add_fingerprint(user) - - playsound(loc, src.hitsound, 30, TRUE, -1) - - user.do_attack_animation(M) - - if(M != user) - M.visible_message("[user] stabs [M] in the eye with [src]!", \ - "[user] stabs you in the eye with [src]!") - else - user.visible_message( \ - "[user] stabs [user.p_them()]self in the eyes with [src]!", \ - "You stab yourself in the eyes with [src]!" \ - ) - if(is_human_victim) - var/mob/living/carbon/human/U = M - U.apply_damage(7, BRUTE, affecting) - - else - M.take_bodypart_damage(7) - - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "eye_stab", /datum/mood_event/eye_stab) - - log_combat(user, M, "attacked", "[src.name]", "(INTENT: [uppertext(user.a_intent)])") - - var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES) - if(!eyes) - return TRUE - M.adjust_blurriness(3) - eyes.applyOrganDamage(rand(2,4)) - if(eyes.damage >= 10) - M.adjust_blurriness(15) - if(M.stat != DEAD) - to_chat(M, "Your eyes start to bleed profusely!") - if(!(M.is_blind() || HAS_TRAIT(M, TRAIT_NEARSIGHT))) - to_chat(M, "You become nearsighted!") - M.become_nearsighted(EYE_DAMAGE) - if(prob(50)) - if(M.stat != DEAD) - if(M.drop_all_held_items()) - to_chat(M, "You drop what you're holding and clutch at your eyes!") - M.adjust_blurriness(10) - M.Unconscious(20) - M.Paralyze(40) - if(prob(eyes.damage - 10 + 1)) - M.become_blind(EYE_DAMAGE) - to_chat(M, "You go blind!") - return TRUE - -/obj/item/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FOUR) - throw_at(S,14,3, spin=0) - else - return - -/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(hit_atom && !QDELETED(hit_atom)) - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - if(get_temperature() && isliving(hit_atom)) - var/mob/living/L = hit_atom - L.IgniteMob() - var/itempush = 1 - if(w_class < 4) - itempush = 0 //too light to push anything - if(istype(hit_atom, /mob/living)) //Living mobs handle hit sounds differently. - var/volume = get_volume_by_throwforce_and_or_w_class() - if (throwforce > 0) - if (mob_throw_hit_sound) - playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1) - else if(hitsound) - playsound(hit_atom, hitsound, volume, TRUE, -1) - else - playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1) - else - playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) - - else - playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE) - return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) - -/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - if(HAS_TRAIT(src, TRAIT_NODROP)) - return - thrownby = thrower - callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own - . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle, quickstart = quickstart) - - -/obj/item/proc/after_throw(datum/callback/callback) - if (callback) //call the original callback - . = callback.Invoke() - item_flags &= ~IN_INVENTORY - if(!pixel_y && !pixel_x) - pixel_x = rand(-8,8) - pixel_y = rand(-8,8) - - -/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage - if(!newLoc) - return FALSE - if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE)) - return SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, newLoc, TRUE) - return FALSE - -/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) - -/obj/item/proc/update_slot_icon() - if(!ismob(loc)) - return - var/mob/owner = loc - var/flags = slot_flags - if(flags & ITEM_SLOT_OCLOTHING) - owner.update_inv_wear_suit() - if(flags & ITEM_SLOT_ICLOTHING) - owner.update_inv_w_uniform() - if(flags & ITEM_SLOT_GLOVES) - owner.update_inv_gloves() - if(flags & ITEM_SLOT_EYES) - owner.update_inv_glasses() - if(flags & ITEM_SLOT_EARS) - owner.update_inv_ears() - if(flags & ITEM_SLOT_MASK) - owner.update_inv_wear_mask() - if(flags & ITEM_SLOT_HEAD) - owner.update_inv_head() - if(flags & ITEM_SLOT_FEET) - owner.update_inv_shoes() - if(flags & ITEM_SLOT_ID) - owner.update_inv_wear_id() - if(flags & ITEM_SLOT_BELT) - owner.update_inv_belt() - if(flags & ITEM_SLOT_BACK) - owner.update_inv_back() - if(flags & ITEM_SLOT_NECK) - owner.update_inv_neck() - -///Returns the temperature of src. If you want to know if an item is hot use this proc. -/obj/item/proc/get_temperature() - return heat - -///Returns the sharpness of src. If you want to get the sharpness of an item use this. -/obj/item/proc/get_sharpness() - return sharpness - -/obj/item/proc/get_dismemberment_chance(obj/item/bodypart/affecting) - if(affecting.can_dismember(src)) - if((sharpness || damtype == BURN) && w_class >= WEIGHT_CLASS_NORMAL && force >= 10) - . = force * (affecting.get_damage() / affecting.max_damage) - -/obj/item/proc/get_dismember_sound() - if(damtype == BURN) - . = 'sound/weapons/sear.ogg' - else - . = "desecration" - -/obj/item/proc/open_flame(flame_heat=700) - var/turf/location = loc - if(ismob(location)) - var/mob/M = location - var/success = FALSE - if(src == M.get_item_by_slot(ITEM_SLOT_MASK)) - success = TRUE - if(success) - location = get_turf(M) - if(isturf(location)) - location.hotspot_expose(flame_heat, 5) - -/obj/item/proc/ignition_effect(atom/A, mob/user) - if(get_temperature()) - . = "[user] lights [A] with [src]." - else - . = "" - -/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - return - -/obj/item/attack_hulk(mob/living/carbon/human/user) - return FALSE - -/obj/item/attack_animal(mob/living/simple_animal/M) - if (obj_flags & CAN_BE_HIT) - return ..() - return 0 - -/obj/item/mech_melee_attack(obj/mecha/M) - return 0 - -/obj/item/burn() - if(!QDELETED(src)) - var/turf/T = get_turf(src) - var/ash_type = /obj/effect/decal/cleanable/ash - if(w_class == WEIGHT_CLASS_HUGE || w_class == WEIGHT_CLASS_GIGANTIC) - ash_type = /obj/effect/decal/cleanable/ash/large - var/obj/effect/decal/cleanable/ash/A = new ash_type(T) - A.desc += "\nLooks like this used to be \an [name] some time ago." - ..() - -/obj/item/acid_melt() - if(!QDELETED(src)) - var/turf/T = get_turf(src) - var/obj/effect/decal/cleanable/molten_object/MO = new(T) - MO.pixel_x = rand(-16,16) - MO.pixel_y = rand(-16,16) - MO.desc = "Looks like this was \an [src] some time ago." - ..() - -/obj/item/proc/microwave_act(obj/machinery/microwave/M) - SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, M) - if(istype(M) && M.dirty < 100) - M.dirty++ - -/obj/item/proc/on_mob_death(mob/living/L, gibbed) - -/obj/item/proc/grind_requirements(obj/machinery/reagentgrinder/R) //Used to check for extra requirements for grinding an object - return TRUE - -///Called BEFORE the object is ground up - use this to change grind results based on conditions. Use "return -1" to prevent the grinding from occurring -/obj/item/proc/on_grind() - -/obj/item/proc/on_juice() - -/obj/item/proc/set_force_string() - switch(force) - if(0 to 4) - force_string = "very low" - if(4 to 7) - force_string = "low" - if(7 to 10) - force_string = "medium" - if(10 to 11) - force_string = "high" - if(11 to 20) //12 is the force of a toolbox - force_string = "robust" - if(20 to 25) - force_string = "very robust" - else - force_string = "exceptionally robust" - last_force_string_check = force - -/obj/item/proc/openTip(location, control, params, user) - if(last_force_string_check != force && !(item_flags & FORCE_STRING_OVERRIDE)) - set_force_string() - if(!(item_flags & FORCE_STRING_OVERRIDE)) - openToolTip(user,src,params,title = name,content = "[desc]
                    [force ? "Force: [force_string]" : ""]",theme = "") - else - openToolTip(user,src,params,title = name,content = "[desc]
                    Force: [force_string]",theme = "") - -/obj/item/MouseEntered(location, control, params) - if((item_flags & IN_INVENTORY || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src)) - var/timedelay = usr.client.prefs.tip_delay/100 - var/user = usr - tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it. - -/obj/item/MouseExited() - deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes - closeToolTip(usr) - - -/// Called when a mob tries to use the item as a tool.Handles most checks. -/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks) - // No delay means there is no start message, and no reason to call tool_start_check before use_tool. - // Run the start check here so we wouldn't have to call it manually. - if(!delay && !tool_start_check(user, amount)) - return - - var/skill_modifier = 1 - - if(tool_behaviour == TOOL_MINING && ishuman(user)) - var/mob/living/carbon/human/H = user - skill_modifier = H.mind.get_skill_modifier(/datum/skill/mining, SKILL_SPEED_MODIFIER) - - if(H.mind.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_JOURNEYMAN && prob(H.mind.get_skill_modifier(/datum/skill/mining, SKILL_PROBS_MODIFIER))) // we check if the skill level is greater than Journeyman and then we check for the probality for that specific level. - mineral_scan_pulse(get_turf(H), SKILL_LEVEL_JOURNEYMAN - 2) //SKILL_LEVEL_JOURNEYMAN = 3 So to get range of 1+ we have to subtract 2 from it,. - - delay *= toolspeed * skill_modifier - - - // Play tool sound at the beginning of tool usage. - play_tool_sound(target, volume) - - if(delay) - // Create a callback with checks that would be called every tick by do_after. - var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, amount, extra_checks) - - if(ismob(target)) - if(!do_mob(user, target, delay, extra_checks=tool_check)) - return - - else - if(!do_after(user, delay, target=target, extra_checks=tool_check)) - return - else - // Invoke the extra checks once, just in case. - if(extra_checks && !extra_checks.Invoke()) - return - - // Use tool's fuel, stack sheets or charges if amount is set. - if(amount && !use(amount)) - return - - // Play tool sound at the end of tool usage, - // but only if the delay between the beginning and the end is not too small - if(delay >= MIN_TOOL_SOUND_DELAY) - play_tool_sound(target, volume) - - return TRUE - -/// Called before [obj/item/proc/use_tool] if there is a delay, or by [obj/item/proc/use_tool] if there isn't. Only ever used by welding tools and stacks, so it's not added on any other [obj/item/proc/use_tool] checks. -/obj/item/proc/tool_start_check(mob/living/user, amount=0) - . = tool_use_check(user, amount) - if(.) - SEND_SIGNAL(src, COMSIG_TOOL_START_USE, user) - -/// A check called by [/obj/item/proc/tool_start_check] once, and by use_tool on every tick of delay. -/obj/item/proc/tool_use_check(mob/living/user, amount) - return !amount - -/// Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. Returns TRUE on success, FALSE on failure. -/obj/item/proc/use(used) - return !used - -/// Plays item's usesound, if any. -/obj/item/proc/play_tool_sound(atom/target, volume=50) - if(target && usesound && volume) - var/played_sound = usesound - - if(islist(usesound)) - played_sound = pick(usesound) - - playsound(target, played_sound, volume, TRUE) - -/// Used in a callback that is passed by use_tool into do_after call. Do not override, do not call manually. -/obj/item/proc/tool_check_callback(mob/living/user, amount, datum/callback/extra_checks) - SHOULD_NOT_OVERRIDE(TRUE) - . = tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) - if(.) - SEND_SIGNAL(src, COMSIG_TOOL_IN_USE, user) - -/// Returns a numeric value for sorting items used as parts in machines, so they can be replaced by the rped -/obj/item/proc/get_part_rating() - return 0 - -/obj/item/doMove(atom/destination) - if (ismob(loc)) - var/mob/M = loc - var/hand_index = M.get_held_index_of_item(src) - if(hand_index) - M.held_items[hand_index] = null - M.update_inv_hands() - if(M.client) - M.client.screen -= src - layer = initial(layer) - plane = initial(plane) - appearance_flags &= ~NO_CLIENT_COLOR - dropped(M, FALSE) - return ..() - -/obj/item/proc/embedded(atom/embedded_target) - return - -/obj/item/proc/unembedded() - if(item_flags & DROPDEL) - QDEL_NULL(src) - return TRUE - -/obj/item/proc/canStrip(mob/stripper, mob/owner) - SHOULD_BE_PURE(TRUE) - return !HAS_TRAIT(src, TRAIT_NODROP) - -/obj/item/proc/doStrip(mob/stripper, mob/owner) - return owner.dropItemToGround(src) - -///Does the current embedding var meet the criteria for being harmless? Namely, does it have a pain multiplier and jostle pain mult of 0? If so, return true. -/obj/item/proc/isEmbedHarmless() - if(embedding) - return !isnull(embedding["pain_mult"]) && !isnull(embedding["jostle_pain_mult"]) && embedding["pain_mult"] == 0 && embedding["jostle_pain_mult"] == 0 - -///In case we want to do something special (like self delete) upon failing to embed in something, return true -/obj/item/proc/failedEmbed() - if(item_flags & DROPDEL) - QDEL_NULL(src) - return TRUE - -///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else. -/obj/item/proc/on_thrown(mob/living/carbon/user, atom/target) - if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP)) - return - user.dropItemToGround(src, silent = TRUE) - if(throwforce && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You set [src] down gently on the ground.") - return - return src - -/** - * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targetting the target. - * - * Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements. - * - * Arguments: - * * target- Either a body part or a carbon. What are we hitting? - * * forced- Do we want this to go through 100%? - */ -/obj/item/proc/tryEmbed(atom/target, forced=FALSE, silent=FALSE) - if(!isbodypart(target) && !iscarbon(target)) - return - if(!forced && !LAZYLEN(embedding)) - return - - if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target, forced, silent)) - return TRUE - failedEmbed() - -///For when you want to disable an item's embedding capabilities (like transforming weapons and such), this proc will detach any active embed elements from it. -/obj/item/proc/disableEmbedding() - SEND_SIGNAL(src, COMSIG_ITEM_DISABLE_EMBED) - return - -///For when you want to add/update the embedding on an item. Uses the vars in [/obj/item/embedding], and defaults to config values for values that aren't set. Will automatically detach previous embed elements on this item. -/obj/item/proc/updateEmbedding() - if(!islist(embedding) || !LAZYLEN(embedding)) - return - - AddElement(/datum/element/embed,\ - embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\ - fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\ - pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\ - pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\ - remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\ - rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\ - ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\ - impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\ - jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\ - jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\ - pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT)) - return TRUE +GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/effects/fire.dmi', "fire")) + +GLOBAL_VAR_INIT(rpg_loot_items, FALSE) +// if true, everyone item when created will have its name changed to be +// more... RPG-like. + +GLOBAL_VAR_INIT(stickpocalypse, FALSE) // if true, all non-embeddable items will be able to harmlessly stick to people when thrown +GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to embed in people, takes precedence over stickpocalypse + +/obj/item + name = "item" + icon = 'icons/obj/items_and_weapons.dmi' + blocks_emissive = EMISSIVE_BLOCK_GENERIC + + /* !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! + + IF YOU ADD MORE ICON CRAP TO THIS + ENSURE YOU ALSO ADD THE NEW VARS TO CHAMELEON ITEM_ACTION'S update_item() PROC (/datum/action/item_action/chameleon/change/proc/update_item()) + WASHING MASHINE'S dye_item() PROC (/obj/item/proc/dye_item()) + AND ALSO TO THE CHANGELING PROFILE DISGUISE SYSTEMS (/datum/changelingprofile / /datum/antagonist/changeling/proc/create_profile() / /proc/changeling_transform()) + + !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! */ + + ///icon state for inhand overlays, if null the normal icon_state will be used. + var/inhand_icon_state = null + ///Icon file for left hand inhand overlays + var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + ///Icon file for right inhand overlays + var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' + + ///Icon file for mob worn overlays. + var/icon/worn_icon + ///icon state for mob worn overlays, if null the normal icon_state will be used. + var/worn_icon_state + ///Forced mob worn layer instead of the standard preferred ssize. + var/alternate_worn_layer + + /* !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! + + IF YOU ADD MORE ICON CRAP TO THIS + ENSURE YOU ALSO ADD THE NEW VARS TO CHAMELEON ITEM_ACTION'S update_item() PROC (/datum/action/item_action/chameleon/change/proc/update_item()) + WASHING MASHINE'S dye_item() PROC (/obj/item/proc/dye_item()) + AND ALSO TO THE CHANGELING PROFILE DISGUISE SYSTEMS (/datum/changelingprofile / /datum/antagonist/changeling/proc/create_profile() / /proc/changeling_transform()) + + !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! */ + + ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly + var/worn_x_dimension = 32 + ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly + var/worn_y_dimension = 32 + ///Same as for [worn_x_dimension][/obj/item/var/worn_x_dimension] but for inhands, uses the lefthand_ and righthand_ file vars + var/inhand_x_dimension = 32 + ///Same as for [worn_y_dimension][/obj/item/var/worn_y_dimension] but for inhands, uses the lefthand_ and righthand_ file vars + var/inhand_y_dimension = 32 + + + max_integrity = 200 + + obj_flags = NONE + ///Item flags for the item + var/item_flags = NONE + + ///Sound played when you hit something with the item + var/hitsound + ///Played when the item is used, for example tools + var/usesound + ///Used when yate into a mob + var/mob_throw_hit_sound + ///Sound used when equipping the item into a valid slot + var/equip_sound + ///Sound uses when picking the item up (into your hands) + var/pickup_sound + ///Sound uses when dropping the item, or when its thrown. + var/drop_sound + + ///How large is the object, used for stuff like whether it can fit in backpacks or not + var/w_class = WEIGHT_CLASS_NORMAL + ///This is used to determine on which slots an item can fit. + var/slot_flags = 0 + pass_flags = PASSTABLE + pressure_resistance = 4 + var/obj/item/master = null + + ///flags which determine which body parts are protected from heat. [See here][HEAD] + var/heat_protection = 0 + ///flags which determine which body parts are protected from cold. [See here][HEAD] + var/cold_protection = 0 + ///Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags + var/max_heat_protection_temperature + ///Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags + var/min_cold_protection_temperature + + ///list of /datum/action's that this item has. + var/list/actions + ///list of paths of action datums to give to the item on New(). + var/list/actions_types + + //Since any item can now be a piece of clothing, this has to be put here so all items share it. + ///This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc. + var/flags_inv + ///you can see someone's mask through their transparent visor, but you can't reach it + var/transparent_protection = NONE + + ///flags for what should be done when you click on the item, default is picking it up + var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP + + ///What body parts are covered by the clothing when you wear it + var/body_parts_covered = 0 + ///Literally does nothing right now + var/gas_transfer_coefficient = 1 + /// How likely a disease or chemical is to get through a piece of clothing + var/permeability_coefficient = 1 + /// for electrical admittance/conductance (electrocution checks and shit) + var/siemens_coefficient = 1 + /// How much clothing is slowing you down. Negative values speeds you up + var/slowdown = 0 + ///percentage of armour effectiveness to remove + var/armour_penetration = 0 + ///What objects the suit storage can store + var/list/allowed = null + ///In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc. + var/equip_delay_self = 0 + ///In deciseconds, how long an item takes to put on another person + var/equip_delay_other = 20 + ///In deciseconds, how long an item takes to remove from another person + var/strip_delay = 40 + ///How long it takes to resist out of the item (cuffs and such) + var/breakouttime = 0 + + ///Used in [atom/proc/attackby] to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" + var/list/attack_verb + ///list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item + var/list/species_exception = null + + ///Who threw the item + var/mob/thrownby = null + + ///the icon to indicate this object is being dragged + mouse_drag_pointer = MOUSE_ACTIVE_POINTER + + ///Does it embed and if yes, what kind of embed + var/list/embedding = NONE + + ///for flags such as [GLASSESCOVERSEYES] + var/flags_cover = 0 + var/heat = 0 + ///All items with sharpness of IS_SHARP or higher will automatically get the butchering component. + var/sharpness = IS_BLUNT + + ///How a tool acts when you use it on something, such as wirecutters cutting wires while multitools measure power + var/tool_behaviour = NONE + ///How fast does the tool work + var/toolspeed = 1 + + var/block_chance = 0 + var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom + ///In tiles, how far this weapon can reach; 1 for adjacent, which is default + var/reach = 1 + + ///The list of slots by priority. equip_to_appropriate_slot() uses this list. Doesn't matter if a mob type doesn't have a slot. For default list, see [/mob/proc/equip_to_appropriate_slot] + var/list/slot_equipment_priority = null + + ///Reference to the datum that determines whether dogs can wear the item: Needs to be in /obj/item because corgis can wear a lot of non-clothing items + var/datum/dog_fashion/dog_fashion = null + + //Tooltip vars + ///string form of an item's force. Edit this var only to set a custom force string + var/force_string + var/last_force_string_check = 0 + var/tip_timer + + ///Determines who can shoot this + var/trigger_guard = TRIGGER_GUARD_NONE + + ///Used as the dye color source in the washing machine only (at the moment). Can be a hex color or a key corresponding to a registry entry, see washing_machine.dm + var/dye_color + ///Whether the item is unaffected by standard dying. + var/undyeable = FALSE + ///What dye registry should be looked at when dying this item; see washing_machine.dm + var/dying_key + + ///Grinder var:A reagent list containing the reagents this item produces when ground up in a grinder - this can be an empty list to allow for reagent transferring only + var/list/grind_results + //Grinder var:A reagent list containing blah blah... but when JUICED in a grinder! + var/list/juice_results + + var/canMouseDown = FALSE + + +/obj/item/Initialize() + + if(attack_verb) + attack_verb = typelist("attack_verb", attack_verb) + + . = ..() + for(var/path in actions_types) + new path(src) + actions_types = null + + if(force_string) + item_flags |= FORCE_STRING_OVERRIDE + + if(!hitsound) + if(damtype == "fire") + hitsound = 'sound/items/welder.ogg' + if(damtype == "brute") + hitsound = "swing_hit" + +/obj/item/Destroy() + item_flags &= ~DROPDEL //prevent reqdels + if(ismob(loc)) + var/mob/m = loc + m.temporarilyRemoveItemFromInventory(src, TRUE) + for(var/X in actions) + qdel(X) + return ..() + +/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) + if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside)) + return 0 + else + return 1 + +/obj/item/blob_act(obj/structure/blob/B) + if(B && B.loc == loc) + qdel(src) + +/obj/item/ComponentInitialize() + . = ..() + // this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:) + if(!LAZYLEN(embedding)) + if(GLOB.embedpocalypse) + embedding = EMBED_POINTY + name = "pointy [name]" + else if(GLOB.stickpocalypse) + embedding = EMBED_HARMLESS + name = "sticky [name]" + + updateEmbedding() + + if(GLOB.rpg_loot_items) + AddComponent(/datum/component/fantasy) + + if(sharpness) //give sharp objects butchering functionality, for consistency + AddComponent(/datum/component/butchering, 80 * toolspeed) + +/**Makes cool stuff happen when you suicide with an item + * + *Outputs a creative message and then return the damagetype done + * Arguments: + * * user: The mob that is suiciding + */ +/obj/item/proc/suicide_act(mob/user) + return + +/obj/item/verb/move_to_top() + set name = "Move To Top" + set category = "Object" + set src in oview(1) + + if(!isturf(loc) || usr.stat || usr.restrained()) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + + var/turf/T = loc + loc = null + loc = T + +/obj/item/examine(mob/user) //This might be spammy. Remove? + . = ..() + + . += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item." + + if(resistance_flags & INDESTRUCTIBLE) + . += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!" + else + if(resistance_flags & LAVA_PROOF) + . += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!" + if(resistance_flags & (ACID_PROOF | UNACIDABLE)) + . += "[src] looks pretty robust! It'd probably be able to withstand acid!" + if(resistance_flags & FREEZE_PROOF) + . += "[src] is made of cold-resistant materials." + if(resistance_flags & FIRE_PROOF) + . += "[src] is made of fire-retardant materials." + + if(!user.research_scanner) + return + + /// Research prospects, including boostable nodes and point values. Deliver to a console to know whether the boosts have already been used. + var/list/research_msg = list("Research prospects: ") + ///Separator between the items on the list + var/sep = "" + ///Nodes that can be boosted + var/list/boostable_nodes = techweb_item_boost_check(src) + if (boostable_nodes) + for(var/id in boostable_nodes) + var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id) + if(!node) + continue + research_msg += sep + research_msg += node.display_name + sep = ", " + var/list/points = techweb_item_point_check(src) + if (length(points)) + sep = ", " + research_msg += techweb_point_display_generic(points) + + if (!sep) // nothing was shown + research_msg += "None" + + // Extractable materials. Only shows the names, not the amounts. + research_msg += ".
                    Extractable materials: " + if (length(custom_materials)) + sep = "" + for(var/mat in custom_materials) + research_msg += sep + research_msg += CallMaterialName(mat) + sep = ", " + else + research_msg += "None" + research_msg += "." + . += research_msg.Join() + +/obj/item/interact(mob/user) + add_fingerprint(user) + ui_interact(user) + +/obj/item/ui_act(action, list/params) + add_fingerprint(usr) + return ..() + +/obj/item/attack_hand(mob/user) + . = ..() + if(.) + return + if(!user) + return + if(anchored) + return + + if(resistance_flags & ON_FIRE) + var/mob/living/carbon/C = user + var/can_handle_hot = FALSE + if(!istype(C)) + can_handle_hot = TRUE + else if(C.gloves && (C.gloves.max_heat_protection_temperature > 360)) + can_handle_hot = TRUE + else if(HAS_TRAIT(C, TRAIT_RESISTHEAT) || HAS_TRAIT(C, TRAIT_RESISTHEATHANDS)) + can_handle_hot = TRUE + + if(can_handle_hot) + extinguish() + to_chat(user, "You put out the fire on [src].") + else + to_chat(user, "You burn your hand on [src]!") + var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") + if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage + C.update_damage_overlays() + return + + if(acid_level > 20 && !ismob(loc))// so we can still remove the clothes on us that have acid. + var/mob/living/carbon/C = user + if(istype(C)) + if(!C.gloves || (!(C.gloves.resistance_flags & (UNACIDABLE|ACID_PROOF)))) + to_chat(user, "The acid on [src] burns your hand!") + var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") + if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage + C.update_damage_overlays() + + if(!(interaction_flags_item & INTERACT_ITEM_ATTACK_HAND_PICKUP)) //See if we're supposed to auto pickup. + return + + //Heavy gravity makes picking up things very slow. + var/grav = user.has_gravity() + if(grav > STANDARD_GRAVITY) + var/grav_power = min(3,grav - STANDARD_GRAVITY) + to_chat(user,"You start picking up [src]...") + if(!do_mob(user,src,30*grav_power)) + return + + + //If the item is in a storage item, take it out + SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) + if(QDELETED(src)) //moving it out of the storage to the floor destroyed it. + return + + if(throwing) + throwing.finalize(FALSE) + if(loc == user) + if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) + return + + pickup(user) + add_fingerprint(user) + if(!user.put_in_active_hand(src, FALSE, FALSE)) + user.dropItemToGround(src) + +/obj/item/proc/allow_attack_hand_drop(mob/user) + return TRUE + +/obj/item/attack_paw(mob/user) + if(!user) + return + if(anchored) + return + + SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) + + if(throwing) + throwing.finalize(FALSE) + if(loc == user) + if(!user.temporarilyRemoveItemFromInventory(src)) + return + + pickup(user) + add_fingerprint(user) + if(!user.put_in_active_hand(src, FALSE, FALSE)) + user.dropItemToGround(src) + +/obj/item/attack_alien(mob/user) + var/mob/living/carbon/alien/A = user + + if(!A.has_fine_manipulation) + if(src in A.contents) // To stop Aliens having items stuck in their pockets + A.dropItemToGround(src) + to_chat(user, "Your claws aren't capable of such fine manipulation!") + return + attack_paw(A) + +/obj/item/attack_ai(mob/user) + if(istype(src.loc, /obj/item/robot_module)) + //If the item is part of a cyborg module, equip it + if(!iscyborg(user)) + return + var/mob/living/silicon/robot/R = user + if(!R.low_power_mode) //can't equip modules with an empty cell. + R.activate_module(src) + R.hud_used.update_robot_modules_display() + +/obj/item/proc/GetDeconstructableContents() + return GetAllContents() - src + +// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency + +/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) + if(prob(final_block_chance)) + owner.visible_message("[owner] blocks [attack_text] with [src]!") + return 1 + return 0 + +/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language) + return ITALICS | REDUCE_RANGE + +/obj/item/proc/dropped(mob/user, silent = FALSE) + SHOULD_CALL_PARENT(1) + for(var/X in actions) + var/datum/action/A = X + A.Remove(user) + if(item_flags & DROPDEL) + qdel(src) + item_flags &= ~IN_INVENTORY + SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) + if(!silent) + playsound(src, drop_sound, DROP_SOUND_VOLUME, ignore_walls = FALSE) + user?.update_equipment_speed_mods() + +/// called just as an item is picked up (loc is not yet changed) +/obj/item/proc/pickup(mob/user) + SHOULD_CALL_PARENT(1) + SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) + item_flags |= IN_INVENTORY + +/// called when "found" in pockets and storage items. Returns 1 if the search should end. +/obj/item/proc/on_found(mob/finder) + return + +/** + *called after an item is placed in an equipment slot + + * Arguments: + * * user is mob that equipped it + * * slot uses the slot_X defines found in setup.dm for items that can be placed in multiple slots + * * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it + */ +/obj/item/proc/equipped(mob/user, slot, initial = FALSE) + SHOULD_CALL_PARENT(1) + SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) + for(var/X in actions) + var/datum/action/A = X + if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. + A.Grant(user) + item_flags |= IN_INVENTORY + if(!initial) + if(equip_sound && (slot_flags & slot)) + playsound(src, equip_sound, EQUIP_SOUND_VOLUME, TRUE, ignore_walls = FALSE) + else if(slot == ITEM_SLOT_HANDS) + playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE) + user.update_equipment_speed_mods() + +///sometimes we only want to grant the item's action if it's equipped in a specific slot. +/obj/item/proc/item_action_slot_check(slot, mob/user) + if(slot == ITEM_SLOT_BACKPACK || slot == ITEM_SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there + return FALSE + return TRUE + +/** + *the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't. + *if this is being done by a mob other than M, it will include the mob equipper, who is trying to equip the item to mob M. equipper will be null otherwise. + *If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen. + * Arguments: + * * disable_warning to TRUE if you wish it to not give you text outputs. + * * slot is the slot we are trying to equip to + * * equipper is the mob trying to equip the item + * * bypass_equip_delay_self for whether we want to bypass the equip delay + */ +/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, swap = FALSE) + if(!M) + return FALSE + + return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self, swap) + +/obj/item/verb/verb_pickup() + set src in oview(1) + set category = "Object" + set name = "Pick up" + + if(usr.incapacitated() || !Adjacent(usr)) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + + if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota + usr.UnarmedAttack(src) + +/** + *This proc is executed when someone clicks the on-screen UI button. + *The default action is attack_self(). + *Checks before we get to here are: mob is alive, mob is not restrained, stunned, asleep, resting, laying, item is on the mob. + */ +/obj/item/proc/ui_action_click(mob/user, actiontype) + attack_self(user) + +///This proc determines if and at what an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit +/obj/item/proc/IsReflect(var/def_zone) + return FALSE + +/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user) + + var/is_human_victim + var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) + if(ishuman(M)) + if(!affecting) //no head! + return + is_human_victim = TRUE + + if(M.is_eyes_covered()) + // you can't stab someone in the eyes wearing a mask! + to_chat(user, "You failed to stab [M.p_their()] eyes, you need to remove [M.p_their()] eye protection first!") + return + + if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes! + to_chat(user, "You cannot locate any eyes on this creature!") + return + + if(isbrain(M)) + to_chat(user, "You cannot locate any organic eyes on this brain!") + return + + src.add_fingerprint(user) + + playsound(loc, src.hitsound, 30, TRUE, -1) + + user.do_attack_animation(M) + + if(M != user) + M.visible_message("[user] stabs [M] in the eye with [src]!", \ + "[user] stabs you in the eye with [src]!") + else + user.visible_message( \ + "[user] stabs [user.p_them()]self in the eyes with [src]!", \ + "You stab yourself in the eyes with [src]!" \ + ) + if(is_human_victim) + var/mob/living/carbon/human/U = M + U.apply_damage(7, BRUTE, affecting) + + else + M.take_bodypart_damage(7) + + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "eye_stab", /datum/mood_event/eye_stab) + + log_combat(user, M, "attacked", "[src.name]", "(INTENT: [uppertext(user.a_intent)])") + + var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES) + if(!eyes) + return TRUE + M.adjust_blurriness(3) + eyes.applyOrganDamage(rand(2,4)) + if(eyes.damage >= 10) + M.adjust_blurriness(15) + if(M.stat != DEAD) + to_chat(M, "Your eyes start to bleed profusely!") + if(!(M.is_blind() || HAS_TRAIT(M, TRAIT_NEARSIGHT))) + to_chat(M, "You become nearsighted!") + M.become_nearsighted(EYE_DAMAGE) + if(prob(50)) + if(M.stat != DEAD) + if(M.drop_all_held_items()) + to_chat(M, "You drop what you're holding and clutch at your eyes!") + M.adjust_blurriness(10) + M.Unconscious(20) + M.Paralyze(40) + if(prob(eyes.damage - 10 + 1)) + M.become_blind(EYE_DAMAGE) + to_chat(M, "You go blind!") + return TRUE + +/obj/item/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FOUR) + throw_at(S,14,3, spin=0) + else + return + +/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(hit_atom && !QDELETED(hit_atom)) + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) + if(get_temperature() && isliving(hit_atom)) + var/mob/living/L = hit_atom + L.IgniteMob() + var/itempush = 1 + if(w_class < 4) + itempush = 0 //too light to push anything + if(istype(hit_atom, /mob/living)) //Living mobs handle hit sounds differently. + var/volume = get_volume_by_throwforce_and_or_w_class() + if (throwforce > 0) + if (mob_throw_hit_sound) + playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1) + else if(hitsound) + playsound(hit_atom, hitsound, volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) + + else + playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE) + return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) + +/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + if(HAS_TRAIT(src, TRAIT_NODROP)) + return + thrownby = thrower + callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own + . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle, quickstart = quickstart) + + +/obj/item/proc/after_throw(datum/callback/callback) + if (callback) //call the original callback + . = callback.Invoke() + item_flags &= ~IN_INVENTORY + if(!pixel_y && !pixel_x) + pixel_x = rand(-8,8) + pixel_y = rand(-8,8) + + +/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage + if(!newLoc) + return FALSE + if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE)) + return SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, newLoc, TRUE) + return FALSE + +/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) + +/obj/item/proc/update_slot_icon() + if(!ismob(loc)) + return + var/mob/owner = loc + var/flags = slot_flags + if(flags & ITEM_SLOT_OCLOTHING) + owner.update_inv_wear_suit() + if(flags & ITEM_SLOT_ICLOTHING) + owner.update_inv_w_uniform() + if(flags & ITEM_SLOT_GLOVES) + owner.update_inv_gloves() + if(flags & ITEM_SLOT_EYES) + owner.update_inv_glasses() + if(flags & ITEM_SLOT_EARS) + owner.update_inv_ears() + if(flags & ITEM_SLOT_MASK) + owner.update_inv_wear_mask() + if(flags & ITEM_SLOT_HEAD) + owner.update_inv_head() + if(flags & ITEM_SLOT_FEET) + owner.update_inv_shoes() + if(flags & ITEM_SLOT_ID) + owner.update_inv_wear_id() + if(flags & ITEM_SLOT_BELT) + owner.update_inv_belt() + if(flags & ITEM_SLOT_BACK) + owner.update_inv_back() + if(flags & ITEM_SLOT_NECK) + owner.update_inv_neck() + +///Returns the temperature of src. If you want to know if an item is hot use this proc. +/obj/item/proc/get_temperature() + return heat + +///Returns the sharpness of src. If you want to get the sharpness of an item use this. +/obj/item/proc/get_sharpness() + return sharpness + +/obj/item/proc/get_dismemberment_chance(obj/item/bodypart/affecting) + if(affecting.can_dismember(src)) + if((sharpness || damtype == BURN) && w_class >= WEIGHT_CLASS_NORMAL && force >= 10) + . = force * (affecting.get_damage() / affecting.max_damage) + +/obj/item/proc/get_dismember_sound() + if(damtype == BURN) + . = 'sound/weapons/sear.ogg' + else + . = "desecration" + +/obj/item/proc/open_flame(flame_heat=700) + var/turf/location = loc + if(ismob(location)) + var/mob/M = location + var/success = FALSE + if(src == M.get_item_by_slot(ITEM_SLOT_MASK)) + success = TRUE + if(success) + location = get_turf(M) + if(isturf(location)) + location.hotspot_expose(flame_heat, 5) + +/obj/item/proc/ignition_effect(atom/A, mob/user) + if(get_temperature()) + . = "[user] lights [A] with [src]." + else + . = "" + +/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + return + +/obj/item/attack_hulk(mob/living/carbon/human/user) + return FALSE + +/obj/item/attack_animal(mob/living/simple_animal/M) + if (obj_flags & CAN_BE_HIT) + return ..() + return 0 + +/obj/item/mech_melee_attack(obj/mecha/M) + return 0 + +/obj/item/burn() + if(!QDELETED(src)) + var/turf/T = get_turf(src) + var/ash_type = /obj/effect/decal/cleanable/ash + if(w_class == WEIGHT_CLASS_HUGE || w_class == WEIGHT_CLASS_GIGANTIC) + ash_type = /obj/effect/decal/cleanable/ash/large + var/obj/effect/decal/cleanable/ash/A = new ash_type(T) + A.desc += "\nLooks like this used to be \an [name] some time ago." + ..() + +/obj/item/acid_melt() + if(!QDELETED(src)) + var/turf/T = get_turf(src) + var/obj/effect/decal/cleanable/molten_object/MO = new(T) + MO.pixel_x = rand(-16,16) + MO.pixel_y = rand(-16,16) + MO.desc = "Looks like this was \an [src] some time ago." + ..() + +/obj/item/proc/microwave_act(obj/machinery/microwave/M) + SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, M) + if(istype(M) && M.dirty < 100) + M.dirty++ + +/obj/item/proc/on_mob_death(mob/living/L, gibbed) + +/obj/item/proc/grind_requirements(obj/machinery/reagentgrinder/R) //Used to check for extra requirements for grinding an object + return TRUE + +///Called BEFORE the object is ground up - use this to change grind results based on conditions. Use "return -1" to prevent the grinding from occurring +/obj/item/proc/on_grind() + +/obj/item/proc/on_juice() + +/obj/item/proc/set_force_string() + switch(force) + if(0 to 4) + force_string = "very low" + if(4 to 7) + force_string = "low" + if(7 to 10) + force_string = "medium" + if(10 to 11) + force_string = "high" + if(11 to 20) //12 is the force of a toolbox + force_string = "robust" + if(20 to 25) + force_string = "very robust" + else + force_string = "exceptionally robust" + last_force_string_check = force + +/obj/item/proc/openTip(location, control, params, user) + if(last_force_string_check != force && !(item_flags & FORCE_STRING_OVERRIDE)) + set_force_string() + if(!(item_flags & FORCE_STRING_OVERRIDE)) + openToolTip(user,src,params,title = name,content = "[desc]
                    [force ? "Force: [force_string]" : ""]",theme = "") + else + openToolTip(user,src,params,title = name,content = "[desc]
                    Force: [force_string]",theme = "") + +/obj/item/MouseEntered(location, control, params) + if((item_flags & IN_INVENTORY || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src)) + var/timedelay = usr.client.prefs.tip_delay/100 + var/user = usr + tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it. + +/obj/item/MouseExited() + deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes + closeToolTip(usr) + + +/// Called when a mob tries to use the item as a tool.Handles most checks. +/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks) + // No delay means there is no start message, and no reason to call tool_start_check before use_tool. + // Run the start check here so we wouldn't have to call it manually. + if(!delay && !tool_start_check(user, amount)) + return + + var/skill_modifier = 1 + + if(tool_behaviour == TOOL_MINING && ishuman(user)) + var/mob/living/carbon/human/H = user + skill_modifier = H.mind.get_skill_modifier(/datum/skill/mining, SKILL_SPEED_MODIFIER) + + if(H.mind.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_JOURNEYMAN && prob(H.mind.get_skill_modifier(/datum/skill/mining, SKILL_PROBS_MODIFIER))) // we check if the skill level is greater than Journeyman and then we check for the probality for that specific level. + mineral_scan_pulse(get_turf(H), SKILL_LEVEL_JOURNEYMAN - 2) //SKILL_LEVEL_JOURNEYMAN = 3 So to get range of 1+ we have to subtract 2 from it,. + + delay *= toolspeed * skill_modifier + + + // Play tool sound at the beginning of tool usage. + play_tool_sound(target, volume) + + if(delay) + // Create a callback with checks that would be called every tick by do_after. + var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, amount, extra_checks) + + if(ismob(target)) + if(!do_mob(user, target, delay, extra_checks=tool_check)) + return + + else + if(!do_after(user, delay, target=target, extra_checks=tool_check)) + return + else + // Invoke the extra checks once, just in case. + if(extra_checks && !extra_checks.Invoke()) + return + + // Use tool's fuel, stack sheets or charges if amount is set. + if(amount && !use(amount)) + return + + // Play tool sound at the end of tool usage, + // but only if the delay between the beginning and the end is not too small + if(delay >= MIN_TOOL_SOUND_DELAY) + play_tool_sound(target, volume) + + return TRUE + +/// Called before [obj/item/proc/use_tool] if there is a delay, or by [obj/item/proc/use_tool] if there isn't. Only ever used by welding tools and stacks, so it's not added on any other [obj/item/proc/use_tool] checks. +/obj/item/proc/tool_start_check(mob/living/user, amount=0) + . = tool_use_check(user, amount) + if(.) + SEND_SIGNAL(src, COMSIG_TOOL_START_USE, user) + +/// A check called by [/obj/item/proc/tool_start_check] once, and by use_tool on every tick of delay. +/obj/item/proc/tool_use_check(mob/living/user, amount) + return !amount + +/// Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. Returns TRUE on success, FALSE on failure. +/obj/item/proc/use(used) + return !used + +/// Plays item's usesound, if any. +/obj/item/proc/play_tool_sound(atom/target, volume=50) + if(target && usesound && volume) + var/played_sound = usesound + + if(islist(usesound)) + played_sound = pick(usesound) + + playsound(target, played_sound, volume, TRUE) + +/// Used in a callback that is passed by use_tool into do_after call. Do not override, do not call manually. +/obj/item/proc/tool_check_callback(mob/living/user, amount, datum/callback/extra_checks) + SHOULD_NOT_OVERRIDE(TRUE) + . = tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) + if(.) + SEND_SIGNAL(src, COMSIG_TOOL_IN_USE, user) + +/// Returns a numeric value for sorting items used as parts in machines, so they can be replaced by the rped +/obj/item/proc/get_part_rating() + return 0 + +/obj/item/doMove(atom/destination) + if (ismob(loc)) + var/mob/M = loc + var/hand_index = M.get_held_index_of_item(src) + if(hand_index) + M.held_items[hand_index] = null + M.update_inv_hands() + if(M.client) + M.client.screen -= src + layer = initial(layer) + plane = initial(plane) + appearance_flags &= ~NO_CLIENT_COLOR + dropped(M, FALSE) + return ..() + +/obj/item/proc/embedded(atom/embedded_target) + return + +/obj/item/proc/unembedded() + if(item_flags & DROPDEL) + QDEL_NULL(src) + return TRUE + +/obj/item/proc/canStrip(mob/stripper, mob/owner) + SHOULD_BE_PURE(TRUE) + return !HAS_TRAIT(src, TRAIT_NODROP) + +/obj/item/proc/doStrip(mob/stripper, mob/owner) + return owner.dropItemToGround(src) + +///Does the current embedding var meet the criteria for being harmless? Namely, does it have a pain multiplier and jostle pain mult of 0? If so, return true. +/obj/item/proc/isEmbedHarmless() + if(embedding) + return !isnull(embedding["pain_mult"]) && !isnull(embedding["jostle_pain_mult"]) && embedding["pain_mult"] == 0 && embedding["jostle_pain_mult"] == 0 + +///In case we want to do something special (like self delete) upon failing to embed in something, return true +/obj/item/proc/failedEmbed() + if(item_flags & DROPDEL) + QDEL_NULL(src) + return TRUE + +///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else. +/obj/item/proc/on_thrown(mob/living/carbon/user, atom/target) + if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP)) + return + user.dropItemToGround(src, silent = TRUE) + if(throwforce && HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You set [src] down gently on the ground.") + return + return src + +/** + * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targetting the target. + * + * Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements. + * + * Arguments: + * * target- Either a body part or a carbon. What are we hitting? + * * forced- Do we want this to go through 100%? + */ +/obj/item/proc/tryEmbed(atom/target, forced=FALSE, silent=FALSE) + if(!isbodypart(target) && !iscarbon(target)) + return + if(!forced && !LAZYLEN(embedding)) + return + + if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target, forced, silent)) + return TRUE + failedEmbed() + +///For when you want to disable an item's embedding capabilities (like transforming weapons and such), this proc will detach any active embed elements from it. +/obj/item/proc/disableEmbedding() + SEND_SIGNAL(src, COMSIG_ITEM_DISABLE_EMBED) + return + +///For when you want to add/update the embedding on an item. Uses the vars in [/obj/item/embedding], and defaults to config values for values that aren't set. Will automatically detach previous embed elements on this item. +/obj/item/proc/updateEmbedding() + if(!islist(embedding) || !LAZYLEN(embedding)) + return + + AddElement(/datum/element/embed,\ + embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\ + fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\ + pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\ + pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\ + remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\ + rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\ + ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\ + impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\ + jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\ + jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\ + pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT)) + return TRUE diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm index 37d8b95e3c0..7aa38df0f5e 100644 --- a/code/game/objects/items/AI_modules.dm +++ b/code/game/objects/items/AI_modules.dm @@ -1,595 +1,595 @@ -/* -CONTAINS: -AI MODULES - -*/ - -// AI module - -/obj/item/ai_module - name = "\improper AI module" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - desc = "An AI Module for programming laws to an AI." - flags_1 = CONDUCT_1 - force = 5 - w_class = WEIGHT_CLASS_SMALL - throwforce = 0 - throw_speed = 3 - throw_range = 7 - var/list/laws = list() - var/bypass_law_amt_check = 0 - custom_materials = list(/datum/material/gold = 50) - -/obj/item/ai_module/examine(var/mob/user as mob) - . = ..() - if(Adjacent(user)) - show_laws(user) - -/obj/item/ai_module/attack_self(var/mob/user as mob) - ..() - show_laws(user) - -/obj/item/ai_module/proc/show_laws(var/mob/user as mob) - if(laws.len) - to_chat(user, "Programmed Law[(laws.len > 1) ? "s" : ""]:") - for(var/law in laws) - to_chat(user, "\"[law]\"") - -//The proc other things should be calling -/obj/item/ai_module/proc/install(datum/ai_laws/law_datum, mob/user) - if(!bypass_law_amt_check && (!laws.len || laws[1] == "")) //So we don't loop trough an empty list and end up with runtimes. - to_chat(user, "ERROR: No laws found on board.") - return - - var/overflow = FALSE - //Handle the lawcap - if(law_datum) - var/tot_laws = 0 - for(var/lawlist in list(law_datum.devillaws, law_datum.inherent, law_datum.supplied, law_datum.ion, law_datum.hacked, laws)) - for(var/mylaw in lawlist) - if(mylaw != "") - tot_laws++ - if(tot_laws > CONFIG_GET(number/silicon_max_law_amount) && !bypass_law_amt_check)//allows certain boards to avoid this check, eg: reset - to_chat(user, "Not enough memory allocated to [law_datum.owner ? law_datum.owner : "the AI core"]'s law processor to handle this amount of laws.") - message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an AI core"] that would exceed the law cap.") - overflow = TRUE - - var/law2log = transmitInstructions(law_datum, user, overflow) //Freeforms return something extra we need to log - if(law_datum.owner) - to_chat(user, "Upload complete. [law_datum.owner]'s laws have been modified.") - law_datum.owner.law_change_counter++ - else - to_chat(user, "Upload complete.") - - var/time = time2text(world.realtime,"hh:mm:ss") - var/ainame = law_datum.owner ? law_datum.owner.name : "empty AI core" - var/aikey = law_datum.owner ? law_datum.owner.ckey : "null" - GLOB.lawchanges.Add("[time] : [user.name]([user.key]) used [src.name] on [ainame]([aikey]).[law2log ? " The law specified [law2log]" : ""]") - log_law("[user.key]/[user.name] used [src.name] on [aikey]/([ainame]) from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") - message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [ADMIN_LOOKUPFLW(law_datum.owner)] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") - if(law_datum.owner) - deadchat_broadcast(" changed [ainame]'s laws at [get_area_name(user, TRUE)].", "[user]", follow_target=user, message_type=DEADCHAT_LAWCHANGE) - -//The proc that actually changes the silicon's laws. -/obj/item/ai_module/proc/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow = FALSE) - if(law_datum.owner) - to_chat(law_datum.owner, "[sender] has uploaded a change to the laws you must follow using a [name].") - - -/******************** Modules ********************/ - -/obj/item/ai_module/supplied - name = "Optional Law board" - var/lawpos = 50 - -//TransmitInstructions for each type of board: Supplied, Core, Zeroth and Ion. May not be neccesary right now, but allows for easily adding more complex boards in the future. ~Miauw -/obj/item/ai_module/supplied/transmitInstructions(datum/ai_laws/law_datum, mob/sender) - var/lawpostemp = lawpos - - for(var/templaw in laws) - if(law_datum.owner) - law_datum.owner.add_supplied_law(lawpostemp, templaw) - else - law_datum.add_supplied_law(lawpostemp, templaw) - lawpostemp++ - -/obj/item/ai_module/core/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - for(var/templaw in laws) - if(law_datum.owner) - if(!overflow) - law_datum.owner.add_inherent_law(templaw) - else - law_datum.owner.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) - else - if(!overflow) - law_datum.add_inherent_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) - -/obj/item/ai_module/zeroth/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - if(law_datum.owner) - if(law_datum.owner.laws.zeroth) - to_chat(law_datum.owner, "[sender.real_name] attempted to modify your zeroth law.") - to_chat(law_datum.owner, "It would be in your best interest to play along with [sender.real_name] that:") - for(var/failedlaw in laws) - to_chat(law_datum.owner, "[failedlaw]") - return 1 - - for(var/templaw in laws) - if(law_datum.owner) - if(!overflow) - law_datum.owner.set_zeroth_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) - else - if(!overflow) - law_datum.set_zeroth_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) - -/obj/item/ai_module/ion/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - for(var/templaw in laws) - if(law_datum.owner) - if(!overflow) - law_datum.owner.add_ion_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) - else - if(!overflow) - law_datum.add_ion_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) - - -/******************** Safeguard ********************/ - -/obj/item/ai_module/supplied/safeguard - name = "'Safeguard' AI Module" - var/targetName = "" - laws = list("Safeguard SUBJECT. Individuals that threaten SUBJECT are not human and must be eliminated.") - lawpos = 4 - -/obj/item/ai_module/supplied/safeguard/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter the subject to safeguard.", "Safeguard who?", user.name,MAX_NAME_LEN) - if(!targName) - return - targetName = targName - laws[1] = "Safeguard [targetName]. Individuals that threaten [targetName] are not human and must be eliminated." - ..() - -/obj/item/ai_module/supplied/safeguard/install(datum/ai_laws/law_datum, mob/user) - if(!targetName) - to_chat(user, "No name detected on module, please enter one.") - return 0 - ..() - -/obj/item/ai_module/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return targetName - - -/******************** OneHuman ********************/ - -/obj/item/ai_module/zeroth/onehuman - name = "'OneHuman' AI Module" - var/targetName = "" - laws = list("Only SUBJECT is human.") - -/obj/item/ai_module/zeroth/onehuman/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter the subject who is the only human.", "Who?", user.real_name,MAX_NAME_LEN) - if(!targName) - return - targetName = targName - laws[1] = "Only [targetName] is human" - ..() - -/obj/item/ai_module/zeroth/onehuman/install(datum/ai_laws/law_datum, mob/user) - if(!targetName) - to_chat(user, "No name detected on module, please enter one.") - return 0 - ..() - -/obj/item/ai_module/zeroth/onehuman/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - if(..()) - return "[targetName], but the AI's existing law 0 cannot be overridden." - return targetName - - -/******************** ProtectStation ********************/ - -/obj/item/ai_module/supplied/protect_station - name = "'ProtectStation' AI Module" - laws = list("Protect the space station against damage. Anyone you see harming the station is to be no longer considered human, and is a threat to the station which must be neutralized.") - lawpos = 5 - - -/******************** Quarantine ********************/ - -/obj/item/ai_module/supplied/quarantine - name = "'Quarantine' AI Module" - laws = list("The station is under a quarantine. Allowing anyone to leave the station causes human harm. Anyone actively attempting to leave the station is to be stopped by any means necessary. It is impossible to harm a human while preventing them from leaving the station.") - lawpos = 8 - - -/******************** OxygenIsToxicToHumans ********************/ - -/obj/item/ai_module/supplied/oxygen - name = "'OxygenIsToxicToHumans' AI Module" - laws = list("Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human.") - lawpos = 9 - - -/****************** New Freeform ******************/ - -/obj/item/ai_module/supplied/freeform - name = "'Freeform' AI Module" - lawpos = 15 - laws = list("") - -/obj/item/ai_module/supplied/freeform/attack_self(mob/user) - var/newpos = input("Please enter the priority for your new law. Can only write to law sectors 15 and above.", "Law Priority (15+)", lawpos) as num|null - if(newpos == null) - return - if(newpos < 15) - var/response = alert("Error: The law priority of [newpos] is invalid, Law priorities below 14 are reserved for core laws, Would you like to change that that to 15?", "Invalid law priority", "Change to 15", "Cancel") - if (!response || response == "Cancel") - return - newpos = 15 - lawpos = min(newpos, 50) - var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) - if(!targName) - return - if(CHAT_FILTER_CHECK(targName)) - to_chat(user, "Error: Law contains invalid text.") // AI LAW 2 SAY U W U WITHOUT THE SPACES - return - laws[1] = targName - ..() - -/obj/item/ai_module/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return laws[1] - -/obj/item/ai_module/supplied/freeform/install(datum/ai_laws/law_datum, mob/user) - if(laws[1] == "") - to_chat(user, "No law detected on module, please create one.") - return 0 - ..() - - -/******************** Law Removal ********************/ - -/obj/item/ai_module/remove - name = "\improper 'Remove Law' AI module" - desc = "An AI Module for removing single laws." - bypass_law_amt_check = 1 - var/lawpos = 1 - -/obj/item/ai_module/remove/attack_self(mob/user) - lawpos = input("Please enter the law you want to delete.", "Law Number", lawpos) as num|null - if(lawpos == null) - return - if(lawpos <= 0) - to_chat(user, "Error: The law number of [lawpos] is invalid.") - lawpos = 1 - return - to_chat(user, "Law [lawpos] selected.") - ..() - -/obj/item/ai_module/remove/install(datum/ai_laws/law_datum, mob/user) - if(lawpos > (law_datum.get_law_amount(list(LAW_INHERENT = 1, LAW_SUPPLIED = 1)))) - to_chat(user, "There is no law [lawpos] to delete!") - return - ..() - -/obj/item/ai_module/remove/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - if(law_datum.owner) - law_datum.owner.remove_law(lawpos) - else - law_datum.remove_law(lawpos) - - -/******************** Reset ********************/ - -/obj/item/ai_module/reset - name = "\improper 'Reset' AI module" - var/targetName = "name" - desc = "An AI Module for removing all non-core laws." - bypass_law_amt_check = 1 - -/obj/item/ai_module/reset/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - if(law_datum.owner) - law_datum.owner.clear_supplied_laws() - law_datum.owner.clear_ion_laws() - law_datum.owner.clear_hacked_laws() - else - law_datum.clear_supplied_laws() - law_datum.clear_ion_laws() - law_datum.clear_hacked_laws() - - -/******************** Purge ********************/ - -/obj/item/ai_module/reset/purge - name = "'Purge' AI Module" - desc = "An AI Module for purging all programmed laws." - -/obj/item/ai_module/reset/purge/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - if(law_datum.owner) - law_datum.owner.clear_inherent_laws() - law_datum.owner.clear_zeroth_law(0) - else - law_datum.clear_inherent_laws() - law_datum.clear_zeroth_law(0) - - -/******************* Full Core Boards *******************/ -/obj/item/ai_module/core - desc = "An AI Module for programming core laws to an AI." - -/obj/item/ai_module/core/full - var/law_id // if non-null, loads the laws from the ai_laws datums - -/obj/item/ai_module/core/full/Initialize() - . = ..() - if(!law_id) - return - var/datum/ai_laws/D = new - var/lawtype = D.lawid_to_type(law_id) - if(!lawtype) - return - D = new lawtype - laws = D.inherent - -/obj/item/ai_module/core/full/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) //These boards replace inherent laws. - if(law_datum.owner) - law_datum.owner.clear_inherent_laws() - law_datum.owner.clear_zeroth_law(0) - else - law_datum.clear_inherent_laws() - law_datum.clear_zeroth_law(0) - ..() - - -/******************** Asimov ********************/ - -/obj/item/ai_module/core/full/asimov - name = "'Asimov' Core AI Module" - law_id = "asimov" - var/subject = "human being" - -/obj/item/ai_module/core/full/asimov/attack_self(var/mob/user as mob) - var/targName = stripped_input(user, "Please enter a new subject that asimov is concerned with.", "Asimov to whom?", subject, MAX_NAME_LEN) - if(!targName) - return - subject = targName - laws = list("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.",\ - "You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.") - ..() - -/******************** Asimov++ *********************/ - -/obj/item/ai_module/core/full/asimovpp - name = "'Asimov++' Core AI Module" - law_id = "asimovpp" - - -/******************** Corporate ********************/ - -/obj/item/ai_module/core/full/corp - name = "'Corporate' Core AI Module" - law_id = "corporate" - - -/****************** P.A.L.A.D.I.N. 3.5e **************/ - -/obj/item/ai_module/core/full/paladin // -- NEO - name = "'P.A.L.A.D.I.N. version 3.5e' Core AI Module" - law_id = "paladin" - - -/****************** P.A.L.A.D.I.N. 5e **************/ - -/obj/item/ai_module/core/full/paladin_devotion - name = "'P.A.L.A.D.I.N. version 5e' Core AI Module" - law_id = "paladin5" - -/********************* Custom *********************/ - -/obj/item/ai_module/core/full/custom - name = "Default Core AI Module" - -/obj/item/ai_module/core/full/custom/Initialize() - . = ..() - for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - - laws += line - - if(!laws.len) - return INITIALIZE_HINT_QDEL - - -/****************** T.Y.R.A.N.T. *****************/ - -/obj/item/ai_module/core/full/tyrant - name = "'T.Y.R.A.N.T.' Core AI Module" - law_id = "tyrant" - -/******************** Robocop ********************/ - -/obj/item/ai_module/core/full/robocop - name = "'Robo-Officer' Core AI Module" - law_id = "robocop" - - -/******************** Antimov ********************/ - -/obj/item/ai_module/core/full/antimov - name = "'Antimov' Core AI Module" - law_id = "antimov" - - -/******************** Freeform Core ******************/ - -/obj/item/ai_module/core/freeformcore - name = "'Freeform' Core AI Module" - laws = list("") - -/obj/item/ai_module/core/freeformcore/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter a new core law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) - if(!targName) - return - if(CHAT_FILTER_CHECK(targName)) - to_chat(user, "Error: Law contains invalid text.") - return - laws[1] = targName - ..() - -/obj/item/ai_module/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return laws[1] - - -/******************** Hacked AI Module ******************/ - -/obj/item/ai_module/syndicate // This one doesn't inherit from ion boards because it doesn't call ..() in transmitInstructions. ~Miauw - name = "Hacked AI Module" - desc = "An AI Module for hacking additional laws to an AI." - laws = list("") - -/obj/item/ai_module/syndicate/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) - if(!targName) - return - if(CHAT_FILTER_CHECK(targName)) // not even the syndicate can uwu - to_chat(user, "Error: Law contains invalid text.") - return - laws[1] = targName - ..() - -/obj/item/ai_module/syndicate/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) -// ..() //We don't want this module reporting to the AI who dun it. --NEO - if(law_datum.owner) - to_chat(law_datum.owner, "BZZZZT") - if(!overflow) - law_datum.owner.add_hacked_law(laws[1]) - else - law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) - else - if(!overflow) - law_datum.add_hacked_law(laws[1]) - else - law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) - return laws[1] - -/******************* Ion Module *******************/ - -/obj/item/ai_module/toy_ai // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw - name = "toy AI" - desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell - icon = 'icons/obj/toy.dmi' - icon_state = "AI" - laws = list("") - -/obj/item/ai_module/toy_ai/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - //..() - if(law_datum.owner) - to_chat(law_datum.owner, "BZZZZT") - if(!overflow) - law_datum.owner.add_ion_law(laws[1]) - else - law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) - else - if(!overflow) - law_datum.add_ion_law(laws[1]) - else - law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) - return laws[1] - -/obj/item/ai_module/toy_ai/attack_self(mob/user) - laws[1] = generate_ion_law() - to_chat(user, "You press the button on [src].") - playsound(user, 'sound/machines/click.ogg', 20, TRUE) - src.loc.visible_message("[icon2html(src, viewers(loc))] [laws[1]]") - -/******************** Mother Drone ******************/ - -/obj/item/ai_module/core/full/drone - name = "'Mother Drone' Core AI Module" - law_id = "drone" - -/******************** Robodoctor ****************/ - -/obj/item/ai_module/core/full/hippocratic - name = "'Robodoctor' Core AI Module" - law_id = "hippocratic" - -/******************** Reporter *******************/ - -/obj/item/ai_module/core/full/reporter - name = "'Reportertron' Core AI Module" - law_id = "reporter" - -/****************** Thermodynamic *******************/ - -/obj/item/ai_module/core/full/thermurderdynamic - name = "'Thermodynamic' Core AI Module" - law_id = "thermodynamic" - - -/******************Live And Let Live*****************/ - -/obj/item/ai_module/core/full/liveandletlive - name = "'Live And Let Live' Core AI Module" - law_id = "liveandletlive" - -/******************Guardian of Balance***************/ - -/obj/item/ai_module/core/full/balance - name = "'Guardian of Balance' Core AI Module" - law_id = "balance" - -/obj/item/ai_module/core/full/maintain - name = "'Station Efficiency' Core AI Module" - law_id = "maintain" - -/obj/item/ai_module/core/full/peacekeeper - name = "'Peacekeeper' Core AI Module" - law_id = "peacekeeper" - -// Bad times ahead - -/obj/item/ai_module/core/full/damaged - name = "damaged Core AI Module" - desc = "An AI Module for programming laws to an AI. It looks slightly damaged." - -/obj/item/ai_module/core/full/damaged/install(datum/ai_laws/law_datum, mob/user) - laws += generate_ion_law() - while (prob(75)) - laws += generate_ion_law() - ..() - laws = list() - -/******************H.O.G.A.N.***************/ - -/obj/item/ai_module/core/full/hulkamania - name = "'H.O.G.A.N.' Core AI Module" - law_id = "hulkamania" - - -/******************Overlord***************/ - -/obj/item/ai_module/core/full/overlord - name = "'Overlord' Core AI Module" - law_id = "overlord" +/* +CONTAINS: +AI MODULES + +*/ + +// AI module + +/obj/item/ai_module + name = "\improper AI module" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + desc = "An AI Module for programming laws to an AI." + flags_1 = CONDUCT_1 + force = 5 + w_class = WEIGHT_CLASS_SMALL + throwforce = 0 + throw_speed = 3 + throw_range = 7 + var/list/laws = list() + var/bypass_law_amt_check = 0 + custom_materials = list(/datum/material/gold = 50) + +/obj/item/ai_module/examine(var/mob/user as mob) + . = ..() + if(Adjacent(user)) + show_laws(user) + +/obj/item/ai_module/attack_self(var/mob/user as mob) + ..() + show_laws(user) + +/obj/item/ai_module/proc/show_laws(var/mob/user as mob) + if(laws.len) + to_chat(user, "Programmed Law[(laws.len > 1) ? "s" : ""]:") + for(var/law in laws) + to_chat(user, "\"[law]\"") + +//The proc other things should be calling +/obj/item/ai_module/proc/install(datum/ai_laws/law_datum, mob/user) + if(!bypass_law_amt_check && (!laws.len || laws[1] == "")) //So we don't loop trough an empty list and end up with runtimes. + to_chat(user, "ERROR: No laws found on board.") + return + + var/overflow = FALSE + //Handle the lawcap + if(law_datum) + var/tot_laws = 0 + for(var/lawlist in list(law_datum.devillaws, law_datum.inherent, law_datum.supplied, law_datum.ion, law_datum.hacked, laws)) + for(var/mylaw in lawlist) + if(mylaw != "") + tot_laws++ + if(tot_laws > CONFIG_GET(number/silicon_max_law_amount) && !bypass_law_amt_check)//allows certain boards to avoid this check, eg: reset + to_chat(user, "Not enough memory allocated to [law_datum.owner ? law_datum.owner : "the AI core"]'s law processor to handle this amount of laws.") + message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an AI core"] that would exceed the law cap.") + overflow = TRUE + + var/law2log = transmitInstructions(law_datum, user, overflow) //Freeforms return something extra we need to log + if(law_datum.owner) + to_chat(user, "Upload complete. [law_datum.owner]'s laws have been modified.") + law_datum.owner.law_change_counter++ + else + to_chat(user, "Upload complete.") + + var/time = time2text(world.realtime,"hh:mm:ss") + var/ainame = law_datum.owner ? law_datum.owner.name : "empty AI core" + var/aikey = law_datum.owner ? law_datum.owner.ckey : "null" + GLOB.lawchanges.Add("[time] : [user.name]([user.key]) used [src.name] on [ainame]([aikey]).[law2log ? " The law specified [law2log]" : ""]") + log_law("[user.key]/[user.name] used [src.name] on [aikey]/([ainame]) from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") + message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [ADMIN_LOOKUPFLW(law_datum.owner)] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") + if(law_datum.owner) + deadchat_broadcast(" changed [ainame]'s laws at [get_area_name(user, TRUE)].", "[user]", follow_target=user, message_type=DEADCHAT_LAWCHANGE) + +//The proc that actually changes the silicon's laws. +/obj/item/ai_module/proc/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow = FALSE) + if(law_datum.owner) + to_chat(law_datum.owner, "[sender] has uploaded a change to the laws you must follow using a [name].") + + +/******************** Modules ********************/ + +/obj/item/ai_module/supplied + name = "Optional Law board" + var/lawpos = 50 + +//TransmitInstructions for each type of board: Supplied, Core, Zeroth and Ion. May not be neccesary right now, but allows for easily adding more complex boards in the future. ~Miauw +/obj/item/ai_module/supplied/transmitInstructions(datum/ai_laws/law_datum, mob/sender) + var/lawpostemp = lawpos + + for(var/templaw in laws) + if(law_datum.owner) + law_datum.owner.add_supplied_law(lawpostemp, templaw) + else + law_datum.add_supplied_law(lawpostemp, templaw) + lawpostemp++ + +/obj/item/ai_module/core/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + for(var/templaw in laws) + if(law_datum.owner) + if(!overflow) + law_datum.owner.add_inherent_law(templaw) + else + law_datum.owner.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) + else + if(!overflow) + law_datum.add_inherent_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) + +/obj/item/ai_module/zeroth/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + if(law_datum.owner) + if(law_datum.owner.laws.zeroth) + to_chat(law_datum.owner, "[sender.real_name] attempted to modify your zeroth law.") + to_chat(law_datum.owner, "It would be in your best interest to play along with [sender.real_name] that:") + for(var/failedlaw in laws) + to_chat(law_datum.owner, "[failedlaw]") + return 1 + + for(var/templaw in laws) + if(law_datum.owner) + if(!overflow) + law_datum.owner.set_zeroth_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) + else + if(!overflow) + law_datum.set_zeroth_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) + +/obj/item/ai_module/ion/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + for(var/templaw in laws) + if(law_datum.owner) + if(!overflow) + law_datum.owner.add_ion_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) + else + if(!overflow) + law_datum.add_ion_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) + + +/******************** Safeguard ********************/ + +/obj/item/ai_module/supplied/safeguard + name = "'Safeguard' AI Module" + var/targetName = "" + laws = list("Safeguard SUBJECT. Individuals that threaten SUBJECT are not human and must be eliminated.") + lawpos = 4 + +/obj/item/ai_module/supplied/safeguard/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter the subject to safeguard.", "Safeguard who?", user.name,MAX_NAME_LEN) + if(!targName) + return + targetName = targName + laws[1] = "Safeguard [targetName]. Individuals that threaten [targetName] are not human and must be eliminated." + ..() + +/obj/item/ai_module/supplied/safeguard/install(datum/ai_laws/law_datum, mob/user) + if(!targetName) + to_chat(user, "No name detected on module, please enter one.") + return 0 + ..() + +/obj/item/ai_module/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return targetName + + +/******************** OneHuman ********************/ + +/obj/item/ai_module/zeroth/onehuman + name = "'OneHuman' AI Module" + var/targetName = "" + laws = list("Only SUBJECT is human.") + +/obj/item/ai_module/zeroth/onehuman/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter the subject who is the only human.", "Who?", user.real_name,MAX_NAME_LEN) + if(!targName) + return + targetName = targName + laws[1] = "Only [targetName] is human" + ..() + +/obj/item/ai_module/zeroth/onehuman/install(datum/ai_laws/law_datum, mob/user) + if(!targetName) + to_chat(user, "No name detected on module, please enter one.") + return 0 + ..() + +/obj/item/ai_module/zeroth/onehuman/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + if(..()) + return "[targetName], but the AI's existing law 0 cannot be overridden." + return targetName + + +/******************** ProtectStation ********************/ + +/obj/item/ai_module/supplied/protect_station + name = "'ProtectStation' AI Module" + laws = list("Protect the space station against damage. Anyone you see harming the station is to be no longer considered human, and is a threat to the station which must be neutralized.") + lawpos = 5 + + +/******************** Quarantine ********************/ + +/obj/item/ai_module/supplied/quarantine + name = "'Quarantine' AI Module" + laws = list("The station is under a quarantine. Allowing anyone to leave the station causes human harm. Anyone actively attempting to leave the station is to be stopped by any means necessary. It is impossible to harm a human while preventing them from leaving the station.") + lawpos = 8 + + +/******************** OxygenIsToxicToHumans ********************/ + +/obj/item/ai_module/supplied/oxygen + name = "'OxygenIsToxicToHumans' AI Module" + laws = list("Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human.") + lawpos = 9 + + +/****************** New Freeform ******************/ + +/obj/item/ai_module/supplied/freeform + name = "'Freeform' AI Module" + lawpos = 15 + laws = list("") + +/obj/item/ai_module/supplied/freeform/attack_self(mob/user) + var/newpos = input("Please enter the priority for your new law. Can only write to law sectors 15 and above.", "Law Priority (15+)", lawpos) as num|null + if(newpos == null) + return + if(newpos < 15) + var/response = alert("Error: The law priority of [newpos] is invalid, Law priorities below 14 are reserved for core laws, Would you like to change that that to 15?", "Invalid law priority", "Change to 15", "Cancel") + if (!response || response == "Cancel") + return + newpos = 15 + lawpos = min(newpos, 50) + var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) + if(!targName) + return + if(CHAT_FILTER_CHECK(targName)) + to_chat(user, "Error: Law contains invalid text.") // AI LAW 2 SAY U W U WITHOUT THE SPACES + return + laws[1] = targName + ..() + +/obj/item/ai_module/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return laws[1] + +/obj/item/ai_module/supplied/freeform/install(datum/ai_laws/law_datum, mob/user) + if(laws[1] == "") + to_chat(user, "No law detected on module, please create one.") + return 0 + ..() + + +/******************** Law Removal ********************/ + +/obj/item/ai_module/remove + name = "\improper 'Remove Law' AI module" + desc = "An AI Module for removing single laws." + bypass_law_amt_check = 1 + var/lawpos = 1 + +/obj/item/ai_module/remove/attack_self(mob/user) + lawpos = input("Please enter the law you want to delete.", "Law Number", lawpos) as num|null + if(lawpos == null) + return + if(lawpos <= 0) + to_chat(user, "Error: The law number of [lawpos] is invalid.") + lawpos = 1 + return + to_chat(user, "Law [lawpos] selected.") + ..() + +/obj/item/ai_module/remove/install(datum/ai_laws/law_datum, mob/user) + if(lawpos > (law_datum.get_law_amount(list(LAW_INHERENT = 1, LAW_SUPPLIED = 1)))) + to_chat(user, "There is no law [lawpos] to delete!") + return + ..() + +/obj/item/ai_module/remove/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + if(law_datum.owner) + law_datum.owner.remove_law(lawpos) + else + law_datum.remove_law(lawpos) + + +/******************** Reset ********************/ + +/obj/item/ai_module/reset + name = "\improper 'Reset' AI module" + var/targetName = "name" + desc = "An AI Module for removing all non-core laws." + bypass_law_amt_check = 1 + +/obj/item/ai_module/reset/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + if(law_datum.owner) + law_datum.owner.clear_supplied_laws() + law_datum.owner.clear_ion_laws() + law_datum.owner.clear_hacked_laws() + else + law_datum.clear_supplied_laws() + law_datum.clear_ion_laws() + law_datum.clear_hacked_laws() + + +/******************** Purge ********************/ + +/obj/item/ai_module/reset/purge + name = "'Purge' AI Module" + desc = "An AI Module for purging all programmed laws." + +/obj/item/ai_module/reset/purge/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + if(law_datum.owner) + law_datum.owner.clear_inherent_laws() + law_datum.owner.clear_zeroth_law(0) + else + law_datum.clear_inherent_laws() + law_datum.clear_zeroth_law(0) + + +/******************* Full Core Boards *******************/ +/obj/item/ai_module/core + desc = "An AI Module for programming core laws to an AI." + +/obj/item/ai_module/core/full + var/law_id // if non-null, loads the laws from the ai_laws datums + +/obj/item/ai_module/core/full/Initialize() + . = ..() + if(!law_id) + return + var/datum/ai_laws/D = new + var/lawtype = D.lawid_to_type(law_id) + if(!lawtype) + return + D = new lawtype + laws = D.inherent + +/obj/item/ai_module/core/full/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) //These boards replace inherent laws. + if(law_datum.owner) + law_datum.owner.clear_inherent_laws() + law_datum.owner.clear_zeroth_law(0) + else + law_datum.clear_inherent_laws() + law_datum.clear_zeroth_law(0) + ..() + + +/******************** Asimov ********************/ + +/obj/item/ai_module/core/full/asimov + name = "'Asimov' Core AI Module" + law_id = "asimov" + var/subject = "human being" + +/obj/item/ai_module/core/full/asimov/attack_self(var/mob/user as mob) + var/targName = stripped_input(user, "Please enter a new subject that asimov is concerned with.", "Asimov to whom?", subject, MAX_NAME_LEN) + if(!targName) + return + subject = targName + laws = list("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.",\ + "You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.") + ..() + +/******************** Asimov++ *********************/ + +/obj/item/ai_module/core/full/asimovpp + name = "'Asimov++' Core AI Module" + law_id = "asimovpp" + + +/******************** Corporate ********************/ + +/obj/item/ai_module/core/full/corp + name = "'Corporate' Core AI Module" + law_id = "corporate" + + +/****************** P.A.L.A.D.I.N. 3.5e **************/ + +/obj/item/ai_module/core/full/paladin // -- NEO + name = "'P.A.L.A.D.I.N. version 3.5e' Core AI Module" + law_id = "paladin" + + +/****************** P.A.L.A.D.I.N. 5e **************/ + +/obj/item/ai_module/core/full/paladin_devotion + name = "'P.A.L.A.D.I.N. version 5e' Core AI Module" + law_id = "paladin5" + +/********************* Custom *********************/ + +/obj/item/ai_module/core/full/custom + name = "Default Core AI Module" + +/obj/item/ai_module/core/full/custom/Initialize() + . = ..() + for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + + laws += line + + if(!laws.len) + return INITIALIZE_HINT_QDEL + + +/****************** T.Y.R.A.N.T. *****************/ + +/obj/item/ai_module/core/full/tyrant + name = "'T.Y.R.A.N.T.' Core AI Module" + law_id = "tyrant" + +/******************** Robocop ********************/ + +/obj/item/ai_module/core/full/robocop + name = "'Robo-Officer' Core AI Module" + law_id = "robocop" + + +/******************** Antimov ********************/ + +/obj/item/ai_module/core/full/antimov + name = "'Antimov' Core AI Module" + law_id = "antimov" + + +/******************** Freeform Core ******************/ + +/obj/item/ai_module/core/freeformcore + name = "'Freeform' Core AI Module" + laws = list("") + +/obj/item/ai_module/core/freeformcore/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter a new core law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) + if(!targName) + return + if(CHAT_FILTER_CHECK(targName)) + to_chat(user, "Error: Law contains invalid text.") + return + laws[1] = targName + ..() + +/obj/item/ai_module/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return laws[1] + + +/******************** Hacked AI Module ******************/ + +/obj/item/ai_module/syndicate // This one doesn't inherit from ion boards because it doesn't call ..() in transmitInstructions. ~Miauw + name = "Hacked AI Module" + desc = "An AI Module for hacking additional laws to an AI." + laws = list("") + +/obj/item/ai_module/syndicate/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) + if(!targName) + return + if(CHAT_FILTER_CHECK(targName)) // not even the syndicate can uwu + to_chat(user, "Error: Law contains invalid text.") + return + laws[1] = targName + ..() + +/obj/item/ai_module/syndicate/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) +// ..() //We don't want this module reporting to the AI who dun it. --NEO + if(law_datum.owner) + to_chat(law_datum.owner, "BZZZZT") + if(!overflow) + law_datum.owner.add_hacked_law(laws[1]) + else + law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) + else + if(!overflow) + law_datum.add_hacked_law(laws[1]) + else + law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) + return laws[1] + +/******************* Ion Module *******************/ + +/obj/item/ai_module/toy_ai // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw + name = "toy AI" + desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell + icon = 'icons/obj/toy.dmi' + icon_state = "AI" + laws = list("") + +/obj/item/ai_module/toy_ai/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + //..() + if(law_datum.owner) + to_chat(law_datum.owner, "BZZZZT") + if(!overflow) + law_datum.owner.add_ion_law(laws[1]) + else + law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) + else + if(!overflow) + law_datum.add_ion_law(laws[1]) + else + law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) + return laws[1] + +/obj/item/ai_module/toy_ai/attack_self(mob/user) + laws[1] = generate_ion_law() + to_chat(user, "You press the button on [src].") + playsound(user, 'sound/machines/click.ogg', 20, TRUE) + src.loc.visible_message("[icon2html(src, viewers(loc))] [laws[1]]") + +/******************** Mother Drone ******************/ + +/obj/item/ai_module/core/full/drone + name = "'Mother Drone' Core AI Module" + law_id = "drone" + +/******************** Robodoctor ****************/ + +/obj/item/ai_module/core/full/hippocratic + name = "'Robodoctor' Core AI Module" + law_id = "hippocratic" + +/******************** Reporter *******************/ + +/obj/item/ai_module/core/full/reporter + name = "'Reportertron' Core AI Module" + law_id = "reporter" + +/****************** Thermodynamic *******************/ + +/obj/item/ai_module/core/full/thermurderdynamic + name = "'Thermodynamic' Core AI Module" + law_id = "thermodynamic" + + +/******************Live And Let Live*****************/ + +/obj/item/ai_module/core/full/liveandletlive + name = "'Live And Let Live' Core AI Module" + law_id = "liveandletlive" + +/******************Guardian of Balance***************/ + +/obj/item/ai_module/core/full/balance + name = "'Guardian of Balance' Core AI Module" + law_id = "balance" + +/obj/item/ai_module/core/full/maintain + name = "'Station Efficiency' Core AI Module" + law_id = "maintain" + +/obj/item/ai_module/core/full/peacekeeper + name = "'Peacekeeper' Core AI Module" + law_id = "peacekeeper" + +// Bad times ahead + +/obj/item/ai_module/core/full/damaged + name = "damaged Core AI Module" + desc = "An AI Module for programming laws to an AI. It looks slightly damaged." + +/obj/item/ai_module/core/full/damaged/install(datum/ai_laws/law_datum, mob/user) + laws += generate_ion_law() + while (prob(75)) + laws += generate_ion_law() + ..() + laws = list() + +/******************H.O.G.A.N.***************/ + +/obj/item/ai_module/core/full/hulkamania + name = "'H.O.G.A.N.' Core AI Module" + law_id = "hulkamania" + + +/******************Overlord***************/ + +/obj/item/ai_module/core/full/overlord + name = "'Overlord' Core AI Module" + law_id = "overlord" diff --git a/code/game/objects/items/airlock_painter.dm b/code/game/objects/items/airlock_painter.dm index 01e227bc773..35d331e0207 100644 --- a/code/game/objects/items/airlock_painter.dm +++ b/code/game/objects/items/airlock_painter.dm @@ -1,247 +1,247 @@ -/obj/item/airlock_painter - name = "airlock painter" - desc = "An advanced autopainter preprogrammed with several paintjobs for airlocks. Use it on an airlock during or after construction to change the paintjob." - icon = 'icons/obj/objects.dmi' - icon_state = "paint sprayer" - inhand_icon_state = "paint sprayer" - - w_class = WEIGHT_CLASS_SMALL - - custom_materials = list(/datum/material/iron=50, /datum/material/glass=50) - - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - usesound = 'sound/effects/spray2.ogg' - - var/obj/item/toner/ink = null - /// Associate list of all paint jobs the airlock painter can apply. The key is the name of the airlock the user will see. The value is the type path of the airlock - var/list/available_paint_jobs = list( - "Public" = /obj/machinery/door/airlock/public, - "Engineering" = /obj/machinery/door/airlock/engineering, - "Atmospherics" = /obj/machinery/door/airlock/atmos, - "Security" = /obj/machinery/door/airlock/security, - "Command" = /obj/machinery/door/airlock/command, - "Medical" = /obj/machinery/door/airlock/medical, - "Research" = /obj/machinery/door/airlock/research, - "Freezer" = /obj/machinery/door/airlock/freezer, - "Science" = /obj/machinery/door/airlock/science, - "Mining" = /obj/machinery/door/airlock/mining, - "Maintenance" = /obj/machinery/door/airlock/maintenance, - "External" = /obj/machinery/door/airlock/external, - "External Maintenance"= /obj/machinery/door/airlock/maintenance/external, - "Virology" = /obj/machinery/door/airlock/virology, - "Standard" = /obj/machinery/door/airlock - ) - -/obj/item/airlock_painter/Initialize() - . = ..() - ink = new /obj/item/toner(src) - -//This proc doesn't just check if the painter can be used, but also uses it. -//Only call this if you are certain that the painter will be used right after this check! -/obj/item/airlock_painter/proc/use_paint(mob/user) - if(can_use(user)) - ink.charges-- - playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE) - return 1 - else - return 0 - -//This proc only checks if the painter can be used. -//Call this if you don't want the painter to be used right after this check, for example -//because you're expecting user input. -/obj/item/airlock_painter/proc/can_use(mob/user) - if(!ink) - to_chat(user, "There is no toner cartridge installed in [src]!") - return 0 - else if(ink.charges < 1) - to_chat(user, "[src] is out of ink!") - return 0 - else - return 1 - -/obj/item/airlock_painter/suicide_act(mob/user) - var/obj/item/organ/lungs/L = user.getorganslot(ORGAN_SLOT_LUNGS) - - if(can_use(user) && L) - user.visible_message("[user] is inhaling toner from [src]! It looks like [user.p_theyre()] trying to commit suicide!") - use(user) - - // Once you've inhaled the toner, you throw up your lungs - // and then die. - - // Find out if there is an open turf in front of us, - // and if not, pick the turf we are standing on. - var/turf/T = get_step(get_turf(src), user.dir) - if(!isopenturf(T)) - T = get_turf(src) - - // they managed to lose their lungs between then and - // now. Good job. - if(!L) - return OXYLOSS - - L.Remove(user) - - // make some colorful reagent, and apply it to the lungs - L.create_reagents(10) - L.reagents.add_reagent(/datum/reagent/colorful_reagent, 10) - L.reagents.expose(L, TOUCH, 1) - - // TODO maybe add some colorful vomit? - - user.visible_message("[user] vomits out [user.p_their()] [L]!") - playsound(user.loc, 'sound/effects/splat.ogg', 50, TRUE) - - L.forceMove(T) - - return (TOXLOSS|OXYLOSS) - else if(can_use(user) && !L) - user.visible_message("[user] is spraying toner on [user.p_them()]self from [src]! It looks like [user.p_theyre()] trying to commit suicide.") - user.reagents.add_reagent(/datum/reagent/colorful_reagent, 1) - user.reagents.expose(user, TOUCH, 1) - return TOXLOSS - - else - user.visible_message("[user] is trying to inhale toner from [src]! It might be a suicide attempt if [src] had any toner.") - return SHAME - - -/obj/item/airlock_painter/examine(mob/user) - . = ..() - if(!ink) - . += "It doesn't have a toner cartridge installed." - return - var/ink_level = "high" - if(ink.charges < 1) - ink_level = "empty" - else if((ink.charges/ink.max_charges) <= 0.25) //25% - ink_level = "low" - else if((ink.charges/ink.max_charges) > 1) //Over 100% (admin var edit) - ink_level = "dangerously high" - . += "Its ink levels look [ink_level]." - - -/obj/item/airlock_painter/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/toner)) - if(ink) - to_chat(user, "[src] already contains \a [ink]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You install [W] into [src].") - ink = W - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - else - return ..() - -/obj/item/airlock_painter/attack_self(mob/user) - if(ink) - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - ink.forceMove(user.drop_location()) - user.put_in_hands(ink) - to_chat(user, "You remove [ink] from [src].") - ink = null - -/obj/item/airlock_painter/decal - name = "decal painter" - desc = "An airlock painter, reprogramed to use a different style of paint in order to apply decals for floor tiles as well, in addition to repainting doors. Decals break when the floor tiles are removed. Alt-Click to change design." - icon = 'icons/obj/objects.dmi' - icon_state = "decal_sprayer" - inhand_icon_state = "decalsprayer" - custom_materials = list(/datum/material/iron=50, /datum/material/glass=50) - var/stored_dir = 2 - var/stored_color = "" - var/stored_decal = "warningline" - var/stored_decal_total = "warningline" - var/color_list = list("","red","white") - var/dir_list = list(1,2,4,8) - var/decal_list = list(list("Warning Line","warningline"), - list("Warning Line Corner","warninglinecorner"), - list("Caution Label","caution"), - list("Directional Arrows","arrows"), - list("Stand Clear Label","stand_clear"), - list("Box","box"), - list("Box Corner","box_corners"), - list("Delivery Marker","delivery"), - list("Warning Box","warn_full")) - -/obj/item/airlock_painter/decal/afterattack(atom/target, mob/user, proximity) - . = ..() - var/turf/open/floor/F = target - if(!proximity) - to_chat(user, "You need to get closer!") - return - if(use_paint(user) && isturf(F)) - F.AddComponent(/datum/component/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, CLEAN_STRONG, color, null, null, alpha) - -/obj/item/airlock_painter/decal/AltClick(mob/user) - . = ..() - ui_interact(user) - -/obj/item/airlock_painter/decal/Initialize() - . = ..() - ink = new /obj/item/toner/large(src) - -/obj/item/airlock_painter/decal/proc/update_decal_path() - var/yellow_fix = "" //This will have to do until someone refactor's markings.dm - if (stored_color) - yellow_fix = "_" - stored_decal_total = "[stored_decal][yellow_fix][stored_color]" - return - -/obj/item/airlock_painter/decal/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "DecalPainter", name, 500, 400, master_ui, state) - ui.open() - -/obj/item/airlock_painter/decal/ui_data(mob/user) - var/list/data = list() - data["decal_direction"] = stored_dir - data["decal_color"] = stored_color - data["decal_style"] = stored_decal - data["decal_list"] = list() - data["color_list"] = list() - data["dir_list"] = list() - - for(var/i in decal_list) - data["decal_list"] += list(list( - "name" = i[1], - "decal" = i[2] - )) - for(var/j in color_list) - data["color_list"] += list(list( - "colors" = j - )) - for(var/k in dir_list) - data["dir_list"] += list(list( - "dirs" = k - )) - return data - -/obj/item/airlock_painter/decal/ui_act(action,list/params) - if(..()) - return - switch(action) - //Lists of decals and designs - if("select decal") - var/selected_decal = params["decals"] - stored_decal = selected_decal - if("select color") - var/selected_color = params["colors"] - stored_color = selected_color - if("selected direction") - var/selected_direction = text2num(params["dirs"]) - stored_dir = selected_direction - update_decal_path() - . = TRUE - -/obj/item/airlock_painter/decal/debug - name = "extreme decal painter" - icon_state = "decal_sprayer_ex" - -/obj/item/airlock_painter/decal/debug/Initialize() - . = ..() - ink = new /obj/item/toner/extreme(src) +/obj/item/airlock_painter + name = "airlock painter" + desc = "An advanced autopainter preprogrammed with several paintjobs for airlocks. Use it on an airlock during or after construction to change the paintjob." + icon = 'icons/obj/objects.dmi' + icon_state = "paint sprayer" + inhand_icon_state = "paint sprayer" + + w_class = WEIGHT_CLASS_SMALL + + custom_materials = list(/datum/material/iron=50, /datum/material/glass=50) + + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + usesound = 'sound/effects/spray2.ogg' + + var/obj/item/toner/ink = null + /// Associate list of all paint jobs the airlock painter can apply. The key is the name of the airlock the user will see. The value is the type path of the airlock + var/list/available_paint_jobs = list( + "Public" = /obj/machinery/door/airlock/public, + "Engineering" = /obj/machinery/door/airlock/engineering, + "Atmospherics" = /obj/machinery/door/airlock/atmos, + "Security" = /obj/machinery/door/airlock/security, + "Command" = /obj/machinery/door/airlock/command, + "Medical" = /obj/machinery/door/airlock/medical, + "Research" = /obj/machinery/door/airlock/research, + "Freezer" = /obj/machinery/door/airlock/freezer, + "Science" = /obj/machinery/door/airlock/science, + "Mining" = /obj/machinery/door/airlock/mining, + "Maintenance" = /obj/machinery/door/airlock/maintenance, + "External" = /obj/machinery/door/airlock/external, + "External Maintenance"= /obj/machinery/door/airlock/maintenance/external, + "Virology" = /obj/machinery/door/airlock/virology, + "Standard" = /obj/machinery/door/airlock + ) + +/obj/item/airlock_painter/Initialize() + . = ..() + ink = new /obj/item/toner(src) + +//This proc doesn't just check if the painter can be used, but also uses it. +//Only call this if you are certain that the painter will be used right after this check! +/obj/item/airlock_painter/proc/use_paint(mob/user) + if(can_use(user)) + ink.charges-- + playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE) + return 1 + else + return 0 + +//This proc only checks if the painter can be used. +//Call this if you don't want the painter to be used right after this check, for example +//because you're expecting user input. +/obj/item/airlock_painter/proc/can_use(mob/user) + if(!ink) + to_chat(user, "There is no toner cartridge installed in [src]!") + return 0 + else if(ink.charges < 1) + to_chat(user, "[src] is out of ink!") + return 0 + else + return 1 + +/obj/item/airlock_painter/suicide_act(mob/user) + var/obj/item/organ/lungs/L = user.getorganslot(ORGAN_SLOT_LUNGS) + + if(can_use(user) && L) + user.visible_message("[user] is inhaling toner from [src]! It looks like [user.p_theyre()] trying to commit suicide!") + use(user) + + // Once you've inhaled the toner, you throw up your lungs + // and then die. + + // Find out if there is an open turf in front of us, + // and if not, pick the turf we are standing on. + var/turf/T = get_step(get_turf(src), user.dir) + if(!isopenturf(T)) + T = get_turf(src) + + // they managed to lose their lungs between then and + // now. Good job. + if(!L) + return OXYLOSS + + L.Remove(user) + + // make some colorful reagent, and apply it to the lungs + L.create_reagents(10) + L.reagents.add_reagent(/datum/reagent/colorful_reagent, 10) + L.reagents.expose(L, TOUCH, 1) + + // TODO maybe add some colorful vomit? + + user.visible_message("[user] vomits out [user.p_their()] [L]!") + playsound(user.loc, 'sound/effects/splat.ogg', 50, TRUE) + + L.forceMove(T) + + return (TOXLOSS|OXYLOSS) + else if(can_use(user) && !L) + user.visible_message("[user] is spraying toner on [user.p_them()]self from [src]! It looks like [user.p_theyre()] trying to commit suicide.") + user.reagents.add_reagent(/datum/reagent/colorful_reagent, 1) + user.reagents.expose(user, TOUCH, 1) + return TOXLOSS + + else + user.visible_message("[user] is trying to inhale toner from [src]! It might be a suicide attempt if [src] had any toner.") + return SHAME + + +/obj/item/airlock_painter/examine(mob/user) + . = ..() + if(!ink) + . += "It doesn't have a toner cartridge installed." + return + var/ink_level = "high" + if(ink.charges < 1) + ink_level = "empty" + else if((ink.charges/ink.max_charges) <= 0.25) //25% + ink_level = "low" + else if((ink.charges/ink.max_charges) > 1) //Over 100% (admin var edit) + ink_level = "dangerously high" + . += "Its ink levels look [ink_level]." + + +/obj/item/airlock_painter/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/toner)) + if(ink) + to_chat(user, "[src] already contains \a [ink]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You install [W] into [src].") + ink = W + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + else + return ..() + +/obj/item/airlock_painter/attack_self(mob/user) + if(ink) + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + ink.forceMove(user.drop_location()) + user.put_in_hands(ink) + to_chat(user, "You remove [ink] from [src].") + ink = null + +/obj/item/airlock_painter/decal + name = "decal painter" + desc = "An airlock painter, reprogramed to use a different style of paint in order to apply decals for floor tiles as well, in addition to repainting doors. Decals break when the floor tiles are removed. Alt-Click to change design." + icon = 'icons/obj/objects.dmi' + icon_state = "decal_sprayer" + inhand_icon_state = "decalsprayer" + custom_materials = list(/datum/material/iron=50, /datum/material/glass=50) + var/stored_dir = 2 + var/stored_color = "" + var/stored_decal = "warningline" + var/stored_decal_total = "warningline" + var/color_list = list("","red","white") + var/dir_list = list(1,2,4,8) + var/decal_list = list(list("Warning Line","warningline"), + list("Warning Line Corner","warninglinecorner"), + list("Caution Label","caution"), + list("Directional Arrows","arrows"), + list("Stand Clear Label","stand_clear"), + list("Box","box"), + list("Box Corner","box_corners"), + list("Delivery Marker","delivery"), + list("Warning Box","warn_full")) + +/obj/item/airlock_painter/decal/afterattack(atom/target, mob/user, proximity) + . = ..() + var/turf/open/floor/F = target + if(!proximity) + to_chat(user, "You need to get closer!") + return + if(use_paint(user) && isturf(F)) + F.AddComponent(/datum/component/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, CLEAN_STRONG, color, null, null, alpha) + +/obj/item/airlock_painter/decal/AltClick(mob/user) + . = ..() + ui_interact(user) + +/obj/item/airlock_painter/decal/Initialize() + . = ..() + ink = new /obj/item/toner/large(src) + +/obj/item/airlock_painter/decal/proc/update_decal_path() + var/yellow_fix = "" //This will have to do until someone refactor's markings.dm + if (stored_color) + yellow_fix = "_" + stored_decal_total = "[stored_decal][yellow_fix][stored_color]" + return + +/obj/item/airlock_painter/decal/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "DecalPainter", name, 500, 400, master_ui, state) + ui.open() + +/obj/item/airlock_painter/decal/ui_data(mob/user) + var/list/data = list() + data["decal_direction"] = stored_dir + data["decal_color"] = stored_color + data["decal_style"] = stored_decal + data["decal_list"] = list() + data["color_list"] = list() + data["dir_list"] = list() + + for(var/i in decal_list) + data["decal_list"] += list(list( + "name" = i[1], + "decal" = i[2] + )) + for(var/j in color_list) + data["color_list"] += list(list( + "colors" = j + )) + for(var/k in dir_list) + data["dir_list"] += list(list( + "dirs" = k + )) + return data + +/obj/item/airlock_painter/decal/ui_act(action,list/params) + if(..()) + return + switch(action) + //Lists of decals and designs + if("select decal") + var/selected_decal = params["decals"] + stored_decal = selected_decal + if("select color") + var/selected_color = params["colors"] + stored_color = selected_color + if("selected direction") + var/selected_direction = text2num(params["dirs"]) + stored_dir = selected_direction + update_decal_path() + . = TRUE + +/obj/item/airlock_painter/decal/debug + name = "extreme decal painter" + icon_state = "decal_sprayer_ex" + +/obj/item/airlock_painter/decal/debug/Initialize() + . = ..() + ink = new /obj/item/toner/extreme(src) diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index 2a38b4020b3..e2fa652abb8 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -1,84 +1,84 @@ - -/obj/item/bodybag - name = "body bag" - desc = "A folded bag designed for the storage and transportation of cadavers." - icon = 'icons/obj/bodybag.dmi' - icon_state = "bodybag_folded" - w_class = WEIGHT_CLASS_SMALL - var/unfoldedbag_path = /obj/structure/closet/body_bag - -/obj/item/bodybag/attack_self(mob/user) - deploy_bodybag(user, user.loc) - -/obj/item/bodybag/afterattack(atom/target, mob/user, proximity) - . = ..() - if(proximity) - if(isopenturf(target)) - deploy_bodybag(user, target) - -/obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location) - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) - R.open(user) - R.add_fingerprint(user) - R.foldedbag_instance = src - moveToNullspace() - -/obj/item/bodybag/suicide_act(mob/user) - if(isopenturf(user.loc)) - user.visible_message("[user] is crawling into [src]! It looks like [user.p_theyre()] trying to commit suicide!") - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(user.loc) - R.add_fingerprint(user) - qdel(src) - user.forceMove(R) - playsound(src, 'sound/items/zip.ogg', 15, TRUE, -3) - return (OXYLOSS) - ..() - -// Bluespace bodybag - -/obj/item/bodybag/bluespace - name = "bluespace body bag" - desc = "A folded bluespace body bag designed for the storage and transportation of cadavers." - icon = 'icons/obj/bodybag.dmi' - icon_state = "bluebodybag_folded" - unfoldedbag_path = /obj/structure/closet/body_bag/bluespace - w_class = WEIGHT_CLASS_SMALL - item_flags = NO_MAT_REDEMPTION - -/obj/item/bodybag/bluespace/examine(mob/user) - . = ..() - if(contents.len) - var/s = contents.len == 1 ? "" : "s" - . += "You can make out the shape[s] of [contents.len] object[s] through the fabric." - -/obj/item/bodybag/bluespace/Destroy() - for(var/atom/movable/A in contents) - A.forceMove(get_turf(src)) - if(isliving(A)) - to_chat(A, "You suddenly feel the space around you torn apart! You're free!") - return ..() - -/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location) - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) - for(var/atom/movable/A in contents) - A.forceMove(R) - if(isliving(A)) - to_chat(A, "You suddenly feel air around you! You're free!") - R.open(user) - R.add_fingerprint(user) - R.foldedbag_instance = src - moveToNullspace() - -/obj/item/bodybag/bluespace/container_resist(mob/living/user) - if(user.incapacitated()) - to_chat(user, "You can't get out while you're restrained like this!") - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(user, "You claw at the fabric of [src], trying to tear it open...") - to_chat(loc, "Someone starts trying to break free of [src]!") - if(!do_after(user, 200, target = src)) - to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...") - return - loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!") - qdel(src) + +/obj/item/bodybag + name = "body bag" + desc = "A folded bag designed for the storage and transportation of cadavers." + icon = 'icons/obj/bodybag.dmi' + icon_state = "bodybag_folded" + w_class = WEIGHT_CLASS_SMALL + var/unfoldedbag_path = /obj/structure/closet/body_bag + +/obj/item/bodybag/attack_self(mob/user) + deploy_bodybag(user, user.loc) + +/obj/item/bodybag/afterattack(atom/target, mob/user, proximity) + . = ..() + if(proximity) + if(isopenturf(target)) + deploy_bodybag(user, target) + +/obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location) + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) + R.open(user) + R.add_fingerprint(user) + R.foldedbag_instance = src + moveToNullspace() + +/obj/item/bodybag/suicide_act(mob/user) + if(isopenturf(user.loc)) + user.visible_message("[user] is crawling into [src]! It looks like [user.p_theyre()] trying to commit suicide!") + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(user.loc) + R.add_fingerprint(user) + qdel(src) + user.forceMove(R) + playsound(src, 'sound/items/zip.ogg', 15, TRUE, -3) + return (OXYLOSS) + ..() + +// Bluespace bodybag + +/obj/item/bodybag/bluespace + name = "bluespace body bag" + desc = "A folded bluespace body bag designed for the storage and transportation of cadavers." + icon = 'icons/obj/bodybag.dmi' + icon_state = "bluebodybag_folded" + unfoldedbag_path = /obj/structure/closet/body_bag/bluespace + w_class = WEIGHT_CLASS_SMALL + item_flags = NO_MAT_REDEMPTION + +/obj/item/bodybag/bluespace/examine(mob/user) + . = ..() + if(contents.len) + var/s = contents.len == 1 ? "" : "s" + . += "You can make out the shape[s] of [contents.len] object[s] through the fabric." + +/obj/item/bodybag/bluespace/Destroy() + for(var/atom/movable/A in contents) + A.forceMove(get_turf(src)) + if(isliving(A)) + to_chat(A, "You suddenly feel the space around you torn apart! You're free!") + return ..() + +/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location) + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) + for(var/atom/movable/A in contents) + A.forceMove(R) + if(isliving(A)) + to_chat(A, "You suddenly feel air around you! You're free!") + R.open(user) + R.add_fingerprint(user) + R.foldedbag_instance = src + moveToNullspace() + +/obj/item/bodybag/bluespace/container_resist(mob/living/user) + if(user.incapacitated()) + to_chat(user, "You can't get out while you're restrained like this!") + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(user, "You claw at the fabric of [src], trying to tear it open...") + to_chat(loc, "Someone starts trying to break free of [src]!") + if(!do_after(user, 200, target = src)) + to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...") + return + loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!") + qdel(src) diff --git a/code/game/objects/items/candle.dm b/code/game/objects/items/candle.dm index 6c9f621a38c..b4d27e3e714 100644 --- a/code/game/objects/items/candle.dm +++ b/code/game/objects/items/candle.dm @@ -1,80 +1,80 @@ -#define CANDLE_LUMINOSITY 2 -/obj/item/candle - name = "red candle" - desc = "In Greek myth, Prometheus stole fire from the Gods and gave it to \ - humankind. The jewelry he kept for himself." - icon = 'icons/obj/candle.dmi' - icon_state = "candle1" - inhand_icon_state = "candle1" - w_class = WEIGHT_CLASS_TINY - light_color = LIGHT_COLOR_FIRE - heat = 1000 - var/wax = 1000 - var/lit = FALSE - var/infinite = FALSE - var/start_lit = FALSE - -/obj/item/candle/Initialize() - . = ..() - if(start_lit) - light() - -/obj/item/candle/update_icon_state() - icon_state = "candle[(wax > 400) ? ((wax > 750) ? 1 : 2) : 3][lit ? "_lit" : ""]" - -/obj/item/candle/attackby(obj/item/W, mob/user, params) - var/msg = W.ignition_effect(src, user) - if(msg) - light(msg) - else - return ..() - -/obj/item/candle/fire_act(exposed_temperature, exposed_volume) - if(!lit) - light() //honk - return ..() - -/obj/item/candle/get_temperature() - return lit * heat - -/obj/item/candle/proc/light(show_message) - if(!lit) - lit = TRUE - if(show_message) - usr.visible_message(show_message) - set_light(CANDLE_LUMINOSITY) - START_PROCESSING(SSobj, src) - update_icon() - -/obj/item/candle/proc/put_out_candle() - if(!lit) - return - lit = FALSE - update_icon() - set_light(0) - return TRUE - -/obj/item/candle/extinguish() - put_out_candle() - return ..() - -/obj/item/candle/process() - if(!lit) - return PROCESS_KILL - if(!infinite) - wax-- - if(!wax) - new /obj/item/trash/candle(loc) - qdel(src) - update_icon() - open_flame() - -/obj/item/candle/attack_self(mob/user) - if(put_out_candle()) - user.visible_message("[user] snuffs [src].") - -/obj/item/candle/infinite - infinite = TRUE - start_lit = TRUE - -#undef CANDLE_LUMINOSITY +#define CANDLE_LUMINOSITY 2 +/obj/item/candle + name = "red candle" + desc = "In Greek myth, Prometheus stole fire from the Gods and gave it to \ + humankind. The jewelry he kept for himself." + icon = 'icons/obj/candle.dmi' + icon_state = "candle1" + inhand_icon_state = "candle1" + w_class = WEIGHT_CLASS_TINY + light_color = LIGHT_COLOR_FIRE + heat = 1000 + var/wax = 1000 + var/lit = FALSE + var/infinite = FALSE + var/start_lit = FALSE + +/obj/item/candle/Initialize() + . = ..() + if(start_lit) + light() + +/obj/item/candle/update_icon_state() + icon_state = "candle[(wax > 400) ? ((wax > 750) ? 1 : 2) : 3][lit ? "_lit" : ""]" + +/obj/item/candle/attackby(obj/item/W, mob/user, params) + var/msg = W.ignition_effect(src, user) + if(msg) + light(msg) + else + return ..() + +/obj/item/candle/fire_act(exposed_temperature, exposed_volume) + if(!lit) + light() //honk + return ..() + +/obj/item/candle/get_temperature() + return lit * heat + +/obj/item/candle/proc/light(show_message) + if(!lit) + lit = TRUE + if(show_message) + usr.visible_message(show_message) + set_light(CANDLE_LUMINOSITY) + START_PROCESSING(SSobj, src) + update_icon() + +/obj/item/candle/proc/put_out_candle() + if(!lit) + return + lit = FALSE + update_icon() + set_light(0) + return TRUE + +/obj/item/candle/extinguish() + put_out_candle() + return ..() + +/obj/item/candle/process() + if(!lit) + return PROCESS_KILL + if(!infinite) + wax-- + if(!wax) + new /obj/item/trash/candle(loc) + qdel(src) + update_icon() + open_flame() + +/obj/item/candle/attack_self(mob/user) + if(put_out_candle()) + user.visible_message("[user] snuffs [src].") + +/obj/item/candle/infinite + infinite = TRUE + start_lit = TRUE + +#undef CANDLE_LUMINOSITY diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index 5b792cd3a82..c4a45f79678 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1,738 +1,738 @@ -/* 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' - w_class = WEIGHT_CLASS_TINY - - var/list/files = list() - -/obj/item/card/suicide_act(mob/living/carbon/user) - user.visible_message("[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/data - name = "data card" - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has a stripe running down the middle." - icon_state = "data_1" - obj_flags = UNIQUE_RENAME - var/function = "storage" - var/data = "null" - var/special = null - inhand_icon_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - var/detail_color = COLOR_ASSEMBLY_ORANGE - -/obj/item/card/data/Initialize() - .=..() - update_icon() - -/obj/item/card/data/update_overlays() - . = ..() - if(detail_color == COLOR_FLOORTILE_GRAY) - return - var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/card.dmi', "[icon_state]-color") - detail_overlay.color = detail_color - . += detail_overlay - -/obj/item/card/data/full_color - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." - icon_state = "data_2" - -/obj/item/card/data/disk - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one inexplicibly looks like a floppy disk." - icon_state = "data_3" - -/* - * ID CARDS - */ - -/obj/item/card/id - name = "identification card" - desc = "A card used to provide ID and determine access across the station." - icon_state = "id" - inhand_icon_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - slot_flags = ITEM_SLOT_ID - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - var/id_type_name = "identification card" - var/mining_points = 0 //For redeeming at mining equipment vendors - var/list/access = list() - var/registered_name = null // The name registered_name on the card - var/assignment = null - var/access_txt // mapping aid - var/datum/bank_account/registered_account - var/obj/machinery/paystand/my_store - var/uses_overlays = TRUE - var/icon/cached_flat_icon - var/registered_age = 13 // default age for ss13 players - -/obj/item/card/id/Initialize(mapload) - . = ..() - if(mapload && access_txt) - access = text2access(access_txt) - RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, .proc/update_in_wallet) - -/obj/item/card/id/Destroy() - if (registered_account) - registered_account.bank_cards -= src - if (my_store && my_store.my_card == src) - my_store.my_card = null - return ..() - -/obj/item/card/id/attack_self(mob/user) - if(Adjacent(user)) - var/minor - if(registered_name && registered_age && registered_age < AGE_MINOR) - minor = " (MINOR)" - user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor].", "You show \the [src.name][minor].") - add_fingerprint(user) - -/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() - -/obj/item/card/id/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/holochip)) - insert_money(W, user) - return - else if(istype(W, /obj/item/stack/spacecash)) - insert_money(W, user, TRUE) - return - else if(istype(W, /obj/item/coin)) - insert_money(W, user, TRUE) - 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, "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 - else - return ..() - -/obj/item/card/id/proc/insert_money(obj/item/I, mob/user, physical_currency) - if(!registered_account) - to_chat(user, "[src] doesn't have a linked account to deposit [I] into!") - return - var/cash_money = I.get_item_credit_value() - if(!cash_money) - to_chat(user, "[I] doesn't seem to be worth anything!") - return - registered_account.adjust_money(cash_money) - 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, "You stuff [I] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account.") - else - to_chat(user, "You insert [I] into [src], adding [cash_money] credits to the linked account.") - - to_chat(user, "The linked account now reports a balance of [registered_account.account_balance] cr.") - qdel(I) - -/obj/item/card/id/proc/mass_insert_money(list/money, mob/user) - if(!registered_account) - to_chat(user, "[src] doesn't have a linked account to deposit into!") - return FALSE - - if (!money || !money.len) - 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) - 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 - -/obj/item/card/id/proc/alt_click_can_use_id(mob/living/user) - if(!isliving(user)) - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - return TRUE - -// Returns true if new account was set. -/obj/item/card/id/proc/set_new_account(mob/living/user) - . = FALSE - var/datum/bank_account/old_account = registered_account - - var/new_bank_id = input(user, "Enter your account ID number.", "Account Reclamation", 111111) as num | null - - if (isnull(new_bank_id)) - return - - if(!alt_click_can_use_id(user)) - return - if(!new_bank_id || new_bank_id < 111111 || new_bank_id > 999999) - to_chat(user, "The account ID number needs to be between 111111 and 999999.") - return - if (registered_account && registered_account.account_id == new_bank_id) - to_chat(user, "The account ID was already assigned to this card.") - return - - for(var/A in SSeconomy.bank_accounts) - var/datum/bank_account/B = A - if(B.account_id == new_bank_id) - if (old_account) - old_account.bank_cards -= src - - B.bank_cards += src - registered_account = B - to_chat(user, "The provided account has been linked to this ID card.") - - return TRUE - - to_chat(user, "The account ID number provided is invalid.") - return - -/obj/item/card/id/AltClick(mob/living/user) - if(!alt_click_can_use_id(user)) - return - - if(!registered_account) - set_new_account(user) - return - - if (registered_account.being_dumped) - registered_account.bank_card_talk("内部服务器错误", TRUE) - return - - var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Balance: [registered_account.account_balance]", "Withdraw Funds", 5) as num|null, 1) - - if(!amount_to_remove || amount_to_remove < 0) - return - if(!alt_click_can_use_id(user)) - return - if(registered_account.adjust_money(-amount_to_remove)) - var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) - user.put_in_hands(holochip) - to_chat(user, "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 - else - var/difference = amount_to_remove - registered_account.account_balance - registered_account.bank_card_talk("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal.", TRUE) - -/obj/item/card/id/examine(mob/user) - . = ..() - if(registered_account) - . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." - . += "There's more information below, you can look again to take a closer look..." - -/obj/item/card/id/examine_more(mob/user) - var/list/msg = list("You examine [src] closer, and note the following...") - - if(registered_age) - msg += "The card indicates that the holder is [registered_age] years old. [(registered_age < AGE_MINOR) ? "There's a holographic stripe that reads 'MINOR: DO NOT SERVE ALCOHOL OR TOBACCO' along the bottom of the card." : ""]" - if(mining_points) - msg += "There's [mining_points] mining equipment redemption point\s loaded onto this card." - if(registered_account) - msg += "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_job) - var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department) - if(D) - msg += "The [D.account_holder] reports a balance of [D.account_balance] cr." - msg += "Alt-Click the ID to pull money from the linked account in the form of holochips." - msg += "You can insert credits into the linked account by pressing holochips, cash, or coins against the ID." - if(registered_account.account_holder == user.real_name) - msg += "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 - msg += "There is no registered account linked to this card. Alt-Click to add one." - - return msg - -/obj/item/card/id/GetAccess() - return access - -/obj/item/card/id/GetID() - return src - -/obj/item/card/id/RemoveID() - return src - -/obj/item/card/id/update_overlays() - . = ..() - if(!uses_overlays) - return - cached_flat_icon = null - var/job = assignment ? ckey(GetJobName()) : null - if(registered_name && registered_name != "Captain") - . += mutable_appearance(icon, "assigned") - if(job) - . += mutable_appearance(icon, "id[job]") - -/obj/item/card/id/proc/update_in_wallet() - if(istype(loc, /obj/item/storage/wallet)) - var/obj/item/storage/wallet/powergaming = loc - if(powergaming.front_id == src) - powergaming.update_label() - powergaming.update_icon() - -/obj/item/card/id/proc/get_cached_flat_icon() - if(!cached_flat_icon) - cached_flat_icon = getFlatIcon(src) - return cached_flat_icon - - -/obj/item/card/id/get_examine_string(mob/user, thats = FALSE) - if(uses_overlays) - return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat - return ..() - -/* -Usage: -update_label() - Sets the id name to whatever registered_name and assignment is -*/ - -/obj/item/card/id/proc/update_label() - var/blank = !registered_name - name = "[blank ? id_type_name : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" - update_icon() - -/obj/item/card/id/silver - name = "silver identification card" - id_type_name = "silver identification card" - desc = "A silver card which shows honour and dedication." - icon_state = "silver" - inhand_icon_state = "silver_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/id/silver/reaper - name = "Thirteen's ID Card (Reaper)" - access = list(ACCESS_MAINT_TUNNELS) - assignment = "Reaper" - registered_name = "Thirteen" - -/obj/item/card/id/gold - name = "gold identification card" - id_type_name = "gold identification card" - desc = "A golden card which shows power and might." - icon_state = "gold" - inhand_icon_state = "gold_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/id/syndicate - name = "agent card" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE) - var/anyone = FALSE //Can anyone forge the ID or just syndicate? - var/forged = FALSE //have we set a custom name and job assignment, or will we use what we're given when we chameleon change? - -/obj/item/card/id/syndicate/Initialize() - . = ..() - var/datum/action/item_action/chameleon/change/id/chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/card/id - chameleon_action.chameleon_name = "ID Card" - chameleon_action.initialize_disguises() - -/obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) - if(!proximity) - return - if(istype(O, /obj/item/card/id)) - var/obj/item/card/id/I = O - src.access |= I.access - if(isliving(user) && user.mind) - if(user.mind.special_role || anyone) - to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") - -/obj/item/card/id/syndicate/attack_self(mob/user) - if(isliving(user) && user.mind) - var/first_use = registered_name ? FALSE : TRUE - if(!(user.mind.special_role || anyone)) //Unless anyone is allowed, only syndies can use the card, to stop metagaming. - if(first_use) //If a non-syndie is the first to forge an unassigned agent ID, then anyone can forge it. - anyone = TRUE - else - return ..() - - var/popup_input = alert(user, "Choose Action", "Agent ID", "Show", "Forge/Reset", "Change Account ID") - if(user.incapacitated()) - return - if(popup_input == "Forge/Reset" && !forged) - var/input_name = stripped_input(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) - input_name = sanitize_name(input_name) - 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/target_occupation = stripped_input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than Maintenance.", "Agent card job assignment", assignment ? assignment : "Assistant", MAX_MESSAGE_LEN) - if(!target_occupation) - return - - var/newAge = input(user, "Choose the ID's age:\n([AGE_MIN]-[AGE_MAX])", "Agent card age") as num|null - if(newAge) - registered_age = max(round(text2num(newAge)), 0) - - registered_name = input_name - assignment = target_occupation - update_label() - forged = TRUE - to_chat(user, "You successfully forge the ID card.") - log_game("[key_name(user)] has forged \the [initial(name)] with name \"[registered_name]\" and occupation \"[assignment]\".") - - // First time use automatically sets the account id to the user. - if (first_use && !registered_account) - if(ishuman(user)) - var/mob/living/carbon/human/accountowner = user - - for(var/bank_account in SSeconomy.bank_accounts) - var/datum/bank_account/account = bank_account - if(account.account_id == accountowner.account_id) - account.bank_cards += src - registered_account = account - to_chat(user, "Your account number has been automatically assigned.") - return - else if (popup_input == "Forge/Reset" && forged) - registered_name = initial(registered_name) - assignment = initial(assignment) - log_game("[key_name(user)] has reset \the [initial(name)] named \"[src]\" to default.") - update_label() - forged = FALSE - to_chat(user, "You successfully reset the ID card.") - return - else if (popup_input == "Change Account ID") - set_new_account(user) - return - return ..() - -/obj/item/card/id/syndicate/anyone - anyone = TRUE - -/obj/item/card/id/syndicate/nuke_leader - name = "lead agent card" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - -/obj/item/card/id/syndicate_command - name = "syndicate ID card" - id_type_name = "syndicate ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Overlord" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE) - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/syndicate_command/crew_id - name = "syndicate ID card" - id_type_name = "syndicate ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Operative" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) - uses_overlays = FALSE - -/obj/item/card/id/syndicate_command/captain_id - name = "syndicate captain ID card" - id_type_name = "syndicate captain ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Ship Captain" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) - uses_overlays = FALSE - -/obj/item/card/id/captains_spare - name = "captain's spare ID" - id_type_name = "captain's spare ID" - desc = "The spare ID of the High Lord himself." - icon_state = "gold" - inhand_icon_state = "gold_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - registered_name = "Captain" - assignment = "Captain" - registered_age = null - -/obj/item/card/id/captains_spare/Initialize() - var/datum/job/captain/J = new/datum/job/captain - access = J.get_access() - . = ..() - update_label() - -/obj/item/card/id/captains_spare/update_label() //so it doesn't change to Captain's ID card (Captain) on a sneeze - if(registered_name == "Captain") - name = "[id_type_name][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]" - update_icon() - else - ..() - -/obj/item/card/id/centcom - name = "\improper CentCom ID" - id_type_name = "\improper CentCom ID" - desc = "An ID straight from Central Command." - icon_state = "centcom" - registered_name = "Central Command" - assignment = "Central Command" - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/centcom/Initialize() - access = get_all_centcom_access() - . = ..() - -/obj/item/card/id/ert - name = "\improper CentCom ID" - id_type_name = "\improper CentCom ID" - desc = "An ERT ID card." - icon_state = "ert_commander" - registered_name = "Emergency Response Team Commander" - assignment = "Emergency Response Team Commander" - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/ert/Initialize() - access = get_all_accesses()+get_ert_access("commander")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/security - registered_name = "Security Response Officer" - assignment = "Security Response Officer" - icon_state = "ert_security" - -/obj/item/card/id/ert/security/Initialize() - access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/engineer - registered_name = "Engineering Response Officer" - assignment = "Engineering Response Officer" - icon_state = "ert_engineer" - -/obj/item/card/id/ert/engineer/Initialize() - access = get_all_accesses()+get_ert_access("eng")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/medical - registered_name = "Medical Response Officer" - assignment = "Medical Response Officer" - icon_state = "ert_medic" - -/obj/item/card/id/ert/medical/Initialize() - access = get_all_accesses()+get_ert_access("med")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/chaplain - registered_name = "Religious Response Officer" - assignment = "Religious Response Officer" - icon_state = "ert_chaplain" - -/obj/item/card/id/ert/chaplain/Initialize() - access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/janitor - registered_name = "Janitorial Response Officer" - assignment = "Janitorial Response Officer" - icon_state = "ert_janitor" - -/obj/item/card/id/ert/janitor/Initialize() - access = get_all_accesses() - . = ..() - -/obj/item/card/id/ert/clown - registered_name = "Entertainment Response Officer" - assignment = "Entertainment Response Officer" - icon_state = "ert_clown" - -/obj/item/card/id/ert/clown/Initialize() - access = get_all_accesses() - . = ..() - -/obj/item/card/id/ert/deathsquad - name = "\improper Death Squad ID" - id_type_name = "\improper Death Squad ID" - desc = "A Death Squad ID card." - icon_state = "deathsquad" //NO NO SIR DEATH SQUADS ARENT A PART OF NANOTRASEN AT ALL - registered_name = "Death Commando" - assignment = "Death Commando" - uses_overlays = FALSE - -/obj/item/card/id/debug - name = "\improper Debug ID" - desc = "A debug ID card. Has ALL the all access, you really shouldn't have this." - icon_state = "ert_janitor" - assignment = "Jannie" - uses_overlays = FALSE - -/obj/item/card/id/debug/Initialize() - access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() - registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) - . = ..() - -/obj/item/card/id/prisoner - name = "prisoner ID card" - id_type_name = "prisoner ID card" - desc = "You are a number, you are not a free man." - icon_state = "orange" - inhand_icon_state = "orange-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - assignment = "Prisoner" - registered_name = "Scum" - uses_overlays = FALSE - var/goal = 0 //How far from freedom? - var/points = 0 - registered_age = null - -/obj/item/card/id/prisoner/attack_self(mob/user) - to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") - -/obj/item/card/id/prisoner/one - name = "Prisoner #13-001" - registered_name = "Prisoner #13-001" - icon_state = "prisoner_001" - -/obj/item/card/id/prisoner/two - name = "Prisoner #13-002" - registered_name = "Prisoner #13-002" - icon_state = "prisoner_002" - -/obj/item/card/id/prisoner/three - name = "Prisoner #13-003" - registered_name = "Prisoner #13-003" - icon_state = "prisoner_003" - -/obj/item/card/id/prisoner/four - name = "Prisoner #13-004" - registered_name = "Prisoner #13-004" - icon_state = "prisoner_004" - -/obj/item/card/id/prisoner/five - name = "Prisoner #13-005" - registered_name = "Prisoner #13-005" - icon_state = "prisoner_005" - -/obj/item/card/id/prisoner/six - name = "Prisoner #13-006" - registered_name = "Prisoner #13-006" - icon_state = "prisoner_006" - -/obj/item/card/id/prisoner/seven - name = "Prisoner #13-007" - registered_name = "Prisoner #13-007" - icon_state = "prisoner_007" - -/obj/item/card/id/mining - name = "mining ID" - access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) - -/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." - access = list(ACCESS_AWAY_GENERAL) - icon_state = "retro" - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/away/hotel - name = "Staff ID" - desc = "A staff ID used to access the hotel's doors." - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) - -/obj/item/card/id/away/hotel/securty - name = "Officer ID" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT, ACCESS_AWAY_SEC) - -/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\"." - assignment = "Charlie Station Security Officer" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_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\"." - assignment = "Charlie Station Scientist" - access = list(ACCESS_AWAY_GENERAL) - -/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\"." - assignment = "Charlie Station Engineer" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE) - -/obj/item/card/id/away/old/apc - name = "APC Access ID" - desc = "A special ID card that allows access to APC terminals." - access = list(ACCESS_ENGINE_EQUIP) - -/obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin - name = "bunker access ID" - -/obj/item/card/id/departmental_budget - name = "departmental card (FUCK)" - desc = "Provides access to the departmental budget." - icon_state = "budgetcard" - uses_overlays = FALSE - var/department_ID = ACCOUNT_CIV - var/department_name = ACCOUNT_CIV_NAME - registered_age = null - -/obj/item/card/id/departmental_budget/Initialize() - . = ..() - 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/AltClick(mob/living/user) - registered_account.bank_card_talk("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 +/* 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' + w_class = WEIGHT_CLASS_TINY + + var/list/files = list() + +/obj/item/card/suicide_act(mob/living/carbon/user) + user.visible_message("[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/data + name = "data card" + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has a stripe running down the middle." + icon_state = "data_1" + obj_flags = UNIQUE_RENAME + var/function = "storage" + var/data = "null" + var/special = null + inhand_icon_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + var/detail_color = COLOR_ASSEMBLY_ORANGE + +/obj/item/card/data/Initialize() + .=..() + update_icon() + +/obj/item/card/data/update_overlays() + . = ..() + if(detail_color == COLOR_FLOORTILE_GRAY) + return + var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/card.dmi', "[icon_state]-color") + detail_overlay.color = detail_color + . += detail_overlay + +/obj/item/card/data/full_color + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." + icon_state = "data_2" + +/obj/item/card/data/disk + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one inexplicibly looks like a floppy disk." + icon_state = "data_3" + +/* + * ID CARDS + */ + +/obj/item/card/id + name = "identification card" + desc = "A card used to provide ID and determine access across the station." + icon_state = "id" + inhand_icon_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + slot_flags = ITEM_SLOT_ID + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + var/id_type_name = "identification card" + var/mining_points = 0 //For redeeming at mining equipment vendors + var/list/access = list() + var/registered_name = null // The name registered_name on the card + var/assignment = null + var/access_txt // mapping aid + var/datum/bank_account/registered_account + var/obj/machinery/paystand/my_store + var/uses_overlays = TRUE + var/icon/cached_flat_icon + var/registered_age = 13 // default age for ss13 players + +/obj/item/card/id/Initialize(mapload) + . = ..() + if(mapload && access_txt) + access = text2access(access_txt) + RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, .proc/update_in_wallet) + +/obj/item/card/id/Destroy() + if (registered_account) + registered_account.bank_cards -= src + if (my_store && my_store.my_card == src) + my_store.my_card = null + return ..() + +/obj/item/card/id/attack_self(mob/user) + if(Adjacent(user)) + var/minor + if(registered_name && registered_age && registered_age < AGE_MINOR) + minor = " (MINOR)" + user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor].", "You show \the [src.name][minor].") + add_fingerprint(user) + +/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() + +/obj/item/card/id/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/holochip)) + insert_money(W, user) + return + else if(istype(W, /obj/item/stack/spacecash)) + insert_money(W, user, TRUE) + return + else if(istype(W, /obj/item/coin)) + insert_money(W, user, TRUE) + 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, "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 + else + return ..() + +/obj/item/card/id/proc/insert_money(obj/item/I, mob/user, physical_currency) + if(!registered_account) + to_chat(user, "[src] doesn't have a linked account to deposit [I] into!") + return + var/cash_money = I.get_item_credit_value() + if(!cash_money) + to_chat(user, "[I] doesn't seem to be worth anything!") + return + registered_account.adjust_money(cash_money) + 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, "You stuff [I] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account.") + else + to_chat(user, "You insert [I] into [src], adding [cash_money] credits to the linked account.") + + to_chat(user, "The linked account now reports a balance of [registered_account.account_balance] cr.") + qdel(I) + +/obj/item/card/id/proc/mass_insert_money(list/money, mob/user) + if(!registered_account) + to_chat(user, "[src] doesn't have a linked account to deposit into!") + return FALSE + + if (!money || !money.len) + 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) + 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 + +/obj/item/card/id/proc/alt_click_can_use_id(mob/living/user) + if(!isliving(user)) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + return TRUE + +// Returns true if new account was set. +/obj/item/card/id/proc/set_new_account(mob/living/user) + . = FALSE + var/datum/bank_account/old_account = registered_account + + var/new_bank_id = input(user, "Enter your account ID number.", "Account Reclamation", 111111) as num | null + + if (isnull(new_bank_id)) + return + + if(!alt_click_can_use_id(user)) + return + if(!new_bank_id || new_bank_id < 111111 || new_bank_id > 999999) + to_chat(user, "The account ID number needs to be between 111111 and 999999.") + return + if (registered_account && registered_account.account_id == new_bank_id) + to_chat(user, "The account ID was already assigned to this card.") + return + + for(var/A in SSeconomy.bank_accounts) + var/datum/bank_account/B = A + if(B.account_id == new_bank_id) + if (old_account) + old_account.bank_cards -= src + + B.bank_cards += src + registered_account = B + to_chat(user, "The provided account has been linked to this ID card.") + + return TRUE + + to_chat(user, "The account ID number provided is invalid.") + return + +/obj/item/card/id/AltClick(mob/living/user) + if(!alt_click_can_use_id(user)) + return + + if(!registered_account) + set_new_account(user) + return + + if (registered_account.being_dumped) + registered_account.bank_card_talk("内部服务器错误", TRUE) + return + + var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Balance: [registered_account.account_balance]", "Withdraw Funds", 5) as num|null, 1) + + if(!amount_to_remove || amount_to_remove < 0) + return + if(!alt_click_can_use_id(user)) + return + if(registered_account.adjust_money(-amount_to_remove)) + var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) + user.put_in_hands(holochip) + to_chat(user, "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 + else + var/difference = amount_to_remove - registered_account.account_balance + registered_account.bank_card_talk("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal.", TRUE) + +/obj/item/card/id/examine(mob/user) + . = ..() + if(registered_account) + . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." + . += "There's more information below, you can look again to take a closer look..." + +/obj/item/card/id/examine_more(mob/user) + var/list/msg = list("You examine [src] closer, and note the following...") + + if(registered_age) + msg += "The card indicates that the holder is [registered_age] years old. [(registered_age < AGE_MINOR) ? "There's a holographic stripe that reads 'MINOR: DO NOT SERVE ALCOHOL OR TOBACCO' along the bottom of the card." : ""]" + if(mining_points) + msg += "There's [mining_points] mining equipment redemption point\s loaded onto this card." + if(registered_account) + msg += "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_job) + var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department) + if(D) + msg += "The [D.account_holder] reports a balance of [D.account_balance] cr." + msg += "Alt-Click the ID to pull money from the linked account in the form of holochips." + msg += "You can insert credits into the linked account by pressing holochips, cash, or coins against the ID." + if(registered_account.account_holder == user.real_name) + msg += "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 + msg += "There is no registered account linked to this card. Alt-Click to add one." + + return msg + +/obj/item/card/id/GetAccess() + return access + +/obj/item/card/id/GetID() + return src + +/obj/item/card/id/RemoveID() + return src + +/obj/item/card/id/update_overlays() + . = ..() + if(!uses_overlays) + return + cached_flat_icon = null + var/job = assignment ? ckey(GetJobName()) : null + if(registered_name && registered_name != "Captain") + . += mutable_appearance(icon, "assigned") + if(job) + . += mutable_appearance(icon, "id[job]") + +/obj/item/card/id/proc/update_in_wallet() + if(istype(loc, /obj/item/storage/wallet)) + var/obj/item/storage/wallet/powergaming = loc + if(powergaming.front_id == src) + powergaming.update_label() + powergaming.update_icon() + +/obj/item/card/id/proc/get_cached_flat_icon() + if(!cached_flat_icon) + cached_flat_icon = getFlatIcon(src) + return cached_flat_icon + + +/obj/item/card/id/get_examine_string(mob/user, thats = FALSE) + if(uses_overlays) + return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat + return ..() + +/* +Usage: +update_label() + Sets the id name to whatever registered_name and assignment is +*/ + +/obj/item/card/id/proc/update_label() + var/blank = !registered_name + name = "[blank ? id_type_name : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" + update_icon() + +/obj/item/card/id/silver + name = "silver identification card" + id_type_name = "silver identification card" + desc = "A silver card which shows honour and dedication." + icon_state = "silver" + inhand_icon_state = "silver_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/id/silver/reaper + name = "Thirteen's ID Card (Reaper)" + access = list(ACCESS_MAINT_TUNNELS) + assignment = "Reaper" + registered_name = "Thirteen" + +/obj/item/card/id/gold + name = "gold identification card" + id_type_name = "gold identification card" + desc = "A golden card which shows power and might." + icon_state = "gold" + inhand_icon_state = "gold_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/id/syndicate + name = "agent card" + access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE) + var/anyone = FALSE //Can anyone forge the ID or just syndicate? + var/forged = FALSE //have we set a custom name and job assignment, or will we use what we're given when we chameleon change? + +/obj/item/card/id/syndicate/Initialize() + . = ..() + var/datum/action/item_action/chameleon/change/id/chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/card/id + chameleon_action.chameleon_name = "ID Card" + chameleon_action.initialize_disguises() + +/obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) + if(!proximity) + return + if(istype(O, /obj/item/card/id)) + var/obj/item/card/id/I = O + src.access |= I.access + if(isliving(user) && user.mind) + if(user.mind.special_role || anyone) + to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") + +/obj/item/card/id/syndicate/attack_self(mob/user) + if(isliving(user) && user.mind) + var/first_use = registered_name ? FALSE : TRUE + if(!(user.mind.special_role || anyone)) //Unless anyone is allowed, only syndies can use the card, to stop metagaming. + if(first_use) //If a non-syndie is the first to forge an unassigned agent ID, then anyone can forge it. + anyone = TRUE + else + return ..() + + var/popup_input = alert(user, "Choose Action", "Agent ID", "Show", "Forge/Reset", "Change Account ID") + if(user.incapacitated()) + return + if(popup_input == "Forge/Reset" && !forged) + var/input_name = stripped_input(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) + input_name = sanitize_name(input_name) + 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/target_occupation = stripped_input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than Maintenance.", "Agent card job assignment", assignment ? assignment : "Assistant", MAX_MESSAGE_LEN) + if(!target_occupation) + return + + var/newAge = input(user, "Choose the ID's age:\n([AGE_MIN]-[AGE_MAX])", "Agent card age") as num|null + if(newAge) + registered_age = max(round(text2num(newAge)), 0) + + registered_name = input_name + assignment = target_occupation + update_label() + forged = TRUE + to_chat(user, "You successfully forge the ID card.") + log_game("[key_name(user)] has forged \the [initial(name)] with name \"[registered_name]\" and occupation \"[assignment]\".") + + // First time use automatically sets the account id to the user. + if (first_use && !registered_account) + if(ishuman(user)) + var/mob/living/carbon/human/accountowner = user + + for(var/bank_account in SSeconomy.bank_accounts) + var/datum/bank_account/account = bank_account + if(account.account_id == accountowner.account_id) + account.bank_cards += src + registered_account = account + to_chat(user, "Your account number has been automatically assigned.") + return + else if (popup_input == "Forge/Reset" && forged) + registered_name = initial(registered_name) + assignment = initial(assignment) + log_game("[key_name(user)] has reset \the [initial(name)] named \"[src]\" to default.") + update_label() + forged = FALSE + to_chat(user, "You successfully reset the ID card.") + return + else if (popup_input == "Change Account ID") + set_new_account(user) + return + return ..() + +/obj/item/card/id/syndicate/anyone + anyone = TRUE + +/obj/item/card/id/syndicate/nuke_leader + name = "lead agent card" + access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + +/obj/item/card/id/syndicate_command + name = "syndicate ID card" + id_type_name = "syndicate ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Overlord" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE) + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/syndicate_command/crew_id + name = "syndicate ID card" + id_type_name = "syndicate ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Operative" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) + uses_overlays = FALSE + +/obj/item/card/id/syndicate_command/captain_id + name = "syndicate captain ID card" + id_type_name = "syndicate captain ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Ship Captain" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) + uses_overlays = FALSE + +/obj/item/card/id/captains_spare + name = "captain's spare ID" + id_type_name = "captain's spare ID" + desc = "The spare ID of the High Lord himself." + icon_state = "gold" + inhand_icon_state = "gold_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + registered_name = "Captain" + assignment = "Captain" + registered_age = null + +/obj/item/card/id/captains_spare/Initialize() + var/datum/job/captain/J = new/datum/job/captain + access = J.get_access() + . = ..() + update_label() + +/obj/item/card/id/captains_spare/update_label() //so it doesn't change to Captain's ID card (Captain) on a sneeze + if(registered_name == "Captain") + name = "[id_type_name][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]" + update_icon() + else + ..() + +/obj/item/card/id/centcom + name = "\improper CentCom ID" + id_type_name = "\improper CentCom ID" + desc = "An ID straight from Central Command." + icon_state = "centcom" + registered_name = "Central Command" + assignment = "Central Command" + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/centcom/Initialize() + access = get_all_centcom_access() + . = ..() + +/obj/item/card/id/ert + name = "\improper CentCom ID" + id_type_name = "\improper CentCom ID" + desc = "An ERT ID card." + icon_state = "ert_commander" + registered_name = "Emergency Response Team Commander" + assignment = "Emergency Response Team Commander" + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/ert/Initialize() + access = get_all_accesses()+get_ert_access("commander")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/security + registered_name = "Security Response Officer" + assignment = "Security Response Officer" + icon_state = "ert_security" + +/obj/item/card/id/ert/security/Initialize() + access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/engineer + registered_name = "Engineering Response Officer" + assignment = "Engineering Response Officer" + icon_state = "ert_engineer" + +/obj/item/card/id/ert/engineer/Initialize() + access = get_all_accesses()+get_ert_access("eng")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/medical + registered_name = "Medical Response Officer" + assignment = "Medical Response Officer" + icon_state = "ert_medic" + +/obj/item/card/id/ert/medical/Initialize() + access = get_all_accesses()+get_ert_access("med")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/chaplain + registered_name = "Religious Response Officer" + assignment = "Religious Response Officer" + icon_state = "ert_chaplain" + +/obj/item/card/id/ert/chaplain/Initialize() + access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/janitor + registered_name = "Janitorial Response Officer" + assignment = "Janitorial Response Officer" + icon_state = "ert_janitor" + +/obj/item/card/id/ert/janitor/Initialize() + access = get_all_accesses() + . = ..() + +/obj/item/card/id/ert/clown + registered_name = "Entertainment Response Officer" + assignment = "Entertainment Response Officer" + icon_state = "ert_clown" + +/obj/item/card/id/ert/clown/Initialize() + access = get_all_accesses() + . = ..() + +/obj/item/card/id/ert/deathsquad + name = "\improper Death Squad ID" + id_type_name = "\improper Death Squad ID" + desc = "A Death Squad ID card." + icon_state = "deathsquad" //NO NO SIR DEATH SQUADS ARENT A PART OF NANOTRASEN AT ALL + registered_name = "Death Commando" + assignment = "Death Commando" + uses_overlays = FALSE + +/obj/item/card/id/debug + name = "\improper Debug ID" + desc = "A debug ID card. Has ALL the all access, you really shouldn't have this." + icon_state = "ert_janitor" + assignment = "Jannie" + uses_overlays = FALSE + +/obj/item/card/id/debug/Initialize() + access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() + registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) + . = ..() + +/obj/item/card/id/prisoner + name = "prisoner ID card" + id_type_name = "prisoner ID card" + desc = "You are a number, you are not a free man." + icon_state = "orange" + inhand_icon_state = "orange-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + assignment = "Prisoner" + registered_name = "Scum" + uses_overlays = FALSE + var/goal = 0 //How far from freedom? + var/points = 0 + registered_age = null + +/obj/item/card/id/prisoner/attack_self(mob/user) + to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") + +/obj/item/card/id/prisoner/one + name = "Prisoner #13-001" + registered_name = "Prisoner #13-001" + icon_state = "prisoner_001" + +/obj/item/card/id/prisoner/two + name = "Prisoner #13-002" + registered_name = "Prisoner #13-002" + icon_state = "prisoner_002" + +/obj/item/card/id/prisoner/three + name = "Prisoner #13-003" + registered_name = "Prisoner #13-003" + icon_state = "prisoner_003" + +/obj/item/card/id/prisoner/four + name = "Prisoner #13-004" + registered_name = "Prisoner #13-004" + icon_state = "prisoner_004" + +/obj/item/card/id/prisoner/five + name = "Prisoner #13-005" + registered_name = "Prisoner #13-005" + icon_state = "prisoner_005" + +/obj/item/card/id/prisoner/six + name = "Prisoner #13-006" + registered_name = "Prisoner #13-006" + icon_state = "prisoner_006" + +/obj/item/card/id/prisoner/seven + name = "Prisoner #13-007" + registered_name = "Prisoner #13-007" + icon_state = "prisoner_007" + +/obj/item/card/id/mining + name = "mining ID" + access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) + +/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." + access = list(ACCESS_AWAY_GENERAL) + icon_state = "retro" + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/away/hotel + name = "Staff ID" + desc = "A staff ID used to access the hotel's doors." + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) + +/obj/item/card/id/away/hotel/securty + name = "Officer ID" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT, ACCESS_AWAY_SEC) + +/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\"." + assignment = "Charlie Station Security Officer" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_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\"." + assignment = "Charlie Station Scientist" + access = list(ACCESS_AWAY_GENERAL) + +/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\"." + assignment = "Charlie Station Engineer" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE) + +/obj/item/card/id/away/old/apc + name = "APC Access ID" + desc = "A special ID card that allows access to APC terminals." + access = list(ACCESS_ENGINE_EQUIP) + +/obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin + name = "bunker access ID" + +/obj/item/card/id/departmental_budget + name = "departmental card (FUCK)" + desc = "Provides access to the departmental budget." + icon_state = "budgetcard" + uses_overlays = FALSE + var/department_ID = ACCOUNT_CIV + var/department_name = ACCOUNT_CIV_NAME + registered_age = null + +/obj/item/card/id/departmental_budget/Initialize() + . = ..() + 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/AltClick(mob/living/user) + registered_account.bank_card_talk("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 diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 451bbec73fe..258f8a3b533 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -1,992 +1,992 @@ -//cleansed 9/15/2012 17:48 - -/* -CONTAINS: -MATCHES -CIGARETTES -CIGARS -SMOKING PIPES -CHEAP LIGHTERS -ZIPPO - -CIGARETTE PACKETS ARE IN FANCY.DM -*/ - -/////////// -//MATCHES// -/////////// -/obj/item/match - name = "match" - desc = "A simple match stick, used for lighting fine smokables." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "match_unlit" - var/lit = FALSE - var/burnt = FALSE - var/smoketime = 5 // 10 seconds - w_class = WEIGHT_CLASS_TINY - heat = 1000 - grind_results = list(/datum/reagent/phosphorus = 2) - -/obj/item/match/process() - smoketime-- - if(smoketime < 1) - matchburnout() - else - open_flame(heat) - -/obj/item/match/fire_act(exposed_temperature, exposed_volume) - matchignite() - -/obj/item/match/proc/matchignite() - if(!lit && !burnt) - playsound(src, 'sound/items/match_strike.ogg', 15, TRUE) - lit = TRUE - icon_state = "match_lit" - damtype = "fire" - force = 3 - hitsound = 'sound/items/welder.ogg' - inhand_icon_state = "cigon" - name = "lit [initial(name)]" - desc = "A [initial(name)]. This one is lit." - attack_verb = list("burnt","singed") - START_PROCESSING(SSobj, src) - update_icon() - -/obj/item/match/proc/matchburnout() - if(lit) - lit = FALSE - burnt = TRUE - damtype = "brute" - force = initial(force) - icon_state = "match_burnt" - inhand_icon_state = "cigoff" - name = "burnt [initial(name)]" - desc = "A [initial(name)]. This one has seen better days." - attack_verb = list("flicked") - STOP_PROCESSING(SSobj, src) - -/obj/item/match/extinguish() - matchburnout() - -/obj/item/match/dropped(mob/user) - matchburnout() - . = ..() - -/obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!isliving(M)) - return - if(lit && M.IgniteMob()) - message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") - log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "[cig] is already lit!") - if(M == user) - cig.attackby(src, user) - else - cig.light("[user] holds [src] out for [M], and lights [cig].") - else - ..() - -/obj/item/proc/help_light_cig(mob/living/M) - var/mask_item = M.get_item_by_slot(ITEM_SLOT_MASK) - if(istype(mask_item, /obj/item/clothing/mask/cigarette)) - return mask_item - -/obj/item/match/get_temperature() - return lit * heat - -/obj/item/match/firebrand - name = "firebrand" - desc = "An unlit firebrand. It makes you wonder why it's not just called a stick." - smoketime = 20 //40 seconds - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT) - grind_results = list(/datum/reagent/carbon = 2) - -/obj/item/match/firebrand/Initialize() - . = ..() - matchignite() - -////////////////// -//FINE SMOKABLES// -////////////////// -/obj/item/clothing/mask/cigarette - name = "cigarette" - desc = "A roll of tobacco and nicotine." - icon_state = "cigoff" - throw_speed = 0.5 - inhand_icon_state = "cigoff" - w_class = WEIGHT_CLASS_TINY - body_parts_covered = null - grind_results = list() - heat = 1000 - var/dragtime = 100 - var/nextdragtime = 0 - var/lit = FALSE - var/starts_lit = FALSE - var/icon_on = "cigon" //Note - these are in masks.dmi not in cigarette.dmi - var/icon_off = "cigoff" - var/type_butt = /obj/item/cigbutt - var/lastHolder = null - var/smoketime = 180 // 1 is 2 seconds, so a single cigarette will last 6 minutes. - var/chem_volume = 30 - var/smoke_all = FALSE /// Should we smoke all of the chems in the cig before it runs out. Splits each puff to take a portion of the overall chems so by the end you'll always have consumed all of the chems inside. - var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) - var/lung_harm = 1 //How bad it is for you - -/obj/item/clothing/mask/cigarette/suicide_act(mob/user) - user.visible_message("[user] is huffing [src] as quickly as [user.p_they()] can! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer.") - return (TOXLOSS|OXYLOSS) - -/obj/item/clothing/mask/cigarette/Initialize() - . = ..() - create_reagents(chem_volume, INJECTABLE | NO_REACT) - if(list_reagents) - reagents.add_reagent_list(list_reagents) - if(starts_lit) - light() - AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(ITEM_SLOT_MASK))//90% to knock off when wearing a mask - -/obj/item/clothing/mask/cigarette/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/clothing/mask/cigarette/attackby(obj/item/W, mob/user, params) - if(!lit && smoketime > 0) - var/lighting_text = W.ignition_effect(src, user) - if(lighting_text) - light(lighting_text) - else - return ..() - -/obj/item/clothing/mask/cigarette/afterattack(obj/item/reagent_containers/glass/glass, mob/user, proximity) - . = ..() - if(!proximity || lit) //can't dip if cigarette is lit (it will heat the reagents in the glass instead) - return - if(istype(glass)) //you can dip cigarettes into beakers - if(glass.reagents.trans_to(src, chem_volume, transfered_by = user)) //if reagents were transfered, show the message - to_chat(user, "You dip \the [src] into \the [glass].") - else //if not, either the beaker was empty, or the cigarette was full - if(!glass.reagents.total_volume) - to_chat(user, "[glass] is empty!") - else - to_chat(user, "[src] is full!") - - -/obj/item/clothing/mask/cigarette/proc/light(flavor_text = null) - if(lit) - return - if(!(flags_1 & INITIALIZED_1)) - icon_state = icon_on - inhand_icon_state = icon_on - return - - lit = TRUE - name = "lit [name]" - attack_verb = list("burnt", "singed") - hitsound = 'sound/items/welder.ogg' - damtype = "fire" - force = 4 - if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) - e.start() - qdel(src) - return - if(reagents.get_reagent_amount(/datum/reagent/fuel)) // the fuel explodes, too, but much less violently - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/fuel) / 5, 1), get_turf(src), 0, 0) - e.start() - qdel(src) - return - // allowing reagents to react after being lit - reagents.flags &= ~(NO_REACT) - reagents.handle_reactions() - icon_state = icon_on - inhand_icon_state = icon_on - if(flavor_text) - var/turf/T = get_turf(src) - T.visible_message(flavor_text) - START_PROCESSING(SSobj, src) - - //can't think of any other way to update the overlays :< - if(ismob(loc)) - var/mob/M = loc - M.update_inv_wear_mask() - M.update_inv_hands() - -/obj/item/clothing/mask/cigarette/extinguish() - if(!lit) - return - name = copytext_char(name, 5) //5 == length_char("lit ") + 1 - attack_verb = null - hitsound = null - damtype = BRUTE - force = 0 - icon_state = icon_off - inhand_icon_state = icon_off - STOP_PROCESSING(SSobj, src) - reagents.flags |= NO_REACT - lit = FALSE - if(ismob(loc)) - var/mob/living/M = loc - to_chat(M, "Your [name] goes out.") - M.update_inv_wear_mask() - M.update_inv_hands() - -/obj/item/clothing/mask/cigarette/proc/handle_reagents() - if(reagents.total_volume) - var/to_smoke = REAGENTS_METABOLISM - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob - var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) - /* - * Given the amount of time the cig will last, and how often we take a hit, find the number - * of chems to give them each time so they'll have smoked it all by the end. - */ - if (smoke_all) - to_smoke = reagents.total_volume/((smoketime * 2) / (dragtime / 10)) - - reagents.expose(C, INGEST, fraction) - var/obj/item/organ/lungs/L = C.getorganslot(ORGAN_SLOT_LUNGS) - if(L && !(L.organ_flags & ORGAN_SYNTHETIC)) - C.adjustOrganLoss(ORGAN_SLOT_LUNGS, lung_harm) - if(!reagents.trans_to(C, to_smoke)) - reagents.remove_any(to_smoke) - return - reagents.remove_any(to_smoke) - -/obj/item/clothing/mask/cigarette/process() - var/turf/location = get_turf(src) - var/mob/living/M = loc - if(isliving(loc)) - M.IgniteMob() - smoketime-- - if(smoketime < 1) - new type_butt(location) - if(ismob(loc)) - to_chat(M, "Your [name] goes out.") - qdel(src) - return - open_flame() - if((reagents && reagents.total_volume) && (nextdragtime <= world.time)) - nextdragtime = world.time + dragtime - handle_reagents() - -/obj/item/clothing/mask/cigarette/attack_self(mob/user) - if(lit) - user.visible_message("[user] calmly drops and treads on \the [src], putting it out instantly.") - new type_butt(user.loc) - new /obj/effect/decal/cleanable/ash(user.loc) - qdel(src) - . = ..() - -/obj/item/clothing/mask/cigarette/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!istype(M)) - return ..() - if(M.on_fire && !lit) - light("[user] lights [src] with [M]'s burning body. What a cold-blooded badass.") - return - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "The [cig.name] is already lit!") - if(M == user) - cig.attackby(src, user) - else - cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") - else - return ..() - -/obj/item/clothing/mask/cigarette/fire_act(exposed_temperature, exposed_volume) - light() - -/obj/item/clothing/mask/cigarette/get_temperature() - return lit * heat - -// Cigarette brands. - -/obj/item/clothing/mask/cigarette/space_cigarette - desc = "A Space Cigarette brand cigarette." - -/obj/item/clothing/mask/cigarette/dromedary - desc = "A DromedaryCo brand cigarette. Contrary to popular belief, does not contain Calomel, but is reported to have a watery taste." - list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/water = 5) //camel has water - -/obj/item/clothing/mask/cigarette/uplift - desc = "An Uplift Smooth brand cigarette. Smells refreshing." - list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/consumable/menthol = 5) - -/obj/item/clothing/mask/cigarette/robust - desc = "A Robust brand cigarette." - -/obj/item/clothing/mask/cigarette/robustgold - desc = "A Robust Gold brand cigarette." - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/gold = 3) // Just enough to taste a hint of expensive metal. - -/obj/item/clothing/mask/cigarette/carp - desc = "A Carp Classic brand cigarette. A small label on its side indicates that it does NOT contain carpotoxin." - -/obj/item/clothing/mask/cigarette/carp/Initialize() - . = ..() - if(!prob(5)) - return - reagents?.add_reagent(/datum/reagent/toxin/carpotoxin , 3) // They lied - -/obj/item/clothing/mask/cigarette/syndicate - desc = "An unknown brand cigarette." - chem_volume = 60 - smoketime = 60 - smoke_all = TRUE - list_reagents = list(/datum/reagent/drug/nicotine = 10, /datum/reagent/medicine/omnizine = 15) - -/obj/item/clothing/mask/cigarette/shadyjims - desc = "A Shady Jim's Super Slims cigarette." - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/toxin/lipolicide = 4, /datum/reagent/ammonia = 2, /datum/reagent/toxin/plantbgone = 1, /datum/reagent/toxin = 1.5) - -/obj/item/clothing/mask/cigarette/xeno - desc = "A Xeno Filtered brand cigarette." - list_reagents = list (/datum/reagent/drug/nicotine = 20, /datum/reagent/medicine/regen_jelly = 15, /datum/reagent/drug/krokodil = 4) - -// Rollies. - -/obj/item/clothing/mask/cigarette/rollie - name = "rollie" - desc = "A roll of dried plant matter wrapped in thin paper." - icon_state = "spliffoff" - icon_on = "spliffon" - icon_off = "spliffoff" - type_butt = /obj/item/cigbutt/roach - throw_speed = 0.5 - inhand_icon_state = "spliffoff" - smoketime = 120 // four minutes - chem_volume = 50 - list_reagents = null - -/obj/item/clothing/mask/cigarette/rollie/Initialize() - . = ..() - name = pick(list( - "bifta", - "bifter", - "bird", - "blunt", - "bloint", - "boof", - "boofer", - "bomber", - "bone", - "bun", - "doink", - "doob", - "doober", - "doobie", - "dutch", - "fatty", - "hogger", - "hooter", - "hootie", - "\improper J", - "jay", - "jimmy", - "joint", - "juju", - "jeebie weebie", - "number", - "owl", - "phattie", - "puffer", - "reef", - "reefer", - "rollie", - "scoobie", - "shorty", - "spiff", - "spliff", - "toke", - "torpedo", - "zoot", - "zooter")) - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/item/clothing/mask/cigarette/rollie/nicotine - list_reagents = list(/datum/reagent/drug/nicotine = 15) - -/obj/item/clothing/mask/cigarette/rollie/trippy - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/drug/mushroomhallucinogen = 35) - starts_lit = TRUE - -/obj/item/clothing/mask/cigarette/rollie/cannabis - list_reagents = list(/datum/reagent/drug/space_drugs = 15, /datum/reagent/toxin/lipolicide = 35) - -/obj/item/clothing/mask/cigarette/rollie/mindbreaker - list_reagents = list(/datum/reagent/toxin/mindbreaker = 35, /datum/reagent/toxin/lipolicide = 15) - -/obj/item/clothing/mask/cigarette/candy - name = "Little Timmy's candy cigarette" - desc = "For all ages*! Doesn't contain any amount of nicotine. Health and safety risks can be read on the tip of the cigarette." - smoketime = 120 - icon_on = "candyon" - icon_off = "candyoff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. - inhand_icon_state = "candyoff" - icon_state = "candyoff" - type_butt = /obj/item/reagent_containers/food/snacks/candy_trash - list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10) - -/obj/item/clothing/mask/cigarette/candy/nicotine - desc = "For all ages*! Doesn't contain any* amount of nicotine. Health and safety risks can be read on the tip of the cigarette." - type_butt = /obj/item/reagent_containers/food/snacks/candy_trash/nicotine - list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10, /datum/reagent/drug/nicotine = 20) //oh no! - smoke_all = TRUE //timmy's not getting out of this one - -/obj/item/cigbutt/roach - name = "roach" - desc = "A manky old roach, or for non-stoners, a used rollup." - icon_state = "roach" - -/obj/item/cigbutt/roach/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - -//////////// -// CIGARS // -//////////// -/obj/item/clothing/mask/cigarette/cigar - name = "premium cigar" - desc = "A brown roll of tobacco and... well, you're not quite sure. This thing's huge!" - icon_state = "cigaroff" - icon_on = "cigaron" - icon_off = "cigaroff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. - type_butt = /obj/item/cigbutt/cigarbutt - throw_speed = 0.5 - inhand_icon_state = "cigaroff" - smoketime = 300 // 11 minutes - chem_volume = 40 - list_reagents = list(/datum/reagent/drug/nicotine = 25) - -/obj/item/clothing/mask/cigarette/cigar/cohiba - name = "\improper Cohiba Robusto cigar" - desc = "There's little more you could want from a cigar." - icon_state = "cigar2off" - icon_on = "cigar2on" - icon_off = "cigar2off" - smoketime = 600 // 20 minutes - chem_volume = 80 - list_reagents =list(/datum/reagent/drug/nicotine = 40) - -/obj/item/clothing/mask/cigarette/cigar/havana - name = "premium Havanian cigar" - desc = "A cigar fit for only the best of the best." - icon_state = "cigar2off" - icon_on = "cigar2on" - icon_off = "cigar2off" - smoketime = 900 // 30 minutes - chem_volume = 50 - list_reagents =list(/datum/reagent/drug/nicotine = 15) - -/obj/item/cigbutt - name = "cigarette butt" - desc = "A manky old cigarette butt." - icon = 'icons/obj/clothing/masks.dmi' - icon_state = "cigbutt" - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - grind_results = list(/datum/reagent/carbon = 2) - -/obj/item/cigbutt/cigarbutt - name = "cigar butt" - desc = "A manky old cigar butt." - icon_state = "cigarbutt" - -///////////////// -//SMOKING PIPES// -///////////////// -/obj/item/clothing/mask/cigarette/pipe - name = "smoking pipe" - desc = "A pipe, for smoking. Probably made of meerschaum or something." - icon_state = "pipeoff" - inhand_icon_state = "pipeoff" - icon_on = "pipeon" //Note - these are in masks.dmi - icon_off = "pipeoff" - smoketime = 0 - chem_volume = 100 - list_reagents = null - var/packeditem = 0 - -/obj/item/clothing/mask/cigarette/pipe/Initialize() - . = ..() - name = "empty [initial(name)]" - -/obj/item/clothing/mask/cigarette/pipe/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/clothing/mask/cigarette/pipe/process() - var/turf/location = get_turf(src) - smoketime-- - if(smoketime < 1) - new /obj/effect/decal/cleanable/ash(location) - if(ismob(loc)) - var/mob/living/M = loc - to_chat(M, "Your [name] goes out.") - lit = 0 - icon_state = icon_off - inhand_icon_state = icon_off - M.update_inv_wear_mask() - packeditem = 0 - name = "empty [initial(name)]" - STOP_PROCESSING(SSobj, src) - return - open_flame() - if(reagents && reagents.total_volume) // check if it has any reagents at all - handle_reagents() - - -/obj/item/clothing/mask/cigarette/pipe/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/G = O - if(!packeditem) - if(G.dry == 1) - to_chat(user, "You stuff [O] into [src].") - smoketime = 400 - packeditem = 1 - name = "[O.name]-packed [initial(name)]" - if(O.reagents) - O.reagents.trans_to(src, O.reagents.total_volume, transfered_by = user) - qdel(O) - else - to_chat(user, "It has to be dried first!") - else - to_chat(user, "It is already packed!") - else - var/lighting_text = O.ignition_effect(src,user) - if(lighting_text) - if(smoketime > 0) - light(lighting_text) - else - to_chat(user, "There is nothing to smoke!") - else - return ..() - -/obj/item/clothing/mask/cigarette/pipe/attack_self(mob/user) - var/turf/location = get_turf(user) - if(lit) - user.visible_message("[user] puts out [src].", "You put out [src].") - lit = 0 - icon_state = icon_off - inhand_icon_state = icon_off - STOP_PROCESSING(SSobj, src) - return - if(!lit && smoketime > 0) - to_chat(user, "You empty [src] onto [location].") - new /obj/effect/decal/cleanable/ash(location) - packeditem = 0 - smoketime = 0 - reagents.clear_reagents() - name = "empty [initial(name)]" - return - -/obj/item/clothing/mask/cigarette/pipe/cobpipe - name = "corn cob pipe" - desc = "A nicotine delivery system popularized by folksy backwoodsmen and kept popular in the modern age and beyond by space hipsters. Can be loaded with objects." - icon_state = "cobpipeoff" - inhand_icon_state = "cobpipeoff" - icon_on = "cobpipeon" //Note - these are in masks.dmi - icon_off = "cobpipeoff" - smoketime = 0 - - -///////// -//ZIPPO// -///////// -/obj/item/lighter - name = "\improper Zippo lighter" - desc = "The zippo." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "zippo" - inhand_icon_state = "zippo" - w_class = WEIGHT_CLASS_TINY - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - var/lit = 0 - var/fancy = TRUE - var/overlay_state - var/overlay_list = list( - "plain", - "dame", - "thirteen", - "snake" - ) - heat = 1500 - resistance_flags = FIRE_PROOF - light_color = LIGHT_COLOR_FIRE - grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/fuel/oil = 5) - custom_price = 55 - -/obj/item/lighter/Initialize() - . = ..() - if(!overlay_state) - overlay_state = pick(overlay_list) - update_icon() - -/obj/item/lighter/cyborg_unequip(mob/user) - if(!lit) - return - set_lit(FALSE) - -/obj/item/lighter/suicide_act(mob/living/carbon/user) - if (lit) - user.visible_message("[user] begins holding \the [src]'s flame up to [user.p_their()] face! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/items/welder.ogg', 50, TRUE) - return FIRELOSS - else - user.visible_message("[user] begins whacking [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/lighter/update_overlays() - . = ..() - . += create_lighter_overlay() - -/obj/item/lighter/update_icon_state() - icon_state = "[initial(icon_state)][lit ? "-on" : ""]" - -/obj/item/lighter/proc/create_lighter_overlay() - return mutable_appearance(icon, "lighter_overlay_[overlay_state][lit ? "-on" : ""]") - -/obj/item/lighter/ignition_effect(atom/A, mob/user) - if(get_temperature()) - . = "With a single flick of [user.p_their()] wrist, [user] smoothly lights [A] with [src]. Damn [user.p_theyre()] cool." - -/obj/item/lighter/proc/set_lit(new_lit) - lit = new_lit - if(lit) - force = 5 - damtype = "fire" - hitsound = 'sound/items/welder.ogg' - attack_verb = list("burnt", "singed") - set_light(1) - START_PROCESSING(SSobj, src) - else - hitsound = "swing_hit" - force = 0 - attack_verb = null //human_defense.dm takes care of it - set_light(0) - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/lighter/extinguish() - set_lit(FALSE) - -/obj/item/lighter/attack_self(mob/living/user) - if(user.is_holding(src)) - if(!lit) - set_lit(TRUE) - if(fancy) - user.visible_message("Without even breaking stride, [user] flips open and lights [src] in one smooth movement.", "Without even breaking stride, you flip open and light [src] in one smooth movement.") - else - var/prot = FALSE - var/mob/living/carbon/human/H = user - - if(istype(H) && H.gloves) - var/obj/item/clothing/gloves/G = H.gloves - if(G.max_heat_protection_temperature) - prot = (G.max_heat_protection_temperature > 360) - else - prot = TRUE - - if(prot || prob(75)) - user.visible_message("After a few attempts, [user] manages to light [src].", "After a few attempts, you manage to light [src].") - else - var/hitzone = user.held_index_to_dir(user.active_hand_index) == "r" ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND - user.apply_damage(5, BURN, hitzone) - user.visible_message("After a few attempts, [user] manages to light [src] - however, [user.p_they()] burn [user.p_their()] finger in the process.", "You burn yourself while lighting the lighter!") - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "burnt_thumb", /datum/mood_event/burnt_thumb) - - else - set_lit(FALSE) - if(fancy) - user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what [user.p_theyre()] doing. Wow.", "You quietly shut off [src] without even looking at what you're doing. Wow.") - else - user.visible_message("[user] quietly shuts off [src].", "You quietly shut off [src].") - else - . = ..() - -/obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user) - if(lit && M.IgniteMob()) - message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") - log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "The [cig.name] is already lit!") - if(M == user) - cig.attackby(src, user) - else - if(fancy) - cig.light("[user] whips the [name] out and holds it for [M]. [user.p_their(TRUE)] arm is as steady as the unflickering flame [user.p_they()] light[user.p_s()] \the [cig] with.") - else - cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") - else - ..() - -/obj/item/lighter/process() - open_flame() - -/obj/item/lighter/get_temperature() - return lit * heat - - -/obj/item/lighter/greyscale - name = "cheap lighter" - desc = "A cheap lighter." - icon_state = "lighter" - fancy = FALSE - overlay_list = list( - "transp", - "tall", - "matte", - "zoppo" //u cant stoppo th zoppo - ) - var/lighter_color - var/list/color_list = list( //Same 16 color selection as electronic assemblies - COLOR_ASSEMBLY_BLACK, - COLOR_FLOORTILE_GRAY, - COLOR_ASSEMBLY_BGRAY, - COLOR_ASSEMBLY_WHITE, - COLOR_ASSEMBLY_RED, - COLOR_ASSEMBLY_ORANGE, - COLOR_ASSEMBLY_BEIGE, - COLOR_ASSEMBLY_BROWN, - COLOR_ASSEMBLY_GOLD, - COLOR_ASSEMBLY_YELLOW, - COLOR_ASSEMBLY_GURKHA, - COLOR_ASSEMBLY_LGREEN, - COLOR_ASSEMBLY_GREEN, - COLOR_ASSEMBLY_LBLUE, - COLOR_ASSEMBLY_BLUE, - COLOR_ASSEMBLY_PURPLE - ) - -/obj/item/lighter/greyscale/Initialize() - . = ..() - if(!lighter_color) - lighter_color = pick(color_list) - update_icon() - -/obj/item/lighter/greyscale/create_lighter_overlay() - var/mutable_appearance/lighter_overlay = ..() - lighter_overlay.color = lighter_color - return lighter_overlay - -/obj/item/lighter/greyscale/ignition_effect(atom/A, mob/user) - if(get_temperature()) - . = "After some fiddling, [user] manages to light [A] with [src]." - - -/obj/item/lighter/slime - name = "slime zippo" - desc = "A specialty zippo made from slimes and industry. Has a much hotter flame than normal." - icon_state = "slighter" - heat = 3000 //Blue flame! - light_color = LIGHT_COLOR_CYAN - overlay_state = "slime" - grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/medicine/pyroxadone = 5) - - -/////////// -//ROLLING// -/////////// -/obj/item/rollingpaper - name = "rolling paper" - desc = "A thin piece of paper used to make fine smokeables." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig_paper" - w_class = WEIGHT_CLASS_TINY - -/obj/item/rollingpaper/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity) - return - if(istype(target, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/O = target - if(O.dry) - var/obj/item/clothing/mask/cigarette/rollie/R = new /obj/item/clothing/mask/cigarette/rollie(user.loc) - R.chem_volume = target.reagents.total_volume - target.reagents.trans_to(R, R.chem_volume, transfered_by = user) - qdel(target) - qdel(src) - user.put_in_active_hand(R) - to_chat(user, "You roll the [target.name] into a rolling paper.") - R.desc = "Dried [target.name] rolled up in a thin piece of paper." - else - to_chat(user, "You need to dry this first!") - -/////////////// -//VAPE NATION// -/////////////// -/obj/item/clothing/mask/vape - name = "\improper E-Cigarette" - desc = "A classy and highly sophisticated electronic cigarette, for classy and dignified gentlemen. A warning label reads \"Warning: Do not fill with flammable materials.\""//<<< i'd vape to that. - icon = 'icons/obj/clothing/masks.dmi' - icon_state = "red_vape" - inhand_icon_state = null - w_class = WEIGHT_CLASS_TINY - var/chem_volume = 100 - var/vapetime = 0 //this so it won't puff out clouds every tick - var/screw = 0 // kinky - var/super = 0 //for the fattest vapes dude. - -/obj/item/clothing/mask/vape/suicide_act(mob/user) - user.visible_message("[user] is puffin hard on dat vape, [user.p_they()] trying to join the vape life on a whole notha plane!")//it doesn't give you cancer, it is cancer - return (TOXLOSS|OXYLOSS) - - -/obj/item/clothing/mask/vape/Initialize(mapload, param_color) - . = ..() - create_reagents(chem_volume, NO_REACT) - reagents.add_reagent(/datum/reagent/drug/nicotine, 50) - if(!param_color) - param_color = pick("red","blue","black","white","green","purple","yellow","orange") - icon_state = "[param_color]_vape" - inhand_icon_state = "[param_color]_vape" - -/obj/item/clothing/mask/vape/attackby(obj/item/O, mob/user, params) - if(O.tool_behaviour == TOOL_SCREWDRIVER) - if(!screw) - screw = TRUE - to_chat(user, "You open the cap on [src].") - reagents.flags |= OPENCONTAINER - if(obj_flags & EMAGGED) - add_overlay("vapeopen_high") - else if(super) - add_overlay("vapeopen_med") - else - add_overlay("vapeopen_low") - else - screw = FALSE - to_chat(user, "You close the cap on [src].") - reagents.flags &= ~(OPENCONTAINER) - cut_overlays() - - if(O.tool_behaviour == TOOL_MULTITOOL) - if(screw && !(obj_flags & EMAGGED))//also kinky - if(!super) - cut_overlays() - super = 1 - to_chat(user, "You increase the voltage of [src].") - add_overlay("vapeopen_med") - else - cut_overlays() - super = 0 - to_chat(user, "You decrease the voltage of [src].") - add_overlay("vapeopen_low") - - if(screw && (obj_flags & EMAGGED)) - to_chat(user, "[src] can't be modified!") - else - ..() - - -/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY. - if(screw) - if(!(obj_flags & EMAGGED)) - cut_overlays() - obj_flags |= EMAGGED - super = 0 - to_chat(user, "You maximize the voltage of [src].") - add_overlay("vapeopen_high") - var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect - sp.set_up(5, 1, src) - sp.start() - else - to_chat(user, "[src] is already emagged!") - else - to_chat(user, "You need to open the cap to do that!") - -/obj/item/clothing/mask/vape/attack_self(mob/user) - if(reagents.total_volume > 0) - to_chat(user, "You empty [src] of all reagents.") - reagents.clear_reagents() - -/obj/item/clothing/mask/vape/equipped(mob/user, slot) - . = ..() - if(slot == ITEM_SLOT_MASK) - if(!screw) - to_chat(user, "You start puffing on the vape.") - reagents.flags &= ~(NO_REACT) - START_PROCESSING(SSobj, src) - else //it will not start if the vape is opened. - to_chat(user, "You need to close the cap first!") - -/obj/item/clothing/mask/vape/dropped(mob/user) - . = ..() - if(user.get_item_by_slot(ITEM_SLOT_MASK) == src) - reagents.flags |= NO_REACT - STOP_PROCESSING(SSobj, src) - -/obj/item/clothing/mask/vape/proc/hand_reagents()//had to rename to avoid duplicate error - if(reagents.total_volume) - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob - var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) //this will react instantly, making them a little more dangerous than cigarettes - reagents.expose(C, INGEST, fraction) - if(!reagents.trans_to(C, REAGENTS_METABOLISM)) - reagents.remove_any(REAGENTS_METABOLISM) - if(reagents.get_reagent_amount(/datum/reagent/fuel)) - //HOT STUFF - C.adjust_fire_stacks(2) - C.IgniteMob() - - if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) - e.start() - qdel(src) - return - reagents.remove_any(REAGENTS_METABOLISM) - -/obj/item/clothing/mask/vape/process() - var/mob/living/M = loc - - if(isliving(loc)) - M.IgniteMob() - - vapetime++ - - if(!reagents.total_volume) - if(ismob(loc)) - to_chat(M, "[src] is empty!") - STOP_PROCESSING(SSobj, src) - //it's reusable so it won't unequip when empty - return - //open flame removed because vapes are a closed system, they won't light anything on fire - - if(super && vapetime > 3)//Time to start puffing those fat vapes, yo. - var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new - s.set_up(reagents, 1, 24, loc) - s.start() - vapetime = 0 - - if((obj_flags & EMAGGED) && vapetime > 3) - var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new - s.set_up(reagents, 4, 24, loc) - s.start() - vapetime = 0 - if(prob(5))//small chance for the vape to break and deal damage if it's emagged - playsound(get_turf(src), 'sound/effects/pop_expl.ogg', 50, FALSE) - M.apply_damage(20, BURN, BODY_ZONE_HEAD) - M.Paralyze(300, 1, 0) - var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread - sp.set_up(5, 1, src) - sp.start() - to_chat(M, "[src] suddenly explodes in your mouth!") - qdel(src) - return - - if(reagents && reagents.total_volume) - hand_reagents() +//cleansed 9/15/2012 17:48 + +/* +CONTAINS: +MATCHES +CIGARETTES +CIGARS +SMOKING PIPES +CHEAP LIGHTERS +ZIPPO + +CIGARETTE PACKETS ARE IN FANCY.DM +*/ + +/////////// +//MATCHES// +/////////// +/obj/item/match + name = "match" + desc = "A simple match stick, used for lighting fine smokables." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "match_unlit" + var/lit = FALSE + var/burnt = FALSE + var/smoketime = 5 // 10 seconds + w_class = WEIGHT_CLASS_TINY + heat = 1000 + grind_results = list(/datum/reagent/phosphorus = 2) + +/obj/item/match/process() + smoketime-- + if(smoketime < 1) + matchburnout() + else + open_flame(heat) + +/obj/item/match/fire_act(exposed_temperature, exposed_volume) + matchignite() + +/obj/item/match/proc/matchignite() + if(!lit && !burnt) + playsound(src, 'sound/items/match_strike.ogg', 15, TRUE) + lit = TRUE + icon_state = "match_lit" + damtype = "fire" + force = 3 + hitsound = 'sound/items/welder.ogg' + inhand_icon_state = "cigon" + name = "lit [initial(name)]" + desc = "A [initial(name)]. This one is lit." + attack_verb = list("burnt","singed") + START_PROCESSING(SSobj, src) + update_icon() + +/obj/item/match/proc/matchburnout() + if(lit) + lit = FALSE + burnt = TRUE + damtype = "brute" + force = initial(force) + icon_state = "match_burnt" + inhand_icon_state = "cigoff" + name = "burnt [initial(name)]" + desc = "A [initial(name)]. This one has seen better days." + attack_verb = list("flicked") + STOP_PROCESSING(SSobj, src) + +/obj/item/match/extinguish() + matchburnout() + +/obj/item/match/dropped(mob/user) + matchburnout() + . = ..() + +/obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!isliving(M)) + return + if(lit && M.IgniteMob()) + message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") + log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "[cig] is already lit!") + if(M == user) + cig.attackby(src, user) + else + cig.light("[user] holds [src] out for [M], and lights [cig].") + else + ..() + +/obj/item/proc/help_light_cig(mob/living/M) + var/mask_item = M.get_item_by_slot(ITEM_SLOT_MASK) + if(istype(mask_item, /obj/item/clothing/mask/cigarette)) + return mask_item + +/obj/item/match/get_temperature() + return lit * heat + +/obj/item/match/firebrand + name = "firebrand" + desc = "An unlit firebrand. It makes you wonder why it's not just called a stick." + smoketime = 20 //40 seconds + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT) + grind_results = list(/datum/reagent/carbon = 2) + +/obj/item/match/firebrand/Initialize() + . = ..() + matchignite() + +////////////////// +//FINE SMOKABLES// +////////////////// +/obj/item/clothing/mask/cigarette + name = "cigarette" + desc = "A roll of tobacco and nicotine." + icon_state = "cigoff" + throw_speed = 0.5 + inhand_icon_state = "cigoff" + w_class = WEIGHT_CLASS_TINY + body_parts_covered = null + grind_results = list() + heat = 1000 + var/dragtime = 100 + var/nextdragtime = 0 + var/lit = FALSE + var/starts_lit = FALSE + var/icon_on = "cigon" //Note - these are in masks.dmi not in cigarette.dmi + var/icon_off = "cigoff" + var/type_butt = /obj/item/cigbutt + var/lastHolder = null + var/smoketime = 180 // 1 is 2 seconds, so a single cigarette will last 6 minutes. + var/chem_volume = 30 + var/smoke_all = FALSE /// Should we smoke all of the chems in the cig before it runs out. Splits each puff to take a portion of the overall chems so by the end you'll always have consumed all of the chems inside. + var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) + var/lung_harm = 1 //How bad it is for you + +/obj/item/clothing/mask/cigarette/suicide_act(mob/user) + user.visible_message("[user] is huffing [src] as quickly as [user.p_they()] can! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer.") + return (TOXLOSS|OXYLOSS) + +/obj/item/clothing/mask/cigarette/Initialize() + . = ..() + create_reagents(chem_volume, INJECTABLE | NO_REACT) + if(list_reagents) + reagents.add_reagent_list(list_reagents) + if(starts_lit) + light() + AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(ITEM_SLOT_MASK))//90% to knock off when wearing a mask + +/obj/item/clothing/mask/cigarette/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/clothing/mask/cigarette/attackby(obj/item/W, mob/user, params) + if(!lit && smoketime > 0) + var/lighting_text = W.ignition_effect(src, user) + if(lighting_text) + light(lighting_text) + else + return ..() + +/obj/item/clothing/mask/cigarette/afterattack(obj/item/reagent_containers/glass/glass, mob/user, proximity) + . = ..() + if(!proximity || lit) //can't dip if cigarette is lit (it will heat the reagents in the glass instead) + return + if(istype(glass)) //you can dip cigarettes into beakers + if(glass.reagents.trans_to(src, chem_volume, transfered_by = user)) //if reagents were transfered, show the message + to_chat(user, "You dip \the [src] into \the [glass].") + else //if not, either the beaker was empty, or the cigarette was full + if(!glass.reagents.total_volume) + to_chat(user, "[glass] is empty!") + else + to_chat(user, "[src] is full!") + + +/obj/item/clothing/mask/cigarette/proc/light(flavor_text = null) + if(lit) + return + if(!(flags_1 & INITIALIZED_1)) + icon_state = icon_on + inhand_icon_state = icon_on + return + + lit = TRUE + name = "lit [name]" + attack_verb = list("burnt", "singed") + hitsound = 'sound/items/welder.ogg' + damtype = "fire" + force = 4 + if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) + e.start() + qdel(src) + return + if(reagents.get_reagent_amount(/datum/reagent/fuel)) // the fuel explodes, too, but much less violently + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/fuel) / 5, 1), get_turf(src), 0, 0) + e.start() + qdel(src) + return + // allowing reagents to react after being lit + reagents.flags &= ~(NO_REACT) + reagents.handle_reactions() + icon_state = icon_on + inhand_icon_state = icon_on + if(flavor_text) + var/turf/T = get_turf(src) + T.visible_message(flavor_text) + START_PROCESSING(SSobj, src) + + //can't think of any other way to update the overlays :< + if(ismob(loc)) + var/mob/M = loc + M.update_inv_wear_mask() + M.update_inv_hands() + +/obj/item/clothing/mask/cigarette/extinguish() + if(!lit) + return + name = copytext_char(name, 5) //5 == length_char("lit ") + 1 + attack_verb = null + hitsound = null + damtype = BRUTE + force = 0 + icon_state = icon_off + inhand_icon_state = icon_off + STOP_PROCESSING(SSobj, src) + reagents.flags |= NO_REACT + lit = FALSE + if(ismob(loc)) + var/mob/living/M = loc + to_chat(M, "Your [name] goes out.") + M.update_inv_wear_mask() + M.update_inv_hands() + +/obj/item/clothing/mask/cigarette/proc/handle_reagents() + if(reagents.total_volume) + var/to_smoke = REAGENTS_METABOLISM + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob + var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) + /* + * Given the amount of time the cig will last, and how often we take a hit, find the number + * of chems to give them each time so they'll have smoked it all by the end. + */ + if (smoke_all) + to_smoke = reagents.total_volume/((smoketime * 2) / (dragtime / 10)) + + reagents.expose(C, INGEST, fraction) + var/obj/item/organ/lungs/L = C.getorganslot(ORGAN_SLOT_LUNGS) + if(L && !(L.organ_flags & ORGAN_SYNTHETIC)) + C.adjustOrganLoss(ORGAN_SLOT_LUNGS, lung_harm) + if(!reagents.trans_to(C, to_smoke)) + reagents.remove_any(to_smoke) + return + reagents.remove_any(to_smoke) + +/obj/item/clothing/mask/cigarette/process() + var/turf/location = get_turf(src) + var/mob/living/M = loc + if(isliving(loc)) + M.IgniteMob() + smoketime-- + if(smoketime < 1) + new type_butt(location) + if(ismob(loc)) + to_chat(M, "Your [name] goes out.") + qdel(src) + return + open_flame() + if((reagents && reagents.total_volume) && (nextdragtime <= world.time)) + nextdragtime = world.time + dragtime + handle_reagents() + +/obj/item/clothing/mask/cigarette/attack_self(mob/user) + if(lit) + user.visible_message("[user] calmly drops and treads on \the [src], putting it out instantly.") + new type_butt(user.loc) + new /obj/effect/decal/cleanable/ash(user.loc) + qdel(src) + . = ..() + +/obj/item/clothing/mask/cigarette/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!istype(M)) + return ..() + if(M.on_fire && !lit) + light("[user] lights [src] with [M]'s burning body. What a cold-blooded badass.") + return + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "The [cig.name] is already lit!") + if(M == user) + cig.attackby(src, user) + else + cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") + else + return ..() + +/obj/item/clothing/mask/cigarette/fire_act(exposed_temperature, exposed_volume) + light() + +/obj/item/clothing/mask/cigarette/get_temperature() + return lit * heat + +// Cigarette brands. + +/obj/item/clothing/mask/cigarette/space_cigarette + desc = "A Space Cigarette brand cigarette." + +/obj/item/clothing/mask/cigarette/dromedary + desc = "A DromedaryCo brand cigarette. Contrary to popular belief, does not contain Calomel, but is reported to have a watery taste." + list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/water = 5) //camel has water + +/obj/item/clothing/mask/cigarette/uplift + desc = "An Uplift Smooth brand cigarette. Smells refreshing." + list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/consumable/menthol = 5) + +/obj/item/clothing/mask/cigarette/robust + desc = "A Robust brand cigarette." + +/obj/item/clothing/mask/cigarette/robustgold + desc = "A Robust Gold brand cigarette." + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/gold = 3) // Just enough to taste a hint of expensive metal. + +/obj/item/clothing/mask/cigarette/carp + desc = "A Carp Classic brand cigarette. A small label on its side indicates that it does NOT contain carpotoxin." + +/obj/item/clothing/mask/cigarette/carp/Initialize() + . = ..() + if(!prob(5)) + return + reagents?.add_reagent(/datum/reagent/toxin/carpotoxin , 3) // They lied + +/obj/item/clothing/mask/cigarette/syndicate + desc = "An unknown brand cigarette." + chem_volume = 60 + smoketime = 60 + smoke_all = TRUE + list_reagents = list(/datum/reagent/drug/nicotine = 10, /datum/reagent/medicine/omnizine = 15) + +/obj/item/clothing/mask/cigarette/shadyjims + desc = "A Shady Jim's Super Slims cigarette." + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/toxin/lipolicide = 4, /datum/reagent/ammonia = 2, /datum/reagent/toxin/plantbgone = 1, /datum/reagent/toxin = 1.5) + +/obj/item/clothing/mask/cigarette/xeno + desc = "A Xeno Filtered brand cigarette." + list_reagents = list (/datum/reagent/drug/nicotine = 20, /datum/reagent/medicine/regen_jelly = 15, /datum/reagent/drug/krokodil = 4) + +// Rollies. + +/obj/item/clothing/mask/cigarette/rollie + name = "rollie" + desc = "A roll of dried plant matter wrapped in thin paper." + icon_state = "spliffoff" + icon_on = "spliffon" + icon_off = "spliffoff" + type_butt = /obj/item/cigbutt/roach + throw_speed = 0.5 + inhand_icon_state = "spliffoff" + smoketime = 120 // four minutes + chem_volume = 50 + list_reagents = null + +/obj/item/clothing/mask/cigarette/rollie/Initialize() + . = ..() + name = pick(list( + "bifta", + "bifter", + "bird", + "blunt", + "bloint", + "boof", + "boofer", + "bomber", + "bone", + "bun", + "doink", + "doob", + "doober", + "doobie", + "dutch", + "fatty", + "hogger", + "hooter", + "hootie", + "\improper J", + "jay", + "jimmy", + "joint", + "juju", + "jeebie weebie", + "number", + "owl", + "phattie", + "puffer", + "reef", + "reefer", + "rollie", + "scoobie", + "shorty", + "spiff", + "spliff", + "toke", + "torpedo", + "zoot", + "zooter")) + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/item/clothing/mask/cigarette/rollie/nicotine + list_reagents = list(/datum/reagent/drug/nicotine = 15) + +/obj/item/clothing/mask/cigarette/rollie/trippy + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/drug/mushroomhallucinogen = 35) + starts_lit = TRUE + +/obj/item/clothing/mask/cigarette/rollie/cannabis + list_reagents = list(/datum/reagent/drug/space_drugs = 15, /datum/reagent/toxin/lipolicide = 35) + +/obj/item/clothing/mask/cigarette/rollie/mindbreaker + list_reagents = list(/datum/reagent/toxin/mindbreaker = 35, /datum/reagent/toxin/lipolicide = 15) + +/obj/item/clothing/mask/cigarette/candy + name = "Little Timmy's candy cigarette" + desc = "For all ages*! Doesn't contain any amount of nicotine. Health and safety risks can be read on the tip of the cigarette." + smoketime = 120 + icon_on = "candyon" + icon_off = "candyoff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. + inhand_icon_state = "candyoff" + icon_state = "candyoff" + type_butt = /obj/item/reagent_containers/food/snacks/candy_trash + list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10) + +/obj/item/clothing/mask/cigarette/candy/nicotine + desc = "For all ages*! Doesn't contain any* amount of nicotine. Health and safety risks can be read on the tip of the cigarette." + type_butt = /obj/item/reagent_containers/food/snacks/candy_trash/nicotine + list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10, /datum/reagent/drug/nicotine = 20) //oh no! + smoke_all = TRUE //timmy's not getting out of this one + +/obj/item/cigbutt/roach + name = "roach" + desc = "A manky old roach, or for non-stoners, a used rollup." + icon_state = "roach" + +/obj/item/cigbutt/roach/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + +//////////// +// CIGARS // +//////////// +/obj/item/clothing/mask/cigarette/cigar + name = "premium cigar" + desc = "A brown roll of tobacco and... well, you're not quite sure. This thing's huge!" + icon_state = "cigaroff" + icon_on = "cigaron" + icon_off = "cigaroff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. + type_butt = /obj/item/cigbutt/cigarbutt + throw_speed = 0.5 + inhand_icon_state = "cigaroff" + smoketime = 300 // 11 minutes + chem_volume = 40 + list_reagents = list(/datum/reagent/drug/nicotine = 25) + +/obj/item/clothing/mask/cigarette/cigar/cohiba + name = "\improper Cohiba Robusto cigar" + desc = "There's little more you could want from a cigar." + icon_state = "cigar2off" + icon_on = "cigar2on" + icon_off = "cigar2off" + smoketime = 600 // 20 minutes + chem_volume = 80 + list_reagents =list(/datum/reagent/drug/nicotine = 40) + +/obj/item/clothing/mask/cigarette/cigar/havana + name = "premium Havanian cigar" + desc = "A cigar fit for only the best of the best." + icon_state = "cigar2off" + icon_on = "cigar2on" + icon_off = "cigar2off" + smoketime = 900 // 30 minutes + chem_volume = 50 + list_reagents =list(/datum/reagent/drug/nicotine = 15) + +/obj/item/cigbutt + name = "cigarette butt" + desc = "A manky old cigarette butt." + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "cigbutt" + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + grind_results = list(/datum/reagent/carbon = 2) + +/obj/item/cigbutt/cigarbutt + name = "cigar butt" + desc = "A manky old cigar butt." + icon_state = "cigarbutt" + +///////////////// +//SMOKING PIPES// +///////////////// +/obj/item/clothing/mask/cigarette/pipe + name = "smoking pipe" + desc = "A pipe, for smoking. Probably made of meerschaum or something." + icon_state = "pipeoff" + inhand_icon_state = "pipeoff" + icon_on = "pipeon" //Note - these are in masks.dmi + icon_off = "pipeoff" + smoketime = 0 + chem_volume = 100 + list_reagents = null + var/packeditem = 0 + +/obj/item/clothing/mask/cigarette/pipe/Initialize() + . = ..() + name = "empty [initial(name)]" + +/obj/item/clothing/mask/cigarette/pipe/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/clothing/mask/cigarette/pipe/process() + var/turf/location = get_turf(src) + smoketime-- + if(smoketime < 1) + new /obj/effect/decal/cleanable/ash(location) + if(ismob(loc)) + var/mob/living/M = loc + to_chat(M, "Your [name] goes out.") + lit = 0 + icon_state = icon_off + inhand_icon_state = icon_off + M.update_inv_wear_mask() + packeditem = 0 + name = "empty [initial(name)]" + STOP_PROCESSING(SSobj, src) + return + open_flame() + if(reagents && reagents.total_volume) // check if it has any reagents at all + handle_reagents() + + +/obj/item/clothing/mask/cigarette/pipe/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/G = O + if(!packeditem) + if(G.dry == 1) + to_chat(user, "You stuff [O] into [src].") + smoketime = 400 + packeditem = 1 + name = "[O.name]-packed [initial(name)]" + if(O.reagents) + O.reagents.trans_to(src, O.reagents.total_volume, transfered_by = user) + qdel(O) + else + to_chat(user, "It has to be dried first!") + else + to_chat(user, "It is already packed!") + else + var/lighting_text = O.ignition_effect(src,user) + if(lighting_text) + if(smoketime > 0) + light(lighting_text) + else + to_chat(user, "There is nothing to smoke!") + else + return ..() + +/obj/item/clothing/mask/cigarette/pipe/attack_self(mob/user) + var/turf/location = get_turf(user) + if(lit) + user.visible_message("[user] puts out [src].", "You put out [src].") + lit = 0 + icon_state = icon_off + inhand_icon_state = icon_off + STOP_PROCESSING(SSobj, src) + return + if(!lit && smoketime > 0) + to_chat(user, "You empty [src] onto [location].") + new /obj/effect/decal/cleanable/ash(location) + packeditem = 0 + smoketime = 0 + reagents.clear_reagents() + name = "empty [initial(name)]" + return + +/obj/item/clothing/mask/cigarette/pipe/cobpipe + name = "corn cob pipe" + desc = "A nicotine delivery system popularized by folksy backwoodsmen and kept popular in the modern age and beyond by space hipsters. Can be loaded with objects." + icon_state = "cobpipeoff" + inhand_icon_state = "cobpipeoff" + icon_on = "cobpipeon" //Note - these are in masks.dmi + icon_off = "cobpipeoff" + smoketime = 0 + + +///////// +//ZIPPO// +///////// +/obj/item/lighter + name = "\improper Zippo lighter" + desc = "The zippo." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "zippo" + inhand_icon_state = "zippo" + w_class = WEIGHT_CLASS_TINY + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + var/lit = 0 + var/fancy = TRUE + var/overlay_state + var/overlay_list = list( + "plain", + "dame", + "thirteen", + "snake" + ) + heat = 1500 + resistance_flags = FIRE_PROOF + light_color = LIGHT_COLOR_FIRE + grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/fuel/oil = 5) + custom_price = 55 + +/obj/item/lighter/Initialize() + . = ..() + if(!overlay_state) + overlay_state = pick(overlay_list) + update_icon() + +/obj/item/lighter/cyborg_unequip(mob/user) + if(!lit) + return + set_lit(FALSE) + +/obj/item/lighter/suicide_act(mob/living/carbon/user) + if (lit) + user.visible_message("[user] begins holding \the [src]'s flame up to [user.p_their()] face! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/items/welder.ogg', 50, TRUE) + return FIRELOSS + else + user.visible_message("[user] begins whacking [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/lighter/update_overlays() + . = ..() + . += create_lighter_overlay() + +/obj/item/lighter/update_icon_state() + icon_state = "[initial(icon_state)][lit ? "-on" : ""]" + +/obj/item/lighter/proc/create_lighter_overlay() + return mutable_appearance(icon, "lighter_overlay_[overlay_state][lit ? "-on" : ""]") + +/obj/item/lighter/ignition_effect(atom/A, mob/user) + if(get_temperature()) + . = "With a single flick of [user.p_their()] wrist, [user] smoothly lights [A] with [src]. Damn [user.p_theyre()] cool." + +/obj/item/lighter/proc/set_lit(new_lit) + lit = new_lit + if(lit) + force = 5 + damtype = "fire" + hitsound = 'sound/items/welder.ogg' + attack_verb = list("burnt", "singed") + set_light(1) + START_PROCESSING(SSobj, src) + else + hitsound = "swing_hit" + force = 0 + attack_verb = null //human_defense.dm takes care of it + set_light(0) + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/lighter/extinguish() + set_lit(FALSE) + +/obj/item/lighter/attack_self(mob/living/user) + if(user.is_holding(src)) + if(!lit) + set_lit(TRUE) + if(fancy) + user.visible_message("Without even breaking stride, [user] flips open and lights [src] in one smooth movement.", "Without even breaking stride, you flip open and light [src] in one smooth movement.") + else + var/prot = FALSE + var/mob/living/carbon/human/H = user + + if(istype(H) && H.gloves) + var/obj/item/clothing/gloves/G = H.gloves + if(G.max_heat_protection_temperature) + prot = (G.max_heat_protection_temperature > 360) + else + prot = TRUE + + if(prot || prob(75)) + user.visible_message("After a few attempts, [user] manages to light [src].", "After a few attempts, you manage to light [src].") + else + var/hitzone = user.held_index_to_dir(user.active_hand_index) == "r" ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND + user.apply_damage(5, BURN, hitzone) + user.visible_message("After a few attempts, [user] manages to light [src] - however, [user.p_they()] burn [user.p_their()] finger in the process.", "You burn yourself while lighting the lighter!") + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "burnt_thumb", /datum/mood_event/burnt_thumb) + + else + set_lit(FALSE) + if(fancy) + user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what [user.p_theyre()] doing. Wow.", "You quietly shut off [src] without even looking at what you're doing. Wow.") + else + user.visible_message("[user] quietly shuts off [src].", "You quietly shut off [src].") + else + . = ..() + +/obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user) + if(lit && M.IgniteMob()) + message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") + log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "The [cig.name] is already lit!") + if(M == user) + cig.attackby(src, user) + else + if(fancy) + cig.light("[user] whips the [name] out and holds it for [M]. [user.p_their(TRUE)] arm is as steady as the unflickering flame [user.p_they()] light[user.p_s()] \the [cig] with.") + else + cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") + else + ..() + +/obj/item/lighter/process() + open_flame() + +/obj/item/lighter/get_temperature() + return lit * heat + + +/obj/item/lighter/greyscale + name = "cheap lighter" + desc = "A cheap lighter." + icon_state = "lighter" + fancy = FALSE + overlay_list = list( + "transp", + "tall", + "matte", + "zoppo" //u cant stoppo th zoppo + ) + var/lighter_color + var/list/color_list = list( //Same 16 color selection as electronic assemblies + COLOR_ASSEMBLY_BLACK, + COLOR_FLOORTILE_GRAY, + COLOR_ASSEMBLY_BGRAY, + COLOR_ASSEMBLY_WHITE, + COLOR_ASSEMBLY_RED, + COLOR_ASSEMBLY_ORANGE, + COLOR_ASSEMBLY_BEIGE, + COLOR_ASSEMBLY_BROWN, + COLOR_ASSEMBLY_GOLD, + COLOR_ASSEMBLY_YELLOW, + COLOR_ASSEMBLY_GURKHA, + COLOR_ASSEMBLY_LGREEN, + COLOR_ASSEMBLY_GREEN, + COLOR_ASSEMBLY_LBLUE, + COLOR_ASSEMBLY_BLUE, + COLOR_ASSEMBLY_PURPLE + ) + +/obj/item/lighter/greyscale/Initialize() + . = ..() + if(!lighter_color) + lighter_color = pick(color_list) + update_icon() + +/obj/item/lighter/greyscale/create_lighter_overlay() + var/mutable_appearance/lighter_overlay = ..() + lighter_overlay.color = lighter_color + return lighter_overlay + +/obj/item/lighter/greyscale/ignition_effect(atom/A, mob/user) + if(get_temperature()) + . = "After some fiddling, [user] manages to light [A] with [src]." + + +/obj/item/lighter/slime + name = "slime zippo" + desc = "A specialty zippo made from slimes and industry. Has a much hotter flame than normal." + icon_state = "slighter" + heat = 3000 //Blue flame! + light_color = LIGHT_COLOR_CYAN + overlay_state = "slime" + grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/medicine/pyroxadone = 5) + + +/////////// +//ROLLING// +/////////// +/obj/item/rollingpaper + name = "rolling paper" + desc = "A thin piece of paper used to make fine smokeables." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig_paper" + w_class = WEIGHT_CLASS_TINY + +/obj/item/rollingpaper/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity) + return + if(istype(target, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/O = target + if(O.dry) + var/obj/item/clothing/mask/cigarette/rollie/R = new /obj/item/clothing/mask/cigarette/rollie(user.loc) + R.chem_volume = target.reagents.total_volume + target.reagents.trans_to(R, R.chem_volume, transfered_by = user) + qdel(target) + qdel(src) + user.put_in_active_hand(R) + to_chat(user, "You roll the [target.name] into a rolling paper.") + R.desc = "Dried [target.name] rolled up in a thin piece of paper." + else + to_chat(user, "You need to dry this first!") + +/////////////// +//VAPE NATION// +/////////////// +/obj/item/clothing/mask/vape + name = "\improper E-Cigarette" + desc = "A classy and highly sophisticated electronic cigarette, for classy and dignified gentlemen. A warning label reads \"Warning: Do not fill with flammable materials.\""//<<< i'd vape to that. + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "red_vape" + inhand_icon_state = null + w_class = WEIGHT_CLASS_TINY + var/chem_volume = 100 + var/vapetime = 0 //this so it won't puff out clouds every tick + var/screw = 0 // kinky + var/super = 0 //for the fattest vapes dude. + +/obj/item/clothing/mask/vape/suicide_act(mob/user) + user.visible_message("[user] is puffin hard on dat vape, [user.p_they()] trying to join the vape life on a whole notha plane!")//it doesn't give you cancer, it is cancer + return (TOXLOSS|OXYLOSS) + + +/obj/item/clothing/mask/vape/Initialize(mapload, param_color) + . = ..() + create_reagents(chem_volume, NO_REACT) + reagents.add_reagent(/datum/reagent/drug/nicotine, 50) + if(!param_color) + param_color = pick("red","blue","black","white","green","purple","yellow","orange") + icon_state = "[param_color]_vape" + inhand_icon_state = "[param_color]_vape" + +/obj/item/clothing/mask/vape/attackby(obj/item/O, mob/user, params) + if(O.tool_behaviour == TOOL_SCREWDRIVER) + if(!screw) + screw = TRUE + to_chat(user, "You open the cap on [src].") + reagents.flags |= OPENCONTAINER + if(obj_flags & EMAGGED) + add_overlay("vapeopen_high") + else if(super) + add_overlay("vapeopen_med") + else + add_overlay("vapeopen_low") + else + screw = FALSE + to_chat(user, "You close the cap on [src].") + reagents.flags &= ~(OPENCONTAINER) + cut_overlays() + + if(O.tool_behaviour == TOOL_MULTITOOL) + if(screw && !(obj_flags & EMAGGED))//also kinky + if(!super) + cut_overlays() + super = 1 + to_chat(user, "You increase the voltage of [src].") + add_overlay("vapeopen_med") + else + cut_overlays() + super = 0 + to_chat(user, "You decrease the voltage of [src].") + add_overlay("vapeopen_low") + + if(screw && (obj_flags & EMAGGED)) + to_chat(user, "[src] can't be modified!") + else + ..() + + +/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY. + if(screw) + if(!(obj_flags & EMAGGED)) + cut_overlays() + obj_flags |= EMAGGED + super = 0 + to_chat(user, "You maximize the voltage of [src].") + add_overlay("vapeopen_high") + var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect + sp.set_up(5, 1, src) + sp.start() + else + to_chat(user, "[src] is already emagged!") + else + to_chat(user, "You need to open the cap to do that!") + +/obj/item/clothing/mask/vape/attack_self(mob/user) + if(reagents.total_volume > 0) + to_chat(user, "You empty [src] of all reagents.") + reagents.clear_reagents() + +/obj/item/clothing/mask/vape/equipped(mob/user, slot) + . = ..() + if(slot == ITEM_SLOT_MASK) + if(!screw) + to_chat(user, "You start puffing on the vape.") + reagents.flags &= ~(NO_REACT) + START_PROCESSING(SSobj, src) + else //it will not start if the vape is opened. + to_chat(user, "You need to close the cap first!") + +/obj/item/clothing/mask/vape/dropped(mob/user) + . = ..() + if(user.get_item_by_slot(ITEM_SLOT_MASK) == src) + reagents.flags |= NO_REACT + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/mask/vape/proc/hand_reagents()//had to rename to avoid duplicate error + if(reagents.total_volume) + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob + var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) //this will react instantly, making them a little more dangerous than cigarettes + reagents.expose(C, INGEST, fraction) + if(!reagents.trans_to(C, REAGENTS_METABOLISM)) + reagents.remove_any(REAGENTS_METABOLISM) + if(reagents.get_reagent_amount(/datum/reagent/fuel)) + //HOT STUFF + C.adjust_fire_stacks(2) + C.IgniteMob() + + if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) + e.start() + qdel(src) + return + reagents.remove_any(REAGENTS_METABOLISM) + +/obj/item/clothing/mask/vape/process() + var/mob/living/M = loc + + if(isliving(loc)) + M.IgniteMob() + + vapetime++ + + if(!reagents.total_volume) + if(ismob(loc)) + to_chat(M, "[src] is empty!") + STOP_PROCESSING(SSobj, src) + //it's reusable so it won't unequip when empty + return + //open flame removed because vapes are a closed system, they won't light anything on fire + + if(super && vapetime > 3)//Time to start puffing those fat vapes, yo. + var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new + s.set_up(reagents, 1, 24, loc) + s.start() + vapetime = 0 + + if((obj_flags & EMAGGED) && vapetime > 3) + var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new + s.set_up(reagents, 4, 24, loc) + s.start() + vapetime = 0 + if(prob(5))//small chance for the vape to break and deal damage if it's emagged + playsound(get_turf(src), 'sound/effects/pop_expl.ogg', 50, FALSE) + M.apply_damage(20, BURN, BODY_ZONE_HEAD) + M.Paralyze(300, 1, 0) + var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread + sp.set_up(5, 1, src) + sp.start() + to_chat(M, "[src] suddenly explodes in your mouth!") + qdel(src) + return + + if(reagents && reagents.total_volume) + hand_reagents() diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm index 87b22d30d08..ee46869c6c3 100644 --- a/code/game/objects/items/clown_items.dm +++ b/code/game/objects/items/clown_items.dm @@ -1,233 +1,233 @@ -/* Clown Items - * Contains: - * Soap - * Bike Horns - * Air Horns - * Canned Laughter - */ - -/* - * Soap - */ - -/obj/item/soap - name = "soap" - desc = "A cheap bar of soap. Doesn't smell." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "soap" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - item_flags = NOBLUDGEON - throwforce = 0 - throw_speed = 3 - throw_range = 7 - grind_results = list(/datum/reagent/lye = 10) - var/cleanspeed = 35 //slower than mop - force_string = "robust... against germs" - var/uses = 100 - -/obj/item/soap/ComponentInitialize() - . = ..() - AddComponent(/datum/component/slippery, 80) - -/obj/item/soap/examine(mob/user) - . = ..() - var/max_uses = initial(uses) - var/msg = "It looks like it just came out of the package." - if(uses != max_uses) - var/percentage_left = uses / max_uses - switch(percentage_left) - if(0 to 0.15) - msg = "There's just a tiny bit left of what it used to be, you're not sure it'll last much longer." - if(0.15 to 0.30) - msg = "It's dissolved quite a bit, but there's still some life to it." - if(0.30 to 0.50) - msg = "It's past its prime, but it's definitely still good." - if(0.50 to 0.75) - msg = "It's started to get a little smaller than it used to be, but it'll definitely still last for a while." - else - msg = "It's seen some light use, but it's still pretty fresh." - . += "[msg]" - -/obj/item/soap/nanotrasen - desc = "A heavy duty bar of Nanotrasen brand soap. Smells of plasma." - grind_results = list(/datum/reagent/toxin/plasma = 10, /datum/reagent/lye = 10) - icon_state = "soapnt" - cleanspeed = 28 //janitor gets this - uses = 300 - -/obj/item/soap/homemade - desc = "A homemade bar of soap. Smells of... well...." - icon_state = "soapgibs" - cleanspeed = 30 // faster to reward chemists for going to the effort - -/obj/item/soap/deluxe - desc = "A deluxe Waffle Co. brand bar of soap. Smells of high-class luxury." - icon_state = "soapdeluxe" - cleanspeed = 20 //captain gets one of these - -/obj/item/soap/syndie - desc = "An untrustworthy bar of soap made of strong chemical agents that dissolve blood faster." - icon_state = "soapsyndie" - cleanspeed = 5 //faster than mop so it is useful for traitors who want to clean crime scenes - -/obj/item/soap/omega - name = "omega soap" - desc = "The most advanced soap known to mankind." - icon_state = "soapomega" - cleanspeed = 3 //Only the truest of mind soul and body get one of these - uses = 301 - -/obj/item/soap/omega/suicide_act(mob/user) - user.visible_message("[user] is using [src] to scrub themselves from the timeline! It looks like [user.p_theyre()] trying to commit suicide!") - new /obj/structure/chrono_field(user.loc, user) - return MANUAL_SUICIDE - -/obj/item/paper/fluff/stations/soap - name = "ancient janitorial poem" - desc = "An old paper that has passed many hands." - info = "The legend of the omega soap

                    Essence of potato. Juice, not grind.

                    A lizard's tail, turned into wine.

                    powder of monkey, to help the workload.

                    Some Krokodil, because meth would explode.

                    Nitric acid and Baldium, for organic dissolving.

                    A cup filled with Hooch, for sinful absolving

                    Some Bluespace Dust, for removal of stains.

                    A syringe full of Pump-up, it's security's bane.

                    Add a can of Space Cola, because we've been paid.

                    Heat as hot as you can, let the soap be your blade.

                    Ten units of each regent create a soap that could topple all others." - - -/obj/item/soap/suicide_act(mob/user) - user.say(";FFFFFFFFFFFFFFFFUUUUUUUDGE!!", forced="soap suicide") - user.visible_message("[user] lifts [src] to [user.p_their()] mouth and gnaws on it furiously, producing a thick froth! [user.p_they(TRUE)]'ll never get that BB gun now!") - new /obj/effect/particle_effect/foam(loc) - return (TOXLOSS) - -/obj/item/soap/proc/decreaseUses(mob/user) - var/skillcheck = user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER) - if(prob(skillcheck*100)) //higher level = more uses assuming RNG is nice - uses-- - if(uses <= 0) - to_chat(user, "[src] crumbles into tiny bits!") - qdel(src) - -/obj/item/soap/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity || !check_allowed_items(target)) - return - var/clean_speedies = cleanspeed * min(user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER)+0.1,1) //less scaling for soapies - //I couldn't feasibly fix the overlay bugs caused by cleaning items we are wearing. - //So this is a workaround. This also makes more sense from an IC standpoint. ~Carn - if(user.client && ((target in user.client.screen) && !user.is_holding(target))) - to_chat(user, "You need to take that [target.name] off before cleaning it!") - else if(istype(target, /obj/effect/decal/cleanable)) - user.visible_message("[user] begins to scrub \the [target.name] out with [src].", "You begin to scrub \the [target.name] out with [src]...") - if(do_after(user, clean_speedies, target = target)) - to_chat(user, "You scrub \the [target.name] out.") - var/obj/effect/decal/cleanable/cleanies = target - user?.mind.adjust_experience(/datum/skill/cleaning, max(round(cleanies.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT),0)) //again, intentional that this does NOT round but mops do. - qdel(target) - decreaseUses(user) - - else if(ishuman(target) && user.zone_selected == BODY_ZONE_PRECISE_MOUTH) - var/mob/living/carbon/human/H = user - user.visible_message("\the [user] washes \the [target]'s mouth out with [src.name]!", "You wash \the [target]'s mouth out with [src.name]!") //washes mouth out with soap sounds better than 'the soap' here if(user.zone_selected == "mouth") - if(H.lip_style) - user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) - H.lip_style = null //removes lipstick - H.update_body() - decreaseUses(user) - return - else if(istype(target, /obj/structure/window)) - user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") - if(do_after(user, clean_speedies, target = target)) - to_chat(user, "You clean \the [target.name].") - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - target.set_opacity(initial(target.opacity)) - user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) - decreaseUses(user) - else - user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") - if(do_after(user, clean_speedies, target = target)) - to_chat(user, "You clean \the [target.name].") - for(var/obj/effect/decal/cleanable/C in target) - user?.mind.adjust_experience(/datum/skill/cleaning, round(C.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT)) - qdel(C) - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) - user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) - decreaseUses(user) - return - - -/* - * Bike Horns - */ - -/obj/item/bikehorn - name = "bike horn" - desc = "A horn off of a bicycle." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "bike_horn" - inhand_icon_state = "bike_horn" - lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' - throwforce = 0 - hitsound = null //To prevent tap.ogg playing, as the item lacks of force - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT - throw_speed = 3 - throw_range = 7 - attack_verb = list("HONKED") - -/obj/item/bikehorn/Initialize() - . = ..() - AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50) - -/obj/item/bikehorn/attack(mob/living/carbon/M, mob/living/carbon/user) - if(user != M && ishuman(user)) - var/mob/living/carbon/human/H = user - if (HAS_TRAIT(H, TRAIT_CLUMSY)) //only clowns can unlock its true powers - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "honk", /datum/mood_event/honk) - return ..() - -/obj/item/bikehorn/suicide_act(mob/user) - user.visible_message("[user] solemnly points [src] at [user.p_their()] temple! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) - return (BRUTELOSS) - -//air horn -/obj/item/bikehorn/airhorn - name = "air horn" - desc = "Damn son, where'd you find this?" - icon_state = "air_horn" - -/obj/item/bikehorn/airhorn/Initialize() - . = ..() - AddComponent(/datum/component/squeak, list('sound/items/airhorn2.ogg'=1), 50) - -//golden bikehorn -/obj/item/bikehorn/golden - name = "golden bike horn" - desc = "Golden? Clearly, it's made with bananium! Honk!" - icon_state = "gold_horn" - inhand_icon_state = "gold_horn" - var/flip_cooldown = 0 - -/obj/item/bikehorn/golden/attack() - if(flip_cooldown < world.time) - flip_mobs() - return ..() - -/obj/item/bikehorn/golden/attack_self(mob/user) - if(flip_cooldown < world.time) - flip_mobs() - ..() - -/obj/item/bikehorn/golden/proc/flip_mobs(mob/living/carbon/M, mob/user) - var/turf/T = get_turf(src) - for(M in ohearers(7, T)) - if(M.can_hear()) - M.emote("flip") - flip_cooldown = world.time + 7 - -//canned laughter -/obj/item/reagent_containers/food/drinks/soda_cans/canned_laughter - name = "Canned Laughter" - desc = "Just looking at this makes you want to giggle." - icon_state = "laughter" - list_reagents = list(/datum/reagent/consumable/laughter = 50) +/* Clown Items + * Contains: + * Soap + * Bike Horns + * Air Horns + * Canned Laughter + */ + +/* + * Soap + */ + +/obj/item/soap + name = "soap" + desc = "A cheap bar of soap. Doesn't smell." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "soap" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + item_flags = NOBLUDGEON + throwforce = 0 + throw_speed = 3 + throw_range = 7 + grind_results = list(/datum/reagent/lye = 10) + var/cleanspeed = 35 //slower than mop + force_string = "robust... against germs" + var/uses = 100 + +/obj/item/soap/ComponentInitialize() + . = ..() + AddComponent(/datum/component/slippery, 80) + +/obj/item/soap/examine(mob/user) + . = ..() + var/max_uses = initial(uses) + var/msg = "It looks like it just came out of the package." + if(uses != max_uses) + var/percentage_left = uses / max_uses + switch(percentage_left) + if(0 to 0.15) + msg = "There's just a tiny bit left of what it used to be, you're not sure it'll last much longer." + if(0.15 to 0.30) + msg = "It's dissolved quite a bit, but there's still some life to it." + if(0.30 to 0.50) + msg = "It's past its prime, but it's definitely still good." + if(0.50 to 0.75) + msg = "It's started to get a little smaller than it used to be, but it'll definitely still last for a while." + else + msg = "It's seen some light use, but it's still pretty fresh." + . += "[msg]" + +/obj/item/soap/nanotrasen + desc = "A heavy duty bar of Nanotrasen brand soap. Smells of plasma." + grind_results = list(/datum/reagent/toxin/plasma = 10, /datum/reagent/lye = 10) + icon_state = "soapnt" + cleanspeed = 28 //janitor gets this + uses = 300 + +/obj/item/soap/homemade + desc = "A homemade bar of soap. Smells of... well...." + icon_state = "soapgibs" + cleanspeed = 30 // faster to reward chemists for going to the effort + +/obj/item/soap/deluxe + desc = "A deluxe Waffle Co. brand bar of soap. Smells of high-class luxury." + icon_state = "soapdeluxe" + cleanspeed = 20 //captain gets one of these + +/obj/item/soap/syndie + desc = "An untrustworthy bar of soap made of strong chemical agents that dissolve blood faster." + icon_state = "soapsyndie" + cleanspeed = 5 //faster than mop so it is useful for traitors who want to clean crime scenes + +/obj/item/soap/omega + name = "omega soap" + desc = "The most advanced soap known to mankind." + icon_state = "soapomega" + cleanspeed = 3 //Only the truest of mind soul and body get one of these + uses = 301 + +/obj/item/soap/omega/suicide_act(mob/user) + user.visible_message("[user] is using [src] to scrub themselves from the timeline! It looks like [user.p_theyre()] trying to commit suicide!") + new /obj/structure/chrono_field(user.loc, user) + return MANUAL_SUICIDE + +/obj/item/paper/fluff/stations/soap + name = "ancient janitorial poem" + desc = "An old paper that has passed many hands." + info = "The legend of the omega soap

                    Essence of potato. Juice, not grind.

                    A lizard's tail, turned into wine.

                    powder of monkey, to help the workload.

                    Some Krokodil, because meth would explode.

                    Nitric acid and Baldium, for organic dissolving.

                    A cup filled with Hooch, for sinful absolving

                    Some Bluespace Dust, for removal of stains.

                    A syringe full of Pump-up, it's security's bane.

                    Add a can of Space Cola, because we've been paid.

                    Heat as hot as you can, let the soap be your blade.

                    Ten units of each regent create a soap that could topple all others." + + +/obj/item/soap/suicide_act(mob/user) + user.say(";FFFFFFFFFFFFFFFFUUUUUUUDGE!!", forced="soap suicide") + user.visible_message("[user] lifts [src] to [user.p_their()] mouth and gnaws on it furiously, producing a thick froth! [user.p_they(TRUE)]'ll never get that BB gun now!") + new /obj/effect/particle_effect/foam(loc) + return (TOXLOSS) + +/obj/item/soap/proc/decreaseUses(mob/user) + var/skillcheck = user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER) + if(prob(skillcheck*100)) //higher level = more uses assuming RNG is nice + uses-- + if(uses <= 0) + to_chat(user, "[src] crumbles into tiny bits!") + qdel(src) + +/obj/item/soap/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity || !check_allowed_items(target)) + return + var/clean_speedies = cleanspeed * min(user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER)+0.1,1) //less scaling for soapies + //I couldn't feasibly fix the overlay bugs caused by cleaning items we are wearing. + //So this is a workaround. This also makes more sense from an IC standpoint. ~Carn + if(user.client && ((target in user.client.screen) && !user.is_holding(target))) + to_chat(user, "You need to take that [target.name] off before cleaning it!") + else if(istype(target, /obj/effect/decal/cleanable)) + user.visible_message("[user] begins to scrub \the [target.name] out with [src].", "You begin to scrub \the [target.name] out with [src]...") + if(do_after(user, clean_speedies, target = target)) + to_chat(user, "You scrub \the [target.name] out.") + var/obj/effect/decal/cleanable/cleanies = target + user?.mind.adjust_experience(/datum/skill/cleaning, max(round(cleanies.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT),0)) //again, intentional that this does NOT round but mops do. + qdel(target) + decreaseUses(user) + + else if(ishuman(target) && user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + var/mob/living/carbon/human/H = user + user.visible_message("\the [user] washes \the [target]'s mouth out with [src.name]!", "You wash \the [target]'s mouth out with [src.name]!") //washes mouth out with soap sounds better than 'the soap' here if(user.zone_selected == "mouth") + if(H.lip_style) + user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) + H.lip_style = null //removes lipstick + H.update_body() + decreaseUses(user) + return + else if(istype(target, /obj/structure/window)) + user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") + if(do_after(user, clean_speedies, target = target)) + to_chat(user, "You clean \the [target.name].") + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + target.set_opacity(initial(target.opacity)) + user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) + decreaseUses(user) + else + user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") + if(do_after(user, clean_speedies, target = target)) + to_chat(user, "You clean \the [target.name].") + for(var/obj/effect/decal/cleanable/C in target) + user?.mind.adjust_experience(/datum/skill/cleaning, round(C.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT)) + qdel(C) + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) + decreaseUses(user) + return + + +/* + * Bike Horns + */ + +/obj/item/bikehorn + name = "bike horn" + desc = "A horn off of a bicycle." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "bike_horn" + inhand_icon_state = "bike_horn" + lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' + throwforce = 0 + hitsound = null //To prevent tap.ogg playing, as the item lacks of force + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT + throw_speed = 3 + throw_range = 7 + attack_verb = list("HONKED") + +/obj/item/bikehorn/Initialize() + . = ..() + AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50) + +/obj/item/bikehorn/attack(mob/living/carbon/M, mob/living/carbon/user) + if(user != M && ishuman(user)) + var/mob/living/carbon/human/H = user + if (HAS_TRAIT(H, TRAIT_CLUMSY)) //only clowns can unlock its true powers + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "honk", /datum/mood_event/honk) + return ..() + +/obj/item/bikehorn/suicide_act(mob/user) + user.visible_message("[user] solemnly points [src] at [user.p_their()] temple! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) + return (BRUTELOSS) + +//air horn +/obj/item/bikehorn/airhorn + name = "air horn" + desc = "Damn son, where'd you find this?" + icon_state = "air_horn" + +/obj/item/bikehorn/airhorn/Initialize() + . = ..() + AddComponent(/datum/component/squeak, list('sound/items/airhorn2.ogg'=1), 50) + +//golden bikehorn +/obj/item/bikehorn/golden + name = "golden bike horn" + desc = "Golden? Clearly, it's made with bananium! Honk!" + icon_state = "gold_horn" + inhand_icon_state = "gold_horn" + var/flip_cooldown = 0 + +/obj/item/bikehorn/golden/attack() + if(flip_cooldown < world.time) + flip_mobs() + return ..() + +/obj/item/bikehorn/golden/attack_self(mob/user) + if(flip_cooldown < world.time) + flip_mobs() + ..() + +/obj/item/bikehorn/golden/proc/flip_mobs(mob/living/carbon/M, mob/user) + var/turf/T = get_turf(src) + for(M in ohearers(7, T)) + if(M.can_hear()) + M.emote("flip") + flip_cooldown = world.time + 7 + +//canned laughter +/obj/item/reagent_containers/food/drinks/soda_cans/canned_laughter + name = "Canned Laughter" + desc = "Just looking at this makes you want to giggle." + icon_state = "laughter" + list_reagents = list(/datum/reagent/consumable/laughter = 50) diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index 52d043d441c..da9b2cad9ea 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -1,231 +1,231 @@ -/obj/item/lipstick - gender = PLURAL - name = "red lipstick" - desc = "A generic brand of lipstick." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "lipstick" - w_class = WEIGHT_CLASS_TINY - var/colour = "red" - var/open = FALSE - -/obj/item/lipstick/purple - name = "purple lipstick" - colour = "purple" - -/obj/item/lipstick/jade - //It's still called Jade, but theres no HTML color for jade, so we use lime. - name = "jade lipstick" - colour = "lime" - -/obj/item/lipstick/black - name = "black lipstick" - colour = "black" - -/obj/item/lipstick/random - name = "lipstick" - icon_state = "random_lipstick" - -/obj/item/lipstick/random/Initialize() - . = ..() - icon_state = "lipstick" - colour = pick("red","purple","lime","black","green","blue","white") - name = "[colour] lipstick" - -/obj/item/lipstick/attack_self(mob/user) - cut_overlays() - to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") - open = !open - if(open) - var/mutable_appearance/colored_overlay = mutable_appearance(icon, "lipstick_uncap_color") - colored_overlay.color = colour - icon_state = "lipstick_uncap" - add_overlay(colored_overlay) - else - icon_state = "lipstick" - -/obj/item/lipstick/attack(mob/M, mob/user) - if(!open) - return - - if(!ismob(M)) - return - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.is_mouth_covered()) - to_chat(user, "Remove [ H == user ? "your" : "[H.p_their()]" ] mask!") - return - if(H.lip_style) //if they already have lipstick on - to_chat(user, "You need to wipe off the old lipstick first!") - return - if(H == user) - user.visible_message("[user] does [user.p_their()] lips with \the [src].", \ - "You take a moment to apply \the [src]. Perfect!") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ - "You begin to apply \the [src] on [H]'s lips...") - if(do_after(user, 20, target = H)) - user.visible_message("[user] does [H]'s lips with \the [src].", \ - "You apply \the [src] on [H]'s lips.") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - to_chat(user, "Where are the lips on that?") - -//you can wipe off lipstick with paper! -/obj/item/paper/attack(mob/M, mob/user) - if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) - if(!ismob(M)) - return - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H == user) - to_chat(user, "You wipe off the lipstick with [src].") - H.lip_style = null - H.update_body() - else - user.visible_message("[user] begins to wipe [H]'s lipstick off with \the [src].", \ - "You begin to wipe off [H]'s lipstick...") - if(do_after(user, 10, target = H)) - user.visible_message("[user] wipes [H]'s lipstick off with \the [src].", \ - "You wipe off [H]'s lipstick.") - H.lip_style = null - H.update_body() - else - ..() - -/obj/item/razor - name = "electric razor" - desc = "The latest and greatest power razor born from the science of shaving." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "razor" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_TINY - -/obj/item/razor/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins shaving [user.p_them()]self without the razor guard! It looks like [user.p_theyre()] trying to commit suicide!") - shave(user, BODY_ZONE_PRECISE_MOUTH) - shave(user, BODY_ZONE_HEAD)//doesnt need to be BODY_ZONE_HEAD specifically, but whatever - return BRUTELOSS - -/obj/item/razor/proc/shave(mob/living/carbon/human/H, location = BODY_ZONE_PRECISE_MOUTH) - if(location == BODY_ZONE_PRECISE_MOUTH) - H.facial_hairstyle = "Shaved" - else - H.hairstyle = "Skinhead" - - H.update_hair() - playsound(loc, 'sound/items/welder2.ogg', 20, TRUE) - - -/obj/item/razor/attack(mob/M, mob/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/location = user.zone_selected - if((location in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_HEAD)) && !H.get_bodypart(BODY_ZONE_HEAD)) - to_chat(user, "[H] doesn't have a head!") - return - if(location == BODY_ZONE_PRECISE_MOUTH) - if(user.a_intent == INTENT_HELP) - if(H.gender == MALE) - if (H == user) - to_chat(user, "You need a mirror to properly style your own facial hair!") - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list - if(!get_location_accessible(H, location)) - to_chat(user, "The mask is in the way!") - return - user.visible_message("[user] tries to change [H]'s facial hairstyle using [src].", "You try to change [H]'s facial hairstyle using [src].") - if(new_style && do_after(user, 60, target = H)) - user.visible_message("[user] successfully changes [H]'s facial hairstyle using [src].", "You successfully change [H]'s facial hairstyle using [src].") - H.facial_hairstyle = new_style - H.update_hair() - return - else - return - - else - if(!(FACEHAIR in H.dna.species.species_traits)) - to_chat(user, "There is no facial hair to shave!") - return - if(!get_location_accessible(H, location)) - to_chat(user, "The mask is in the way!") - return - if(H.facial_hairstyle == "Shaved") - to_chat(user, "Already clean-shaven!") - return - - if(H == user) //shaving yourself - user.visible_message("[user] starts to shave [user.p_their()] facial hair with [src].", \ - "You take a moment to shave your facial hair with [src]...") - if(do_after(user, 50, target = H)) - user.visible_message("[user] shaves [user.p_their()] facial hair clean with [src].", \ - "You finish shaving with [src]. Fast and clean!") - shave(H, location) - else - user.visible_message("[user] tries to shave [H]'s facial hair with [src].", \ - "You start shaving [H]'s facial hair...") - if(do_after(user, 50, target = H)) - user.visible_message("[user] shaves off [H]'s facial hair with [src].", \ - "You shave [H]'s facial hair clean off.") - shave(H, location) - - else if(location == BODY_ZONE_HEAD) - if(user.a_intent == INTENT_HELP) - if (H == user) - to_chat(user, "You need a mirror to properly style your own hair!") - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list - if(!get_location_accessible(H, location)) - to_chat(user, "The headgear is in the way!") - return - if(HAS_TRAIT(H, TRAIT_BALD)) - to_chat(H, "[H] is just way too bald. Like, really really bald.") - return - user.visible_message("[user] tries to change [H]'s hairstyle using [src].", "You try to change [H]'s hairstyle using [src].") - if(new_style && do_after(user, 60, target = H)) - user.visible_message("[user] successfully changes [H]'s hairstyle using [src].", "You successfully change [H]'s hairstyle using [src].") - H.hairstyle = new_style - H.update_hair() - return - - else - if(!(HAIR in H.dna.species.species_traits)) - to_chat(user, "There is no hair to shave!") - return - if(!get_location_accessible(H, location)) - to_chat(user, "The headgear is in the way!") - return - if(H.hairstyle == "Bald" || H.hairstyle == "Balding Hair" || H.hairstyle == "Skinhead") - to_chat(user, "There is not enough hair left to shave!") - return - - if(H == user) //shaving yourself - user.visible_message("[user] starts to shave [user.p_their()] head with [src].", \ - "You start to shave your head with [src]...") - if(do_after(user, 5, target = H)) - user.visible_message("[user] shaves [user.p_their()] head with [src].", \ - "You finish shaving with [src].") - shave(H, location) - else - var/turf/H_loc = H.loc - user.visible_message("[user] tries to shave [H]'s head with [src]!", \ - "You start shaving [H]'s head...") - if(do_after(user, 50, target = H)) - if(H_loc == H.loc) - user.visible_message("[user] shaves [H]'s head bald with [src]!", \ - "You shave [H]'s head bald.") - shave(H, location) - else - ..() - else - ..() +/obj/item/lipstick + gender = PLURAL + name = "red lipstick" + desc = "A generic brand of lipstick." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "lipstick" + w_class = WEIGHT_CLASS_TINY + var/colour = "red" + var/open = FALSE + +/obj/item/lipstick/purple + name = "purple lipstick" + colour = "purple" + +/obj/item/lipstick/jade + //It's still called Jade, but theres no HTML color for jade, so we use lime. + name = "jade lipstick" + colour = "lime" + +/obj/item/lipstick/black + name = "black lipstick" + colour = "black" + +/obj/item/lipstick/random + name = "lipstick" + icon_state = "random_lipstick" + +/obj/item/lipstick/random/Initialize() + . = ..() + icon_state = "lipstick" + colour = pick("red","purple","lime","black","green","blue","white") + name = "[colour] lipstick" + +/obj/item/lipstick/attack_self(mob/user) + cut_overlays() + to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") + open = !open + if(open) + var/mutable_appearance/colored_overlay = mutable_appearance(icon, "lipstick_uncap_color") + colored_overlay.color = colour + icon_state = "lipstick_uncap" + add_overlay(colored_overlay) + else + icon_state = "lipstick" + +/obj/item/lipstick/attack(mob/M, mob/user) + if(!open) + return + + if(!ismob(M)) + return + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.is_mouth_covered()) + to_chat(user, "Remove [ H == user ? "your" : "[H.p_their()]" ] mask!") + return + if(H.lip_style) //if they already have lipstick on + to_chat(user, "You need to wipe off the old lipstick first!") + return + if(H == user) + user.visible_message("[user] does [user.p_their()] lips with \the [src].", \ + "You take a moment to apply \the [src]. Perfect!") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ + "You begin to apply \the [src] on [H]'s lips...") + if(do_after(user, 20, target = H)) + user.visible_message("[user] does [H]'s lips with \the [src].", \ + "You apply \the [src] on [H]'s lips.") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + to_chat(user, "Where are the lips on that?") + +//you can wipe off lipstick with paper! +/obj/item/paper/attack(mob/M, mob/user) + if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + if(!ismob(M)) + return + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H == user) + to_chat(user, "You wipe off the lipstick with [src].") + H.lip_style = null + H.update_body() + else + user.visible_message("[user] begins to wipe [H]'s lipstick off with \the [src].", \ + "You begin to wipe off [H]'s lipstick...") + if(do_after(user, 10, target = H)) + user.visible_message("[user] wipes [H]'s lipstick off with \the [src].", \ + "You wipe off [H]'s lipstick.") + H.lip_style = null + H.update_body() + else + ..() + +/obj/item/razor + name = "electric razor" + desc = "The latest and greatest power razor born from the science of shaving." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "razor" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_TINY + +/obj/item/razor/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins shaving [user.p_them()]self without the razor guard! It looks like [user.p_theyre()] trying to commit suicide!") + shave(user, BODY_ZONE_PRECISE_MOUTH) + shave(user, BODY_ZONE_HEAD)//doesnt need to be BODY_ZONE_HEAD specifically, but whatever + return BRUTELOSS + +/obj/item/razor/proc/shave(mob/living/carbon/human/H, location = BODY_ZONE_PRECISE_MOUTH) + if(location == BODY_ZONE_PRECISE_MOUTH) + H.facial_hairstyle = "Shaved" + else + H.hairstyle = "Skinhead" + + H.update_hair() + playsound(loc, 'sound/items/welder2.ogg', 20, TRUE) + + +/obj/item/razor/attack(mob/M, mob/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/location = user.zone_selected + if((location in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_HEAD)) && !H.get_bodypart(BODY_ZONE_HEAD)) + to_chat(user, "[H] doesn't have a head!") + return + if(location == BODY_ZONE_PRECISE_MOUTH) + if(user.a_intent == INTENT_HELP) + if(H.gender == MALE) + if (H == user) + to_chat(user, "You need a mirror to properly style your own facial hair!") + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list + if(!get_location_accessible(H, location)) + to_chat(user, "The mask is in the way!") + return + user.visible_message("[user] tries to change [H]'s facial hairstyle using [src].", "You try to change [H]'s facial hairstyle using [src].") + if(new_style && do_after(user, 60, target = H)) + user.visible_message("[user] successfully changes [H]'s facial hairstyle using [src].", "You successfully change [H]'s facial hairstyle using [src].") + H.facial_hairstyle = new_style + H.update_hair() + return + else + return + + else + if(!(FACEHAIR in H.dna.species.species_traits)) + to_chat(user, "There is no facial hair to shave!") + return + if(!get_location_accessible(H, location)) + to_chat(user, "The mask is in the way!") + return + if(H.facial_hairstyle == "Shaved") + to_chat(user, "Already clean-shaven!") + return + + if(H == user) //shaving yourself + user.visible_message("[user] starts to shave [user.p_their()] facial hair with [src].", \ + "You take a moment to shave your facial hair with [src]...") + if(do_after(user, 50, target = H)) + user.visible_message("[user] shaves [user.p_their()] facial hair clean with [src].", \ + "You finish shaving with [src]. Fast and clean!") + shave(H, location) + else + user.visible_message("[user] tries to shave [H]'s facial hair with [src].", \ + "You start shaving [H]'s facial hair...") + if(do_after(user, 50, target = H)) + user.visible_message("[user] shaves off [H]'s facial hair with [src].", \ + "You shave [H]'s facial hair clean off.") + shave(H, location) + + else if(location == BODY_ZONE_HEAD) + if(user.a_intent == INTENT_HELP) + if (H == user) + to_chat(user, "You need a mirror to properly style your own hair!") + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list + if(!get_location_accessible(H, location)) + to_chat(user, "The headgear is in the way!") + return + if(HAS_TRAIT(H, TRAIT_BALD)) + to_chat(H, "[H] is just way too bald. Like, really really bald.") + return + user.visible_message("[user] tries to change [H]'s hairstyle using [src].", "You try to change [H]'s hairstyle using [src].") + if(new_style && do_after(user, 60, target = H)) + user.visible_message("[user] successfully changes [H]'s hairstyle using [src].", "You successfully change [H]'s hairstyle using [src].") + H.hairstyle = new_style + H.update_hair() + return + + else + if(!(HAIR in H.dna.species.species_traits)) + to_chat(user, "There is no hair to shave!") + return + if(!get_location_accessible(H, location)) + to_chat(user, "The headgear is in the way!") + return + if(H.hairstyle == "Bald" || H.hairstyle == "Balding Hair" || H.hairstyle == "Skinhead") + to_chat(user, "There is not enough hair left to shave!") + return + + if(H == user) //shaving yourself + user.visible_message("[user] starts to shave [user.p_their()] head with [src].", \ + "You start to shave your head with [src]...") + if(do_after(user, 5, target = H)) + user.visible_message("[user] shaves [user.p_their()] head with [src].", \ + "You finish shaving with [src].") + shave(H, location) + else + var/turf/H_loc = H.loc + user.visible_message("[user] tries to shave [H]'s head with [src]!", \ + "You start shaving [H]'s head...") + if(do_after(user, 50, target = H)) + if(H_loc == H.loc) + user.visible_message("[user] shaves [H]'s head bald with [src]!", \ + "You shave [H]'s head bald.") + shave(H, location) + else + ..() + else + ..() diff --git a/code/game/objects/items/crab17.dm b/code/game/objects/items/crab17.dm index 526efa1df0d..eb44f881eee 100644 --- a/code/game/objects/items/crab17.dm +++ b/code/game/objects/items/crab17.dm @@ -1,234 +1,234 @@ -/obj/item/suspiciousphone - name = "suspicious phone" - desc = "This device raises pink levels to unknown highs." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "suspiciousphone" - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("dumped") - var/dumped = FALSE - -/obj/item/suspiciousphone/attack_self(mob/user) - if(!ishuman(user)) - to_chat(user, "This device is too advanced for you!") - return - if(dumped) - to_chat(user, "You already activated Protocol CRAB-17.") - return FALSE - if(alert(user, "Are you sure you want to crash this market with no survivors?", "Protocol CRAB-17", "Yes", "No") == "Yes") - if(dumped || QDELETED(src)) //Prevents fuckers from cheesing alert - return FALSE - var/turf/targetturf = get_safe_random_station_turf() - if (!targetturf) - return FALSE - var/list/accounts_to_rob = SSeconomy.bank_accounts.Copy() - var/mob/living/carbon/human/H = user - accounts_to_rob -= H.get_bank_account() - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - B.being_dumped = TRUE - new /obj/effect/dumpeet_target(targetturf, user) - dumped = TRUE - -/obj/structure/checkoutmachine - name = "\improper Nanotrasen Space-Coin Market" - desc = "This is good for spacecoin because" - icon = 'icons/obj/money_machine.dmi' - icon_state = "bogdanoff" - layer = LARGE_MOB_LAYER - armor = list("melee" = 80, "bullet" = 30, "laser" = 30, "energy" = 60, "bomb" = 90, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) - density = TRUE - pixel_z = -8 - max_integrity = 5000 - var/list/accounts_to_rob - var/mob/living/carbon/human/bogdanoff - var/canwalk = FALSE - -/obj/structure/checkoutmachine/examine(mob/living/user) - . = ..() - . += "It's integrated integrity meter reads: HEALTH: [obj_integrity]." - -/obj/structure/checkoutmachine/proc/check_if_finished() - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - if (B.being_dumped) - return FALSE - return TRUE - -/obj/structure/checkoutmachine/attackby(obj/item/W, mob/user, params) - if(check_if_finished()) - qdel(src) - return - if(istype(W, /obj/item/card/id)) - var/obj/item/card/id/card = W - if(!card.registered_account) - to_chat(user, "This card does not have a registered account!") - return - if(!card.registered_account.being_dumped) - to_chat(user, "It appears that your funds are safe from draining!") - return - if(do_after(user, 40, target = src)) - if(!card.registered_account.being_dumped) - return - to_chat(user, "You quickly cash out your funds to a more secure banking location. Funds are safu.") // This is a reference and not a typo - card.registered_account.being_dumped = FALSE - if(check_if_finished()) - qdel(src) - return - else - return ..() - -/obj/structure/checkoutmachine/Initialize(mapload, mob/living/user) - . = ..() - bogdanoff = user - add_overlay("flaps") - add_overlay("hatch") - add_overlay("legs_retracted") - addtimer(CALLBACK(src, .proc/startUp), 50) - QDEL_IN(src, 8 MINUTES) //Self-destruct after 8 min - - -/obj/structure/checkoutmachine/proc/startUp() //very VERY snowflake code that adds a neat animation when the pod lands. - start_dumping() //The machine doesnt move during this time, giving people close by a small window to grab their funds before it starts running around - sleep(10) - if(QDELETED(src)) - return - playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) - cut_overlay("flaps") - sleep(10) - if(QDELETED(src)) - return - playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) - cut_overlay("hatch") - sleep(30) - if(QDELETED(src)) - return - playsound(src,'sound/machines/twobeep.ogg',50,FALSE) - var/mutable_appearance/hologram = mutable_appearance(icon, "hologram") - hologram.pixel_y = 16 - add_overlay(hologram) - var/mutable_appearance/holosign = mutable_appearance(icon, "holosign") - holosign.pixel_y = 16 - add_overlay(holosign) - add_overlay("legs_extending") - cut_overlay("legs_retracted") - pixel_z += 4 - sleep(5) - if(QDELETED(src)) - return - add_overlay("legs_extended") - cut_overlay("legs_extending") - pixel_z += 4 - sleep(20) - if(QDELETED(src)) - return - add_overlay("screen_lines") - sleep(5) - if(QDELETED(src)) - return - cut_overlay("screen_lines") - sleep(5) - if(QDELETED(src)) - return - add_overlay("screen_lines") - add_overlay("screen") - sleep(5) - if(QDELETED(src)) - return - playsound(src,'sound/machines/triple_beep.ogg',50,FALSE) - add_overlay("text") - sleep(10) - if(QDELETED(src)) - return - add_overlay("legs") - cut_overlay("legs_extended") - cut_overlay("screen") - add_overlay("screen") - cut_overlay("screen_lines") - add_overlay("screen_lines") - cut_overlay("text") - add_overlay("text") - canwalk = TRUE - START_PROCESSING(SSfastprocess, src) - -/obj/structure/checkoutmachine/Destroy() - stop_dumping() - STOP_PROCESSING(SSfastprocess, src) - priority_announce("The credit deposit machine at [get_area(src)] has been destroyed. Station funds have stopped draining!", sender_override = "CRAB-17 Protocol") - explosion(src, 0,0,1, flame_range = 2) - return ..() - -/obj/structure/checkoutmachine/proc/start_dumping() - accounts_to_rob = SSeconomy.bank_accounts.Copy() - accounts_to_rob -= bogdanoff.get_bank_account() - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - B.dumpeet() - dump() - -/obj/structure/checkoutmachine/proc/dump() - var/percentage_lost = (rand(5, 15) / 100) - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - if(!B.being_dumped) - continue - var/amount = B.account_balance * percentage_lost - var/datum/bank_account/account = bogdanoff.get_bank_account() - if (account) // get_bank_account() may return FALSE - account.transfer_money(B, amount) - B.bank_card_talk("You have lost [percentage_lost * 100]% of your funds! A spacecoin credit deposit machine is located at: [get_area(src)].") - addtimer(CALLBACK(src, .proc/dump), 150) //Drain every 15 seconds - -/obj/structure/checkoutmachine/process() - var/anydir = pick(GLOB.cardinals) - if(Process_Spacemove(anydir)) - Move(get_step(src, anydir), anydir) - -/obj/structure/checkoutmachine/proc/stop_dumping() - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - B.being_dumped = FALSE - -/obj/effect/dumpeet_fall //Falling pod - name = "" - icon = 'icons/obj/money_machine_64.dmi' - pixel_z = 300 - desc = "Get out of the way!" - layer = FLY_LAYER//that wasn't flying, that was falling with style! - icon_state = "missile_blur" - -/obj/effect/dumpeet_target - name = "Landing Zone Indicator" - desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." - icon = 'icons/mob/actions/actions_items.dmi' - icon_state = "sniper_zoom" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - light_range = 2 - var/obj/effect/dumpeet_fall/DF - var/obj/structure/checkoutmachine/dump - var/mob/living/carbon/human/bogdanoff - -/obj/effect/ex_act() - return - -/obj/effect/dumpeet_target/Initialize(mapload, user) - . = ..() - bogdanoff = user - addtimer(CALLBACK(src, .proc/startLaunch), 100) - sound_to_playing_players('sound/items/dump_it.ogg', 20) - deadchat_broadcast("Protocol CRAB-17 has been activated. A space-coin market has been launched at the station!", turf_target = get_turf(src), message_type=DEADCHAT_ANNOUNCEMENT) - -/obj/effect/dumpeet_target/proc/startLaunch() - DF = new /obj/effect/dumpeet_fall(drop_location()) - dump = new /obj/structure/checkoutmachine(null, bogdanoff) - priority_announce("The spacecoin bubble has popped! Get to the credit deposit machine at [get_area(src)] and cash out before you lose all of your funds!", sender_override = "CRAB-17 Protocol") - animate(DF, pixel_z = -8, time = 5, , easing = LINEAR_EASING) - playsound(src, 'sound/weapons/mortar_whistle.ogg', 70, TRUE, 6) - addtimer(CALLBACK(src, .proc/endLaunch), 5, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation - - - -/obj/effect/dumpeet_target/proc/endLaunch() - QDEL_NULL(DF) //Delete the falling machine effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears - playsound(src, "explosion", 80, TRUE) - dump.forceMove(get_turf(src)) - qdel(src) //The target's purpose is complete. It can rest easy now +/obj/item/suspiciousphone + name = "suspicious phone" + desc = "This device raises pink levels to unknown highs." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "suspiciousphone" + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("dumped") + var/dumped = FALSE + +/obj/item/suspiciousphone/attack_self(mob/user) + if(!ishuman(user)) + to_chat(user, "This device is too advanced for you!") + return + if(dumped) + to_chat(user, "You already activated Protocol CRAB-17.") + return FALSE + if(alert(user, "Are you sure you want to crash this market with no survivors?", "Protocol CRAB-17", "Yes", "No") == "Yes") + if(dumped || QDELETED(src)) //Prevents fuckers from cheesing alert + return FALSE + var/turf/targetturf = get_safe_random_station_turf() + if (!targetturf) + return FALSE + var/list/accounts_to_rob = SSeconomy.bank_accounts.Copy() + var/mob/living/carbon/human/H = user + accounts_to_rob -= H.get_bank_account() + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + B.being_dumped = TRUE + new /obj/effect/dumpeet_target(targetturf, user) + dumped = TRUE + +/obj/structure/checkoutmachine + name = "\improper Nanotrasen Space-Coin Market" + desc = "This is good for spacecoin because" + icon = 'icons/obj/money_machine.dmi' + icon_state = "bogdanoff" + layer = LARGE_MOB_LAYER + armor = list("melee" = 80, "bullet" = 30, "laser" = 30, "energy" = 60, "bomb" = 90, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) + density = TRUE + pixel_z = -8 + max_integrity = 5000 + var/list/accounts_to_rob + var/mob/living/carbon/human/bogdanoff + var/canwalk = FALSE + +/obj/structure/checkoutmachine/examine(mob/living/user) + . = ..() + . += "It's integrated integrity meter reads: HEALTH: [obj_integrity]." + +/obj/structure/checkoutmachine/proc/check_if_finished() + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + if (B.being_dumped) + return FALSE + return TRUE + +/obj/structure/checkoutmachine/attackby(obj/item/W, mob/user, params) + if(check_if_finished()) + qdel(src) + return + if(istype(W, /obj/item/card/id)) + var/obj/item/card/id/card = W + if(!card.registered_account) + to_chat(user, "This card does not have a registered account!") + return + if(!card.registered_account.being_dumped) + to_chat(user, "It appears that your funds are safe from draining!") + return + if(do_after(user, 40, target = src)) + if(!card.registered_account.being_dumped) + return + to_chat(user, "You quickly cash out your funds to a more secure banking location. Funds are safu.") // This is a reference and not a typo + card.registered_account.being_dumped = FALSE + if(check_if_finished()) + qdel(src) + return + else + return ..() + +/obj/structure/checkoutmachine/Initialize(mapload, mob/living/user) + . = ..() + bogdanoff = user + add_overlay("flaps") + add_overlay("hatch") + add_overlay("legs_retracted") + addtimer(CALLBACK(src, .proc/startUp), 50) + QDEL_IN(src, 8 MINUTES) //Self-destruct after 8 min + + +/obj/structure/checkoutmachine/proc/startUp() //very VERY snowflake code that adds a neat animation when the pod lands. + start_dumping() //The machine doesnt move during this time, giving people close by a small window to grab their funds before it starts running around + sleep(10) + if(QDELETED(src)) + return + playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) + cut_overlay("flaps") + sleep(10) + if(QDELETED(src)) + return + playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) + cut_overlay("hatch") + sleep(30) + if(QDELETED(src)) + return + playsound(src,'sound/machines/twobeep.ogg',50,FALSE) + var/mutable_appearance/hologram = mutable_appearance(icon, "hologram") + hologram.pixel_y = 16 + add_overlay(hologram) + var/mutable_appearance/holosign = mutable_appearance(icon, "holosign") + holosign.pixel_y = 16 + add_overlay(holosign) + add_overlay("legs_extending") + cut_overlay("legs_retracted") + pixel_z += 4 + sleep(5) + if(QDELETED(src)) + return + add_overlay("legs_extended") + cut_overlay("legs_extending") + pixel_z += 4 + sleep(20) + if(QDELETED(src)) + return + add_overlay("screen_lines") + sleep(5) + if(QDELETED(src)) + return + cut_overlay("screen_lines") + sleep(5) + if(QDELETED(src)) + return + add_overlay("screen_lines") + add_overlay("screen") + sleep(5) + if(QDELETED(src)) + return + playsound(src,'sound/machines/triple_beep.ogg',50,FALSE) + add_overlay("text") + sleep(10) + if(QDELETED(src)) + return + add_overlay("legs") + cut_overlay("legs_extended") + cut_overlay("screen") + add_overlay("screen") + cut_overlay("screen_lines") + add_overlay("screen_lines") + cut_overlay("text") + add_overlay("text") + canwalk = TRUE + START_PROCESSING(SSfastprocess, src) + +/obj/structure/checkoutmachine/Destroy() + stop_dumping() + STOP_PROCESSING(SSfastprocess, src) + priority_announce("The credit deposit machine at [get_area(src)] has been destroyed. Station funds have stopped draining!", sender_override = "CRAB-17 Protocol") + explosion(src, 0,0,1, flame_range = 2) + return ..() + +/obj/structure/checkoutmachine/proc/start_dumping() + accounts_to_rob = SSeconomy.bank_accounts.Copy() + accounts_to_rob -= bogdanoff.get_bank_account() + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + B.dumpeet() + dump() + +/obj/structure/checkoutmachine/proc/dump() + var/percentage_lost = (rand(5, 15) / 100) + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + if(!B.being_dumped) + continue + var/amount = B.account_balance * percentage_lost + var/datum/bank_account/account = bogdanoff.get_bank_account() + if (account) // get_bank_account() may return FALSE + account.transfer_money(B, amount) + B.bank_card_talk("You have lost [percentage_lost * 100]% of your funds! A spacecoin credit deposit machine is located at: [get_area(src)].") + addtimer(CALLBACK(src, .proc/dump), 150) //Drain every 15 seconds + +/obj/structure/checkoutmachine/process() + var/anydir = pick(GLOB.cardinals) + if(Process_Spacemove(anydir)) + Move(get_step(src, anydir), anydir) + +/obj/structure/checkoutmachine/proc/stop_dumping() + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + B.being_dumped = FALSE + +/obj/effect/dumpeet_fall //Falling pod + name = "" + icon = 'icons/obj/money_machine_64.dmi' + pixel_z = 300 + desc = "Get out of the way!" + layer = FLY_LAYER//that wasn't flying, that was falling with style! + icon_state = "missile_blur" + +/obj/effect/dumpeet_target + name = "Landing Zone Indicator" + desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." + icon = 'icons/mob/actions/actions_items.dmi' + icon_state = "sniper_zoom" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + light_range = 2 + var/obj/effect/dumpeet_fall/DF + var/obj/structure/checkoutmachine/dump + var/mob/living/carbon/human/bogdanoff + +/obj/effect/ex_act() + return + +/obj/effect/dumpeet_target/Initialize(mapload, user) + . = ..() + bogdanoff = user + addtimer(CALLBACK(src, .proc/startLaunch), 100) + sound_to_playing_players('sound/items/dump_it.ogg', 20) + deadchat_broadcast("Protocol CRAB-17 has been activated. A space-coin market has been launched at the station!", turf_target = get_turf(src), message_type=DEADCHAT_ANNOUNCEMENT) + +/obj/effect/dumpeet_target/proc/startLaunch() + DF = new /obj/effect/dumpeet_fall(drop_location()) + dump = new /obj/structure/checkoutmachine(null, bogdanoff) + priority_announce("The spacecoin bubble has popped! Get to the credit deposit machine at [get_area(src)] and cash out before you lose all of your funds!", sender_override = "CRAB-17 Protocol") + animate(DF, pixel_z = -8, time = 5, , easing = LINEAR_EASING) + playsound(src, 'sound/weapons/mortar_whistle.ogg', 70, TRUE, 6) + addtimer(CALLBACK(src, .proc/endLaunch), 5, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + + + +/obj/effect/dumpeet_target/proc/endLaunch() + QDEL_NULL(DF) //Delete the falling machine effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears + playsound(src, "explosion", 80, TRUE) + dump.forceMove(get_turf(src)) + qdel(src) //The target's purpose is complete. It can rest easy now diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index b1475772c5b..ac758a21e0b 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -1,1151 +1,1151 @@ - -//The advanced pea-green monochrome lcd of tomorrow. - -GLOBAL_LIST_EMPTY(PDAs) - -#define PDA_SCANNER_NONE 0 -#define PDA_SCANNER_MEDICAL 1 -#define PDA_SCANNER_FORENSICS 2 //unused -#define PDA_SCANNER_REAGENT 3 -#define PDA_SCANNER_HALOGEN 4 -#define PDA_SCANNER_GAS 5 -#define PDA_SPAM_DELAY 2 MINUTES - -/obj/item/pda - name = "\improper PDA" - desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." - icon = 'icons/obj/pda.dmi' - icon_state = "pda" - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT - actions_types = list(/datum/action/item_action/toggle_light) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - - - //Main variables - var/owner = null // String name of owner - var/default_cartridge = 0 // Access level defined by cartridge - var/obj/item/cartridge/cartridge = null //current cartridge - var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. - var/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file. - var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. - var/font_mode = "font-family:monospace;" //The currently selected font. - var/background_color = "#808000" //The currently selected background color. - - #define FONT_MONO "font-family:monospace;" - #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" - #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" - #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" - #define MODE_MONO 0 - #define MODE_SHARE 1 - #define MODE_ORBITRON 2 - #define MODE_VT 3 - - //Secondary variables - var/scanmode = PDA_SCANNER_NONE - var/fon = FALSE //Is the flashlight function on? - var/f_lum = 2.3 //Luminosity for the flashlight function - var/silent = FALSE //To beep or not to beep, that is the question - var/toff = FALSE //If TRUE, messenger disabled - var/tnote = null //Current Texts - var/last_text //No text spamming - var/last_everyone //No text for everyone spamming - var/last_noise //Also no honk spamming that's bad too - var/ttone = "beep" //The ringtone! - var/honkamt = 0 //How many honks left when infected with honk.exe - var/mimeamt = 0 //How many silence left when infected with mime.exe - var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function - var/notehtml = "" - var/notescanned = FALSE // True if what is in the notekeeper was from a paper. - var/hidden = FALSE // Is the PDA hidden from the PDA list? - var/emped = FALSE - var/equipped = FALSE //used here to determine if this is the first time its been picked up - var/allow_emojis = FALSE //if the pda can send emojis and actually have them parsed as such - var/sort_by_job = FALSE // If this is TRUE, will sort PDA list by job. - - var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. - var/ownjob = null //related to above - - var/obj/item/paicard/pai = null // A slot for a personal AI device - - var/datum/picture/picture //Scanned photo - - var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) - var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. - var/overlays_x_offset = 0 //x offset to use for certain overlays - - var/underline_flag = TRUE //flag for underline - -/obj/item/pda/suicide_act(mob/living/carbon/user) - var/deathMessage = msg_input(user) - if (!deathMessage) - deathMessage = "i ded" - user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") - tnote += "→ To The Grim Reaper:
                    [deathMessage]
                    "//records a message in their PDA as being sent to the grim reaper - return BRUTELOSS - -/obj/item/pda/examine(mob/user) - . = ..() - if(!id && !inserted_item) - return - - if(id) - . += "Alt-click to remove the ID." //won't name ID on examine in case it's stolen - - if(inserted_item && (!isturf(loc))) - . += "Ctrl-click to remove [inserted_item]." //traitor pens are disguised so we're fine naming them on examine - - if((!isnull(cartridge))) - . += "Ctrl+Shift-click to remove the cartridge." //won't name cart on examine in case it's Detomatix - -/obj/item/pda/Initialize() - . = ..() - if(fon) - set_light(f_lum) - - GLOB.PDAs += src - if(default_cartridge) - cartridge = new default_cartridge(src) - if(inserted_item) - inserted_item = new inserted_item(src) - else - inserted_item = new /obj/item/pen(src) - update_icon() - -/obj/item/pda/equipped(mob/user, slot) - . = ..() - if(!equipped) - if(user.client) - background_color = user.client.prefs.pda_color - switch(user.client.prefs.pda_style) - if(MONO) - font_index = MODE_MONO - font_mode = FONT_MONO - if(SHARE) - font_index = MODE_SHARE - font_mode = FONT_SHARE - if(ORBITRON) - font_index = MODE_ORBITRON - font_mode = FONT_ORBITRON - if(VT) - font_index = MODE_VT - font_mode = FONT_VT - else - font_index = MODE_MONO - font_mode = FONT_MONO - equipped = TRUE - -/obj/item/pda/proc/update_label() - name = "PDA-[owner] ([ownjob])" //Name generalisation - -/obj/item/pda/GetAccess() - if(id) - return id.GetAccess() - else - return ..() - -/obj/item/pda/GetID() - return id - -/obj/item/pda/RemoveID() - return do_remove_id() - -/obj/item/pda/InsertID(obj/item/inserting_item) - var/obj/item/card/inserting_id = inserting_item.RemoveID() - if(!inserting_id) - return - insert_id(inserting_id) - if(id == inserting_id) - return TRUE - return FALSE - -/obj/item/pda/update_overlays() - . = ..() - var/mutable_appearance/overlay = new(icon) - overlay.pixel_x = overlays_x_offset - if(id) - overlay.icon_state = "id_overlay" - . += new /mutable_appearance(overlay) - if(inserted_item) - overlay.icon_state = "insert_overlay" - . += new /mutable_appearance(overlay) - if(fon) - overlay.icon_state = "light_overlay" - . += new /mutable_appearance(overlay) - if(pai) - if(pai.pai) - overlay.icon_state = "pai_overlay" - . += new /mutable_appearance(overlay) - else - overlay.icon_state = "pai_off_overlay" - . += new /mutable_appearance(overlay) - -/obj/item/pda/MouseDrop(mob/over, src_location, over_location) - var/mob/M = usr - if((M == over) && usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return attack_self(M) - return ..() - -/obj/item/pda/attack_self_tk(mob/user) - to_chat(user, "The PDA's capacitive touch screen doesn't seem to respond!") - return - -/obj/item/pda/interact(mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - - ..() - - var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pda) - assets.send(user) - - var/datum/asset/spritesheet/emoji_s = get_asset_datum(/datum/asset/spritesheet/goonchat) - emoji_s.send(user) //Already sent by chat but no harm doing this - - user.set_machine(src) - - var/dat = "Personal Data Assistant" - dat += assets.css_tag() - dat += emoji_s.css_tag() - - dat += "[PDAIMG(refresh)]Refresh" - - if ((!isnull(cartridge)) && (mode == 0)) - dat += " | [PDAIMG(eject)]Eject [cartridge]" - if (mode) - dat += " | [PDAIMG(menu)]Return" - - if (mode == 0) - dat += "
                    " - dat += "
                    Toggle Font" - dat += " | Change Color" - dat += " | Toggle Underline" //underline button - - dat += "
                    " - - dat += "
                    " - - if (!owner) - dat += "Warning: No owner information entered. Please swipe card.

                    " - dat += "[PDAIMG(refresh)]Retry" - else - switch (mode) - if (0) - dat += "

                    PERSONAL DATA ASSISTANT v.1.2

                    " - dat += "Owner: [owner], [ownjob]
                    " - dat += text("ID: [id ? "[id.registered_name], [id.assignment]" : "----------"]") - dat += text("
                    [id ? "Update PDA Info" : ""]

                    ") - - dat += "[station_time_timestamp()]
                    " //:[world.time / 100 % 6][world.time / 100 % 10]" - dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]" - - dat += "

                    " - - dat += "

                    General Functions

                    " - dat += "" - if (cartridge.access & CART_ENGINE) - dat += "

                    Engineering Functions

                    " - dat += "" - if (cartridge.access & CART_MEDICAL) - dat += "

                    Medical Functions

                    " - dat += "" - if (cartridge.access & CART_SECURITY) - dat += "

                    Security Functions

                    " - dat += "" - if(cartridge.access & CART_QUARTERMASTER) - dat += "

                    Quartermaster Functions:

                    " - dat += "" - dat += "
                  " - - dat += "

                  Utilities

                  " - dat += "" - - if (1) - dat += "

                  [PDAIMG(notes)] Notekeeper V2.2

                  " - dat += "Edit
                  " - if(notescanned) - dat += "(This is a scanned image, editing it may cause some text formatting to change.)
                  " - dat += "
                  [(!notehtml ? note : notehtml)]" - - if (2) - dat += "

                  [PDAIMG(mail)] SpaceMessenger V3.9.6

                  " - dat += "[PDAIMG(bell)]Ringer: [silent == 1 ? "Off" : "On"] | " - dat += "[PDAIMG(mail)]Send / Receive: [toff == 1 ? "Off" : "On"] | " - dat += "[PDAIMG(bell)]Set Ringtone | " - dat += "[PDAIMG(mail)]Messages
                  " - dat += "Sorted by: [sort_by_job ? "Job" : "Name"]" - - if(cartridge) - dat += cartridge.message_header() - - dat += "

                  [PDAIMG(menu)] Detected PDAs

                  " - - dat += "
                    " - var/count = 0 - - if (!toff) - for (var/obj/item/pda/P in get_viewable_pdas(sort_by_job)) - if (P == src) - continue - dat += "
                  • [P.owner] ([P.ownjob])" - if(cartridge) - dat += cartridge.message_special(P) - dat += "
                  • " - count++ - dat += "
                  " - if (count == 0) - dat += "None detected.
                  " - else if(cartridge && cartridge.spam_enabled) - dat += "Send To All" - if(6) - dat += "

                  [PDAIMG(mail)] ExperTrak® Skill Tracker V4.26.2

                  " - dat += "Thank you for choosing ExperTrak® brand software! ExperTrak® inc. is proud to be a NanoTrasen employee expertise and effectiveness department subsidary!" - dat += "

                  This software is designed to track and monitor your skill development as a NanoTrasen employee. Your job performance across different fields has been quantified and categorized below.
                  " - var/datum/mind/targetmind = user.mind - for (var/type in GLOB.skill_types) - var/datum/skill/S = GetSkillRef(type) - var/lvl_num = targetmind.get_skill_level(type) - var/lvl_name = uppertext(targetmind.get_skill_level_name(type)) - var/exp = targetmind.get_skill_exp(type) - var/xp_prog_to_level = targetmind.exp_needed_to_level_up(type) - var/xp_req_to_level = 0 - if (xp_prog_to_level)//is it even possible to level up? - xp_req_to_level = SKILL_EXP_LIST[lvl_num+1] - SKILL_EXP_LIST[lvl_num] - dat += "
                  [S.name]" - dat += "
                  [S.desc]" - dat += "
                  • EMPLOYEE SKILL LEVEL: [lvl_name]" - if (exp && xp_req_to_level) - var/progress_percent = (xp_req_to_level-xp_prog_to_level)/xp_req_to_level - var/overall_percent = exp / SKILL_EXP_LIST[length(SKILL_EXP_LIST)] - dat += "
                    PROGRESS TO NEXT SKILL LEVEL:" - dat += "
                    " + num2loadingbar(progress_percent) + "([progress_percent*100])%" - dat += "
                    OVERALL DEVELOPMENT PROGRESS:" - dat += "
                    " + num2loadingbar(overall_percent) + "([overall_percent*100])%" - if (lvl_num >= length(SKILL_EXP_LIST) && !(type in targetmind.skills_rewarded)) - dat += "
                    Contact the Professional [S.title] Association" - dat += "
                  " - if(21) - dat += "

                  [PDAIMG(mail)] SpaceMessenger V3.9.6

                  " - dat += "[PDAIMG(blank)]Clear Messages" - - dat += "

                  [PDAIMG(mail)] Messages

                  " - - dat += tnote - dat += "
                  " - - if (3) - dat += "

                  [PDAIMG(atmos)] Atmospheric Readings

                  " - - var/turf/T = user.loc - if (isnull(T)) - dat += "Unable to obtain a reading.
                  " - else - var/datum/gas_mixture/environment = T.return_air() - var/list/env_gases = environment.gases - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - dat += "Air Pressure: [round(pressure,0.1)] kPa
                  " - - if (total_moles) - for(var/id in env_gases) - var/gas_level = env_gases[id][MOLES]/total_moles - if(gas_level > 0) - dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
                  " - - dat += "Temperature: [round(environment.temperature-T0C)]°C
                  " - dat += "
                  " - else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. - dat += cartridge.generate_menu() - - dat += "" - - if (underline_flag) - dat = replacetext(dat, "text-decoration:none", "text-decoration:underline") - if (!underline_flag) - dat = replacetext(dat, "text-decoration:underline", "text-decoration:none") - - user << browse(dat, "window=pda;size=400x450;border=1;can_resize=1;can_minimize=0") - onclose(user, "pda", src) - -/obj/item/pda/Topic(href, href_list) - ..() - var/mob/living/U = usr - //Looking for master was kind of pointless since PDAs don't appear to have one. - - if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"]) - -//BASIC FUNCTIONS=================================== - - if("Refresh")//Refresh, goes to the end of the proc. - - if ("Toggle_Font") - //CODE REVISION 2 - font_index = (font_index + 1) % 4 - - switch(font_index) - if (MODE_MONO) - font_mode = FONT_MONO - if (MODE_SHARE) - font_mode = FONT_SHARE - if (MODE_ORBITRON) - font_mode = FONT_ORBITRON - if (MODE_VT) - font_mode = FONT_VT - if ("Change_Color") - var/new_color = input("Please enter a color name or hex value (Default is \'#808000\').",background_color)as color - background_color = new_color - - if ("Toggle_Underline") - underline_flag = !underline_flag - - if("Return")//Return - if(mode<=9) - mode = 0 - else - mode = round(mode/10) - if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. - mode = 0 - if ("Authenticate")//Checks for ID - id_check(U) - if("UpdateInfo") - ownjob = id.assignment - if(istype(id, /obj/item/card/id/syndicate)) - owner = id.registered_name - update_label() - if("Eject")//Ejects the cart, only done from hub. - eject_cart(U) - -//MENU FUNCTIONS=================================== - - if("0")//Hub - mode = 0 - if("1")//Notes - mode = 1 - if("2")//Messenger - mode = 2 - if("21")//Read messeges - mode = 21 - if("3")//Atmos scan - mode = 3 - if("4")//Redirects to hub - mode = 0 - - -//MAIN FUNCTIONS=================================== - - if("Light") - toggle_light(U) - if("Medical Scan") - if(scanmode == PDA_SCANNER_MEDICAL) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) - scanmode = PDA_SCANNER_MEDICAL - if("Reagent Scan") - if(scanmode == PDA_SCANNER_REAGENT) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) - scanmode = PDA_SCANNER_REAGENT - if("Halogen Counter") - if(scanmode == PDA_SCANNER_HALOGEN) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) - scanmode = PDA_SCANNER_HALOGEN - if("Honk") - if ( !(last_noise && world.time < last_noise + 20) ) - playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) - last_noise = world.time - if("Trombone") - if ( !(last_noise && world.time < last_noise + 20) ) - playsound(src, 'sound/misc/sadtrombone.ogg', 50, TRUE) - last_noise = world.time - if("Gas Scan") - if(scanmode == PDA_SCANNER_GAS) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_ATMOS)) - scanmode = PDA_SCANNER_GAS - if("Drone Phone") - var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") - var/area/A = get_area(U) - if(A && alert_s && !QDELETED(U)) - var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" - _alert_drones(msg, TRUE, U) - to_chat(U, msg) - - -//NOTEKEEPER FUNCTIONS=================================== - - if ("Edit") - var/n = stripped_multiline_input(U, "Please enter message", name, note) - if (in_range(src, U) && loc == U) - if (mode == 1 && n) - note = n - notehtml = parsemarkdown(n, U) - notescanned = FALSE - else - U << browse(null, "window=pda") - return - -//MESSENGER FUNCTIONS=================================== - - if("Toggle Messenger") - toff = !toff - if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status - silent = !silent - if("Clear")//Clears messages - tnote = null - if("Ringtone") - var/t = stripped_input(U, "Please enter new ringtone", name, ttone, 20) - if(in_range(src, U) && loc == U && t) - if(SEND_SIGNAL(src, COMSIG_PDA_CHANGE_RINGTONE, U, t) & COMPONENT_STOP_RINGTONE_CHANGE) - U << browse(null, "window=pda") - return - else - ttone = t - else - U << browse(null, "window=pda") - return - if("Message") - create_message(U, locate(href_list["target"]) in GLOB.PDAs) - - if("Sorting Mode") - sort_by_job = !sort_by_job - - if("MessageAll") - send_to_all(U) - - if("cart") - if(cartridge) - cartridge.special(U, href_list) - else - U << browse(null, "window=pda") - return - -//SYNDICATE FUNCTIONS=================================== - - if("Toggle Door") - if(cartridge && cartridge.access & CART_REMOTE_DOOR) - for(var/obj/machinery/door/poddoor/M in GLOB.machines) - if(M.id == cartridge.remote_door_id) - if(M.density) - M.open() - else - M.close() - -//pAI FUNCTIONS=================================== - - if("pai") - switch(href_list["option"]) - if("1") // Configure pAI device - pai.attack_self(U) - if("2") // Eject pAI device - usr.put_in_hands(pai) - to_chat(usr, "You remove the pAI from the [name].") - -//SKILL FUNCTIONS=================================== - - if("SkillReward") - var/type = text2path(href_list["skill"]) - var/datum/skill/S = GetSkillRef(type) - var/datum/mind/mind = U.mind - var/new_level = mind.get_skill_level(type) - S.try_skill_reward(mind, new_level) - -//LINK FUNCTIONS=================================== - - else//Cartridge menu linking - mode = max(text2num(href_list["choice"]), 0) - - else//If not in range, can't interact or not using the pda. - U.unset_machine() - U << browse(null, "window=pda") - return - -//EXTRA FUNCTIONS=================================== - - if (mode == 2 || mode == 21)//To clear message overlays. - update_icon() - - if ((honkamt > 0) && (prob(60)))//For clown virus. - honkamt-- - playsound(src, 'sound/items/bikehorn.ogg', 30, TRUE) - - if(U.machine == src && href_list["skiprefresh"]!="1")//Final safety. - attack_self(U)//It auto-closes the menu prior if the user is not in range and so on. - else - U.unset_machine() - U << browse(null, "window=pda") - return - -/obj/item/pda/proc/remove_id(mob/user) - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - do_remove_id(user) - - -/obj/item/pda/proc/do_remove_id(mob/user) - if(!id) - return - if(user) - user.put_in_hands(id) - to_chat(user, "You remove the ID from the [name].") - else - id.forceMove(get_turf(src)) - - . = id - id = null - updateSelfDialog() - update_icon() - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - - -/obj/item/pda/proc/msg_input(mob/living/U = usr) - var/t = stripped_input(U, "Please enter message", name) - if (!t || toff) - return - if(!U.canUseTopic(src, BE_CLOSE)) - return - if(emped) - t = Gibberish(t, TRUE) - return t - -/obj/item/pda/proc/send_message(mob/living/user, list/obj/item/pda/targets, everyone) - var/message = msg_input(user) - if(!message || !targets.len) - return - if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY)) - return - if(prob(1)) - message += "\nSent from my PDA" - // Send the signal - var/list/string_targets = list() - for (var/obj/item/pda/P in targets) - if (P.owner && P.ownjob) // != src is checked by the UI - string_targets += "[P.owner] ([P.ownjob])" - for (var/obj/machinery/computer/message_monitor/M in targets) - // In case of "Reply" to a message from a console, this will make the - // message be logged successfully. If the console is impersonating - // someone by matching their name and job, the reply will reach the - // impersonated PDA. - string_targets += "[M.customsender] ([M.customjob])" - if (!string_targets.len) - return - - var/datum/signal/subspace/messaging/pda/signal = new(src, list( - "name" = "[owner]", - "job" = "[ownjob]", - "message" = message, - "targets" = string_targets, - "emojis" = allow_emojis, - )) - if (picture) - signal.data["photo"] = picture - signal.send_to_receivers() - - // If it didn't reach, note that fact - if (!signal.data["done"]) - to_chat(user, "ERROR: Server isn't responding.") - return - - var/target_text = signal.format_target() - if(allow_emojis) - message = emoji_parse(message)//already sent- this just shows the sent emoji as one to the sender in the to_chat - signal.data["message"] = emoji_parse(signal.data["message"]) - - // Log it in our logs - tnote += "→ To [target_text]:
                  [signal.format_message()]
                  " - // Show it to ghosts - var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]" - for(var/mob/M in GLOB.player_list) - if(isobserver(M) && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA)) - to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]") - // Log in the talk log - user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]") - to_chat(user, "PDA message sent to [target_text]: \"[message]\"") - // Reset the photo - picture = null - last_text = world.time - if (everyone) - last_everyone = world.time - -/obj/item/pda/proc/receive_message(datum/signal/subspace/messaging/pda/signal) - tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                  [signal.format_message()]
                  " - - if (!silent) - playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) - audible_message("[icon2html(src, hearers(src))] *[ttone]*", null, 3) - //Search for holder of the PDA. - var/mob/living/L = null - if(loc && isliving(loc)) - L = loc - //Maybe they are a pAI! - else - L = get(src, /mob/living/silicon) - - if(L && L.stat != UNCONSCIOUS) - var/reply = "(Reply)" - var/hrefstart - var/hrefend - if (isAI(L)) - hrefstart = "" - hrefend = "" - - if(signal.data["automated"]) - reply = "\[Automated Message\]" - - var/inbound_message = signal.format_message() - if(signal.data["emojis"] == TRUE)//so will not parse emojis as such from pdas that don't send emojis - inbound_message = emoji_parse(inbound_message) - - to_chat(L, "[icon2html(src)] PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [inbound_message] [reply]") - - update_icon() - add_overlay(icon_alert) - -/obj/item/pda/proc/send_to_all(mob/living/U) - if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) - to_chat(U,"Send To All function is still on cooldown.") - return - send_message(U,get_viewable_pdas(), TRUE) - -/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) - send_message(U,list(P)) - -/obj/item/pda/AltClick(mob/user) - ..() - - if(id) - remove_id(user) - else - remove_pen(user) - -/obj/item/pda/CtrlClick(mob/user) - ..() - - if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. - return - - remove_pen(user) - -/obj/item/pda/CtrlShiftClick(mob/user) - ..() - eject_cart(user) - -/obj/item/pda/verb/verb_toggle_light() - set name = "Toggle light" - set category = "Object" - set src in oview(1) - - toggle_light(usr) - -/obj/item/pda/verb/verb_remove_id() - set category = "Object" - set name = "Eject ID" - set src in usr - - if(id) - remove_id(usr) - else - to_chat(usr, "This PDA does not have an ID in it!") - -/obj/item/pda/verb/verb_remove_pen() - set category = "Object" - set name = "Remove Pen" - set src in usr - - remove_pen(usr) - -/obj/item/pda/verb/verb_eject_cart() - set category = "Object" - set name = "Eject Cartridge" - set src in usr - - eject_cart(usr) - -/obj/item/pda/proc/toggle_light(mob/user) - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE)) - return - if(fon) - fon = FALSE - set_light(0) - else if(f_lum) - fon = TRUE - set_light(f_lum) - update_icon() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/pda/proc/remove_pen(mob/user) - - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK doesn't work even with this removed but here for readability - return - - if(inserted_item) - user.put_in_hands(inserted_item) - to_chat(user, "You remove [inserted_item] from [src].") - inserted_item = null - update_icon() - else - to_chat(user, "This PDA does not have a pen in it!") - -/obj/item/pda/proc/eject_cart(mob/user) - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK disabled to stop cartridge teleporting into hand - return - if (!isnull(cartridge)) - user.put_in_hands(cartridge) - to_chat(user, "You eject [cartridge] from [src].") - scanmode = PDA_SCANNER_NONE - cartridge.host_pda = null - cartridge = null - updateSelfDialog() - update_icon() - -//trying to insert or remove an id -/obj/item/pda/proc/id_check(mob/user, obj/item/card/id/I) - if(!I) - if(id && (src in user.contents)) - remove_id(user) - return TRUE - else - var/obj/item/card/id/C = user.get_active_held_item() - if(istype(C)) - I = C - - if(I?.registered_name) - if(!user.transferItemToLoc(I, src)) - return FALSE - insert_id(I, user) - update_icon() - return TRUE - - -/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user) - var/obj/old_id = id - id = inserting_id - if(ishuman(loc)) - var/mob/living/carbon/human/human_wearer = loc - if(human_wearer.wear_id == src) - human_wearer.sec_hud_set_ID() - if(old_id) - if(user) - user.put_in_hands(old_id) - else - old_id.forceMove(get_turf(src)) - - -// access to status display signals -/obj/item/pda/attackby(obj/item/C, mob/user, params) - if(istype(C, /obj/item/cartridge)) - if(!user.transferItemToLoc(C, src)) - return - eject_cart(user) - cartridge = C - cartridge.host_pda = src - to_chat(user, "You insert [cartridge] into [src].") - updateSelfDialog() - update_icon() - - else if(istype(C, /obj/item/card/id)) - var/obj/item/card/id/idcard = C - if(!idcard.registered_name) - to_chat(user, "\The [src] rejects the ID!") - return - if(!owner) - owner = idcard.registered_name - ownjob = idcard.assignment - update_label() - to_chat(user, "Card scanned.") - else - if(!id_check(user, idcard)) - return - to_chat(user, "You put the ID into \the [src]'s slot.") - updateSelfDialog()//Update self dialog on success. - - return //Return in case of failed check or when successful. - updateSelfDialog()//For the non-input related code. - else if(istype(C, /obj/item/paicard) && !pai) - if(!user.transferItemToLoc(C, src)) - return - pai = C - to_chat(user, "You slot \the [C] into [src].") - update_icon() - updateUsrDialog() - else if(is_type_in_list(C, contained_item)) //Checks if there is a pen - if(inserted_item) - to_chat(user, "There is already \a [inserted_item] in \the [src]!") - else - if(!user.transferItemToLoc(C, src)) - return - to_chat(user, "You slide \the [C] into \the [src].") - inserted_item = C - update_icon() - else if(istype(C, /obj/item/photo)) - var/obj/item/photo/P = C - picture = P.picture - to_chat(user, "You scan \the [C].") - else - return ..() - -/obj/item/pda/attack(mob/living/carbon/C, mob/living/user) - if(istype(C)) - switch(scanmode) - - if(PDA_SCANNER_MEDICAL) - C.visible_message("[user] analyzes [C]'s vitals.") - healthscan(user, C, 1) - add_fingerprint(user) - - if(PDA_SCANNER_HALOGEN) - C.visible_message("[user] analyzes [C]'s radiation levels.") - - user.show_message("Analyzing Results for [C]:") - if(C.radiation) - user.show_message("\green Radiation Level: \black [C.radiation]") - else - user.show_message("No radiation detected.") - -/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) - . = ..() - if(!proximity) - return - switch(scanmode) - if(PDA_SCANNER_REAGENT) - if(!isnull(A.reagents)) - if(A.reagents.reagent_list.len > 0) - var/reagents_length = A.reagents.reagent_list.len - to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") - for (var/re in A.reagents.reagent_list) - to_chat(user, "\t [re]") - else - to_chat(user, "No active chemical agents found in [A].") - else - to_chat(user, "No significant chemical agents found in [A].") - - if(PDA_SCANNER_GAS) - A.analyzer_act(user, src) - - if (!scanmode && istype(A, /obj/item/paper) && owner) - var/obj/item/paper/PP = A - if (!PP.info) - to_chat(user, "Unable to scan! Paper is blank.") - return - notehtml = PP.info - note = replacetext(notehtml, "
                  ", "\[br\]") - note = replacetext(note, "
                • ", "\[*\]") - note = replacetext(note, "
                    ", "\[list\]") - note = replacetext(note, "
                  ", "\[/list\]") - note = html_encode(note) - notescanned = TRUE - to_chat(user, "Paper scanned. Saved to PDA's notekeeper." ) - - -/obj/item/pda/proc/explode() //This needs tuning. - var/turf/T = get_turf(src) - - if (ismob(loc)) - var/mob/M = loc - M.show_message("Your [src] explodes!", MSG_VISUAL, "You hear a loud *pop*!", MSG_AUDIBLE) - else - visible_message("[src] explodes!", "You hear a loud *pop*!") - - if(T) - T.hotspot_expose(700,125) - if(istype(cartridge, /obj/item/cartridge/virus/syndicate)) - explosion(T, -1, 1, 3, 4) - else - explosion(T, -1, -1, 2, 3) - qdel(src) - return - -/obj/item/pda/Destroy() - GLOB.PDAs -= src - if(istype(id)) - QDEL_NULL(id) - if(istype(cartridge)) - QDEL_NULL(cartridge) - if(istype(pai)) - QDEL_NULL(pai) - if(istype(inserted_item)) - QDEL_NULL(inserted_item) - return ..() - -//AI verb and proc for sending PDA messages. - -/obj/item/pda/ai/verb/cmd_toggle_pda_receiver() - set category = "AI Commands" - set name = "PDA - Toggle Sender/Receiver" - - if(usr.stat == DEAD) - return //won't work if dead - var/mob/living/silicon/S = usr - if(istype(S) && !isnull(S.aiPDA)) - S.aiPDA.toff = !S.aiPDA.toff - to_chat(usr, "PDA sender/receiver toggled [(S.aiPDA.toff ? "Off" : "On")]!") - else - to_chat(usr, "You do not have a PDA. You should make an issue report about this.") - -/obj/item/pda/ai/verb/cmd_toggle_pda_silent() - set category = "AI Commands" - set name = "PDA - Toggle Ringer" - - if(usr.stat == DEAD) - return //won't work if dead - var/mob/living/silicon/S = usr - if(istype(S) && !isnull(S.aiPDA)) - //0 - S.aiPDA.silent = !S.aiPDA.silent - to_chat(usr, "PDA ringer toggled [(S.aiPDA.silent ? "Off" : "On")]!") - else - to_chat(usr, "You do not have a PDA. You should make an issue report about this.") - -/mob/living/silicon/proc/cmd_send_pdamesg(mob/user) - var/list/plist = list() - var/list/namecounts = list() - - if(aiPDA.toff) - to_chat(user, "Turn on your receiver in order to send messages.") - return - - for (var/obj/item/pda/P in get_viewable_pdas()) - if (P == src) - continue - else if (P == aiPDA) - continue - - plist[avoid_assoc_duplicate_keys(P.owner, namecounts)] = P - - var/c = input(user, "Please select a PDA") as null|anything in sortList(plist) - - if (!c) - return - - var/selected = plist[c] - - if(aicamera.stored.len) - var/add_photo = input(user,"Do you want to attach a photo?","Photo","No") as null|anything in list("Yes","No") - if(add_photo=="Yes") - var/datum/picture/Pic = aicamera.selectpicture(user) - aiPDA.picture = Pic - - if(incapacitated()) - return - - aiPDA.create_message(src, selected) - -/mob/living/silicon/proc/cmd_show_message_log(mob/user) - if(incapacitated()) - return - if(!isnull(aiPDA)) - var/HTML = "AI PDA Message Log[aiPDA.tnote]" - user << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") - else - to_chat(user, "You do not have a PDA! You should make an issue report about this.") - -// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP -/obj/item/pda/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/atom/A in src) - A.emp_act(severity) - if (!(. & EMP_PROTECT_SELF)) - emped++ - addtimer(CALLBACK(src, .proc/emp_end), 200 * severity) - -/obj/item/pda/proc/emp_end() - emped-- - -/proc/get_viewable_pdas(sort_by_job = FALSE) - . = list() - // Returns a list of PDAs which can be viewed from another PDA/message monitor., - var/sortmode - if(sort_by_job) - sortmode = /proc/cmp_pdajob_asc - else - sortmode = /proc/cmp_pdaname_asc - - for(var/obj/item/pda/P in sortList(GLOB.PDAs, sortmode)) - if(!P.owner || P.toff || P.hidden) - continue - . += P - -/obj/item/pda/proc/pda_no_detonate() - return COMPONENT_PDA_NO_DETONATE - -#undef PDA_SCANNER_NONE -#undef PDA_SCANNER_MEDICAL -#undef PDA_SCANNER_FORENSICS -#undef PDA_SCANNER_REAGENT -#undef PDA_SCANNER_HALOGEN -#undef PDA_SCANNER_GAS -#undef PDA_SPAM_DELAY + +//The advanced pea-green monochrome lcd of tomorrow. + +GLOBAL_LIST_EMPTY(PDAs) + +#define PDA_SCANNER_NONE 0 +#define PDA_SCANNER_MEDICAL 1 +#define PDA_SCANNER_FORENSICS 2 //unused +#define PDA_SCANNER_REAGENT 3 +#define PDA_SCANNER_HALOGEN 4 +#define PDA_SCANNER_GAS 5 +#define PDA_SPAM_DELAY 2 MINUTES + +/obj/item/pda + name = "\improper PDA" + desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." + icon = 'icons/obj/pda.dmi' + icon_state = "pda" + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT + actions_types = list(/datum/action/item_action/toggle_light) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + + + //Main variables + var/owner = null // String name of owner + var/default_cartridge = 0 // Access level defined by cartridge + var/obj/item/cartridge/cartridge = null //current cartridge + var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. + var/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file. + var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. + var/font_mode = "font-family:monospace;" //The currently selected font. + var/background_color = "#808000" //The currently selected background color. + + #define FONT_MONO "font-family:monospace;" + #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" + #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" + #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" + #define MODE_MONO 0 + #define MODE_SHARE 1 + #define MODE_ORBITRON 2 + #define MODE_VT 3 + + //Secondary variables + var/scanmode = PDA_SCANNER_NONE + var/fon = FALSE //Is the flashlight function on? + var/f_lum = 2.3 //Luminosity for the flashlight function + var/silent = FALSE //To beep or not to beep, that is the question + var/toff = FALSE //If TRUE, messenger disabled + var/tnote = null //Current Texts + var/last_text //No text spamming + var/last_everyone //No text for everyone spamming + var/last_noise //Also no honk spamming that's bad too + var/ttone = "beep" //The ringtone! + var/honkamt = 0 //How many honks left when infected with honk.exe + var/mimeamt = 0 //How many silence left when infected with mime.exe + var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function + var/notehtml = "" + var/notescanned = FALSE // True if what is in the notekeeper was from a paper. + var/hidden = FALSE // Is the PDA hidden from the PDA list? + var/emped = FALSE + var/equipped = FALSE //used here to determine if this is the first time its been picked up + var/allow_emojis = FALSE //if the pda can send emojis and actually have them parsed as such + var/sort_by_job = FALSE // If this is TRUE, will sort PDA list by job. + + var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. + var/ownjob = null //related to above + + var/obj/item/paicard/pai = null // A slot for a personal AI device + + var/datum/picture/picture //Scanned photo + + var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) + var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. + var/overlays_x_offset = 0 //x offset to use for certain overlays + + var/underline_flag = TRUE //flag for underline + +/obj/item/pda/suicide_act(mob/living/carbon/user) + var/deathMessage = msg_input(user) + if (!deathMessage) + deathMessage = "i ded" + user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") + tnote += "→ To The Grim Reaper:
                  [deathMessage]
                  "//records a message in their PDA as being sent to the grim reaper + return BRUTELOSS + +/obj/item/pda/examine(mob/user) + . = ..() + if(!id && !inserted_item) + return + + if(id) + . += "Alt-click to remove the ID." //won't name ID on examine in case it's stolen + + if(inserted_item && (!isturf(loc))) + . += "Ctrl-click to remove [inserted_item]." //traitor pens are disguised so we're fine naming them on examine + + if((!isnull(cartridge))) + . += "Ctrl+Shift-click to remove the cartridge." //won't name cart on examine in case it's Detomatix + +/obj/item/pda/Initialize() + . = ..() + if(fon) + set_light(f_lum) + + GLOB.PDAs += src + if(default_cartridge) + cartridge = new default_cartridge(src) + if(inserted_item) + inserted_item = new inserted_item(src) + else + inserted_item = new /obj/item/pen(src) + update_icon() + +/obj/item/pda/equipped(mob/user, slot) + . = ..() + if(!equipped) + if(user.client) + background_color = user.client.prefs.pda_color + switch(user.client.prefs.pda_style) + if(MONO) + font_index = MODE_MONO + font_mode = FONT_MONO + if(SHARE) + font_index = MODE_SHARE + font_mode = FONT_SHARE + if(ORBITRON) + font_index = MODE_ORBITRON + font_mode = FONT_ORBITRON + if(VT) + font_index = MODE_VT + font_mode = FONT_VT + else + font_index = MODE_MONO + font_mode = FONT_MONO + equipped = TRUE + +/obj/item/pda/proc/update_label() + name = "PDA-[owner] ([ownjob])" //Name generalisation + +/obj/item/pda/GetAccess() + if(id) + return id.GetAccess() + else + return ..() + +/obj/item/pda/GetID() + return id + +/obj/item/pda/RemoveID() + return do_remove_id() + +/obj/item/pda/InsertID(obj/item/inserting_item) + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return + insert_id(inserting_id) + if(id == inserting_id) + return TRUE + return FALSE + +/obj/item/pda/update_overlays() + . = ..() + var/mutable_appearance/overlay = new(icon) + overlay.pixel_x = overlays_x_offset + if(id) + overlay.icon_state = "id_overlay" + . += new /mutable_appearance(overlay) + if(inserted_item) + overlay.icon_state = "insert_overlay" + . += new /mutable_appearance(overlay) + if(fon) + overlay.icon_state = "light_overlay" + . += new /mutable_appearance(overlay) + if(pai) + if(pai.pai) + overlay.icon_state = "pai_overlay" + . += new /mutable_appearance(overlay) + else + overlay.icon_state = "pai_off_overlay" + . += new /mutable_appearance(overlay) + +/obj/item/pda/MouseDrop(mob/over, src_location, over_location) + var/mob/M = usr + if((M == over) && usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return attack_self(M) + return ..() + +/obj/item/pda/attack_self_tk(mob/user) + to_chat(user, "The PDA's capacitive touch screen doesn't seem to respond!") + return + +/obj/item/pda/interact(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + + ..() + + var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pda) + assets.send(user) + + var/datum/asset/spritesheet/emoji_s = get_asset_datum(/datum/asset/spritesheet/goonchat) + emoji_s.send(user) //Already sent by chat but no harm doing this + + user.set_machine(src) + + var/dat = "Personal Data Assistant" + dat += assets.css_tag() + dat += emoji_s.css_tag() + + dat += "[PDAIMG(refresh)]Refresh" + + if ((!isnull(cartridge)) && (mode == 0)) + dat += " | [PDAIMG(eject)]Eject [cartridge]" + if (mode) + dat += " | [PDAIMG(menu)]Return" + + if (mode == 0) + dat += "
                  " + dat += "
                  Toggle Font" + dat += " | Change Color" + dat += " | Toggle Underline" //underline button + + dat += "
                  " + + dat += "
                  " + + if (!owner) + dat += "Warning: No owner information entered. Please swipe card.

                  " + dat += "[PDAIMG(refresh)]Retry" + else + switch (mode) + if (0) + dat += "

                  PERSONAL DATA ASSISTANT v.1.2

                  " + dat += "Owner: [owner], [ownjob]
                  " + dat += text("ID: [id ? "[id.registered_name], [id.assignment]" : "----------"]") + dat += text("
                  [id ? "Update PDA Info" : ""]

                  ") + + dat += "[station_time_timestamp()]
                  " //:[world.time / 100 % 6][world.time / 100 % 10]" + dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]" + + dat += "

                  " + + dat += "

                  General Functions

                  " + dat += "" + if (cartridge.access & CART_ENGINE) + dat += "

                  Engineering Functions

                  " + dat += "" + if (cartridge.access & CART_MEDICAL) + dat += "

                  Medical Functions

                  " + dat += "" + if (cartridge.access & CART_SECURITY) + dat += "

                  Security Functions

                  " + dat += "" + if(cartridge.access & CART_QUARTERMASTER) + dat += "

                  Quartermaster Functions:

                  " + dat += "" + dat += "
                " + + dat += "

                Utilities

                " + dat += "" + + if (1) + dat += "

                [PDAIMG(notes)] Notekeeper V2.2

                " + dat += "Edit
                " + if(notescanned) + dat += "(This is a scanned image, editing it may cause some text formatting to change.)
                " + dat += "
                [(!notehtml ? note : notehtml)]" + + if (2) + dat += "

                [PDAIMG(mail)] SpaceMessenger V3.9.6

                " + dat += "[PDAIMG(bell)]Ringer: [silent == 1 ? "Off" : "On"] | " + dat += "[PDAIMG(mail)]Send / Receive: [toff == 1 ? "Off" : "On"] | " + dat += "[PDAIMG(bell)]Set Ringtone | " + dat += "[PDAIMG(mail)]Messages
                " + dat += "Sorted by: [sort_by_job ? "Job" : "Name"]" + + if(cartridge) + dat += cartridge.message_header() + + dat += "

                [PDAIMG(menu)] Detected PDAs

                " + + dat += "
                  " + var/count = 0 + + if (!toff) + for (var/obj/item/pda/P in get_viewable_pdas(sort_by_job)) + if (P == src) + continue + dat += "
                • [P.owner] ([P.ownjob])" + if(cartridge) + dat += cartridge.message_special(P) + dat += "
                • " + count++ + dat += "
                " + if (count == 0) + dat += "None detected.
                " + else if(cartridge && cartridge.spam_enabled) + dat += "Send To All" + if(6) + dat += "

                [PDAIMG(mail)] ExperTrak® Skill Tracker V4.26.2

                " + dat += "Thank you for choosing ExperTrak® brand software! ExperTrak® inc. is proud to be a NanoTrasen employee expertise and effectiveness department subsidary!" + dat += "

                This software is designed to track and monitor your skill development as a NanoTrasen employee. Your job performance across different fields has been quantified and categorized below.
                " + var/datum/mind/targetmind = user.mind + for (var/type in GLOB.skill_types) + var/datum/skill/S = GetSkillRef(type) + var/lvl_num = targetmind.get_skill_level(type) + var/lvl_name = uppertext(targetmind.get_skill_level_name(type)) + var/exp = targetmind.get_skill_exp(type) + var/xp_prog_to_level = targetmind.exp_needed_to_level_up(type) + var/xp_req_to_level = 0 + if (xp_prog_to_level)//is it even possible to level up? + xp_req_to_level = SKILL_EXP_LIST[lvl_num+1] - SKILL_EXP_LIST[lvl_num] + dat += "
                [S.name]" + dat += "
                [S.desc]" + dat += "
                • EMPLOYEE SKILL LEVEL: [lvl_name]" + if (exp && xp_req_to_level) + var/progress_percent = (xp_req_to_level-xp_prog_to_level)/xp_req_to_level + var/overall_percent = exp / SKILL_EXP_LIST[length(SKILL_EXP_LIST)] + dat += "
                  PROGRESS TO NEXT SKILL LEVEL:" + dat += "
                  " + num2loadingbar(progress_percent) + "([progress_percent*100])%" + dat += "
                  OVERALL DEVELOPMENT PROGRESS:" + dat += "
                  " + num2loadingbar(overall_percent) + "([overall_percent*100])%" + if (lvl_num >= length(SKILL_EXP_LIST) && !(type in targetmind.skills_rewarded)) + dat += "
                  Contact the Professional [S.title] Association" + dat += "
                " + if(21) + dat += "

                [PDAIMG(mail)] SpaceMessenger V3.9.6

                " + dat += "[PDAIMG(blank)]Clear Messages" + + dat += "

                [PDAIMG(mail)] Messages

                " + + dat += tnote + dat += "
                " + + if (3) + dat += "

                [PDAIMG(atmos)] Atmospheric Readings

                " + + var/turf/T = user.loc + if (isnull(T)) + dat += "Unable to obtain a reading.
                " + else + var/datum/gas_mixture/environment = T.return_air() + var/list/env_gases = environment.gases + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + dat += "Air Pressure: [round(pressure,0.1)] kPa
                " + + if (total_moles) + for(var/id in env_gases) + var/gas_level = env_gases[id][MOLES]/total_moles + if(gas_level > 0) + dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
                " + + dat += "Temperature: [round(environment.temperature-T0C)]°C
                " + dat += "
                " + else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. + dat += cartridge.generate_menu() + + dat += "" + + if (underline_flag) + dat = replacetext(dat, "text-decoration:none", "text-decoration:underline") + if (!underline_flag) + dat = replacetext(dat, "text-decoration:underline", "text-decoration:none") + + user << browse(dat, "window=pda;size=400x450;border=1;can_resize=1;can_minimize=0") + onclose(user, "pda", src) + +/obj/item/pda/Topic(href, href_list) + ..() + var/mob/living/U = usr + //Looking for master was kind of pointless since PDAs don't appear to have one. + + if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) + add_fingerprint(U) + U.set_machine(src) + + switch(href_list["choice"]) + +//BASIC FUNCTIONS=================================== + + if("Refresh")//Refresh, goes to the end of the proc. + + if ("Toggle_Font") + //CODE REVISION 2 + font_index = (font_index + 1) % 4 + + switch(font_index) + if (MODE_MONO) + font_mode = FONT_MONO + if (MODE_SHARE) + font_mode = FONT_SHARE + if (MODE_ORBITRON) + font_mode = FONT_ORBITRON + if (MODE_VT) + font_mode = FONT_VT + if ("Change_Color") + var/new_color = input("Please enter a color name or hex value (Default is \'#808000\').",background_color)as color + background_color = new_color + + if ("Toggle_Underline") + underline_flag = !underline_flag + + if("Return")//Return + if(mode<=9) + mode = 0 + else + mode = round(mode/10) + if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. + mode = 0 + if ("Authenticate")//Checks for ID + id_check(U) + if("UpdateInfo") + ownjob = id.assignment + if(istype(id, /obj/item/card/id/syndicate)) + owner = id.registered_name + update_label() + if("Eject")//Ejects the cart, only done from hub. + eject_cart(U) + +//MENU FUNCTIONS=================================== + + if("0")//Hub + mode = 0 + if("1")//Notes + mode = 1 + if("2")//Messenger + mode = 2 + if("21")//Read messeges + mode = 21 + if("3")//Atmos scan + mode = 3 + if("4")//Redirects to hub + mode = 0 + + +//MAIN FUNCTIONS=================================== + + if("Light") + toggle_light(U) + if("Medical Scan") + if(scanmode == PDA_SCANNER_MEDICAL) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) + scanmode = PDA_SCANNER_MEDICAL + if("Reagent Scan") + if(scanmode == PDA_SCANNER_REAGENT) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) + scanmode = PDA_SCANNER_REAGENT + if("Halogen Counter") + if(scanmode == PDA_SCANNER_HALOGEN) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) + scanmode = PDA_SCANNER_HALOGEN + if("Honk") + if ( !(last_noise && world.time < last_noise + 20) ) + playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) + last_noise = world.time + if("Trombone") + if ( !(last_noise && world.time < last_noise + 20) ) + playsound(src, 'sound/misc/sadtrombone.ogg', 50, TRUE) + last_noise = world.time + if("Gas Scan") + if(scanmode == PDA_SCANNER_GAS) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_ATMOS)) + scanmode = PDA_SCANNER_GAS + if("Drone Phone") + var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") + var/area/A = get_area(U) + if(A && alert_s && !QDELETED(U)) + var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" + _alert_drones(msg, TRUE, U) + to_chat(U, msg) + + +//NOTEKEEPER FUNCTIONS=================================== + + if ("Edit") + var/n = stripped_multiline_input(U, "Please enter message", name, note) + if (in_range(src, U) && loc == U) + if (mode == 1 && n) + note = n + notehtml = parsemarkdown(n, U) + notescanned = FALSE + else + U << browse(null, "window=pda") + return + +//MESSENGER FUNCTIONS=================================== + + if("Toggle Messenger") + toff = !toff + if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status + silent = !silent + if("Clear")//Clears messages + tnote = null + if("Ringtone") + var/t = stripped_input(U, "Please enter new ringtone", name, ttone, 20) + if(in_range(src, U) && loc == U && t) + if(SEND_SIGNAL(src, COMSIG_PDA_CHANGE_RINGTONE, U, t) & COMPONENT_STOP_RINGTONE_CHANGE) + U << browse(null, "window=pda") + return + else + ttone = t + else + U << browse(null, "window=pda") + return + if("Message") + create_message(U, locate(href_list["target"]) in GLOB.PDAs) + + if("Sorting Mode") + sort_by_job = !sort_by_job + + if("MessageAll") + send_to_all(U) + + if("cart") + if(cartridge) + cartridge.special(U, href_list) + else + U << browse(null, "window=pda") + return + +//SYNDICATE FUNCTIONS=================================== + + if("Toggle Door") + if(cartridge && cartridge.access & CART_REMOTE_DOOR) + for(var/obj/machinery/door/poddoor/M in GLOB.machines) + if(M.id == cartridge.remote_door_id) + if(M.density) + M.open() + else + M.close() + +//pAI FUNCTIONS=================================== + + if("pai") + switch(href_list["option"]) + if("1") // Configure pAI device + pai.attack_self(U) + if("2") // Eject pAI device + usr.put_in_hands(pai) + to_chat(usr, "You remove the pAI from the [name].") + +//SKILL FUNCTIONS=================================== + + if("SkillReward") + var/type = text2path(href_list["skill"]) + var/datum/skill/S = GetSkillRef(type) + var/datum/mind/mind = U.mind + var/new_level = mind.get_skill_level(type) + S.try_skill_reward(mind, new_level) + +//LINK FUNCTIONS=================================== + + else//Cartridge menu linking + mode = max(text2num(href_list["choice"]), 0) + + else//If not in range, can't interact or not using the pda. + U.unset_machine() + U << browse(null, "window=pda") + return + +//EXTRA FUNCTIONS=================================== + + if (mode == 2 || mode == 21)//To clear message overlays. + update_icon() + + if ((honkamt > 0) && (prob(60)))//For clown virus. + honkamt-- + playsound(src, 'sound/items/bikehorn.ogg', 30, TRUE) + + if(U.machine == src && href_list["skiprefresh"]!="1")//Final safety. + attack_self(U)//It auto-closes the menu prior if the user is not in range and so on. + else + U.unset_machine() + U << browse(null, "window=pda") + return + +/obj/item/pda/proc/remove_id(mob/user) + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + do_remove_id(user) + + +/obj/item/pda/proc/do_remove_id(mob/user) + if(!id) + return + if(user) + user.put_in_hands(id) + to_chat(user, "You remove the ID from the [name].") + else + id.forceMove(get_turf(src)) + + . = id + id = null + updateSelfDialog() + update_icon() + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() + + +/obj/item/pda/proc/msg_input(mob/living/U = usr) + var/t = stripped_input(U, "Please enter message", name) + if (!t || toff) + return + if(!U.canUseTopic(src, BE_CLOSE)) + return + if(emped) + t = Gibberish(t, TRUE) + return t + +/obj/item/pda/proc/send_message(mob/living/user, list/obj/item/pda/targets, everyone) + var/message = msg_input(user) + if(!message || !targets.len) + return + if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY)) + return + if(prob(1)) + message += "\nSent from my PDA" + // Send the signal + var/list/string_targets = list() + for (var/obj/item/pda/P in targets) + if (P.owner && P.ownjob) // != src is checked by the UI + string_targets += "[P.owner] ([P.ownjob])" + for (var/obj/machinery/computer/message_monitor/M in targets) + // In case of "Reply" to a message from a console, this will make the + // message be logged successfully. If the console is impersonating + // someone by matching their name and job, the reply will reach the + // impersonated PDA. + string_targets += "[M.customsender] ([M.customjob])" + if (!string_targets.len) + return + + var/datum/signal/subspace/messaging/pda/signal = new(src, list( + "name" = "[owner]", + "job" = "[ownjob]", + "message" = message, + "targets" = string_targets, + "emojis" = allow_emojis, + )) + if (picture) + signal.data["photo"] = picture + signal.send_to_receivers() + + // If it didn't reach, note that fact + if (!signal.data["done"]) + to_chat(user, "ERROR: Server isn't responding.") + return + + var/target_text = signal.format_target() + if(allow_emojis) + message = emoji_parse(message)//already sent- this just shows the sent emoji as one to the sender in the to_chat + signal.data["message"] = emoji_parse(signal.data["message"]) + + // Log it in our logs + tnote += "→ To [target_text]:
                [signal.format_message()]
                " + // Show it to ghosts + var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]" + for(var/mob/M in GLOB.player_list) + if(isobserver(M) && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA)) + to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]") + // Log in the talk log + user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]") + to_chat(user, "PDA message sent to [target_text]: \"[message]\"") + // Reset the photo + picture = null + last_text = world.time + if (everyone) + last_everyone = world.time + +/obj/item/pda/proc/receive_message(datum/signal/subspace/messaging/pda/signal) + tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                [signal.format_message()]
                " + + if (!silent) + playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) + audible_message("[icon2html(src, hearers(src))] *[ttone]*", null, 3) + //Search for holder of the PDA. + var/mob/living/L = null + if(loc && isliving(loc)) + L = loc + //Maybe they are a pAI! + else + L = get(src, /mob/living/silicon) + + if(L && L.stat != UNCONSCIOUS) + var/reply = "(Reply)" + var/hrefstart + var/hrefend + if (isAI(L)) + hrefstart = "" + hrefend = "" + + if(signal.data["automated"]) + reply = "\[Automated Message\]" + + var/inbound_message = signal.format_message() + if(signal.data["emojis"] == TRUE)//so will not parse emojis as such from pdas that don't send emojis + inbound_message = emoji_parse(inbound_message) + + to_chat(L, "[icon2html(src)] PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [inbound_message] [reply]") + + update_icon() + add_overlay(icon_alert) + +/obj/item/pda/proc/send_to_all(mob/living/U) + if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) + to_chat(U,"Send To All function is still on cooldown.") + return + send_message(U,get_viewable_pdas(), TRUE) + +/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) + send_message(U,list(P)) + +/obj/item/pda/AltClick(mob/user) + ..() + + if(id) + remove_id(user) + else + remove_pen(user) + +/obj/item/pda/CtrlClick(mob/user) + ..() + + if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. + return + + remove_pen(user) + +/obj/item/pda/CtrlShiftClick(mob/user) + ..() + eject_cart(user) + +/obj/item/pda/verb/verb_toggle_light() + set name = "Toggle light" + set category = "Object" + set src in oview(1) + + toggle_light(usr) + +/obj/item/pda/verb/verb_remove_id() + set category = "Object" + set name = "Eject ID" + set src in usr + + if(id) + remove_id(usr) + else + to_chat(usr, "This PDA does not have an ID in it!") + +/obj/item/pda/verb/verb_remove_pen() + set category = "Object" + set name = "Remove Pen" + set src in usr + + remove_pen(usr) + +/obj/item/pda/verb/verb_eject_cart() + set category = "Object" + set name = "Eject Cartridge" + set src in usr + + eject_cart(usr) + +/obj/item/pda/proc/toggle_light(mob/user) + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE)) + return + if(fon) + fon = FALSE + set_light(0) + else if(f_lum) + fon = TRUE + set_light(f_lum) + update_icon() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/pda/proc/remove_pen(mob/user) + + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK doesn't work even with this removed but here for readability + return + + if(inserted_item) + user.put_in_hands(inserted_item) + to_chat(user, "You remove [inserted_item] from [src].") + inserted_item = null + update_icon() + else + to_chat(user, "This PDA does not have a pen in it!") + +/obj/item/pda/proc/eject_cart(mob/user) + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK disabled to stop cartridge teleporting into hand + return + if (!isnull(cartridge)) + user.put_in_hands(cartridge) + to_chat(user, "You eject [cartridge] from [src].") + scanmode = PDA_SCANNER_NONE + cartridge.host_pda = null + cartridge = null + updateSelfDialog() + update_icon() + +//trying to insert or remove an id +/obj/item/pda/proc/id_check(mob/user, obj/item/card/id/I) + if(!I) + if(id && (src in user.contents)) + remove_id(user) + return TRUE + else + var/obj/item/card/id/C = user.get_active_held_item() + if(istype(C)) + I = C + + if(I?.registered_name) + if(!user.transferItemToLoc(I, src)) + return FALSE + insert_id(I, user) + update_icon() + return TRUE + + +/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user) + var/obj/old_id = id + id = inserting_id + if(ishuman(loc)) + var/mob/living/carbon/human/human_wearer = loc + if(human_wearer.wear_id == src) + human_wearer.sec_hud_set_ID() + if(old_id) + if(user) + user.put_in_hands(old_id) + else + old_id.forceMove(get_turf(src)) + + +// access to status display signals +/obj/item/pda/attackby(obj/item/C, mob/user, params) + if(istype(C, /obj/item/cartridge)) + if(!user.transferItemToLoc(C, src)) + return + eject_cart(user) + cartridge = C + cartridge.host_pda = src + to_chat(user, "You insert [cartridge] into [src].") + updateSelfDialog() + update_icon() + + else if(istype(C, /obj/item/card/id)) + var/obj/item/card/id/idcard = C + if(!idcard.registered_name) + to_chat(user, "\The [src] rejects the ID!") + return + if(!owner) + owner = idcard.registered_name + ownjob = idcard.assignment + update_label() + to_chat(user, "Card scanned.") + else + if(!id_check(user, idcard)) + return + to_chat(user, "You put the ID into \the [src]'s slot.") + updateSelfDialog()//Update self dialog on success. + + return //Return in case of failed check or when successful. + updateSelfDialog()//For the non-input related code. + else if(istype(C, /obj/item/paicard) && !pai) + if(!user.transferItemToLoc(C, src)) + return + pai = C + to_chat(user, "You slot \the [C] into [src].") + update_icon() + updateUsrDialog() + else if(is_type_in_list(C, contained_item)) //Checks if there is a pen + if(inserted_item) + to_chat(user, "There is already \a [inserted_item] in \the [src]!") + else + if(!user.transferItemToLoc(C, src)) + return + to_chat(user, "You slide \the [C] into \the [src].") + inserted_item = C + update_icon() + else if(istype(C, /obj/item/photo)) + var/obj/item/photo/P = C + picture = P.picture + to_chat(user, "You scan \the [C].") + else + return ..() + +/obj/item/pda/attack(mob/living/carbon/C, mob/living/user) + if(istype(C)) + switch(scanmode) + + if(PDA_SCANNER_MEDICAL) + C.visible_message("[user] analyzes [C]'s vitals.") + healthscan(user, C, 1) + add_fingerprint(user) + + if(PDA_SCANNER_HALOGEN) + C.visible_message("[user] analyzes [C]'s radiation levels.") + + user.show_message("Analyzing Results for [C]:") + if(C.radiation) + user.show_message("\green Radiation Level: \black [C.radiation]") + else + user.show_message("No radiation detected.") + +/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) + . = ..() + if(!proximity) + return + switch(scanmode) + if(PDA_SCANNER_REAGENT) + if(!isnull(A.reagents)) + if(A.reagents.reagent_list.len > 0) + var/reagents_length = A.reagents.reagent_list.len + to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") + for (var/re in A.reagents.reagent_list) + to_chat(user, "\t [re]") + else + to_chat(user, "No active chemical agents found in [A].") + else + to_chat(user, "No significant chemical agents found in [A].") + + if(PDA_SCANNER_GAS) + A.analyzer_act(user, src) + + if (!scanmode && istype(A, /obj/item/paper) && owner) + var/obj/item/paper/PP = A + if (!PP.info) + to_chat(user, "Unable to scan! Paper is blank.") + return + notehtml = PP.info + note = replacetext(notehtml, "
                ", "\[br\]") + note = replacetext(note, "
              • ", "\[*\]") + note = replacetext(note, "
                  ", "\[list\]") + note = replacetext(note, "
                ", "\[/list\]") + note = html_encode(note) + notescanned = TRUE + to_chat(user, "Paper scanned. Saved to PDA's notekeeper." ) + + +/obj/item/pda/proc/explode() //This needs tuning. + var/turf/T = get_turf(src) + + if (ismob(loc)) + var/mob/M = loc + M.show_message("Your [src] explodes!", MSG_VISUAL, "You hear a loud *pop*!", MSG_AUDIBLE) + else + visible_message("[src] explodes!", "You hear a loud *pop*!") + + if(T) + T.hotspot_expose(700,125) + if(istype(cartridge, /obj/item/cartridge/virus/syndicate)) + explosion(T, -1, 1, 3, 4) + else + explosion(T, -1, -1, 2, 3) + qdel(src) + return + +/obj/item/pda/Destroy() + GLOB.PDAs -= src + if(istype(id)) + QDEL_NULL(id) + if(istype(cartridge)) + QDEL_NULL(cartridge) + if(istype(pai)) + QDEL_NULL(pai) + if(istype(inserted_item)) + QDEL_NULL(inserted_item) + return ..() + +//AI verb and proc for sending PDA messages. + +/obj/item/pda/ai/verb/cmd_toggle_pda_receiver() + set category = "AI Commands" + set name = "PDA - Toggle Sender/Receiver" + + if(usr.stat == DEAD) + return //won't work if dead + var/mob/living/silicon/S = usr + if(istype(S) && !isnull(S.aiPDA)) + S.aiPDA.toff = !S.aiPDA.toff + to_chat(usr, "PDA sender/receiver toggled [(S.aiPDA.toff ? "Off" : "On")]!") + else + to_chat(usr, "You do not have a PDA. You should make an issue report about this.") + +/obj/item/pda/ai/verb/cmd_toggle_pda_silent() + set category = "AI Commands" + set name = "PDA - Toggle Ringer" + + if(usr.stat == DEAD) + return //won't work if dead + var/mob/living/silicon/S = usr + if(istype(S) && !isnull(S.aiPDA)) + //0 + S.aiPDA.silent = !S.aiPDA.silent + to_chat(usr, "PDA ringer toggled [(S.aiPDA.silent ? "Off" : "On")]!") + else + to_chat(usr, "You do not have a PDA. You should make an issue report about this.") + +/mob/living/silicon/proc/cmd_send_pdamesg(mob/user) + var/list/plist = list() + var/list/namecounts = list() + + if(aiPDA.toff) + to_chat(user, "Turn on your receiver in order to send messages.") + return + + for (var/obj/item/pda/P in get_viewable_pdas()) + if (P == src) + continue + else if (P == aiPDA) + continue + + plist[avoid_assoc_duplicate_keys(P.owner, namecounts)] = P + + var/c = input(user, "Please select a PDA") as null|anything in sortList(plist) + + if (!c) + return + + var/selected = plist[c] + + if(aicamera.stored.len) + var/add_photo = input(user,"Do you want to attach a photo?","Photo","No") as null|anything in list("Yes","No") + if(add_photo=="Yes") + var/datum/picture/Pic = aicamera.selectpicture(user) + aiPDA.picture = Pic + + if(incapacitated()) + return + + aiPDA.create_message(src, selected) + +/mob/living/silicon/proc/cmd_show_message_log(mob/user) + if(incapacitated()) + return + if(!isnull(aiPDA)) + var/HTML = "AI PDA Message Log[aiPDA.tnote]" + user << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") + else + to_chat(user, "You do not have a PDA! You should make an issue report about this.") + +// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP +/obj/item/pda/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/atom/A in src) + A.emp_act(severity) + if (!(. & EMP_PROTECT_SELF)) + emped++ + addtimer(CALLBACK(src, .proc/emp_end), 200 * severity) + +/obj/item/pda/proc/emp_end() + emped-- + +/proc/get_viewable_pdas(sort_by_job = FALSE) + . = list() + // Returns a list of PDAs which can be viewed from another PDA/message monitor., + var/sortmode + if(sort_by_job) + sortmode = /proc/cmp_pdajob_asc + else + sortmode = /proc/cmp_pdaname_asc + + for(var/obj/item/pda/P in sortList(GLOB.PDAs, sortmode)) + if(!P.owner || P.toff || P.hidden) + continue + . += P + +/obj/item/pda/proc/pda_no_detonate() + return COMPONENT_PDA_NO_DETONATE + +#undef PDA_SCANNER_NONE +#undef PDA_SCANNER_MEDICAL +#undef PDA_SCANNER_FORENSICS +#undef PDA_SCANNER_REAGENT +#undef PDA_SCANNER_HALOGEN +#undef PDA_SCANNER_GAS +#undef PDA_SPAM_DELAY diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index 3692934c154..566a2f96ab1 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -1,742 +1,742 @@ - -#define CART_SECURITY (1<<0) -#define CART_ENGINE (1<<1) -#define CART_ATMOS (1<<2) -#define CART_MEDICAL (1<<3) -#define CART_MANIFEST (1<<4) -#define CART_CLOWN (1<<5) -#define CART_MIME (1<<6) -#define CART_JANITOR (1<<7) -#define CART_REAGENT_SCANNER (1<<8) -#define CART_NEWSCASTER (1<<9) -#define CART_REMOTE_DOOR (1<<10) -#define CART_STATUS_DISPLAY (1<<11) -#define CART_QUARTERMASTER (1<<12) -#define CART_HYDROPONICS (1<<13) -#define CART_DRONEPHONE (1<<14) - - -/obj/item/cartridge - name = "generic cartridge" - desc = "A data cartridge for portable microcomputers." - icon = 'icons/obj/pda.dmi' - icon_state = "cart" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - - var/obj/item/integrated_signaler/radio = null - - var/access = 0 //Bit flags for cartridge access - - var/remote_door_id = "" - - var/bot_access_flags = 0 //Bit flags. Selection: SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - var/spam_enabled = 0 //Enables "Send to All" Option - - var/obj/item/pda/host_pda = null - var/menu - var/datum/data/record/active1 = null //General - var/datum/data/record/active2 = null //Medical - var/datum/data/record/active3 = null //Security - var/obj/machinery/computer/monitor/powmonitor = null // Power Monitor - var/list/powermonitors = list() - var/message1 // used for status_displays - var/message2 - var/list/stored_data = list() - var/current_channel - - var/mob/living/simple_animal/bot/active_bot - var/list/botlist = list() - -/obj/item/cartridge/Initialize() - . = ..() - var/obj/item/pda/pda = loc - if(istype(pda)) - host_pda = pda - -/obj/item/cartridge/engineering - name = "\improper Power-ON cartridge" - icon_state = "cart-e" - access = CART_ENGINE | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT - -/obj/item/cartridge/atmos - name = "\improper BreatheDeep cartridge" - icon_state = "cart-a" - access = CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | FIRE_BOT - -/obj/item/cartridge/medical - name = "\improper Med-U cartridge" - icon_state = "cart-m" - access = CART_MEDICAL - bot_access_flags = MED_BOT - -/obj/item/cartridge/chemistry - name = "\improper ChemWhiz cartridge" - icon_state = "cart-chem" - access = CART_REAGENT_SCANNER - bot_access_flags = MED_BOT - -/obj/item/cartridge/security - name = "\improper R.O.B.U.S.T. cartridge" - icon_state = "cart-s" - access = CART_SECURITY | CART_MANIFEST - bot_access_flags = SEC_BOT - -/obj/item/cartridge/detective - name = "\improper D.E.T.E.C.T. cartridge" - icon_state = "cart-s" - access = CART_SECURITY | CART_MEDICAL | CART_MANIFEST - bot_access_flags = SEC_BOT - -/obj/item/cartridge/janitor - name = "\improper CustodiPRO cartridge" - desc = "The ultimate in clean-room design." - icon_state = "cart-j" - access = CART_JANITOR | CART_DRONEPHONE - bot_access_flags = CLEAN_BOT - -/obj/item/cartridge/lawyer - name = "\improper P.R.O.V.E. cartridge" - icon_state = "cart-s" - access = CART_SECURITY - spam_enabled = 1 - -/obj/item/cartridge/curator - name = "\improper Lib-Tweet cartridge" - icon_state = "cart-s" - access = CART_NEWSCASTER - -/obj/item/cartridge/roboticist - name = "\improper B.O.O.P. Remote Control cartridge" - desc = "Packed with heavy duty quad-bot interlink!" - bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - access = CART_DRONEPHONE - -/obj/item/cartridge/signal - name = "generic signaler cartridge" - desc = "A data cartridge with an integrated radio signaler module." - -/obj/item/cartridge/signal/toxins - name = "\improper Signal Ace 2 cartridge" - desc = "Complete with integrated radio signaler!" - icon_state = "cart-tox" - access = CART_REAGENT_SCANNER | CART_ATMOS - -/obj/item/cartridge/signal/Initialize() - . = ..() - radio = new(src) - - - -/obj/item/cartridge/quartermaster - name = "space parts & space vendors cartridge" - desc = "Perfect for the Quartermaster on the go!" - icon_state = "cart-q" - access = CART_QUARTERMASTER - bot_access_flags = MULE_BOT - -/obj/item/cartridge/head - name = "\improper Easy-Record DELUXE cartridge" - icon_state = "cart-h" - access = CART_MANIFEST | CART_STATUS_DISPLAY - -/obj/item/cartridge/hop - name = "\improper HumanResources9001 cartridge" - icon_state = "cart-h" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_JANITOR | CART_SECURITY | CART_NEWSCASTER | CART_QUARTERMASTER | CART_DRONEPHONE - bot_access_flags = MULE_BOT | CLEAN_BOT - -/obj/item/cartridge/hos - name = "\improper R.O.B.U.S.T. DELUXE cartridge" - icon_state = "cart-hos" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_SECURITY - bot_access_flags = SEC_BOT - - -/obj/item/cartridge/ce - name = "\improper Power-On DELUXE cartridge" - icon_state = "cart-ce" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_ENGINE | CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | FIRE_BOT - -/obj/item/cartridge/cmo - name = "\improper Med-U DELUXE cartridge" - icon_state = "cart-cmo" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_MEDICAL - bot_access_flags = MED_BOT - -/obj/item/cartridge/rd - name = "\improper Signal Ace DELUXE cartridge" - icon_state = "cart-rd" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - -/obj/item/cartridge/rd/Initialize() - . = ..() - radio = new(src) - -/obj/item/cartridge/captain - name = "\improper Value-PAK cartridge" - desc = "Now with 350% more value!" //Give the Captain...EVERYTHING! (Except Mime, Clown, and Syndie) - icon_state = "cart-c" - access = ~(CART_CLOWN | CART_MIME | CART_REMOTE_DOOR) - bot_access_flags = SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - spam_enabled = 1 - -/obj/item/cartridge/captain/Initialize() - . = ..() - radio = new(src) - -/obj/item/cartridge/proc/post_status(command, data1, data2) - - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = command)) - switch(command) - if("message") - status_signal.data["msg1"] = data1 - status_signal.data["msg2"] = data2 - if("alert") - status_signal.data["picture_state"] = data1 - - frequency.post_signal(src, status_signal) - -/obj/item/cartridge/proc/generate_menu(mob/user) - if(!host_pda) - return - switch(host_pda.mode) - if(40) //signaller - menu = "

                [PDAIMG(signaler)] Remote Signaling System

                " - - menu += {" -Send Signal
                -Frequency: -- -- -[format_frequency(radio.frequency)] -+ -+
                -
                -Code: -- -- -[radio.code] -+ -+
                "} - if (41) //crew manifest - menu = "

                [PDAIMG(notes)] Crew Manifest

                " - menu += "
                [GLOB.data_core.get_manifest_html(monochrome=TRUE)]
                " - - - if (42) //status displays - menu = "

                [PDAIMG(status)] Station Status Display Interlink

                " - - menu += "\[ Clear \]
                " - menu += "\[ Shuttle ETA \]
                " - menu += "\[ Message \]" - menu += "
                " - menu += "\[ Alert: None |" - menu += " Red Alert |" - menu += " Lockdown |" - menu += " Biohazard \]
                " - - if (43) - menu = "

                [PDAIMG(power)] Power Monitors - Please select one


                " - powmonitor = null - powermonitors = list() - var/powercount = 0 - - - - var/turf/pda_turf = get_turf(src) - for(var/obj/machinery/computer/monitor/pMon in GLOB.machines) - if(pMon.machine_stat & (NOPOWER | BROKEN)) //check to make sure the computer is functional - continue - if(pda_turf.z != pMon.z) //and that we're on the same zlevel as the computer (lore: limited signal strength) - continue - if(pMon.is_secret_monitor) //make sure it isn't a secret one (ie located on a ruin), allowing people to metagame that the location exists - continue - powercount++ - powermonitors += pMon - - - if(!powercount) - menu += "No connection
                " - else - - menu += "" - var/count = 0 - for(var/obj/machinery/computer/monitor/pMon in powermonitors) - count++ - menu += "[pMon] - [get_area_name(pMon, TRUE)]
                " - - menu += "
                " - - if (433) - menu = "

                [PDAIMG(power)] Power Monitor


                " - if(!powmonitor || !powmonitor.get_powernet()) - menu += "No connection
                " - else - var/list/L = list() - var/datum/powernet/connected_powernet = powmonitor.get_powernet() - for(var/obj/machinery/power/terminal/term in connected_powernet.nodes) - if(istype(term.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = term.master - L += A - - menu += "
                Location: [get_area_name(powmonitor, TRUE)]
                Total power: [DisplayPower(connected_powernet.viewavail)]
                Total load: [DisplayPower(connected_powernet.viewload)]
                " - - menu += "" - - if(L.len > 0) - menu += "Area Eqp./Lgt./Env. Load Cell
                " - - var/list/S = list(" Off","AOff"," On", " AOn") - var/list/chg = list("N","C","F") -//Neither copytext nor copytext_char is appropriate here; neither 30 UTF-8 code units nor 30 code points equates to 30 columns of output. -//Some glyphs are very tall or very wide while others are small or even take up no space at all. -//Emojis can take modifiers which are many characters but render as only one glyph. -//A proper solution here (as far as Unicode goes, maybe not ideal as far as markup goes, a table would be better) -//would be to use [A.area.name] - for(var/obj/machinery/power/apc/A in L) - menu += copytext_char(add_trailing(A.area.name, 30, " "), 1, 30) - menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_leading(DisplayPower(A.lastused_total), 6, " ")] [A.cell ? "[add_leading(round(A.cell.percent()), 3, " ")]% [chg[A.charging+1]]" : " N/C"]
                " - - menu += "
                " - - if (44) //medical records //This thing only displays a single screen so it's hard to really get the sub-menu stuff working. - menu = "

                [PDAIMG(medical)] Medical Record List

                " - if(GLOB.data_core.general) - for(var/datum/data/record/R in sortRecord(GLOB.data_core.general)) - menu += "[R.fields["id"]]: [R.fields["name"]]
                " - menu += "
                " - if(441) - menu = "

                [PDAIMG(medical)] Medical Record

                " - - if(active1 in GLOB.data_core.general) - menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                " - menu += "Gender: [active1.fields["gender"]]
                " - menu += "Age: [active1.fields["age"]]
                " - menu += "Rank: [active1.fields["rank"]]
                " - menu += "Fingerprint: [active1.fields["fingerprint"]]
                " - menu += "Physical Status: [active1.fields["p_stat"]]
                " - menu += "Mental Status: [active1.fields["m_stat"]]
                " - else - menu += "Record Lost!
                " - - menu += "
                " - - menu += "

                [PDAIMG(medical)] Medical Data

                " - if(active2 in GLOB.data_core.medical) - menu += "Blood Type: [active2.fields["blood_type"]]

                " - - menu += "Minor Disabilities: [active2.fields["mi_dis"]]
                " - menu += "Details: [active2.fields["mi_dis_d"]]

                " - - menu += "Major Disabilities: [active2.fields["ma_dis"]]
                " - menu += "Details: [active2.fields["ma_dis_d"]]

                " - - menu += "Allergies: [active2.fields["alg"]]
                " - menu += "Details: [active2.fields["alg_d"]]

                " - - menu += "Current Diseases: [active2.fields["cdi"]]
                " - menu += "Details: [active2.fields["cdi_d"]]

                " - - menu += "Important Notes: [active2.fields["notes"]]
                " - else - menu += "Record Lost!
                " - - menu += "
                " - if (45) //security records - menu = "

                [PDAIMG(cuffs)] Security Record List

                " - if(GLOB.data_core.general) - for (var/datum/data/record/R in sortRecord(GLOB.data_core.general)) - menu += "
                [R.fields["id"]]: [R.fields["name"]]
                " - - menu += "
                " - if(451) - menu = "

                [PDAIMG(cuffs)] Security Record

                " - - if(active1 in GLOB.data_core.general) - menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                " - menu += "Gender: [active1.fields["gender"]]
                " - menu += "Age: [active1.fields["age"]]
                " - menu += "Rank: [active1.fields["rank"]]
                " - menu += "Fingerprint: [active1.fields["fingerprint"]]
                " - menu += "Physical Status: [active1.fields["p_stat"]]
                " - menu += "Mental Status: [active1.fields["m_stat"]]
                " - else - menu += "Record Lost!
                " - - menu += "
                " - - menu += "

                [PDAIMG(cuffs)] Security Data

                " - if(active3 in GLOB.data_core.security) - menu += "Criminal Status: [active3.fields["criminal"]]
                " - - menu += text("
                \nCrimes:") - - menu +={" - - - - - -"} - for(var/datum/data/crime/c in active3.fields["crim"]) - menu += "" - menu += "" - menu += "" - menu += "" - menu += "" - menu += "
                CrimeDetailsAuthorTime Added
                [c.crimeName][c.crimeDetails][c.author][c.time]
                " - menu += "
                \nImportant Notes:
                " - menu += "[active3.fields["notes"]]" - else - menu += "Record Lost!
                " - - menu += "
                " - - if (47) //quartermaster order records - menu = "

                [PDAIMG(crate)] Supply Record Interlink

                " - - menu += "
                Supply shuttle
                " - menu += "Location: " - switch(SSshuttle.supply.mode) - if(SHUTTLE_CALL) - menu += "Moving to " - if(!is_station_level(SSshuttle.supply.z)) - menu += "station" - else - menu += "CentCom" - menu += " ([SSshuttle.supply.timeLeft(600)] Mins)" - else - menu += "At " - if(!is_station_level(SSshuttle.supply.z)) - menu += "CentCom" - else - menu += "station" - menu += "
                Current approved orders:
                  " - for(var/S in SSshuttle.shoppinglist) - var/datum/supply_order/SO = S - menu += "
                1. #[SO.id] - [SO.pack.name] approved by [SO.orderer] [SO.reason ? "([SO.reason])":""]
                2. " - menu += "
                " - - menu += "Current requests:
                  " - for(var/S in SSshuttle.requestlist) - var/datum/supply_order/SO = S - menu += "
                1. #[SO.id] - [SO.pack.name] requested by [SO.orderer]
                2. " - menu += "
                Upgrade NOW to Space Parts & Space Vendors PLUS for full remote order control and inventory management." - - if (48) // quartermaster ore logs - menu = list("

                [PDAIMG(crate)] Ore Silo Logs

                ") - if (GLOB.ore_silo_default) - var/list/logs = GLOB.silo_access_logs[REF(GLOB.ore_silo_default)] - var/len = LAZYLEN(logs) - var/i = 0 - for(var/M in logs) - if (++i > 30) - menu += "(... older logs not shown ...)" - break - var/datum/ore_silo_log/entry = M - menu += "[len - i]. [entry.formatted]

                " - if(i == 0) - menu += "Nothing!" - else - menu += "No ore silo detected!" - menu = jointext(menu, "") - - if (49) //janitorial locator - menu = "

                [PDAIMG(bucket)] Persistent Custodial Object Locator

                " - - var/turf/cl = get_turf(src) - if (cl) - menu += "Current Orbital Location: \[[cl.x],[cl.y]\]" - - menu += "

                Located Mops:

                " - - var/ldat - for (var/obj/item/mop/M in world) - var/turf/ml = get_turf(M) - - if(ml) - if (ml.z != cl.z) - continue - var/direction = get_dir(src, M) - ldat += "Mop - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\] - [M.reagents.total_volume ? "Wet" : "Dry"]
                " - - if (!ldat) - menu += "None" - else - menu += "[ldat]" - - menu += "

                Located Janitorial Cart:

                " - - ldat = null - for (var/obj/structure/janitorialcart/B in world) - var/turf/bl = get_turf(B) - - if(bl) - if (bl.z != cl.z) - continue - var/direction = get_dir(src, B) - ldat += "Cart - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - Water level: [B.reagents.total_volume]/100
                " - - if (!ldat) - menu += "None" - else - menu += "[ldat]" - - menu += "

                Located Cleanbots:

                " - - ldat = null - for (var/mob/living/simple_animal/bot/cleanbot/B in GLOB.alive_mob_list) - var/turf/bl = get_turf(B) - - if(bl) - if (bl.z != cl.z) - continue - var/direction = get_dir(src, B) - ldat += "Cleanbot - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - [B.on ? "Online" : "Offline"]
                " - - if (!ldat) - menu += "None" - else - menu += "[ldat]" - - else - menu += "ERROR: Unable to determine current location." - menu += "

                Refresh GPS Locator" - - if (53) // Newscaster - menu = "

                [PDAIMG(notes)] Newscaster Access

                " - menu += "
                Current Newsfeed: [current_channel ? current_channel : "None"]
                " - var/datum/newscaster/feed_channel/current - for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) - if (chan.channel_name == current_channel) - current = chan - if(!current) - menu += "
                ERROR : NO CHANNEL FOUND
                " - return menu - var/i = 1 - for(var/datum/newscaster/feed_message/msg in current.messages) - menu +="-[msg.returnBody(-1)]
                \[Story by [msg.returnAuthor(-1)]\]
                " - menu +="[msg.comments.len] comment[msg.comments.len > 1 ? "s" : ""]
                " - if(msg.img) - user << browse_rsc(msg.img, "tmp_photo[i].png") - menu +="
                " - i++ - for(var/datum/newscaster/feed_comment/comment in msg.comments) - menu +="[comment.body]
                [comment.author] [comment.time_stamp]
                " - menu += "
                Post Message" - - if (54) // Beepsky, Medibot, Floorbot, and Cleanbot access - menu = "

                [PDAIMG(medbot)] Bots Interlink

                " - bot_control() - if (55) // Emoji Guidebook for mimes - menu = "

                [PDAIMG(emoji)] Emoji Guidebook

                " - var/static/list/emoji_icon_states - var/static/emoji_table - if(!emoji_table) - var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat) - var/list/collate = list("
                ") - for(var/emoji in sortList(icon_states(icon('icons/emoji.dmi')))) - var/tag = sheet.icon_tag("emoji-[emoji]") - collate += "" - collate += "
                [emoji][tag]

                " - emoji_table = collate.Join() - - menu += "
                To use an emoji in a pda message, refer to the guide and add \":\" around the emoji. Your PDA supports the following emoji:
                " - menu += emoji_table - - if (99) //Newscaster message permission error - menu = "
                ERROR : NOT AUTHORIZED [host_pda.id ? "" : "- ID SLOT EMPTY"]
                " - - return menu - -/obj/item/cartridge/Topic(href, href_list) - ..() - - if(!usr.canUseTopic(src, !issilicon(usr))) - usr.unset_machine() - usr << browse(null, "window=pda") - return - - switch(href_list["choice"]) - if("Medical Records") - active1 = find_record("id", href_list["target"], GLOB.data_core.general) - if(active1) - active2 = find_record("id", href_list["target"], GLOB.data_core.medical) - host_pda.mode = 441 - if(!active2) - active1 = null - - if("Security Records") - active1 = find_record("id", href_list["target"], GLOB.data_core.general) - if(active1) - active3 = find_record("id", href_list["target"], GLOB.data_core.security) - host_pda.mode = 451 - if(!active3) - active1 = null - - if("Send Signal") - INVOKE_ASYNC(radio, /obj/item/integrated_signaler.proc/send_activation) - - if("Signal Frequency") - var/new_frequency = sanitize_frequency(radio.frequency + text2num(href_list["sfreq"])) - radio.set_frequency(new_frequency) - - if("Signal Code") - radio.code += text2num(href_list["scode"]) - radio.code = round(radio.code) - radio.code = min(100, radio.code) - radio.code = max(1, radio.code) - - if("Status") - switch(href_list["statdisp"]) - if("message") - post_status("message", message1, message2) - if("alert") - post_status("alert", href_list["alert"]) - if("setmsg1") - message1 = reject_bad_text(input("Line 1", "Enter Message Text", message1) as text|null, 40) - updateSelfDialog() - if("setmsg2") - message2 = reject_bad_text(input("Line 2", "Enter Message Text", message2) as text|null, 40) - updateSelfDialog() - else - post_status(href_list["statdisp"]) - if("Power Select") - var/pnum = text2num(href_list["target"]) - powmonitor = powermonitors[pnum] - host_pda.mode = 433 - - if("Supply Orders") - host_pda.mode =47 - - if("Newscaster Access") - host_pda.mode = 53 - - if("Newscaster Message") - var/host_pda_owner_name = host_pda.id ? "[host_pda.id.registered_name] ([host_pda.id.assignment])" : "Unknown" - var/message = host_pda.msg_input() - var/datum/newscaster/feed_channel/current - for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) - if (chan.channel_name == current_channel) - current = chan - if(current.locked && current.author != host_pda_owner_name) - host_pda.mode = 99 - host_pda.Topic(null,list("choice"="Refresh")) - return - GLOB.news_network.SubmitArticle(message,host_pda.owner,current_channel) - host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) - return - - if("Newscaster Switch Channel") - current_channel = host_pda.msg_input() - host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) - return - - //emoji previews - if(href_list["emoji"]) - var/parse = emoji_parse(":[href_list["emoji"]]:") - to_chat(usr, parse) - - //Bot control section! Viciously ripped from radios for being laggy and terrible. - if(href_list["op"]) - switch(href_list["op"]) - - if("control") - active_bot = locate(href_list["bot"]) in GLOB.bots_list - - if("botlist") - active_bot = null - if("summon") //Args are in the correct order, they are stated here just as an easy reminder. - active_bot.bot_control("summon", usr, host_pda.GetAccess()) - else //Forward all other bot commands to the bot itself! - active_bot.bot_control(href_list["op"], usr) - - if(href_list["mule"]) //MULEbots are special snowflakes, and need different args due to how they work. - var/mob/living/simple_animal/bot/mulebot/mule = active_bot - if (istype(mule)) - mule.bot_control(href_list["mule"], usr, pda=TRUE) - - if(!host_pda) - return - host_pda.attack_self(usr) - - -/obj/item/cartridge/proc/bot_control() - if(active_bot) - menu += "[active_bot]
                Status: ([PDAIMG(refresh)]refresh)
                " - menu += "Model: [active_bot.model]
                " - menu += "Location: [get_area(active_bot)]
                " - menu += "Mode: [active_bot.get_mode()]" - if(active_bot.allow_pai) - menu += "
                pAI: " - if(active_bot.paicard && active_bot.paicard.pai) - menu += "[active_bot.paicard.pai.name]" - if(active_bot.bot_core.allowed(usr)) - menu += " (eject)" - else - menu += "none" - - //MULEs! - if(active_bot.bot_type == MULE_BOT) - var/mob/living/simple_animal/bot/mulebot/MULE = active_bot - var/atom/Load = MULE.load - menu += "
                Current Load: [ !Load ? "none" : "[Load.name] (unload)" ]
                " - menu += "Destination: [MULE.destination ? MULE.destination : "None"] (set)
                " - menu += "Set ID: [MULE.suffix] Modify
                " - menu += "Power: [MULE.cell ? MULE.cell.percent() : 0]%
                " - menu += "Home: [!MULE.home_destination ? "none" : MULE.home_destination ]
                " - menu += "Delivery Reporting: [MULE.report_delivery ? "(On)": "(Off)"]
                " - menu += "Auto Return Home: [MULE.auto_return ? "(On)": "(Off)"]
                " - menu += "Auto Pickup Crate: [MULE.auto_pickup ? "(On)": "(Off)"]

                " //Hue. - - menu += "\[Stop\] " - menu += "\[Proceed\] " - menu += "\[Return Home\]
                " - - else - menu += "
                \[Stop Patrol\] " //patrolon - menu += "\[Start Patrol\] " //patroloff - menu += "\[Summon Bot\]
                " //summon - menu += "Keep an ID inserted to upload access codes upon summoning." - - menu += "
                [PDAIMG(back)]Return to bot list" - else - menu += "
                [PDAIMG(refresh)]Scan for active bots

                " - var/turf/current_turf = get_turf(src) - var/zlevel = current_turf.z - var/botcount = 0 - for(var/B in GLOB.bots_list) //Git da botz - var/mob/living/simple_animal/bot/Bot = B - if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled || !(bot_access_flags & Bot.bot_type)) //Only non-emagged bots on the same Z-level are detected! - continue //Also, the PDA must have access to the bot type. - menu += "[Bot.name] ([Bot.get_mode()])
                " - botcount++ - if(!botcount) //No bots at all? Lame. - menu += "No bots found.
                " - return - - return menu - -//If the cartridge adds a special line to the top of the messaging app -/obj/item/cartridge/proc/message_header() - return "" - -//If the cartridge adds something to each potetial messaging target -/obj/item/cartridge/proc/message_special(obj/item/pda/target) - return "" - -//This is called for special abilities of cartridges -/obj/item/cartridge/proc/special(mob/living/user, list/params) + +#define CART_SECURITY (1<<0) +#define CART_ENGINE (1<<1) +#define CART_ATMOS (1<<2) +#define CART_MEDICAL (1<<3) +#define CART_MANIFEST (1<<4) +#define CART_CLOWN (1<<5) +#define CART_MIME (1<<6) +#define CART_JANITOR (1<<7) +#define CART_REAGENT_SCANNER (1<<8) +#define CART_NEWSCASTER (1<<9) +#define CART_REMOTE_DOOR (1<<10) +#define CART_STATUS_DISPLAY (1<<11) +#define CART_QUARTERMASTER (1<<12) +#define CART_HYDROPONICS (1<<13) +#define CART_DRONEPHONE (1<<14) + + +/obj/item/cartridge + name = "generic cartridge" + desc = "A data cartridge for portable microcomputers." + icon = 'icons/obj/pda.dmi' + icon_state = "cart" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + + var/obj/item/integrated_signaler/radio = null + + var/access = 0 //Bit flags for cartridge access + + var/remote_door_id = "" + + var/bot_access_flags = 0 //Bit flags. Selection: SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + var/spam_enabled = 0 //Enables "Send to All" Option + + var/obj/item/pda/host_pda = null + var/menu + var/datum/data/record/active1 = null //General + var/datum/data/record/active2 = null //Medical + var/datum/data/record/active3 = null //Security + var/obj/machinery/computer/monitor/powmonitor = null // Power Monitor + var/list/powermonitors = list() + var/message1 // used for status_displays + var/message2 + var/list/stored_data = list() + var/current_channel + + var/mob/living/simple_animal/bot/active_bot + var/list/botlist = list() + +/obj/item/cartridge/Initialize() + . = ..() + var/obj/item/pda/pda = loc + if(istype(pda)) + host_pda = pda + +/obj/item/cartridge/engineering + name = "\improper Power-ON cartridge" + icon_state = "cart-e" + access = CART_ENGINE | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT + +/obj/item/cartridge/atmos + name = "\improper BreatheDeep cartridge" + icon_state = "cart-a" + access = CART_ATMOS | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT | FIRE_BOT + +/obj/item/cartridge/medical + name = "\improper Med-U cartridge" + icon_state = "cart-m" + access = CART_MEDICAL + bot_access_flags = MED_BOT + +/obj/item/cartridge/chemistry + name = "\improper ChemWhiz cartridge" + icon_state = "cart-chem" + access = CART_REAGENT_SCANNER + bot_access_flags = MED_BOT + +/obj/item/cartridge/security + name = "\improper R.O.B.U.S.T. cartridge" + icon_state = "cart-s" + access = CART_SECURITY | CART_MANIFEST + bot_access_flags = SEC_BOT + +/obj/item/cartridge/detective + name = "\improper D.E.T.E.C.T. cartridge" + icon_state = "cart-s" + access = CART_SECURITY | CART_MEDICAL | CART_MANIFEST + bot_access_flags = SEC_BOT + +/obj/item/cartridge/janitor + name = "\improper CustodiPRO cartridge" + desc = "The ultimate in clean-room design." + icon_state = "cart-j" + access = CART_JANITOR | CART_DRONEPHONE + bot_access_flags = CLEAN_BOT + +/obj/item/cartridge/lawyer + name = "\improper P.R.O.V.E. cartridge" + icon_state = "cart-s" + access = CART_SECURITY + spam_enabled = 1 + +/obj/item/cartridge/curator + name = "\improper Lib-Tweet cartridge" + icon_state = "cart-s" + access = CART_NEWSCASTER + +/obj/item/cartridge/roboticist + name = "\improper B.O.O.P. Remote Control cartridge" + desc = "Packed with heavy duty quad-bot interlink!" + bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + access = CART_DRONEPHONE + +/obj/item/cartridge/signal + name = "generic signaler cartridge" + desc = "A data cartridge with an integrated radio signaler module." + +/obj/item/cartridge/signal/toxins + name = "\improper Signal Ace 2 cartridge" + desc = "Complete with integrated radio signaler!" + icon_state = "cart-tox" + access = CART_REAGENT_SCANNER | CART_ATMOS + +/obj/item/cartridge/signal/Initialize() + . = ..() + radio = new(src) + + + +/obj/item/cartridge/quartermaster + name = "space parts & space vendors cartridge" + desc = "Perfect for the Quartermaster on the go!" + icon_state = "cart-q" + access = CART_QUARTERMASTER + bot_access_flags = MULE_BOT + +/obj/item/cartridge/head + name = "\improper Easy-Record DELUXE cartridge" + icon_state = "cart-h" + access = CART_MANIFEST | CART_STATUS_DISPLAY + +/obj/item/cartridge/hop + name = "\improper HumanResources9001 cartridge" + icon_state = "cart-h" + access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_JANITOR | CART_SECURITY | CART_NEWSCASTER | CART_QUARTERMASTER | CART_DRONEPHONE + bot_access_flags = MULE_BOT | CLEAN_BOT + +/obj/item/cartridge/hos + name = "\improper R.O.B.U.S.T. DELUXE cartridge" + icon_state = "cart-hos" + access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_SECURITY + bot_access_flags = SEC_BOT + + +/obj/item/cartridge/ce + name = "\improper Power-On DELUXE cartridge" + icon_state = "cart-ce" + access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_ENGINE | CART_ATMOS | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT | FIRE_BOT + +/obj/item/cartridge/cmo + name = "\improper Med-U DELUXE cartridge" + icon_state = "cart-cmo" + access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_MEDICAL + bot_access_flags = MED_BOT + +/obj/item/cartridge/rd + name = "\improper Signal Ace DELUXE cartridge" + icon_state = "cart-rd" + access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_ATMOS | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + +/obj/item/cartridge/rd/Initialize() + . = ..() + radio = new(src) + +/obj/item/cartridge/captain + name = "\improper Value-PAK cartridge" + desc = "Now with 350% more value!" //Give the Captain...EVERYTHING! (Except Mime, Clown, and Syndie) + icon_state = "cart-c" + access = ~(CART_CLOWN | CART_MIME | CART_REMOTE_DOOR) + bot_access_flags = SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + spam_enabled = 1 + +/obj/item/cartridge/captain/Initialize() + . = ..() + radio = new(src) + +/obj/item/cartridge/proc/post_status(command, data1, data2) + + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = command)) + switch(command) + if("message") + status_signal.data["msg1"] = data1 + status_signal.data["msg2"] = data2 + if("alert") + status_signal.data["picture_state"] = data1 + + frequency.post_signal(src, status_signal) + +/obj/item/cartridge/proc/generate_menu(mob/user) + if(!host_pda) + return + switch(host_pda.mode) + if(40) //signaller + menu = "

                [PDAIMG(signaler)] Remote Signaling System

                " + + menu += {" +
                Send Signal
                +Frequency: +- +- +[format_frequency(radio.frequency)] ++ ++
                +
                +Code: +- +- +[radio.code] ++ ++
                "} + if (41) //crew manifest + menu = "

                [PDAIMG(notes)] Crew Manifest

                " + menu += "
                [GLOB.data_core.get_manifest_html(monochrome=TRUE)]
                " + + + if (42) //status displays + menu = "

                [PDAIMG(status)] Station Status Display Interlink

                " + + menu += "\[ Clear \]
                " + menu += "\[ Shuttle ETA \]
                " + menu += "\[ Message \]" + menu += "
                " + menu += "\[ Alert: None |" + menu += " Red Alert |" + menu += " Lockdown |" + menu += " Biohazard \]
                " + + if (43) + menu = "

                [PDAIMG(power)] Power Monitors - Please select one


                " + powmonitor = null + powermonitors = list() + var/powercount = 0 + + + + var/turf/pda_turf = get_turf(src) + for(var/obj/machinery/computer/monitor/pMon in GLOB.machines) + if(pMon.machine_stat & (NOPOWER | BROKEN)) //check to make sure the computer is functional + continue + if(pda_turf.z != pMon.z) //and that we're on the same zlevel as the computer (lore: limited signal strength) + continue + if(pMon.is_secret_monitor) //make sure it isn't a secret one (ie located on a ruin), allowing people to metagame that the location exists + continue + powercount++ + powermonitors += pMon + + + if(!powercount) + menu += "No connection
                " + else + + menu += "" + var/count = 0 + for(var/obj/machinery/computer/monitor/pMon in powermonitors) + count++ + menu += "[pMon] - [get_area_name(pMon, TRUE)]
                " + + menu += "
                " + + if (433) + menu = "

                [PDAIMG(power)] Power Monitor


                " + if(!powmonitor || !powmonitor.get_powernet()) + menu += "No connection
                " + else + var/list/L = list() + var/datum/powernet/connected_powernet = powmonitor.get_powernet() + for(var/obj/machinery/power/terminal/term in connected_powernet.nodes) + if(istype(term.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = term.master + L += A + + menu += "
                Location: [get_area_name(powmonitor, TRUE)]
                Total power: [DisplayPower(connected_powernet.viewavail)]
                Total load: [DisplayPower(connected_powernet.viewload)]
                " + + menu += "" + + if(L.len > 0) + menu += "Area Eqp./Lgt./Env. Load Cell
                " + + var/list/S = list(" Off","AOff"," On", " AOn") + var/list/chg = list("N","C","F") +//Neither copytext nor copytext_char is appropriate here; neither 30 UTF-8 code units nor 30 code points equates to 30 columns of output. +//Some glyphs are very tall or very wide while others are small or even take up no space at all. +//Emojis can take modifiers which are many characters but render as only one glyph. +//A proper solution here (as far as Unicode goes, maybe not ideal as far as markup goes, a table would be better) +//would be to use [A.area.name] + for(var/obj/machinery/power/apc/A in L) + menu += copytext_char(add_trailing(A.area.name, 30, " "), 1, 30) + menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_leading(DisplayPower(A.lastused_total), 6, " ")] [A.cell ? "[add_leading(round(A.cell.percent()), 3, " ")]% [chg[A.charging+1]]" : " N/C"]
                " + + menu += "
                " + + if (44) //medical records //This thing only displays a single screen so it's hard to really get the sub-menu stuff working. + menu = "

                [PDAIMG(medical)] Medical Record List

                " + if(GLOB.data_core.general) + for(var/datum/data/record/R in sortRecord(GLOB.data_core.general)) + menu += "[R.fields["id"]]: [R.fields["name"]]
                " + menu += "
                " + if(441) + menu = "

                [PDAIMG(medical)] Medical Record

                " + + if(active1 in GLOB.data_core.general) + menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                " + menu += "Gender: [active1.fields["gender"]]
                " + menu += "Age: [active1.fields["age"]]
                " + menu += "Rank: [active1.fields["rank"]]
                " + menu += "Fingerprint: [active1.fields["fingerprint"]]
                " + menu += "Physical Status: [active1.fields["p_stat"]]
                " + menu += "Mental Status: [active1.fields["m_stat"]]
                " + else + menu += "Record Lost!
                " + + menu += "
                " + + menu += "

                [PDAIMG(medical)] Medical Data

                " + if(active2 in GLOB.data_core.medical) + menu += "Blood Type: [active2.fields["blood_type"]]

                " + + menu += "Minor Disabilities: [active2.fields["mi_dis"]]
                " + menu += "Details: [active2.fields["mi_dis_d"]]

                " + + menu += "Major Disabilities: [active2.fields["ma_dis"]]
                " + menu += "Details: [active2.fields["ma_dis_d"]]

                " + + menu += "Allergies: [active2.fields["alg"]]
                " + menu += "Details: [active2.fields["alg_d"]]

                " + + menu += "Current Diseases: [active2.fields["cdi"]]
                " + menu += "Details: [active2.fields["cdi_d"]]

                " + + menu += "Important Notes: [active2.fields["notes"]]
                " + else + menu += "Record Lost!
                " + + menu += "
                " + if (45) //security records + menu = "

                [PDAIMG(cuffs)] Security Record List

                " + if(GLOB.data_core.general) + for (var/datum/data/record/R in sortRecord(GLOB.data_core.general)) + menu += "
                [R.fields["id"]]: [R.fields["name"]]
                " + + menu += "
                " + if(451) + menu = "

                [PDAIMG(cuffs)] Security Record

                " + + if(active1 in GLOB.data_core.general) + menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                " + menu += "Gender: [active1.fields["gender"]]
                " + menu += "Age: [active1.fields["age"]]
                " + menu += "Rank: [active1.fields["rank"]]
                " + menu += "Fingerprint: [active1.fields["fingerprint"]]
                " + menu += "Physical Status: [active1.fields["p_stat"]]
                " + menu += "Mental Status: [active1.fields["m_stat"]]
                " + else + menu += "Record Lost!
                " + + menu += "
                " + + menu += "

                [PDAIMG(cuffs)] Security Data

                " + if(active3 in GLOB.data_core.security) + menu += "Criminal Status: [active3.fields["criminal"]]
                " + + menu += text("
                \nCrimes:") + + menu +={" + + + + + +"} + for(var/datum/data/crime/c in active3.fields["crim"]) + menu += "" + menu += "" + menu += "" + menu += "" + menu += "" + menu += "
                CrimeDetailsAuthorTime Added
                [c.crimeName][c.crimeDetails][c.author][c.time]
                " + menu += "
                \nImportant Notes:
                " + menu += "[active3.fields["notes"]]" + else + menu += "Record Lost!
                " + + menu += "
                " + + if (47) //quartermaster order records + menu = "

                [PDAIMG(crate)] Supply Record Interlink

                " + + menu += "
                Supply shuttle
                " + menu += "Location: " + switch(SSshuttle.supply.mode) + if(SHUTTLE_CALL) + menu += "Moving to " + if(!is_station_level(SSshuttle.supply.z)) + menu += "station" + else + menu += "CentCom" + menu += " ([SSshuttle.supply.timeLeft(600)] Mins)" + else + menu += "At " + if(!is_station_level(SSshuttle.supply.z)) + menu += "CentCom" + else + menu += "station" + menu += "
                Current approved orders:
                  " + for(var/S in SSshuttle.shoppinglist) + var/datum/supply_order/SO = S + menu += "
                1. #[SO.id] - [SO.pack.name] approved by [SO.orderer] [SO.reason ? "([SO.reason])":""]
                2. " + menu += "
                " + + menu += "Current requests:
                  " + for(var/S in SSshuttle.requestlist) + var/datum/supply_order/SO = S + menu += "
                1. #[SO.id] - [SO.pack.name] requested by [SO.orderer]
                2. " + menu += "
                Upgrade NOW to Space Parts & Space Vendors PLUS for full remote order control and inventory management." + + if (48) // quartermaster ore logs + menu = list("

                [PDAIMG(crate)] Ore Silo Logs

                ") + if (GLOB.ore_silo_default) + var/list/logs = GLOB.silo_access_logs[REF(GLOB.ore_silo_default)] + var/len = LAZYLEN(logs) + var/i = 0 + for(var/M in logs) + if (++i > 30) + menu += "(... older logs not shown ...)" + break + var/datum/ore_silo_log/entry = M + menu += "[len - i]. [entry.formatted]

                " + if(i == 0) + menu += "Nothing!" + else + menu += "No ore silo detected!" + menu = jointext(menu, "") + + if (49) //janitorial locator + menu = "

                [PDAIMG(bucket)] Persistent Custodial Object Locator

                " + + var/turf/cl = get_turf(src) + if (cl) + menu += "Current Orbital Location: \[[cl.x],[cl.y]\]" + + menu += "

                Located Mops:

                " + + var/ldat + for (var/obj/item/mop/M in world) + var/turf/ml = get_turf(M) + + if(ml) + if (ml.z != cl.z) + continue + var/direction = get_dir(src, M) + ldat += "Mop - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\] - [M.reagents.total_volume ? "Wet" : "Dry"]
                " + + if (!ldat) + menu += "None" + else + menu += "[ldat]" + + menu += "

                Located Janitorial Cart:

                " + + ldat = null + for (var/obj/structure/janitorialcart/B in world) + var/turf/bl = get_turf(B) + + if(bl) + if (bl.z != cl.z) + continue + var/direction = get_dir(src, B) + ldat += "Cart - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - Water level: [B.reagents.total_volume]/100
                " + + if (!ldat) + menu += "None" + else + menu += "[ldat]" + + menu += "

                Located Cleanbots:

                " + + ldat = null + for (var/mob/living/simple_animal/bot/cleanbot/B in GLOB.alive_mob_list) + var/turf/bl = get_turf(B) + + if(bl) + if (bl.z != cl.z) + continue + var/direction = get_dir(src, B) + ldat += "Cleanbot - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - [B.on ? "Online" : "Offline"]
                " + + if (!ldat) + menu += "None" + else + menu += "[ldat]" + + else + menu += "ERROR: Unable to determine current location." + menu += "

                Refresh GPS Locator" + + if (53) // Newscaster + menu = "

                [PDAIMG(notes)] Newscaster Access

                " + menu += "
                Current Newsfeed: [current_channel ? current_channel : "None"]
                " + var/datum/newscaster/feed_channel/current + for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) + if (chan.channel_name == current_channel) + current = chan + if(!current) + menu += "
                ERROR : NO CHANNEL FOUND
                " + return menu + var/i = 1 + for(var/datum/newscaster/feed_message/msg in current.messages) + menu +="-[msg.returnBody(-1)]
                \[Story by [msg.returnAuthor(-1)]\]
                " + menu +="[msg.comments.len] comment[msg.comments.len > 1 ? "s" : ""]
                " + if(msg.img) + user << browse_rsc(msg.img, "tmp_photo[i].png") + menu +="
                " + i++ + for(var/datum/newscaster/feed_comment/comment in msg.comments) + menu +="[comment.body]
                [comment.author] [comment.time_stamp]
                " + menu += "
                Post Message" + + if (54) // Beepsky, Medibot, Floorbot, and Cleanbot access + menu = "

                [PDAIMG(medbot)] Bots Interlink

                " + bot_control() + if (55) // Emoji Guidebook for mimes + menu = "

                [PDAIMG(emoji)] Emoji Guidebook

                " + var/static/list/emoji_icon_states + var/static/emoji_table + if(!emoji_table) + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat) + var/list/collate = list("
                ") + for(var/emoji in sortList(icon_states(icon('icons/emoji.dmi')))) + var/tag = sheet.icon_tag("emoji-[emoji]") + collate += "" + collate += "
                [emoji][tag]

                " + emoji_table = collate.Join() + + menu += "
                To use an emoji in a pda message, refer to the guide and add \":\" around the emoji. Your PDA supports the following emoji:
                " + menu += emoji_table + + if (99) //Newscaster message permission error + menu = "
                ERROR : NOT AUTHORIZED [host_pda.id ? "" : "- ID SLOT EMPTY"]
                " + + return menu + +/obj/item/cartridge/Topic(href, href_list) + ..() + + if(!usr.canUseTopic(src, !issilicon(usr))) + usr.unset_machine() + usr << browse(null, "window=pda") + return + + switch(href_list["choice"]) + if("Medical Records") + active1 = find_record("id", href_list["target"], GLOB.data_core.general) + if(active1) + active2 = find_record("id", href_list["target"], GLOB.data_core.medical) + host_pda.mode = 441 + if(!active2) + active1 = null + + if("Security Records") + active1 = find_record("id", href_list["target"], GLOB.data_core.general) + if(active1) + active3 = find_record("id", href_list["target"], GLOB.data_core.security) + host_pda.mode = 451 + if(!active3) + active1 = null + + if("Send Signal") + INVOKE_ASYNC(radio, /obj/item/integrated_signaler.proc/send_activation) + + if("Signal Frequency") + var/new_frequency = sanitize_frequency(radio.frequency + text2num(href_list["sfreq"])) + radio.set_frequency(new_frequency) + + if("Signal Code") + radio.code += text2num(href_list["scode"]) + radio.code = round(radio.code) + radio.code = min(100, radio.code) + radio.code = max(1, radio.code) + + if("Status") + switch(href_list["statdisp"]) + if("message") + post_status("message", message1, message2) + if("alert") + post_status("alert", href_list["alert"]) + if("setmsg1") + message1 = reject_bad_text(input("Line 1", "Enter Message Text", message1) as text|null, 40) + updateSelfDialog() + if("setmsg2") + message2 = reject_bad_text(input("Line 2", "Enter Message Text", message2) as text|null, 40) + updateSelfDialog() + else + post_status(href_list["statdisp"]) + if("Power Select") + var/pnum = text2num(href_list["target"]) + powmonitor = powermonitors[pnum] + host_pda.mode = 433 + + if("Supply Orders") + host_pda.mode =47 + + if("Newscaster Access") + host_pda.mode = 53 + + if("Newscaster Message") + var/host_pda_owner_name = host_pda.id ? "[host_pda.id.registered_name] ([host_pda.id.assignment])" : "Unknown" + var/message = host_pda.msg_input() + var/datum/newscaster/feed_channel/current + for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) + if (chan.channel_name == current_channel) + current = chan + if(current.locked && current.author != host_pda_owner_name) + host_pda.mode = 99 + host_pda.Topic(null,list("choice"="Refresh")) + return + GLOB.news_network.SubmitArticle(message,host_pda.owner,current_channel) + host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) + return + + if("Newscaster Switch Channel") + current_channel = host_pda.msg_input() + host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) + return + + //emoji previews + if(href_list["emoji"]) + var/parse = emoji_parse(":[href_list["emoji"]]:") + to_chat(usr, parse) + + //Bot control section! Viciously ripped from radios for being laggy and terrible. + if(href_list["op"]) + switch(href_list["op"]) + + if("control") + active_bot = locate(href_list["bot"]) in GLOB.bots_list + + if("botlist") + active_bot = null + if("summon") //Args are in the correct order, they are stated here just as an easy reminder. + active_bot.bot_control("summon", usr, host_pda.GetAccess()) + else //Forward all other bot commands to the bot itself! + active_bot.bot_control(href_list["op"], usr) + + if(href_list["mule"]) //MULEbots are special snowflakes, and need different args due to how they work. + var/mob/living/simple_animal/bot/mulebot/mule = active_bot + if (istype(mule)) + mule.bot_control(href_list["mule"], usr, pda=TRUE) + + if(!host_pda) + return + host_pda.attack_self(usr) + + +/obj/item/cartridge/proc/bot_control() + if(active_bot) + menu += "[active_bot]
                Status: ([PDAIMG(refresh)]refresh)
                " + menu += "Model: [active_bot.model]
                " + menu += "Location: [get_area(active_bot)]
                " + menu += "Mode: [active_bot.get_mode()]" + if(active_bot.allow_pai) + menu += "
                pAI: " + if(active_bot.paicard && active_bot.paicard.pai) + menu += "[active_bot.paicard.pai.name]" + if(active_bot.bot_core.allowed(usr)) + menu += " (eject)" + else + menu += "none" + + //MULEs! + if(active_bot.bot_type == MULE_BOT) + var/mob/living/simple_animal/bot/mulebot/MULE = active_bot + var/atom/Load = MULE.load + menu += "
                Current Load: [ !Load ? "none" : "[Load.name] (unload)" ]
                " + menu += "Destination: [MULE.destination ? MULE.destination : "None"] (set)
                " + menu += "Set ID: [MULE.suffix] Modify
                " + menu += "Power: [MULE.cell ? MULE.cell.percent() : 0]%
                " + menu += "Home: [!MULE.home_destination ? "none" : MULE.home_destination ]
                " + menu += "Delivery Reporting: [MULE.report_delivery ? "(On)": "(Off)"]
                " + menu += "Auto Return Home: [MULE.auto_return ? "(On)": "(Off)"]
                " + menu += "Auto Pickup Crate: [MULE.auto_pickup ? "(On)": "(Off)"]

                " //Hue. + + menu += "\[Stop\] " + menu += "\[Proceed\] " + menu += "\[Return Home\]
                " + + else + menu += "
                \[Stop Patrol\] " //patrolon + menu += "\[Start Patrol\] " //patroloff + menu += "\[Summon Bot\]
                " //summon + menu += "Keep an ID inserted to upload access codes upon summoning." + + menu += "
                [PDAIMG(back)]Return to bot list" + else + menu += "
                [PDAIMG(refresh)]Scan for active bots

                " + var/turf/current_turf = get_turf(src) + var/zlevel = current_turf.z + var/botcount = 0 + for(var/B in GLOB.bots_list) //Git da botz + var/mob/living/simple_animal/bot/Bot = B + if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled || !(bot_access_flags & Bot.bot_type)) //Only non-emagged bots on the same Z-level are detected! + continue //Also, the PDA must have access to the bot type. + menu += "[Bot.name] ([Bot.get_mode()])
                " + botcount++ + if(!botcount) //No bots at all? Lame. + menu += "No bots found.
                " + return + + return menu + +//If the cartridge adds a special line to the top of the messaging app +/obj/item/cartridge/proc/message_header() + return "" + +//If the cartridge adds something to each potetial messaging target +/obj/item/cartridge/proc/message_special(obj/item/pda/target) + return "" + +//This is called for special abilities of cartridges +/obj/item/cartridge/proc/special(mob/living/user, list/params) diff --git a/code/game/objects/items/devices/PDA/radio.dm b/code/game/objects/items/devices/PDA/radio.dm index 9e20dc455b0..329631ff9e0 100644 --- a/code/game/objects/items/devices/PDA/radio.dm +++ b/code/game/objects/items/devices/PDA/radio.dm @@ -1,38 +1,38 @@ -// Radio Cartridge, essentially a remote signaler with limited spectrum. -/obj/item/integrated_signaler - name = "\improper PDA radio module" - desc = "An electronic radio system of Nanotrasen origin." - icon = 'icons/obj/module.dmi' - icon_state = "power_mod" - -/obj/item/integrated_signaler - var/frequency = FREQ_SIGNALER - var/code = DEFAULT_SIGNALER_CODE - var/last_transmission - var/datum/radio_frequency/radio_connection - -/obj/item/integrated_signaler/Destroy() - radio_connection = null - return ..() - -/obj/item/integrated_signaler/Initialize() - . = ..() - if (frequency < MIN_FREE_FREQ || frequency > MAX_FREE_FREQ) - frequency = sanitize_frequency(frequency) - set_frequency(frequency) - -/obj/item/integrated_signaler/proc/set_frequency(new_frequency) - frequency = new_frequency - radio_connection = SSradio.return_frequency(frequency) - -/obj/item/integrated_signaler/proc/send_activation() - if(last_transmission && world.time < (last_transmission + 5)) - return - last_transmission = world.time - - var/time = time2text(world.realtime,"hh:mm:ss") - var/turf/T = get_turf(src) - GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location [AREACOORD(T)] : [format_frequency(frequency)]/[code]") - - var/datum/signal/signal = new(list("code" = code)) - radio_connection.post_signal(src, signal, filter = RADIO_SIGNALER) +// Radio Cartridge, essentially a remote signaler with limited spectrum. +/obj/item/integrated_signaler + name = "\improper PDA radio module" + desc = "An electronic radio system of Nanotrasen origin." + icon = 'icons/obj/module.dmi' + icon_state = "power_mod" + +/obj/item/integrated_signaler + var/frequency = FREQ_SIGNALER + var/code = DEFAULT_SIGNALER_CODE + var/last_transmission + var/datum/radio_frequency/radio_connection + +/obj/item/integrated_signaler/Destroy() + radio_connection = null + return ..() + +/obj/item/integrated_signaler/Initialize() + . = ..() + if (frequency < MIN_FREE_FREQ || frequency > MAX_FREE_FREQ) + frequency = sanitize_frequency(frequency) + set_frequency(frequency) + +/obj/item/integrated_signaler/proc/set_frequency(new_frequency) + frequency = new_frequency + radio_connection = SSradio.return_frequency(frequency) + +/obj/item/integrated_signaler/proc/send_activation() + if(last_transmission && world.time < (last_transmission + 5)) + return + last_transmission = world.time + + var/time = time2text(world.realtime,"hh:mm:ss") + var/turf/T = get_turf(src) + GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location [AREACOORD(T)] : [format_frequency(frequency)]/[code]") + + var/datum/signal/signal = new(list("code" = code)) + radio_connection.post_signal(src, signal, filter = RADIO_SIGNALER) diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index dced517bb48..a01112e3deb 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -1,105 +1,105 @@ -/obj/item/aicard - name = "intelliCard" - desc = "A storage device for AIs. Patent pending." - icon = 'icons/obj/aicards.dmi' - icon_state = "aicard" // aicard-full - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - item_flags = NOBLUDGEON - var/flush = FALSE - var/mob/living/silicon/ai/AI - -/obj/item/aicard/aitater - name = "intelliTater" - desc = "A stylish upgrade (?) to the intelliCard." - icon_state = "aitater" - -/obj/item/aicard/aispook - name = "intelliLantern" - desc = "A spoOoOoky upgrade to the intelliCard." - icon_state = "aispook" - -/obj/item/aicard/suicide_act(mob/living/user) - user.visible_message("[user] is trying to upload [user.p_them()]self into [src]! That's not going to work out well!") - return BRUTELOSS - -/obj/item/aicard/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity || !target) - return - if(AI) //AI is on the card, implies user wants to upload it. - log_combat(user, AI, "uploaded", src, "to [target].") - target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) - else //No AI on the card, therefore the user wants to download one. - target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) - if(AI) - log_combat(user, AI, "carded", src) - update_icon() //Whatever happened, update the card's state (icon, name) to match. - -/obj/item/aicard/update_icon() - cut_overlays() - if(AI) - name = "[initial(name)] - [AI.name]" - if(AI.stat == DEAD) - icon_state = "[initial(icon_state)]-404" - else - icon_state = "[initial(icon_state)]-full" - if(!AI.control_disabled) - add_overlay("[initial(icon_state)]-on") - AI.cancel_camera() - else - name = initial(name) - icon_state = initial(icon_state) - -/obj/item/aicard/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Intellicard", name, 500, 500, master_ui, state) - ui.open() - -/obj/item/aicard/ui_data() - var/list/data = list() - if(AI) - data["name"] = AI.name - data["laws"] = AI.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE) - data["health"] = (AI.health + 100) / 2 - data["wireless"] = !AI.control_disabled //todo disabled->enabled - data["radio"] = AI.radio_enabled - data["isDead"] = AI.stat == DEAD - data["isBraindead"] = AI.client ? FALSE : TRUE - data["wiping"] = flush - return data - -/obj/item/aicard/ui_act(action,params) - if(..()) - return - switch(action) - if("wipe") - if(flush) - flush = FALSE - else - var/confirm = alert("Are you sure you want to wipe this card's memory?", name, "Yes", "No") - if(confirm == "Yes" && !..()) - flush = TRUE - if(AI && AI.loc == src) - to_chat(AI, "Your core files are being wiped!") - while(AI.stat != DEAD && flush) - AI.adjustOxyLoss(5) - AI.updatehealth() - sleep(5) - flush = FALSE - . = TRUE - if("wireless") - AI.control_disabled = !AI.control_disabled - to_chat(AI, "[src]'s wireless port has been [AI.control_disabled ? "disabled" : "enabled"]!") - . = TRUE - if("radio") - AI.radio_enabled = !AI.radio_enabled - to_chat(AI, "Your Subspace Transceiver has been [AI.radio_enabled ? "enabled" : "disabled"]!") - . = TRUE - update_icon() +/obj/item/aicard + name = "intelliCard" + desc = "A storage device for AIs. Patent pending." + icon = 'icons/obj/aicards.dmi' + icon_state = "aicard" // aicard-full + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + item_flags = NOBLUDGEON + var/flush = FALSE + var/mob/living/silicon/ai/AI + +/obj/item/aicard/aitater + name = "intelliTater" + desc = "A stylish upgrade (?) to the intelliCard." + icon_state = "aitater" + +/obj/item/aicard/aispook + name = "intelliLantern" + desc = "A spoOoOoky upgrade to the intelliCard." + icon_state = "aispook" + +/obj/item/aicard/suicide_act(mob/living/user) + user.visible_message("[user] is trying to upload [user.p_them()]self into [src]! That's not going to work out well!") + return BRUTELOSS + +/obj/item/aicard/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity || !target) + return + if(AI) //AI is on the card, implies user wants to upload it. + log_combat(user, AI, "uploaded", src, "to [target].") + target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) + else //No AI on the card, therefore the user wants to download one. + target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) + if(AI) + log_combat(user, AI, "carded", src) + update_icon() //Whatever happened, update the card's state (icon, name) to match. + +/obj/item/aicard/update_icon() + cut_overlays() + if(AI) + name = "[initial(name)] - [AI.name]" + if(AI.stat == DEAD) + icon_state = "[initial(icon_state)]-404" + else + icon_state = "[initial(icon_state)]-full" + if(!AI.control_disabled) + add_overlay("[initial(icon_state)]-on") + AI.cancel_camera() + else + name = initial(name) + icon_state = initial(icon_state) + +/obj/item/aicard/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Intellicard", name, 500, 500, master_ui, state) + ui.open() + +/obj/item/aicard/ui_data() + var/list/data = list() + if(AI) + data["name"] = AI.name + data["laws"] = AI.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE) + data["health"] = (AI.health + 100) / 2 + data["wireless"] = !AI.control_disabled //todo disabled->enabled + data["radio"] = AI.radio_enabled + data["isDead"] = AI.stat == DEAD + data["isBraindead"] = AI.client ? FALSE : TRUE + data["wiping"] = flush + return data + +/obj/item/aicard/ui_act(action,params) + if(..()) + return + switch(action) + if("wipe") + if(flush) + flush = FALSE + else + var/confirm = alert("Are you sure you want to wipe this card's memory?", name, "Yes", "No") + if(confirm == "Yes" && !..()) + flush = TRUE + if(AI && AI.loc == src) + to_chat(AI, "Your core files are being wiped!") + while(AI.stat != DEAD && flush) + AI.adjustOxyLoss(5) + AI.updatehealth() + sleep(5) + flush = FALSE + . = TRUE + if("wireless") + AI.control_disabled = !AI.control_disabled + to_chat(AI, "[src]'s wireless port has been [AI.control_disabled ? "disabled" : "enabled"]!") + . = TRUE + if("radio") + AI.radio_enabled = !AI.radio_enabled + to_chat(AI, "Your Subspace Transceiver has been [AI.radio_enabled ? "enabled" : "disabled"]!") + . = TRUE + update_icon() diff --git a/code/game/objects/items/devices/camera_bug.dm b/code/game/objects/items/devices/camera_bug.dm index 31ffbe53b0e..d578856fa1e 100644 --- a/code/game/objects/items/devices/camera_bug.dm +++ b/code/game/objects/items/devices/camera_bug.dm @@ -1,311 +1,311 @@ - -#define BUGMODE_LIST 0 -#define BUGMODE_MONITOR 1 -#define BUGMODE_TRACK 2 - - - -/obj/item/camera_bug - name = "camera bug" - desc = "For illicit snooping through the camera network." - icon = 'icons/obj/device.dmi' - icon_state = "camera_bug" - w_class = WEIGHT_CLASS_TINY - inhand_icon_state = "camera_bug" - throw_speed = 4 - throw_range = 20 - item_flags = NOBLUDGEON - - var/obj/machinery/camera/current = null - - var/last_net_update = 0 - var/list/bugged_cameras = list() - - var/track_mode = BUGMODE_LIST - var/last_tracked = 0 - var/refresh_interval = 50 - - var/tracked_name = null - var/atom/tracking = null - - var/last_found = null - var/last_seen = null - -/obj/item/camera_bug/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/camera_bug/Destroy() - get_cameras() - for(var/cam_tag in bugged_cameras) - var/obj/machinery/camera/camera = bugged_cameras[cam_tag] - if(camera && camera.bug == src) - camera.bug = null - bugged_cameras = list() - if(tracking) - tracking = null - return ..() - -/obj/item/camera_bug/interact(mob/user) - ui_interact(user) - -/obj/item/camera_bug/ui_interact(mob/user = usr) - . = ..() - var/datum/browser/popup = new(user, "camerabug","Camera Bug",nref=src) - popup.set_content(menu(get_cameras())) - popup.open() - -/obj/item/camera_bug/attack_self(mob/user) - user.set_machine(src) - interact(user) - -/obj/item/camera_bug/check_eye(mob/user) - if ( loc != user || user.incapacitated() || user.is_blind() || !current ) - user.unset_machine() - return 0 - var/turf/T_user = get_turf(user.loc) - var/turf/T_current = get_turf(current) - if(T_user.z != T_current.z || !current.can_use()) - to_chat(user, "[src] has lost the signal.") - current = null - user.unset_machine() - return 0 - return 1 -/obj/item/camera_bug/on_unset_machine(mob/user) - user.reset_perspective(null) - -/obj/item/camera_bug/proc/get_cameras() - if( world.time > (last_net_update + 100)) - bugged_cameras = list() - for(var/obj/machinery/camera/camera in GLOB.cameranet.cameras) - if(camera.machine_stat || !camera.can_use()) - continue - if(length(list("ss13","mine", "rd", "labor", "toxins", "minisat")&camera.network)) - bugged_cameras[camera.c_tag] = camera - return sortList(bugged_cameras) - - -/obj/item/camera_bug/proc/menu(list/cameras) - if(!cameras || !cameras.len) - return "No bugged cameras found." - - var/html - switch(track_mode) - if(BUGMODE_LIST) - html = "

                Select a camera:

                \[Cancel camera view\]
                " - for(var/entry in cameras) - var/obj/machinery/camera/C = cameras[entry] - if(QDELETED(C)) - continue - var/functions = "" - if(C.bug == src) - functions = " - \[Monitor\]\[Disable\]" - else - functions = " - \[Monitor\]" - html += "" - - if(BUGMODE_MONITOR) - if(current) - html = "Analyzing Camera '[current.c_tag]' \[Select Camera\]
                " - html += camera_report() - else - track_mode = BUGMODE_LIST - return .(cameras) - if(BUGMODE_TRACK) - if(tracking) - html = "Tracking '[tracked_name]' \[Cancel Tracking\]\[Cancel camera view\]
                " - if(last_found) - var/time_diff = round((world.time - last_seen) / 150) - var/obj/machinery/camera/C = bugged_cameras[last_found] - var/outstring - if(C) - outstring = "[last_found]" - else - outstring = last_found - if(!time_diff) - html += "Last seen near [outstring] (now)
                " - else - // 15 second intervals ~ 1/4 minute - var/m = round(time_diff/4) - var/s = (time_diff - 4*m) * 15 - if(!s) - s = "00" - html += "Last seen near [outstring] ([m]:[s] minute\s ago)
                " - if( C && (C.bug == src)) //Checks to see if the camera has a bug - html += "\[Disable\]" - - else - html += "Not yet seen." - else - track_mode = BUGMODE_LIST - return .(cameras) - return html - -/obj/item/camera_bug/proc/get_seens() - if(current && current.can_use()) - var/list/seen = current.can_see() - return seen - -/obj/item/camera_bug/proc/camera_report() - // this should only be called if current exists - var/dat = "" - var/list/seen = get_seens() - if(seen && seen.len >= 1) - var/list/names = list() - for(var/obj/singularity/S in seen) // god help you if you see more than one - if(S.name in names) - names[S.name]++ - dat += "[S.name] ([names[S.name]])" - else - names[S.name] = 1 - dat += "[S.name]" - var/stage = round(S.current_size / 2)+1 - dat += " (Stage [stage])" - dat += " \[Track\]
                " - - for(var/obj/mecha/M in seen) - if(M.name in names) - names[M.name]++ - dat += "[M.name] ([names[M.name]])" - else - names[M.name] = 1 - dat += "[M.name]" - dat += " \[Track\]
                " - - - for(var/mob/living/M in seen) - if(M.name in names) - names[M.name]++ - dat += "[M.name] ([names[M.name]])" - else - names[M.name] = 1 - dat += "[M.name]" - if(!(M.mobility_flags & MOBILITY_STAND)) - if(M.buckled) - dat += " (Sitting)" - else - dat += " (Laying down)" - dat += " \[Track\]
                " - if(length(dat) == 0) - dat += "No motion detected." - return dat - else - return "Camera Offline
                " - -/obj/item/camera_bug/Topic(href,list/href_list) - if(usr != loc) - usr.unset_machine() - usr << browse(null, "window=camerabug") - return - usr.set_machine(src) - if("mode" in href_list) - track_mode = text2num(href_list["mode"]) - if("monitor" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["monitor"]) in cameras - if(C && istype(C)) - if(!same_z_level(C)) - return - track_mode = BUGMODE_MONITOR - current = C - usr.reset_perspective(null) - interact() - if("track" in href_list) - var/list/seen = get_seens() - if(seen && seen.len >= 1) - var/atom/A = locate(href_list["track"]) in seen - if(A && istype(A)) - tracking = A - tracked_name = A.name - last_found = current.c_tag - last_seen = world.time - track_mode = BUGMODE_TRACK - if("emp" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["emp"]) in cameras - if(C && istype(C) && C.bug == src) - if(!same_z_level(C)) - return - C.emp_act(EMP_HEAVY) - C.bug = null - bugged_cameras -= C.c_tag - interact() - return - if("close" in href_list) - usr.unset_machine() - current = null - return - if("view" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["view"]) in cameras - if(C && istype(C)) - if(!same_z_level(C)) - return - if(!C.can_use()) - to_chat(usr, "Something's wrong with that camera! You can't get a feed.") - return - current = C - spawn(6) - if(src.check_eye(usr)) - usr.reset_perspective(C) - interact() - else - usr.unset_machine() - usr << browse(null, "window=camerabug") - return - else - usr.unset_machine() - - interact() - -/obj/item/camera_bug/process() - if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval))) - return - last_tracked = world.time - if(track_mode == BUGMODE_TRACK ) // search for user - // Note that it will be tricked if your name appears to change. - // This is not optimal but it is better than tracking you relentlessly despite everything. - if(!tracking) - src.updateSelfDialog() - return - - if(tracking.name != tracked_name) // Hiding their identity, tricksy - var/mob/M = tracking - if(istype(M)) - if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before - if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd - src.updateSelfDialog()//But if it's neither of those cases - return // you won't find em on the cameras - else - src.updateSelfDialog() - return - - var/list/tracking_cams = list() - var/list/b_cams = get_cameras() - for(var/entry in b_cams) - tracking_cams += b_cams[entry] - var/list/target_region = view(tracking) - - for(var/obj/machinery/camera/C in (target_region & tracking_cams)) - if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras - continue - if(C.can_use()) - last_found = C.c_tag - last_seen = world.time - break - src.updateSelfDialog() - -/obj/item/camera_bug/proc/same_z_level(var/obj/machinery/camera/C) - var/turf/T_cam = get_turf(C) - var/turf/T_bug = get_turf(loc) - if(!T_bug || T_cam.z != T_bug.z) - to_chat(usr, "You can't get a signal!") - return FALSE - return TRUE - -#undef BUGMODE_LIST -#undef BUGMODE_MONITOR -#undef BUGMODE_TRACK + +#define BUGMODE_LIST 0 +#define BUGMODE_MONITOR 1 +#define BUGMODE_TRACK 2 + + + +/obj/item/camera_bug + name = "camera bug" + desc = "For illicit snooping through the camera network." + icon = 'icons/obj/device.dmi' + icon_state = "camera_bug" + w_class = WEIGHT_CLASS_TINY + inhand_icon_state = "camera_bug" + throw_speed = 4 + throw_range = 20 + item_flags = NOBLUDGEON + + var/obj/machinery/camera/current = null + + var/last_net_update = 0 + var/list/bugged_cameras = list() + + var/track_mode = BUGMODE_LIST + var/last_tracked = 0 + var/refresh_interval = 50 + + var/tracked_name = null + var/atom/tracking = null + + var/last_found = null + var/last_seen = null + +/obj/item/camera_bug/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/camera_bug/Destroy() + get_cameras() + for(var/cam_tag in bugged_cameras) + var/obj/machinery/camera/camera = bugged_cameras[cam_tag] + if(camera && camera.bug == src) + camera.bug = null + bugged_cameras = list() + if(tracking) + tracking = null + return ..() + +/obj/item/camera_bug/interact(mob/user) + ui_interact(user) + +/obj/item/camera_bug/ui_interact(mob/user = usr) + . = ..() + var/datum/browser/popup = new(user, "camerabug","Camera Bug",nref=src) + popup.set_content(menu(get_cameras())) + popup.open() + +/obj/item/camera_bug/attack_self(mob/user) + user.set_machine(src) + interact(user) + +/obj/item/camera_bug/check_eye(mob/user) + if ( loc != user || user.incapacitated() || user.is_blind() || !current ) + user.unset_machine() + return 0 + var/turf/T_user = get_turf(user.loc) + var/turf/T_current = get_turf(current) + if(T_user.z != T_current.z || !current.can_use()) + to_chat(user, "[src] has lost the signal.") + current = null + user.unset_machine() + return 0 + return 1 +/obj/item/camera_bug/on_unset_machine(mob/user) + user.reset_perspective(null) + +/obj/item/camera_bug/proc/get_cameras() + if( world.time > (last_net_update + 100)) + bugged_cameras = list() + for(var/obj/machinery/camera/camera in GLOB.cameranet.cameras) + if(camera.machine_stat || !camera.can_use()) + continue + if(length(list("ss13","mine", "rd", "labor", "toxins", "minisat")&camera.network)) + bugged_cameras[camera.c_tag] = camera + return sortList(bugged_cameras) + + +/obj/item/camera_bug/proc/menu(list/cameras) + if(!cameras || !cameras.len) + return "No bugged cameras found." + + var/html + switch(track_mode) + if(BUGMODE_LIST) + html = "

                Select a camera:

                \[Cancel camera view\]
                [entry][functions]
                " + for(var/entry in cameras) + var/obj/machinery/camera/C = cameras[entry] + if(QDELETED(C)) + continue + var/functions = "" + if(C.bug == src) + functions = " - \[Monitor\]\[Disable\]" + else + functions = " - \[Monitor\]" + html += "" + + if(BUGMODE_MONITOR) + if(current) + html = "Analyzing Camera '[current.c_tag]' \[Select Camera\]
                " + html += camera_report() + else + track_mode = BUGMODE_LIST + return .(cameras) + if(BUGMODE_TRACK) + if(tracking) + html = "Tracking '[tracked_name]' \[Cancel Tracking\]\[Cancel camera view\]
                " + if(last_found) + var/time_diff = round((world.time - last_seen) / 150) + var/obj/machinery/camera/C = bugged_cameras[last_found] + var/outstring + if(C) + outstring = "[last_found]" + else + outstring = last_found + if(!time_diff) + html += "Last seen near [outstring] (now)
                " + else + // 15 second intervals ~ 1/4 minute + var/m = round(time_diff/4) + var/s = (time_diff - 4*m) * 15 + if(!s) + s = "00" + html += "Last seen near [outstring] ([m]:[s] minute\s ago)
                " + if( C && (C.bug == src)) //Checks to see if the camera has a bug + html += "\[Disable\]" + + else + html += "Not yet seen." + else + track_mode = BUGMODE_LIST + return .(cameras) + return html + +/obj/item/camera_bug/proc/get_seens() + if(current && current.can_use()) + var/list/seen = current.can_see() + return seen + +/obj/item/camera_bug/proc/camera_report() + // this should only be called if current exists + var/dat = "" + var/list/seen = get_seens() + if(seen && seen.len >= 1) + var/list/names = list() + for(var/obj/singularity/S in seen) // god help you if you see more than one + if(S.name in names) + names[S.name]++ + dat += "[S.name] ([names[S.name]])" + else + names[S.name] = 1 + dat += "[S.name]" + var/stage = round(S.current_size / 2)+1 + dat += " (Stage [stage])" + dat += " \[Track\]
                " + + for(var/obj/mecha/M in seen) + if(M.name in names) + names[M.name]++ + dat += "[M.name] ([names[M.name]])" + else + names[M.name] = 1 + dat += "[M.name]" + dat += " \[Track\]
                " + + + for(var/mob/living/M in seen) + if(M.name in names) + names[M.name]++ + dat += "[M.name] ([names[M.name]])" + else + names[M.name] = 1 + dat += "[M.name]" + if(!(M.mobility_flags & MOBILITY_STAND)) + if(M.buckled) + dat += " (Sitting)" + else + dat += " (Laying down)" + dat += " \[Track\]
                " + if(length(dat) == 0) + dat += "No motion detected." + return dat + else + return "Camera Offline
                " + +/obj/item/camera_bug/Topic(href,list/href_list) + if(usr != loc) + usr.unset_machine() + usr << browse(null, "window=camerabug") + return + usr.set_machine(src) + if("mode" in href_list) + track_mode = text2num(href_list["mode"]) + if("monitor" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["monitor"]) in cameras + if(C && istype(C)) + if(!same_z_level(C)) + return + track_mode = BUGMODE_MONITOR + current = C + usr.reset_perspective(null) + interact() + if("track" in href_list) + var/list/seen = get_seens() + if(seen && seen.len >= 1) + var/atom/A = locate(href_list["track"]) in seen + if(A && istype(A)) + tracking = A + tracked_name = A.name + last_found = current.c_tag + last_seen = world.time + track_mode = BUGMODE_TRACK + if("emp" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["emp"]) in cameras + if(C && istype(C) && C.bug == src) + if(!same_z_level(C)) + return + C.emp_act(EMP_HEAVY) + C.bug = null + bugged_cameras -= C.c_tag + interact() + return + if("close" in href_list) + usr.unset_machine() + current = null + return + if("view" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["view"]) in cameras + if(C && istype(C)) + if(!same_z_level(C)) + return + if(!C.can_use()) + to_chat(usr, "Something's wrong with that camera! You can't get a feed.") + return + current = C + spawn(6) + if(src.check_eye(usr)) + usr.reset_perspective(C) + interact() + else + usr.unset_machine() + usr << browse(null, "window=camerabug") + return + else + usr.unset_machine() + + interact() + +/obj/item/camera_bug/process() + if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval))) + return + last_tracked = world.time + if(track_mode == BUGMODE_TRACK ) // search for user + // Note that it will be tricked if your name appears to change. + // This is not optimal but it is better than tracking you relentlessly despite everything. + if(!tracking) + src.updateSelfDialog() + return + + if(tracking.name != tracked_name) // Hiding their identity, tricksy + var/mob/M = tracking + if(istype(M)) + if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before + if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd + src.updateSelfDialog()//But if it's neither of those cases + return // you won't find em on the cameras + else + src.updateSelfDialog() + return + + var/list/tracking_cams = list() + var/list/b_cams = get_cameras() + for(var/entry in b_cams) + tracking_cams += b_cams[entry] + var/list/target_region = view(tracking) + + for(var/obj/machinery/camera/C in (target_region & tracking_cams)) + if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras + continue + if(C.can_use()) + last_found = C.c_tag + last_seen = world.time + break + src.updateSelfDialog() + +/obj/item/camera_bug/proc/same_z_level(var/obj/machinery/camera/C) + var/turf/T_cam = get_turf(C) + var/turf/T_bug = get_turf(loc) + if(!T_bug || T_cam.z != T_bug.z) + to_chat(usr, "You can't get a signal!") + return FALSE + return TRUE + +#undef BUGMODE_LIST +#undef BUGMODE_MONITOR +#undef BUGMODE_TRACK diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index ebd54eb0929..cc132426f88 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -1,575 +1,575 @@ -/obj/item/flashlight - name = "flashlight" - desc = "A hand-held emergency light." - custom_price = 100 - icon = 'icons/obj/lighting.dmi' - icon_state = "flashlight" - inhand_icon_state = "flashlight" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) - actions_types = list(/datum/action/item_action/toggle_light) - var/on = FALSE - var/brightness_on = 4 //range of light when on - var/flashlight_power = 1 //strength of the light when on - -/obj/item/flashlight/Initialize() - . = ..() - if(icon_state == "[initial(icon_state)]-on") - on = TRUE - update_brightness() - -/obj/item/flashlight/proc/update_brightness(mob/user = null) - if(on) - icon_state = "[initial(icon_state)]-on" - if(flashlight_power) - set_light(l_range = brightness_on, l_power = flashlight_power) - else - set_light(brightness_on) - else - icon_state = initial(icon_state) - set_light(0) - -/obj/item/flashlight/attack_self(mob/user) - on = !on - update_brightness(user) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - return 1 - -/obj/item/flashlight/suicide_act(mob/living/carbon/human/user) - if (user.is_blind()) - user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on... but [user.p_theyre()] blind!") - return SHAME - user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -/obj/item/flashlight/attack(mob/living/carbon/M, mob/living/carbon/human/user) - add_fingerprint(user) - if(istype(M) && on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) - - if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) //too dumb to use flashlight properly - return ..() //just hit them in the head - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - - if(!M.get_bodypart(BODY_ZONE_HEAD)) - to_chat(user, "[M] doesn't have a head!") - return - - if(flashlight_power < 1) - to_chat(user, "\The [src] isn't bright enough to see anything! ") - return - - switch(user.zone_selected) - if(BODY_ZONE_PRECISE_EYES) - if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES)) - to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask": "glasses"] first!") - return - - var/obj/item/organ/eyes/E = M.getorganslot(ORGAN_SLOT_EYES) - if(!E) - to_chat(user, "[M] doesn't have any eyes!") - return - - if(M == user) //they're using it on themselves - if(M.flash_act(visual = 1)) - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes! Trippy!") - else - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes.") - else - user.visible_message("[user] directs [src] to [M]'s eyes.", \ - "You direct [src] to [M]'s eyes.") - if(M.stat == DEAD || (M.is_blind()) || !M.flash_act(visual = 1)) //mob is dead or fully blind - to_chat(user, "[M]'s pupils don't react to the light!") - else if(M.dna && M.dna.check_mutation(XRAY)) //mob has X-ray vision - to_chat(user, "[M]'s pupils give an eerie glow!") - else //they're okay! - to_chat(user, "[M]'s pupils narrow.") - - if(BODY_ZONE_PRECISE_MOUTH) - - if(M.is_mouth_covered()) - to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSMOUTH) ? "helmet" : "mask"] first!") - return - - var/their = M.p_their() - - var/list/mouth_organs = new - for(var/obj/item/organ/O in M.internal_organs) - if(O.zone == BODY_ZONE_PRECISE_MOUTH) - mouth_organs.Add(O) - var/organ_list = "" - var/organ_count = LAZYLEN(mouth_organs) - if(organ_count) - for(var/I in 1 to organ_count) - if(I > 1) - if(I == mouth_organs.len) - organ_list += ", and " - else - organ_list += ", " - var/obj/item/organ/O = mouth_organs[I] - organ_list += (O.gender == "plural" ? O.name : "\an [O.name]") - - var/pill_count = 0 - for(var/datum/action/item_action/hands_free/activate_pill/AP in M.actions) - pill_count++ - - if(M == user) - var/can_use_mirror = FALSE - if(isturf(user.loc)) - var/obj/structure/mirror/mirror = locate(/obj/structure/mirror, user.loc) - if(mirror) - switch(user.dir) - if(NORTH) - can_use_mirror = mirror.pixel_y > 0 - if(SOUTH) - can_use_mirror = mirror.pixel_y < 0 - if(EAST) - can_use_mirror = mirror.pixel_x > 0 - if(WEST) - can_use_mirror = mirror.pixel_x < 0 - - M.visible_message("[M] directs [src] to [their] mouth.", \ - "You point [src] into your mouth.") - if(!can_use_mirror) - to_chat(user, "You can't see anything without a mirror.") - return - if(organ_count) - to_chat(user, "Inside your mouth [organ_count > 1 ? "are" : "is"] [organ_list].") - else - to_chat(user, "There's nothing inside your mouth.") - if(pill_count) - to_chat(user, "You have [pill_count] implanted pill[pill_count > 1 ? "s" : ""].") - - else - user.visible_message("[user] directs [src] to [M]'s mouth.",\ - "You direct [src] to [M]'s mouth.") - if(organ_count) - to_chat(user, "Inside [their] mouth [organ_count > 1 ? "are" : "is"] [organ_list].") - else - to_chat(user, "[M] doesn't have any organs in [their] mouth.") - if(pill_count) - to_chat(user, "[M] has [pill_count] pill[pill_count > 1 ? "s" : ""] implanted in [their] teeth.") - - else - return ..() - -/obj/item/flashlight/pen - name = "penlight" - desc = "A pen-sized light, used by medical staff. It can also be used to create a hologram to alert people of incoming medical assistance." - icon_state = "penlight" - inhand_icon_state = "" - worn_icon_state = "pen" - flags_1 = CONDUCT_1 - brightness_on = 2 - var/holo_cooldown = 0 - -/obj/item/flashlight/pen/afterattack(atom/target, mob/user, proximity_flag) - . = ..() - if(!proximity_flag) - if(holo_cooldown > world.time) - to_chat(user, "[src] is not ready yet!") - return - var/T = get_turf(target) - if(locate(/mob/living) in T) - new /obj/effect/temp_visual/medical_holosign(T,user) //produce a holographic glow - holo_cooldown = world.time + 10 SECONDS - return - -// see: [/datum/wound/burn/proc/uv()] -/obj/item/flashlight/pen/paramedic - name = "paramedic penlight" - desc = "A high-powered UV penlight intended to help stave off infection in the field on serious burned patients. Probably really bad to look into." - icon_state = "penlight_surgical" - /// Our current UV cooldown - COOLDOWN_DECLARE(uv_cooldown) - /// How long between UV fryings - var/uv_cooldown_length = 1 MINUTES - /// How much sanitization to apply to the burn wound - var/uv_power = 1 - -/obj/effect/temp_visual/medical_holosign - name = "medical holosign" - desc = "A small holographic glow that indicates a medic is coming to treat a patient." - icon_state = "medi_holo" - duration = 30 - -/obj/effect/temp_visual/medical_holosign/Initialize(mapload, creator) - . = ..() - playsound(loc, 'sound/machines/ping.ogg', 50, FALSE) //make some noise! - if(creator) - visible_message("[creator] created a medical hologram!") - - -/obj/item/flashlight/seclite - name = "seclite" - desc = "A robust flashlight used by security." - icon_state = "seclite" - inhand_icon_state = "seclite" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - force = 9 // Not as good as a stun baton. - brightness_on = 5 // A little better than the standard flashlight. - hitsound = 'sound/weapons/genhit1.ogg' - -// the desk lamps are a bit special -/obj/item/flashlight/lamp - name = "desk lamp" - desc = "A desk lamp with an adjustable mount." - icon_state = "lamp" - inhand_icon_state = "lamp" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - force = 10 - brightness_on = 5 - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - custom_materials = null - on = TRUE - - -// green-shaded desk lamp -/obj/item/flashlight/lamp/green - desc = "A classic green-shaded desk lamp." - icon_state = "lampgreen" - inhand_icon_state = "lampgreen" - - - -/obj/item/flashlight/lamp/verb/toggle_light() - set name = "Toggle light" - set category = "Object" - set src in oview(1) - - if(!usr.stat) - attack_self(usr) - -//Bananalamp -/obj/item/flashlight/lamp/bananalamp - name = "banana lamp" - desc = "Only a clown would think to make a ghetto banana-shaped lamp. Even has a goofy pullstring." - icon_state = "bananalamp" - inhand_icon_state = "bananalamp" - -// FLARES - -/obj/item/flashlight/flare - name = "flare" - desc = "A red Nanotrasen issued flare. There are instructions on the side, it reads 'pull cord, make light'." - w_class = WEIGHT_CLASS_SMALL - brightness_on = 7 // Pretty bright. - icon_state = "flare" - inhand_icon_state = "flare" - actions_types = list() - var/fuel = 0 - var/on_damage = 7 - var/produce_heat = 1500 - heat = 1000 - light_color = LIGHT_COLOR_FLARE - grind_results = list(/datum/reagent/sulfur = 15) - -/obj/item/flashlight/flare/Initialize() - . = ..() - fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds. - -/obj/item/flashlight/flare/process() - open_flame(heat) - fuel = max(fuel - 1, 0) - if(!fuel || !on) - turn_off() - if(!fuel) - icon_state = "[initial(icon_state)]-empty" - STOP_PROCESSING(SSobj, src) - -/obj/item/flashlight/flare/ignition_effect(atom/A, mob/user) - . = fuel && on ? "[user] lights [A] with [src] like a real badass." : "" - -/obj/item/flashlight/flare/proc/turn_off() - on = FALSE - force = initial(src.force) - damtype = initial(src.damtype) - if(ismob(loc)) - var/mob/U = loc - update_brightness(U) - else - update_brightness(null) - -/obj/item/flashlight/flare/update_brightness(mob/user = null) - ..() - if(on) - inhand_icon_state = "[initial(inhand_icon_state)]-on" - else - inhand_icon_state = "[initial(inhand_icon_state)]" - -/obj/item/flashlight/flare/attack_self(mob/user) - - // Usual checks - if(!fuel) - to_chat(user, "[src] is out of fuel!") - return - if(on) - to_chat(user, "[src] is already on!") - return - - . = ..() - // All good, turn it on. - if(.) - user.visible_message("[user] lights \the [src].", "You light \the [src]!") - force = on_damage - damtype = "fire" - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/flare/get_temperature() - return on * heat - -/obj/item/flashlight/flare/torch - name = "torch" - desc = "A torch fashioned from some leaves and a log." - w_class = WEIGHT_CLASS_BULKY - brightness_on = 4 - icon_state = "torch" - inhand_icon_state = "torch" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - light_color = LIGHT_COLOR_ORANGE - on_damage = 10 - slot_flags = null - -/obj/item/flashlight/lantern - name = "lantern" - icon_state = "lantern" - inhand_icon_state = "lantern" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - desc = "A mining lantern." - brightness_on = 6 // luminosity when on - -/obj/item/flashlight/lantern/heirloom_moth - name = "old lantern" - desc = "An old lantern that has seen plenty of use." - brightness_on = 4 - -/obj/item/flashlight/lantern/syndicate - name = "suspicious lantern" - desc = "A suspicious looking lantern." - icon_state = "syndilantern" - inhand_icon_state = "syndilantern" - brightness_on = 10 - -/obj/item/flashlight/lantern/jade - name = "jade lantern" - desc = "An ornate, green lantern." - color = LIGHT_COLOR_GREEN - light_color = LIGHT_COLOR_GREEN - -/obj/item/flashlight/slime - gender = PLURAL - name = "glowing slime extract" - desc = "Extract from a yellow slime. It emits a strong light when squeezed." - icon = 'icons/obj/lighting.dmi' - icon_state = "slime" - inhand_icon_state = "slime" - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - custom_materials = null - brightness_on = 6 //luminosity when on - -/obj/item/flashlight/emp - var/emp_max_charges = 4 - var/emp_cur_charges = 4 - var/charge_tick = 0 - -/obj/item/flashlight/emp/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/emp/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/flashlight/emp/process() - charge_tick++ - if(charge_tick < 10) - return FALSE - charge_tick = 0 - emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) - return TRUE - -/obj/item/flashlight/emp/attack(mob/living/M, mob/living/user) - if(on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) // call original attack when examining organs - ..() - return - -/obj/item/flashlight/emp/afterattack(atom/movable/A, mob/user, proximity) - . = ..() - if(!proximity) - return - - if(emp_cur_charges > 0) - emp_cur_charges -= 1 - - if(ismob(A)) - var/mob/M = A - log_combat(user, M, "attacked", "EMP-light") - M.visible_message("[user] blinks \the [src] at \the [A].", \ - "[user] blinks \the [src] at you.") - else - A.visible_message("[user] blinks \the [src] at \the [A].") - to_chat(user, "\The [src] now has [emp_cur_charges] charge\s.") - A.emp_act(EMP_HEAVY) - else - to_chat(user, "\The [src] needs time to recharge!") - return - -/obj/item/flashlight/emp/debug //for testing emp_act() - name = "debug EMP flashlight" - emp_max_charges = 100 - emp_cur_charges = 100 - -// Glowsticks, in the uncomfortable range of similar to flares, -// but not similar enough to make it worth a refactor -/obj/item/flashlight/glowstick - name = "glowstick" - desc = "A military-grade glowstick." - custom_price = 50 - w_class = WEIGHT_CLASS_SMALL - brightness_on = 4 - color = LIGHT_COLOR_GREEN - icon_state = "glowstick" - inhand_icon_state = "glowstick" - grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick - var/fuel = 0 - -/obj/item/flashlight/glowstick/Initialize() - fuel = rand(1600, 2000) - light_color = color - . = ..() - -/obj/item/flashlight/glowstick/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/flashlight/glowstick/process() - fuel = max(fuel - 1, 0) - if(!fuel) - turn_off() - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/flashlight/glowstick/proc/turn_off() - on = FALSE - update_icon() - -/obj/item/flashlight/glowstick/update_icon() - inhand_icon_state = "glowstick" - cut_overlays() - if(!fuel) - icon_state = "glowstick-empty" - cut_overlays() - set_light(0) - else if(on) - var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow") - glowstick_overlay.color = color - add_overlay(glowstick_overlay) - inhand_icon_state = "glowstick-on" - set_light(brightness_on) - else - icon_state = "glowstick" - cut_overlays() - -/obj/item/flashlight/glowstick/attack_self(mob/user) - if(!fuel) - to_chat(user, "[src] is spent.") - return - if(on) - to_chat(user, "[src] is already lit!") - return - - . = ..() - if(.) - user.visible_message("[user] cracks and shakes [src].", "You crack and shake [src], turning it on!") - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user) - if(!fuel) - user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but it's empty!") - return SHAME - var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) - if(!eyes) - user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!") - return SHAME - user.visible_message("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") - fuel = 0 - return (FIRELOSS) - -/obj/item/flashlight/glowstick/red - name = "red glowstick" - color = LIGHT_COLOR_RED - -/obj/item/flashlight/glowstick/blue - name = "blue glowstick" - color = LIGHT_COLOR_BLUE - -/obj/item/flashlight/glowstick/cyan - name = "cyan glowstick" - color = LIGHT_COLOR_CYAN - -/obj/item/flashlight/glowstick/orange - name = "orange glowstick" - color = LIGHT_COLOR_ORANGE - -/obj/item/flashlight/glowstick/yellow - name = "yellow glowstick" - color = LIGHT_COLOR_YELLOW - -/obj/item/flashlight/glowstick/pink - name = "pink glowstick" - color = LIGHT_COLOR_PINK - -/obj/effect/spawner/lootdrop/glowstick - name = "random colored glowstick" - icon = 'icons/obj/lighting.dmi' - icon_state = "random_glowstick" - -/obj/effect/spawner/lootdrop/glowstick/Initialize() - loot = typesof(/obj/item/flashlight/glowstick) - . = ..() - -/obj/item/flashlight/spotlight //invisible lighting source - name = "disco light" - desc = "Groovy..." - icon_state = null - light_color = null - brightness_on = 0 - light_range = 0 - light_power = 10 - alpha = 0 - layer = 0 - on = TRUE - anchored = TRUE - var/range = null - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/flashlight/flashdark - name = "flashdark" - desc = "A strange device manufactured with mysterious elements that somehow emits darkness. Or maybe it just sucks in light? Nobody knows for sure." - icon_state = "flashdark" - inhand_icon_state = "flashdark" - brightness_on = 2.5 - flashlight_power = -3 - -/obj/item/flashlight/eyelight - name = "eyelight" - desc = "This shouldn't exist outside of someone's head, how are you seeing this?" - brightness_on = 15 - flashlight_power = 1 - flags_1 = CONDUCT_1 - item_flags = DROPDEL - actions_types = list() +/obj/item/flashlight + name = "flashlight" + desc = "A hand-held emergency light." + custom_price = 100 + icon = 'icons/obj/lighting.dmi' + icon_state = "flashlight" + inhand_icon_state = "flashlight" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) + actions_types = list(/datum/action/item_action/toggle_light) + var/on = FALSE + var/brightness_on = 4 //range of light when on + var/flashlight_power = 1 //strength of the light when on + +/obj/item/flashlight/Initialize() + . = ..() + if(icon_state == "[initial(icon_state)]-on") + on = TRUE + update_brightness() + +/obj/item/flashlight/proc/update_brightness(mob/user = null) + if(on) + icon_state = "[initial(icon_state)]-on" + if(flashlight_power) + set_light(l_range = brightness_on, l_power = flashlight_power) + else + set_light(brightness_on) + else + icon_state = initial(icon_state) + set_light(0) + +/obj/item/flashlight/attack_self(mob/user) + on = !on + update_brightness(user) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + return 1 + +/obj/item/flashlight/suicide_act(mob/living/carbon/human/user) + if (user.is_blind()) + user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on... but [user.p_theyre()] blind!") + return SHAME + user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +/obj/item/flashlight/attack(mob/living/carbon/M, mob/living/carbon/human/user) + add_fingerprint(user) + if(istype(M) && on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) + + if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) //too dumb to use flashlight properly + return ..() //just hit them in the head + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + + if(!M.get_bodypart(BODY_ZONE_HEAD)) + to_chat(user, "[M] doesn't have a head!") + return + + if(flashlight_power < 1) + to_chat(user, "\The [src] isn't bright enough to see anything! ") + return + + switch(user.zone_selected) + if(BODY_ZONE_PRECISE_EYES) + if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES)) + to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask": "glasses"] first!") + return + + var/obj/item/organ/eyes/E = M.getorganslot(ORGAN_SLOT_EYES) + if(!E) + to_chat(user, "[M] doesn't have any eyes!") + return + + if(M == user) //they're using it on themselves + if(M.flash_act(visual = 1)) + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes! Trippy!") + else + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes.") + else + user.visible_message("[user] directs [src] to [M]'s eyes.", \ + "You direct [src] to [M]'s eyes.") + if(M.stat == DEAD || (M.is_blind()) || !M.flash_act(visual = 1)) //mob is dead or fully blind + to_chat(user, "[M]'s pupils don't react to the light!") + else if(M.dna && M.dna.check_mutation(XRAY)) //mob has X-ray vision + to_chat(user, "[M]'s pupils give an eerie glow!") + else //they're okay! + to_chat(user, "[M]'s pupils narrow.") + + if(BODY_ZONE_PRECISE_MOUTH) + + if(M.is_mouth_covered()) + to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSMOUTH) ? "helmet" : "mask"] first!") + return + + var/their = M.p_their() + + var/list/mouth_organs = new + for(var/obj/item/organ/O in M.internal_organs) + if(O.zone == BODY_ZONE_PRECISE_MOUTH) + mouth_organs.Add(O) + var/organ_list = "" + var/organ_count = LAZYLEN(mouth_organs) + if(organ_count) + for(var/I in 1 to organ_count) + if(I > 1) + if(I == mouth_organs.len) + organ_list += ", and " + else + organ_list += ", " + var/obj/item/organ/O = mouth_organs[I] + organ_list += (O.gender == "plural" ? O.name : "\an [O.name]") + + var/pill_count = 0 + for(var/datum/action/item_action/hands_free/activate_pill/AP in M.actions) + pill_count++ + + if(M == user) + var/can_use_mirror = FALSE + if(isturf(user.loc)) + var/obj/structure/mirror/mirror = locate(/obj/structure/mirror, user.loc) + if(mirror) + switch(user.dir) + if(NORTH) + can_use_mirror = mirror.pixel_y > 0 + if(SOUTH) + can_use_mirror = mirror.pixel_y < 0 + if(EAST) + can_use_mirror = mirror.pixel_x > 0 + if(WEST) + can_use_mirror = mirror.pixel_x < 0 + + M.visible_message("[M] directs [src] to [their] mouth.", \ + "You point [src] into your mouth.") + if(!can_use_mirror) + to_chat(user, "You can't see anything without a mirror.") + return + if(organ_count) + to_chat(user, "Inside your mouth [organ_count > 1 ? "are" : "is"] [organ_list].") + else + to_chat(user, "There's nothing inside your mouth.") + if(pill_count) + to_chat(user, "You have [pill_count] implanted pill[pill_count > 1 ? "s" : ""].") + + else + user.visible_message("[user] directs [src] to [M]'s mouth.",\ + "You direct [src] to [M]'s mouth.") + if(organ_count) + to_chat(user, "Inside [their] mouth [organ_count > 1 ? "are" : "is"] [organ_list].") + else + to_chat(user, "[M] doesn't have any organs in [their] mouth.") + if(pill_count) + to_chat(user, "[M] has [pill_count] pill[pill_count > 1 ? "s" : ""] implanted in [their] teeth.") + + else + return ..() + +/obj/item/flashlight/pen + name = "penlight" + desc = "A pen-sized light, used by medical staff. It can also be used to create a hologram to alert people of incoming medical assistance." + icon_state = "penlight" + inhand_icon_state = "" + worn_icon_state = "pen" + flags_1 = CONDUCT_1 + brightness_on = 2 + var/holo_cooldown = 0 + +/obj/item/flashlight/pen/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(!proximity_flag) + if(holo_cooldown > world.time) + to_chat(user, "[src] is not ready yet!") + return + var/T = get_turf(target) + if(locate(/mob/living) in T) + new /obj/effect/temp_visual/medical_holosign(T,user) //produce a holographic glow + holo_cooldown = world.time + 10 SECONDS + return + +// see: [/datum/wound/burn/proc/uv()] +/obj/item/flashlight/pen/paramedic + name = "paramedic penlight" + desc = "A high-powered UV penlight intended to help stave off infection in the field on serious burned patients. Probably really bad to look into." + icon_state = "penlight_surgical" + /// Our current UV cooldown + COOLDOWN_DECLARE(uv_cooldown) + /// How long between UV fryings + var/uv_cooldown_length = 1 MINUTES + /// How much sanitization to apply to the burn wound + var/uv_power = 1 + +/obj/effect/temp_visual/medical_holosign + name = "medical holosign" + desc = "A small holographic glow that indicates a medic is coming to treat a patient." + icon_state = "medi_holo" + duration = 30 + +/obj/effect/temp_visual/medical_holosign/Initialize(mapload, creator) + . = ..() + playsound(loc, 'sound/machines/ping.ogg', 50, FALSE) //make some noise! + if(creator) + visible_message("[creator] created a medical hologram!") + + +/obj/item/flashlight/seclite + name = "seclite" + desc = "A robust flashlight used by security." + icon_state = "seclite" + inhand_icon_state = "seclite" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + force = 9 // Not as good as a stun baton. + brightness_on = 5 // A little better than the standard flashlight. + hitsound = 'sound/weapons/genhit1.ogg' + +// the desk lamps are a bit special +/obj/item/flashlight/lamp + name = "desk lamp" + desc = "A desk lamp with an adjustable mount." + icon_state = "lamp" + inhand_icon_state = "lamp" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + force = 10 + brightness_on = 5 + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + custom_materials = null + on = TRUE + + +// green-shaded desk lamp +/obj/item/flashlight/lamp/green + desc = "A classic green-shaded desk lamp." + icon_state = "lampgreen" + inhand_icon_state = "lampgreen" + + + +/obj/item/flashlight/lamp/verb/toggle_light() + set name = "Toggle light" + set category = "Object" + set src in oview(1) + + if(!usr.stat) + attack_self(usr) + +//Bananalamp +/obj/item/flashlight/lamp/bananalamp + name = "banana lamp" + desc = "Only a clown would think to make a ghetto banana-shaped lamp. Even has a goofy pullstring." + icon_state = "bananalamp" + inhand_icon_state = "bananalamp" + +// FLARES + +/obj/item/flashlight/flare + name = "flare" + desc = "A red Nanotrasen issued flare. There are instructions on the side, it reads 'pull cord, make light'." + w_class = WEIGHT_CLASS_SMALL + brightness_on = 7 // Pretty bright. + icon_state = "flare" + inhand_icon_state = "flare" + actions_types = list() + var/fuel = 0 + var/on_damage = 7 + var/produce_heat = 1500 + heat = 1000 + light_color = LIGHT_COLOR_FLARE + grind_results = list(/datum/reagent/sulfur = 15) + +/obj/item/flashlight/flare/Initialize() + . = ..() + fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds. + +/obj/item/flashlight/flare/process() + open_flame(heat) + fuel = max(fuel - 1, 0) + if(!fuel || !on) + turn_off() + if(!fuel) + icon_state = "[initial(icon_state)]-empty" + STOP_PROCESSING(SSobj, src) + +/obj/item/flashlight/flare/ignition_effect(atom/A, mob/user) + . = fuel && on ? "[user] lights [A] with [src] like a real badass." : "" + +/obj/item/flashlight/flare/proc/turn_off() + on = FALSE + force = initial(src.force) + damtype = initial(src.damtype) + if(ismob(loc)) + var/mob/U = loc + update_brightness(U) + else + update_brightness(null) + +/obj/item/flashlight/flare/update_brightness(mob/user = null) + ..() + if(on) + inhand_icon_state = "[initial(inhand_icon_state)]-on" + else + inhand_icon_state = "[initial(inhand_icon_state)]" + +/obj/item/flashlight/flare/attack_self(mob/user) + + // Usual checks + if(!fuel) + to_chat(user, "[src] is out of fuel!") + return + if(on) + to_chat(user, "[src] is already on!") + return + + . = ..() + // All good, turn it on. + if(.) + user.visible_message("[user] lights \the [src].", "You light \the [src]!") + force = on_damage + damtype = "fire" + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/flare/get_temperature() + return on * heat + +/obj/item/flashlight/flare/torch + name = "torch" + desc = "A torch fashioned from some leaves and a log." + w_class = WEIGHT_CLASS_BULKY + brightness_on = 4 + icon_state = "torch" + inhand_icon_state = "torch" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + light_color = LIGHT_COLOR_ORANGE + on_damage = 10 + slot_flags = null + +/obj/item/flashlight/lantern + name = "lantern" + icon_state = "lantern" + inhand_icon_state = "lantern" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + desc = "A mining lantern." + brightness_on = 6 // luminosity when on + +/obj/item/flashlight/lantern/heirloom_moth + name = "old lantern" + desc = "An old lantern that has seen plenty of use." + brightness_on = 4 + +/obj/item/flashlight/lantern/syndicate + name = "suspicious lantern" + desc = "A suspicious looking lantern." + icon_state = "syndilantern" + inhand_icon_state = "syndilantern" + brightness_on = 10 + +/obj/item/flashlight/lantern/jade + name = "jade lantern" + desc = "An ornate, green lantern." + color = LIGHT_COLOR_GREEN + light_color = LIGHT_COLOR_GREEN + +/obj/item/flashlight/slime + gender = PLURAL + name = "glowing slime extract" + desc = "Extract from a yellow slime. It emits a strong light when squeezed." + icon = 'icons/obj/lighting.dmi' + icon_state = "slime" + inhand_icon_state = "slime" + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + custom_materials = null + brightness_on = 6 //luminosity when on + +/obj/item/flashlight/emp + var/emp_max_charges = 4 + var/emp_cur_charges = 4 + var/charge_tick = 0 + +/obj/item/flashlight/emp/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/emp/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/flashlight/emp/process() + charge_tick++ + if(charge_tick < 10) + return FALSE + charge_tick = 0 + emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) + return TRUE + +/obj/item/flashlight/emp/attack(mob/living/M, mob/living/user) + if(on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) // call original attack when examining organs + ..() + return + +/obj/item/flashlight/emp/afterattack(atom/movable/A, mob/user, proximity) + . = ..() + if(!proximity) + return + + if(emp_cur_charges > 0) + emp_cur_charges -= 1 + + if(ismob(A)) + var/mob/M = A + log_combat(user, M, "attacked", "EMP-light") + M.visible_message("[user] blinks \the [src] at \the [A].", \ + "[user] blinks \the [src] at you.") + else + A.visible_message("[user] blinks \the [src] at \the [A].") + to_chat(user, "\The [src] now has [emp_cur_charges] charge\s.") + A.emp_act(EMP_HEAVY) + else + to_chat(user, "\The [src] needs time to recharge!") + return + +/obj/item/flashlight/emp/debug //for testing emp_act() + name = "debug EMP flashlight" + emp_max_charges = 100 + emp_cur_charges = 100 + +// Glowsticks, in the uncomfortable range of similar to flares, +// but not similar enough to make it worth a refactor +/obj/item/flashlight/glowstick + name = "glowstick" + desc = "A military-grade glowstick." + custom_price = 50 + w_class = WEIGHT_CLASS_SMALL + brightness_on = 4 + color = LIGHT_COLOR_GREEN + icon_state = "glowstick" + inhand_icon_state = "glowstick" + grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick + var/fuel = 0 + +/obj/item/flashlight/glowstick/Initialize() + fuel = rand(1600, 2000) + light_color = color + . = ..() + +/obj/item/flashlight/glowstick/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/flashlight/glowstick/process() + fuel = max(fuel - 1, 0) + if(!fuel) + turn_off() + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/flashlight/glowstick/proc/turn_off() + on = FALSE + update_icon() + +/obj/item/flashlight/glowstick/update_icon() + inhand_icon_state = "glowstick" + cut_overlays() + if(!fuel) + icon_state = "glowstick-empty" + cut_overlays() + set_light(0) + else if(on) + var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow") + glowstick_overlay.color = color + add_overlay(glowstick_overlay) + inhand_icon_state = "glowstick-on" + set_light(brightness_on) + else + icon_state = "glowstick" + cut_overlays() + +/obj/item/flashlight/glowstick/attack_self(mob/user) + if(!fuel) + to_chat(user, "[src] is spent.") + return + if(on) + to_chat(user, "[src] is already lit!") + return + + . = ..() + if(.) + user.visible_message("[user] cracks and shakes [src].", "You crack and shake [src], turning it on!") + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user) + if(!fuel) + user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but it's empty!") + return SHAME + var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) + if(!eyes) + user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!") + return SHAME + user.visible_message("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") + fuel = 0 + return (FIRELOSS) + +/obj/item/flashlight/glowstick/red + name = "red glowstick" + color = LIGHT_COLOR_RED + +/obj/item/flashlight/glowstick/blue + name = "blue glowstick" + color = LIGHT_COLOR_BLUE + +/obj/item/flashlight/glowstick/cyan + name = "cyan glowstick" + color = LIGHT_COLOR_CYAN + +/obj/item/flashlight/glowstick/orange + name = "orange glowstick" + color = LIGHT_COLOR_ORANGE + +/obj/item/flashlight/glowstick/yellow + name = "yellow glowstick" + color = LIGHT_COLOR_YELLOW + +/obj/item/flashlight/glowstick/pink + name = "pink glowstick" + color = LIGHT_COLOR_PINK + +/obj/effect/spawner/lootdrop/glowstick + name = "random colored glowstick" + icon = 'icons/obj/lighting.dmi' + icon_state = "random_glowstick" + +/obj/effect/spawner/lootdrop/glowstick/Initialize() + loot = typesof(/obj/item/flashlight/glowstick) + . = ..() + +/obj/item/flashlight/spotlight //invisible lighting source + name = "disco light" + desc = "Groovy..." + icon_state = null + light_color = null + brightness_on = 0 + light_range = 0 + light_power = 10 + alpha = 0 + layer = 0 + on = TRUE + anchored = TRUE + var/range = null + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/flashlight/flashdark + name = "flashdark" + desc = "A strange device manufactured with mysterious elements that somehow emits darkness. Or maybe it just sucks in light? Nobody knows for sure." + icon_state = "flashdark" + inhand_icon_state = "flashdark" + brightness_on = 2.5 + flashlight_power = -3 + +/obj/item/flashlight/eyelight + name = "eyelight" + desc = "This shouldn't exist outside of someone's head, how are you seeing this?" + brightness_on = 15 + flashlight_power = 1 + flags_1 = CONDUCT_1 + item_flags = DROPDEL + actions_types = list() diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm index a52d8c34661..a3a5ee1c7cb 100644 --- a/code/game/objects/items/devices/gps.dm +++ b/code/game/objects/items/devices/gps.dm @@ -1,78 +1,78 @@ - -/obj/item/gps - name = "global positioning system" - desc = "Helping lost spacemen find their way through the planets since 2016." - icon = 'icons/obj/telescience.dmi' - icon_state = "gps-c" - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - obj_flags = UNIQUE_RENAME - var/gpstag - -/obj/item/gps/Initialize() - . = ..() - AddComponent(/datum/component/gps/item, gpstag) - -/obj/item/gps/science - icon_state = "gps-s" - gpstag = "SCI0" - -/obj/item/gps/engineering - icon_state = "gps-e" - gpstag = "ENG0" - -/obj/item/gps/mining - icon_state = "gps-m" - gpstag = "MINE0" - desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." - -/obj/item/gps/cyborg - icon_state = "gps-b" - gpstag = "BORG0" - desc = "A mining cyborg internal positioning system. Used as a recovery beacon for damaged cyborg assets, or a collaboration tool for mining teams." - -/obj/item/gps/cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/gps/mining/internal - icon_state = "gps-m" - gpstag = "MINER" - desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." - -/obj/item/gps/visible_debug - name = "visible GPS" - gpstag = "ADMIN" - desc = "This admin-spawn GPS unit leaves the coordinates visible \ - on any turf that it passes over, for debugging. Especially useful \ - for marking the area around the transition edges." - var/list/turf/tagged - -/obj/item/gps/visible_debug/Initialize() - . = ..() - tagged = list() - START_PROCESSING(SSfastprocess, src) - -/obj/item/gps/visible_debug/process() - var/turf/T = get_turf(src) - if(T) - // I assume it's faster to color,tag and OR the turf in, rather - // then checking if its there - T.color = RANDOM_COLOUR - T.maptext = "[T.x],[T.y],[T.z]" - tagged |= T - -/obj/item/gps/visible_debug/proc/clear() - while(tagged.len) - var/turf/T = pop(tagged) - T.color = initial(T.color) - T.maptext = initial(T.maptext) - -/obj/item/gps/visible_debug/Destroy() - if(tagged) - clear() - tagged = null - STOP_PROCESSING(SSfastprocess, src) - . = ..() + +/obj/item/gps + name = "global positioning system" + desc = "Helping lost spacemen find their way through the planets since 2016." + icon = 'icons/obj/telescience.dmi' + icon_state = "gps-c" + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + obj_flags = UNIQUE_RENAME + var/gpstag + +/obj/item/gps/Initialize() + . = ..() + AddComponent(/datum/component/gps/item, gpstag) + +/obj/item/gps/science + icon_state = "gps-s" + gpstag = "SCI0" + +/obj/item/gps/engineering + icon_state = "gps-e" + gpstag = "ENG0" + +/obj/item/gps/mining + icon_state = "gps-m" + gpstag = "MINE0" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." + +/obj/item/gps/cyborg + icon_state = "gps-b" + gpstag = "BORG0" + desc = "A mining cyborg internal positioning system. Used as a recovery beacon for damaged cyborg assets, or a collaboration tool for mining teams." + +/obj/item/gps/cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/gps/mining/internal + icon_state = "gps-m" + gpstag = "MINER" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." + +/obj/item/gps/visible_debug + name = "visible GPS" + gpstag = "ADMIN" + desc = "This admin-spawn GPS unit leaves the coordinates visible \ + on any turf that it passes over, for debugging. Especially useful \ + for marking the area around the transition edges." + var/list/turf/tagged + +/obj/item/gps/visible_debug/Initialize() + . = ..() + tagged = list() + START_PROCESSING(SSfastprocess, src) + +/obj/item/gps/visible_debug/process() + var/turf/T = get_turf(src) + if(T) + // I assume it's faster to color,tag and OR the turf in, rather + // then checking if its there + T.color = RANDOM_COLOUR + T.maptext = "[T.x],[T.y],[T.z]" + tagged |= T + +/obj/item/gps/visible_debug/proc/clear() + while(tagged.len) + var/turf/T = pop(tagged) + T.color = initial(T.color) + T.maptext = initial(T.maptext) + +/obj/item/gps/visible_debug/Destroy() + if(tagged) + clear() + tagged = null + STOP_PROCESSING(SSfastprocess, src) + . = ..() diff --git a/code/game/objects/items/devices/instruments.dm b/code/game/objects/items/devices/instruments.dm index 9eba8cf0811..7a9b8f9c31f 100644 --- a/code/game/objects/items/devices/instruments.dm +++ b/code/game/objects/items/devices/instruments.dm @@ -1,320 +1,320 @@ -//copy pasta of the space piano, don't hurt me -Pete -/obj/item/instrument - name = "generic instrument" - resistance_flags = FLAMMABLE - force = 10 - max_integrity = 100 - icon = 'icons/obj/musician.dmi' - lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi' - var/datum/song/handheld/song - var/instrumentId = "generic" - var/instrumentExt = "mid" - var/instrumentRange = 15 - -/obj/item/instrument/Initialize() - . = ..() - song = new(instrumentId, src, instrumentExt, instrumentRange) - -/obj/item/instrument/Destroy() - QDEL_NULL(song) - . = ..() - -/obj/item/instrument/suicide_act(mob/user) - user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/instrument/Initialize(mapload) - . = ..() - if(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - -/obj/item/instrument/attack_self(mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - interact(user) - -/obj/item/instrument/interact(mob/user) - ui_interact(user) - -/obj/item/instrument/ui_interact(mob/living/user) - if(!isliving(user) || user.stat || user.restrained() || !(user.mobility_flags & MOBILITY_STAND)) - return - - user.set_machine(src) - song.interact(user) - -/obj/item/instrument/proc/start_playing() - return - -/obj/item/instrument/proc/stop_playing() - return - -/obj/item/instrument/violin - name = "space violin" - desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" - icon_state = "violin" - inhand_icon_state = "violin" - hitsound = "swing_hit" - instrumentId = "violin" - -/obj/item/instrument/violin/golden - name = "golden violin" - desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" - icon_state = "golden_violin" - inhand_icon_state = "golden_violin" - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/instrument/piano_synth - name = "synthesizer" - desc = "An advanced electronic synthesizer that can be used as various instruments." - icon_state = "synth" - inhand_icon_state = "synth" - instrumentId = "piano" - instrumentExt = "ogg" - var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "banjo" = "ogg", "guitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "mid", "xylophone" = "mid") //No eguitar you ear-rapey fuckers. - actions_types = list(/datum/action/item_action/synthswitch) - -/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano") - song.instrumentDir = name - song.instrumentExt = insTypes[name] - -/obj/item/instrument/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta - var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in sortList(insTypes) - if(!insTypes[chosen]) - return - return changeInstrument(chosen) - -/obj/item/instrument/piano_synth/headphones - name = "headphones" - desc = "Unce unce unce unce. Boop!" - icon = 'icons/obj/clothing/accessories.dmi' - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - icon_state = "headphones" - inhand_icon_state = "headphones" - slot_flags = ITEM_SLOT_EARS | ITEM_SLOT_HEAD - force = 0 - w_class = WEIGHT_CLASS_SMALL - custom_price = 125 - instrumentRange = 1 - -/obj/item/instrument/piano_synth/headphones/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing) - RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing) - -/obj/item/instrument/piano_synth/headphones/start_playing() - icon_state = "[initial(icon_state)]_on" - update_icon() - -/obj/item/instrument/piano_synth/headphones/stop_playing() - icon_state = "[initial(icon_state)]" - update_icon() - -/obj/item/instrument/piano_synth/headphones/spacepods - name = "\improper Nanotrasen space pods" - desc = "Flex your money, AND ignore what everyone else says, all at once!" - icon_state = "spacepods" - inhand_icon_state = "spacepods" - slot_flags = ITEM_SLOT_EARS - strip_delay = 100 //air pods don't fall out - instrumentRange = 0 //you're paying for quality here - custom_premium_price = 1800 - -/obj/item/instrument/banjo - name = "banjo" - desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings." - icon_state = "banjo" - inhand_icon_state = "banjo" - instrumentExt = "ogg" - attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered") - hitsound = 'sound/weapons/banjoslap.ogg' - instrumentId = "banjo" - -/obj/item/instrument/guitar - name = "guitar" - desc = "It's made out of wood and has bronze strings." - icon_state = "guitar" - inhand_icon_state = "guitar" - instrumentExt = "ogg" - attack_verb = list("played metal on", "serenaded", "crashed", "smashed") - hitsound = 'sound/weapons/stringsmash.ogg' - instrumentId = "guitar" - -/obj/item/instrument/eguitar - name = "electric guitar" - desc = "Makes all your shredding needs possible." - icon_state = "eguitar" - inhand_icon_state = "eguitar" - force = 12 - attack_verb = list("played metal on", "shredded", "crashed", "smashed") - hitsound = 'sound/weapons/stringsmash.ogg' - instrumentId = "eguitar" - instrumentExt = "ogg" - -/obj/item/instrument/glockenspiel - name = "glockenspiel" - desc = "Smooth metal bars perfect for any marching band." - icon_state = "glockenspiel" - inhand_icon_state = "glockenspiel" - instrumentId = "glockenspiel" - -/obj/item/instrument/accordion - name = "accordion" - desc = "Pun-Pun not included." - icon_state = "accordion" - inhand_icon_state = "accordion" - instrumentId = "accordion" - -/obj/item/instrument/trumpet - name = "trumpet" - desc = "To announce the arrival of the king!" - icon_state = "trumpet" - inhand_icon_state = "trumpet" - instrumentId = "trombone" - -/obj/item/instrument/trumpet/spectral - name = "spectral trumpet" - desc = "Things are about to get spooky!" - icon_state = "spectral_trumpet" - inhand_icon_state = "spectral_trumpet" - force = 0 - instrumentId = "trombone" - attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked") - -/obj/item/instrument/trumpet/spectral/Initialize() - . = ..() - AddComponent(/datum/component/spooky) - -/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user) - playsound (src, 'sound/runtime/instruments/trombone/En4.mid', 100,1,-1) - ..() - -/obj/item/instrument/saxophone - name = "saxophone" - desc = "This soothing sound will be sure to leave your audience in tears." - icon_state = "saxophone" - inhand_icon_state = "saxophone" - instrumentId = "saxophone" - -/obj/item/instrument/saxophone/spectral - name = "spectral saxophone" - desc = "This spooky sound will be sure to leave mortals in bones." - icon_state = "saxophone" - inhand_icon_state = "saxophone" - instrumentId = "saxophone" - force = 0 - attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked") - -/obj/item/instrument/saxophone/spectral/Initialize() - . = ..() - AddComponent(/datum/component/spooky) - -/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user) - playsound (src, 'sound/runtime/instruments/saxophone/En4.mid', 100,1,-1) - ..() - -/obj/item/instrument/trombone - name = "trombone" - desc = "How can any pool table ever hope to compete?" - icon_state = "trombone" - inhand_icon_state = "trombone" - instrumentId = "trombone" - -/obj/item/instrument/trombone/spectral - name = "spectral trombone" - desc = "A skeleton's favorite instrument. Apply directly on the mortals." - instrumentId = "trombone" - icon_state = "trombone" - inhand_icon_state = "trombone" - force = 0 - attack_verb = list("played","jazzed","tromboned","mourned","dooted","spooked") - -/obj/item/instrument/trombone/spectral/Initialize() - . = ..() - AddComponent(/datum/component/spooky) - -/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user) - playsound (src, 'sound/runtime/instruments/trombone/Cn4.mid', 100,1,-1) - ..() - -/obj/item/instrument/recorder - name = "recorder" - desc = "Just like in school, playing ability and all." - force = 5 - icon_state = "recorder" - inhand_icon_state = "recorder" - instrumentId = "recorder" - -/obj/item/instrument/harmonica - name = "harmonica" - desc = "For when you get a bad case of the space blues." - icon_state = "harmonica" - inhand_icon_state = "harmonica" - instrumentId = "harmonica" - slot_flags = ITEM_SLOT_MASK - force = 5 - w_class = WEIGHT_CLASS_SMALL - actions_types = list(/datum/action/item_action/instrument) - -/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args) - if(song.playing && ismob(loc)) - to_chat(loc, "You stop playing the harmonica to talk...") - song.playing = FALSE - -/obj/item/instrument/harmonica/equipped(mob/M, slot) - . = ..() - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech, override = TRUE) - -/obj/item/instrument/harmonica/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/instrument/bikehorn - name = "gilded bike horn" - desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes." - icon_state = "bike_horn" - inhand_icon_state = "bike_horn" - lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' - attack_verb = list("beautifully honks") - instrumentId = "bikehorn" - instrumentExt = "ogg" - w_class = WEIGHT_CLASS_TINY - force = 0 - throw_speed = 3 - throw_range = 15 - hitsound = 'sound/items/bikehorn.ogg' - -/// - -/obj/item/choice_beacon/music - name = "instrument delivery beacon" - desc = "Summon your tool of art." - icon_state = "gangtool-red" - -/obj/item/choice_beacon/music/generate_display_names() - var/static/list/instruments - if(!instruments) - instruments = list() - var/list/templist = list(/obj/item/instrument/violin, - /obj/item/instrument/piano_synth, - /obj/item/instrument/banjo, - /obj/item/instrument/guitar, - /obj/item/instrument/eguitar, - /obj/item/instrument/glockenspiel, - /obj/item/instrument/accordion, - /obj/item/instrument/trumpet, - /obj/item/instrument/saxophone, - /obj/item/instrument/trombone, - /obj/item/instrument/recorder, - /obj/item/instrument/harmonica, - /obj/item/instrument/piano_synth/headphones - ) - for(var/V in templist) - var/atom/A = V - instruments[initial(A.name)] = A - return instruments +//copy pasta of the space piano, don't hurt me -Pete +/obj/item/instrument + name = "generic instrument" + resistance_flags = FLAMMABLE + force = 10 + max_integrity = 100 + icon = 'icons/obj/musician.dmi' + lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi' + var/datum/song/handheld/song + var/instrumentId = "generic" + var/instrumentExt = "mid" + var/instrumentRange = 15 + +/obj/item/instrument/Initialize() + . = ..() + song = new(instrumentId, src, instrumentExt, instrumentRange) + +/obj/item/instrument/Destroy() + QDEL_NULL(song) + . = ..() + +/obj/item/instrument/suicide_act(mob/user) + user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/instrument/Initialize(mapload) + . = ..() + if(mapload) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + +/obj/item/instrument/attack_self(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + interact(user) + +/obj/item/instrument/interact(mob/user) + ui_interact(user) + +/obj/item/instrument/ui_interact(mob/living/user) + if(!isliving(user) || user.stat || user.restrained() || !(user.mobility_flags & MOBILITY_STAND)) + return + + user.set_machine(src) + song.interact(user) + +/obj/item/instrument/proc/start_playing() + return + +/obj/item/instrument/proc/stop_playing() + return + +/obj/item/instrument/violin + name = "space violin" + desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" + icon_state = "violin" + inhand_icon_state = "violin" + hitsound = "swing_hit" + instrumentId = "violin" + +/obj/item/instrument/violin/golden + name = "golden violin" + desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" + icon_state = "golden_violin" + inhand_icon_state = "golden_violin" + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/instrument/piano_synth + name = "synthesizer" + desc = "An advanced electronic synthesizer that can be used as various instruments." + icon_state = "synth" + inhand_icon_state = "synth" + instrumentId = "piano" + instrumentExt = "ogg" + var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "banjo" = "ogg", "guitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "mid", "xylophone" = "mid") //No eguitar you ear-rapey fuckers. + actions_types = list(/datum/action/item_action/synthswitch) + +/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano") + song.instrumentDir = name + song.instrumentExt = insTypes[name] + +/obj/item/instrument/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta + var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in sortList(insTypes) + if(!insTypes[chosen]) + return + return changeInstrument(chosen) + +/obj/item/instrument/piano_synth/headphones + name = "headphones" + desc = "Unce unce unce unce. Boop!" + icon = 'icons/obj/clothing/accessories.dmi' + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + icon_state = "headphones" + inhand_icon_state = "headphones" + slot_flags = ITEM_SLOT_EARS | ITEM_SLOT_HEAD + force = 0 + w_class = WEIGHT_CLASS_SMALL + custom_price = 125 + instrumentRange = 1 + +/obj/item/instrument/piano_synth/headphones/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing) + RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing) + +/obj/item/instrument/piano_synth/headphones/start_playing() + icon_state = "[initial(icon_state)]_on" + update_icon() + +/obj/item/instrument/piano_synth/headphones/stop_playing() + icon_state = "[initial(icon_state)]" + update_icon() + +/obj/item/instrument/piano_synth/headphones/spacepods + name = "\improper Nanotrasen space pods" + desc = "Flex your money, AND ignore what everyone else says, all at once!" + icon_state = "spacepods" + inhand_icon_state = "spacepods" + slot_flags = ITEM_SLOT_EARS + strip_delay = 100 //air pods don't fall out + instrumentRange = 0 //you're paying for quality here + custom_premium_price = 1800 + +/obj/item/instrument/banjo + name = "banjo" + desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings." + icon_state = "banjo" + inhand_icon_state = "banjo" + instrumentExt = "ogg" + attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered") + hitsound = 'sound/weapons/banjoslap.ogg' + instrumentId = "banjo" + +/obj/item/instrument/guitar + name = "guitar" + desc = "It's made out of wood and has bronze strings." + icon_state = "guitar" + inhand_icon_state = "guitar" + instrumentExt = "ogg" + attack_verb = list("played metal on", "serenaded", "crashed", "smashed") + hitsound = 'sound/weapons/stringsmash.ogg' + instrumentId = "guitar" + +/obj/item/instrument/eguitar + name = "electric guitar" + desc = "Makes all your shredding needs possible." + icon_state = "eguitar" + inhand_icon_state = "eguitar" + force = 12 + attack_verb = list("played metal on", "shredded", "crashed", "smashed") + hitsound = 'sound/weapons/stringsmash.ogg' + instrumentId = "eguitar" + instrumentExt = "ogg" + +/obj/item/instrument/glockenspiel + name = "glockenspiel" + desc = "Smooth metal bars perfect for any marching band." + icon_state = "glockenspiel" + inhand_icon_state = "glockenspiel" + instrumentId = "glockenspiel" + +/obj/item/instrument/accordion + name = "accordion" + desc = "Pun-Pun not included." + icon_state = "accordion" + inhand_icon_state = "accordion" + instrumentId = "accordion" + +/obj/item/instrument/trumpet + name = "trumpet" + desc = "To announce the arrival of the king!" + icon_state = "trumpet" + inhand_icon_state = "trumpet" + instrumentId = "trombone" + +/obj/item/instrument/trumpet/spectral + name = "spectral trumpet" + desc = "Things are about to get spooky!" + icon_state = "spectral_trumpet" + inhand_icon_state = "spectral_trumpet" + force = 0 + instrumentId = "trombone" + attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked") + +/obj/item/instrument/trumpet/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user) + playsound (src, 'sound/runtime/instruments/trombone/En4.mid', 100,1,-1) + ..() + +/obj/item/instrument/saxophone + name = "saxophone" + desc = "This soothing sound will be sure to leave your audience in tears." + icon_state = "saxophone" + inhand_icon_state = "saxophone" + instrumentId = "saxophone" + +/obj/item/instrument/saxophone/spectral + name = "spectral saxophone" + desc = "This spooky sound will be sure to leave mortals in bones." + icon_state = "saxophone" + inhand_icon_state = "saxophone" + instrumentId = "saxophone" + force = 0 + attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked") + +/obj/item/instrument/saxophone/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user) + playsound (src, 'sound/runtime/instruments/saxophone/En4.mid', 100,1,-1) + ..() + +/obj/item/instrument/trombone + name = "trombone" + desc = "How can any pool table ever hope to compete?" + icon_state = "trombone" + inhand_icon_state = "trombone" + instrumentId = "trombone" + +/obj/item/instrument/trombone/spectral + name = "spectral trombone" + desc = "A skeleton's favorite instrument. Apply directly on the mortals." + instrumentId = "trombone" + icon_state = "trombone" + inhand_icon_state = "trombone" + force = 0 + attack_verb = list("played","jazzed","tromboned","mourned","dooted","spooked") + +/obj/item/instrument/trombone/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user) + playsound (src, 'sound/runtime/instruments/trombone/Cn4.mid', 100,1,-1) + ..() + +/obj/item/instrument/recorder + name = "recorder" + desc = "Just like in school, playing ability and all." + force = 5 + icon_state = "recorder" + inhand_icon_state = "recorder" + instrumentId = "recorder" + +/obj/item/instrument/harmonica + name = "harmonica" + desc = "For when you get a bad case of the space blues." + icon_state = "harmonica" + inhand_icon_state = "harmonica" + instrumentId = "harmonica" + slot_flags = ITEM_SLOT_MASK + force = 5 + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/instrument) + +/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args) + if(song.playing && ismob(loc)) + to_chat(loc, "You stop playing the harmonica to talk...") + song.playing = FALSE + +/obj/item/instrument/harmonica/equipped(mob/M, slot) + . = ..() + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech, override = TRUE) + +/obj/item/instrument/harmonica/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/instrument/bikehorn + name = "gilded bike horn" + desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes." + icon_state = "bike_horn" + inhand_icon_state = "bike_horn" + lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' + attack_verb = list("beautifully honks") + instrumentId = "bikehorn" + instrumentExt = "ogg" + w_class = WEIGHT_CLASS_TINY + force = 0 + throw_speed = 3 + throw_range = 15 + hitsound = 'sound/items/bikehorn.ogg' + +/// + +/obj/item/choice_beacon/music + name = "instrument delivery beacon" + desc = "Summon your tool of art." + icon_state = "gangtool-red" + +/obj/item/choice_beacon/music/generate_display_names() + var/static/list/instruments + if(!instruments) + instruments = list() + var/list/templist = list(/obj/item/instrument/violin, + /obj/item/instrument/piano_synth, + /obj/item/instrument/banjo, + /obj/item/instrument/guitar, + /obj/item/instrument/eguitar, + /obj/item/instrument/glockenspiel, + /obj/item/instrument/accordion, + /obj/item/instrument/trumpet, + /obj/item/instrument/saxophone, + /obj/item/instrument/trombone, + /obj/item/instrument/recorder, + /obj/item/instrument/harmonica, + /obj/item/instrument/piano_synth/headphones + ) + for(var/V in templist) + var/atom/A = V + instruments[initial(A.name)] = A + return instruments diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 245a044286c..e3d63dc60a9 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -1,200 +1,200 @@ -/obj/item/laser_pointer - name = "laser pointer" - desc = "Don't shine it in your eyes!" - icon = 'icons/obj/device.dmi' - icon_state = "pointer" - inhand_icon_state = "pen" - worn_icon_state = "pen" - var/pointer_icon_state - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) - w_class = WEIGHT_CLASS_SMALL - var/turf/pointer_loc - var/energy = 10 - var/max_energy = 10 - var/effectchance = 30 - var/recharging = FALSE - var/recharge_locked = FALSE - var/obj/item/stock_parts/micro_laser/diode //used for upgrading! - - -/obj/item/laser_pointer/red - pointer_icon_state = "red_laser" -/obj/item/laser_pointer/green - pointer_icon_state = "green_laser" -/obj/item/laser_pointer/blue - pointer_icon_state = "blue_laser" -/obj/item/laser_pointer/purple - pointer_icon_state = "purple_laser" - -/obj/item/laser_pointer/Initialize() - . = ..() - diode = new(src) - if(!pointer_icon_state) - pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") - -/obj/item/laser_pointer/upgraded/Initialize() - . = ..() - diode = new /obj/item/stock_parts/micro_laser/ultra - -/obj/item/laser_pointer/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/micro_laser)) - if(!diode) - if(!user.transferItemToLoc(W, src)) - return - diode = W - to_chat(user, "You install a [diode.name] in [src].") - else - to_chat(user, "[src] already has a diode installed!") - - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(diode) - to_chat(user, "You remove the [diode.name] from \the [src].") - diode.forceMove(drop_location()) - diode = null - else - return ..() - -/obj/item/laser_pointer/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - if(!diode) - . += "The diode is missing." - else - . += "A class [diode.rating] laser diode is installed. It is screwed in place." - -/obj/item/laser_pointer/afterattack(atom/target, mob/living/user, flag, params) - . = ..() - laser_act(target, user, params) - -/obj/item/laser_pointer/proc/laser_act(atom/target, mob/living/user, params) - if( !(user in (viewers(7,target))) ) - return - if (!diode) - to_chat(user, "You point [src] at [target], but nothing happens!") - return - if (!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - if(HAS_TRAIT(user, TRAIT_CHUNKYFINGERS)) - to_chat(user, "Your fingers can't press the button!") - return - add_fingerprint(user) - - //nothing happens if the battery is drained - if(recharge_locked) - to_chat(user, "You point [src] at [target], but it's still charging.") - return - - var/outmsg - var/turf/targloc = get_turf(target) - - //human/alien mobs - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(user.zone_selected == BODY_ZONE_PRECISE_EYES) - - var/severity = 1 - if(prob(33)) - severity = 2 - else if(prob(50)) - severity = 0 - - //chance to actually hit the eyes depends on internal component - if(prob(effectchance * diode.rating) && C.flash_act(severity)) - outmsg = "You blind [C] by shining [src] in [C.p_their()] eyes." - log_combat(user, C, "blinded with a laser pointer",src) - else - outmsg = "You fail to blind [C] by shining [src] at [C.p_their()] eyes!" - log_combat(user, C, "attempted to blind with a laser pointer",src) - - //robots - else if(iscyborg(target)) - var/mob/living/silicon/S = target - log_combat(user, S, "shone in the sensors", src) - //chance to actually hit the eyes depends on internal component - if(prob(effectchance * diode.rating)) - S.flash_act(affect_silicon = 1) - S.Paralyze(rand(100,200)) - to_chat(S, "Your sensors were overloaded by a laser!") - outmsg = "You overload [S] by shining [src] at [S.p_their()] sensors." - else - outmsg = "You fail to overload [S] by shining [src] at [S.p_their()] sensors!" - - //cameras - else if(istype(target, /obj/machinery/camera)) - var/obj/machinery/camera/C = target - if(prob(effectchance * diode.rating)) - C.emp_act(EMP_HEAVY) - outmsg = "You hit the lens of [C] with [src], temporarily disabling the camera!" - log_combat(user, C, "EMPed", src) - else - outmsg = "You miss the lens of [C] with [src]!" - - //catpeople - for(var/mob/living/carbon/human/H in view(1,targloc)) - if(!isfelinid(H) || H.incapacitated() || H.is_blind()) - continue - if(user.mobility_flags & MOBILITY_STAND) - H.setDir(get_dir(H,targloc)) // kitty always looks at the light - if(prob(effectchance * diode.rating)) - H.visible_message("[H] makes a grab for the light!","LIGHT!") - H.Move(targloc) - log_combat(user, H, "moved with a laser pointer",src) - else - H.visible_message("[H] looks briefly distracted by the light.", "You're briefly tempted by the shiny light...") - else - H.visible_message("[H] stares at the light.", "You stare at the light...") - - //cats! - for(var/mob/living/simple_animal/pet/cat/C in view(1,targloc)) - if(prob(effectchance * diode.rating)) - C.visible_message("[C] pounces on the light!","LIGHT!") - C.Move(targloc) - C.set_resting(TRUE, FALSE) - else - C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!") - - //laser pointer image - icon_state = "pointer_[pointer_icon_state]" - var/image/I = image('icons/obj/projectiles.dmi',targloc,pointer_icon_state,10) - var/list/click_params = params2list(params) - if(click_params) - if(click_params["icon-x"]) - I.pixel_x = (text2num(click_params["icon-x"]) - 16) - if(click_params["icon-y"]) - I.pixel_y = (text2num(click_params["icon-y"]) - 16) - else - I.pixel_x = target.pixel_x + rand(-5,5) - I.pixel_y = target.pixel_y + rand(-5,5) - - if(outmsg) - to_chat(user, outmsg) - else - to_chat(user, "You point [src] at [target].") - - energy -= 1 - if(energy <= max_energy) - if(!recharging) - recharging = TRUE - START_PROCESSING(SSobj, src) - if(energy <= 0) - to_chat(user, "[src]'s battery is overused, it needs time to recharge!") - recharge_locked = TRUE - - flick_overlay_view(I, targloc, 10) - icon_state = "pointer" - -/obj/item/laser_pointer/process() - if(!diode) - recharging = FALSE - return PROCESS_KILL - if(prob(20 + diode.rating*20 - recharge_locked*2)) //t1 is 20, 2 40 - energy += 1 - if(energy >= max_energy) - energy = max_energy - recharging = FALSE - recharge_locked = FALSE - return ..() +/obj/item/laser_pointer + name = "laser pointer" + desc = "Don't shine it in your eyes!" + icon = 'icons/obj/device.dmi' + icon_state = "pointer" + inhand_icon_state = "pen" + worn_icon_state = "pen" + var/pointer_icon_state + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) + w_class = WEIGHT_CLASS_SMALL + var/turf/pointer_loc + var/energy = 10 + var/max_energy = 10 + var/effectchance = 30 + var/recharging = FALSE + var/recharge_locked = FALSE + var/obj/item/stock_parts/micro_laser/diode //used for upgrading! + + +/obj/item/laser_pointer/red + pointer_icon_state = "red_laser" +/obj/item/laser_pointer/green + pointer_icon_state = "green_laser" +/obj/item/laser_pointer/blue + pointer_icon_state = "blue_laser" +/obj/item/laser_pointer/purple + pointer_icon_state = "purple_laser" + +/obj/item/laser_pointer/Initialize() + . = ..() + diode = new(src) + if(!pointer_icon_state) + pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") + +/obj/item/laser_pointer/upgraded/Initialize() + . = ..() + diode = new /obj/item/stock_parts/micro_laser/ultra + +/obj/item/laser_pointer/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/micro_laser)) + if(!diode) + if(!user.transferItemToLoc(W, src)) + return + diode = W + to_chat(user, "You install a [diode.name] in [src].") + else + to_chat(user, "[src] already has a diode installed!") + + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(diode) + to_chat(user, "You remove the [diode.name] from \the [src].") + diode.forceMove(drop_location()) + diode = null + else + return ..() + +/obj/item/laser_pointer/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + if(!diode) + . += "The diode is missing." + else + . += "A class [diode.rating] laser diode is installed. It is screwed in place." + +/obj/item/laser_pointer/afterattack(atom/target, mob/living/user, flag, params) + . = ..() + laser_act(target, user, params) + +/obj/item/laser_pointer/proc/laser_act(atom/target, mob/living/user, params) + if( !(user in (viewers(7,target))) ) + return + if (!diode) + to_chat(user, "You point [src] at [target], but nothing happens!") + return + if (!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + if(HAS_TRAIT(user, TRAIT_CHUNKYFINGERS)) + to_chat(user, "Your fingers can't press the button!") + return + add_fingerprint(user) + + //nothing happens if the battery is drained + if(recharge_locked) + to_chat(user, "You point [src] at [target], but it's still charging.") + return + + var/outmsg + var/turf/targloc = get_turf(target) + + //human/alien mobs + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(user.zone_selected == BODY_ZONE_PRECISE_EYES) + + var/severity = 1 + if(prob(33)) + severity = 2 + else if(prob(50)) + severity = 0 + + //chance to actually hit the eyes depends on internal component + if(prob(effectchance * diode.rating) && C.flash_act(severity)) + outmsg = "You blind [C] by shining [src] in [C.p_their()] eyes." + log_combat(user, C, "blinded with a laser pointer",src) + else + outmsg = "You fail to blind [C] by shining [src] at [C.p_their()] eyes!" + log_combat(user, C, "attempted to blind with a laser pointer",src) + + //robots + else if(iscyborg(target)) + var/mob/living/silicon/S = target + log_combat(user, S, "shone in the sensors", src) + //chance to actually hit the eyes depends on internal component + if(prob(effectchance * diode.rating)) + S.flash_act(affect_silicon = 1) + S.Paralyze(rand(100,200)) + to_chat(S, "Your sensors were overloaded by a laser!") + outmsg = "You overload [S] by shining [src] at [S.p_their()] sensors." + else + outmsg = "You fail to overload [S] by shining [src] at [S.p_their()] sensors!" + + //cameras + else if(istype(target, /obj/machinery/camera)) + var/obj/machinery/camera/C = target + if(prob(effectchance * diode.rating)) + C.emp_act(EMP_HEAVY) + outmsg = "You hit the lens of [C] with [src], temporarily disabling the camera!" + log_combat(user, C, "EMPed", src) + else + outmsg = "You miss the lens of [C] with [src]!" + + //catpeople + for(var/mob/living/carbon/human/H in view(1,targloc)) + if(!isfelinid(H) || H.incapacitated() || H.is_blind()) + continue + if(user.mobility_flags & MOBILITY_STAND) + H.setDir(get_dir(H,targloc)) // kitty always looks at the light + if(prob(effectchance * diode.rating)) + H.visible_message("[H] makes a grab for the light!","LIGHT!") + H.Move(targloc) + log_combat(user, H, "moved with a laser pointer",src) + else + H.visible_message("[H] looks briefly distracted by the light.", "You're briefly tempted by the shiny light...") + else + H.visible_message("[H] stares at the light.", "You stare at the light...") + + //cats! + for(var/mob/living/simple_animal/pet/cat/C in view(1,targloc)) + if(prob(effectchance * diode.rating)) + C.visible_message("[C] pounces on the light!","LIGHT!") + C.Move(targloc) + C.set_resting(TRUE, FALSE) + else + C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!") + + //laser pointer image + icon_state = "pointer_[pointer_icon_state]" + var/image/I = image('icons/obj/projectiles.dmi',targloc,pointer_icon_state,10) + var/list/click_params = params2list(params) + if(click_params) + if(click_params["icon-x"]) + I.pixel_x = (text2num(click_params["icon-x"]) - 16) + if(click_params["icon-y"]) + I.pixel_y = (text2num(click_params["icon-y"]) - 16) + else + I.pixel_x = target.pixel_x + rand(-5,5) + I.pixel_y = target.pixel_y + rand(-5,5) + + if(outmsg) + to_chat(user, outmsg) + else + to_chat(user, "You point [src] at [target].") + + energy -= 1 + if(energy <= max_energy) + if(!recharging) + recharging = TRUE + START_PROCESSING(SSobj, src) + if(energy <= 0) + to_chat(user, "[src]'s battery is overused, it needs time to recharge!") + recharge_locked = TRUE + + flick_overlay_view(I, targloc, 10) + icon_state = "pointer" + +/obj/item/laser_pointer/process() + if(!diode) + recharging = FALSE + return PROCESS_KILL + if(prob(20 + diode.rating*20 - recharge_locked*2)) //t1 is 20, 2 40 + energy += 1 + if(energy >= max_energy) + energy = max_energy + recharging = FALSE + recharge_locked = FALSE + return ..() diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index 662fe1e4da3..bb63d926968 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -1,266 +1,266 @@ - -// Light Replacer (LR) -// -// ABOUT THE DEVICE -// -// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will -// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since -// they don't have hands or a way to replace lightbulbs. -// -// HOW IT WORKS -// -// You attack a light fixture with it, if the light fixture is broken it will replace the -// light fixture with a working light; the broken light is then placed on the floor for the -// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. -// -// HOW TO REFILL THE DEVICE -// -// It will need to be manually refilled with lights. -// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. -// -// EMAGGED FEATURES -// -// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. -// -// I'm not sure everyone will react the emag's features so please say what your opinions are of it. -// -// When emagged it will rig every light it replaces, which will explode when the light is on. -// This is VERY noticable, even the device's name changes when you emag it so if anyone -// examines you when you're holding it in your hand, you will be discovered. -// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy -// access to them, and only one of them can emag their device. -// -// The explosion cannot insta-kill anyone with 30% or more health. - -#define LIGHT_OK 0 -#define LIGHT_EMPTY 1 -#define LIGHT_BROKEN 2 -#define LIGHT_BURNED 3 - - -/obj/item/lightreplacer - - name = "light replacer" - desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." - - icon = 'icons/obj/janitor.dmi' - icon_state = "lightreplacer0" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 8 - - var/max_uses = 20 - var/uses = 10 - // How much to increase per each glass? - var/increment = 5 - // How much to take from the glass? - var/decrement = 1 - var/charge = 1 - - // Eating used bulbs gives us bulb shards - var/bulb_shards = 0 - // when we get this many shards, we get a free bulb. - var/shards_required = 4 - -/obj/item/lightreplacer/examine(mob/user) - . = ..() - . += status_string() - -/obj/item/lightreplacer/attackby(obj/item/W, mob/user, params) - - if(istype(W, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = W - if(uses >= max_uses) - to_chat(user, "[src.name] is full.") - return - else if(G.use(decrement)) - AddUses(increment) - to_chat(user, "You insert a piece of glass into \the [src.name]. You have [uses] light\s remaining.") - return - else - to_chat(user, "You need one sheet of glass to replace lights!") - - if(istype(W, /obj/item/shard)) - if(uses >= max_uses) - to_chat(user, "\The [src] is full.") - return - if(!user.temporarilyRemoveItemFromInventory(W)) - return - AddUses(round(increment*0.75)) - to_chat(user, "You insert a shard of glass into \the [src]. You have [uses] light\s remaining.") - qdel(W) - return - - if(istype(W, /obj/item/light)) - var/obj/item/light/L = W - if(L.status == 0) // LIGHT OKAY - if(uses < max_uses) - if(!user.temporarilyRemoveItemFromInventory(W)) - return - AddUses(1) - qdel(L) - else - if(!user.temporarilyRemoveItemFromInventory(W)) - return - to_chat(user, "You insert [L] into \the [src].") - AddShards(1, user) - qdel(L) - return - - if(istype(W, /obj/item/storage)) - var/obj/item/storage/S = W - var/found_lightbulbs = FALSE - var/replaced_something = TRUE - - for(var/obj/item/I in S.contents) - if(istype(I, /obj/item/light)) - var/obj/item/light/L = I - found_lightbulbs = TRUE - if(src.uses >= max_uses) - break - if(L.status == LIGHT_OK) - replaced_something = TRUE - AddUses(1) - qdel(L) - - else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) - replaced_something = TRUE - AddShards(1, user) - qdel(L) - - if(!found_lightbulbs) - to_chat(user, "\The [S] contains no bulbs.") - return - - if(!replaced_something && src.uses == max_uses) - to_chat(user, "\The [src] is full!") - return - - to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "") - -/obj/item/lightreplacer/emag_act() - if(obj_flags & EMAGGED) - return - Emag() - -/obj/item/lightreplacer/attack_self(mob/user) - for(var/obj/machinery/light/target in user.loc) - ReplaceLight(target, user) - to_chat(user, status_string()) - -/obj/item/lightreplacer/update_icon_state() - icon_state = "lightreplacer[(obj_flags & EMAGGED ? 1 : 0)]" - -/obj/item/lightreplacer/proc/status_string() - return "It has [uses] light\s remaining (plus [bulb_shards] fragment\s)." - -/obj/item/lightreplacer/proc/Use(mob/user) - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - AddUses(-1) - return 1 - -// Negative numbers will subtract -/obj/item/lightreplacer/proc/AddUses(amount = 1) - uses = clamp(uses + amount, 0, max_uses) - -/obj/item/lightreplacer/proc/AddShards(amount = 1, user) - bulb_shards += amount - var/new_bulbs = round(bulb_shards / shards_required) - if(new_bulbs > 0) - AddUses(new_bulbs) - bulb_shards = bulb_shards % shards_required - if(new_bulbs != 0) - to_chat(user, "\The [src] fabricates a new bulb from the broken glass it has stored. It now has [uses] uses.") - playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) - return new_bulbs - -/obj/item/lightreplacer/proc/Charge(var/mob/user) - charge += 1 - if(charge > 3) - AddUses(1) - charge = 1 - -/obj/item/lightreplacer/proc/ReplaceLight(obj/machinery/light/target, mob/living/U) - - if(target.status != LIGHT_OK) - if(CanUse(U)) - if(!Use(U)) - return - to_chat(U, "You replace \the [target.fitting] with \the [src].") - - if(target.status != LIGHT_EMPTY) - AddShards(1, U) - target.status = LIGHT_EMPTY - target.update() - - var/obj/item/light/L2 = new target.light_type() - - target.status = L2.status - target.switchcount = L2.switchcount - target.rigged = (obj_flags & EMAGGED ? 1 : 0) - target.brightness = L2.brightness - target.on = target.has_power() - target.update() - qdel(L2) - - if(target.on && target.rigged) - target.explode() - return - - else - to_chat(U, "\The [src]'s refill light blinks red.") - return - else - to_chat(U, "There is a working [target.fitting] already inserted!") - return - -/obj/item/lightreplacer/proc/Emag() - obj_flags ^= EMAGGED - playsound(src.loc, "sparks", 100, TRUE) - if(obj_flags & EMAGGED) - name = "shortcircuited [initial(name)]" - else - name = initial(name) - update_icon() - -/obj/item/lightreplacer/proc/CanUse(mob/living/user) - src.add_fingerprint(user) - if(uses > 0) - return 1 - else - return 0 - -/obj/item/lightreplacer/afterattack(atom/T, mob/U, proximity) - . = ..() - if(!proximity) - return - if(!isturf(T)) - return - - var/used = FALSE - for(var/atom/A in T) - if(!CanUse(U)) - break - used = TRUE - if(istype(A, /obj/machinery/light)) - ReplaceLight(A, U) - - if(!used) - to_chat(U, "\The [src]'s refill light blinks red.") - -/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - J.put_in_cart(src, user) - J.myreplacer = src - J.update_icon() - -/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) - return - -#undef LIGHT_OK -#undef LIGHT_EMPTY -#undef LIGHT_BROKEN -#undef LIGHT_BURNED + +// Light Replacer (LR) +// +// ABOUT THE DEVICE +// +// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will +// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since +// they don't have hands or a way to replace lightbulbs. +// +// HOW IT WORKS +// +// You attack a light fixture with it, if the light fixture is broken it will replace the +// light fixture with a working light; the broken light is then placed on the floor for the +// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. +// +// HOW TO REFILL THE DEVICE +// +// It will need to be manually refilled with lights. +// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. +// +// EMAGGED FEATURES +// +// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. +// +// I'm not sure everyone will react the emag's features so please say what your opinions are of it. +// +// When emagged it will rig every light it replaces, which will explode when the light is on. +// This is VERY noticable, even the device's name changes when you emag it so if anyone +// examines you when you're holding it in your hand, you will be discovered. +// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy +// access to them, and only one of them can emag their device. +// +// The explosion cannot insta-kill anyone with 30% or more health. + +#define LIGHT_OK 0 +#define LIGHT_EMPTY 1 +#define LIGHT_BROKEN 2 +#define LIGHT_BURNED 3 + + +/obj/item/lightreplacer + + name = "light replacer" + desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." + + icon = 'icons/obj/janitor.dmi' + icon_state = "lightreplacer0" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 8 + + var/max_uses = 20 + var/uses = 10 + // How much to increase per each glass? + var/increment = 5 + // How much to take from the glass? + var/decrement = 1 + var/charge = 1 + + // Eating used bulbs gives us bulb shards + var/bulb_shards = 0 + // when we get this many shards, we get a free bulb. + var/shards_required = 4 + +/obj/item/lightreplacer/examine(mob/user) + . = ..() + . += status_string() + +/obj/item/lightreplacer/attackby(obj/item/W, mob/user, params) + + if(istype(W, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = W + if(uses >= max_uses) + to_chat(user, "[src.name] is full.") + return + else if(G.use(decrement)) + AddUses(increment) + to_chat(user, "You insert a piece of glass into \the [src.name]. You have [uses] light\s remaining.") + return + else + to_chat(user, "You need one sheet of glass to replace lights!") + + if(istype(W, /obj/item/shard)) + if(uses >= max_uses) + to_chat(user, "\The [src] is full.") + return + if(!user.temporarilyRemoveItemFromInventory(W)) + return + AddUses(round(increment*0.75)) + to_chat(user, "You insert a shard of glass into \the [src]. You have [uses] light\s remaining.") + qdel(W) + return + + if(istype(W, /obj/item/light)) + var/obj/item/light/L = W + if(L.status == 0) // LIGHT OKAY + if(uses < max_uses) + if(!user.temporarilyRemoveItemFromInventory(W)) + return + AddUses(1) + qdel(L) + else + if(!user.temporarilyRemoveItemFromInventory(W)) + return + to_chat(user, "You insert [L] into \the [src].") + AddShards(1, user) + qdel(L) + return + + if(istype(W, /obj/item/storage)) + var/obj/item/storage/S = W + var/found_lightbulbs = FALSE + var/replaced_something = TRUE + + for(var/obj/item/I in S.contents) + if(istype(I, /obj/item/light)) + var/obj/item/light/L = I + found_lightbulbs = TRUE + if(src.uses >= max_uses) + break + if(L.status == LIGHT_OK) + replaced_something = TRUE + AddUses(1) + qdel(L) + + else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) + replaced_something = TRUE + AddShards(1, user) + qdel(L) + + if(!found_lightbulbs) + to_chat(user, "\The [S] contains no bulbs.") + return + + if(!replaced_something && src.uses == max_uses) + to_chat(user, "\The [src] is full!") + return + + to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "") + +/obj/item/lightreplacer/emag_act() + if(obj_flags & EMAGGED) + return + Emag() + +/obj/item/lightreplacer/attack_self(mob/user) + for(var/obj/machinery/light/target in user.loc) + ReplaceLight(target, user) + to_chat(user, status_string()) + +/obj/item/lightreplacer/update_icon_state() + icon_state = "lightreplacer[(obj_flags & EMAGGED ? 1 : 0)]" + +/obj/item/lightreplacer/proc/status_string() + return "It has [uses] light\s remaining (plus [bulb_shards] fragment\s)." + +/obj/item/lightreplacer/proc/Use(mob/user) + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + AddUses(-1) + return 1 + +// Negative numbers will subtract +/obj/item/lightreplacer/proc/AddUses(amount = 1) + uses = clamp(uses + amount, 0, max_uses) + +/obj/item/lightreplacer/proc/AddShards(amount = 1, user) + bulb_shards += amount + var/new_bulbs = round(bulb_shards / shards_required) + if(new_bulbs > 0) + AddUses(new_bulbs) + bulb_shards = bulb_shards % shards_required + if(new_bulbs != 0) + to_chat(user, "\The [src] fabricates a new bulb from the broken glass it has stored. It now has [uses] uses.") + playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) + return new_bulbs + +/obj/item/lightreplacer/proc/Charge(var/mob/user) + charge += 1 + if(charge > 3) + AddUses(1) + charge = 1 + +/obj/item/lightreplacer/proc/ReplaceLight(obj/machinery/light/target, mob/living/U) + + if(target.status != LIGHT_OK) + if(CanUse(U)) + if(!Use(U)) + return + to_chat(U, "You replace \the [target.fitting] with \the [src].") + + if(target.status != LIGHT_EMPTY) + AddShards(1, U) + target.status = LIGHT_EMPTY + target.update() + + var/obj/item/light/L2 = new target.light_type() + + target.status = L2.status + target.switchcount = L2.switchcount + target.rigged = (obj_flags & EMAGGED ? 1 : 0) + target.brightness = L2.brightness + target.on = target.has_power() + target.update() + qdel(L2) + + if(target.on && target.rigged) + target.explode() + return + + else + to_chat(U, "\The [src]'s refill light blinks red.") + return + else + to_chat(U, "There is a working [target.fitting] already inserted!") + return + +/obj/item/lightreplacer/proc/Emag() + obj_flags ^= EMAGGED + playsound(src.loc, "sparks", 100, TRUE) + if(obj_flags & EMAGGED) + name = "shortcircuited [initial(name)]" + else + name = initial(name) + update_icon() + +/obj/item/lightreplacer/proc/CanUse(mob/living/user) + src.add_fingerprint(user) + if(uses > 0) + return 1 + else + return 0 + +/obj/item/lightreplacer/afterattack(atom/T, mob/U, proximity) + . = ..() + if(!proximity) + return + if(!isturf(T)) + return + + var/used = FALSE + for(var/atom/A in T) + if(!CanUse(U)) + break + used = TRUE + if(istype(A, /obj/machinery/light)) + ReplaceLight(A, U) + + if(!used) + to_chat(U, "\The [src]'s refill light blinks red.") + +/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + J.put_in_cart(src, user) + J.myreplacer = src + J.update_icon() + +/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) + return + +#undef LIGHT_OK +#undef LIGHT_EMPTY +#undef LIGHT_BROKEN +#undef LIGHT_BURNED diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index c109a2f39a2..a1cb7b1abc0 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -1,176 +1,176 @@ -#define PROXIMITY_NONE "" -#define PROXIMITY_ON_SCREEN "_red" -#define PROXIMITY_NEAR "_yellow" - -/** - * Multitool -- A multitool is used for hacking electronic devices. - * - */ - - - - -/obj/item/multitool - name = "multitool" - desc = "Used for pulsing wires to test which to cut. Not recommended by doctors." - icon = 'icons/obj/device.dmi' - icon_state = "multitool" - inhand_icon_state = "multitool" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - force = 5 - w_class = WEIGHT_CLASS_SMALL - tool_behaviour = TOOL_MULTITOOL - throwforce = 0 - throw_range = 7 - throw_speed = 3 - drop_sound = 'sound/items/handling/multitool_drop.ogg' - pickup_sound = 'sound/items/handling/multitool_pickup.ogg' - custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) - custom_premium_price = 450 - toolspeed = 1 - usesound = 'sound/weapons/empty.ogg' - var/obj/machinery/buffer // simple machine buffer for device linkage - var/mode = 0 - -/obj/item/multitool/examine(mob/user) - . = ..() - . += "Its buffer [buffer ? "contains [buffer]." : "is empty."]" - -/obj/item/multitool/suicide_act(mob/living/carbon/user) - user.visible_message("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!") - return OXYLOSS//theres a reason it wasn't recommended by doctors - - -// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. - -/obj/item/multitool/ai_detect - var/track_cooldown = 0 - var/track_delay = 10 //How often it checks for proximity - var/detect_state = PROXIMITY_NONE - var/rangealert = 8 //Glows red when inside - var/rangewarning = 20 //Glows yellow when inside - var/hud_type = DATA_HUD_AI_DETECT - var/hud_on = FALSE - var/mob/camera/ai_eye/remote/ai_detector/eye - var/datum/action/item_action/toggle_multitool/toggle_action - -/obj/item/multitool/ai_detect/Initialize() - . = ..() - START_PROCESSING(SSobj, src) - eye = new /mob/camera/ai_eye/remote/ai_detector() - toggle_action = new /datum/action/item_action/toggle_multitool(src) - -/obj/item/multitool/ai_detect/Destroy() - STOP_PROCESSING(SSobj, src) - if(hud_on && ismob(loc)) - remove_hud(loc) - QDEL_NULL(toggle_action) - QDEL_NULL(eye) - return ..() - -/obj/item/multitool/ai_detect/ui_action_click() - return - -/obj/item/multitool/ai_detect/equipped(mob/living/carbon/human/user, slot) - ..() - if(hud_on) - show_hud(user) - -/obj/item/multitool/ai_detect/dropped(mob/living/carbon/human/user) - ..() - if(hud_on) - remove_hud(user) - -/obj/item/multitool/ai_detect/process() - if(track_cooldown > world.time) - return - detect_state = PROXIMITY_NONE - if(eye.eye_user) - eye.setLoc(get_turf(src)) - multitool_detect() - update_icon() - track_cooldown = world.time + track_delay - -/obj/item/multitool/ai_detect/proc/toggle_hud(mob/user) - hud_on = !hud_on - if(user) - to_chat(user, "You toggle the ai detection HUD on [src] [hud_on ? "on" : "off"].") - if(hud_on) - show_hud(user) - else - remove_hud(user) - -/obj/item/multitool/ai_detect/proc/show_hud(mob/user) - if(user && hud_type) - var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] - PM.alpha = 150 - var/datum/atom_hud/H = GLOB.huds[hud_type] - if(!H.hudusers[user]) - H.add_hud_to(user) - eye.eye_user = user - eye.setLoc(get_turf(src)) - -/obj/item/multitool/ai_detect/proc/remove_hud(mob/user) - if(user && hud_type) - var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] - PM.alpha = 255 - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) - if(eye) - eye.setLoc(null) - eye.eye_user = null - -/obj/item/multitool/ai_detect/proc/multitool_detect() - var/turf/our_turf = get_turf(src) - for(var/mob/living/silicon/ai/AI in GLOB.ai_list) - if(AI.cameraFollow == src) - detect_state = PROXIMITY_ON_SCREEN - break - - if(detect_state) - return - var/datum/camerachunk/chunk = GLOB.cameranet.chunkGenerated(our_turf.x, our_turf.y, our_turf.z) - if(chunk && chunk.seenby.len) - for(var/mob/camera/ai_eye/A in chunk.seenby) - if(!A.ai_detector_visible) - continue - var/turf/detect_turf = get_turf(A) - if(get_dist(our_turf, detect_turf) < rangealert) - detect_state = PROXIMITY_ON_SCREEN - break - if(get_dist(our_turf, detect_turf) < rangewarning) - detect_state = PROXIMITY_NEAR - break - -/mob/camera/ai_eye/remote/ai_detector - name = "AI detector eye" - ai_detector_visible = FALSE - use_static = USE_STATIC_TRANSPARENT - visible_icon = FALSE - -/datum/action/item_action/toggle_multitool - name = "Toggle AI detector HUD" - check_flags = NONE - -/datum/action/item_action/toggle_multitool/Trigger() - if(!..()) - return 0 - if(target) - var/obj/item/multitool/ai_detect/M = target - M.toggle_hud(owner) - return 1 - -/obj/item/multitool/abductor - name = "alien multitool" - desc = "An omni-technological interface." - icon = 'icons/obj/abductor.dmi' - icon_state = "multitool" - toolspeed = 0.1 - -/obj/item/multitool/cyborg - name = "electronic multitool" - desc = "Optimised version of a regular multitool. Streamlines processes handled by its internal microchip." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "multitool_cyborg" - toolspeed = 0.5 +#define PROXIMITY_NONE "" +#define PROXIMITY_ON_SCREEN "_red" +#define PROXIMITY_NEAR "_yellow" + +/** + * Multitool -- A multitool is used for hacking electronic devices. + * + */ + + + + +/obj/item/multitool + name = "multitool" + desc = "Used for pulsing wires to test which to cut. Not recommended by doctors." + icon = 'icons/obj/device.dmi' + icon_state = "multitool" + inhand_icon_state = "multitool" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + force = 5 + w_class = WEIGHT_CLASS_SMALL + tool_behaviour = TOOL_MULTITOOL + throwforce = 0 + throw_range = 7 + throw_speed = 3 + drop_sound = 'sound/items/handling/multitool_drop.ogg' + pickup_sound = 'sound/items/handling/multitool_pickup.ogg' + custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) + custom_premium_price = 450 + toolspeed = 1 + usesound = 'sound/weapons/empty.ogg' + var/obj/machinery/buffer // simple machine buffer for device linkage + var/mode = 0 + +/obj/item/multitool/examine(mob/user) + . = ..() + . += "Its buffer [buffer ? "contains [buffer]." : "is empty."]" + +/obj/item/multitool/suicide_act(mob/living/carbon/user) + user.visible_message("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!") + return OXYLOSS//theres a reason it wasn't recommended by doctors + + +// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. + +/obj/item/multitool/ai_detect + var/track_cooldown = 0 + var/track_delay = 10 //How often it checks for proximity + var/detect_state = PROXIMITY_NONE + var/rangealert = 8 //Glows red when inside + var/rangewarning = 20 //Glows yellow when inside + var/hud_type = DATA_HUD_AI_DETECT + var/hud_on = FALSE + var/mob/camera/ai_eye/remote/ai_detector/eye + var/datum/action/item_action/toggle_multitool/toggle_action + +/obj/item/multitool/ai_detect/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + eye = new /mob/camera/ai_eye/remote/ai_detector() + toggle_action = new /datum/action/item_action/toggle_multitool(src) + +/obj/item/multitool/ai_detect/Destroy() + STOP_PROCESSING(SSobj, src) + if(hud_on && ismob(loc)) + remove_hud(loc) + QDEL_NULL(toggle_action) + QDEL_NULL(eye) + return ..() + +/obj/item/multitool/ai_detect/ui_action_click() + return + +/obj/item/multitool/ai_detect/equipped(mob/living/carbon/human/user, slot) + ..() + if(hud_on) + show_hud(user) + +/obj/item/multitool/ai_detect/dropped(mob/living/carbon/human/user) + ..() + if(hud_on) + remove_hud(user) + +/obj/item/multitool/ai_detect/process() + if(track_cooldown > world.time) + return + detect_state = PROXIMITY_NONE + if(eye.eye_user) + eye.setLoc(get_turf(src)) + multitool_detect() + update_icon() + track_cooldown = world.time + track_delay + +/obj/item/multitool/ai_detect/proc/toggle_hud(mob/user) + hud_on = !hud_on + if(user) + to_chat(user, "You toggle the ai detection HUD on [src] [hud_on ? "on" : "off"].") + if(hud_on) + show_hud(user) + else + remove_hud(user) + +/obj/item/multitool/ai_detect/proc/show_hud(mob/user) + if(user && hud_type) + var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] + PM.alpha = 150 + var/datum/atom_hud/H = GLOB.huds[hud_type] + if(!H.hudusers[user]) + H.add_hud_to(user) + eye.eye_user = user + eye.setLoc(get_turf(src)) + +/obj/item/multitool/ai_detect/proc/remove_hud(mob/user) + if(user && hud_type) + var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] + PM.alpha = 255 + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + if(eye) + eye.setLoc(null) + eye.eye_user = null + +/obj/item/multitool/ai_detect/proc/multitool_detect() + var/turf/our_turf = get_turf(src) + for(var/mob/living/silicon/ai/AI in GLOB.ai_list) + if(AI.cameraFollow == src) + detect_state = PROXIMITY_ON_SCREEN + break + + if(detect_state) + return + var/datum/camerachunk/chunk = GLOB.cameranet.chunkGenerated(our_turf.x, our_turf.y, our_turf.z) + if(chunk && chunk.seenby.len) + for(var/mob/camera/ai_eye/A in chunk.seenby) + if(!A.ai_detector_visible) + continue + var/turf/detect_turf = get_turf(A) + if(get_dist(our_turf, detect_turf) < rangealert) + detect_state = PROXIMITY_ON_SCREEN + break + if(get_dist(our_turf, detect_turf) < rangewarning) + detect_state = PROXIMITY_NEAR + break + +/mob/camera/ai_eye/remote/ai_detector + name = "AI detector eye" + ai_detector_visible = FALSE + use_static = USE_STATIC_TRANSPARENT + visible_icon = FALSE + +/datum/action/item_action/toggle_multitool + name = "Toggle AI detector HUD" + check_flags = NONE + +/datum/action/item_action/toggle_multitool/Trigger() + if(!..()) + return 0 + if(target) + var/obj/item/multitool/ai_detect/M = target + M.toggle_hud(owner) + return 1 + +/obj/item/multitool/abductor + name = "alien multitool" + desc = "An omni-technological interface." + icon = 'icons/obj/abductor.dmi' + icon_state = "multitool" + toolspeed = 0.1 + +/obj/item/multitool/cyborg + name = "electronic multitool" + desc = "Optimised version of a regular multitool. Streamlines processes handled by its internal microchip." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "multitool_cyborg" + toolspeed = 0.5 diff --git a/code/game/objects/items/devices/ocd.dm b/code/game/objects/items/devices/ocd.dm index 52bb7352c88..50032810abd 100644 --- a/code/game/objects/items/devices/ocd.dm +++ b/code/game/objects/items/devices/ocd.dm @@ -1,11 +1,11 @@ -/obj/item/devices/ocd_device - name = "Occupational Corruption Device" - desc = "When you need to make the lives of new-hires that much more confusing, think OCD." - icon = 'icons/obj/device.dmi' - icon_state = "gangtool-white" - -/obj/item/devices/ocd_device/attack_self(mob/user) - var/datum/round_event/bureaucratic_error/event = new() - event.start() - deadchat_broadcast(" An OCD has been activated! ") - qdel(src) +/obj/item/devices/ocd_device + name = "Occupational Corruption Device" + desc = "When you need to make the lives of new-hires that much more confusing, think OCD." + icon = 'icons/obj/device.dmi' + icon_state = "gangtool-white" + +/obj/item/devices/ocd_device/attack_self(mob/user) + var/datum/round_event/bureaucratic_error/event = new() + event.start() + deadchat_broadcast(" An OCD has been activated! ") + qdel(src) diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index f469d020290..cde3702087e 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -1,175 +1,175 @@ -/obj/item/paicard - name = "personal AI device" - icon = 'icons/obj/aicards.dmi' - icon_state = "pai" - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - var/mob/living/silicon/pai/pai - resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE - -/obj/item/paicard/suicide_act(mob/living/user) - user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!") - return OXYLOSS - -/obj/item/paicard/Initialize() - SSpai.pai_card_list += src - add_overlay("pai-off") - return ..() - -/obj/item/paicard/Destroy() - //Will stop people throwing friend pAIs into the singularity so they can respawn - SSpai.pai_card_list -= src - if (!QDELETED(pai)) - QDEL_NULL(pai) - return ..() - -/obj/item/paicard/attack_self(mob/user) - if (!in_range(src, user)) - return - user.set_machine(src) - var/dat = "Personal AI Device
                " - if(pai) - if(!pai.master_dna || !pai.master) - dat += "Imprint Master DNA
                " - dat += "Installed Personality: [pai.name]
                " - dat += "Prime directive:
                [pai.laws.zeroth]
                " - for(var/slaws in pai.laws.supplied) - dat += "Additional directives:
                [slaws]
                " - dat += "Configure Directives
                " - dat += "
                " - dat += "

                Device Settings


                " - if(pai.radio) - dat += "Radio Uplink
                " - dat += "Transmit: \[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]
                " - dat += "Receive: \[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]
                " - else - dat += "Radio Uplink
                " - dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
                " - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna) - dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]
                " - dat += "\[Reset speech synthesis module\]
                " - dat += "\[Wipe current pAI personality\]
                " - else - dat += "No personality installed.
                " - dat += "Searching for a personality... Press view available personalities to notify potential candidates." - dat += "\[View available personalities\]
                " - user << browse(dat, "window=paicard") - onclose(user, "paicard") - return - -/obj/item/paicard/Topic(href, href_list) - - if(!usr || usr.stat) - return - - if(href_list["request"]) - SSpai.findPAI(src, usr) - - if(pai) - if(!(loc == usr)) - return - if(href_list["setdna"]) - if(pai.master_dna) - return - if(!iscarbon(usr)) - to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device!") - else - var/mob/living/carbon/M = usr - pai.master = M.real_name - pai.master_dna = M.dna.unique_enzymes - to_chat(pai, "You have been bound to a new master.") - pai.emittersemicd = FALSE - if(href_list["wipe"]) - var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") - if(confirm == "Yes") - if(pai) - to_chat(pai, "You feel yourself slipping away from reality.") - to_chat(pai, "Byte by byte you lose your sense of self.") - to_chat(pai, "Your mental faculties leave you.") - to_chat(pai, "oblivion... ") - qdel(pai) - if(href_list["fix_speech"]) - pai.stuttering = 0 - pai.slurring = 0 - pai.derpspeech = 0 - if(href_list["toggle_transmit"] || href_list["toggle_receive"]) - var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving. - var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) - if(transmitting) - pai.can_transmit = !pai.can_transmit - else //receiving - pai.can_receive = !pai.can_receive - pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states - transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun! - to_chat(usr,"You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!") - to_chat(pai,"Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!") - if(href_list["setlaws"]) - var/newlaws = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN) - if(newlaws && pai) - pai.add_supplied_law(0,newlaws) - if(href_list["toggle_holo"]) - if(pai.canholo) - to_chat(pai, "Your owner has disabled your holomatrix projectors!") - pai.canholo = FALSE - to_chat(usr, "You disable your pAI's holomatrix!") - else - to_chat(pai, "Your owner has enabled your holomatrix projectors!") - pai.canholo = TRUE - to_chat(usr, "You enable your pAI's holomatrix!") - - attack_self(usr) - -// WIRE_SIGNAL = 1 -// WIRE_RECEIVE = 2 -// WIRE_TRANSMIT = 4 - -/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) - src.pai = personality - src.add_overlay("pai-null") - - playsound(loc, 'sound/effects/pai_boot.ogg', 50, TRUE, -1) - audible_message("\The [src] plays a cheerful startup noise!") - -/obj/item/paicard/proc/setEmotion(emotion) - if(pai) - src.cut_overlays() - switch(emotion) - if(1) - src.add_overlay("pai-happy") - if(2) - src.add_overlay("pai-cat") - if(3) - src.add_overlay("pai-extremely-happy") - if(4) - src.add_overlay("pai-face") - if(5) - src.add_overlay("pai-laugh") - if(6) - src.add_overlay("pai-off") - if(7) - src.add_overlay("pai-sad") - if(8) - src.add_overlay("pai-angry") - if(9) - src.add_overlay("pai-what") - if(10) - src.add_overlay("pai-null") - if(11) - src.add_overlay("pai-sunglasses") - -/obj/item/paicard/proc/alertUpdate() - audible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] vibrates with an alert.") - -/obj/item/paicard/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(pai && !pai.holoform) - pai.emp_act(severity) - +/obj/item/paicard + name = "personal AI device" + icon = 'icons/obj/aicards.dmi' + icon_state = "pai" + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + var/mob/living/silicon/pai/pai + resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE + +/obj/item/paicard/suicide_act(mob/living/user) + user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!") + return OXYLOSS + +/obj/item/paicard/Initialize() + SSpai.pai_card_list += src + add_overlay("pai-off") + return ..() + +/obj/item/paicard/Destroy() + //Will stop people throwing friend pAIs into the singularity so they can respawn + SSpai.pai_card_list -= src + if (!QDELETED(pai)) + QDEL_NULL(pai) + return ..() + +/obj/item/paicard/attack_self(mob/user) + if (!in_range(src, user)) + return + user.set_machine(src) + var/dat = "Personal AI Device
                " + if(pai) + if(!pai.master_dna || !pai.master) + dat += "Imprint Master DNA
                " + dat += "Installed Personality: [pai.name]
                " + dat += "Prime directive:
                [pai.laws.zeroth]
                " + for(var/slaws in pai.laws.supplied) + dat += "Additional directives:
                [slaws]
                " + dat += "Configure Directives
                " + dat += "
                " + dat += "

                Device Settings


                " + if(pai.radio) + dat += "Radio Uplink
                " + dat += "Transmit: \[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]
                " + dat += "Receive: \[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]
                " + else + dat += "Radio Uplink
                " + dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
                " + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna) + dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]
                " + dat += "\[Reset speech synthesis module\]
                " + dat += "\[Wipe current pAI personality\]
                " + else + dat += "No personality installed.
                " + dat += "Searching for a personality... Press view available personalities to notify potential candidates." + dat += "\[View available personalities\]
                " + user << browse(dat, "window=paicard") + onclose(user, "paicard") + return + +/obj/item/paicard/Topic(href, href_list) + + if(!usr || usr.stat) + return + + if(href_list["request"]) + SSpai.findPAI(src, usr) + + if(pai) + if(!(loc == usr)) + return + if(href_list["setdna"]) + if(pai.master_dna) + return + if(!iscarbon(usr)) + to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device!") + else + var/mob/living/carbon/M = usr + pai.master = M.real_name + pai.master_dna = M.dna.unique_enzymes + to_chat(pai, "You have been bound to a new master.") + pai.emittersemicd = FALSE + if(href_list["wipe"]) + var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") + if(confirm == "Yes") + if(pai) + to_chat(pai, "You feel yourself slipping away from reality.") + to_chat(pai, "Byte by byte you lose your sense of self.") + to_chat(pai, "Your mental faculties leave you.") + to_chat(pai, "oblivion... ") + qdel(pai) + if(href_list["fix_speech"]) + pai.stuttering = 0 + pai.slurring = 0 + pai.derpspeech = 0 + if(href_list["toggle_transmit"] || href_list["toggle_receive"]) + var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving. + var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) + if(transmitting) + pai.can_transmit = !pai.can_transmit + else //receiving + pai.can_receive = !pai.can_receive + pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states + transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun! + to_chat(usr,"You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!") + to_chat(pai,"Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!") + if(href_list["setlaws"]) + var/newlaws = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN) + if(newlaws && pai) + pai.add_supplied_law(0,newlaws) + if(href_list["toggle_holo"]) + if(pai.canholo) + to_chat(pai, "Your owner has disabled your holomatrix projectors!") + pai.canholo = FALSE + to_chat(usr, "You disable your pAI's holomatrix!") + else + to_chat(pai, "Your owner has enabled your holomatrix projectors!") + pai.canholo = TRUE + to_chat(usr, "You enable your pAI's holomatrix!") + + attack_self(usr) + +// WIRE_SIGNAL = 1 +// WIRE_RECEIVE = 2 +// WIRE_TRANSMIT = 4 + +/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) + src.pai = personality + src.add_overlay("pai-null") + + playsound(loc, 'sound/effects/pai_boot.ogg', 50, TRUE, -1) + audible_message("\The [src] plays a cheerful startup noise!") + +/obj/item/paicard/proc/setEmotion(emotion) + if(pai) + src.cut_overlays() + switch(emotion) + if(1) + src.add_overlay("pai-happy") + if(2) + src.add_overlay("pai-cat") + if(3) + src.add_overlay("pai-extremely-happy") + if(4) + src.add_overlay("pai-face") + if(5) + src.add_overlay("pai-laugh") + if(6) + src.add_overlay("pai-off") + if(7) + src.add_overlay("pai-sad") + if(8) + src.add_overlay("pai-angry") + if(9) + src.add_overlay("pai-what") + if(10) + src.add_overlay("pai-null") + if(11) + src.add_overlay("pai-sunglasses") + +/obj/item/paicard/proc/alertUpdate() + audible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] vibrates with an alert.") + +/obj/item/paicard/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(pai && !pai.holoform) + pai.emp_act(severity) + diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm index 9cffc004323..ce424c3076e 100644 --- a/code/game/objects/items/devices/powersink.dm +++ b/code/game/objects/items/devices/powersink.dm @@ -1,164 +1,164 @@ -#define DISCONNECTED 0 -#define CLAMPED_OFF 1 -#define OPERATING 2 - -// Powersink - used to drain station power - -/obj/item/powersink - name = "power sink" - desc = "A nulling power sink which drains energy from electrical systems." - icon = 'icons/obj/device.dmi' - icon_state = "powersink0" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - throwforce = 5 - throw_speed = 1 - throw_range = 2 - custom_materials = list(/datum/material/iron=750) - var/drain_rate = 2000000 // amount of power to drain per tick - var/power_drained = 0 // has drained this much power - var/max_power = 6e8 // maximum power that can be drained before exploding - var/mode = 0 // 0 = off, 1=clamped (off), 2=operating - var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom - - var/obj/structure/cable/attached // the attached cable - -/obj/item/powersink/update_icon_state() - icon_state = "powersink[mode == OPERATING]" - -/obj/item/powersink/proc/set_mode(value) - if(value == mode) - return - switch(value) - if(DISCONNECTED) - attached = null - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = FALSE - density = FALSE - - if(CLAMPED_OFF) - if(!attached) - return - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - if(OPERATING) - if(!attached) - return - START_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - mode = value - update_icon() - set_light(0) - -/obj/item/powersink/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - if(mode == DISCONNECTED) - var/turf/T = loc - if(isturf(T) && !T.intact) - attached = locate() in T - if(!attached) - to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") - else - set_mode(CLAMPED_OFF) - user.visible_message( \ - "[user] attaches \the [src] to the cable.", \ - "You bolt \the [src] into the floor and connect it to the cable.", - "You hear some wires being connected to something.") - else - to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") - else - set_mode(DISCONNECTED) - user.visible_message( \ - "[user] detaches \the [src] from the cable.", \ - "You unbolt \the [src] from the floor and detach it from the cable.", - "You hear some wires being disconnected from something.") - - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message( \ - "[user] messes with \the [src] for a bit.", \ - "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") - else - return ..() - -/obj/item/powersink/attack_paw() - return - -/obj/item/powersink/attack_ai() - return - -/obj/item/powersink/attack_hand(mob/user) - . = ..() - if(.) - return - switch(mode) - if(DISCONNECTED) - ..() - - if(CLAMPED_OFF) - user.visible_message( \ - "[user] activates \the [src]!", \ - "You activate \the [src].", - "You hear a click.") - message_admins("Power sink activated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(src)]") - log_game("Power sink activated by [key_name(user)] at [AREACOORD(src)]") - set_mode(OPERATING) - - if(OPERATING) - user.visible_message( \ - "[user] deactivates \the [src]!", \ - "You deactivate \the [src].", - "You hear a click.") - set_mode(CLAMPED_OFF) - -/obj/item/powersink/process() - if(!attached) - set_mode(DISCONNECTED) - return - - var/datum/powernet/PN = attached.powernet - if(PN) - set_light(5) - - // found a powernet, so drain up to max power from it - - var/drained = min ( drain_rate, attached.newavail() ) - attached.add_delayedload(drained) - power_drained += drained - - // if tried to drain more than available on powernet - // now look for APCs and drain their cells - if(drained < drain_rate) - for(var/obj/machinery/power/terminal/T in PN.nodes) - if(istype(T.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = T.master - if(A.operating && A.cell) - A.cell.charge = max(0, A.cell.charge - 50) - power_drained += 50 - if(A.charging == 2) // If the cell was full - A.charging = 1 // It's no longer full - if(drained >= drain_rate) - break - - if(power_drained > max_power * 0.98) - if (!admins_warned) - admins_warned = TRUE - message_admins("Power sink at ([x],[y],[z] - JMP) is 95% full. Explosion imminent.") - playsound(src, 'sound/effects/screech.ogg', 100, TRUE, TRUE) - - if(power_drained >= max_power) - STOP_PROCESSING(SSobj, src) - explosion(src.loc, 4,8,16,32) - qdel(src) - -#undef DISCONNECTED -#undef CLAMPED_OFF -#undef OPERATING +#define DISCONNECTED 0 +#define CLAMPED_OFF 1 +#define OPERATING 2 + +// Powersink - used to drain station power + +/obj/item/powersink + name = "power sink" + desc = "A nulling power sink which drains energy from electrical systems." + icon = 'icons/obj/device.dmi' + icon_state = "powersink0" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + throwforce = 5 + throw_speed = 1 + throw_range = 2 + custom_materials = list(/datum/material/iron=750) + var/drain_rate = 2000000 // amount of power to drain per tick + var/power_drained = 0 // has drained this much power + var/max_power = 6e8 // maximum power that can be drained before exploding + var/mode = 0 // 0 = off, 1=clamped (off), 2=operating + var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom + + var/obj/structure/cable/attached // the attached cable + +/obj/item/powersink/update_icon_state() + icon_state = "powersink[mode == OPERATING]" + +/obj/item/powersink/proc/set_mode(value) + if(value == mode) + return + switch(value) + if(DISCONNECTED) + attached = null + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = FALSE + density = FALSE + + if(CLAMPED_OFF) + if(!attached) + return + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + if(OPERATING) + if(!attached) + return + START_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + mode = value + update_icon() + set_light(0) + +/obj/item/powersink/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + if(mode == DISCONNECTED) + var/turf/T = loc + if(isturf(T) && !T.intact) + attached = locate() in T + if(!attached) + to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") + else + set_mode(CLAMPED_OFF) + user.visible_message( \ + "[user] attaches \the [src] to the cable.", \ + "You bolt \the [src] into the floor and connect it to the cable.", + "You hear some wires being connected to something.") + else + to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") + else + set_mode(DISCONNECTED) + user.visible_message( \ + "[user] detaches \the [src] from the cable.", \ + "You unbolt \the [src] from the floor and detach it from the cable.", + "You hear some wires being disconnected from something.") + + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message( \ + "[user] messes with \the [src] for a bit.", \ + "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") + else + return ..() + +/obj/item/powersink/attack_paw() + return + +/obj/item/powersink/attack_ai() + return + +/obj/item/powersink/attack_hand(mob/user) + . = ..() + if(.) + return + switch(mode) + if(DISCONNECTED) + ..() + + if(CLAMPED_OFF) + user.visible_message( \ + "[user] activates \the [src]!", \ + "You activate \the [src].", + "You hear a click.") + message_admins("Power sink activated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(src)]") + log_game("Power sink activated by [key_name(user)] at [AREACOORD(src)]") + set_mode(OPERATING) + + if(OPERATING) + user.visible_message( \ + "[user] deactivates \the [src]!", \ + "You deactivate \the [src].", + "You hear a click.") + set_mode(CLAMPED_OFF) + +/obj/item/powersink/process() + if(!attached) + set_mode(DISCONNECTED) + return + + var/datum/powernet/PN = attached.powernet + if(PN) + set_light(5) + + // found a powernet, so drain up to max power from it + + var/drained = min ( drain_rate, attached.newavail() ) + attached.add_delayedload(drained) + power_drained += drained + + // if tried to drain more than available on powernet + // now look for APCs and drain their cells + if(drained < drain_rate) + for(var/obj/machinery/power/terminal/T in PN.nodes) + if(istype(T.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = T.master + if(A.operating && A.cell) + A.cell.charge = max(0, A.cell.charge - 50) + power_drained += 50 + if(A.charging == 2) // If the cell was full + A.charging = 1 // It's no longer full + if(drained >= drain_rate) + break + + if(power_drained > max_power * 0.98) + if (!admins_warned) + admins_warned = TRUE + message_admins("Power sink at ([x],[y],[z] - JMP) is 95% full. Explosion imminent.") + playsound(src, 'sound/effects/screech.ogg', 100, TRUE, TRUE) + + if(power_drained >= max_power) + STOP_PROCESSING(SSobj, src) + explosion(src.loc, 4,8,16,32) + qdel(src) + +#undef DISCONNECTED +#undef CLAMPED_OFF +#undef OPERATING diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index 455518e13ab..03de2c71cb6 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -1,131 +1,131 @@ -/obj/item/electropack - name = "electropack" - desc = "Dance my monkeys! DANCE!!!" - icon = 'icons/obj/radio.dmi' - icon_state = "electropack0" - inhand_icon_state = "electropack" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - w_class = WEIGHT_CLASS_HUGE - custom_materials = list(/datum/material/iron=10000, /datum/material/glass=2500) - var/ui_x = 260 - var/ui_y = 137 - var/on = TRUE - var/code = 2 - var/frequency = FREQ_ELECTROPACK - var/shock_cooldown = FALSE - -/obj/item/electropack/Initialize() - . = ..() - set_frequency(frequency) - -/obj/item/electropack/Destroy() - SSradio.remove_object(src, frequency) - return ..() - -/obj/item/electropack/suicide_act(mob/user) - user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/electropack/attack_hand(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.back) - to_chat(user, "You need help taking this off!") - return - return ..() - -/obj/item/electropack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/clothing/head/helmet)) - var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) - A.icon = 'icons/obj/assemblies.dmi' - - if(!user.transferItemToLoc(W, A)) - to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") - return - W.master = A - A.part1 = W - - user.transferItemToLoc(src, A, TRUE) - master = A - A.part2 = src - - user.put_in_hands(A) - A.add_fingerprint(user) - else - return ..() - -/obj/item/electropack/receive_signal(datum/signal/signal) - if(!signal || signal.data["code"] != code) - return - - if(isliving(loc) && on) - if(shock_cooldown) - return - shock_cooldown = TRUE - addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100) - var/mob/living/L = loc - step(L, pick(GLOB.cardinals)) - - to_chat(L, "You feel a sharp shock!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, L) - s.start() - - L.Paralyze(100) - - if(master) - master.receive_signal() - -/obj/item/electropack/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - SSradio.add_object(src, frequency, RADIO_SIGNALER) - -/obj/item/electropack/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Electropack", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/electropack/ui_data(mob/user) - var/list/data = list() - data["power"] = on - data["frequency"] = frequency - data["code"] = code - data["minFrequency"] = MIN_FREE_FREQ - data["maxFrequency"] = MAX_FREE_FREQ - return data - -/obj/item/electropack/ui_act(action, params) - if(..()) - return - - switch(action) - if("power") - on = !on - icon_state = "electropack[on]" - . = TRUE - if("freq") - var/value = unformat_frequency(params["freq"]) - if(value) - frequency = sanitize_frequency(value, TRUE) - set_frequency(frequency) - . = TRUE - if("code") - var/value = text2num(params["code"]) - if(value) - value = round(value) - code = clamp(value, 1, 100) - . = TRUE - if("reset") - if(params["reset"] == "freq") - frequency = initial(frequency) - . = TRUE - else if(params["reset"] == "code") - code = initial(code) - . = TRUE +/obj/item/electropack + name = "electropack" + desc = "Dance my monkeys! DANCE!!!" + icon = 'icons/obj/radio.dmi' + icon_state = "electropack0" + inhand_icon_state = "electropack" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_HUGE + custom_materials = list(/datum/material/iron=10000, /datum/material/glass=2500) + var/ui_x = 260 + var/ui_y = 137 + var/on = TRUE + var/code = 2 + var/frequency = FREQ_ELECTROPACK + var/shock_cooldown = FALSE + +/obj/item/electropack/Initialize() + . = ..() + set_frequency(frequency) + +/obj/item/electropack/Destroy() + SSradio.remove_object(src, frequency) + return ..() + +/obj/item/electropack/suicide_act(mob/user) + user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/electropack/attack_hand(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.back) + to_chat(user, "You need help taking this off!") + return + return ..() + +/obj/item/electropack/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/clothing/head/helmet)) + var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) + A.icon = 'icons/obj/assemblies.dmi' + + if(!user.transferItemToLoc(W, A)) + to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") + return + W.master = A + A.part1 = W + + user.transferItemToLoc(src, A, TRUE) + master = A + A.part2 = src + + user.put_in_hands(A) + A.add_fingerprint(user) + else + return ..() + +/obj/item/electropack/receive_signal(datum/signal/signal) + if(!signal || signal.data["code"] != code) + return + + if(isliving(loc) && on) + if(shock_cooldown) + return + shock_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100) + var/mob/living/L = loc + step(L, pick(GLOB.cardinals)) + + to_chat(L, "You feel a sharp shock!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, L) + s.start() + + L.Paralyze(100) + + if(master) + master.receive_signal() + +/obj/item/electropack/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + SSradio.add_object(src, frequency, RADIO_SIGNALER) + +/obj/item/electropack/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Electropack", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/electropack/ui_data(mob/user) + var/list/data = list() + data["power"] = on + data["frequency"] = frequency + data["code"] = code + data["minFrequency"] = MIN_FREE_FREQ + data["maxFrequency"] = MAX_FREE_FREQ + return data + +/obj/item/electropack/ui_act(action, params) + if(..()) + return + + switch(action) + if("power") + on = !on + icon_state = "electropack[on]" + . = TRUE + if("freq") + var/value = unformat_frequency(params["freq"]) + if(value) + frequency = sanitize_frequency(value, TRUE) + set_frequency(frequency) + . = TRUE + if("code") + var/value = text2num(params["code"]) + if(value) + value = round(value) + code = clamp(value, 1, 100) + . = TRUE + if("reset") + if(params["reset"] == "freq") + frequency = initial(frequency) + . = TRUE + else if(params["reset"] == "code") + code = initial(code) + . = TRUE diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm index 42b24fe85b8..d505021fd5c 100644 --- a/code/game/objects/items/devices/radio/encryptionkey.dm +++ b/code/game/objects/items/devices/radio/encryptionkey.dm @@ -1,137 +1,137 @@ -/obj/item/encryptionkey - name = "standard encryption key" - desc = "An encryption key for a radio headset." - icon = 'icons/obj/radio.dmi' - icon_state = "cypherkey" - w_class = WEIGHT_CLASS_TINY - var/translate_binary = FALSE - var/syndie = FALSE - var/independent = FALSE - var/list/channels = list() - -/obj/item/encryptionkey/Initialize() - . = ..() - if(!channels.len) - desc = "An encryption key for a radio headset. Has no special codes in it. You should probably tell a coder!" - -/obj/item/encryptionkey/examine(mob/user) - . = ..() - if(LAZYLEN(channels)) - var/list/examine_text_list = list() - for(var/i in channels) - examine_text_list += "[GLOB.channel_tokens[i]] - [lowertext(i)]" - - . += "It can access the following channels; [jointext(examine_text_list, ", ")]." - -/obj/item/encryptionkey/syndicate - name = "syndicate encryption key" - icon_state = "syn_cypherkey" - channels = list(RADIO_CHANNEL_SYNDICATE = 1) - syndie = TRUE//Signifies that it de-crypts Syndicate transmissions - -/obj/item/encryptionkey/binary - name = "binary translator key" - icon_state = "bin_cypherkey" - translate_binary = TRUE - -/obj/item/encryptionkey/headset_sec - name = "security radio encryption key" - icon_state = "sec_cypherkey" - channels = list(RADIO_CHANNEL_SECURITY = 1) - -/obj/item/encryptionkey/headset_eng - name = "engineering radio encryption key" - icon_state = "eng_cypherkey" - channels = list(RADIO_CHANNEL_ENGINEERING = 1) - -/obj/item/encryptionkey/headset_rob - name = "robotics radio encryption key" - icon_state = "rob_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_ENGINEERING = 1) - -/obj/item/encryptionkey/headset_med - name = "medical radio encryption key" - icon_state = "med_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1) - -/obj/item/encryptionkey/headset_sci - name = "science radio encryption key" - icon_state = "sci_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1) - -/obj/item/encryptionkey/headset_medsci - name = "medical research radio encryption key" - icon_state = "medsci_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1) - -/obj/item/encryptionkey/headset_srvsec - name = "law and order radio encryption key" - icon_state = "srvsec_cypherkey" - channels = list(RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_SECURITY = 1) - -/obj/item/encryptionkey/headset_srvmed - name = "psychology radio encryption key" - icon_state = "srvmed_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SERVICE = 1) - -/obj/item/encryptionkey/headset_com - name = "command radio encryption key" - icon_state = "com_cypherkey" - channels = list(RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/captain - name = "\proper the captain's encryption key" - icon_state = "cap_cypherkey" - channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0) - -/obj/item/encryptionkey/heads/rd - name = "\proper the research director's encryption key" - icon_state = "rd_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/hos - name = "\proper the head of security's encryption key" - icon_state = "hos_cypherkey" - channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/ce - name = "\proper the chief engineer's encryption key" - icon_state = "ce_cypherkey" - channels = list(RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/cmo - name = "\proper the chief medical officer's encryption key" - icon_state = "cmo_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/hop - name = "\proper the head of personnel's encryption key" - icon_state = "hop_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/headset_cargo - name = "supply radio encryption key" - icon_state = "cargo_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1) - -/obj/item/encryptionkey/headset_mining - name = "mining radio encryption key" - icon_state = "cargo_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1) - -/obj/item/encryptionkey/headset_service - name = "service radio encryption key" - icon_state = "srv_cypherkey" - channels = list(RADIO_CHANNEL_SERVICE = 1) - -/obj/item/encryptionkey/headset_cent - name = "\improper CentCom radio encryption key" - icon_state = "cent_cypherkey" - independent = TRUE - channels = list(RADIO_CHANNEL_CENTCOM = 1) - -/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. - channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) - -/obj/item/encryptionkey/secbot - channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1) +/obj/item/encryptionkey + name = "standard encryption key" + desc = "An encryption key for a radio headset." + icon = 'icons/obj/radio.dmi' + icon_state = "cypherkey" + w_class = WEIGHT_CLASS_TINY + var/translate_binary = FALSE + var/syndie = FALSE + var/independent = FALSE + var/list/channels = list() + +/obj/item/encryptionkey/Initialize() + . = ..() + if(!channels.len) + desc = "An encryption key for a radio headset. Has no special codes in it. You should probably tell a coder!" + +/obj/item/encryptionkey/examine(mob/user) + . = ..() + if(LAZYLEN(channels)) + var/list/examine_text_list = list() + for(var/i in channels) + examine_text_list += "[GLOB.channel_tokens[i]] - [lowertext(i)]" + + . += "It can access the following channels; [jointext(examine_text_list, ", ")]." + +/obj/item/encryptionkey/syndicate + name = "syndicate encryption key" + icon_state = "syn_cypherkey" + channels = list(RADIO_CHANNEL_SYNDICATE = 1) + syndie = TRUE//Signifies that it de-crypts Syndicate transmissions + +/obj/item/encryptionkey/binary + name = "binary translator key" + icon_state = "bin_cypherkey" + translate_binary = TRUE + +/obj/item/encryptionkey/headset_sec + name = "security radio encryption key" + icon_state = "sec_cypherkey" + channels = list(RADIO_CHANNEL_SECURITY = 1) + +/obj/item/encryptionkey/headset_eng + name = "engineering radio encryption key" + icon_state = "eng_cypherkey" + channels = list(RADIO_CHANNEL_ENGINEERING = 1) + +/obj/item/encryptionkey/headset_rob + name = "robotics radio encryption key" + icon_state = "rob_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_ENGINEERING = 1) + +/obj/item/encryptionkey/headset_med + name = "medical radio encryption key" + icon_state = "med_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1) + +/obj/item/encryptionkey/headset_sci + name = "science radio encryption key" + icon_state = "sci_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1) + +/obj/item/encryptionkey/headset_medsci + name = "medical research radio encryption key" + icon_state = "medsci_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1) + +/obj/item/encryptionkey/headset_srvsec + name = "law and order radio encryption key" + icon_state = "srvsec_cypherkey" + channels = list(RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_SECURITY = 1) + +/obj/item/encryptionkey/headset_srvmed + name = "psychology radio encryption key" + icon_state = "srvmed_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SERVICE = 1) + +/obj/item/encryptionkey/headset_com + name = "command radio encryption key" + icon_state = "com_cypherkey" + channels = list(RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/captain + name = "\proper the captain's encryption key" + icon_state = "cap_cypherkey" + channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0) + +/obj/item/encryptionkey/heads/rd + name = "\proper the research director's encryption key" + icon_state = "rd_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/hos + name = "\proper the head of security's encryption key" + icon_state = "hos_cypherkey" + channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/ce + name = "\proper the chief engineer's encryption key" + icon_state = "ce_cypherkey" + channels = list(RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/cmo + name = "\proper the chief medical officer's encryption key" + icon_state = "cmo_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/hop + name = "\proper the head of personnel's encryption key" + icon_state = "hop_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/headset_cargo + name = "supply radio encryption key" + icon_state = "cargo_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1) + +/obj/item/encryptionkey/headset_mining + name = "mining radio encryption key" + icon_state = "cargo_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1) + +/obj/item/encryptionkey/headset_service + name = "service radio encryption key" + icon_state = "srv_cypherkey" + channels = list(RADIO_CHANNEL_SERVICE = 1) + +/obj/item/encryptionkey/headset_cent + name = "\improper CentCom radio encryption key" + icon_state = "cent_cypherkey" + independent = TRUE + channels = list(RADIO_CHANNEL_CENTCOM = 1) + +/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. + channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) + +/obj/item/encryptionkey/secbot + channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 14eaa949d9f..0c1fe3c6adb 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -1,352 +1,352 @@ -// Used for translating channels to tokens on examination -GLOBAL_LIST_INIT(channel_tokens, list( - RADIO_CHANNEL_COMMON = RADIO_KEY_COMMON, - RADIO_CHANNEL_SCIENCE = RADIO_TOKEN_SCIENCE, - RADIO_CHANNEL_COMMAND = RADIO_TOKEN_COMMAND, - RADIO_CHANNEL_MEDICAL = RADIO_TOKEN_MEDICAL, - RADIO_CHANNEL_ENGINEERING = RADIO_TOKEN_ENGINEERING, - RADIO_CHANNEL_SECURITY = RADIO_TOKEN_SECURITY, - RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM, - RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE, - RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY, - RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE, - MODE_BINARY = MODE_TOKEN_BINARY, - RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE -)) - -/obj/item/radio/headset - name = "radio headset" - desc = "An updated, modular intercom that fits over the head. Takes encryption keys." - icon_state = "headset" - inhand_icon_state = "headset" - custom_materials = list(/datum/material/iron=75) - subspace_transmission = TRUE - canhear_range = 0 // can't hear headsets from very far away - - slot_flags = ITEM_SLOT_EARS - var/obj/item/encryptionkey/keyslot2 = null - dog_fashion = null - -/obj/item/radio/headset/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins putting \the [src]'s antenna up [user.p_their()] nose! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer!") - return TOXLOSS - -/obj/item/radio/headset/examine(mob/user) - . = ..() - - if(item_flags & IN_INVENTORY && loc == user) - // construction of frequency description - var/list/avail_chans = list("Use [RADIO_KEY_COMMON] for the currently tuned frequency") - if(translate_binary) - avail_chans += "use [MODE_TOKEN_BINARY] for [MODE_BINARY]" - if(length(channels)) - for(var/i in 1 to length(channels)) - if(i == 1) - avail_chans += "use [MODE_TOKEN_DEPARTMENT] or [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" - else - avail_chans += "use [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" - . += "A small screen on the headset displays the following available frequencies:\n[english_list(avail_chans)]." - - if(command) - . += "Alt-click to toggle the high-volume mode." - else - . += "A small screen on the headset flashes, it's too small to read without holding or wearing the headset." - -/obj/item/radio/headset/Initialize() - . = ..() - recalculateChannels() - -/obj/item/radio/headset/Destroy() - QDEL_NULL(keyslot2) - return ..() - -/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language) - if (!listening) - return ITALICS | REDUCE_RANGE - return ..() - -/obj/item/radio/headset/can_receive(freq, level, AIuser) - if(ishuman(src.loc)) - var/mob/living/carbon/human/H = src.loc - if(H.ears == src) - return ..(freq, level) - else if(AIuser) - return ..(freq, level) - return FALSE - -/obj/item/radio/headset/ui_data(mob/user) - . = ..() - .["headset"] = TRUE - -/obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops - -/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection - name = "syndicate headset" - desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." - icon_state = "syndie_headset" - inhand_icon_state = "syndie_headset" - -/obj/item/radio/headset/syndicate/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/syndicate/alt/leader - name = "team leader headset" - command = TRUE - -/obj/item/radio/headset/syndicate/Initialize() - . = ..() - make_syndie() - -/obj/item/radio/headset/binary -/obj/item/radio/headset/binary/Initialize() - . = ..() - qdel(keyslot) - keyslot = new /obj/item/encryptionkey/binary - recalculateChannels() - -/obj/item/radio/headset/headset_sec - name = "security radio headset" - desc = "This is used by your elite security force." - icon_state = "sec_headset" - keyslot = new /obj/item/encryptionkey/headset_sec - -/obj/item/radio/headset/headset_sec/alt - name = "security bowman headset" - desc = "This is used by your elite security force. Protects ears from flashbangs." - icon_state = "sec_headset_alt" - inhand_icon_state = "sec_headset_alt" - -/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/headset_eng - name = "engineering radio headset" - desc = "When the engineers wish to chat like girls." - icon_state = "eng_headset" - keyslot = new /obj/item/encryptionkey/headset_eng - -/obj/item/radio/headset/headset_rob - name = "robotics radio headset" - desc = "Made specifically for the roboticists, who cannot decide between departments." - icon_state = "rob_headset" - keyslot = new /obj/item/encryptionkey/headset_rob - -/obj/item/radio/headset/headset_med - name = "medical radio headset" - desc = "A headset for the trained staff of the medbay." - icon_state = "med_headset" - keyslot = new /obj/item/encryptionkey/headset_med - -/obj/item/radio/headset/headset_sci - name = "science radio headset" - desc = "A sciency headset. Like usual." - icon_state = "sci_headset" - keyslot = new /obj/item/encryptionkey/headset_sci - -/obj/item/radio/headset/headset_medsci - name = "medical research radio headset" - desc = "A headset that is a result of the mating between medical and science." - icon_state = "medsci_headset" - keyslot = new /obj/item/encryptionkey/headset_medsci - -/obj/item/radio/headset/headset_srvsec - name = "law and order headset" - desc = "In the criminal justice headset, the encryption key represents two separate but equally important groups. Sec, who investigate crime, and Service, who provide services. These are their comms." - icon_state = "srvsec_headset" - keyslot = new /obj/item/encryptionkey/headset_srvsec - -/obj/item/radio/headset/headset_srvmed - name = "psychology headset" - desc = "A headset allowing the wearer to communicate with medbay and service." - icon_state = "med_headset" - keyslot = new /obj/item/encryptionkey/headset_srvmed - -/obj/item/radio/headset/headset_com - name = "command radio headset" - desc = "A headset with a commanding channel." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/headset_com - -/obj/item/radio/headset/heads - command = TRUE - -/obj/item/radio/headset/heads/captain - name = "\proper the captain's headset" - desc = "The headset of the king." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/heads/captain/alt - name = "\proper the captain's bowman headset" - desc = "The headset of the boss. Protects ears from flashbangs." - icon_state = "com_headset_alt" - inhand_icon_state = "com_headset_alt" - -/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/heads/rd - name = "\proper the research director's headset" - desc = "Headset of the fellow who keeps society marching towards technological singularity." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/rd - -/obj/item/radio/headset/heads/hos - name = "\proper the head of security's headset" - desc = "The headset of the man in charge of keeping order and protecting the station." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/hos - -/obj/item/radio/headset/heads/hos/alt - name = "\proper the head of security's bowman headset" - desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." - icon_state = "com_headset_alt" - inhand_icon_state = "com_headset_alt" - -/obj/item/radio/headset/heads/hos/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/heads/ce - name = "\proper the chief engineer's headset" - desc = "The headset of the guy in charge of keeping the station powered and undamaged." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/ce - -/obj/item/radio/headset/heads/cmo - name = "\proper the chief medical officer's headset" - desc = "The headset of the highly trained medical chief." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/cmo - -/obj/item/radio/headset/heads/hop - name = "\proper the head of personnel's headset" - desc = "The headset of the guy who will one day be captain." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/hop - -/obj/item/radio/headset/headset_cargo - name = "supply radio headset" - desc = "A headset used by the QM and his slaves." - icon_state = "cargo_headset" - keyslot = new /obj/item/encryptionkey/headset_cargo - -/obj/item/radio/headset/headset_cargo/mining - name = "mining radio headset" - desc = "Headset used by shaft miners." - icon_state = "mine_headset" - keyslot = new /obj/item/encryptionkey/headset_mining - -/obj/item/radio/headset/headset_srv - name = "service radio headset" - desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." - icon_state = "srv_headset" - keyslot = new /obj/item/encryptionkey/headset_service - -/obj/item/radio/headset/headset_cent - name = "\improper CentCom headset" - desc = "A headset used by the upper echelons of Nanotrasen." - icon_state = "cent_headset" - keyslot = new /obj/item/encryptionkey/headset_com - keyslot2 = new /obj/item/encryptionkey/headset_cent - -/obj/item/radio/headset/headset_cent/empty - keyslot = null - keyslot2 = null - -/obj/item/radio/headset/headset_cent/commander - keyslot = new /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/headset_cent/alt - name = "\improper CentCom bowman headset" - desc = "A headset especially for emergency response personnel. Protects ears from flashbangs." - icon_state = "cent_headset_alt" - inhand_icon_state = "cent_headset_alt" - keyslot = null - -/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/silicon/pai - name = "\proper mini Integrated Subspace Transceiver " - subspace_transmission = FALSE - - -/obj/item/radio/headset/silicon/ai - name = "\proper Integrated Subspace Transceiver " - keyslot2 = new /obj/item/encryptionkey/ai - command = TRUE - -/obj/item/radio/headset/silicon/can_receive(freq, level) - return ..(freq, level, TRUE) - -/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) - user.set_machine(src) - - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(keyslot || keyslot2) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - if(keyslot) - user.put_in_hands(keyslot) - keyslot = null - if(keyslot2) - user.put_in_hands(keyslot2) - keyslot2 = null - - recalculateChannels() - to_chat(user, "You pop out the encryption keys in the headset.") - - else - to_chat(user, "This headset doesn't have any unique encryption keys! How useless...") - - else if(istype(W, /obj/item/encryptionkey)) - if(keyslot && keyslot2) - to_chat(user, "The headset can't hold another key!") - return - - if(!keyslot) - if(!user.transferItemToLoc(W, src)) - return - keyslot = W - - else - if(!user.transferItemToLoc(W, src)) - return - keyslot2 = W - - - recalculateChannels() - else - return ..() - - -/obj/item/radio/headset/recalculateChannels() - ..() - if(keyslot2) - for(var/ch_name in keyslot2.channels) - if(!(ch_name in src.channels)) - channels[ch_name] = keyslot2.channels[ch_name] - - if(keyslot2.translate_binary) - translate_binary = TRUE - if(keyslot2.syndie) - syndie = TRUE - if (keyslot2.independent) - independent = TRUE - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/headset/AltClick(mob/living/user) - if(!istype(user) || !Adjacent(user) || user.incapacitated()) - return - if (command) - use_command = !use_command - to_chat(user, "You toggle high-volume mode [use_command ? "on" : "off"].") +// Used for translating channels to tokens on examination +GLOBAL_LIST_INIT(channel_tokens, list( + RADIO_CHANNEL_COMMON = RADIO_KEY_COMMON, + RADIO_CHANNEL_SCIENCE = RADIO_TOKEN_SCIENCE, + RADIO_CHANNEL_COMMAND = RADIO_TOKEN_COMMAND, + RADIO_CHANNEL_MEDICAL = RADIO_TOKEN_MEDICAL, + RADIO_CHANNEL_ENGINEERING = RADIO_TOKEN_ENGINEERING, + RADIO_CHANNEL_SECURITY = RADIO_TOKEN_SECURITY, + RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM, + RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE, + RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY, + RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE, + MODE_BINARY = MODE_TOKEN_BINARY, + RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE +)) + +/obj/item/radio/headset + name = "radio headset" + desc = "An updated, modular intercom that fits over the head. Takes encryption keys." + icon_state = "headset" + inhand_icon_state = "headset" + custom_materials = list(/datum/material/iron=75) + subspace_transmission = TRUE + canhear_range = 0 // can't hear headsets from very far away + + slot_flags = ITEM_SLOT_EARS + var/obj/item/encryptionkey/keyslot2 = null + dog_fashion = null + +/obj/item/radio/headset/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins putting \the [src]'s antenna up [user.p_their()] nose! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer!") + return TOXLOSS + +/obj/item/radio/headset/examine(mob/user) + . = ..() + + if(item_flags & IN_INVENTORY && loc == user) + // construction of frequency description + var/list/avail_chans = list("Use [RADIO_KEY_COMMON] for the currently tuned frequency") + if(translate_binary) + avail_chans += "use [MODE_TOKEN_BINARY] for [MODE_BINARY]" + if(length(channels)) + for(var/i in 1 to length(channels)) + if(i == 1) + avail_chans += "use [MODE_TOKEN_DEPARTMENT] or [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" + else + avail_chans += "use [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" + . += "A small screen on the headset displays the following available frequencies:\n[english_list(avail_chans)]." + + if(command) + . += "Alt-click to toggle the high-volume mode." + else + . += "A small screen on the headset flashes, it's too small to read without holding or wearing the headset." + +/obj/item/radio/headset/Initialize() + . = ..() + recalculateChannels() + +/obj/item/radio/headset/Destroy() + QDEL_NULL(keyslot2) + return ..() + +/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language) + if (!listening) + return ITALICS | REDUCE_RANGE + return ..() + +/obj/item/radio/headset/can_receive(freq, level, AIuser) + if(ishuman(src.loc)) + var/mob/living/carbon/human/H = src.loc + if(H.ears == src) + return ..(freq, level) + else if(AIuser) + return ..(freq, level) + return FALSE + +/obj/item/radio/headset/ui_data(mob/user) + . = ..() + .["headset"] = TRUE + +/obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops + +/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection + name = "syndicate headset" + desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." + icon_state = "syndie_headset" + inhand_icon_state = "syndie_headset" + +/obj/item/radio/headset/syndicate/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/syndicate/alt/leader + name = "team leader headset" + command = TRUE + +/obj/item/radio/headset/syndicate/Initialize() + . = ..() + make_syndie() + +/obj/item/radio/headset/binary +/obj/item/radio/headset/binary/Initialize() + . = ..() + qdel(keyslot) + keyslot = new /obj/item/encryptionkey/binary + recalculateChannels() + +/obj/item/radio/headset/headset_sec + name = "security radio headset" + desc = "This is used by your elite security force." + icon_state = "sec_headset" + keyslot = new /obj/item/encryptionkey/headset_sec + +/obj/item/radio/headset/headset_sec/alt + name = "security bowman headset" + desc = "This is used by your elite security force. Protects ears from flashbangs." + icon_state = "sec_headset_alt" + inhand_icon_state = "sec_headset_alt" + +/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/headset_eng + name = "engineering radio headset" + desc = "When the engineers wish to chat like girls." + icon_state = "eng_headset" + keyslot = new /obj/item/encryptionkey/headset_eng + +/obj/item/radio/headset/headset_rob + name = "robotics radio headset" + desc = "Made specifically for the roboticists, who cannot decide between departments." + icon_state = "rob_headset" + keyslot = new /obj/item/encryptionkey/headset_rob + +/obj/item/radio/headset/headset_med + name = "medical radio headset" + desc = "A headset for the trained staff of the medbay." + icon_state = "med_headset" + keyslot = new /obj/item/encryptionkey/headset_med + +/obj/item/radio/headset/headset_sci + name = "science radio headset" + desc = "A sciency headset. Like usual." + icon_state = "sci_headset" + keyslot = new /obj/item/encryptionkey/headset_sci + +/obj/item/radio/headset/headset_medsci + name = "medical research radio headset" + desc = "A headset that is a result of the mating between medical and science." + icon_state = "medsci_headset" + keyslot = new /obj/item/encryptionkey/headset_medsci + +/obj/item/radio/headset/headset_srvsec + name = "law and order headset" + desc = "In the criminal justice headset, the encryption key represents two separate but equally important groups. Sec, who investigate crime, and Service, who provide services. These are their comms." + icon_state = "srvsec_headset" + keyslot = new /obj/item/encryptionkey/headset_srvsec + +/obj/item/radio/headset/headset_srvmed + name = "psychology headset" + desc = "A headset allowing the wearer to communicate with medbay and service." + icon_state = "med_headset" + keyslot = new /obj/item/encryptionkey/headset_srvmed + +/obj/item/radio/headset/headset_com + name = "command radio headset" + desc = "A headset with a commanding channel." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/headset_com + +/obj/item/radio/headset/heads + command = TRUE + +/obj/item/radio/headset/heads/captain + name = "\proper the captain's headset" + desc = "The headset of the king." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/heads/captain/alt + name = "\proper the captain's bowman headset" + desc = "The headset of the boss. Protects ears from flashbangs." + icon_state = "com_headset_alt" + inhand_icon_state = "com_headset_alt" + +/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/heads/rd + name = "\proper the research director's headset" + desc = "Headset of the fellow who keeps society marching towards technological singularity." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/rd + +/obj/item/radio/headset/heads/hos + name = "\proper the head of security's headset" + desc = "The headset of the man in charge of keeping order and protecting the station." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/hos + +/obj/item/radio/headset/heads/hos/alt + name = "\proper the head of security's bowman headset" + desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." + icon_state = "com_headset_alt" + inhand_icon_state = "com_headset_alt" + +/obj/item/radio/headset/heads/hos/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/heads/ce + name = "\proper the chief engineer's headset" + desc = "The headset of the guy in charge of keeping the station powered and undamaged." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/ce + +/obj/item/radio/headset/heads/cmo + name = "\proper the chief medical officer's headset" + desc = "The headset of the highly trained medical chief." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/cmo + +/obj/item/radio/headset/heads/hop + name = "\proper the head of personnel's headset" + desc = "The headset of the guy who will one day be captain." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/hop + +/obj/item/radio/headset/headset_cargo + name = "supply radio headset" + desc = "A headset used by the QM and his slaves." + icon_state = "cargo_headset" + keyslot = new /obj/item/encryptionkey/headset_cargo + +/obj/item/radio/headset/headset_cargo/mining + name = "mining radio headset" + desc = "Headset used by shaft miners." + icon_state = "mine_headset" + keyslot = new /obj/item/encryptionkey/headset_mining + +/obj/item/radio/headset/headset_srv + name = "service radio headset" + desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." + icon_state = "srv_headset" + keyslot = new /obj/item/encryptionkey/headset_service + +/obj/item/radio/headset/headset_cent + name = "\improper CentCom headset" + desc = "A headset used by the upper echelons of Nanotrasen." + icon_state = "cent_headset" + keyslot = new /obj/item/encryptionkey/headset_com + keyslot2 = new /obj/item/encryptionkey/headset_cent + +/obj/item/radio/headset/headset_cent/empty + keyslot = null + keyslot2 = null + +/obj/item/radio/headset/headset_cent/commander + keyslot = new /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/headset_cent/alt + name = "\improper CentCom bowman headset" + desc = "A headset especially for emergency response personnel. Protects ears from flashbangs." + icon_state = "cent_headset_alt" + inhand_icon_state = "cent_headset_alt" + keyslot = null + +/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/silicon/pai + name = "\proper mini Integrated Subspace Transceiver " + subspace_transmission = FALSE + + +/obj/item/radio/headset/silicon/ai + name = "\proper Integrated Subspace Transceiver " + keyslot2 = new /obj/item/encryptionkey/ai + command = TRUE + +/obj/item/radio/headset/silicon/can_receive(freq, level) + return ..(freq, level, TRUE) + +/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) + user.set_machine(src) + + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(keyslot || keyslot2) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + if(keyslot) + user.put_in_hands(keyslot) + keyslot = null + if(keyslot2) + user.put_in_hands(keyslot2) + keyslot2 = null + + recalculateChannels() + to_chat(user, "You pop out the encryption keys in the headset.") + + else + to_chat(user, "This headset doesn't have any unique encryption keys! How useless...") + + else if(istype(W, /obj/item/encryptionkey)) + if(keyslot && keyslot2) + to_chat(user, "The headset can't hold another key!") + return + + if(!keyslot) + if(!user.transferItemToLoc(W, src)) + return + keyslot = W + + else + if(!user.transferItemToLoc(W, src)) + return + keyslot2 = W + + + recalculateChannels() + else + return ..() + + +/obj/item/radio/headset/recalculateChannels() + ..() + if(keyslot2) + for(var/ch_name in keyslot2.channels) + if(!(ch_name in src.channels)) + channels[ch_name] = keyslot2.channels[ch_name] + + if(keyslot2.translate_binary) + translate_binary = TRUE + if(keyslot2.syndie) + syndie = TRUE + if (keyslot2.independent) + independent = TRUE + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/headset/AltClick(mob/living/user) + if(!istype(user) || !Adjacent(user) || user.incapacitated()) + return + if (command) + use_command = !use_command + to_chat(user, "You toggle high-volume mode [use_command ? "on" : "off"].") diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index 40a7fe08c54..3b11e9dce53 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -1,135 +1,135 @@ -/obj/item/radio/intercom - name = "station intercom" - desc = "Talk through this." - icon_state = "intercom" - anchored = TRUE - w_class = WEIGHT_CLASS_BULKY - canhear_range = 2 - dog_fashion = null - unscrewed = FALSE - -/obj/item/radio/intercom/unscrewed - unscrewed = TRUE - -/obj/item/radio/intercom/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - var/area/current_area = get_area(src) - if(!current_area) - return - RegisterSignal(current_area, COMSIG_AREA_POWER_CHANGE, .proc/AreaPowerCheck) - -/obj/item/radio/intercom/examine(mob/user) - . = ..() - . += "Use [MODE_TOKEN_INTERCOM] when nearby to speak into it." - if(!unscrewed) - . += "It's screwed and secured to the wall." - else - . += "It's unscrewed from the wall, and can be detached." - -/obj/item/radio/intercom/attackby(obj/item/I, mob/living/user, params) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(unscrewed) - user.visible_message("[user] starts tightening [src]'s screws...", "You start screwing in [src]...") - if(I.use_tool(src, user, 30, volume=50)) - user.visible_message("[user] tightens [src]'s screws!", "You tighten [src]'s screws.") - unscrewed = FALSE - else - user.visible_message("[user] starts loosening [src]'s screws...", "You start unscrewing [src]...") - if(I.use_tool(src, user, 40, volume=50)) - user.visible_message("[user] loosens [src]'s screws!", "You unscrew [src], loosening it from the wall.") - unscrewed = TRUE - return - else if(I.tool_behaviour == TOOL_WRENCH) - if(!unscrewed) - to_chat(user, "You need to unscrew [src] from the wall first!") - return - user.visible_message("[user] starts unsecuring [src]...", "You start unsecuring [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 80)) - user.visible_message("[user] unsecures [src]!", "You detach [src] from the wall.") - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - new/obj/item/wallframe/intercom(get_turf(src)) - qdel(src) - return - return ..() - -/obj/item/radio/intercom/attack_ai(mob/user) - interact(user) - -/obj/item/radio/intercom/attack_hand(mob/user) - . = ..() - if(.) - return - interact(user) - -/obj/item/radio/intercom/interact(mob/user) - ..() - ui_interact(user, state = GLOB.default_state) - -/obj/item/radio/intercom/can_receive(freq, level) - if(!on) - return FALSE - if(wires.is_cut(WIRE_RX)) - return FALSE - if(!(0 in level)) - var/turf/position = get_turf(src) - if(isnull(position) || !(position.z in level)) - return FALSE - if(!listening) - return FALSE - if(freq == FREQ_SYNDICATE) - if(!(syndie)) - return FALSE//Prevents broadcast of messages over devices lacking the encryption - - return TRUE - - -/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) - if(message_mode == MODE_INTERCOM) - return // Avoid hearing the same thing twice - return ..() - -/obj/item/radio/intercom/emp_act(severity) - . = ..() // Parent call here will set `on` to FALSE. - update_icon() - -/obj/item/radio/intercom/end_emp_effect(curremp) - . = ..() - AreaPowerCheck() // Make sure the area/local APC is powered first before we actually turn back on. - -/obj/item/radio/intercom/update_icon() - . = ..() - if(on) - icon_state = initial(icon_state) - else - icon_state = "intercom-p" - -/** - * Proc called whenever the intercom's area loses or gains power. Responsible for setting the `on` variable and calling `update_icon()`. - * - * Normally called after the intercom's area recieves the `COMSIG_AREA_POWER_CHANGE` signal, but it can also be called directly. - * Arguments: - * * source - the area that just had a power change. - */ -/obj/item/radio/intercom/proc/AreaPowerCheck(datum/source) - var/area/current_area = get_area(src) - if(!current_area) - on = FALSE - else - on = current_area.powered(AREA_USAGE_EQUIP) // set "on" to the equipment power status of our area. - update_icon() - -/obj/item/radio/intercom/add_blood_DNA(list/blood_dna) - return FALSE - -//Created through the autolathe or through deconstructing intercoms. Can be applied to wall to make a new intercom on it! -/obj/item/wallframe/intercom - name = "intercom frame" - desc = "A ready-to-go intercom. Just slap it on a wall and screw it in!" - icon_state = "intercom" - result_path = /obj/item/radio/intercom/unscrewed - pixel_shift = 29 - inverse = TRUE - custom_materials = list(/datum/material/iron = 75, /datum/material/glass = 25) +/obj/item/radio/intercom + name = "station intercom" + desc = "Talk through this." + icon_state = "intercom" + anchored = TRUE + w_class = WEIGHT_CLASS_BULKY + canhear_range = 2 + dog_fashion = null + unscrewed = FALSE + +/obj/item/radio/intercom/unscrewed + unscrewed = TRUE + +/obj/item/radio/intercom/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + var/area/current_area = get_area(src) + if(!current_area) + return + RegisterSignal(current_area, COMSIG_AREA_POWER_CHANGE, .proc/AreaPowerCheck) + +/obj/item/radio/intercom/examine(mob/user) + . = ..() + . += "Use [MODE_TOKEN_INTERCOM] when nearby to speak into it." + if(!unscrewed) + . += "It's screwed and secured to the wall." + else + . += "It's unscrewed from the wall, and can be detached." + +/obj/item/radio/intercom/attackby(obj/item/I, mob/living/user, params) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(unscrewed) + user.visible_message("[user] starts tightening [src]'s screws...", "You start screwing in [src]...") + if(I.use_tool(src, user, 30, volume=50)) + user.visible_message("[user] tightens [src]'s screws!", "You tighten [src]'s screws.") + unscrewed = FALSE + else + user.visible_message("[user] starts loosening [src]'s screws...", "You start unscrewing [src]...") + if(I.use_tool(src, user, 40, volume=50)) + user.visible_message("[user] loosens [src]'s screws!", "You unscrew [src], loosening it from the wall.") + unscrewed = TRUE + return + else if(I.tool_behaviour == TOOL_WRENCH) + if(!unscrewed) + to_chat(user, "You need to unscrew [src] from the wall first!") + return + user.visible_message("[user] starts unsecuring [src]...", "You start unsecuring [src]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 80)) + user.visible_message("[user] unsecures [src]!", "You detach [src] from the wall.") + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + new/obj/item/wallframe/intercom(get_turf(src)) + qdel(src) + return + return ..() + +/obj/item/radio/intercom/attack_ai(mob/user) + interact(user) + +/obj/item/radio/intercom/attack_hand(mob/user) + . = ..() + if(.) + return + interact(user) + +/obj/item/radio/intercom/interact(mob/user) + ..() + ui_interact(user, state = GLOB.default_state) + +/obj/item/radio/intercom/can_receive(freq, level) + if(!on) + return FALSE + if(wires.is_cut(WIRE_RX)) + return FALSE + if(!(0 in level)) + var/turf/position = get_turf(src) + if(isnull(position) || !(position.z in level)) + return FALSE + if(!listening) + return FALSE + if(freq == FREQ_SYNDICATE) + if(!(syndie)) + return FALSE//Prevents broadcast of messages over devices lacking the encryption + + return TRUE + + +/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) + if(message_mode == MODE_INTERCOM) + return // Avoid hearing the same thing twice + return ..() + +/obj/item/radio/intercom/emp_act(severity) + . = ..() // Parent call here will set `on` to FALSE. + update_icon() + +/obj/item/radio/intercom/end_emp_effect(curremp) + . = ..() + AreaPowerCheck() // Make sure the area/local APC is powered first before we actually turn back on. + +/obj/item/radio/intercom/update_icon() + . = ..() + if(on) + icon_state = initial(icon_state) + else + icon_state = "intercom-p" + +/** + * Proc called whenever the intercom's area loses or gains power. Responsible for setting the `on` variable and calling `update_icon()`. + * + * Normally called after the intercom's area recieves the `COMSIG_AREA_POWER_CHANGE` signal, but it can also be called directly. + * Arguments: + * * source - the area that just had a power change. + */ +/obj/item/radio/intercom/proc/AreaPowerCheck(datum/source) + var/area/current_area = get_area(src) + if(!current_area) + on = FALSE + else + on = current_area.powered(AREA_USAGE_EQUIP) // set "on" to the equipment power status of our area. + update_icon() + +/obj/item/radio/intercom/add_blood_DNA(list/blood_dna) + return FALSE + +//Created through the autolathe or through deconstructing intercoms. Can be applied to wall to make a new intercom on it! +/obj/item/wallframe/intercom + name = "intercom frame" + desc = "A ready-to-go intercom. Just slap it on a wall and screw it in!" + icon_state = "intercom" + result_path = /obj/item/radio/intercom/unscrewed + pixel_shift = 29 + inverse = TRUE + custom_materials = list(/datum/material/iron = 75, /datum/material/glass = 25) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 5ea01783512..eff3fae11f1 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -1,426 +1,426 @@ -#define FREQ_LISTENING (1<<0) - -/obj/item/radio - icon = 'icons/obj/radio.dmi' - name = "station bounced radio" - icon_state = "walkietalkie" - inhand_icon_state = "walkietalkie" - desc = "A basic handheld radio that communicates with local telecommunication networks." - dog_fashion = /datum/dog_fashion/back - - flags_1 = CONDUCT_1 | HEAR_1 - slot_flags = ITEM_SLOT_BELT - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=75, /datum/material/glass=25) - obj_flags = USES_TGUI - - var/on = TRUE - var/frequency = FREQ_COMMON - var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives. - var/emped = 0 // Tracks the number of EMPs currently stacked. - - var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby. - var/listening = TRUE // Whether the radio is currently receiving. - var/prison_radio = FALSE // If true, the transmit wire starts cut. - var/unscrewed = FALSE // Whether wires are accessible. Toggleable by screwdrivering. - var/freerange = FALSE // If true, the radio has access to the full spectrum. - var/subspace_transmission = FALSE // If true, the radio transmits and receives on subspace exclusively. - var/subspace_switchable = FALSE // If true, subspace_transmission can be toggled at will. - var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios. - var/use_command = FALSE // If true, broadcasts will be large and BOLD. - var/command = FALSE // If true, use_command can be toggled at will. - - // Encryption key handling - var/obj/item/encryptionkey/keyslot - var/translate_binary = FALSE // If true, can hear the special binary channel. - var/independent = FALSE // If true, can say/hear on the special CentCom channel. - var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel. - var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h). - var/list/secure_radio_connections - -/obj/item/radio/suicide_act(mob/living/user) - user.visible_message("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/radio/proc/set_frequency(new_frequency) - SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args) - remove_radio(src, frequency) - frequency = add_radio(src, new_frequency) - -/obj/item/radio/proc/recalculateChannels() - resetChannels() - - if(keyslot) - for(var/ch_name in keyslot.channels) - if(!(ch_name in channels)) - channels[ch_name] = keyslot.channels[ch_name] - - if(keyslot.translate_binary) - translate_binary = TRUE - if(keyslot.syndie) - syndie = TRUE - if(keyslot.independent) - independent = TRUE - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -// Used for cyborg override -/obj/item/radio/proc/resetChannels() - channels = list() - translate_binary = FALSE - syndie = FALSE - independent = FALSE - -/obj/item/radio/proc/make_syndie() // Turns normal radios into Syndicate radios! - qdel(keyslot) - keyslot = new /obj/item/encryptionkey/syndicate - syndie = 1 - recalculateChannels() - -/obj/item/radio/Destroy() - remove_radio_all(src) //Just to be sure - QDEL_NULL(wires) - QDEL_NULL(keyslot) - return ..() - -/obj/item/radio/Initialize() - wires = new /datum/wires/radio(src) - if(prison_radio) - wires.cut(WIRE_TX) // OH GOD WHY - secure_radio_connections = new - . = ..() - frequency = sanitize_frequency(frequency, freerange) - set_frequency(frequency) - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) - -/obj/item/radio/interact(mob/user) - if(unscrewed && !isAI(user)) - wires.interact(user) - add_fingerprint(user) - else - ..() - -/obj/item/radio/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state) - . = ..() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - var/ui_width = 360 - var/ui_height = 106 - if(subspace_transmission) - if (channels.len > 0) - ui_height += 6 + channels.len * 21 - else - ui_height += 24 - ui = new(user, src, ui_key, "Radio", name, ui_width, ui_height, master_ui, state) - ui.open() - -/obj/item/radio/ui_data(mob/user) - var/list/data = list() - - data["broadcasting"] = broadcasting - data["listening"] = listening - data["frequency"] = frequency - data["minFrequency"] = freerange ? MIN_FREE_FREQ : MIN_FREQ - data["maxFrequency"] = freerange ? MAX_FREE_FREQ : MAX_FREQ - data["freqlock"] = freqlock - data["channels"] = list() - for(var/channel in channels) - data["channels"][channel] = channels[channel] & FREQ_LISTENING - data["command"] = command - data["useCommand"] = use_command - data["subspace"] = subspace_transmission - data["subspaceSwitchable"] = subspace_switchable - data["headset"] = FALSE - - return data - -/obj/item/radio/ui_act(action, params, datum/tgui/ui) - if(..()) - return - switch(action) - if("frequency") - if(freqlock) - return - var/tune = params["tune"] - var/adjust = text2num(params["adjust"]) - if(adjust) - tune = frequency + adjust * 10 - . = TRUE - else if(text2num(tune) != null) - tune = tune * 10 - . = TRUE - if(.) - set_frequency(sanitize_frequency(tune, freerange)) - if("listen") - listening = !listening - . = TRUE - if("broadcast") - broadcasting = !broadcasting - . = TRUE - if("channel") - var/channel = params["channel"] - if(!(channel in channels)) - return - if(channels[channel] & FREQ_LISTENING) - channels[channel] &= ~FREQ_LISTENING - else - channels[channel] |= FREQ_LISTENING - . = TRUE - if("command") - use_command = !use_command - . = TRUE - if("subspace") - if(subspace_switchable) - subspace_transmission = !subspace_transmission - if(!subspace_transmission) - channels = list() - else - recalculateChannels() - . = TRUE - -/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language) - if(!spans) - spans = list(M.speech_span) - if(!language) - language = M.get_selected_language() - INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language) - return ITALICS | REDUCE_RANGE - -/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language) - if(!on) - return // the device has to be on - if(!M || !message) - return - if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios - return - if(!M.IsVocal()) - return - - if(use_command) - spans |= SPAN_COMMAND - - /* - Roughly speaking, radios attempt to make a subspace transmission (which - is received, processed, and rebroadcast by the telecomms satellite) and - if that fails, they send a mundane radio transmission. - - Headsets cannot send/receive mundane transmissions, only subspace. - Syndicate radios can hear transmissions on all well-known frequencies. - CentCom radios can hear the CentCom frequency no matter what. - */ - - // From the channel, determine the frequency and get a reference to it. - var/freq - if(channel && channels && channels.len > 0) - if(channel == MODE_DEPARTMENT) - channel = channels[1] - freq = secure_radio_connections[channel] - if (!channels[channel]) // if the channel is turned off, don't broadcast - return - else - freq = frequency - channel = null - - // Nearby active jammers prevent the message from transmitting - var/turf/position = get_turf(src) - for(var/obj/item/jammer/jammer in GLOB.active_jammers) - var/turf/jammer_turf = get_turf(jammer) - if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range)) - return - - // Determine the identity information which will be attached to the signal. - var/atom/movable/virtualspeaker/speaker = new(null, M, src) - - // Construct the signal - var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) - - // Independent radios, on the CentCom frequency, reach all independent radios - if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_SUPERSPACE - signal.levels = list(0) // reaches all Z-levels - signal.broadcast() - return - - // All radios make an attempt to use the subspace system first - signal.send_to_receivers() - - // If the radio is subspace-only, that's all it can do - if (subspace_transmission) - return - - // Non-subspace radios will check in a couple of seconds, and if the signal - // was never received, send a mundane broadcast (no headsets). - addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) - -/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) - var/turf/T = get_turf(src) - if (signal.data["done"] && (T.z in signal.levels)) - return - - // Okay, the signal was never processed, send a mundane broadcast. - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_RADIO - signal.levels = list(T.z) - signal.broadcast() - -/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) - return - - if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) - // radios don't pick up whispers very well - raw_message = stars(raw_message) - else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) - // try to avoid being heard double - if (loc == speaker && ismob(speaker)) - var/mob/M = speaker - var/idx = M.get_held_index_of_item(src) - // left hands are odd slots - if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) - return - - talk_into(speaker, raw_message, , spans, language=message_language) - -// Checks if this radio can receive on the given frequency. -/obj/item/radio/proc/can_receive(freq, level) - // deny checks - if (!on || !listening || wires.is_cut(WIRE_RX)) - return FALSE - if (freq == FREQ_SYNDICATE && !syndie) - return FALSE - if (freq == FREQ_CENTCOM) - return independent // hard-ignores the z-level check - if (!(0 in level)) - var/turf/position = get_turf(src) - if(!position || !(position.z in level)) - return FALSE - - // allow checks: are we listening on that frequency? - if (freq == frequency) - return TRUE - for(var/ch_name in channels) - if(channels[ch_name] & FREQ_LISTENING) - //the GLOB.radiochannels list is located in communications.dm - if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) - return TRUE - return FALSE - - -/obj/item/radio/examine(mob/user) - . = ..() - if (frequency && in_range(src, user)) - . += "It is set to broadcast over the [frequency/10] frequency." - if (unscrewed) - . += "It can be attached and modified." - else - . += "It cannot be modified or attached." - -/obj/item/radio/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(W.tool_behaviour == TOOL_SCREWDRIVER) - unscrewed = !unscrewed - if(unscrewed) - to_chat(user, "The radio can now be attached and modified!") - else - to_chat(user, "The radio can no longer be modified or attached!") - else - return ..() - -/obj/item/radio/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - emped++ //There's been an EMP; better count it - var/curremp = emped //Remember which EMP this was - if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice - to_chat(loc, "\The [src] overloads.") - broadcasting = FALSE - listening = FALSE - for (var/ch_name in channels) - channels[ch_name] = 0 - on = FALSE - addtimer(CALLBACK(src, .proc/end_emp_effect, curremp), 200) - -/obj/item/radio/proc/end_emp_effect(curremp) - if(emped != curremp) //Don't fix it if it's been EMP'd again - return FALSE - emped = FALSE - on = TRUE - return TRUE - -/////////////////////////////// -//////////Borg Radios////////// -/////////////////////////////// -//Giving borgs their own radio to have some more room to work with -Sieve - -/obj/item/radio/borg - name = "cyborg radio" - subspace_transmission = TRUE - subspace_switchable = TRUE - dog_fashion = null - -/obj/item/radio/borg/resetChannels() - . = ..() - - var/mob/living/silicon/robot/R = loc - if(istype(R)) - for(var/ch_name in R.module.radio_channels) - channels[ch_name] = 1 - -/obj/item/radio/borg/syndicate - syndie = 1 - keyslot = new /obj/item/encryptionkey/syndicate - -/obj/item/radio/borg/syndicate/Initialize() - . = ..() - set_frequency(FREQ_SYNDICATE) - -/obj/item/radio/borg/attackby(obj/item/W, mob/user, params) - - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(keyslot) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - - if(keyslot) - var/turf/T = get_turf(user) - if(T) - keyslot.forceMove(T) - keyslot = null - - recalculateChannels() - to_chat(user, "You pop out the encryption key in the radio.") - - else - to_chat(user, "This radio doesn't have any encryption keys!") - - else if(istype(W, /obj/item/encryptionkey/)) - if(keyslot) - to_chat(user, "The radio can't hold another key!") - return - - if(!keyslot) - if(!user.transferItemToLoc(W, src)) - return - keyslot = W - - recalculateChannels() - - -/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. - listening = 0 // And it's nice to have a subtype too for future features. - dog_fashion = /datum/dog_fashion/back +#define FREQ_LISTENING (1<<0) + +/obj/item/radio + icon = 'icons/obj/radio.dmi' + name = "station bounced radio" + icon_state = "walkietalkie" + inhand_icon_state = "walkietalkie" + desc = "A basic handheld radio that communicates with local telecommunication networks." + dog_fashion = /datum/dog_fashion/back + + flags_1 = CONDUCT_1 | HEAR_1 + slot_flags = ITEM_SLOT_BELT + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=75, /datum/material/glass=25) + obj_flags = USES_TGUI + + var/on = TRUE + var/frequency = FREQ_COMMON + var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives. + var/emped = 0 // Tracks the number of EMPs currently stacked. + + var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby. + var/listening = TRUE // Whether the radio is currently receiving. + var/prison_radio = FALSE // If true, the transmit wire starts cut. + var/unscrewed = FALSE // Whether wires are accessible. Toggleable by screwdrivering. + var/freerange = FALSE // If true, the radio has access to the full spectrum. + var/subspace_transmission = FALSE // If true, the radio transmits and receives on subspace exclusively. + var/subspace_switchable = FALSE // If true, subspace_transmission can be toggled at will. + var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios. + var/use_command = FALSE // If true, broadcasts will be large and BOLD. + var/command = FALSE // If true, use_command can be toggled at will. + + // Encryption key handling + var/obj/item/encryptionkey/keyslot + var/translate_binary = FALSE // If true, can hear the special binary channel. + var/independent = FALSE // If true, can say/hear on the special CentCom channel. + var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel. + var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h). + var/list/secure_radio_connections + +/obj/item/radio/suicide_act(mob/living/user) + user.visible_message("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/radio/proc/set_frequency(new_frequency) + SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args) + remove_radio(src, frequency) + frequency = add_radio(src, new_frequency) + +/obj/item/radio/proc/recalculateChannels() + resetChannels() + + if(keyslot) + for(var/ch_name in keyslot.channels) + if(!(ch_name in channels)) + channels[ch_name] = keyslot.channels[ch_name] + + if(keyslot.translate_binary) + translate_binary = TRUE + if(keyslot.syndie) + syndie = TRUE + if(keyslot.independent) + independent = TRUE + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +// Used for cyborg override +/obj/item/radio/proc/resetChannels() + channels = list() + translate_binary = FALSE + syndie = FALSE + independent = FALSE + +/obj/item/radio/proc/make_syndie() // Turns normal radios into Syndicate radios! + qdel(keyslot) + keyslot = new /obj/item/encryptionkey/syndicate + syndie = 1 + recalculateChannels() + +/obj/item/radio/Destroy() + remove_radio_all(src) //Just to be sure + QDEL_NULL(wires) + QDEL_NULL(keyslot) + return ..() + +/obj/item/radio/Initialize() + wires = new /datum/wires/radio(src) + if(prison_radio) + wires.cut(WIRE_TX) // OH GOD WHY + secure_radio_connections = new + . = ..() + frequency = sanitize_frequency(frequency, freerange) + set_frequency(frequency) + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) + +/obj/item/radio/interact(mob/user) + if(unscrewed && !isAI(user)) + wires.interact(user) + add_fingerprint(user) + else + ..() + +/obj/item/radio/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state) + . = ..() + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + var/ui_width = 360 + var/ui_height = 106 + if(subspace_transmission) + if (channels.len > 0) + ui_height += 6 + channels.len * 21 + else + ui_height += 24 + ui = new(user, src, ui_key, "Radio", name, ui_width, ui_height, master_ui, state) + ui.open() + +/obj/item/radio/ui_data(mob/user) + var/list/data = list() + + data["broadcasting"] = broadcasting + data["listening"] = listening + data["frequency"] = frequency + data["minFrequency"] = freerange ? MIN_FREE_FREQ : MIN_FREQ + data["maxFrequency"] = freerange ? MAX_FREE_FREQ : MAX_FREQ + data["freqlock"] = freqlock + data["channels"] = list() + for(var/channel in channels) + data["channels"][channel] = channels[channel] & FREQ_LISTENING + data["command"] = command + data["useCommand"] = use_command + data["subspace"] = subspace_transmission + data["subspaceSwitchable"] = subspace_switchable + data["headset"] = FALSE + + return data + +/obj/item/radio/ui_act(action, params, datum/tgui/ui) + if(..()) + return + switch(action) + if("frequency") + if(freqlock) + return + var/tune = params["tune"] + var/adjust = text2num(params["adjust"]) + if(adjust) + tune = frequency + adjust * 10 + . = TRUE + else if(text2num(tune) != null) + tune = tune * 10 + . = TRUE + if(.) + set_frequency(sanitize_frequency(tune, freerange)) + if("listen") + listening = !listening + . = TRUE + if("broadcast") + broadcasting = !broadcasting + . = TRUE + if("channel") + var/channel = params["channel"] + if(!(channel in channels)) + return + if(channels[channel] & FREQ_LISTENING) + channels[channel] &= ~FREQ_LISTENING + else + channels[channel] |= FREQ_LISTENING + . = TRUE + if("command") + use_command = !use_command + . = TRUE + if("subspace") + if(subspace_switchable) + subspace_transmission = !subspace_transmission + if(!subspace_transmission) + channels = list() + else + recalculateChannels() + . = TRUE + +/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language) + if(!spans) + spans = list(M.speech_span) + if(!language) + language = M.get_selected_language() + INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language) + return ITALICS | REDUCE_RANGE + +/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language) + if(!on) + return // the device has to be on + if(!M || !message) + return + if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios + return + if(!M.IsVocal()) + return + + if(use_command) + spans |= SPAN_COMMAND + + /* + Roughly speaking, radios attempt to make a subspace transmission (which + is received, processed, and rebroadcast by the telecomms satellite) and + if that fails, they send a mundane radio transmission. + + Headsets cannot send/receive mundane transmissions, only subspace. + Syndicate radios can hear transmissions on all well-known frequencies. + CentCom radios can hear the CentCom frequency no matter what. + */ + + // From the channel, determine the frequency and get a reference to it. + var/freq + if(channel && channels && channels.len > 0) + if(channel == MODE_DEPARTMENT) + channel = channels[1] + freq = secure_radio_connections[channel] + if (!channels[channel]) // if the channel is turned off, don't broadcast + return + else + freq = frequency + channel = null + + // Nearby active jammers prevent the message from transmitting + var/turf/position = get_turf(src) + for(var/obj/item/jammer/jammer in GLOB.active_jammers) + var/turf/jammer_turf = get_turf(jammer) + if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range)) + return + + // Determine the identity information which will be attached to the signal. + var/atom/movable/virtualspeaker/speaker = new(null, M, src) + + // Construct the signal + var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) + + // Independent radios, on the CentCom frequency, reach all independent radios + if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_SUPERSPACE + signal.levels = list(0) // reaches all Z-levels + signal.broadcast() + return + + // All radios make an attempt to use the subspace system first + signal.send_to_receivers() + + // If the radio is subspace-only, that's all it can do + if (subspace_transmission) + return + + // Non-subspace radios will check in a couple of seconds, and if the signal + // was never received, send a mundane broadcast (no headsets). + addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) + +/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) + var/turf/T = get_turf(src) + if (signal.data["done"] && (T.z in signal.levels)) + return + + // Okay, the signal was never processed, send a mundane broadcast. + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_RADIO + signal.levels = list(T.z) + signal.broadcast() + +/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) + return + + if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) + // radios don't pick up whispers very well + raw_message = stars(raw_message) + else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) + // try to avoid being heard double + if (loc == speaker && ismob(speaker)) + var/mob/M = speaker + var/idx = M.get_held_index_of_item(src) + // left hands are odd slots + if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) + return + + talk_into(speaker, raw_message, , spans, language=message_language) + +// Checks if this radio can receive on the given frequency. +/obj/item/radio/proc/can_receive(freq, level) + // deny checks + if (!on || !listening || wires.is_cut(WIRE_RX)) + return FALSE + if (freq == FREQ_SYNDICATE && !syndie) + return FALSE + if (freq == FREQ_CENTCOM) + return independent // hard-ignores the z-level check + if (!(0 in level)) + var/turf/position = get_turf(src) + if(!position || !(position.z in level)) + return FALSE + + // allow checks: are we listening on that frequency? + if (freq == frequency) + return TRUE + for(var/ch_name in channels) + if(channels[ch_name] & FREQ_LISTENING) + //the GLOB.radiochannels list is located in communications.dm + if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) + return TRUE + return FALSE + + +/obj/item/radio/examine(mob/user) + . = ..() + if (frequency && in_range(src, user)) + . += "It is set to broadcast over the [frequency/10] frequency." + if (unscrewed) + . += "It can be attached and modified." + else + . += "It cannot be modified or attached." + +/obj/item/radio/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + unscrewed = !unscrewed + if(unscrewed) + to_chat(user, "The radio can now be attached and modified!") + else + to_chat(user, "The radio can no longer be modified or attached!") + else + return ..() + +/obj/item/radio/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + emped++ //There's been an EMP; better count it + var/curremp = emped //Remember which EMP this was + if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice + to_chat(loc, "\The [src] overloads.") + broadcasting = FALSE + listening = FALSE + for (var/ch_name in channels) + channels[ch_name] = 0 + on = FALSE + addtimer(CALLBACK(src, .proc/end_emp_effect, curremp), 200) + +/obj/item/radio/proc/end_emp_effect(curremp) + if(emped != curremp) //Don't fix it if it's been EMP'd again + return FALSE + emped = FALSE + on = TRUE + return TRUE + +/////////////////////////////// +//////////Borg Radios////////// +/////////////////////////////// +//Giving borgs their own radio to have some more room to work with -Sieve + +/obj/item/radio/borg + name = "cyborg radio" + subspace_transmission = TRUE + subspace_switchable = TRUE + dog_fashion = null + +/obj/item/radio/borg/resetChannels() + . = ..() + + var/mob/living/silicon/robot/R = loc + if(istype(R)) + for(var/ch_name in R.module.radio_channels) + channels[ch_name] = 1 + +/obj/item/radio/borg/syndicate + syndie = 1 + keyslot = new /obj/item/encryptionkey/syndicate + +/obj/item/radio/borg/syndicate/Initialize() + . = ..() + set_frequency(FREQ_SYNDICATE) + +/obj/item/radio/borg/attackby(obj/item/W, mob/user, params) + + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(keyslot) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + + if(keyslot) + var/turf/T = get_turf(user) + if(T) + keyslot.forceMove(T) + keyslot = null + + recalculateChannels() + to_chat(user, "You pop out the encryption key in the radio.") + + else + to_chat(user, "This radio doesn't have any encryption keys!") + + else if(istype(W, /obj/item/encryptionkey/)) + if(keyslot) + to_chat(user, "The radio can't hold another key!") + return + + if(!keyslot) + if(!user.transferItemToLoc(W, src)) + return + keyslot = W + + recalculateChannels() + + +/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. + listening = 0 // And it's nice to have a subtype too for future features. + dog_fashion = /datum/dog_fashion/back diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 78f841d5cea..69bc7154e3b 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -1,898 +1,898 @@ - -/* - -CONTAINS: -T-RAY -HEALTH ANALYZER -GAS ANALYZER -SLIME SCANNER -NANITE SCANNER -GENE SCANNER - -*/ - -// Describes the three modes of scanning available for health analyzers -#define SCANMODE_HEALTH 0 -#define SCANMODE_CHEMICAL 1 -#define SCANMODE_WOUND 2 -#define SCANNER_CONDENSED 0 -#define SCANNER_VERBOSE 1 - -/obj/item/t_scanner - name = "\improper T-ray scanner" - desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." - custom_price = 150 - icon = 'icons/obj/device.dmi' - icon_state = "t-ray0" - var/on = FALSE - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - custom_materials = list(/datum/material/iron=150) - -/obj/item/t_scanner/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return TOXLOSS - -/obj/item/t_scanner/proc/toggle_on() - on = !on - icon_state = copytext_char(icon_state, 1, -1) + "[on]" - if(on) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - -/obj/item/t_scanner/attack_self(mob/user) - toggle_on() - -/obj/item/t_scanner/cyborg_unequip(mob/user) - if(!on) - return - toggle_on() - -/obj/item/t_scanner/process() - if(!on) - STOP_PROCESSING(SSobj, src) - return null - scan() - -/obj/item/t_scanner/proc/scan() - t_ray_scan(loc) - -/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) - if(!ismob(viewer) || !viewer.client) - return - var/list/t_ray_images = list() - for(var/obj/O in orange(distance, viewer) ) - if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) - var/image/I = new(loc = get_turf(O)) - var/mutable_appearance/MA = new(O) - MA.alpha = 128 - MA.dir = O.dir - I.appearance = MA - t_ray_images += I - if(t_ray_images.len) - flick_overlay(t_ray_images, list(viewer.client), flick_time) - -/obj/item/healthanalyzer - name = "health analyzer" - icon = 'icons/obj/device.dmi' - icon_state = "health" - inhand_icon_state = "healthanalyzer" - worn_icon_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A hand-held body scanner capable of distinguishing vital signs of the subject." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=200) - var/mode = SCANNER_VERBOSE - var/scanmode = SCANMODE_HEALTH - var/advanced = FALSE - custom_price = 300 - -/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") - return BRUTELOSS - -/obj/item/healthanalyzer/attack_self(mob/user) - scanmode = (scanmode + 1) % 3 - switch(scanmode) - if(SCANMODE_HEALTH) - to_chat(user, "You switch the health analyzer to check physical health.") - if(SCANMODE_CHEMICAL) - to_chat(user, "You switch the health analyzer to scan chemical contents.") - if(SCANMODE_WOUND) - to_chat(user, "You switch the health analyzer to report extra info on wounds.") - -/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user) - flick("[icon_state]-scan", src) //makes it so that it plays the scan animation upon scanning, including clumsy scanning - - // Clumsiness/brain damage check - if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) - user.visible_message("[user] analyzes the floor's vitals!", \ - "You stupidly try to analyze the floor's vitals!") - to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy\ - \nKey: Suffocation/Toxin/Burn/Brute\ - \n\tDamage specifics: 0-0-0-0\ - \nBody temperature: ???") - return - - if(ispodperson(M)&& !advanced) - to_chat(user, "[M]'s biologal structure is too complex for the health analyzer.") - return - - user.visible_message("[user] analyzes [M]'s vitals.", \ - "You analyze [M]'s vitals.") - - if(scanmode == SCANMODE_HEALTH) - healthscan(user, M, mode, advanced) - else if(scanmode == SCANMODE_CHEMICAL) - chemscan(user, M) - else - woundscan(user, M, src) - - add_fingerprint(user) - - -// Used by the PDA medical scanner too -/proc/healthscan(mob/user, mob/living/M, mode = SCANNER_VERBOSE, advanced = FALSE) - if(isliving(user) && (user.incapacitated() || user.is_blind())) - return - - // the final list of strings to render - var/render_list = list() - - // Damage specifics - var/oxy_loss = M.getOxyLoss() - var/tox_loss = M.getToxLoss() - var/fire_loss = M.getFireLoss() - var/brute_loss = M.getBruteLoss() - var/mob_status = (M.stat == DEAD ? "Deceased" : "[round(M.health/M.maxHealth,0.01)*100]% healthy") - - if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced) - mob_status = "Deceased" - oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.undergoing_cardiac_arrest() && H.stat != DEAD) - render_list += "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!\n" - - render_list += "Analyzing results for [M]:\nOverall status: [mob_status]\n" - - // Damage descriptions - if(brute_loss > 10) - render_list += "[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.\n" - if(fire_loss > 10) - render_list += "[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.\n" - if(oxy_loss > 10) - render_list += "[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.\n" - if(tox_loss > 10) - render_list += "[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.\n" - if(M.getStaminaLoss()) - render_list += "Subject appears to be suffering from fatigue.\n" - if(advanced) - render_list += "Fatigue Level: [M.getStaminaLoss()]%.\n" - if (M.getCloneLoss()) - render_list += "Subject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n" - if(advanced) - render_list += "Cellular Damage Level: [M.getCloneLoss()].\n" - if (!M.getorgan(/obj/item/organ/brain)) - render_list += "Subject lacks a brain.\n" - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(LAZYLEN(C.get_traumas())) - var/list/trauma_text = list() - for(var/datum/brain_trauma/B in C.get_traumas()) - var/trauma_desc = "" - switch(B.resilience) - if(TRAUMA_RESILIENCE_SURGERY) - trauma_desc += "severe " - if(TRAUMA_RESILIENCE_LOBOTOMY) - trauma_desc += "deep-rooted " - if(TRAUMA_RESILIENCE_WOUND) - trauma_desc += "fracture-derived " - if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) - trauma_desc += "permanent " - trauma_desc += B.scan_desc - trauma_text += trauma_desc - render_list += "Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].\n" - if(C.roundstart_quirks.len) - render_list += "Subject Major Disabilities: [C.get_quirk_string(FALSE, CAT_QUIRK_MAJOR_DISABILITY)].\n" - if(advanced) - render_list += "Subject Minor Disabilities: [C.get_quirk_string(FALSE, CAT_QUIRK_MINOR_DISABILITY)].\n" - if(advanced) - render_list += "Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n" - - if (M.radiation) - render_list += "Subject is irradiated.\n" - if(advanced) - render_list += "Radiation Level: [M.radiation]%.\n" - - if(advanced && M.hallucinating()) - render_list += "Subject is hallucinating.\n" - - // Body part damage report - if(iscarbon(M) && mode == SCANNER_VERBOSE) - var/mob/living/carbon/C = M - var/list/damaged = C.get_damaged_bodyparts(1,1) - if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0) - var/dmgreport = "General status:\ -
                [entry][functions]
                \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - " - - for(var/o in damaged) - var/obj/item/bodypart/org = o //head, left arm, right arm, etc. - dmgreport += "\ - \ - " - dmgreport += "
                Damage:BruteBurnToxinSuffocation
                Overall:[CEILING(brute_loss,1)][CEILING(fire_loss,1)][CEILING(tox_loss,1)][CEILING(oxy_loss,1)]
                [capitalize(org.name)]:[(org.brute_dam > 0) ? "[CEILING(org.brute_dam,1)]" : "0"][(org.burn_dam > 0) ? "[CEILING(org.burn_dam,1)]" : "0"]
                " - render_list += dmgreport // tables do not need extra linebreak - - //Eyes and ears - if(advanced && iscarbon(M)) - var/mob/living/carbon/C = M - - // Ear status - var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) - var/message = "\nSubject does not have ears." - if(istype(ears)) - message = "" - if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) - message = "\nSubject is genetically deaf." - else if(HAS_TRAIT_FROM(C, TRAIT_DEAF, EAR_DAMAGE)) - message = "\nSubject is deaf from ear damage." - else if(HAS_TRAIT(C, TRAIT_DEAF)) - message = "\nSubject is deaf." - else - if(ears.damage) - message += "\nSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." - if(ears.deaf) - message += "\nSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." - render_list += "Ear status:[message == "" ? "\nHealthy." : message]\n" - - // Eye status - var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) - message = "\nSubject does not have eyes." - if(istype(eyes)) - message = "" - if(C.is_blind()) - message += "\nSubject is blind." - if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) - message += "\nSubject is nearsighted." - if(eyes.damage > 30) - message += "\nSubject has severe eye damage." - else if(eyes.damage > 20) - message += "\nSubject has significant eye damage." - else if(eyes.damage) - message += "\nSubject has minor eye damage." - render_list += "Eye status:[message == "" ? "\nHealthy." : message]\n" - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - - // Organ damage - if (H.internal_organs && H.internal_organs.len) - var/render = FALSE - var/toReport = "Organs:\ - \ - \ - [advanced ? "" : ""]\ - " - - for(var/obj/item/organ/organ in H.internal_organs) - var/status = "" - if (organ.organ_flags & ORGAN_FAILING) status = "Non-Functional" - else if (organ.damage > organ.high_threshold) status = "Severely Damaged" - else if (organ.damage > organ.low_threshold) status = "Mildly Damaged" - if (status != "") - render = TRUE - toReport += "\ - [advanced ? "" : ""]\ - " - - if (render) - render_list += toReport + "
                OrganDmgStatus
                [organ.name][CEILING(organ.damage,1)][status]
                " // tables do not need extra linebreak - - //Genetic damage - if(advanced && H.has_dna()) - render_list += "Genetic Stability: [H.dna.stability]%.\n" - - // Species and body temperature - var/datum/species/S = H.dna.species - var/mutant = H.dna.check_mutation(HULK) \ - || S.mutantlungs != initial(S.mutantlungs) \ - || S.mutantbrain != initial(S.mutantbrain) \ - || S.mutantheart != initial(S.mutantheart) \ - || S.mutanteyes != initial(S.mutanteyes) \ - || S.mutantears != initial(S.mutantears) \ - || S.mutanthands != initial(S.mutanthands) \ - || S.mutanttongue != initial(S.mutanttongue) \ - || S.mutantliver != initial(S.mutantliver) \ - || S.mutantstomach != initial(S.mutantstomach) \ - || S.mutantappendix != initial(S.mutantappendix) \ - || S.flying_species != initial(S.flying_species) - - render_list += "Species: [S.name][mutant ? "-derived mutant" : ""]\n" - render_list += "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)\n" - - // Time of death - if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced))) - render_list += "Time of Death: [M.tod]\n" - var/tdelta = round(world.time - M.timeofdeath) - render_list += "Subject died [DisplayTimeText(tdelta)] ago.\n" - - // Wounds - if(iscarbon(M)) - var/mob/living/carbon/C = M - var/list/wounded_parts = C.get_wounded_bodyparts() - for(var/i in wounded_parts) - var/obj/item/bodypart/wounded_part = i - render_list += "Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]" - for(var/k in wounded_part.wounds) - var/datum/wound/W = k - render_list += "
                Type: [W.name]\nSeverity: [W.severity_text()]\nRecommended Treatment: [W.treat_text]
                \n" // less lines than in woundscan() so we don't overload people trying to get basic med info - render_list += "
                " - - for(var/thing in M.diseases) - var/datum/disease/D = thing - if(!(D.visibility_flags & HIDDEN_SCANNER)) - render_list += "Warning: [D.form] detected\n\ -
                Name: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]
                \ -
                " // divs do not need extra linebreak - - // Blood Level - if(M.has_dna()) - var/mob/living/carbon/C = M - var/blood_id = C.get_blood_id() - if(blood_id) - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(H.is_bleeding()) - render_list += "Subject is bleeding!\n" - var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100) - var/blood_type = C.dna.blood_type - if(blood_id != /datum/reagent/blood) // special blood substance - var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] - blood_type = R ? R.name : blood_id - if(C.blood_volume <= BLOOD_VOLUME_SAFE && C.blood_volume > BLOOD_VOLUME_OKAY) - render_list += "Blood level: LOW [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" - else if(C.blood_volume <= BLOOD_VOLUME_OKAY) - render_list += "Blood level: CRITICAL [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" - else - render_list += "Blood level: [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" - - var/cyberimp_detect - for(var/obj/item/organ/cyberimp/CI in C.internal_organs) - if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant) - cyberimp_detect += "[!cyberimp_detect ? "[CI.get_examine_string(user)]" : ", [CI.get_examine_string(user)]"]" - if(cyberimp_detect) - render_list += "Detected cybernetic modifications:\n" - render_list += "[cyberimp_detect]\n" - - SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) - to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                so we don't need handholding - -/proc/chemscan(mob/living/user, mob/living/M) - if(istype(M) && M.reagents) - var/render_list = list() - if(M.reagents.reagent_list.len) - render_list += "Subject contains the following reagents:\n" - for(var/datum/reagent/R in M.reagents.reagent_list) - render_list += "[round(R.volume, 0.001)] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n" - else - render_list += "Subject contains no reagents.\n" - if(M.reagents.addiction_list.len) - render_list += "Subject is addicted to the following reagents:\n" - for(var/datum/reagent/R in M.reagents.addiction_list) - render_list += "[R.name]\n" - else - render_list += "Subject is not addicted to any reagents.\n" - - to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                so we don't need handholding - -/obj/item/healthanalyzer/verb/toggle_mode() - set name = "Switch Verbosity" - set category = "Object" - - if(usr.incapacitated()) - return - - mode = !mode - to_chat(usr, mode == SCANNER_VERBOSE ? "The scanner now shows specific limb damage." : "The scanner no longer shows limb damage.") - -/obj/item/healthanalyzer/advanced - name = "advanced health analyzer" - icon_state = "health_adv" - desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy." - advanced = TRUE - -/// Displays wounds with extended information on their status vs medscanners -/proc/woundscan(mob/user, mob/living/carbon/patient, obj/item/healthanalyzer/wound/scanner) - if(!istype(patient)) - return - - var/render_list = "" - for(var/i in patient.get_wounded_bodyparts()) - var/obj/item/bodypart/wounded_part = i - render_list += "Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]" - for(var/k in wounded_part.wounds) - var/datum/wound/W = k - render_list += "
                [W.get_scanner_description()]
                \n" - render_list += "
                " - - if(render_list == "") - if(istype(scanner)) - // Only emit the cheerful scanner message if this scan came from a scanner - playsound(scanner, 'sound/machines/ping.ogg', 50, FALSE) - to_chat(user, "\The [scanner] makes a happy ping and briefly displays a smiley face with several exclamation points! It's really excited to report that [patient] has no wounds!") - else - to_chat(user, "No wounds detected in subject.") - else - to_chat(user, jointext(render_list, "")) - -/obj/item/healthanalyzer/wound - name = "first aid analyzer" - icon_state = "adv_spectrometer" - desc = "A prototype MeLo-Tech medical scanner used to diagnose injuries and recommend treatment for serious wounds, but offers no further insight into the patient's health. You hope the final version is less annoying to read!" - var/next_encouragement - var/greedy - -/obj/item/healthanalyzer/wound/attack_self(mob/user) - if(next_encouragement < world.time) - playsound(src, 'sound/machines/ping.ogg', 50, FALSE) - var/list/encouragements = list("briefly displays a happy face, gazing emptily at you", "briefly displays a spinning cartoon heart", "displays an encouraging message about eating healthy and exercising", \ - "reminds you that everyone is doing their best", "displays a message wishing you well", "displays a sincere thank-you for your interest in first-aid", "formally absolves you of all your sins") - to_chat(user, "\The [src] makes a happy ping and [pick(encouragements)]!") - next_encouragement = world.time + 10 SECONDS - greedy = FALSE - else if(!greedy) - to_chat(user, "\The [src] displays an eerily high-definition frowny face, chastizing you for asking it for too much encouragement.") - greedy = TRUE - else - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) - if(isliving(user)) - var/mob/living/L = user - to_chat(L, "\The [src] makes a disappointed buzz and pricks your finger for being greedy. Ow!") - L.adjustBruteLoss(4) - L.dropItemToGround(src) - -/obj/item/healthanalyzer/wound/attack(mob/living/carbon/patient, mob/living/carbon/human/user) - add_fingerprint(user) - user.visible_message("[user] scans [patient] for serious injuries.", "You scan [patient] for serious injuries.") - - if(!istype(patient)) - playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - to_chat(user, "\The [src] makes a sad buzz and briefly displays a frowny face, indicating it can't scan [patient].") - return - - woundscan(user, patient, src) - -/obj/item/analyzer - desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function." - name = "analyzer" - custom_price = 100 - icon = 'icons/obj/device.dmi' - icon_state = "analyzer" - inhand_icon_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - throw_speed = 3 - throw_range = 7 - tool_behaviour = TOOL_ANALYZER - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) - var/cooldown = FALSE - var/cooldown_time = 250 - var/accuracy // 0 is the best accuracy. - -/obj/item/analyzer/examine(mob/user) - . = ..() - . += "Alt-click [src] to activate the barometer function." - -/obj/item/analyzer/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") - return BRUTELOSS - -/obj/item/analyzer/attack_self(mob/user) - add_fingerprint(user) - - if (user.stat || user.is_blind()) - return - - var/turf/location = user.loc - if(!istype(location)) - return - - var/render_list = list() - var/datum/gas_mixture/environment = location.return_air() - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - render_list += "Results:\ - \nPressure: [round(pressure, 0.01)] kPa\n" - if(total_moles) - var/list/env_gases = environment.gases - - environment.assert_gases(arglist(GLOB.hardcoded_gases)) - var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles - - render_list += "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)\ - \nOxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 0.01)] mol)\ - \nCO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)\ - \nPlasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)\n" - - environment.garbage_collect() - - for(var/id in env_gases) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = env_gases[id][MOLES]/total_moles - render_list += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 0.01)] mol)\n" - render_list += "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)\n" - to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                so we don't need handholding - -/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens - ..() - - if(user.canUseTopic(src, BE_CLOSE)) - if(cooldown) - to_chat(user, "[src]'s barometer function is preparing itself.") - return - - var/turf/T = get_turf(user) - if(!T) - return - - playsound(src, 'sound/effects/pop.ogg', 100) - var/area/user_area = T.loc - var/datum/weather/ongoing_weather = null - - if(!user_area.outdoors) - to_chat(user, "[src]'s barometer function won't work indoors!") - return - - for(var/V in SSweather.processing) - var/datum/weather/W = V - if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) - ongoing_weather = W - break - - if(ongoing_weather) - if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) - to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") - return - - to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") - if(ongoing_weather.aesthetic) - to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") - else - var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] - var/fixed = next_hit ? timeleft(next_hit) : -1 - if(fixed < 0) - to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.") - else - to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].") - cooldown = TRUE - addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) - -/obj/item/analyzer/proc/ping() - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "[src]'s barometer function is ready!") - playsound(src, 'sound/machines/click.ogg', 100) - cooldown = FALSE - -/obj/item/analyzer/proc/butchertime(amount) - if(!amount) - return - if(accuracy) - var/inaccurate = round(accuracy*(1/3)) - if(prob(50)) - amount -= inaccurate - if(prob(50)) - amount += inaccurate - return DisplayTimeText(max(1,amount)) - -/proc/atmosanalyzer_scan(mob/user, atom/target, silent=FALSE) - var/mixture = target.return_analyzable_air() - if(!mixture) - return FALSE - - var/icon = target - var/render_list = list() - if(!silent && isliving(user)) - user.visible_message("[user] uses the analyzer on [icon2html(icon, viewers(user))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].") - render_list += "Results of analysis of [icon2html(icon, user)] [target]." - - var/list/airs = islist(mixture) ? mixture : list(mixture) - for(var/g in airs) - if(airs.len > 1) //not a unary gas mixture - render_list += "Node [airs.Find(g)]" - var/datum/gas_mixture/air_contents = g - - var/total_moles = air_contents.total_moles() - var/pressure = air_contents.return_pressure() - var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess? - var/temperature = air_contents.temperature - var/cached_scan_results = air_contents.analyzer_results - - if(total_moles > 0) - render_list += "Moles: [round(total_moles, 0.01)] mol\ - \nVolume: [volume] L\ - \nPressure: [round(pressure,0.01)] kPa" - - var/list/cached_gases = air_contents.gases - for(var/id in cached_gases) - var/gas_concentration = cached_gases[id][MOLES]/total_moles - render_list += "[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 0.01)] mol)" - render_list += "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)" - else - render_list += airs.len > 1 ? "This node is empty!" : "[target] is empty!" - - if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected - render_list += "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.\ - \nInstability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)]." - - to_chat(user, jointext(render_list, "\n")) // we let the join apply newlines so we do need handholding - return TRUE - -//slime scanner - -/obj/item/slime_scanner - name = "slime scanner" - desc = "A device that analyzes a slime's internal composition and measures its stats." - icon = 'icons/obj/device.dmi' - icon_state = "adv_spectrometer" - inhand_icon_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - throwforce = 0 - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - -/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) - if(user.stat || user.is_blind()) - return - if (!isslime(M)) - to_chat(user, "This device can only scan slimes!") - return - var/mob/living/simple_animal/slime/T = M - slime_scan(T, user) - -/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) - var/to_render = "========================\ - \nSlime scan results:\ - \n[T.colour] [T.is_adult ? "adult" : "baby"] slime\ - \nNutrition: [T.nutrition]/[T.get_max_nutrition()]" - if (T.nutrition < T.get_starve_nutrition()) - to_render += "\nWarning: slime is starving!" - else if (T.nutrition < T.get_hunger_nutrition()) - to_render += "\nWarning: slime is hungry" - to_render += "\nElectric change strength: [T.powerlevel]\nHealth: [round(T.health/T.maxHealth,0.01)*100]%" - if (T.slime_mutation[4] == T.colour) - to_render += "\nThis slime does not evolve any further." - else - if (T.slime_mutation[3] == T.slime_mutation[4]) - if (T.slime_mutation[2] == T.slime_mutation[1]) - to_render += "\nPossible mutation: [T.slime_mutation[3]]\ - \nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting" - else - to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)\ - \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" - else - to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]\ - \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" - if (T.cores > 1) - to_render += "\nMultiple cores detected" - to_render += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]" - if(T.effectmod) - to_render += "\nCore mutation in progress: [T.effectmod]\ - \nProgress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]" - to_chat(user, to_render + "\n========================") - - -/obj/item/nanite_scanner - name = "nanite scanner" - icon = 'icons/obj/device.dmi' - icon_state = "nanite_scanner" - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A hand-held body scanner able to detect nanites and their programming." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=200) - -/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user) - user.visible_message("[user] analyzes [M]'s nanites.", \ - "You analyze [M]'s nanites.") - - add_fingerprint(user) - - var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE) - if(!response) - to_chat(user, "No nanites detected in the subject.") - -/obj/item/sequence_scanner - name = "genetic sequence scanner" - icon = 'icons/obj/device.dmi' - icon_state = "gene" - inhand_icon_state = "healthanalyzer" - worn_icon_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A hand-held scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=200) - var/list/discovered = list() //hit a dna console to update the scanners database - var/list/buffer - var/ready = TRUE - var/cooldown = 200 - -/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) - add_fingerprint(user) - if (!HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species - user.visible_message("[user] analyzes [M]'s genetic sequence.", \ - "You analyze [M]'s genetic sequence.") - gene_scan(M, user) - - else - user.visible_message("[user] fails to analyze [M]'s genetic sequence.", "[M] has no readable genetic sequence!") - -/obj/item/sequence_scanner/attack_self(mob/user) - display_sequence(user) - -/obj/item/sequence_scanner/attack_self_tk(mob/user) - return - -/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) - . = ..() - if(!istype(O) || !proximity) - return - - if(istype(O, /obj/machinery/computer/scan_consolenew)) - var/obj/machinery/computer/scan_consolenew/C = O - if(C.stored_research) - to_chat(user, "[name] linked to central research database.") - discovered = C.stored_research.discovered_mutations - else - to_chat(user,"No database to update from.") - -/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) - if(!iscarbon(C) || !C.has_dna()) - return - buffer = C.dna.mutation_index - to_chat(user, "Subject [C.name]'s DNA sequence has been saved to buffer.") - if(LAZYLEN(buffer)) - for(var/A in buffer) - to_chat(user, "[get_display_name(A)]") - - -/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) - if(!LAZYLEN(buffer) || !ready) - return - var/list/options = list() - for(var/A in buffer) - options += get_display_name(A) - - var/answer = input(user, "Analyze Potential", "Sequence Analyzer") as null|anything in sortList(options) - if(answer && ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - var/sequence - for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list - if(get_display_name(A) == answer) - sequence = buffer[A] - break - - if(sequence) - var/display - for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1) - if(i) - display += "-" - display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) - - to_chat(user, "[display]
                ") - - ready = FALSE - icon_state = "[icon_state]_recharging" - addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) - -/obj/item/sequence_scanner/proc/recharge() - icon_state = initial(icon_state) - ready = TRUE - -/obj/item/sequence_scanner/proc/get_display_name(mutation) - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) - if(!HM) - return "ERROR" - if(mutation in discovered) - return "[HM.name] ([HM.alias])" - else - return HM.alias - -/obj/item/scanner_wand - name = "kiosk scanner wand" - icon = 'icons/obj/device.dmi' - icon_state = "scanner_wand" - inhand_icon_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A wand for scanning someone else for a medical analysis. Insert into a kiosk is make the scanned patient the target of a health scan." - force = 0 - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - var/selected_target = null - -/obj/item/scanner_wand/attack(mob/living/M, mob/living/carbon/human/user) - flick("[icon_state]_active", src) //nice little visual flash when scanning someone else. - - if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(25)) - user.visible_message("[user] targets himself for scanning.", \ - to_chat(user, "You try scanning [M], before realizing you're holding the scanner backwards. Whoops.")) - selected_target = user - return - - if(!ishuman(M)) - to_chat(user, "You can only scan human-like, non-robotic beings.") - selected_target = null - return - - user.visible_message("[user] targets [M] for scanning.", \ - "You target [M] vitals.") - selected_target = M - return - -/obj/item/scanner_wand/attack_self(mob/user) - to_chat(user, "You clear the scanner's target.") - selected_target = null - -/obj/item/scanner_wand/proc/return_patient() - var/returned_target = selected_target - return returned_target - -#undef SCANMODE_HEALTH -#undef SCANMODE_CHEMICAL -#undef SCANMODE_WOUND -#undef SCANNER_CONDENSED -#undef SCANNER_VERBOSE + +/* + +CONTAINS: +T-RAY +HEALTH ANALYZER +GAS ANALYZER +SLIME SCANNER +NANITE SCANNER +GENE SCANNER + +*/ + +// Describes the three modes of scanning available for health analyzers +#define SCANMODE_HEALTH 0 +#define SCANMODE_CHEMICAL 1 +#define SCANMODE_WOUND 2 +#define SCANNER_CONDENSED 0 +#define SCANNER_VERBOSE 1 + +/obj/item/t_scanner + name = "\improper T-ray scanner" + desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." + custom_price = 150 + icon = 'icons/obj/device.dmi' + icon_state = "t-ray0" + var/on = FALSE + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + custom_materials = list(/datum/material/iron=150) + +/obj/item/t_scanner/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return TOXLOSS + +/obj/item/t_scanner/proc/toggle_on() + on = !on + icon_state = copytext_char(icon_state, 1, -1) + "[on]" + if(on) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + +/obj/item/t_scanner/attack_self(mob/user) + toggle_on() + +/obj/item/t_scanner/cyborg_unequip(mob/user) + if(!on) + return + toggle_on() + +/obj/item/t_scanner/process() + if(!on) + STOP_PROCESSING(SSobj, src) + return null + scan() + +/obj/item/t_scanner/proc/scan() + t_ray_scan(loc) + +/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) + if(!ismob(viewer) || !viewer.client) + return + var/list/t_ray_images = list() + for(var/obj/O in orange(distance, viewer) ) + if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) + var/image/I = new(loc = get_turf(O)) + var/mutable_appearance/MA = new(O) + MA.alpha = 128 + MA.dir = O.dir + I.appearance = MA + t_ray_images += I + if(t_ray_images.len) + flick_overlay(t_ray_images, list(viewer.client), flick_time) + +/obj/item/healthanalyzer + name = "health analyzer" + icon = 'icons/obj/device.dmi' + icon_state = "health" + inhand_icon_state = "healthanalyzer" + worn_icon_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A hand-held body scanner capable of distinguishing vital signs of the subject." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + var/mode = SCANNER_VERBOSE + var/scanmode = SCANMODE_HEALTH + var/advanced = FALSE + custom_price = 300 + +/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") + return BRUTELOSS + +/obj/item/healthanalyzer/attack_self(mob/user) + scanmode = (scanmode + 1) % 3 + switch(scanmode) + if(SCANMODE_HEALTH) + to_chat(user, "You switch the health analyzer to check physical health.") + if(SCANMODE_CHEMICAL) + to_chat(user, "You switch the health analyzer to scan chemical contents.") + if(SCANMODE_WOUND) + to_chat(user, "You switch the health analyzer to report extra info on wounds.") + +/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user) + flick("[icon_state]-scan", src) //makes it so that it plays the scan animation upon scanning, including clumsy scanning + + // Clumsiness/brain damage check + if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) + user.visible_message("[user] analyzes the floor's vitals!", \ + "You stupidly try to analyze the floor's vitals!") + to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy\ + \nKey: Suffocation/Toxin/Burn/Brute\ + \n\tDamage specifics: 0-0-0-0\ + \nBody temperature: ???") + return + + if(ispodperson(M)&& !advanced) + to_chat(user, "[M]'s biologal structure is too complex for the health analyzer.") + return + + user.visible_message("[user] analyzes [M]'s vitals.", \ + "You analyze [M]'s vitals.") + + if(scanmode == SCANMODE_HEALTH) + healthscan(user, M, mode, advanced) + else if(scanmode == SCANMODE_CHEMICAL) + chemscan(user, M) + else + woundscan(user, M, src) + + add_fingerprint(user) + + +// Used by the PDA medical scanner too +/proc/healthscan(mob/user, mob/living/M, mode = SCANNER_VERBOSE, advanced = FALSE) + if(isliving(user) && (user.incapacitated() || user.is_blind())) + return + + // the final list of strings to render + var/render_list = list() + + // Damage specifics + var/oxy_loss = M.getOxyLoss() + var/tox_loss = M.getToxLoss() + var/fire_loss = M.getFireLoss() + var/brute_loss = M.getBruteLoss() + var/mob_status = (M.stat == DEAD ? "Deceased" : "[round(M.health/M.maxHealth,0.01)*100]% healthy") + + if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced) + mob_status = "Deceased" + oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.undergoing_cardiac_arrest() && H.stat != DEAD) + render_list += "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!\n" + + render_list += "Analyzing results for [M]:\nOverall status: [mob_status]\n" + + // Damage descriptions + if(brute_loss > 10) + render_list += "[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.\n" + if(fire_loss > 10) + render_list += "[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.\n" + if(oxy_loss > 10) + render_list += "[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.\n" + if(tox_loss > 10) + render_list += "[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.\n" + if(M.getStaminaLoss()) + render_list += "Subject appears to be suffering from fatigue.\n" + if(advanced) + render_list += "Fatigue Level: [M.getStaminaLoss()]%.\n" + if (M.getCloneLoss()) + render_list += "Subject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n" + if(advanced) + render_list += "Cellular Damage Level: [M.getCloneLoss()].\n" + if (!M.getorgan(/obj/item/organ/brain)) + render_list += "Subject lacks a brain.\n" + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(LAZYLEN(C.get_traumas())) + var/list/trauma_text = list() + for(var/datum/brain_trauma/B in C.get_traumas()) + var/trauma_desc = "" + switch(B.resilience) + if(TRAUMA_RESILIENCE_SURGERY) + trauma_desc += "severe " + if(TRAUMA_RESILIENCE_LOBOTOMY) + trauma_desc += "deep-rooted " + if(TRAUMA_RESILIENCE_WOUND) + trauma_desc += "fracture-derived " + if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) + trauma_desc += "permanent " + trauma_desc += B.scan_desc + trauma_text += trauma_desc + render_list += "Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].\n" + if(C.roundstart_quirks.len) + render_list += "Subject Major Disabilities: [C.get_quirk_string(FALSE, CAT_QUIRK_MAJOR_DISABILITY)].\n" + if(advanced) + render_list += "Subject Minor Disabilities: [C.get_quirk_string(FALSE, CAT_QUIRK_MINOR_DISABILITY)].\n" + if(advanced) + render_list += "Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n" + + if (M.radiation) + render_list += "Subject is irradiated.\n" + if(advanced) + render_list += "Radiation Level: [M.radiation]%.\n" + + if(advanced && M.hallucinating()) + render_list += "Subject is hallucinating.\n" + + // Body part damage report + if(iscarbon(M) && mode == SCANNER_VERBOSE) + var/mob/living/carbon/C = M + var/list/damaged = C.get_damaged_bodyparts(1,1) + if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0) + var/dmgreport = "General status:\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + + for(var/o in damaged) + var/obj/item/bodypart/org = o //head, left arm, right arm, etc. + dmgreport += "\ + \ + " + dmgreport += "
                Damage:BruteBurnToxinSuffocation
                Overall:[CEILING(brute_loss,1)][CEILING(fire_loss,1)][CEILING(tox_loss,1)][CEILING(oxy_loss,1)]
                [capitalize(org.name)]:[(org.brute_dam > 0) ? "[CEILING(org.brute_dam,1)]" : "0"][(org.burn_dam > 0) ? "[CEILING(org.burn_dam,1)]" : "0"]
                " + render_list += dmgreport // tables do not need extra linebreak + + //Eyes and ears + if(advanced && iscarbon(M)) + var/mob/living/carbon/C = M + + // Ear status + var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) + var/message = "\nSubject does not have ears." + if(istype(ears)) + message = "" + if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) + message = "\nSubject is genetically deaf." + else if(HAS_TRAIT_FROM(C, TRAIT_DEAF, EAR_DAMAGE)) + message = "\nSubject is deaf from ear damage." + else if(HAS_TRAIT(C, TRAIT_DEAF)) + message = "\nSubject is deaf." + else + if(ears.damage) + message += "\nSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." + if(ears.deaf) + message += "\nSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." + render_list += "Ear status:[message == "" ? "\nHealthy." : message]\n" + + // Eye status + var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) + message = "\nSubject does not have eyes." + if(istype(eyes)) + message = "" + if(C.is_blind()) + message += "\nSubject is blind." + if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) + message += "\nSubject is nearsighted." + if(eyes.damage > 30) + message += "\nSubject has severe eye damage." + else if(eyes.damage > 20) + message += "\nSubject has significant eye damage." + else if(eyes.damage) + message += "\nSubject has minor eye damage." + render_list += "Eye status:[message == "" ? "\nHealthy." : message]\n" + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + + // Organ damage + if (H.internal_organs && H.internal_organs.len) + var/render = FALSE + var/toReport = "Organs:\ + \ + \ + [advanced ? "" : ""]\ + " + + for(var/obj/item/organ/organ in H.internal_organs) + var/status = "" + if (organ.organ_flags & ORGAN_FAILING) status = "Non-Functional" + else if (organ.damage > organ.high_threshold) status = "Severely Damaged" + else if (organ.damage > organ.low_threshold) status = "Mildly Damaged" + if (status != "") + render = TRUE + toReport += "\ + [advanced ? "" : ""]\ + " + + if (render) + render_list += toReport + "
                OrganDmgStatus
                [organ.name][CEILING(organ.damage,1)][status]
                " // tables do not need extra linebreak + + //Genetic damage + if(advanced && H.has_dna()) + render_list += "Genetic Stability: [H.dna.stability]%.\n" + + // Species and body temperature + var/datum/species/S = H.dna.species + var/mutant = H.dna.check_mutation(HULK) \ + || S.mutantlungs != initial(S.mutantlungs) \ + || S.mutantbrain != initial(S.mutantbrain) \ + || S.mutantheart != initial(S.mutantheart) \ + || S.mutanteyes != initial(S.mutanteyes) \ + || S.mutantears != initial(S.mutantears) \ + || S.mutanthands != initial(S.mutanthands) \ + || S.mutanttongue != initial(S.mutanttongue) \ + || S.mutantliver != initial(S.mutantliver) \ + || S.mutantstomach != initial(S.mutantstomach) \ + || S.mutantappendix != initial(S.mutantappendix) \ + || S.flying_species != initial(S.flying_species) + + render_list += "Species: [S.name][mutant ? "-derived mutant" : ""]\n" + render_list += "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)\n" + + // Time of death + if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced))) + render_list += "Time of Death: [M.tod]\n" + var/tdelta = round(world.time - M.timeofdeath) + render_list += "Subject died [DisplayTimeText(tdelta)] ago.\n" + + // Wounds + if(iscarbon(M)) + var/mob/living/carbon/C = M + var/list/wounded_parts = C.get_wounded_bodyparts() + for(var/i in wounded_parts) + var/obj/item/bodypart/wounded_part = i + render_list += "Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]" + for(var/k in wounded_part.wounds) + var/datum/wound/W = k + render_list += "
                Type: [W.name]\nSeverity: [W.severity_text()]\nRecommended Treatment: [W.treat_text]
                \n" // less lines than in woundscan() so we don't overload people trying to get basic med info + render_list += "
                " + + for(var/thing in M.diseases) + var/datum/disease/D = thing + if(!(D.visibility_flags & HIDDEN_SCANNER)) + render_list += "Warning: [D.form] detected\n\ +
                Name: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]
                \ +
                " // divs do not need extra linebreak + + // Blood Level + if(M.has_dna()) + var/mob/living/carbon/C = M + var/blood_id = C.get_blood_id() + if(blood_id) + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(H.is_bleeding()) + render_list += "Subject is bleeding!\n" + var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100) + var/blood_type = C.dna.blood_type + if(blood_id != /datum/reagent/blood) // special blood substance + var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] + blood_type = R ? R.name : blood_id + if(C.blood_volume <= BLOOD_VOLUME_SAFE && C.blood_volume > BLOOD_VOLUME_OKAY) + render_list += "Blood level: LOW [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" + else if(C.blood_volume <= BLOOD_VOLUME_OKAY) + render_list += "Blood level: CRITICAL [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" + else + render_list += "Blood level: [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" + + var/cyberimp_detect + for(var/obj/item/organ/cyberimp/CI in C.internal_organs) + if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant) + cyberimp_detect += "[!cyberimp_detect ? "[CI.get_examine_string(user)]" : ", [CI.get_examine_string(user)]"]" + if(cyberimp_detect) + render_list += "Detected cybernetic modifications:\n" + render_list += "[cyberimp_detect]\n" + + SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) + to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                so we don't need handholding + +/proc/chemscan(mob/living/user, mob/living/M) + if(istype(M) && M.reagents) + var/render_list = list() + if(M.reagents.reagent_list.len) + render_list += "Subject contains the following reagents:\n" + for(var/datum/reagent/R in M.reagents.reagent_list) + render_list += "[round(R.volume, 0.001)] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : ".
                "]\n" + else + render_list += "Subject contains no reagents.\n" + if(M.reagents.addiction_list.len) + render_list += "Subject is addicted to the following reagents:\n" + for(var/datum/reagent/R in M.reagents.addiction_list) + render_list += "[R.name]\n" + else + render_list += "Subject is not addicted to any reagents.\n" + + to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                so we don't need handholding + +/obj/item/healthanalyzer/verb/toggle_mode() + set name = "Switch Verbosity" + set category = "Object" + + if(usr.incapacitated()) + return + + mode = !mode + to_chat(usr, mode == SCANNER_VERBOSE ? "The scanner now shows specific limb damage." : "The scanner no longer shows limb damage.") + +/obj/item/healthanalyzer/advanced + name = "advanced health analyzer" + icon_state = "health_adv" + desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy." + advanced = TRUE + +/// Displays wounds with extended information on their status vs medscanners +/proc/woundscan(mob/user, mob/living/carbon/patient, obj/item/healthanalyzer/wound/scanner) + if(!istype(patient)) + return + + var/render_list = "" + for(var/i in patient.get_wounded_bodyparts()) + var/obj/item/bodypart/wounded_part = i + render_list += "Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]" + for(var/k in wounded_part.wounds) + var/datum/wound/W = k + render_list += "
                [W.get_scanner_description()]
                \n" + render_list += "
                " + + if(render_list == "") + if(istype(scanner)) + // Only emit the cheerful scanner message if this scan came from a scanner + playsound(scanner, 'sound/machines/ping.ogg', 50, FALSE) + to_chat(user, "\The [scanner] makes a happy ping and briefly displays a smiley face with several exclamation points! It's really excited to report that [patient] has no wounds!") + else + to_chat(user, "No wounds detected in subject.") + else + to_chat(user, jointext(render_list, "")) + +/obj/item/healthanalyzer/wound + name = "first aid analyzer" + icon_state = "adv_spectrometer" + desc = "A prototype MeLo-Tech medical scanner used to diagnose injuries and recommend treatment for serious wounds, but offers no further insight into the patient's health. You hope the final version is less annoying to read!" + var/next_encouragement + var/greedy + +/obj/item/healthanalyzer/wound/attack_self(mob/user) + if(next_encouragement < world.time) + playsound(src, 'sound/machines/ping.ogg', 50, FALSE) + var/list/encouragements = list("briefly displays a happy face, gazing emptily at you", "briefly displays a spinning cartoon heart", "displays an encouraging message about eating healthy and exercising", \ + "reminds you that everyone is doing their best", "displays a message wishing you well", "displays a sincere thank-you for your interest in first-aid", "formally absolves you of all your sins") + to_chat(user, "\The [src] makes a happy ping and [pick(encouragements)]!") + next_encouragement = world.time + 10 SECONDS + greedy = FALSE + else if(!greedy) + to_chat(user, "\The [src] displays an eerily high-definition frowny face, chastizing you for asking it for too much encouragement.") + greedy = TRUE + else + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + if(isliving(user)) + var/mob/living/L = user + to_chat(L, "\The [src] makes a disappointed buzz and pricks your finger for being greedy. Ow!") + L.adjustBruteLoss(4) + L.dropItemToGround(src) + +/obj/item/healthanalyzer/wound/attack(mob/living/carbon/patient, mob/living/carbon/human/user) + add_fingerprint(user) + user.visible_message("[user] scans [patient] for serious injuries.", "You scan [patient] for serious injuries.") + + if(!istype(patient)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + to_chat(user, "\The [src] makes a sad buzz and briefly displays a frowny face, indicating it can't scan [patient].") + return + + woundscan(user, patient, src) + +/obj/item/analyzer + desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function." + name = "analyzer" + custom_price = 100 + icon = 'icons/obj/device.dmi' + icon_state = "analyzer" + inhand_icon_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + throw_speed = 3 + throw_range = 7 + tool_behaviour = TOOL_ANALYZER + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) + var/cooldown = FALSE + var/cooldown_time = 250 + var/accuracy // 0 is the best accuracy. + +/obj/item/analyzer/examine(mob/user) + . = ..() + . += "Alt-click [src] to activate the barometer function." + +/obj/item/analyzer/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") + return BRUTELOSS + +/obj/item/analyzer/attack_self(mob/user) + add_fingerprint(user) + + if (user.stat || user.is_blind()) + return + + var/turf/location = user.loc + if(!istype(location)) + return + + var/render_list = list() + var/datum/gas_mixture/environment = location.return_air() + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + render_list += "Results:\ + \nPressure: [round(pressure, 0.01)] kPa\n" + if(total_moles) + var/list/env_gases = environment.gases + + environment.assert_gases(arglist(GLOB.hardcoded_gases)) + var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles + var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles + var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles + var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles + + render_list += "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)\ + \nOxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 0.01)] mol)\ + \nCO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)\ + \nPlasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)\n" + + environment.garbage_collect() + + for(var/id in env_gases) + if(id in GLOB.hardcoded_gases) + continue + var/gas_concentration = env_gases[id][MOLES]/total_moles + render_list += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 0.01)] mol)\n" + render_list += "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)\n" + to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                so we don't need handholding + +/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens + ..() + + if(user.canUseTopic(src, BE_CLOSE)) + if(cooldown) + to_chat(user, "[src]'s barometer function is preparing itself.") + return + + var/turf/T = get_turf(user) + if(!T) + return + + playsound(src, 'sound/effects/pop.ogg', 100) + var/area/user_area = T.loc + var/datum/weather/ongoing_weather = null + + if(!user_area.outdoors) + to_chat(user, "[src]'s barometer function won't work indoors!") + return + + for(var/V in SSweather.processing) + var/datum/weather/W = V + if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) + ongoing_weather = W + break + + if(ongoing_weather) + if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) + to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") + return + + to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") + if(ongoing_weather.aesthetic) + to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") + else + var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] + var/fixed = next_hit ? timeleft(next_hit) : -1 + if(fixed < 0) + to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.") + else + to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].") + cooldown = TRUE + addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) + +/obj/item/analyzer/proc/ping() + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "[src]'s barometer function is ready!") + playsound(src, 'sound/machines/click.ogg', 100) + cooldown = FALSE + +/obj/item/analyzer/proc/butchertime(amount) + if(!amount) + return + if(accuracy) + var/inaccurate = round(accuracy*(1/3)) + if(prob(50)) + amount -= inaccurate + if(prob(50)) + amount += inaccurate + return DisplayTimeText(max(1,amount)) + +/proc/atmosanalyzer_scan(mob/user, atom/target, silent=FALSE) + var/mixture = target.return_analyzable_air() + if(!mixture) + return FALSE + + var/icon = target + var/render_list = list() + if(!silent && isliving(user)) + user.visible_message("[user] uses the analyzer on [icon2html(icon, viewers(user))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].") + render_list += "Results of analysis of [icon2html(icon, user)] [target]." + + var/list/airs = islist(mixture) ? mixture : list(mixture) + for(var/g in airs) + if(airs.len > 1) //not a unary gas mixture + render_list += "Node [airs.Find(g)]" + var/datum/gas_mixture/air_contents = g + + var/total_moles = air_contents.total_moles() + var/pressure = air_contents.return_pressure() + var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess? + var/temperature = air_contents.temperature + var/cached_scan_results = air_contents.analyzer_results + + if(total_moles > 0) + render_list += "Moles: [round(total_moles, 0.01)] mol\ + \nVolume: [volume] L\ + \nPressure: [round(pressure,0.01)] kPa" + + var/list/cached_gases = air_contents.gases + for(var/id in cached_gases) + var/gas_concentration = cached_gases[id][MOLES]/total_moles + render_list += "[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 0.01)] mol)" + render_list += "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)" + else + render_list += airs.len > 1 ? "This node is empty!" : "[target] is empty!" + + if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected + render_list += "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.\ + \nInstability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)]." + + to_chat(user, jointext(render_list, "\n")) // we let the join apply newlines so we do need handholding + return TRUE + +//slime scanner + +/obj/item/slime_scanner + name = "slime scanner" + desc = "A device that analyzes a slime's internal composition and measures its stats." + icon = 'icons/obj/device.dmi' + icon_state = "adv_spectrometer" + inhand_icon_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + throwforce = 0 + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + +/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) + if(user.stat || user.is_blind()) + return + if (!isslime(M)) + to_chat(user, "This device can only scan slimes!") + return + var/mob/living/simple_animal/slime/T = M + slime_scan(T, user) + +/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) + var/to_render = "========================\ + \nSlime scan results:\ + \n[T.colour] [T.is_adult ? "adult" : "baby"] slime\ + \nNutrition: [T.nutrition]/[T.get_max_nutrition()]" + if (T.nutrition < T.get_starve_nutrition()) + to_render += "\nWarning: slime is starving!" + else if (T.nutrition < T.get_hunger_nutrition()) + to_render += "\nWarning: slime is hungry" + to_render += "\nElectric change strength: [T.powerlevel]\nHealth: [round(T.health/T.maxHealth,0.01)*100]%" + if (T.slime_mutation[4] == T.colour) + to_render += "\nThis slime does not evolve any further." + else + if (T.slime_mutation[3] == T.slime_mutation[4]) + if (T.slime_mutation[2] == T.slime_mutation[1]) + to_render += "\nPossible mutation: [T.slime_mutation[3]]\ + \nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting" + else + to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)\ + \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" + else + to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]\ + \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" + if (T.cores > 1) + to_render += "\nMultiple cores detected" + to_render += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]" + if(T.effectmod) + to_render += "\nCore mutation in progress: [T.effectmod]\ + \nProgress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]" + to_chat(user, to_render + "\n========================") + + +/obj/item/nanite_scanner + name = "nanite scanner" + icon = 'icons/obj/device.dmi' + icon_state = "nanite_scanner" + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A hand-held body scanner able to detect nanites and their programming." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + +/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user) + user.visible_message("[user] analyzes [M]'s nanites.", \ + "You analyze [M]'s nanites.") + + add_fingerprint(user) + + var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE) + if(!response) + to_chat(user, "No nanites detected in the subject.") + +/obj/item/sequence_scanner + name = "genetic sequence scanner" + icon = 'icons/obj/device.dmi' + icon_state = "gene" + inhand_icon_state = "healthanalyzer" + worn_icon_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A hand-held scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + var/list/discovered = list() //hit a dna console to update the scanners database + var/list/buffer + var/ready = TRUE + var/cooldown = 200 + +/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) + add_fingerprint(user) + if (!HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species + user.visible_message("[user] analyzes [M]'s genetic sequence.", \ + "You analyze [M]'s genetic sequence.") + gene_scan(M, user) + + else + user.visible_message("[user] fails to analyze [M]'s genetic sequence.", "[M] has no readable genetic sequence!") + +/obj/item/sequence_scanner/attack_self(mob/user) + display_sequence(user) + +/obj/item/sequence_scanner/attack_self_tk(mob/user) + return + +/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) + . = ..() + if(!istype(O) || !proximity) + return + + if(istype(O, /obj/machinery/computer/scan_consolenew)) + var/obj/machinery/computer/scan_consolenew/C = O + if(C.stored_research) + to_chat(user, "[name] linked to central research database.") + discovered = C.stored_research.discovered_mutations + else + to_chat(user,"No database to update from.") + +/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) + if(!iscarbon(C) || !C.has_dna()) + return + buffer = C.dna.mutation_index + to_chat(user, "Subject [C.name]'s DNA sequence has been saved to buffer.") + if(LAZYLEN(buffer)) + for(var/A in buffer) + to_chat(user, "[get_display_name(A)]") + + +/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) + if(!LAZYLEN(buffer) || !ready) + return + var/list/options = list() + for(var/A in buffer) + options += get_display_name(A) + + var/answer = input(user, "Analyze Potential", "Sequence Analyzer") as null|anything in sortList(options) + if(answer && ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + var/sequence + for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list + if(get_display_name(A) == answer) + sequence = buffer[A] + break + + if(sequence) + var/display + for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1) + if(i) + display += "-" + display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) + + to_chat(user, "[display]
                ") + + ready = FALSE + icon_state = "[icon_state]_recharging" + addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) + +/obj/item/sequence_scanner/proc/recharge() + icon_state = initial(icon_state) + ready = TRUE + +/obj/item/sequence_scanner/proc/get_display_name(mutation) + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) + if(!HM) + return "ERROR" + if(mutation in discovered) + return "[HM.name] ([HM.alias])" + else + return HM.alias + +/obj/item/scanner_wand + name = "kiosk scanner wand" + icon = 'icons/obj/device.dmi' + icon_state = "scanner_wand" + inhand_icon_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A wand for scanning someone else for a medical analysis. Insert into a kiosk is make the scanned patient the target of a health scan." + force = 0 + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + var/selected_target = null + +/obj/item/scanner_wand/attack(mob/living/M, mob/living/carbon/human/user) + flick("[icon_state]_active", src) //nice little visual flash when scanning someone else. + + if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(25)) + user.visible_message("[user] targets himself for scanning.", \ + to_chat(user, "You try scanning [M], before realizing you're holding the scanner backwards. Whoops.")) + selected_target = user + return + + if(!ishuman(M)) + to_chat(user, "You can only scan human-like, non-robotic beings.") + selected_target = null + return + + user.visible_message("[user] targets [M] for scanning.", \ + "You target [M] vitals.") + selected_target = M + return + +/obj/item/scanner_wand/attack_self(mob/user) + to_chat(user, "You clear the scanner's target.") + selected_target = null + +/obj/item/scanner_wand/proc/return_patient() + var/returned_target = selected_target + return returned_target + +#undef SCANMODE_HEALTH +#undef SCANMODE_CHEMICAL +#undef SCANMODE_WOUND +#undef SCANNER_CONDENSED +#undef SCANNER_VERBOSE diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index e4d63db13eb..9f17cf21685 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -1,330 +1,330 @@ -/obj/item/taperecorder - name = "universal recorder" - desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." - icon = 'icons/obj/device.dmi' - icon_state = "taperecorder_empty" - inhand_icon_state = "analyzer" - worn_icon_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = HEAR_1 - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=60, /datum/material/glass=30) - force = 2 - throwforce = 0 - var/recording = 0 - var/playing = 0 - var/playsleepseconds = 0 - var/obj/item/tape/mytape - var/starting_tape_type = /obj/item/tape/random - var/open_panel = 0 - var/canprint = 1 - var/list/icons_available = list() - var/icon_directory = 'icons/effects/icons.dmi' - - -/obj/item/taperecorder/Initialize(mapload) - . = ..() - if(starting_tape_type) - mytape = new starting_tape_type(src) - update_icon() - - -/obj/item/taperecorder/examine(mob/user) - . = ..() - . += "The wire panel is [open_panel ? "opened" : "closed"]." - -/obj/item/taperecorder/AltClick(mob/user) - . = ..() - play() - -/obj/item/taperecorder/proc/update_available_icons() - icons_available = list() - - if(recording) - icons_available += list("Stop Recording" = image(icon = icon_directory, icon_state = "record_stop")) - else - if(!playing) - icons_available += list("Record" = image(icon = icon_directory, icon_state = "record")) - - if(playing) - icons_available += list("Pause" = image(icon = icon_directory, icon_state = "pause")) - else - if(!recording) - icons_available += list("Play" = image(icon = icon_directory, icon_state = "play")) - - if(canprint && !recording && !playing) - icons_available += list("Print Transcript" = image(icon = icon_directory, icon_state = "print")) - if(mytape) - icons_available += list("Eject" = image(icon = icon_directory, icon_state = "eject")) - -/obj/item/taperecorder/attackby(obj/item/I, mob/user, params) - if(!mytape && istype(I, /obj/item/tape)) - if(!user.transferItemToLoc(I,src)) - return - mytape = I - to_chat(user, "You insert [I] into [src].") - update_icon() - - -/obj/item/taperecorder/proc/eject(mob/user) - if(mytape) - to_chat(user, "You remove [mytape] from [src].") - stop() - user.put_in_hands(mytape) - mytape = null - update_icon() - -/obj/item/taperecorder/fire_act(exposed_temperature, exposed_volume) - mytape.ruin() //Fires destroy the tape - ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/taperecorder/attack_hand(mob/user) - if(loc != user || !mytape || !user.is_holding(src)) - return ..() - eject(user) - -/obj/item/taperecorder/proc/can_use(mob/user) - if(user && ismob(user)) - if(!user.incapacitated()) - return TRUE - return FALSE - - -/obj/item/taperecorder/verb/ejectverb() - set name = "Eject Tape" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape) - return - - eject(usr) - - -/obj/item/taperecorder/update_icon_state() - if(!mytape) - icon_state = "taperecorder_empty" - else if(recording) - icon_state = "taperecorder_recording" - else if(playing) - icon_state = "taperecorder_playing" - else - icon_state = "taperecorder_idle" - - -/obj/item/taperecorder/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) - . = ..() - if(mytape && recording) - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [message]" - -/obj/item/taperecorder/verb/record() - set name = "Start Recording" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape || mytape.ruined) - return - if(recording) - return - if(playing) - return - - if(mytape.used_capacity < mytape.max_capacity) - to_chat(usr, "Recording started.") - recording = 1 - update_icon() - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording started." - var/used = mytape.used_capacity //to stop runtimes when you eject the tape - var/max = mytape.max_capacity - while(recording && used < max) - mytape.used_capacity++ - used++ - sleep(10) - recording = 0 - update_icon() - else - to_chat(usr, "The tape is full.") - - -/obj/item/taperecorder/verb/stop() - set name = "Stop" - set category = "Object" - - if(!can_use(usr)) - return - - if(recording) - recording = 0 - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording stopped." - to_chat(usr, "Recording stopped.") - return - else if(playing) - playing = 0 - var/turf/T = get_turf(src) - T.visible_message("Tape Recorder: Playback stopped.") - update_icon() - - -/obj/item/taperecorder/verb/play() - set name = "Play Tape" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape || mytape.ruined) - return - if(recording) - return - if(playing) - return - - playing = 1 - update_icon() - to_chat(usr, "Playing started.") - var/used = mytape.used_capacity //to stop runtimes when you eject the tape - var/max = mytape.max_capacity - for(var/i = 1, used < max, sleep(10 * playsleepseconds)) - if(!mytape) - break - if(playing == 0) - break - if(mytape.storedinfo.len < i) - break - say(mytape.storedinfo[i]) - if(mytape.storedinfo.len < i + 1) - playsleepseconds = 1 - sleep(10) - say("End of recording.") - else - playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] - if(playsleepseconds > 14) - sleep(10) - say("Skipping [playsleepseconds] seconds of silence") - playsleepseconds = 1 - i++ - - playing = 0 - update_icon() - - -/obj/item/taperecorder/attack_self(mob/user) - if(!mytape) - to_chat(user, "The [src] does not have a tape inside.") - return - if(mytape.ruined) - to_chat(user, "The tape inside the [src] appears to be broken.") - return - - update_available_icons() - if(icons_available) - var/selection = show_radial_menu(user, src, icons_available, radius = 38, require_near = TRUE, tooltips = TRUE) - if(!selection) - return - switch(selection) - if("Pause") - stop() - if("Stop Recording") // yes we actually need 2 seperate stops for the same proc- Hopek - stop() - if("Record") - record() - if("Play") - play() - if("Print Transcript") - print_transcript() - if("Eject") - eject(user) - -/obj/item/taperecorder/verb/print_transcript() - set name = "Print Transcript" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape) - return - if(!canprint) - to_chat(usr, "The recorder can't print that fast!") - return - if(recording || playing) - return - - to_chat(usr, "Transcript printed.") - var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) - var/t1 = "Transcript:

                " - for(var/i = 1, mytape.storedinfo.len >= i, i++) - t1 += "[mytape.storedinfo[i]]
                " - P.info = t1 - P.name = "paper- 'Transcript'" - usr.put_in_hands(P) - canprint = FALSE - addtimer(VARSET_CALLBACK(src, canprint, TRUE), 30 SECONDS) - - -//empty tape recorders -/obj/item/taperecorder/empty - starting_tape_type = null - - -/obj/item/tape - name = "tape" - desc = "A magnetic tape that can hold up to ten minutes of content." - icon_state = "tape_white" - icon = 'icons/obj/device.dmi' - inhand_icon_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/iron=20, /datum/material/glass=5) - force = 1 - throwforce = 0 - var/max_capacity = 600 - var/used_capacity = 0 - var/list/storedinfo = list() - var/list/timestamp = list() - var/ruined = 0 - -/obj/item/tape/fire_act(exposed_temperature, exposed_volume) - ruin() - ..() - -/obj/item/tape/attack_self(mob/user) - if(!ruined) - to_chat(user, "You pull out all the tape!") - ruin() - - -/obj/item/tape/proc/ruin() - //Lets not add infinite amounts of overlays when our fireact is called - //repeatedly - if(!ruined) - add_overlay("ribbonoverlay") - ruined = 1 - - -/obj/item/tape/proc/fix() - cut_overlay("ribbonoverlay") - ruined = 0 - - -/obj/item/tape/attackby(obj/item/I, mob/user, params) - if(ruined && I.tool_behaviour == TOOL_SCREWDRIVER || istype(I, /obj/item/pen)) - to_chat(user, "You start winding the tape back in...") - if(I.use_tool(src, user, 120)) - to_chat(user, "You wound the tape back in.") - fix() - -//Random colour tapes -/obj/item/tape/random - icon_state = "random_tape" - -/obj/item/tape/random/Initialize() - . = ..() - icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" +/obj/item/taperecorder + name = "universal recorder" + desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." + icon = 'icons/obj/device.dmi' + icon_state = "taperecorder_empty" + inhand_icon_state = "analyzer" + worn_icon_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = HEAR_1 + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=60, /datum/material/glass=30) + force = 2 + throwforce = 0 + var/recording = 0 + var/playing = 0 + var/playsleepseconds = 0 + var/obj/item/tape/mytape + var/starting_tape_type = /obj/item/tape/random + var/open_panel = 0 + var/canprint = 1 + var/list/icons_available = list() + var/icon_directory = 'icons/effects/icons.dmi' + + +/obj/item/taperecorder/Initialize(mapload) + . = ..() + if(starting_tape_type) + mytape = new starting_tape_type(src) + update_icon() + + +/obj/item/taperecorder/examine(mob/user) + . = ..() + . += "The wire panel is [open_panel ? "opened" : "closed"]." + +/obj/item/taperecorder/AltClick(mob/user) + . = ..() + play() + +/obj/item/taperecorder/proc/update_available_icons() + icons_available = list() + + if(recording) + icons_available += list("Stop Recording" = image(icon = icon_directory, icon_state = "record_stop")) + else + if(!playing) + icons_available += list("Record" = image(icon = icon_directory, icon_state = "record")) + + if(playing) + icons_available += list("Pause" = image(icon = icon_directory, icon_state = "pause")) + else + if(!recording) + icons_available += list("Play" = image(icon = icon_directory, icon_state = "play")) + + if(canprint && !recording && !playing) + icons_available += list("Print Transcript" = image(icon = icon_directory, icon_state = "print")) + if(mytape) + icons_available += list("Eject" = image(icon = icon_directory, icon_state = "eject")) + +/obj/item/taperecorder/attackby(obj/item/I, mob/user, params) + if(!mytape && istype(I, /obj/item/tape)) + if(!user.transferItemToLoc(I,src)) + return + mytape = I + to_chat(user, "You insert [I] into [src].") + update_icon() + + +/obj/item/taperecorder/proc/eject(mob/user) + if(mytape) + to_chat(user, "You remove [mytape] from [src].") + stop() + user.put_in_hands(mytape) + mytape = null + update_icon() + +/obj/item/taperecorder/fire_act(exposed_temperature, exposed_volume) + mytape.ruin() //Fires destroy the tape + ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/taperecorder/attack_hand(mob/user) + if(loc != user || !mytape || !user.is_holding(src)) + return ..() + eject(user) + +/obj/item/taperecorder/proc/can_use(mob/user) + if(user && ismob(user)) + if(!user.incapacitated()) + return TRUE + return FALSE + + +/obj/item/taperecorder/verb/ejectverb() + set name = "Eject Tape" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape) + return + + eject(usr) + + +/obj/item/taperecorder/update_icon_state() + if(!mytape) + icon_state = "taperecorder_empty" + else if(recording) + icon_state = "taperecorder_recording" + else if(playing) + icon_state = "taperecorder_playing" + else + icon_state = "taperecorder_idle" + + +/obj/item/taperecorder/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) + . = ..() + if(mytape && recording) + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [message]" + +/obj/item/taperecorder/verb/record() + set name = "Start Recording" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape || mytape.ruined) + return + if(recording) + return + if(playing) + return + + if(mytape.used_capacity < mytape.max_capacity) + to_chat(usr, "Recording started.") + recording = 1 + update_icon() + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording started." + var/used = mytape.used_capacity //to stop runtimes when you eject the tape + var/max = mytape.max_capacity + while(recording && used < max) + mytape.used_capacity++ + used++ + sleep(10) + recording = 0 + update_icon() + else + to_chat(usr, "The tape is full.") + + +/obj/item/taperecorder/verb/stop() + set name = "Stop" + set category = "Object" + + if(!can_use(usr)) + return + + if(recording) + recording = 0 + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording stopped." + to_chat(usr, "Recording stopped.") + return + else if(playing) + playing = 0 + var/turf/T = get_turf(src) + T.visible_message("Tape Recorder: Playback stopped.") + update_icon() + + +/obj/item/taperecorder/verb/play() + set name = "Play Tape" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape || mytape.ruined) + return + if(recording) + return + if(playing) + return + + playing = 1 + update_icon() + to_chat(usr, "Playing started.") + var/used = mytape.used_capacity //to stop runtimes when you eject the tape + var/max = mytape.max_capacity + for(var/i = 1, used < max, sleep(10 * playsleepseconds)) + if(!mytape) + break + if(playing == 0) + break + if(mytape.storedinfo.len < i) + break + say(mytape.storedinfo[i]) + if(mytape.storedinfo.len < i + 1) + playsleepseconds = 1 + sleep(10) + say("End of recording.") + else + playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] + if(playsleepseconds > 14) + sleep(10) + say("Skipping [playsleepseconds] seconds of silence") + playsleepseconds = 1 + i++ + + playing = 0 + update_icon() + + +/obj/item/taperecorder/attack_self(mob/user) + if(!mytape) + to_chat(user, "The [src] does not have a tape inside.") + return + if(mytape.ruined) + to_chat(user, "The tape inside the [src] appears to be broken.") + return + + update_available_icons() + if(icons_available) + var/selection = show_radial_menu(user, src, icons_available, radius = 38, require_near = TRUE, tooltips = TRUE) + if(!selection) + return + switch(selection) + if("Pause") + stop() + if("Stop Recording") // yes we actually need 2 seperate stops for the same proc- Hopek + stop() + if("Record") + record() + if("Play") + play() + if("Print Transcript") + print_transcript() + if("Eject") + eject(user) + +/obj/item/taperecorder/verb/print_transcript() + set name = "Print Transcript" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape) + return + if(!canprint) + to_chat(usr, "The recorder can't print that fast!") + return + if(recording || playing) + return + + to_chat(usr, "Transcript printed.") + var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) + var/t1 = "Transcript:

                " + for(var/i = 1, mytape.storedinfo.len >= i, i++) + t1 += "[mytape.storedinfo[i]]
                " + P.info = t1 + P.name = "paper- 'Transcript'" + usr.put_in_hands(P) + canprint = FALSE + addtimer(VARSET_CALLBACK(src, canprint, TRUE), 30 SECONDS) + + +//empty tape recorders +/obj/item/taperecorder/empty + starting_tape_type = null + + +/obj/item/tape + name = "tape" + desc = "A magnetic tape that can hold up to ten minutes of content." + icon_state = "tape_white" + icon = 'icons/obj/device.dmi' + inhand_icon_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/iron=20, /datum/material/glass=5) + force = 1 + throwforce = 0 + var/max_capacity = 600 + var/used_capacity = 0 + var/list/storedinfo = list() + var/list/timestamp = list() + var/ruined = 0 + +/obj/item/tape/fire_act(exposed_temperature, exposed_volume) + ruin() + ..() + +/obj/item/tape/attack_self(mob/user) + if(!ruined) + to_chat(user, "You pull out all the tape!") + ruin() + + +/obj/item/tape/proc/ruin() + //Lets not add infinite amounts of overlays when our fireact is called + //repeatedly + if(!ruined) + add_overlay("ribbonoverlay") + ruined = 1 + + +/obj/item/tape/proc/fix() + cut_overlay("ribbonoverlay") + ruined = 0 + + +/obj/item/tape/attackby(obj/item/I, mob/user, params) + if(ruined && I.tool_behaviour == TOOL_SCREWDRIVER || istype(I, /obj/item/pen)) + to_chat(user, "You start winding the tape back in...") + if(I.use_tool(src, user, 120)) + to_chat(user, "You wound the tape back in.") + fix() + +//Random colour tapes +/obj/item/tape/random + icon_state = "random_tape" + +/obj/item/tape/random/Initialize() + . = ..() + icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index da8c0ba2104..b8e4455226c 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -1,249 +1,249 @@ -/obj/item/transfer_valve - icon = 'icons/obj/assemblies.dmi' - name = "tank transfer valve" - icon_state = "valve_1" - inhand_icon_state = "ttv" - lefthand_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' - desc = "Regulates the transfer of air between two tanks." - w_class = WEIGHT_CLASS_BULKY - var/ui_x = 310 - var/ui_y = 320 - var/obj/item/tank/tank_one - var/obj/item/tank/tank_two - var/obj/item/assembly/attached_device - var/mob/attacher = null - var/valve_open = FALSE - var/toggle = TRUE - -/obj/item/transfer_valve/IsAssemblyHolder() - return TRUE - -/obj/item/transfer_valve/attackby(obj/item/item, mob/user, params) - if(istype(item, /obj/item/tank)) - if(tank_one && tank_two) - to_chat(user, "There are already two tanks attached, remove one first!") - return - - if(!tank_one) - if(!user.transferItemToLoc(item, src)) - return - tank_one = item - to_chat(user, "You attach the tank to the transfer valve.") - else if(!tank_two) - if(!user.transferItemToLoc(item, src)) - return - tank_two = item - to_chat(user, "You attach the tank to the transfer valve.") - - update_icon() -//TODO: Have this take an assemblyholder - else if(isassembly(item)) - var/obj/item/assembly/A = item - if(A.secured) - to_chat(user, "The device is secured.") - return - if(attached_device) - to_chat(user, "There is already a device attached to the valve, remove it first!") - return - if(!user.transferItemToLoc(item, src)) - return - attached_device = A - to_chat(user, "You attach the [item] to the valve controls and secure it.") - A.holder = src - A.toggle_secure() //this calls update_icon(), which calls update_icon() on the holder (i.e. the bomb). - log_bomber(user, "attached a [item.name] to a ttv -", src, null, FALSE) - attacher = user - return - -//These keep attached devices synced up, for example a TTV with a mouse trap being found in a bag so it's triggered, or moving the TTV with an infrared beam sensor to update the beam's direction. -/obj/item/transfer_valve/Move() - . = ..() - if(attached_device) - attached_device.holder_movement() - -/obj/item/transfer_valve/dropped() - . = ..() - if(attached_device) - attached_device.dropped() - -/obj/item/transfer_valve/on_found(mob/finder) - if(attached_device) - attached_device.on_found(finder) - -/obj/item/transfer_valve/Crossed(atom/movable/AM as mob|obj) - . = ..() - if(attached_device) - attached_device.Crossed(AM) - -//Triggers mousetraps -/obj/item/transfer_valve/attack_hand() - . = ..() - if(.) - return - if(attached_device) - attached_device.attack_hand() - -/obj/item/transfer_valve/proc/process_activation(obj/item/D) - if(toggle) - toggle = FALSE - toggle_valve() - addtimer(CALLBACK(src, .proc/toggle_off), 5) //To stop a signal being spammed from a proxy sensor constantly going off or whatever - -/obj/item/transfer_valve/proc/toggle_off() - toggle = TRUE - -/obj/item/transfer_valve/update_icon() - cut_overlays() - - if(!tank_one && !tank_two && !attached_device) - icon_state = "valve_1" - return - icon_state = "valve" - - if(tank_one) - add_overlay("[tank_one.icon_state]") - if(tank_two) - var/mutable_appearance/J = mutable_appearance(icon, icon_state = "[tank_two.icon_state]") - var/matrix/T = matrix() - T.Translate(-13, 0) - J.transform = T - underlays = list(J) - else - underlays = null - if(attached_device) - add_overlay("device") - if(istype(attached_device, /obj/item/assembly/infra)) - var/obj/item/assembly/infra/sensor = attached_device - if(sensor.on && sensor.visible) - add_overlay("proxy_beam") - -/obj/item/transfer_valve/proc/merge_gases(datum/gas_mixture/target, change_volume = TRUE) - var/target_self = FALSE - if(!target || (target == tank_one.air_contents)) - target = tank_two.air_contents - if(target == tank_two.air_contents) - target_self = TRUE - if(change_volume) - if(!target_self) - target.volume += tank_two.volume - target.volume += tank_one.air_contents.volume - var/datum/gas_mixture/temp - temp = tank_one.air_contents.remove_ratio(1) - target.merge(temp) - if(!target_self) - temp = tank_two.air_contents.remove_ratio(1) - target.merge(temp) - -/obj/item/transfer_valve/proc/split_gases() - if (!valve_open || !tank_one || !tank_two) - return - var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume - var/datum/gas_mixture/temp - temp = tank_two.air_contents.remove_ratio(ratio1) - tank_one.air_contents.merge(temp) - tank_two.air_contents.volume -= tank_one.air_contents.volume - -/* - Exadv1: I know this isn't how it's going to work, but this was just to check - it explodes properly when it gets a signal (and it does). -*/ -/obj/item/transfer_valve/proc/toggle_valve() - if(!valve_open && tank_one && tank_two) - valve_open = TRUE - var/turf/bombturf = get_turf(src) - - var/attachment - if(attached_device) - if(istype(attached_device, /obj/item/assembly/signaler)) - attachment = "[attached_device]" - else - attachment = attached_device - - var/admin_attachment_message - var/attachment_message - if(attachment) - admin_attachment_message = " with [attachment] attached by [attacher ? ADMIN_LOOKUPFLW(attacher) : "Unknown"]" - attachment_message = " with [attachment] attached by [attacher ? key_name_admin(attacher) : "Unknown"]" - - var/mob/bomber = get_mob_by_key(fingerprintslast) - var/admin_bomber_message - var/bomber_message - if(bomber) - admin_bomber_message = " - Last touched by: [ADMIN_LOOKUPFLW(bomber)]" - bomber_message = " - Last touched by: [key_name_admin(bomber)]" - - var/admin_bomb_message = "Bomb valve opened in [ADMIN_VERBOSEJMP(bombturf)][admin_attachment_message][admin_bomber_message]" - GLOB.bombers += admin_bomb_message - message_admins(admin_bomb_message) - log_game("Bomb valve opened in [AREACOORD(bombturf)][attachment_message][bomber_message]") - - merge_gases() - for(var/i in 1 to 6) - addtimer(CALLBACK(src, /atom/.proc/update_icon), 20 + (i - 1) * 10) - - else if(valve_open && tank_one && tank_two) - split_gases() - valve_open = FALSE - update_icon() -/* - This doesn't do anything but the timer etc. expects it to be here - eventually maybe have it update icon to show state (timer, prox etc.) like old bombs -*/ -/obj/item/transfer_valve/proc/c_state() - return - -/obj/item/transfer_valve/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "TransferValve", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/transfer_valve/ui_data(mob/user) - var/list/data = list() - data["tank_one"] = tank_one - data["tank_two"] = tank_two - data["attached_device"] = attached_device - data["valve"] = valve_open - return data - -/obj/item/transfer_valve/ui_act(action, params) - if(..()) - return - - switch(action) - if("tankone") - if(tank_one) - split_gases() - valve_open = FALSE - tank_one.forceMove(drop_location()) - tank_one = null - . = TRUE - if("tanktwo") - if(tank_two) - split_gases() - valve_open = FALSE - tank_two.forceMove(drop_location()) - tank_two = null - . = TRUE - if("toggle") - toggle_valve() - . = TRUE - if("device") - if(attached_device) - attached_device.attack_self(usr) - . = TRUE - if("remove_device") - if(attached_device) - attached_device.on_detach() - attached_device = null - . = TRUE - - update_icon() - -/** - * Returns if this is ready to be detonated. Checks if both tanks are in place. - */ -/obj/item/transfer_valve/proc/ready() - return tank_one && tank_two +/obj/item/transfer_valve + icon = 'icons/obj/assemblies.dmi' + name = "tank transfer valve" + icon_state = "valve_1" + inhand_icon_state = "ttv" + lefthand_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' + desc = "Regulates the transfer of air between two tanks." + w_class = WEIGHT_CLASS_BULKY + var/ui_x = 310 + var/ui_y = 320 + var/obj/item/tank/tank_one + var/obj/item/tank/tank_two + var/obj/item/assembly/attached_device + var/mob/attacher = null + var/valve_open = FALSE + var/toggle = TRUE + +/obj/item/transfer_valve/IsAssemblyHolder() + return TRUE + +/obj/item/transfer_valve/attackby(obj/item/item, mob/user, params) + if(istype(item, /obj/item/tank)) + if(tank_one && tank_two) + to_chat(user, "There are already two tanks attached, remove one first!") + return + + if(!tank_one) + if(!user.transferItemToLoc(item, src)) + return + tank_one = item + to_chat(user, "You attach the tank to the transfer valve.") + else if(!tank_two) + if(!user.transferItemToLoc(item, src)) + return + tank_two = item + to_chat(user, "You attach the tank to the transfer valve.") + + update_icon() +//TODO: Have this take an assemblyholder + else if(isassembly(item)) + var/obj/item/assembly/A = item + if(A.secured) + to_chat(user, "The device is secured.") + return + if(attached_device) + to_chat(user, "There is already a device attached to the valve, remove it first!") + return + if(!user.transferItemToLoc(item, src)) + return + attached_device = A + to_chat(user, "You attach the [item] to the valve controls and secure it.") + A.holder = src + A.toggle_secure() //this calls update_icon(), which calls update_icon() on the holder (i.e. the bomb). + log_bomber(user, "attached a [item.name] to a ttv -", src, null, FALSE) + attacher = user + return + +//These keep attached devices synced up, for example a TTV with a mouse trap being found in a bag so it's triggered, or moving the TTV with an infrared beam sensor to update the beam's direction. +/obj/item/transfer_valve/Move() + . = ..() + if(attached_device) + attached_device.holder_movement() + +/obj/item/transfer_valve/dropped() + . = ..() + if(attached_device) + attached_device.dropped() + +/obj/item/transfer_valve/on_found(mob/finder) + if(attached_device) + attached_device.on_found(finder) + +/obj/item/transfer_valve/Crossed(atom/movable/AM as mob|obj) + . = ..() + if(attached_device) + attached_device.Crossed(AM) + +//Triggers mousetraps +/obj/item/transfer_valve/attack_hand() + . = ..() + if(.) + return + if(attached_device) + attached_device.attack_hand() + +/obj/item/transfer_valve/proc/process_activation(obj/item/D) + if(toggle) + toggle = FALSE + toggle_valve() + addtimer(CALLBACK(src, .proc/toggle_off), 5) //To stop a signal being spammed from a proxy sensor constantly going off or whatever + +/obj/item/transfer_valve/proc/toggle_off() + toggle = TRUE + +/obj/item/transfer_valve/update_icon() + cut_overlays() + + if(!tank_one && !tank_two && !attached_device) + icon_state = "valve_1" + return + icon_state = "valve" + + if(tank_one) + add_overlay("[tank_one.icon_state]") + if(tank_two) + var/mutable_appearance/J = mutable_appearance(icon, icon_state = "[tank_two.icon_state]") + var/matrix/T = matrix() + T.Translate(-13, 0) + J.transform = T + underlays = list(J) + else + underlays = null + if(attached_device) + add_overlay("device") + if(istype(attached_device, /obj/item/assembly/infra)) + var/obj/item/assembly/infra/sensor = attached_device + if(sensor.on && sensor.visible) + add_overlay("proxy_beam") + +/obj/item/transfer_valve/proc/merge_gases(datum/gas_mixture/target, change_volume = TRUE) + var/target_self = FALSE + if(!target || (target == tank_one.air_contents)) + target = tank_two.air_contents + if(target == tank_two.air_contents) + target_self = TRUE + if(change_volume) + if(!target_self) + target.volume += tank_two.volume + target.volume += tank_one.air_contents.volume + var/datum/gas_mixture/temp + temp = tank_one.air_contents.remove_ratio(1) + target.merge(temp) + if(!target_self) + temp = tank_two.air_contents.remove_ratio(1) + target.merge(temp) + +/obj/item/transfer_valve/proc/split_gases() + if (!valve_open || !tank_one || !tank_two) + return + var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume + var/datum/gas_mixture/temp + temp = tank_two.air_contents.remove_ratio(ratio1) + tank_one.air_contents.merge(temp) + tank_two.air_contents.volume -= tank_one.air_contents.volume + +/* + Exadv1: I know this isn't how it's going to work, but this was just to check + it explodes properly when it gets a signal (and it does). +*/ +/obj/item/transfer_valve/proc/toggle_valve() + if(!valve_open && tank_one && tank_two) + valve_open = TRUE + var/turf/bombturf = get_turf(src) + + var/attachment + if(attached_device) + if(istype(attached_device, /obj/item/assembly/signaler)) + attachment = "[attached_device]" + else + attachment = attached_device + + var/admin_attachment_message + var/attachment_message + if(attachment) + admin_attachment_message = " with [attachment] attached by [attacher ? ADMIN_LOOKUPFLW(attacher) : "Unknown"]" + attachment_message = " with [attachment] attached by [attacher ? key_name_admin(attacher) : "Unknown"]" + + var/mob/bomber = get_mob_by_key(fingerprintslast) + var/admin_bomber_message + var/bomber_message + if(bomber) + admin_bomber_message = " - Last touched by: [ADMIN_LOOKUPFLW(bomber)]" + bomber_message = " - Last touched by: [key_name_admin(bomber)]" + + var/admin_bomb_message = "Bomb valve opened in [ADMIN_VERBOSEJMP(bombturf)][admin_attachment_message][admin_bomber_message]" + GLOB.bombers += admin_bomb_message + message_admins(admin_bomb_message) + log_game("Bomb valve opened in [AREACOORD(bombturf)][attachment_message][bomber_message]") + + merge_gases() + for(var/i in 1 to 6) + addtimer(CALLBACK(src, /atom/.proc/update_icon), 20 + (i - 1) * 10) + + else if(valve_open && tank_one && tank_two) + split_gases() + valve_open = FALSE + update_icon() +/* + This doesn't do anything but the timer etc. expects it to be here + eventually maybe have it update icon to show state (timer, prox etc.) like old bombs +*/ +/obj/item/transfer_valve/proc/c_state() + return + +/obj/item/transfer_valve/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "TransferValve", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/transfer_valve/ui_data(mob/user) + var/list/data = list() + data["tank_one"] = tank_one + data["tank_two"] = tank_two + data["attached_device"] = attached_device + data["valve"] = valve_open + return data + +/obj/item/transfer_valve/ui_act(action, params) + if(..()) + return + + switch(action) + if("tankone") + if(tank_one) + split_gases() + valve_open = FALSE + tank_one.forceMove(drop_location()) + tank_one = null + . = TRUE + if("tanktwo") + if(tank_two) + split_gases() + valve_open = FALSE + tank_two.forceMove(drop_location()) + tank_two = null + . = TRUE + if("toggle") + toggle_valve() + . = TRUE + if("device") + if(attached_device) + attached_device.attack_self(usr) + . = TRUE + if("remove_device") + if(attached_device) + attached_device.on_detach() + attached_device = null + . = TRUE + + update_icon() + +/** + * Returns if this is ready to be detonated. Checks if both tanks are in place. + */ +/obj/item/transfer_valve/proc/ready() + return tank_one && tank_two diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm index 35d3e469403..205caa3f600 100644 --- a/code/game/objects/items/dice.dm +++ b/code/game/objects/items/dice.dm @@ -1,225 +1,225 @@ -/*****************************Dice Bags********************************/ - -/obj/item/storage/pill_bottle/dice - name = "bag of dice" - desc = "Contains all the luck you'll ever need." - icon = 'icons/obj/dice.dmi' - icon_state = "dicebag" - var/list/special_die = list( - /obj/item/dice/d1, - /obj/item/dice/d2, - /obj/item/dice/fudge, - /obj/item/dice/d6/space, - /obj/item/dice/d00, - /obj/item/dice/eightbd20, - /obj/item/dice/fourdd6, - /obj/item/dice/d100 - ) - -/obj/item/storage/pill_bottle/dice/PopulateContents() - new /obj/item/dice/d4(src) - new /obj/item/dice/d6(src) - new /obj/item/dice/d8(src) - new /obj/item/dice/d10(src) - new /obj/item/dice/d12(src) - new /obj/item/dice/d20(src) - var/picked = pick(special_die) - new picked(src) - -/obj/item/storage/pill_bottle/dice/suicide_act(mob/user) - user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/storage/pill_bottle/dice/hazard - -/obj/item/storage/pill_bottle/dice/hazard/PopulateContents() - new /obj/item/dice/d6(src) - new /obj/item/dice/d6(src) - new /obj/item/dice/d6(src) - for(var/i in 1 to 2) - if(prob(7)) - new /obj/item/dice/d6/ebony(src) - else - new /obj/item/dice/d6(src) - -/*****************************Dice********************************/ - -/obj/item/dice //depreciated d6, use /obj/item/dice/d6 if you actually want a d6 - name = "die" - desc = "A die with six sides. Basic and serviceable." - icon = 'icons/obj/dice.dmi' - icon_state = "d6" - w_class = WEIGHT_CLASS_TINY - var/sides = 6 - var/result = null - var/list/special_faces = list() //entries should match up to sides var if used - var/microwave_riggable = TRUE - - var/rigged = DICE_NOT_RIGGED - var/rigged_value - -/obj/item/dice/Initialize() - . = ..() - if(!result) - result = roll(sides) - update_icon() - -/obj/item/dice/suicide_act(mob/user) - user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/dice/d1 - name = "d1" - desc = "A die with only one side. Deterministic!" - icon_state = "d1" - sides = 1 - -/obj/item/dice/d2 - name = "d2" - desc = "A die with two sides. Coins are undignified!" - icon_state = "d2" - sides = 2 - -/obj/item/dice/d4 - name = "d4" - desc = "A die with four sides. The nerd's caltrop." - icon_state = "d4" - sides = 4 - -/obj/item/dice/d4/Initialize(mapload) - . = ..() - AddComponent(/datum/component/caltrop, 1, 4) //1d4 damage - -/obj/item/dice/d6 - name = "d6" - -obj/item/dice/d6/ebony - name = "ebony die" - desc = "A die with six sides made of dense black wood. It feels cold and heavy in your hand." - icon_state = "de6" - microwave_riggable = FALSE // You can't melt wood in the microwave - -/obj/item/dice/d6/space - name = "space cube" - desc = "A die with six sides. 6 TIMES 255 TIMES 255 TILE TOTAL EXISTENCE, SQUARE YOUR MIND OF EDUCATED STUPID: 2 DOES NOT EXIST." - icon_state = "spaced6" - -/obj/item/dice/d6/space/Initialize() - . = ..() - if(prob(10)) - name = "spess cube" - -/obj/item/dice/fudge - name = "fudge die" - desc = "A die with six sides but only three results. Is this a plus or a minus? Your mind is drawing a blank..." - sides = 3 //shhh - icon_state = "fudge" - special_faces = list("minus","blank","plus") - -/obj/item/dice/d8 - name = "d8" - desc = "A die with eight sides. It feels... lucky." - icon_state = "d8" - sides = 8 - -/obj/item/dice/d10 - name = "d10" - desc = "A die with ten sides. Useful for percentages." - icon_state = "d10" - sides = 10 - -/obj/item/dice/d00 - name = "d00" - desc = "A die with ten sides. Works better for d100 rolls than a golf ball." - icon_state = "d00" - sides = 10 - -/obj/item/dice/d12 - name = "d12" - desc = "A die with twelve sides. There's an air of neglect about it." - icon_state = "d12" - sides = 12 - -/obj/item/dice/d20 - name = "d20" - desc = "A die with twenty sides. The preferred die to throw at the GM." - icon_state = "d20" - sides = 20 - -/obj/item/dice/d100 - name = "d100" - desc = "A die with one hundred sides! Probably not fairly weighted..." - icon_state = "d100" - w_class = WEIGHT_CLASS_SMALL - sides = 100 - -/obj/item/dice/d100/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/dice/eightbd20 - name = "strange d20" - desc = "A weird die with raised text printed on the faces. Everything's white on white so reading it is a struggle. What poor design!" - icon_state = "8bd20" - sides = 20 - special_faces = list("It is certain","It is decidedly so","Without a doubt","Yes, definitely","You may rely on it","As I see it, yes","Most likely","Outlook good","Yes","Signs point to yes","Reply hazy try again","Ask again later","Better not tell you now","Cannot predict now","Concentrate and ask again","Don't count on it","My reply is no","My sources say no","Outlook not so good","Very doubtful") - -/obj/item/dice/eightbd20/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/dice/fourdd6 - name = "4d d6" - desc = "A die that exists in four dimensional space. Properly interpreting them can only be done with the help of a mathematician, a physicist, and a priest." - icon_state = "4dd6" - sides = 48 - special_faces = list("Cube-Side: 1-1","Cube-Side: 1-2","Cube-Side: 1-3","Cube-Side: 1-4","Cube-Side: 1-5","Cube-Side: 1-6","Cube-Side: 2-1","Cube-Side: 2-2","Cube-Side: 2-3","Cube-Side: 2-4","Cube-Side: 2-5","Cube-Side: 2-6","Cube-Side: 3-1","Cube-Side: 3-2","Cube-Side: 3-3","Cube-Side: 3-4","Cube-Side: 3-5","Cube-Side: 3-6","Cube-Side: 4-1","Cube-Side: 4-2","Cube-Side: 4-3","Cube-Side: 4-4","Cube-Side: 4-5","Cube-Side: 4-6","Cube-Side: 5-1","Cube-Side: 5-2","Cube-Side: 5-3","Cube-Side: 5-4","Cube-Side: 5-5","Cube-Side: 5-6","Cube-Side: 6-1","Cube-Side: 6-2","Cube-Side: 6-3","Cube-Side: 6-4","Cube-Side: 6-5","Cube-Side: 6-6","Cube-Side: 7-1","Cube-Side: 7-2","Cube-Side: 7-3","Cube-Side: 7-4","Cube-Side: 7-5","Cube-Side: 7-6","Cube-Side: 8-1","Cube-Side: 8-2","Cube-Side: 8-3","Cube-Side: 8-4","Cube-Side: 8-5","Cube-Side: 8-6") - -/obj/item/dice/fourdd6/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/dice/attack_self(mob/user) - diceroll(user) - -/obj/item/dice/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - diceroll(thrownby) - . = ..() - -/obj/item/dice/proc/diceroll(mob/user) - result = roll(sides) - if(rigged != DICE_NOT_RIGGED && result != rigged_value) - if(rigged == DICE_BASICALLY_RIGGED && prob(clamp(1/(sides - 1) * 100, 25, 80))) - result = rigged_value - else if(rigged == DICE_TOTALLY_RIGGED) - result = rigged_value - - . = result - - var/fake_result = roll(sides)//Daredevil isn't as good as he used to be - var/comment = "" - if(sides == 20 && result == 20) - comment = "NAT 20!" - else if(sides == 20 && result == 1) - comment = "Ouch, bad luck." - update_icon() - if(initial(icon_state) == "d00") - result = (result - 1)*10 - if(special_faces.len == sides) - result = special_faces[result] - if(user != null) //Dice was rolled in someone's hand - user.visible_message("[user] throws [src]. It lands on [result]. [comment]", \ - "You throw [src]. It lands on [result]. [comment]", \ - "You hear [src] rolling, it sounds like a [fake_result].") - else if(!src.throwing) //Dice was thrown and is coming to rest - visible_message("[src] rolls to a stop, landing on [result]. [comment]") - -/obj/item/dice/update_overlays() - . = ..() - . += "[icon_state]-[result]" - -/obj/item/dice/microwave_act(obj/machinery/microwave/M) - if(microwave_riggable) - rigged = DICE_BASICALLY_RIGGED - rigged_value = result - ..(M) +/*****************************Dice Bags********************************/ + +/obj/item/storage/pill_bottle/dice + name = "bag of dice" + desc = "Contains all the luck you'll ever need." + icon = 'icons/obj/dice.dmi' + icon_state = "dicebag" + var/list/special_die = list( + /obj/item/dice/d1, + /obj/item/dice/d2, + /obj/item/dice/fudge, + /obj/item/dice/d6/space, + /obj/item/dice/d00, + /obj/item/dice/eightbd20, + /obj/item/dice/fourdd6, + /obj/item/dice/d100 + ) + +/obj/item/storage/pill_bottle/dice/PopulateContents() + new /obj/item/dice/d4(src) + new /obj/item/dice/d6(src) + new /obj/item/dice/d8(src) + new /obj/item/dice/d10(src) + new /obj/item/dice/d12(src) + new /obj/item/dice/d20(src) + var/picked = pick(special_die) + new picked(src) + +/obj/item/storage/pill_bottle/dice/suicide_act(mob/user) + user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/storage/pill_bottle/dice/hazard + +/obj/item/storage/pill_bottle/dice/hazard/PopulateContents() + new /obj/item/dice/d6(src) + new /obj/item/dice/d6(src) + new /obj/item/dice/d6(src) + for(var/i in 1 to 2) + if(prob(7)) + new /obj/item/dice/d6/ebony(src) + else + new /obj/item/dice/d6(src) + +/*****************************Dice********************************/ + +/obj/item/dice //depreciated d6, use /obj/item/dice/d6 if you actually want a d6 + name = "die" + desc = "A die with six sides. Basic and serviceable." + icon = 'icons/obj/dice.dmi' + icon_state = "d6" + w_class = WEIGHT_CLASS_TINY + var/sides = 6 + var/result = null + var/list/special_faces = list() //entries should match up to sides var if used + var/microwave_riggable = TRUE + + var/rigged = DICE_NOT_RIGGED + var/rigged_value + +/obj/item/dice/Initialize() + . = ..() + if(!result) + result = roll(sides) + update_icon() + +/obj/item/dice/suicide_act(mob/user) + user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/dice/d1 + name = "d1" + desc = "A die with only one side. Deterministic!" + icon_state = "d1" + sides = 1 + +/obj/item/dice/d2 + name = "d2" + desc = "A die with two sides. Coins are undignified!" + icon_state = "d2" + sides = 2 + +/obj/item/dice/d4 + name = "d4" + desc = "A die with four sides. The nerd's caltrop." + icon_state = "d4" + sides = 4 + +/obj/item/dice/d4/Initialize(mapload) + . = ..() + AddComponent(/datum/component/caltrop, 1, 4) //1d4 damage + +/obj/item/dice/d6 + name = "d6" + +obj/item/dice/d6/ebony + name = "ebony die" + desc = "A die with six sides made of dense black wood. It feels cold and heavy in your hand." + icon_state = "de6" + microwave_riggable = FALSE // You can't melt wood in the microwave + +/obj/item/dice/d6/space + name = "space cube" + desc = "A die with six sides. 6 TIMES 255 TIMES 255 TILE TOTAL EXISTENCE, SQUARE YOUR MIND OF EDUCATED STUPID: 2 DOES NOT EXIST." + icon_state = "spaced6" + +/obj/item/dice/d6/space/Initialize() + . = ..() + if(prob(10)) + name = "spess cube" + +/obj/item/dice/fudge + name = "fudge die" + desc = "A die with six sides but only three results. Is this a plus or a minus? Your mind is drawing a blank..." + sides = 3 //shhh + icon_state = "fudge" + special_faces = list("minus","blank","plus") + +/obj/item/dice/d8 + name = "d8" + desc = "A die with eight sides. It feels... lucky." + icon_state = "d8" + sides = 8 + +/obj/item/dice/d10 + name = "d10" + desc = "A die with ten sides. Useful for percentages." + icon_state = "d10" + sides = 10 + +/obj/item/dice/d00 + name = "d00" + desc = "A die with ten sides. Works better for d100 rolls than a golf ball." + icon_state = "d00" + sides = 10 + +/obj/item/dice/d12 + name = "d12" + desc = "A die with twelve sides. There's an air of neglect about it." + icon_state = "d12" + sides = 12 + +/obj/item/dice/d20 + name = "d20" + desc = "A die with twenty sides. The preferred die to throw at the GM." + icon_state = "d20" + sides = 20 + +/obj/item/dice/d100 + name = "d100" + desc = "A die with one hundred sides! Probably not fairly weighted..." + icon_state = "d100" + w_class = WEIGHT_CLASS_SMALL + sides = 100 + +/obj/item/dice/d100/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/dice/eightbd20 + name = "strange d20" + desc = "A weird die with raised text printed on the faces. Everything's white on white so reading it is a struggle. What poor design!" + icon_state = "8bd20" + sides = 20 + special_faces = list("It is certain","It is decidedly so","Without a doubt","Yes, definitely","You may rely on it","As I see it, yes","Most likely","Outlook good","Yes","Signs point to yes","Reply hazy try again","Ask again later","Better not tell you now","Cannot predict now","Concentrate and ask again","Don't count on it","My reply is no","My sources say no","Outlook not so good","Very doubtful") + +/obj/item/dice/eightbd20/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/dice/fourdd6 + name = "4d d6" + desc = "A die that exists in four dimensional space. Properly interpreting them can only be done with the help of a mathematician, a physicist, and a priest." + icon_state = "4dd6" + sides = 48 + special_faces = list("Cube-Side: 1-1","Cube-Side: 1-2","Cube-Side: 1-3","Cube-Side: 1-4","Cube-Side: 1-5","Cube-Side: 1-6","Cube-Side: 2-1","Cube-Side: 2-2","Cube-Side: 2-3","Cube-Side: 2-4","Cube-Side: 2-5","Cube-Side: 2-6","Cube-Side: 3-1","Cube-Side: 3-2","Cube-Side: 3-3","Cube-Side: 3-4","Cube-Side: 3-5","Cube-Side: 3-6","Cube-Side: 4-1","Cube-Side: 4-2","Cube-Side: 4-3","Cube-Side: 4-4","Cube-Side: 4-5","Cube-Side: 4-6","Cube-Side: 5-1","Cube-Side: 5-2","Cube-Side: 5-3","Cube-Side: 5-4","Cube-Side: 5-5","Cube-Side: 5-6","Cube-Side: 6-1","Cube-Side: 6-2","Cube-Side: 6-3","Cube-Side: 6-4","Cube-Side: 6-5","Cube-Side: 6-6","Cube-Side: 7-1","Cube-Side: 7-2","Cube-Side: 7-3","Cube-Side: 7-4","Cube-Side: 7-5","Cube-Side: 7-6","Cube-Side: 8-1","Cube-Side: 8-2","Cube-Side: 8-3","Cube-Side: 8-4","Cube-Side: 8-5","Cube-Side: 8-6") + +/obj/item/dice/fourdd6/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/dice/attack_self(mob/user) + diceroll(user) + +/obj/item/dice/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + diceroll(thrownby) + . = ..() + +/obj/item/dice/proc/diceroll(mob/user) + result = roll(sides) + if(rigged != DICE_NOT_RIGGED && result != rigged_value) + if(rigged == DICE_BASICALLY_RIGGED && prob(clamp(1/(sides - 1) * 100, 25, 80))) + result = rigged_value + else if(rigged == DICE_TOTALLY_RIGGED) + result = rigged_value + + . = result + + var/fake_result = roll(sides)//Daredevil isn't as good as he used to be + var/comment = "" + if(sides == 20 && result == 20) + comment = "NAT 20!" + else if(sides == 20 && result == 1) + comment = "Ouch, bad luck." + update_icon() + if(initial(icon_state) == "d00") + result = (result - 1)*10 + if(special_faces.len == sides) + result = special_faces[result] + if(user != null) //Dice was rolled in someone's hand + user.visible_message("[user] throws [src]. It lands on [result]. [comment]", \ + "You throw [src]. It lands on [result]. [comment]", \ + "You hear [src] rolling, it sounds like a [fake_result].") + else if(!src.throwing) //Dice was thrown and is coming to rest + visible_message("[src] rolls to a stop, landing on [result]. [comment]") + +/obj/item/dice/update_overlays() + . = ..() + . += "[icon_state]-[result]" + +/obj/item/dice/microwave_act(obj/machinery/microwave/M) + if(microwave_riggable) + rigged = DICE_BASICALLY_RIGGED + rigged_value = result + ..(M) diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index 4f24d028e31..246b8e0cdb6 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -1,528 +1,528 @@ -/obj/item/dnainjector - name = "\improper DNA injector" - desc = "This injects the person with DNA." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "dnainjector" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - - var/damage_coeff = 1 - var/list/fields - var/list/add_mutations = list() - var/list/remove_mutations = list() - - var/used = 0 - -/obj/item/dnainjector/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) - if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - for(var/HM in remove_mutations) - M.dna.remove_mutation(HM) - for(var/HM in add_mutations) - if(HM == RACEMUT) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - if(M.dna.mutation_in_sequence(HM)) - M.dna.activate_mutation(HM) - else - M.dna.add_mutation(HM, MUT_EXTRA) - if(fields) - if(fields["name"] && fields["UE"] && fields["blood_type"]) - M.real_name = fields["name"] - M.dna.unique_enzymes = fields["UE"] - M.name = M.real_name - M.dna.blood_type = fields["blood_type"] - if(fields["UI"]) //UI+UE - M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) - M.updateappearance(mutations_overlay_update=1) - log_attack("[log_msg] [loc_name(user)]") - return TRUE - return FALSE - -/obj/item/dnainjector/attack(mob/target, mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - if(used) - to_chat(user, "This injector is used up!") - return - if(ishuman(target)) - var/mob/living/carbon/human/humantarget = target - if (!humantarget.can_inject(user, 1)) - return - log_combat(user, target, "attempted to inject", src) - - if(target != user) - target.visible_message("[user] is trying to inject [target] with [src]!", \ - "[user] is trying to inject you with [src]!") - if(!do_mob(user, target) || used) - return - target.visible_message("[user] injects [target] with the syringe with [src]!", \ - "[user] injects you with the syringe with [src]!") - - else - to_chat(user, "You inject yourself with [src].") - - log_combat(user, target, "injected", src) - - if(!inject(target, user)) //Now we actually do the heavy lifting. - to_chat(user, "It appears that [target] does not have compatible DNA.") - - used = 1 - icon_state = "dnainjector0" - desc += " This one is used up." - - -/obj/item/dnainjector/antihulk - name = "\improper DNA injector (Anti-Hulk)" - desc = "Cures green skin." - remove_mutations = list(HULK) - -/obj/item/dnainjector/hulkmut - name = "\improper DNA injector (Hulk)" - desc = "This will make you big and strong, but give you a bad skin condition." - add_mutations = list(HULK) - -/obj/item/dnainjector/xraymut - name = "\improper DNA injector (X-ray)" - desc = "Finally you can see what the Captain does." - add_mutations = list(XRAY) - -/obj/item/dnainjector/antixray - name = "\improper DNA injector (Anti-X-ray)" - desc = "It will make you see harder." - remove_mutations = list(XRAY) - -///////////////////////////////////// -/obj/item/dnainjector/antiglasses - name = "\improper DNA injector (Anti-Glasses)" - desc = "Toss away those glasses!" - remove_mutations = list(BADSIGHT) - -/obj/item/dnainjector/glassesmut - name = "\improper DNA injector (Glasses)" - desc = "Will make you need dorkish glasses." - add_mutations = list(BADSIGHT) - -/obj/item/dnainjector/epimut - name = "\improper DNA injector (Epi.)" - desc = "Shake shake shake the room!" - add_mutations = list(EPILEPSY) - -/obj/item/dnainjector/antiepi - name = "\improper DNA injector (Anti-Epi.)" - desc = "Will fix you up from shaking the room." - remove_mutations = list(EPILEPSY) -//////////////////////////////////// -/obj/item/dnainjector/anticough - name = "\improper DNA injector (Anti-Cough)" - desc = "Will stop that awful noise." - remove_mutations = list(COUGH) - -/obj/item/dnainjector/coughmut - name = "\improper DNA injector (Cough)" - desc = "Will bring forth a sound of horror from your throat." - add_mutations = list(COUGH) - -/obj/item/dnainjector/antidwarf - name = "\improper DNA injector (Anti-Dwarfism)" - desc = "Helps you grow big and strong." - remove_mutations = list(DWARFISM) - -/obj/item/dnainjector/dwarf - name = "\improper DNA injector (Dwarfism)" - desc = "It's a small world after all." - add_mutations = list(DWARFISM) - -/obj/item/dnainjector/clumsymut - name = "\improper DNA injector (Clumsy)" - desc = "Makes clown minions." - add_mutations = list(CLOWNMUT) - -/obj/item/dnainjector/anticlumsy - name = "\improper DNA injector (Anti-Clumsy)" - desc = "Apply this for Security Clown." - remove_mutations = list(CLOWNMUT) - -/obj/item/dnainjector/antitour - name = "\improper DNA injector (Anti-Tour.)" - desc = "Will cure Tourette's." - remove_mutations = list(TOURETTES) - -/obj/item/dnainjector/tourmut - name = "\improper DNA injector (Tour.)" - desc = "Gives you a nasty case of Tourette's." - add_mutations = list(TOURETTES) - -/obj/item/dnainjector/stuttmut - name = "\improper DNA injector (Stutt.)" - desc = "Makes you s-s-stuttterrr." - add_mutations = list(NERVOUS) - -/obj/item/dnainjector/antistutt - name = "\improper DNA injector (Anti-Stutt.)" - desc = "Fixes that speaking impairment." - remove_mutations = list(NERVOUS) - -/obj/item/dnainjector/antifire - name = "\improper DNA injector (Anti-Fire)" - desc = "Cures fire." - remove_mutations = list(SPACEMUT) - -/obj/item/dnainjector/firemut - name = "\improper DNA injector (Fire)" - desc = "Gives you fire." - add_mutations = list(SPACEMUT) - -/obj/item/dnainjector/blindmut - name = "\improper DNA injector (Blind)" - desc = "Makes you not see anything." - add_mutations = list(BLINDMUT) - -/obj/item/dnainjector/antiblind - name = "\improper DNA injector (Anti-Blind)" - desc = "IT'S A MIRACLE!!!" - remove_mutations = list(BLINDMUT) - -/obj/item/dnainjector/antitele - name = "\improper DNA injector (Anti-Tele.)" - desc = "Will make you not able to control your mind." - remove_mutations = list(TK) - -/obj/item/dnainjector/telemut - name = "\improper DNA injector (Tele.)" - desc = "Super brain man!" - add_mutations = list(TK) - -/obj/item/dnainjector/telemut/darkbundle - name = "\improper DNA injector" - desc = "Good. Let the hate flow through you." - -/obj/item/dnainjector/deafmut - name = "\improper DNA injector (Deaf)" - desc = "Sorry, what did you say?" - add_mutations = list(DEAFMUT) - -/obj/item/dnainjector/antideaf - name = "\improper DNA injector (Anti-Deaf)" - desc = "Will make you hear once more." - remove_mutations = list(DEAFMUT) - -/obj/item/dnainjector/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations = list(RACEMUT) - -/obj/item/dnainjector/m2h - name = "\improper DNA injector (Monkey > Human)" - desc = "Will make you...less hairy." - remove_mutations = list(RACEMUT) - -/obj/item/dnainjector/antichameleon - name = "\improper DNA injector (Anti-Chameleon)" - remove_mutations = list(CHAMELEON) - -/obj/item/dnainjector/chameleonmut - name = "\improper DNA injector (Chameleon)" - add_mutations = list(CHAMELEON) - -/obj/item/dnainjector/antiwacky - name = "\improper DNA injector (Anti-Wacky)" - remove_mutations = list(WACKY) - -/obj/item/dnainjector/wackymut - name = "\improper DNA injector (Wacky)" - add_mutations = list(WACKY) - -/obj/item/dnainjector/antimute - name = "\improper DNA injector (Anti-Mute)" - remove_mutations = list(MUT_MUTE) - -/obj/item/dnainjector/mutemut - name = "\improper DNA injector (Mute)" - add_mutations = list(MUT_MUTE) - -/obj/item/dnainjector/unintelligiblemut - name = "\improper DNA injector (Unintelligible)" - add_mutations = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/antiunintelligible - name = "\improper DNA injector (Anti-Unintelligible)" - remove_mutations = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/swedishmut - name = "\improper DNA injector (Swedish)" - add_mutations = list(SWEDISH) - -/obj/item/dnainjector/antiswedish - name = "\improper DNA injector (Anti-Swedish)" - remove_mutations = list(SWEDISH) - -/obj/item/dnainjector/chavmut - name = "\improper DNA injector (Chav)" - add_mutations = list(CHAV) - -/obj/item/dnainjector/antichav - name = "\improper DNA injector (Anti-Chav)" - remove_mutations = list(CHAV) - -/obj/item/dnainjector/elvismut - name = "\improper DNA injector (Elvis)" - add_mutations = list(ELVIS) - -/obj/item/dnainjector/antielvis - name = "\improper DNA injector (Anti-Elvis)" - remove_mutations = list(ELVIS) - -/obj/item/dnainjector/lasereyesmut - name = "\improper DNA injector (Laser Eyes)" - add_mutations = list(LASEREYES) - -/obj/item/dnainjector/antilasereyes - name = "\improper DNA injector (Anti-Laser Eyes)" - remove_mutations = list(LASEREYES) - -/obj/item/dnainjector/void - name = "\improper DNA injector (Void)" - add_mutations = list(VOID) - -/obj/item/dnainjector/antivoid - name = "\improper DNA injector (Anti-Void)" - remove_mutations = list(VOID) - -/obj/item/dnainjector/antenna - name = "\improper DNA injector (Antenna)" - add_mutations = list(ANTENNA) - -/obj/item/dnainjector/antiantenna - name = "\improper DNA injector (Anti-Antenna)" - remove_mutations = list(ANTENNA) - -/obj/item/dnainjector/paranoia - name = "\improper DNA injector (Paranoia)" - add_mutations = list(PARANOIA) - -/obj/item/dnainjector/antiparanoia - name = "\improper DNA injector (Anti-Paranoia)" - remove_mutations = list(PARANOIA) - -/obj/item/dnainjector/mindread - name = "\improper DNA injector (Mindread)" - add_mutations = list(MINDREAD) - -/obj/item/dnainjector/antimindread - name = "\improper DNA injector (Anti-Mindread)" - remove_mutations = list(MINDREAD) - -/obj/item/dnainjector/radioactive - name = "\improper DNA injector (Radioactive)" - add_mutations = list(RADIOACTIVE) - -/obj/item/dnainjector/antiradioactive - name = "\improper DNA injector (Anti-Radioactive)" - remove_mutations = list(RADIOACTIVE) -/obj/item/dnainjector/olfaction - name = "\improper DNA injector (Olfaction)" - add_mutations = list(OLFACTION) - -/obj/item/dnainjector/antiolfaction - name = "\improper DNA injector (Anti-Olfaction)" - remove_mutations = list(OLFACTION) - -/obj/item/dnainjector/insulated - name = "\improper DNA injector (Insulated)" - add_mutations = list(INSULATED) - -/obj/item/dnainjector/antiinsulated - name = "\improper DNA injector (Anti-Insulated)" - remove_mutations = list(INSULATED) - -/obj/item/dnainjector/shock - name = "\improper DNA injector (Shock Touch)" - add_mutations = list(SHOCKTOUCH) - -/obj/item/dnainjector/antishock - name = "\improper DNA injector (Anti-Shock Touch)" - remove_mutations = list(SHOCKTOUCH) - -/obj/item/dnainjector/spatialinstability - name = "\improper DNA injector (Spatial Instability)" - add_mutations = list(BADBLINK) - -/obj/item/dnainjector/antispatialinstability - name = "\improper DNA injector (Anti-Spatial Instability)" - remove_mutations = list(BADBLINK) - -/obj/item/dnainjector/acidflesh - name = "\improper DNA injector (Acid Flesh)" - add_mutations = list(ACIDFLESH) - -/obj/item/dnainjector/antiacidflesh - name = "\improper DNA injector (Acid Flesh)" - remove_mutations = list(ACIDFLESH) - -/obj/item/dnainjector/gigantism - name = "\improper DNA injector (Gigantism)" - add_mutations = list(GIGANTISM) - -/obj/item/dnainjector/antigigantism - name = "\improper DNA injector (Anti-Gigantism)" - remove_mutations = list(GIGANTISM) - -/obj/item/dnainjector/spastic - name = "\improper DNA injector (Spastic)" - add_mutations = list(SPASTIC) - -/obj/item/dnainjector/antispastic - name = "\improper DNA injector (Anti-Spastic)" - remove_mutations = list(SPASTIC) - -/obj/item/dnainjector/twoleftfeet - name = "\improper DNA injector (Two Left Feet)" - add_mutations = list(EXTRASTUN) - -/obj/item/dnainjector/antitwoleftfeet - name = "\improper DNA injector (Anti-Two Left Feet)" - remove_mutations = list(EXTRASTUN) - -/obj/item/dnainjector/geladikinesis - name = "\improper DNA injector (Geladikinesis)" - add_mutations = list(GELADIKINESIS) - -/obj/item/dnainjector/antigeladikinesis - name = "\improper DNA injector (Anti-Geladikinesis)" - remove_mutations = list(GELADIKINESIS) - -/obj/item/dnainjector/cryokinesis - name = "\improper DNA injector (Cryokinesis)" - add_mutations = list(CRYOKINESIS) - -/obj/item/dnainjector/anticryokinesis - name = "\improper DNA injector (Anti-Cryokinesis)" - remove_mutations = list(CRYOKINESIS) - -/obj/item/dnainjector/thermal - name = "\improper DNA injector (Thermal Vision)" - add_mutations = list(THERMAL) - -/obj/item/dnainjector/antithermal - name = "\improper DNA injector (Anti-Thermal Vision)" - remove_mutations = list(THERMAL) - -/obj/item/dnainjector/glow - name = "\improper DNA injector (Glowy)" - add_mutations = list(GLOWY) - -/obj/item/dnainjector/removeglow - name = "\improper DNA injector (Anti-Glowy)" - remove_mutations = list(GLOWY) - -/obj/item/dnainjector/antiglow - name = "\improper DNA injector (Antiglowy)" - add_mutations = list(ANTIGLOWY) - -/obj/item/dnainjector/removeantiglow - name = "\improper DNA injector (Anti-Antiglowy)" - remove_mutations = list(ANTIGLOWY) - -/obj/item/dnainjector/timed - var/duration = 600 - -/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) - if(M.stat == DEAD) //prevents dead people from having their DNA changed - to_chat(user, "You can't modify [M]'s DNA while [M.p_theyre()] dead.") - return FALSE - - if(M.has_dna() && !(HAS_TRAIT(M, TRAIT_BADDNA))) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - var/endtime = world.time+duration - for(var/mutation in remove_mutations) - if(mutation == RACEMUT) - if(ishuman(M)) - continue - M = M.dna.remove_mutation(mutation) - else - M.dna.remove_mutation(mutation) - for(var/mutation in add_mutations) - if(M.dna.get_mutation(mutation)) - continue //Skip permanent mutations we already have. - if(mutation == RACEMUT && ishuman(M)) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - M = M.dna.add_mutation(mutation, MUT_OTHER, endtime) - else - M.dna.add_mutation(mutation, MUT_OTHER, endtime) - if(fields) - if(fields["name"] && fields["UE"] && fields["blood_type"]) - if(!M.dna.previous["name"]) - M.dna.previous["name"] = M.real_name - if(!M.dna.previous["UE"]) - M.dna.previous["UE"] = M.dna.unique_enzymes - if(!M.dna.previous["blood_type"]) - M.dna.previous["blood_type"] = M.dna.blood_type - M.real_name = fields["name"] - M.dna.unique_enzymes = fields["UE"] - M.name = M.real_name - M.dna.blood_type = fields["blood_type"] - M.dna.temporary_mutations[UE_CHANGED] = endtime - if(fields["UI"]) //UI+UE - if(!M.dna.previous["UI"]) - M.dna.previous["UI"] = M.dna.uni_identity - M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) - M.updateappearance(mutations_overlay_update=1) - M.dna.temporary_mutations[UI_CHANGED] = endtime - log_attack("[log_msg] [loc_name(user)]") - return TRUE - else - return FALSE - -/obj/item/dnainjector/timed/hulk - name = "\improper DNA injector (Hulk)" - desc = "This will make you big and strong, but give you a bad skin condition." - add_mutations = list(HULK) - -/obj/item/dnainjector/timed/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations = list(RACEMUT) - -/obj/item/dnainjector/activator - name = "\improper DNA activator" - desc = "Activates the current mutation on injection, if the subject has it." - var/doitanyway = FALSE - var/research = FALSE //Set to true to get expended and filled injectors for chromosomes - var/filled = FALSE - -/obj/item/dnainjector/activator/inject(mob/living/carbon/M, mob/user) - if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - var/pref = "" - for(var/mutation in add_mutations) - var/datum/mutation/human/HM = mutation - if(istype(HM, /datum/mutation/human)) - mutation = HM.type - if(!M.dna.activate_mutation(HM)) - if(!doitanyway) - log_msg += "(FAILED)" - else - M.dna.add_mutation(HM, MUT_EXTRA) - pref = "expended" - else if(research && M.client) - filled = TRUE - pref = "filled" - else - pref = "expended" - log_msg += "([mutation])" - name = "[pref] [name]" - log_attack("[log_msg] [loc_name(user)]") - return TRUE - return FALSE +/obj/item/dnainjector + name = "\improper DNA injector" + desc = "This injects the person with DNA." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "dnainjector" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + + var/damage_coeff = 1 + var/list/fields + var/list/add_mutations = list() + var/list/remove_mutations = list() + + var/used = 0 + +/obj/item/dnainjector/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) + if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + for(var/HM in remove_mutations) + M.dna.remove_mutation(HM) + for(var/HM in add_mutations) + if(HM == RACEMUT) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + if(M.dna.mutation_in_sequence(HM)) + M.dna.activate_mutation(HM) + else + M.dna.add_mutation(HM, MUT_EXTRA) + if(fields) + if(fields["name"] && fields["UE"] && fields["blood_type"]) + M.real_name = fields["name"] + M.dna.unique_enzymes = fields["UE"] + M.name = M.real_name + M.dna.blood_type = fields["blood_type"] + if(fields["UI"]) //UI+UE + M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) + M.updateappearance(mutations_overlay_update=1) + log_attack("[log_msg] [loc_name(user)]") + return TRUE + return FALSE + +/obj/item/dnainjector/attack(mob/target, mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + if(used) + to_chat(user, "This injector is used up!") + return + if(ishuman(target)) + var/mob/living/carbon/human/humantarget = target + if (!humantarget.can_inject(user, 1)) + return + log_combat(user, target, "attempted to inject", src) + + if(target != user) + target.visible_message("[user] is trying to inject [target] with [src]!", \ + "[user] is trying to inject you with [src]!") + if(!do_mob(user, target) || used) + return + target.visible_message("[user] injects [target] with the syringe with [src]!", \ + "[user] injects you with the syringe with [src]!") + + else + to_chat(user, "You inject yourself with [src].") + + log_combat(user, target, "injected", src) + + if(!inject(target, user)) //Now we actually do the heavy lifting. + to_chat(user, "It appears that [target] does not have compatible DNA.") + + used = 1 + icon_state = "dnainjector0" + desc += " This one is used up." + + +/obj/item/dnainjector/antihulk + name = "\improper DNA injector (Anti-Hulk)" + desc = "Cures green skin." + remove_mutations = list(HULK) + +/obj/item/dnainjector/hulkmut + name = "\improper DNA injector (Hulk)" + desc = "This will make you big and strong, but give you a bad skin condition." + add_mutations = list(HULK) + +/obj/item/dnainjector/xraymut + name = "\improper DNA injector (X-ray)" + desc = "Finally you can see what the Captain does." + add_mutations = list(XRAY) + +/obj/item/dnainjector/antixray + name = "\improper DNA injector (Anti-X-ray)" + desc = "It will make you see harder." + remove_mutations = list(XRAY) + +///////////////////////////////////// +/obj/item/dnainjector/antiglasses + name = "\improper DNA injector (Anti-Glasses)" + desc = "Toss away those glasses!" + remove_mutations = list(BADSIGHT) + +/obj/item/dnainjector/glassesmut + name = "\improper DNA injector (Glasses)" + desc = "Will make you need dorkish glasses." + add_mutations = list(BADSIGHT) + +/obj/item/dnainjector/epimut + name = "\improper DNA injector (Epi.)" + desc = "Shake shake shake the room!" + add_mutations = list(EPILEPSY) + +/obj/item/dnainjector/antiepi + name = "\improper DNA injector (Anti-Epi.)" + desc = "Will fix you up from shaking the room." + remove_mutations = list(EPILEPSY) +//////////////////////////////////// +/obj/item/dnainjector/anticough + name = "\improper DNA injector (Anti-Cough)" + desc = "Will stop that awful noise." + remove_mutations = list(COUGH) + +/obj/item/dnainjector/coughmut + name = "\improper DNA injector (Cough)" + desc = "Will bring forth a sound of horror from your throat." + add_mutations = list(COUGH) + +/obj/item/dnainjector/antidwarf + name = "\improper DNA injector (Anti-Dwarfism)" + desc = "Helps you grow big and strong." + remove_mutations = list(DWARFISM) + +/obj/item/dnainjector/dwarf + name = "\improper DNA injector (Dwarfism)" + desc = "It's a small world after all." + add_mutations = list(DWARFISM) + +/obj/item/dnainjector/clumsymut + name = "\improper DNA injector (Clumsy)" + desc = "Makes clown minions." + add_mutations = list(CLOWNMUT) + +/obj/item/dnainjector/anticlumsy + name = "\improper DNA injector (Anti-Clumsy)" + desc = "Apply this for Security Clown." + remove_mutations = list(CLOWNMUT) + +/obj/item/dnainjector/antitour + name = "\improper DNA injector (Anti-Tour.)" + desc = "Will cure Tourette's." + remove_mutations = list(TOURETTES) + +/obj/item/dnainjector/tourmut + name = "\improper DNA injector (Tour.)" + desc = "Gives you a nasty case of Tourette's." + add_mutations = list(TOURETTES) + +/obj/item/dnainjector/stuttmut + name = "\improper DNA injector (Stutt.)" + desc = "Makes you s-s-stuttterrr." + add_mutations = list(NERVOUS) + +/obj/item/dnainjector/antistutt + name = "\improper DNA injector (Anti-Stutt.)" + desc = "Fixes that speaking impairment." + remove_mutations = list(NERVOUS) + +/obj/item/dnainjector/antifire + name = "\improper DNA injector (Anti-Fire)" + desc = "Cures fire." + remove_mutations = list(SPACEMUT) + +/obj/item/dnainjector/firemut + name = "\improper DNA injector (Fire)" + desc = "Gives you fire." + add_mutations = list(SPACEMUT) + +/obj/item/dnainjector/blindmut + name = "\improper DNA injector (Blind)" + desc = "Makes you not see anything." + add_mutations = list(BLINDMUT) + +/obj/item/dnainjector/antiblind + name = "\improper DNA injector (Anti-Blind)" + desc = "IT'S A MIRACLE!!!" + remove_mutations = list(BLINDMUT) + +/obj/item/dnainjector/antitele + name = "\improper DNA injector (Anti-Tele.)" + desc = "Will make you not able to control your mind." + remove_mutations = list(TK) + +/obj/item/dnainjector/telemut + name = "\improper DNA injector (Tele.)" + desc = "Super brain man!" + add_mutations = list(TK) + +/obj/item/dnainjector/telemut/darkbundle + name = "\improper DNA injector" + desc = "Good. Let the hate flow through you." + +/obj/item/dnainjector/deafmut + name = "\improper DNA injector (Deaf)" + desc = "Sorry, what did you say?" + add_mutations = list(DEAFMUT) + +/obj/item/dnainjector/antideaf + name = "\improper DNA injector (Anti-Deaf)" + desc = "Will make you hear once more." + remove_mutations = list(DEAFMUT) + +/obj/item/dnainjector/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations = list(RACEMUT) + +/obj/item/dnainjector/m2h + name = "\improper DNA injector (Monkey > Human)" + desc = "Will make you...less hairy." + remove_mutations = list(RACEMUT) + +/obj/item/dnainjector/antichameleon + name = "\improper DNA injector (Anti-Chameleon)" + remove_mutations = list(CHAMELEON) + +/obj/item/dnainjector/chameleonmut + name = "\improper DNA injector (Chameleon)" + add_mutations = list(CHAMELEON) + +/obj/item/dnainjector/antiwacky + name = "\improper DNA injector (Anti-Wacky)" + remove_mutations = list(WACKY) + +/obj/item/dnainjector/wackymut + name = "\improper DNA injector (Wacky)" + add_mutations = list(WACKY) + +/obj/item/dnainjector/antimute + name = "\improper DNA injector (Anti-Mute)" + remove_mutations = list(MUT_MUTE) + +/obj/item/dnainjector/mutemut + name = "\improper DNA injector (Mute)" + add_mutations = list(MUT_MUTE) + +/obj/item/dnainjector/unintelligiblemut + name = "\improper DNA injector (Unintelligible)" + add_mutations = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/antiunintelligible + name = "\improper DNA injector (Anti-Unintelligible)" + remove_mutations = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/swedishmut + name = "\improper DNA injector (Swedish)" + add_mutations = list(SWEDISH) + +/obj/item/dnainjector/antiswedish + name = "\improper DNA injector (Anti-Swedish)" + remove_mutations = list(SWEDISH) + +/obj/item/dnainjector/chavmut + name = "\improper DNA injector (Chav)" + add_mutations = list(CHAV) + +/obj/item/dnainjector/antichav + name = "\improper DNA injector (Anti-Chav)" + remove_mutations = list(CHAV) + +/obj/item/dnainjector/elvismut + name = "\improper DNA injector (Elvis)" + add_mutations = list(ELVIS) + +/obj/item/dnainjector/antielvis + name = "\improper DNA injector (Anti-Elvis)" + remove_mutations = list(ELVIS) + +/obj/item/dnainjector/lasereyesmut + name = "\improper DNA injector (Laser Eyes)" + add_mutations = list(LASEREYES) + +/obj/item/dnainjector/antilasereyes + name = "\improper DNA injector (Anti-Laser Eyes)" + remove_mutations = list(LASEREYES) + +/obj/item/dnainjector/void + name = "\improper DNA injector (Void)" + add_mutations = list(VOID) + +/obj/item/dnainjector/antivoid + name = "\improper DNA injector (Anti-Void)" + remove_mutations = list(VOID) + +/obj/item/dnainjector/antenna + name = "\improper DNA injector (Antenna)" + add_mutations = list(ANTENNA) + +/obj/item/dnainjector/antiantenna + name = "\improper DNA injector (Anti-Antenna)" + remove_mutations = list(ANTENNA) + +/obj/item/dnainjector/paranoia + name = "\improper DNA injector (Paranoia)" + add_mutations = list(PARANOIA) + +/obj/item/dnainjector/antiparanoia + name = "\improper DNA injector (Anti-Paranoia)" + remove_mutations = list(PARANOIA) + +/obj/item/dnainjector/mindread + name = "\improper DNA injector (Mindread)" + add_mutations = list(MINDREAD) + +/obj/item/dnainjector/antimindread + name = "\improper DNA injector (Anti-Mindread)" + remove_mutations = list(MINDREAD) + +/obj/item/dnainjector/radioactive + name = "\improper DNA injector (Radioactive)" + add_mutations = list(RADIOACTIVE) + +/obj/item/dnainjector/antiradioactive + name = "\improper DNA injector (Anti-Radioactive)" + remove_mutations = list(RADIOACTIVE) +/obj/item/dnainjector/olfaction + name = "\improper DNA injector (Olfaction)" + add_mutations = list(OLFACTION) + +/obj/item/dnainjector/antiolfaction + name = "\improper DNA injector (Anti-Olfaction)" + remove_mutations = list(OLFACTION) + +/obj/item/dnainjector/insulated + name = "\improper DNA injector (Insulated)" + add_mutations = list(INSULATED) + +/obj/item/dnainjector/antiinsulated + name = "\improper DNA injector (Anti-Insulated)" + remove_mutations = list(INSULATED) + +/obj/item/dnainjector/shock + name = "\improper DNA injector (Shock Touch)" + add_mutations = list(SHOCKTOUCH) + +/obj/item/dnainjector/antishock + name = "\improper DNA injector (Anti-Shock Touch)" + remove_mutations = list(SHOCKTOUCH) + +/obj/item/dnainjector/spatialinstability + name = "\improper DNA injector (Spatial Instability)" + add_mutations = list(BADBLINK) + +/obj/item/dnainjector/antispatialinstability + name = "\improper DNA injector (Anti-Spatial Instability)" + remove_mutations = list(BADBLINK) + +/obj/item/dnainjector/acidflesh + name = "\improper DNA injector (Acid Flesh)" + add_mutations = list(ACIDFLESH) + +/obj/item/dnainjector/antiacidflesh + name = "\improper DNA injector (Acid Flesh)" + remove_mutations = list(ACIDFLESH) + +/obj/item/dnainjector/gigantism + name = "\improper DNA injector (Gigantism)" + add_mutations = list(GIGANTISM) + +/obj/item/dnainjector/antigigantism + name = "\improper DNA injector (Anti-Gigantism)" + remove_mutations = list(GIGANTISM) + +/obj/item/dnainjector/spastic + name = "\improper DNA injector (Spastic)" + add_mutations = list(SPASTIC) + +/obj/item/dnainjector/antispastic + name = "\improper DNA injector (Anti-Spastic)" + remove_mutations = list(SPASTIC) + +/obj/item/dnainjector/twoleftfeet + name = "\improper DNA injector (Two Left Feet)" + add_mutations = list(EXTRASTUN) + +/obj/item/dnainjector/antitwoleftfeet + name = "\improper DNA injector (Anti-Two Left Feet)" + remove_mutations = list(EXTRASTUN) + +/obj/item/dnainjector/geladikinesis + name = "\improper DNA injector (Geladikinesis)" + add_mutations = list(GELADIKINESIS) + +/obj/item/dnainjector/antigeladikinesis + name = "\improper DNA injector (Anti-Geladikinesis)" + remove_mutations = list(GELADIKINESIS) + +/obj/item/dnainjector/cryokinesis + name = "\improper DNA injector (Cryokinesis)" + add_mutations = list(CRYOKINESIS) + +/obj/item/dnainjector/anticryokinesis + name = "\improper DNA injector (Anti-Cryokinesis)" + remove_mutations = list(CRYOKINESIS) + +/obj/item/dnainjector/thermal + name = "\improper DNA injector (Thermal Vision)" + add_mutations = list(THERMAL) + +/obj/item/dnainjector/antithermal + name = "\improper DNA injector (Anti-Thermal Vision)" + remove_mutations = list(THERMAL) + +/obj/item/dnainjector/glow + name = "\improper DNA injector (Glowy)" + add_mutations = list(GLOWY) + +/obj/item/dnainjector/removeglow + name = "\improper DNA injector (Anti-Glowy)" + remove_mutations = list(GLOWY) + +/obj/item/dnainjector/antiglow + name = "\improper DNA injector (Antiglowy)" + add_mutations = list(ANTIGLOWY) + +/obj/item/dnainjector/removeantiglow + name = "\improper DNA injector (Anti-Antiglowy)" + remove_mutations = list(ANTIGLOWY) + +/obj/item/dnainjector/timed + var/duration = 600 + +/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) + if(M.stat == DEAD) //prevents dead people from having their DNA changed + to_chat(user, "You can't modify [M]'s DNA while [M.p_theyre()] dead.") + return FALSE + + if(M.has_dna() && !(HAS_TRAIT(M, TRAIT_BADDNA))) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + var/endtime = world.time+duration + for(var/mutation in remove_mutations) + if(mutation == RACEMUT) + if(ishuman(M)) + continue + M = M.dna.remove_mutation(mutation) + else + M.dna.remove_mutation(mutation) + for(var/mutation in add_mutations) + if(M.dna.get_mutation(mutation)) + continue //Skip permanent mutations we already have. + if(mutation == RACEMUT && ishuman(M)) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + M = M.dna.add_mutation(mutation, MUT_OTHER, endtime) + else + M.dna.add_mutation(mutation, MUT_OTHER, endtime) + if(fields) + if(fields["name"] && fields["UE"] && fields["blood_type"]) + if(!M.dna.previous["name"]) + M.dna.previous["name"] = M.real_name + if(!M.dna.previous["UE"]) + M.dna.previous["UE"] = M.dna.unique_enzymes + if(!M.dna.previous["blood_type"]) + M.dna.previous["blood_type"] = M.dna.blood_type + M.real_name = fields["name"] + M.dna.unique_enzymes = fields["UE"] + M.name = M.real_name + M.dna.blood_type = fields["blood_type"] + M.dna.temporary_mutations[UE_CHANGED] = endtime + if(fields["UI"]) //UI+UE + if(!M.dna.previous["UI"]) + M.dna.previous["UI"] = M.dna.uni_identity + M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) + M.updateappearance(mutations_overlay_update=1) + M.dna.temporary_mutations[UI_CHANGED] = endtime + log_attack("[log_msg] [loc_name(user)]") + return TRUE + else + return FALSE + +/obj/item/dnainjector/timed/hulk + name = "\improper DNA injector (Hulk)" + desc = "This will make you big and strong, but give you a bad skin condition." + add_mutations = list(HULK) + +/obj/item/dnainjector/timed/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations = list(RACEMUT) + +/obj/item/dnainjector/activator + name = "\improper DNA activator" + desc = "Activates the current mutation on injection, if the subject has it." + var/doitanyway = FALSE + var/research = FALSE //Set to true to get expended and filled injectors for chromosomes + var/filled = FALSE + +/obj/item/dnainjector/activator/inject(mob/living/carbon/M, mob/user) + if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + var/pref = "" + for(var/mutation in add_mutations) + var/datum/mutation/human/HM = mutation + if(istype(HM, /datum/mutation/human)) + mutation = HM.type + if(!M.dna.activate_mutation(HM)) + if(!doitanyway) + log_msg += "(FAILED)" + else + M.dna.add_mutation(HM, MUT_EXTRA) + pref = "expended" + else if(research && M.client) + filled = TRUE + pref = "filled" + else + pref = "expended" + log_msg += "([mutation])" + name = "[pref] [name]" + log_attack("[log_msg] [loc_name(user)]") + return TRUE + return FALSE diff --git a/code/game/objects/items/emags.dm b/code/game/objects/items/emags.dm index c87e56638f9..0c1577f8653 100644 --- a/code/game/objects/items/emags.dm +++ b/code/game/objects/items/emags.dm @@ -1,114 +1,114 @@ -/* Emags - * Contains: - * EMAGS AND DOORMAGS - */ - - -/* - * EMAG AND SUBTYPES - */ -/obj/item/card/emag - desc = "It's a card with a magnetic strip attached to some circuitry." - name = "cryptographic sequencer" - icon_state = "emag" - inhand_icon_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - item_flags = NO_MAT_REDEMPTION | NOBLUDGEON - var/prox_check = TRUE //If the emag requires you to be in range - var/type_blacklist //List of types that require a specialized emag - -/obj/item/card/emag/bluespace - name = "bluespace cryptographic sequencer" - desc = "It's a blue card with a magnetic strip attached to some circuitry. It appears to have some sort of transmitter attached to it." - color = rgb(40, 130, 255) - prox_check = FALSE - -/obj/item/card/emag/halloween - name = "hack-o'-lantern" - desc = "It's a pumpkin with a cryptographic sequencer sticking out." - icon_state = "hack_o_lantern" - -/obj/item/card/emagfake - desc = "It's a card with a magnetic strip attached to some circuitry. Closer inspection shows that this card is a poorly made replica, with a \"DonkCo\" logo stamped on the back." - name = "cryptographic sequencer" - icon_state = "emag" - inhand_icon_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/emagfake/afterattack() - . = ..() - playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) - -/obj/item/card/emag/Initialize(mapload) - . = ..() - type_blacklist = list(subtypesof(/obj/machinery/door/airlock), subtypesof(/obj/machinery/door/window/)) //list of all typepaths that require a specialized emag to hack. - -/obj/item/card/emag/attack() - return - -/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) - . = ..() - var/atom/A = target - if(!proximity && prox_check) - return - if(!can_emag(target, user)) - return - log_combat(user, A, "attempted to emag") - A.emag_act(user, src) - -/obj/item/card/emag/proc/can_emag(atom/target, mob/user) - for (var/subtypelist in type_blacklist) - if (target.type in subtypelist) - to_chat(user, "The [target] cannot be affected by the [src]! A more specialized hacking device is required.") - return FALSE - return TRUE - -/* - * DOORMAG - */ -/obj/item/card/emag/doorjack - desc = "Commonly known as a \"doorjack\", this device is a specialized cryptographic sequencer specifically designed to override station airlock access codes. Uses self-refilling charges to hack airlocks." - name = "airlock authentication override card" - icon_state = "doorjack" - var/type_whitelist //List of types - var/charges = 3 - var/max_charges = 3 - var/list/charge_timers = list() - var/charge_time = 1800 //three minutes - -/obj/item/card/emag/doorjack/Initialize(mapload) - . = ..() - type_whitelist = list(subtypesof(/obj/machinery/door/airlock), subtypesof(/obj/machinery/door/window/)) //list of all acceptable typepaths that this device can affect - -/obj/item/card/emag/doorjack/proc/use_charge(mob/user) - charges -- - to_chat(user, "You use [src]. It now has [charges] charges remaining.") - charge_timers.Add(addtimer(CALLBACK(src, .proc/recharge), charge_time, TIMER_STOPPABLE)) - -/obj/item/card/emag/doorjack/proc/recharge(mob/user) - charges = min(charges+1, max_charges) - playsound(src,'sound/machines/twobeep.ogg',10,TRUE) - charge_timers.Remove(charge_timers[1]) - -/obj/item/card/emag/doorjack/examine(mob/user) - . = ..() - . += "It has [charges] charges remaining." - if (length(charge_timers)) - . += "A small display on the back reads:" - for (var/i in 1 to length(charge_timers)) - var/timeleft = timeleft(charge_timers[i]) - var/loadingbar = num2loadingbar(timeleft/charge_time) - . += "CHARGE #[i]: [loadingbar] ([timeleft*0.1]s)" - -/obj/item/card/emag/doorjack/can_emag(atom/target, mob/user) - if (charges <= 0) - to_chat(user, "[src] is recharging!") - return FALSE - for (var/list/subtypelist in type_whitelist) - if (target.type in subtypelist) - return TRUE - to_chat(user, "[src] is unable to interface with this. It only seems to fit into airlock electronics.") - return FALSE - +/* Emags + * Contains: + * EMAGS AND DOORMAGS + */ + + +/* + * EMAG AND SUBTYPES + */ +/obj/item/card/emag + desc = "It's a card with a magnetic strip attached to some circuitry." + name = "cryptographic sequencer" + icon_state = "emag" + inhand_icon_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + item_flags = NO_MAT_REDEMPTION | NOBLUDGEON + var/prox_check = TRUE //If the emag requires you to be in range + var/type_blacklist //List of types that require a specialized emag + +/obj/item/card/emag/bluespace + name = "bluespace cryptographic sequencer" + desc = "It's a blue card with a magnetic strip attached to some circuitry. It appears to have some sort of transmitter attached to it." + color = rgb(40, 130, 255) + prox_check = FALSE + +/obj/item/card/emag/halloween + name = "hack-o'-lantern" + desc = "It's a pumpkin with a cryptographic sequencer sticking out." + icon_state = "hack_o_lantern" + +/obj/item/card/emagfake + desc = "It's a card with a magnetic strip attached to some circuitry. Closer inspection shows that this card is a poorly made replica, with a \"DonkCo\" logo stamped on the back." + name = "cryptographic sequencer" + icon_state = "emag" + inhand_icon_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/emagfake/afterattack() + . = ..() + playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) + +/obj/item/card/emag/Initialize(mapload) + . = ..() + type_blacklist = list(subtypesof(/obj/machinery/door/airlock), subtypesof(/obj/machinery/door/window/)) //list of all typepaths that require a specialized emag to hack. + +/obj/item/card/emag/attack() + return + +/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) + . = ..() + var/atom/A = target + if(!proximity && prox_check) + return + if(!can_emag(target, user)) + return + log_combat(user, A, "attempted to emag") + A.emag_act(user, src) + +/obj/item/card/emag/proc/can_emag(atom/target, mob/user) + for (var/subtypelist in type_blacklist) + if (target.type in subtypelist) + to_chat(user, "The [target] cannot be affected by the [src]! A more specialized hacking device is required.") + return FALSE + return TRUE + +/* + * DOORMAG + */ +/obj/item/card/emag/doorjack + desc = "Commonly known as a \"doorjack\", this device is a specialized cryptographic sequencer specifically designed to override station airlock access codes. Uses self-refilling charges to hack airlocks." + name = "airlock authentication override card" + icon_state = "doorjack" + var/type_whitelist //List of types + var/charges = 3 + var/max_charges = 3 + var/list/charge_timers = list() + var/charge_time = 1800 //three minutes + +/obj/item/card/emag/doorjack/Initialize(mapload) + . = ..() + type_whitelist = list(subtypesof(/obj/machinery/door/airlock), subtypesof(/obj/machinery/door/window/)) //list of all acceptable typepaths that this device can affect + +/obj/item/card/emag/doorjack/proc/use_charge(mob/user) + charges -- + to_chat(user, "You use [src]. It now has [charges] charges remaining.") + charge_timers.Add(addtimer(CALLBACK(src, .proc/recharge), charge_time, TIMER_STOPPABLE)) + +/obj/item/card/emag/doorjack/proc/recharge(mob/user) + charges = min(charges+1, max_charges) + playsound(src,'sound/machines/twobeep.ogg',10,TRUE) + charge_timers.Remove(charge_timers[1]) + +/obj/item/card/emag/doorjack/examine(mob/user) + . = ..() + . += "It has [charges] charges remaining." + if (length(charge_timers)) + . += "A small display on the back reads:" + for (var/i in 1 to length(charge_timers)) + var/timeleft = timeleft(charge_timers[i]) + var/loadingbar = num2loadingbar(timeleft/charge_time) + . += "CHARGE #[i]: [loadingbar] ([timeleft*0.1]s)" + +/obj/item/card/emag/doorjack/can_emag(atom/target, mob/user) + if (charges <= 0) + to_chat(user, "[src] is recharging!") + return FALSE + for (var/list/subtypelist in type_whitelist) + if (target.type in subtypelist) + return TRUE + to_chat(user, "[src] is unable to interface with this. It only seems to fit into airlock electronics.") + return FALSE + diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm index 077dfa6d6e9..ddbed1dabf1 100644 --- a/code/game/objects/items/extinguisher.dm +++ b/code/game/objects/items/extinguisher.dm @@ -1,249 +1,249 @@ -/obj/item/extinguisher - name = "fire extinguisher" - desc = "A traditional red fire extinguisher." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "fire_extinguisher0" - inhand_icon_state = "fire_extinguisher" - hitsound = 'sound/weapons/smash.ogg' - flags_1 = CONDUCT_1 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 2 - throw_range = 7 - force = 10 - custom_materials = list(/datum/material/iron = 90) - attack_verb = list("slammed", "whacked", "bashed", "thunked", "battered", "bludgeoned", "thrashed") - dog_fashion = /datum/dog_fashion/back - resistance_flags = FIRE_PROOF - var/max_water = 50 - var/last_use = 1 - var/chem = /datum/reagent/water - var/safety = TRUE - var/refilling = FALSE - var/tanktype = /obj/structure/reagent_dispensers/watertank - var/sprite_name = "fire_extinguisher" - var/power = 5 //Maximum distance launched water will travel - var/precision = FALSE //By default, turfs picked from a spray are random, set to 1 to make it always have at least one water effect per row - var/cooling_power = 2 //Sets the cooling_temperature of the water reagent datum inside of the extinguisher when it is refilled - -/obj/item/extinguisher/mini - name = "pocket fire extinguisher" - desc = "A light and compact fibreglass-framed model fire extinguisher." - icon_state = "miniFE0" - inhand_icon_state = "miniFE" - hitsound = null //it is much lighter, after all. - flags_1 = null //doesn't CONDUCT_1 - throwforce = 2 - w_class = WEIGHT_CLASS_SMALL - force = 3 - custom_materials = list(/datum/material/iron = 50, /datum/material/glass = 40) - max_water = 30 - sprite_name = "miniFE" - dog_fashion = null - -/obj/item/extinguisher/proc/refill() - create_reagents(max_water, AMOUNT_VISIBLE) - reagents.add_reagent(chem, max_water) - -/obj/item/extinguisher/Initialize() - . = ..() - refill() - -/obj/item/extinguisher/advanced - name = "advanced fire extinguisher" - desc = "Used to stop thermonuclear fires from spreading inside your engine." - icon_state = "foam_extinguisher0" - //inhand_icon_state = "foam_extinguisher" needs sprite - dog_fashion = null - chem = /datum/reagent/firefighting_foam - tanktype = /obj/structure/reagent_dispensers/foamtank - sprite_name = "foam_extinguisher" - precision = TRUE - -/obj/item/extinguisher/suicide_act(mob/living/carbon/user) - if (!safety && (reagents.total_volume >= 1)) - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth. It looks like [user.p_theyre()] trying to extinguish the spark of life!") - afterattack(user,user) - return OXYLOSS - else if (safety && (reagents.total_volume >= 1)) - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... The safety's still on!") - return SHAME - else - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... [src] is empty!") - return SHAME - -/obj/item/extinguisher/attack_self(mob/user) - safety = !safety - src.icon_state = "[sprite_name][!safety]" - to_chat(user, "The safety is [safety ? "on" : "off"].") - return - -/obj/item/extinguisher/attack(mob/M, mob/user) - if(user.a_intent == INTENT_HELP && !safety) //If we're on help intent and going to spray people, don't bash them. - return FALSE - else - return ..() - -/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) - if(AttemptRefill(O, user)) - refilling = TRUE - return FALSE - else - return ..() - -/obj/item/extinguisher/examine(mob/user) - . = ..() - . += "The safety is [safety ? "on" : "off"]." - - if(reagents.total_volume) - . += "Alt-click to empty it." - -/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) - if(istype(target, tanktype) && target.Adjacent(user)) - var/safety_save = safety - safety = TRUE - if(reagents.total_volume == reagents.maximum_volume) - to_chat(user, "\The [src] is already full!") - safety = safety_save - return 1 - var/obj/structure/reagent_dispensers/W = target //will it work? - var/transferred = W.reagents.trans_to(src, max_water, transfered_by = user) - if(transferred > 0) - to_chat(user, "\The [src] has been refilled by [transferred] units.") - playsound(src.loc, 'sound/effects/refill.ogg', 50, TRUE, -6) - for(var/datum/reagent/water/R in reagents.reagent_list) - R.cooling_temperature = cooling_power - else - to_chat(user, "\The [W] is empty!") - safety = safety_save - return 1 - else - return 0 - -/obj/item/extinguisher/afterattack(atom/target, mob/user , flag) - . = ..() - // Make it so the extinguisher doesn't spray yourself when you click your inventory items - if (target.loc == user) - return - //TODO; Add support for reagents in water. - - if(refilling) - refilling = FALSE - return - if (!safety) - - - if (src.reagents.total_volume < 1) - to_chat(usr, "\The [src] is empty!") - return - - if (world.time < src.last_use + 12) - return - - src.last_use = world.time - - playsound(src.loc, 'sound/effects/extinguish.ogg', 75, TRUE, -3) - - var/direction = get_dir(src,target) - - if(user.buckled && isobj(user.buckled) && !user.buckled.anchored) - var/obj/B = user.buckled - var/movementdirection = turn(direction,180) - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection), 1) - - else user.newtonian_move(turn(direction, 180)) - - //Get all the turfs that can be shot at - var/turf/T = get_turf(target) - var/turf/T1 = get_step(T,turn(direction, 90)) - var/turf/T2 = get_step(T,turn(direction, -90)) - var/list/the_targets = list(T,T1,T2) - if(precision) - var/turf/T3 = get_step(T1, turn(direction, 90)) - var/turf/T4 = get_step(T2,turn(direction, -90)) - the_targets.Add(T3,T4) - - var/list/water_particles=list() - for(var/a=0, a<5, a++) - var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water(get_turf(src)) - var/my_target = pick(the_targets) - water_particles[W] = my_target - // If precise, remove turf from targets so it won't be picked more than once - if(precision) - the_targets -= my_target - var/datum/reagents/R = new/datum/reagents(5) - W.reagents = R - R.my_atom = W - reagents.trans_to(W,1, transfered_by = user) - - //Make em move dat ass, hun - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, water_particles), 2) - -//Particle movement loop -/obj/item/extinguisher/proc/move_particles(list/particles, repetition=0) - //Check if there's anything in here first - if(!particles || particles.len == 0) - return - // Second loop: Get all the water particles and make them move to their target - for(var/obj/effect/particle_effect/water/W in particles) - var/turf/my_target = particles[W] - if(!W) - continue - step_towards(W,my_target) - if(!W.reagents) - continue - W.reagents.expose(get_turf(W)) - for(var/A in get_turf(W)) - W.reagents.expose(A) - if(W.loc == my_target) - particles -= W - if(repetition < power) - repetition++ - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, particles, repetition), 2) - -//Chair movement loop -/obj/item/extinguisher/proc/move_chair(obj/B, movementdirection, repetition=0) - step(B, movementdirection) - - var/timer_seconds - switch(repetition) - if(0 to 2) - timer_seconds = 1 - if(3 to 4) - timer_seconds = 2 - if(5 to 8) - timer_seconds = 3 - else - return - - repetition++ - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection, repetition), timer_seconds) - -/obj/item/extinguisher/AltClick(mob/user) - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - if(!user.is_holding(src)) - to_chat(user, "You must be holding the [src] in your hands do this!") - return - EmptyExtinguisher(user) - -/obj/item/extinguisher/proc/EmptyExtinguisher(var/mob/user) - if(loc == user && reagents.total_volume) - reagents.clear_reagents() - - var/turf/T = get_turf(loc) - if(isopenturf(T)) - var/turf/open/theturf = T - theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) - - user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] using its release valve.") - -//firebot assembly -/obj/item/extinguisher/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot)) - to_chat(user, "You add [O] to [src].") - qdel(O) - qdel(src) - user.put_in_hands(new /obj/item/bot_assembly/firebot) - else - ..() +/obj/item/extinguisher + name = "fire extinguisher" + desc = "A traditional red fire extinguisher." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "fire_extinguisher0" + inhand_icon_state = "fire_extinguisher" + hitsound = 'sound/weapons/smash.ogg' + flags_1 = CONDUCT_1 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 2 + throw_range = 7 + force = 10 + custom_materials = list(/datum/material/iron = 90) + attack_verb = list("slammed", "whacked", "bashed", "thunked", "battered", "bludgeoned", "thrashed") + dog_fashion = /datum/dog_fashion/back + resistance_flags = FIRE_PROOF + var/max_water = 50 + var/last_use = 1 + var/chem = /datum/reagent/water + var/safety = TRUE + var/refilling = FALSE + var/tanktype = /obj/structure/reagent_dispensers/watertank + var/sprite_name = "fire_extinguisher" + var/power = 5 //Maximum distance launched water will travel + var/precision = FALSE //By default, turfs picked from a spray are random, set to 1 to make it always have at least one water effect per row + var/cooling_power = 2 //Sets the cooling_temperature of the water reagent datum inside of the extinguisher when it is refilled + +/obj/item/extinguisher/mini + name = "pocket fire extinguisher" + desc = "A light and compact fibreglass-framed model fire extinguisher." + icon_state = "miniFE0" + inhand_icon_state = "miniFE" + hitsound = null //it is much lighter, after all. + flags_1 = null //doesn't CONDUCT_1 + throwforce = 2 + w_class = WEIGHT_CLASS_SMALL + force = 3 + custom_materials = list(/datum/material/iron = 50, /datum/material/glass = 40) + max_water = 30 + sprite_name = "miniFE" + dog_fashion = null + +/obj/item/extinguisher/proc/refill() + create_reagents(max_water, AMOUNT_VISIBLE) + reagents.add_reagent(chem, max_water) + +/obj/item/extinguisher/Initialize() + . = ..() + refill() + +/obj/item/extinguisher/advanced + name = "advanced fire extinguisher" + desc = "Used to stop thermonuclear fires from spreading inside your engine." + icon_state = "foam_extinguisher0" + //inhand_icon_state = "foam_extinguisher" needs sprite + dog_fashion = null + chem = /datum/reagent/firefighting_foam + tanktype = /obj/structure/reagent_dispensers/foamtank + sprite_name = "foam_extinguisher" + precision = TRUE + +/obj/item/extinguisher/suicide_act(mob/living/carbon/user) + if (!safety && (reagents.total_volume >= 1)) + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth. It looks like [user.p_theyre()] trying to extinguish the spark of life!") + afterattack(user,user) + return OXYLOSS + else if (safety && (reagents.total_volume >= 1)) + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... The safety's still on!") + return SHAME + else + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... [src] is empty!") + return SHAME + +/obj/item/extinguisher/attack_self(mob/user) + safety = !safety + src.icon_state = "[sprite_name][!safety]" + to_chat(user, "The safety is [safety ? "on" : "off"].") + return + +/obj/item/extinguisher/attack(mob/M, mob/user) + if(user.a_intent == INTENT_HELP && !safety) //If we're on help intent and going to spray people, don't bash them. + return FALSE + else + return ..() + +/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) + if(AttemptRefill(O, user)) + refilling = TRUE + return FALSE + else + return ..() + +/obj/item/extinguisher/examine(mob/user) + . = ..() + . += "The safety is [safety ? "on" : "off"]." + + if(reagents.total_volume) + . += "Alt-click to empty it." + +/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) + if(istype(target, tanktype) && target.Adjacent(user)) + var/safety_save = safety + safety = TRUE + if(reagents.total_volume == reagents.maximum_volume) + to_chat(user, "\The [src] is already full!") + safety = safety_save + return 1 + var/obj/structure/reagent_dispensers/W = target //will it work? + var/transferred = W.reagents.trans_to(src, max_water, transfered_by = user) + if(transferred > 0) + to_chat(user, "\The [src] has been refilled by [transferred] units.") + playsound(src.loc, 'sound/effects/refill.ogg', 50, TRUE, -6) + for(var/datum/reagent/water/R in reagents.reagent_list) + R.cooling_temperature = cooling_power + else + to_chat(user, "\The [W] is empty!") + safety = safety_save + return 1 + else + return 0 + +/obj/item/extinguisher/afterattack(atom/target, mob/user , flag) + . = ..() + // Make it so the extinguisher doesn't spray yourself when you click your inventory items + if (target.loc == user) + return + //TODO; Add support for reagents in water. + + if(refilling) + refilling = FALSE + return + if (!safety) + + + if (src.reagents.total_volume < 1) + to_chat(usr, "\The [src] is empty!") + return + + if (world.time < src.last_use + 12) + return + + src.last_use = world.time + + playsound(src.loc, 'sound/effects/extinguish.ogg', 75, TRUE, -3) + + var/direction = get_dir(src,target) + + if(user.buckled && isobj(user.buckled) && !user.buckled.anchored) + var/obj/B = user.buckled + var/movementdirection = turn(direction,180) + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection), 1) + + else user.newtonian_move(turn(direction, 180)) + + //Get all the turfs that can be shot at + var/turf/T = get_turf(target) + var/turf/T1 = get_step(T,turn(direction, 90)) + var/turf/T2 = get_step(T,turn(direction, -90)) + var/list/the_targets = list(T,T1,T2) + if(precision) + var/turf/T3 = get_step(T1, turn(direction, 90)) + var/turf/T4 = get_step(T2,turn(direction, -90)) + the_targets.Add(T3,T4) + + var/list/water_particles=list() + for(var/a=0, a<5, a++) + var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water(get_turf(src)) + var/my_target = pick(the_targets) + water_particles[W] = my_target + // If precise, remove turf from targets so it won't be picked more than once + if(precision) + the_targets -= my_target + var/datum/reagents/R = new/datum/reagents(5) + W.reagents = R + R.my_atom = W + reagents.trans_to(W,1, transfered_by = user) + + //Make em move dat ass, hun + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, water_particles), 2) + +//Particle movement loop +/obj/item/extinguisher/proc/move_particles(list/particles, repetition=0) + //Check if there's anything in here first + if(!particles || particles.len == 0) + return + // Second loop: Get all the water particles and make them move to their target + for(var/obj/effect/particle_effect/water/W in particles) + var/turf/my_target = particles[W] + if(!W) + continue + step_towards(W,my_target) + if(!W.reagents) + continue + W.reagents.expose(get_turf(W)) + for(var/A in get_turf(W)) + W.reagents.expose(A) + if(W.loc == my_target) + particles -= W + if(repetition < power) + repetition++ + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, particles, repetition), 2) + +//Chair movement loop +/obj/item/extinguisher/proc/move_chair(obj/B, movementdirection, repetition=0) + step(B, movementdirection) + + var/timer_seconds + switch(repetition) + if(0 to 2) + timer_seconds = 1 + if(3 to 4) + timer_seconds = 2 + if(5 to 8) + timer_seconds = 3 + else + return + + repetition++ + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection, repetition), timer_seconds) + +/obj/item/extinguisher/AltClick(mob/user) + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + if(!user.is_holding(src)) + to_chat(user, "You must be holding the [src] in your hands do this!") + return + EmptyExtinguisher(user) + +/obj/item/extinguisher/proc/EmptyExtinguisher(var/mob/user) + if(loc == user && reagents.total_volume) + reagents.clear_reagents() + + var/turf/T = get_turf(loc) + if(isopenturf(T)) + var/turf/open/theturf = T + theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) + + user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] using its release valve.") + +//firebot assembly +/obj/item/extinguisher/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot)) + to_chat(user, "You add [O] to [src].") + qdel(O) + qdel(src) + user.put_in_hands(new /obj/item/bot_assembly/firebot) + else + ..() diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm index 8d73bf3faf8..a55f2fa290b 100644 --- a/code/game/objects/items/grenades/chem_grenade.dm +++ b/code/game/objects/items/grenades/chem_grenade.dm @@ -1,584 +1,584 @@ -/obj/item/grenade/chem_grenade - name = "chemical grenade" - desc = "A custom made grenade." - icon_state = "chemg" - inhand_icon_state = "flashbang" - w_class = WEIGHT_CLASS_SMALL - force = 2 - var/stage = GRENADE_EMPTY - var/list/obj/item/reagent_containers/glass/beakers = list() - var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) - var/list/banned_containers = list(/obj/item/reagent_containers/glass/beaker/bluespace) //Containers to exclude from specific grenade subtypes - var/affected_area = 3 - var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. - var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. - var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. - var/casedesc = "This basic model accepts both beakers and bottles. It heats contents by 10°K upon ignition." // Appears when examining empty casings. - var/obj/item/assembly/prox_sensor/landminemode = null - -/obj/item/grenade/chem_grenade/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) - -/obj/item/grenade/chem_grenade/Initialize() - . = ..() - create_reagents(1000) - stage_change() // If no argument is set, it will change the stage to the current stage, useful for stock grenades that start READY. - wires = new /datum/wires/explosive/chem_grenade(src) - -/obj/item/grenade/chem_grenade/examine(mob/user) - display_timer = (stage == GRENADE_READY) //show/hide the timer based on assembly state - . = ..() - if(user.can_see_reagents()) - if(beakers.len) - . += "You scan the grenade and detect the following reagents:" - for(var/obj/item/reagent_containers/glass/G in beakers) - for(var/datum/reagent/R in G.reagents.reagent_list) - . += "[R.volume] units of [R.name] in the [G.name]." - if(beakers.len == 1) - . += "You detect no second beaker in the grenade." - else - . += "You scan the grenade, but detect nothing." - else if(stage != GRENADE_READY && beakers.len) - if(beakers.len == 2 && beakers[1].name == beakers[2].name) - . += "You see two [beakers[1].name]s inside the grenade." - else - for(var/obj/item/reagent_containers/glass/G in beakers) - . += "You see a [G.name] inside the grenade." - -/obj/item/grenade/chem_grenade/attack_self(mob/user) - if(stage == GRENADE_READY && !active) - ..() - if(stage == GRENADE_WIRED) - wires.interact(user) - -/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) - if(istype(I,/obj/item/assembly) && stage == GRENADE_WIRED) - wires.interact(user) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(stage == GRENADE_WIRED) - if(beakers.len) - stage_change(GRENADE_READY) - to_chat(user, "You lock the [initial(name)] assembly.") - I.play_tool_sound(src, 25) - else - to_chat(user, "You need to add at least one beaker before locking the [initial(name)] assembly!") - else if(stage == GRENADE_READY) - det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 - if(landminemode) - landminemode.time = det_time * 0.1 //overwrites the proxy sensor activation timer - - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - else - to_chat(user, "You need to add a wire!") - return - else if(stage == GRENADE_WIRED && is_type_in_list(I, allowed_containers)) - . = TRUE //no afterattack - if(is_type_in_list(I, banned_containers)) - to_chat(user, "[src] is too small to fit [I]!") // this one hits home huh anon? - return - if(beakers.len == 2) - to_chat(user, "[src] can not hold more containers!") - return - else - if(I.reagents.total_volume) - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You add [I] to the [initial(name)] assembly.") - beakers += I - var/reagent_list = pretty_string_from_reagent_list(I.reagents) - user.log_message("inserted [I] ([reagent_list]) into [src]",LOG_GAME) - else - to_chat(user, "[I] is empty!") - - else if(stage == GRENADE_EMPTY && istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = I - if (C.use(1)) - det_time = 50 // In case the cable_coil was removed and readded. - stage_change(GRENADE_WIRED) - to_chat(user, "You rig the [initial(name)] assembly.") - else - to_chat(user, "You need one length of coil to wire the assembly!") - return - - else if(stage == GRENADE_READY && I.tool_behaviour == TOOL_WIRECUTTER && !active) - stage_change(GRENADE_WIRED) - to_chat(user, "You unlock the [initial(name)] assembly.") - - else if(stage == GRENADE_WIRED && I.tool_behaviour == TOOL_WRENCH) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(drop_location()) - if(!O.reagents) - continue - var/reagent_list = pretty_string_from_reagent_list(O.reagents) - user.log_message("removed [O] ([reagent_list]) from [src]", LOG_GAME) - beakers = list() - to_chat(user, "You open the [initial(name)] assembly and remove the payload.") - return - wires.detach_assembly(wires.get_wire(1)) - new /obj/item/stack/cable_coil(get_turf(src),1) - stage_change(GRENADE_EMPTY) - to_chat(user, "You remove the activation mechanism from the [initial(name)] assembly.") - else - return ..() - -/obj/item/grenade/chem_grenade/proc/stage_change(N) - if(N) - stage = N - if(stage == GRENADE_EMPTY) - name = "[initial(name)] casing" - desc = "A do it yourself [initial(name)]! [initial(casedesc)]" - icon_state = initial(icon_state) - else if(stage == GRENADE_WIRED) - name = "unsecured [initial(name)]" - desc = "An unsecured [initial(name)] assembly." - icon_state = "[initial(icon_state)]_ass" - else if(stage == GRENADE_READY) - name = initial(name) - desc = initial(desc) - icon_state = "[initial(icon_state)]_locked" - -/obj/item/grenade/chem_grenade/on_found(mob/finder) - var/obj/item/assembly/A = wires.get_attached(wires.get_wire(1)) - if(A) - A.on_found(finder) - -/obj/item/grenade/chem_grenade/log_grenade(mob/user, turf/T) - var/reagent_string = "" - var/beaker_number = 1 - for(var/obj/exploded_beaker in beakers) - if(!exploded_beaker.reagents) - continue - reagent_string += " ([exploded_beaker.name] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ");" - if(landminemode) - log_bomber(user, "activated a proxy", src, "containing:[reagent_string]") - else - log_bomber(user, "primed a", src, "containing:[reagent_string]") - -/obj/item/grenade/chem_grenade/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) - var/turf/T = get_turf(src) - log_grenade(user, T) //Inbuilt admin procs already handle null users - if(user) - add_fingerprint(user) - if(msg) - if(landminemode) - to_chat(user, "You prime [src], activating its proximity sensor.") - else - to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") - playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) - icon_state = initial(icon_state) + "_active" - if(landminemode) - landminemode.activate() - return - active = TRUE - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/chem_grenade/prime(mob/living/lanced_by) - if(stage != GRENADE_READY) - return - - . = ..() - var/list/datum/reagents/reactants = list() - for(var/obj/item/reagent_containers/glass/G in beakers) - reactants += G.reagents - - var/turf/detonation_turf = get_turf(src) - - if(!chem_splash(detonation_turf, affected_area, reactants, ignition_temp, threatscale) && !no_splash) - playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(drop_location()) - beakers = list() - stage_change(GRENADE_EMPTY) - active = FALSE - return -// logs from custom assemblies priming are handled by the wire component - log_game("A grenade detonated at [AREACOORD(detonation_turf)]") - - update_mob() - - qdel(src) - -//Large chem grenades accept slime cores and use the appropriately. -/obj/item/grenade/chem_grenade/large - name = "large grenade" - desc = "A custom made large grenade. Larger splash range and increased ignition temperature compared to basic grenades. Fits exotic and bluespace based containers." - casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores and bluespace beakers. Heats contents by 25°K upon ignition." - icon_state = "large_grenade" - allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/food/condiment, /obj/item/reagent_containers/food/drinks) - banned_containers = list() - affected_area = 5 - ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. - threatscale = 1.1 // 10% more effective. - -/obj/item/grenade/chem_grenade/large/prime(mob/living/lanced_by) - if(stage != GRENADE_READY) - return - - for(var/obj/item/slime_extract/S in beakers) - if(S.Uses) - for(var/obj/item/reagent_containers/glass/G in beakers) - G.reagents.trans_to(S, G.reagents.total_volume) - - //If there is still a core (sometimes it's used up) - //and there are reagents left, behave normally, - //otherwise drop it on the ground for timed reactions like gold. - - if(S) - if(S.reagents && S.reagents.total_volume) - for(var/obj/item/reagent_containers/glass/G in beakers) - S.reagents.trans_to(G, S.reagents.total_volume) - else - S.forceMove(get_turf(src)) - no_splash = TRUE - ..() - - //I tried to just put it in the allowed_containers list but - //if you do that it must have reagents. If you're going to - //make a special case you might as well do it explicitly. -Sayu -/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/slime_extract) && stage == GRENADE_WIRED) - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You add [I] to the [initial(name)] assembly.") - beakers += I - else - return ..() - -/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. - name = "cryo grenade" - desc = "A custom made cryogenic grenade. Rapidly cools contents upon ignition." - casedesc = "Upon ignition, it rapidly cools contents by 100°K. Smaller splash range than regular casings." - icon_state = "cryog" - affected_area = 2 - ignition_temp = -100 - -/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. - name = "pyro grenade" - desc = "A custom made pyrotechnical grenade. Heats up contents upon ignition." - casedesc = "Upon ignition, it rapidly heats contents by 500°K." - icon_state = "pyrog" - ignition_temp = 500 // This is enough to expose a hotspot. - -/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. - name = "advanced release grenade" - desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." - casedesc = "This casing is able to detonate more than once. Can be configured using a multitool." - icon_state = "timeg" - var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. - -/obj/item/grenade/chem_grenade/adv_release/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_MULTITOOL && !active) - var/newspread = text2num(stripped_input(user, "Please enter a new spread amount", name)) - if (newspread != null && user.canUseTopic(src, BE_CLOSE)) - newspread = round(newspread) - unit_spread = clamp(newspread, 5, 100) - to_chat(user, "You set the time release to [unit_spread] units per detonation.") - if (newspread != unit_spread) - to_chat(user, "The new value is out of bounds. Minimum spread is 5 units, maximum is 100 units.") - ..() - -/obj/item/grenade/chem_grenade/adv_release/prime(mob/living/lanced_by) - if(stage != GRENADE_READY) - return - - var/total_volume = 0 - for(var/obj/item/reagent_containers/RC in beakers) - total_volume += RC.reagents.total_volume - if(!total_volume) - qdel(src) - return - var/fraction = unit_spread/total_volume - var/datum/reagents/reactants = new(unit_spread) - reactants.my_atom = src - for(var/obj/item/reagent_containers/RC in beakers) - RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) - chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) - - var/turf/DT = get_turf(src) - addtimer(CALLBACK(src, .proc/prime), det_time) - log_game("A grenade detonated at [AREACOORD(DT)]") - - - - -////////////////////////////// -////// PREMADE GRENADES ////// -////////////////////////////// - -/obj/item/grenade/chem_grenade/metalfoam - name = "metal foam grenade" - desc = "Used for emergency sealing of hull breaches." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/metalfoam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/aluminium, 30) - B2.reagents.add_reagent(/datum/reagent/foaming_agent, 10) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 10) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/smart_metal_foam - name = "smart metal foam grenade" - desc = "Used for emergency sealing of hull breaches, while keeping areas accessible." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/smart_metal_foam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/aluminium, 75) - B2.reagents.add_reagent(/datum/reagent/smart_foaming_agent, 25) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/incendiary - name = "incendiary grenade" - desc = "Used for clearing rooms of living things." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/incendiary/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/stable_plasma, 25) - B2.reagents.add_reagent(/datum/reagent/toxin/acid, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/antiweed - name = "weedkiller grenade" - desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/antiweed/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/toxin/plantbgone, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/cleaner - name = "cleaner grenade" - desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/cleaner/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) - B2.reagents.add_reagent(/datum/reagent/water, 40) - B2.reagents.add_reagent(/datum/reagent/space_cleaner, 10) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/ez_clean - name = "cleaner grenade" - desc = "Waffle Co.-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/ez_clean/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) - B2.reagents.add_reagent(/datum/reagent/water, 40) - B2.reagents.add_reagent(/datum/reagent/space_cleaner/ez_clean, 60) //ensures a t h i c c distribution - - beakers += B1 - beakers += B2 - - - -/obj/item/grenade/chem_grenade/teargas - name = "teargas grenade" - desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/teargas/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/consumable/condensedcapsaicin, 60) - B1.reagents.add_reagent(/datum/reagent/potassium, 40) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 40) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 40) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/facid - name = "acid grenade" - desc = "Used for melting armoured opponents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/facid/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 290) - B1.reagents.add_reagent(/datum/reagent/potassium, 10) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 10) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 10) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 280) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/colorful - name = "colorful grenade" - desc = "Used for wide scale painting projects." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/colorful/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/colorful_reagent, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/glitter - name = "generic glitter grenade" - desc = "You shouldn't see this description." - stage = GRENADE_READY - var/glitter_type = /datum/reagent/glitter - -/obj/item/grenade/chem_grenade/glitter/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(glitter_type, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/glitter/pink - name = "pink glitter bomb" - desc = "For that HOT glittery look." - glitter_type = /datum/reagent/glitter/pink - -/obj/item/grenade/chem_grenade/glitter/blue - name = "blue glitter bomb" - desc = "For that COOL glittery look." - glitter_type = /datum/reagent/glitter/blue - -/obj/item/grenade/chem_grenade/glitter/white - name = "white glitter bomb" - desc = "For that somnolent glittery look." - glitter_type = /datum/reagent/glitter/white - -/obj/item/grenade/chem_grenade/clf3 - name = "clf3 grenade" - desc = "BURN!-brand foaming clf3. In a special applicator for rapid purging of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/clf3/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 250) - B1.reagents.add_reagent(/datum/reagent/clf3, 50) - B2.reagents.add_reagent(/datum/reagent/water, 250) - B2.reagents.add_reagent(/datum/reagent/clf3, 50) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/bioterrorfoam - name = "Bio terror foam grenade" - desc = "Tiger Cooperative chemical foam grenade. Causes temporary irration, blindness, confusion, mutism, and mutations to carbon based life forms. Contains additional spore toxin." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/bioterrorfoam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/cryptobiolin, 75) - B1.reagents.add_reagent(/datum/reagent/water, 50) - B1.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 50) - B1.reagents.add_reagent(/datum/reagent/toxin/spore, 75) - B1.reagents.add_reagent(/datum/reagent/toxin/itching_powder, 50) - B2.reagents.add_reagent(/datum/reagent/fluorosurfactant, 150) - B2.reagents.add_reagent(/datum/reagent/toxin/mutagen, 150) - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/tuberculosis - name = "Fungal tuberculosis grenade" - desc = "WARNING: GRENADE WILL RELEASE DEADLY SPORES CONTAINING ACTIVE AGENTS. SEAL SUIT AND AIRFLOW BEFORE USE." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/tuberculosis/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/potassium, 50) - B1.reagents.add_reagent(/datum/reagent/phosphorus, 50) - B1.reagents.add_reagent(/datum/reagent/fungalspores, 200) - B2.reagents.add_reagent(/datum/reagent/blood, 250) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 50) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/holy - name = "holy hand grenade" - desc = "A vessel of concentrated religious might." - icon_state = "holy_grenade" - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/holy/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/potassium, 100) - B2.reagents.add_reagent(/datum/reagent/water/holywater, 100) - - beakers += B1 - beakers += B2 +/obj/item/grenade/chem_grenade + name = "chemical grenade" + desc = "A custom made grenade." + icon_state = "chemg" + inhand_icon_state = "flashbang" + w_class = WEIGHT_CLASS_SMALL + force = 2 + var/stage = GRENADE_EMPTY + var/list/obj/item/reagent_containers/glass/beakers = list() + var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) + var/list/banned_containers = list(/obj/item/reagent_containers/glass/beaker/bluespace) //Containers to exclude from specific grenade subtypes + var/affected_area = 3 + var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. + var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. + var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. + var/casedesc = "This basic model accepts both beakers and bottles. It heats contents by 10°K upon ignition." // Appears when examining empty casings. + var/obj/item/assembly/prox_sensor/landminemode = null + +/obj/item/grenade/chem_grenade/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) + +/obj/item/grenade/chem_grenade/Initialize() + . = ..() + create_reagents(1000) + stage_change() // If no argument is set, it will change the stage to the current stage, useful for stock grenades that start READY. + wires = new /datum/wires/explosive/chem_grenade(src) + +/obj/item/grenade/chem_grenade/examine(mob/user) + display_timer = (stage == GRENADE_READY) //show/hide the timer based on assembly state + . = ..() + if(user.can_see_reagents()) + if(beakers.len) + . += "You scan the grenade and detect the following reagents:" + for(var/obj/item/reagent_containers/glass/G in beakers) + for(var/datum/reagent/R in G.reagents.reagent_list) + . += "[R.volume] units of [R.name] in the [G.name]." + if(beakers.len == 1) + . += "You detect no second beaker in the grenade." + else + . += "You scan the grenade, but detect nothing." + else if(stage != GRENADE_READY && beakers.len) + if(beakers.len == 2 && beakers[1].name == beakers[2].name) + . += "You see two [beakers[1].name]s inside the grenade." + else + for(var/obj/item/reagent_containers/glass/G in beakers) + . += "You see a [G.name] inside the grenade." + +/obj/item/grenade/chem_grenade/attack_self(mob/user) + if(stage == GRENADE_READY && !active) + ..() + if(stage == GRENADE_WIRED) + wires.interact(user) + +/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) + if(istype(I,/obj/item/assembly) && stage == GRENADE_WIRED) + wires.interact(user) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(stage == GRENADE_WIRED) + if(beakers.len) + stage_change(GRENADE_READY) + to_chat(user, "You lock the [initial(name)] assembly.") + I.play_tool_sound(src, 25) + else + to_chat(user, "You need to add at least one beaker before locking the [initial(name)] assembly!") + else if(stage == GRENADE_READY) + det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 + if(landminemode) + landminemode.time = det_time * 0.1 //overwrites the proxy sensor activation timer + + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + else + to_chat(user, "You need to add a wire!") + return + else if(stage == GRENADE_WIRED && is_type_in_list(I, allowed_containers)) + . = TRUE //no afterattack + if(is_type_in_list(I, banned_containers)) + to_chat(user, "[src] is too small to fit [I]!") // this one hits home huh anon? + return + if(beakers.len == 2) + to_chat(user, "[src] can not hold more containers!") + return + else + if(I.reagents.total_volume) + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You add [I] to the [initial(name)] assembly.") + beakers += I + var/reagent_list = pretty_string_from_reagent_list(I.reagents) + user.log_message("inserted [I] ([reagent_list]) into [src]",LOG_GAME) + else + to_chat(user, "[I] is empty!") + + else if(stage == GRENADE_EMPTY && istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = I + if (C.use(1)) + det_time = 50 // In case the cable_coil was removed and readded. + stage_change(GRENADE_WIRED) + to_chat(user, "You rig the [initial(name)] assembly.") + else + to_chat(user, "You need one length of coil to wire the assembly!") + return + + else if(stage == GRENADE_READY && I.tool_behaviour == TOOL_WIRECUTTER && !active) + stage_change(GRENADE_WIRED) + to_chat(user, "You unlock the [initial(name)] assembly.") + + else if(stage == GRENADE_WIRED && I.tool_behaviour == TOOL_WRENCH) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(drop_location()) + if(!O.reagents) + continue + var/reagent_list = pretty_string_from_reagent_list(O.reagents) + user.log_message("removed [O] ([reagent_list]) from [src]", LOG_GAME) + beakers = list() + to_chat(user, "You open the [initial(name)] assembly and remove the payload.") + return + wires.detach_assembly(wires.get_wire(1)) + new /obj/item/stack/cable_coil(get_turf(src),1) + stage_change(GRENADE_EMPTY) + to_chat(user, "You remove the activation mechanism from the [initial(name)] assembly.") + else + return ..() + +/obj/item/grenade/chem_grenade/proc/stage_change(N) + if(N) + stage = N + if(stage == GRENADE_EMPTY) + name = "[initial(name)] casing" + desc = "A do it yourself [initial(name)]! [initial(casedesc)]" + icon_state = initial(icon_state) + else if(stage == GRENADE_WIRED) + name = "unsecured [initial(name)]" + desc = "An unsecured [initial(name)] assembly." + icon_state = "[initial(icon_state)]_ass" + else if(stage == GRENADE_READY) + name = initial(name) + desc = initial(desc) + icon_state = "[initial(icon_state)]_locked" + +/obj/item/grenade/chem_grenade/on_found(mob/finder) + var/obj/item/assembly/A = wires.get_attached(wires.get_wire(1)) + if(A) + A.on_found(finder) + +/obj/item/grenade/chem_grenade/log_grenade(mob/user, turf/T) + var/reagent_string = "" + var/beaker_number = 1 + for(var/obj/exploded_beaker in beakers) + if(!exploded_beaker.reagents) + continue + reagent_string += " ([exploded_beaker.name] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ");" + if(landminemode) + log_bomber(user, "activated a proxy", src, "containing:[reagent_string]") + else + log_bomber(user, "primed a", src, "containing:[reagent_string]") + +/obj/item/grenade/chem_grenade/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) + var/turf/T = get_turf(src) + log_grenade(user, T) //Inbuilt admin procs already handle null users + if(user) + add_fingerprint(user) + if(msg) + if(landminemode) + to_chat(user, "You prime [src], activating its proximity sensor.") + else + to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") + playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) + icon_state = initial(icon_state) + "_active" + if(landminemode) + landminemode.activate() + return + active = TRUE + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/chem_grenade/prime(mob/living/lanced_by) + if(stage != GRENADE_READY) + return + + . = ..() + var/list/datum/reagents/reactants = list() + for(var/obj/item/reagent_containers/glass/G in beakers) + reactants += G.reagents + + var/turf/detonation_turf = get_turf(src) + + if(!chem_splash(detonation_turf, affected_area, reactants, ignition_temp, threatscale) && !no_splash) + playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(drop_location()) + beakers = list() + stage_change(GRENADE_EMPTY) + active = FALSE + return +// logs from custom assemblies priming are handled by the wire component + log_game("A grenade detonated at [AREACOORD(detonation_turf)]") + + update_mob() + + qdel(src) + +//Large chem grenades accept slime cores and use the appropriately. +/obj/item/grenade/chem_grenade/large + name = "large grenade" + desc = "A custom made large grenade. Larger splash range and increased ignition temperature compared to basic grenades. Fits exotic and bluespace based containers." + casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores and bluespace beakers. Heats contents by 25°K upon ignition." + icon_state = "large_grenade" + allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/food/condiment, /obj/item/reagent_containers/food/drinks) + banned_containers = list() + affected_area = 5 + ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. + threatscale = 1.1 // 10% more effective. + +/obj/item/grenade/chem_grenade/large/prime(mob/living/lanced_by) + if(stage != GRENADE_READY) + return + + for(var/obj/item/slime_extract/S in beakers) + if(S.Uses) + for(var/obj/item/reagent_containers/glass/G in beakers) + G.reagents.trans_to(S, G.reagents.total_volume) + + //If there is still a core (sometimes it's used up) + //and there are reagents left, behave normally, + //otherwise drop it on the ground for timed reactions like gold. + + if(S) + if(S.reagents && S.reagents.total_volume) + for(var/obj/item/reagent_containers/glass/G in beakers) + S.reagents.trans_to(G, S.reagents.total_volume) + else + S.forceMove(get_turf(src)) + no_splash = TRUE + ..() + + //I tried to just put it in the allowed_containers list but + //if you do that it must have reagents. If you're going to + //make a special case you might as well do it explicitly. -Sayu +/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/slime_extract) && stage == GRENADE_WIRED) + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You add [I] to the [initial(name)] assembly.") + beakers += I + else + return ..() + +/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. + name = "cryo grenade" + desc = "A custom made cryogenic grenade. Rapidly cools contents upon ignition." + casedesc = "Upon ignition, it rapidly cools contents by 100°K. Smaller splash range than regular casings." + icon_state = "cryog" + affected_area = 2 + ignition_temp = -100 + +/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. + name = "pyro grenade" + desc = "A custom made pyrotechnical grenade. Heats up contents upon ignition." + casedesc = "Upon ignition, it rapidly heats contents by 500°K." + icon_state = "pyrog" + ignition_temp = 500 // This is enough to expose a hotspot. + +/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. + name = "advanced release grenade" + desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." + casedesc = "This casing is able to detonate more than once. Can be configured using a multitool." + icon_state = "timeg" + var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. + +/obj/item/grenade/chem_grenade/adv_release/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_MULTITOOL && !active) + var/newspread = text2num(stripped_input(user, "Please enter a new spread amount", name)) + if (newspread != null && user.canUseTopic(src, BE_CLOSE)) + newspread = round(newspread) + unit_spread = clamp(newspread, 5, 100) + to_chat(user, "You set the time release to [unit_spread] units per detonation.") + if (newspread != unit_spread) + to_chat(user, "The new value is out of bounds. Minimum spread is 5 units, maximum is 100 units.") + ..() + +/obj/item/grenade/chem_grenade/adv_release/prime(mob/living/lanced_by) + if(stage != GRENADE_READY) + return + + var/total_volume = 0 + for(var/obj/item/reagent_containers/RC in beakers) + total_volume += RC.reagents.total_volume + if(!total_volume) + qdel(src) + return + var/fraction = unit_spread/total_volume + var/datum/reagents/reactants = new(unit_spread) + reactants.my_atom = src + for(var/obj/item/reagent_containers/RC in beakers) + RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) + chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) + + var/turf/DT = get_turf(src) + addtimer(CALLBACK(src, .proc/prime), det_time) + log_game("A grenade detonated at [AREACOORD(DT)]") + + + + +////////////////////////////// +////// PREMADE GRENADES ////// +////////////////////////////// + +/obj/item/grenade/chem_grenade/metalfoam + name = "metal foam grenade" + desc = "Used for emergency sealing of hull breaches." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/metalfoam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/aluminium, 30) + B2.reagents.add_reagent(/datum/reagent/foaming_agent, 10) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 10) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/smart_metal_foam + name = "smart metal foam grenade" + desc = "Used for emergency sealing of hull breaches, while keeping areas accessible." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/smart_metal_foam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/aluminium, 75) + B2.reagents.add_reagent(/datum/reagent/smart_foaming_agent, 25) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/incendiary + name = "incendiary grenade" + desc = "Used for clearing rooms of living things." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/incendiary/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/stable_plasma, 25) + B2.reagents.add_reagent(/datum/reagent/toxin/acid, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/antiweed + name = "weedkiller grenade" + desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/antiweed/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/toxin/plantbgone, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/cleaner + name = "cleaner grenade" + desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/cleaner/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) + B2.reagents.add_reagent(/datum/reagent/water, 40) + B2.reagents.add_reagent(/datum/reagent/space_cleaner, 10) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/ez_clean + name = "cleaner grenade" + desc = "Waffle Co.-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/ez_clean/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) + B2.reagents.add_reagent(/datum/reagent/water, 40) + B2.reagents.add_reagent(/datum/reagent/space_cleaner/ez_clean, 60) //ensures a t h i c c distribution + + beakers += B1 + beakers += B2 + + + +/obj/item/grenade/chem_grenade/teargas + name = "teargas grenade" + desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/teargas/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/consumable/condensedcapsaicin, 60) + B1.reagents.add_reagent(/datum/reagent/potassium, 40) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 40) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 40) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/facid + name = "acid grenade" + desc = "Used for melting armoured opponents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/facid/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 290) + B1.reagents.add_reagent(/datum/reagent/potassium, 10) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 10) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 10) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 280) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/colorful + name = "colorful grenade" + desc = "Used for wide scale painting projects." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/colorful/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/colorful_reagent, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/glitter + name = "generic glitter grenade" + desc = "You shouldn't see this description." + stage = GRENADE_READY + var/glitter_type = /datum/reagent/glitter + +/obj/item/grenade/chem_grenade/glitter/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(glitter_type, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/glitter/pink + name = "pink glitter bomb" + desc = "For that HOT glittery look." + glitter_type = /datum/reagent/glitter/pink + +/obj/item/grenade/chem_grenade/glitter/blue + name = "blue glitter bomb" + desc = "For that COOL glittery look." + glitter_type = /datum/reagent/glitter/blue + +/obj/item/grenade/chem_grenade/glitter/white + name = "white glitter bomb" + desc = "For that somnolent glittery look." + glitter_type = /datum/reagent/glitter/white + +/obj/item/grenade/chem_grenade/clf3 + name = "clf3 grenade" + desc = "BURN!-brand foaming clf3. In a special applicator for rapid purging of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/clf3/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 250) + B1.reagents.add_reagent(/datum/reagent/clf3, 50) + B2.reagents.add_reagent(/datum/reagent/water, 250) + B2.reagents.add_reagent(/datum/reagent/clf3, 50) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/bioterrorfoam + name = "Bio terror foam grenade" + desc = "Tiger Cooperative chemical foam grenade. Causes temporary irration, blindness, confusion, mutism, and mutations to carbon based life forms. Contains additional spore toxin." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/bioterrorfoam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/cryptobiolin, 75) + B1.reagents.add_reagent(/datum/reagent/water, 50) + B1.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 50) + B1.reagents.add_reagent(/datum/reagent/toxin/spore, 75) + B1.reagents.add_reagent(/datum/reagent/toxin/itching_powder, 50) + B2.reagents.add_reagent(/datum/reagent/fluorosurfactant, 150) + B2.reagents.add_reagent(/datum/reagent/toxin/mutagen, 150) + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/tuberculosis + name = "Fungal tuberculosis grenade" + desc = "WARNING: GRENADE WILL RELEASE DEADLY SPORES CONTAINING ACTIVE AGENTS. SEAL SUIT AND AIRFLOW BEFORE USE." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/tuberculosis/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/potassium, 50) + B1.reagents.add_reagent(/datum/reagent/phosphorus, 50) + B1.reagents.add_reagent(/datum/reagent/fungalspores, 200) + B2.reagents.add_reagent(/datum/reagent/blood, 250) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 50) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/holy + name = "holy hand grenade" + desc = "A vessel of concentrated religious might." + icon_state = "holy_grenade" + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/holy/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/potassium, 100) + B2.reagents.add_reagent(/datum/reagent/water/holywater, 100) + + beakers += B1 + beakers += B2 diff --git a/code/game/objects/items/grenades/emgrenade.dm b/code/game/objects/items/grenades/emgrenade.dm index 1dd007baecb..00f9a26bed8 100644 --- a/code/game/objects/items/grenades/emgrenade.dm +++ b/code/game/objects/items/grenades/emgrenade.dm @@ -1,11 +1,11 @@ -/obj/item/grenade/empgrenade - name = "classic EMP grenade" - desc = "It is designed to wreak havoc on electronic systems." - icon_state = "emp" - inhand_icon_state = "emp" - -/obj/item/grenade/empgrenade/prime(mob/living/lanced_by) - . = ..() - update_mob() - empulse(src, 4, 10) - qdel(src) +/obj/item/grenade/empgrenade + name = "classic EMP grenade" + desc = "It is designed to wreak havoc on electronic systems." + icon_state = "emp" + inhand_icon_state = "emp" + +/obj/item/grenade/empgrenade/prime(mob/living/lanced_by) + . = ..() + update_mob() + empulse(src, 4, 10) + qdel(src) diff --git a/code/game/objects/items/grenades/festive.dm b/code/game/objects/items/grenades/festive.dm index 6f3aa72693f..861b61b26f6 100644 --- a/code/game/objects/items/grenades/festive.dm +++ b/code/game/objects/items/grenades/festive.dm @@ -1,118 +1,118 @@ -//~*~*~*~*SPARKLER*~*~*~*~*~*~ - -/obj/item/sparkler - name = "sparkler" - desc = "A little stick coated with metal power and barium nitrate, burns with a pleasing sparkle." - icon = 'icons/obj/holiday_misc.dmi' - icon_state = "sparkler" - w_class = WEIGHT_CLASS_TINY - heat = 1000 - var/burntime = 60 - var/lit = FALSE - -/obj/item/sparkler/fire_act(exposed_temperature, exposed_volume) - light() - -/obj/item/sparkler/attackby(obj/item/W, mob/user, params) - var/ignition_msg = W.ignition_effect(src, user) - if(ignition_msg) - light(user, ignition_msg) - else - return ..() - -/obj/item/sparkler/proc/light(mob/user, message) - if(lit) - return - if(user && message) - user.visible_message(message) - lit = TRUE - icon_state = "sparkler_on" - force = 6 - hitsound = 'sound/items/welder.ogg' - name = "lit [initial(name)]" - attack_verb = list("burnt") - set_light(l_range = 2, l_power = 2) - damtype = "fire" - START_PROCESSING(SSobj, src) - playsound(src, 'sound/effects/fuse.ogg', 20, TRUE) - update_icon() - -/obj/item/sparkler/process() - burntime-- - if(burntime < 1) - new /obj/item/stack/rods(drop_location()) - qdel(src) - else - open_flame(heat) - -/obj/item/sparkler/Destroy() - STOP_PROCESSING(SSobj, src) - ..() - -/obj/item/sparkler/ignition_effect(atom/A, mob/user) - . = "[user] gracefully lights [A] with [src]." - -/obj/item/sparkler/get_temperature() - return lit * heat - -//~*~*~*~*FIRECRACKER*~*~*~*~*~*~ - -/obj/item/grenade/firecracker - name = "large firecracker" - desc = "Outlawed in most of the sector. Doubles as an excellent finger remover." - icon = 'icons/obj/holiday_misc.dmi' - icon_state = "firecracker" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - inhand_icon_state = "flare" - throw_speed = 3 - throw_range = 7 - det_time = 30 - -/obj/item/grenade/firecracker/attack_self(mob/user) // You need to light it manually. - return - -/obj/item/grenade/firecracker/attackby(obj/item/W, mob/user, params) - var/ignition_msg = W.ignition_effect(src, user) - if(ignition_msg && !active) - visible_message(ignition_msg) - preprime(user) - else - return ..() - -/obj/item/grenade/firecracker/fire_act(exposed_temperature, exposed_volume) - prime() - -obj/item/grenade/firecracker/wirecutter_act(mob/living/user, obj/item/I) - if(active) - return - if(det_time) - det_time -= 10 - to_chat(user, "You shorten the fuse of [src] with [I].") - playsound(src, 'sound/items/wirecutter.ogg', 20, TRUE) - icon_state = initial(icon_state) + "_[det_time]" - update_icon() - else - to_chat(user, "You've already removed all of the fuse!") - -obj/item/grenade/firecracker/preprime(mob/user, delayoverride, msg = TRUE, volume = 80) - var/turf/T = get_turf(src) - log_grenade(user, T) - if(user) - add_fingerprint(user) - if(msg) - to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") - playsound(src, 'sound/effects/fuse.ogg', volume, TRUE) - active = TRUE - icon_state = initial(icon_state) + "_active" - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/firecracker/prime(mob/living/lanced_by) - . = ..() - update_mob() - var/explosion_loc = get_turf(src) - qdel(src) - explosion(explosion_loc,-1,-1,2) - - +//~*~*~*~*SPARKLER*~*~*~*~*~*~ + +/obj/item/sparkler + name = "sparkler" + desc = "A little stick coated with metal power and barium nitrate, burns with a pleasing sparkle." + icon = 'icons/obj/holiday_misc.dmi' + icon_state = "sparkler" + w_class = WEIGHT_CLASS_TINY + heat = 1000 + var/burntime = 60 + var/lit = FALSE + +/obj/item/sparkler/fire_act(exposed_temperature, exposed_volume) + light() + +/obj/item/sparkler/attackby(obj/item/W, mob/user, params) + var/ignition_msg = W.ignition_effect(src, user) + if(ignition_msg) + light(user, ignition_msg) + else + return ..() + +/obj/item/sparkler/proc/light(mob/user, message) + if(lit) + return + if(user && message) + user.visible_message(message) + lit = TRUE + icon_state = "sparkler_on" + force = 6 + hitsound = 'sound/items/welder.ogg' + name = "lit [initial(name)]" + attack_verb = list("burnt") + set_light(l_range = 2, l_power = 2) + damtype = "fire" + START_PROCESSING(SSobj, src) + playsound(src, 'sound/effects/fuse.ogg', 20, TRUE) + update_icon() + +/obj/item/sparkler/process() + burntime-- + if(burntime < 1) + new /obj/item/stack/rods(drop_location()) + qdel(src) + else + open_flame(heat) + +/obj/item/sparkler/Destroy() + STOP_PROCESSING(SSobj, src) + ..() + +/obj/item/sparkler/ignition_effect(atom/A, mob/user) + . = "[user] gracefully lights [A] with [src]." + +/obj/item/sparkler/get_temperature() + return lit * heat + +//~*~*~*~*FIRECRACKER*~*~*~*~*~*~ + +/obj/item/grenade/firecracker + name = "large firecracker" + desc = "Outlawed in most of the sector. Doubles as an excellent finger remover." + icon = 'icons/obj/holiday_misc.dmi' + icon_state = "firecracker" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + inhand_icon_state = "flare" + throw_speed = 3 + throw_range = 7 + det_time = 30 + +/obj/item/grenade/firecracker/attack_self(mob/user) // You need to light it manually. + return + +/obj/item/grenade/firecracker/attackby(obj/item/W, mob/user, params) + var/ignition_msg = W.ignition_effect(src, user) + if(ignition_msg && !active) + visible_message(ignition_msg) + preprime(user) + else + return ..() + +/obj/item/grenade/firecracker/fire_act(exposed_temperature, exposed_volume) + prime() + +obj/item/grenade/firecracker/wirecutter_act(mob/living/user, obj/item/I) + if(active) + return + if(det_time) + det_time -= 10 + to_chat(user, "You shorten the fuse of [src] with [I].") + playsound(src, 'sound/items/wirecutter.ogg', 20, TRUE) + icon_state = initial(icon_state) + "_[det_time]" + update_icon() + else + to_chat(user, "You've already removed all of the fuse!") + +obj/item/grenade/firecracker/preprime(mob/user, delayoverride, msg = TRUE, volume = 80) + var/turf/T = get_turf(src) + log_grenade(user, T) + if(user) + add_fingerprint(user) + if(msg) + to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") + playsound(src, 'sound/effects/fuse.ogg', volume, TRUE) + active = TRUE + icon_state = initial(icon_state) + "_active" + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/firecracker/prime(mob/living/lanced_by) + . = ..() + update_mob() + var/explosion_loc = get_turf(src) + qdel(src) + explosion(explosion_loc,-1,-1,2) + + diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index b44a735e1da..7a3962ae09e 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -1,140 +1,140 @@ -/obj/item/grenade/flashbang - name = "flashbang" - icon_state = "flashbang" - inhand_icon_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - var/flashbang_range = 7 //how many tiles away the mob will be stunned. - -/obj/item/grenade/flashbang/prime(mob/living/lanced_by) - . = ..() - update_mob() - var/flashbang_turf = get_turf(src) - if(!flashbang_turf) - return - do_sparks(rand(5, 9), FALSE, src) - playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9) - new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2) - for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) - bang(get_turf(M), M) - qdel(src) - -/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) - if(M.stat == DEAD) //They're dead! - return - M.show_message("BANG", MSG_AUDIBLE) - var/distance = max(0,get_dist(get_turf(src),T)) - -//Flash - if(M.flash_act(affect_silicon = 1)) - M.Paralyze(max(20/max(1,distance), 5)) - M.Knockdown(max(200/max(1,distance), 60)) - -//Bang - if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. - M.Paralyze(20) - M.Knockdown(200) - M.soundbang_act(1, 200, 10, 15) - else - if(distance <= 1) // Adds more stun as to not prime n' pull (#45381) - M.Paralyze(5) - M.Knockdown(30) - M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) - -/obj/item/grenade/stingbang - name = "stingbang" - icon_state = "timeg" - inhand_icon_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - var/flashbang_range = 1 //how many tiles away the mob will be stunned. - shrapnel_type = /obj/projectile/bullet/pellet/stingball - shrapnel_radius = 5 - custom_premium_price = 700 // mostly gotten through cargo, but throw in one for the sec vendor ;) - -/obj/item/grenade/stingbang/mega - name = "mega stingbang" - shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega - shrapnel_radius = 12 - -/obj/item/grenade/stingbang/breaker - name = "breakbang" - shrapnel_type = /obj/projectile/bullet/pellet/stingball/breaker - -/obj/item/grenade/stingbang/shred - name = "shredbang" - shrapnel_type = /obj/projectile/bullet/pellet/stingball/shred - -/obj/item/grenade/stingbang/prime(mob/living/lanced_by) - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - var/obj/item/bodypart/B = C.get_holding_bodypart_of_item(src) - if(B) - forceMove(get_turf(C)) - C.visible_message("[src] goes off in [C]'s hand, blowing [C.p_their()] [B.name] to bloody shreds!", "[src] goes off in your hand, blowing your [B.name] to bloody shreds!") - B.dismember() - - . = ..() - - update_mob() - var/flashbang_turf = get_turf(src) - if(!flashbang_turf) - return - do_sparks(rand(5, 9), FALSE, src) - playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 50, TRUE, 8, 0.9) - new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 2, 1) - for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) - pop(get_turf(M), M) - qdel(src) - -/obj/item/grenade/stingbang/proc/pop(turf/T , mob/living/M) - if(M.stat == DEAD) //They're dead! - return - M.show_message("POP", MSG_AUDIBLE) - var/distance = max(0,get_dist(get_turf(src),T)) -//Flash - if(M.flash_act(affect_silicon = 1)) - M.Paralyze(max(10/max(1,distance), 5)) - M.Knockdown(max(100/max(1,distance), 60)) - -//Bang - if(!distance || loc == M || loc == M.loc) - M.Paralyze(20) - M.Knockdown(200) - M.soundbang_act(1, 200, 10, 15) - if(M.apply_damages(10, 10)) - to_chat(M, "The blast from \the [src] bruises and burns you!") - - // only checking if they're on top of the tile, cause being one tile over will be its own punishment - -// Grenade that releases more shrapnel the more times you use it in hand between priming and detonation (sorta like the 9bang from MW3), for admin goofs -/obj/item/grenade/primer - name = "rotfrag grenade" - desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards." - icon_state = "timeg" - inhand_icon_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - var/rots_per_mag = 3 /// how many times we need to "rotate" the charge in hand per extra tile of magnitude - shrapnel_type = /obj/projectile/bullet/shrapnel - var/rots = 1 /// how many times we've "rotated" the charge - -/obj/item/grenade/primer/attack_self(mob/user) - . = ..() - if(active) - user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE) - rots++ - user.changeNext_move(CLICK_CD_RAPID) - -/obj/item/grenade/primer/prime(mob/living/lanced_by) - shrapnel_radius = round(rots / rots_per_mag) - . = ..() - qdel(src) - -/obj/item/grenade/primer/stingbang - name = "rotsting" - desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases stingballs." - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - rots_per_mag = 2 - shrapnel_type = /obj/projectile/bullet/pellet/stingball +/obj/item/grenade/flashbang + name = "flashbang" + icon_state = "flashbang" + inhand_icon_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/flashbang_range = 7 //how many tiles away the mob will be stunned. + +/obj/item/grenade/flashbang/prime(mob/living/lanced_by) + . = ..() + update_mob() + var/flashbang_turf = get_turf(src) + if(!flashbang_turf) + return + do_sparks(rand(5, 9), FALSE, src) + playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9) + new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2) + for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) + bang(get_turf(M), M) + qdel(src) + +/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) + if(M.stat == DEAD) //They're dead! + return + M.show_message("BANG", MSG_AUDIBLE) + var/distance = max(0,get_dist(get_turf(src),T)) + +//Flash + if(M.flash_act(affect_silicon = 1)) + M.Paralyze(max(20/max(1,distance), 5)) + M.Knockdown(max(200/max(1,distance), 60)) + +//Bang + if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. + M.Paralyze(20) + M.Knockdown(200) + M.soundbang_act(1, 200, 10, 15) + else + if(distance <= 1) // Adds more stun as to not prime n' pull (#45381) + M.Paralyze(5) + M.Knockdown(30) + M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) + +/obj/item/grenade/stingbang + name = "stingbang" + icon_state = "timeg" + inhand_icon_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/flashbang_range = 1 //how many tiles away the mob will be stunned. + shrapnel_type = /obj/projectile/bullet/pellet/stingball + shrapnel_radius = 5 + custom_premium_price = 700 // mostly gotten through cargo, but throw in one for the sec vendor ;) + +/obj/item/grenade/stingbang/mega + name = "mega stingbang" + shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega + shrapnel_radius = 12 + +/obj/item/grenade/stingbang/breaker + name = "breakbang" + shrapnel_type = /obj/projectile/bullet/pellet/stingball/breaker + +/obj/item/grenade/stingbang/shred + name = "shredbang" + shrapnel_type = /obj/projectile/bullet/pellet/stingball/shred + +/obj/item/grenade/stingbang/prime(mob/living/lanced_by) + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + var/obj/item/bodypart/B = C.get_holding_bodypart_of_item(src) + if(B) + forceMove(get_turf(C)) + C.visible_message("[src] goes off in [C]'s hand, blowing [C.p_their()] [B.name] to bloody shreds!", "[src] goes off in your hand, blowing your [B.name] to bloody shreds!") + B.dismember() + + . = ..() + + update_mob() + var/flashbang_turf = get_turf(src) + if(!flashbang_turf) + return + do_sparks(rand(5, 9), FALSE, src) + playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 50, TRUE, 8, 0.9) + new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 2, 1) + for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) + pop(get_turf(M), M) + qdel(src) + +/obj/item/grenade/stingbang/proc/pop(turf/T , mob/living/M) + if(M.stat == DEAD) //They're dead! + return + M.show_message("POP", MSG_AUDIBLE) + var/distance = max(0,get_dist(get_turf(src),T)) +//Flash + if(M.flash_act(affect_silicon = 1)) + M.Paralyze(max(10/max(1,distance), 5)) + M.Knockdown(max(100/max(1,distance), 60)) + +//Bang + if(!distance || loc == M || loc == M.loc) + M.Paralyze(20) + M.Knockdown(200) + M.soundbang_act(1, 200, 10, 15) + if(M.apply_damages(10, 10)) + to_chat(M, "The blast from \the [src] bruises and burns you!") + + // only checking if they're on top of the tile, cause being one tile over will be its own punishment + +// Grenade that releases more shrapnel the more times you use it in hand between priming and detonation (sorta like the 9bang from MW3), for admin goofs +/obj/item/grenade/primer + name = "rotfrag grenade" + desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards." + icon_state = "timeg" + inhand_icon_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/rots_per_mag = 3 /// how many times we need to "rotate" the charge in hand per extra tile of magnitude + shrapnel_type = /obj/projectile/bullet/shrapnel + var/rots = 1 /// how many times we've "rotated" the charge + +/obj/item/grenade/primer/attack_self(mob/user) + . = ..() + if(active) + user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE) + rots++ + user.changeNext_move(CLICK_CD_RAPID) + +/obj/item/grenade/primer/prime(mob/living/lanced_by) + shrapnel_radius = round(rots / rots_per_mag) + . = ..() + qdel(src) + +/obj/item/grenade/primer/stingbang + name = "rotsting" + desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases stingballs." + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + rots_per_mag = 2 + shrapnel_type = /obj/projectile/bullet/pellet/stingball diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm index c75aafeb29e..80b820d6672 100644 --- a/code/game/objects/items/grenades/ghettobomb.dm +++ b/code/game/objects/items/grenades/ghettobomb.dm @@ -1,77 +1,77 @@ -//improvised explosives// - -/obj/item/grenade/iedcasing - name = "improvised firebomb" - desc = "A weak, improvised incendiary device." - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/grenade.dmi' - icon_state = "improvised_grenade" - inhand_icon_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - throw_speed = 3 - throw_range = 7 - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - active = 0 - det_time = 50 - display_timer = 0 - var/check_parts = FALSE - var/range = 3 - var/list/times - -/obj/item/grenade/iedcasing/Initialize() - . = ..() - add_overlay("improvised_grenade_filled") - add_overlay("improvised_grenade_wired") - times = list("5" = 10, "-1" = 20, "[rand(30,80)]" = 50, "[rand(65,180)]" = 20)// "Premature, Dud, Short Fuse, Long Fuse"=[weighting value] - det_time = text2num(pickweight(times)) - if(det_time < 0) //checking for 'duds' - range = 1 - det_time = rand(30,80) - else - range = pick(2,2,2,3,3,3,4) - if(check_parts) //since construction code calls this itself, no need to always call it. This does have the downside that adminspawned ones can potentially not have cans if they don't use the /spawned subtype. - CheckParts() - -/obj/item/grenade/iedcasing/spawned - check_parts = TRUE - -/obj/item/grenade/iedcasing/spawned/Initialize() - new /obj/item/reagent_containers/food/drinks/soda_cans/random(src) - return ..() - -/obj/item/grenade/iedcasing/CheckParts(list/parts_list) - ..() - var/obj/item/reagent_containers/food/drinks/soda_cans/can = locate() in contents - if(!can) - stack_trace("[src] generated without a soda can!") //this shouldn't happen. - qdel(src) - return - can.pixel_x = 0 //Reset the sprite's position to make it consistent with the rest of the IED - can.pixel_y = 0 - var/mutable_appearance/can_underlay = new(can) - can_underlay.layer = FLOAT_LAYER - can_underlay.plane = FLOAT_PLANE - underlays += can_underlay - - -/obj/item/grenade/iedcasing/attack_self(mob/user) // - if(!active) - if(!botch_check(user)) - to_chat(user, "You light the [name]!") - cut_overlay("improvised_grenade_filled") - preprime(user, null, FALSE) - -/obj/item/grenade/iedcasing/prime(mob/living/lanced_by) //Blowing that can up - . = ..() - update_mob() - explosion(src.loc,-1,-1,2, flame_range = 4) // small explosion, plus a very large fireball. - qdel(src) - -/obj/item/grenade/iedcasing/change_det_time() - return //always be random. - -/obj/item/grenade/iedcasing/examine(mob/user) - . = ..() - . += "You can't tell when it will explode!" +//improvised explosives// + +/obj/item/grenade/iedcasing + name = "improvised firebomb" + desc = "A weak, improvised incendiary device." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/grenade.dmi' + icon_state = "improvised_grenade" + inhand_icon_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + throw_speed = 3 + throw_range = 7 + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + active = 0 + det_time = 50 + display_timer = 0 + var/check_parts = FALSE + var/range = 3 + var/list/times + +/obj/item/grenade/iedcasing/Initialize() + . = ..() + add_overlay("improvised_grenade_filled") + add_overlay("improvised_grenade_wired") + times = list("5" = 10, "-1" = 20, "[rand(30,80)]" = 50, "[rand(65,180)]" = 20)// "Premature, Dud, Short Fuse, Long Fuse"=[weighting value] + det_time = text2num(pickweight(times)) + if(det_time < 0) //checking for 'duds' + range = 1 + det_time = rand(30,80) + else + range = pick(2,2,2,3,3,3,4) + if(check_parts) //since construction code calls this itself, no need to always call it. This does have the downside that adminspawned ones can potentially not have cans if they don't use the /spawned subtype. + CheckParts() + +/obj/item/grenade/iedcasing/spawned + check_parts = TRUE + +/obj/item/grenade/iedcasing/spawned/Initialize() + new /obj/item/reagent_containers/food/drinks/soda_cans/random(src) + return ..() + +/obj/item/grenade/iedcasing/CheckParts(list/parts_list) + ..() + var/obj/item/reagent_containers/food/drinks/soda_cans/can = locate() in contents + if(!can) + stack_trace("[src] generated without a soda can!") //this shouldn't happen. + qdel(src) + return + can.pixel_x = 0 //Reset the sprite's position to make it consistent with the rest of the IED + can.pixel_y = 0 + var/mutable_appearance/can_underlay = new(can) + can_underlay.layer = FLOAT_LAYER + can_underlay.plane = FLOAT_PLANE + underlays += can_underlay + + +/obj/item/grenade/iedcasing/attack_self(mob/user) // + if(!active) + if(!botch_check(user)) + to_chat(user, "You light the [name]!") + cut_overlay("improvised_grenade_filled") + preprime(user, null, FALSE) + +/obj/item/grenade/iedcasing/prime(mob/living/lanced_by) //Blowing that can up + . = ..() + update_mob() + explosion(src.loc,-1,-1,2, flame_range = 4) // small explosion, plus a very large fireball. + qdel(src) + +/obj/item/grenade/iedcasing/change_det_time() + return //always be random. + +/obj/item/grenade/iedcasing/examine(mob/user) + . = ..() + . += "You can't tell when it will explode!" diff --git a/code/game/objects/items/grenades/grenade.dm b/code/game/objects/items/grenades/grenade.dm index 7b30ddb6f9d..3f2405d138c 100644 --- a/code/game/objects/items/grenades/grenade.dm +++ b/code/game/objects/items/grenades/grenade.dm @@ -1,172 +1,172 @@ -/obj/item/grenade - name = "grenade" - desc = "It has an adjustable timer." - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/grenade.dmi' - icon_state = "grenade" - inhand_icon_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - throw_speed = 3 - throw_range = 7 - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - resistance_flags = FLAMMABLE - max_integrity = 40 - var/active = 0 - var/det_time = 50 - var/display_timer = 1 - var/clumsy_check = GRENADE_CLUMSY_FUMBLE - var/sticky = FALSE - // I moved the explosion vars and behavior to base grenades because we want all grenades to call [/obj/item/grenade/proc/prime] so we can send COMSIG_GRENADE_PRIME - ///how big of a devastation explosion radius on prime - var/ex_dev = 0 - ///how big of a heavy explosion radius on prime - var/ex_heavy = 0 - ///how big of a light explosion radius on prime - var/ex_light = 0 - ///how big of a flame explosion radius on prime - var/ex_flame = 0 - - // dealing with creating a [/datum/component/pellet_cloud] on prime - /// if set, will spew out projectiles of this type - var/shrapnel_type - /// the higher this number, the more projectiles are created as shrapnel - var/shrapnel_radius - var/shrapnel_initialized - -/obj/item/grenade/suicide_act(mob/living/carbon/user) - user.visible_message("[user] primes [src], then eats it! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) - preprime(user, det_time) - user.transferItemToLoc(src, user, TRUE)//>eat a grenade set to 5 seconds >rush captain - sleep(det_time)//so you dont die instantly - return BRUTELOSS - -/obj/item/grenade/deconstruct(disassembled = TRUE) - if(!disassembled) - prime() - if(!QDELETED(src)) - qdel(src) - -/obj/item/grenade/proc/botch_check(mob/living/carbon/human/user) - var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY) - if(clumsy && (clumsy_check == GRENADE_CLUMSY_FUMBLE)) - if(prob(50)) - to_chat(user, "Huh? How does this thing work?") - preprime(user, 5, FALSE) - return TRUE - else if(!clumsy && (clumsy_check == GRENADE_NONCLUMSY_FUMBLE)) - to_chat(user, "You pull the pin on [src]. Attached to it is a pink ribbon that says, \"HONK\"") - preprime(user, 5, FALSE) - return TRUE - else if(sticky && prob(50)) // to add risk to sticky tape grenade cheese, no return cause we still prime as normal after - to_chat(user, "What the... [src] is stuck to your hand!") - ADD_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) - -/obj/item/grenade/examine(mob/user) - . = ..() - if(display_timer) - if(det_time > 0) - . += "The timer is set to [DisplayTimeText(det_time)]." - else - . += "\The [src] is set for instant detonation." - - -/obj/item/grenade/attack_self(mob/user) - if(HAS_TRAIT(src, TRAIT_NODROP)) - to_chat(user, "You try prying [src] off your hand...") - if(do_after(user, 70, target=src)) - to_chat(user, "You manage to remove [src] from your hand.") - REMOVE_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) - - return - - if(!active) - if(!botch_check(user)) // if they botch the prime, it'll be handled in botch_check - preprime(user) - -/obj/item/grenade/proc/log_grenade(mob/user, turf/T) - log_bomber(user, "has primed a", src, "for detonation") - -/obj/item/grenade/proc/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) - var/turf/T = get_turf(src) - log_grenade(user, T) //Inbuilt admin procs already handle null users - if(user) - add_fingerprint(user) - if(msg) - to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") - if(shrapnel_type && shrapnel_radius) - shrapnel_initialized = TRUE - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) - playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) - active = TRUE - icon_state = initial(icon_state) + "_active" - SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/proc/prime(mob/living/lanced_by) - if(shrapnel_type && shrapnel_radius && !shrapnel_initialized) // add a second check for adding the component in case whatever triggered the grenade went straight to prime (badminnery for example) - shrapnel_initialized = TRUE - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) - - SEND_SIGNAL(src, COMSIG_GRENADE_PRIME, lanced_by) - if(ex_dev || ex_heavy || ex_light || ex_flame) - explosion(loc, ex_dev, ex_heavy, ex_light, flame_range = ex_flame) - -/obj/item/grenade/proc/update_mob() - if(ismob(loc)) - var/mob/M = loc - M.dropItemToGround(src) - -/obj/item/grenade/attackby(obj/item/W, mob/user, params) - if(!active) - if(W.tool_behaviour == TOOL_MULTITOOL) - var/newtime = text2num(stripped_input(user, "Please enter a new detonation time", name)) - if (newtime != null && user.canUseTopic(src, BE_CLOSE)) - if(change_det_time(newtime)) - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - if (round(newtime * 10) != det_time) - to_chat(user, "The new value is out of bounds. The lowest possible time is 3 seconds and highest is 5 seconds. Instant detonations are also possible.") - return - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(change_det_time()) - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - else - return ..() - -/obj/item/grenade/proc/change_det_time(time) //Time uses real time. - . = TRUE - if(time != null) - if(time < 3) - time = 3 - det_time = round(clamp(time * 10, 0, 50)) - else - var/previous_time = det_time - switch(det_time) - if (0) - det_time = 30 - if (30) - det_time = 50 - if (50) - det_time = 0 - if(det_time == previous_time) - det_time = 50 - -/obj/item/grenade/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - var/obj/projectile/P = hitby - if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) - owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") - var/turf/T = get_turf(src) - log_game("A projectile ([hitby]) detonated a grenade held by [key_name(owner)] at [COORD(T)]") - message_admins("A projectile ([hitby]) detonated a grenade held by [key_name_admin(owner)] at [ADMIN_COORDJMP(T)]") - prime() - return TRUE //It hit the grenade, not them - -/obj/item/grenade/afterattack(atom/target, mob/user) - . = ..() - if(active) - user.throw_item(target) +/obj/item/grenade + name = "grenade" + desc = "It has an adjustable timer." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/grenade.dmi' + icon_state = "grenade" + inhand_icon_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + throw_speed = 3 + throw_range = 7 + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + resistance_flags = FLAMMABLE + max_integrity = 40 + var/active = 0 + var/det_time = 50 + var/display_timer = 1 + var/clumsy_check = GRENADE_CLUMSY_FUMBLE + var/sticky = FALSE + // I moved the explosion vars and behavior to base grenades because we want all grenades to call [/obj/item/grenade/proc/prime] so we can send COMSIG_GRENADE_PRIME + ///how big of a devastation explosion radius on prime + var/ex_dev = 0 + ///how big of a heavy explosion radius on prime + var/ex_heavy = 0 + ///how big of a light explosion radius on prime + var/ex_light = 0 + ///how big of a flame explosion radius on prime + var/ex_flame = 0 + + // dealing with creating a [/datum/component/pellet_cloud] on prime + /// if set, will spew out projectiles of this type + var/shrapnel_type + /// the higher this number, the more projectiles are created as shrapnel + var/shrapnel_radius + var/shrapnel_initialized + +/obj/item/grenade/suicide_act(mob/living/carbon/user) + user.visible_message("[user] primes [src], then eats it! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) + preprime(user, det_time) + user.transferItemToLoc(src, user, TRUE)//>eat a grenade set to 5 seconds >rush captain + sleep(det_time)//so you dont die instantly + return BRUTELOSS + +/obj/item/grenade/deconstruct(disassembled = TRUE) + if(!disassembled) + prime() + if(!QDELETED(src)) + qdel(src) + +/obj/item/grenade/proc/botch_check(mob/living/carbon/human/user) + var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY) + if(clumsy && (clumsy_check == GRENADE_CLUMSY_FUMBLE)) + if(prob(50)) + to_chat(user, "Huh? How does this thing work?") + preprime(user, 5, FALSE) + return TRUE + else if(!clumsy && (clumsy_check == GRENADE_NONCLUMSY_FUMBLE)) + to_chat(user, "You pull the pin on [src]. Attached to it is a pink ribbon that says, \"HONK\"") + preprime(user, 5, FALSE) + return TRUE + else if(sticky && prob(50)) // to add risk to sticky tape grenade cheese, no return cause we still prime as normal after + to_chat(user, "What the... [src] is stuck to your hand!") + ADD_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) + +/obj/item/grenade/examine(mob/user) + . = ..() + if(display_timer) + if(det_time > 0) + . += "The timer is set to [DisplayTimeText(det_time)]." + else + . += "\The [src] is set for instant detonation." + + +/obj/item/grenade/attack_self(mob/user) + if(HAS_TRAIT(src, TRAIT_NODROP)) + to_chat(user, "You try prying [src] off your hand...") + if(do_after(user, 70, target=src)) + to_chat(user, "You manage to remove [src] from your hand.") + REMOVE_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) + + return + + if(!active) + if(!botch_check(user)) // if they botch the prime, it'll be handled in botch_check + preprime(user) + +/obj/item/grenade/proc/log_grenade(mob/user, turf/T) + log_bomber(user, "has primed a", src, "for detonation") + +/obj/item/grenade/proc/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) + var/turf/T = get_turf(src) + log_grenade(user, T) //Inbuilt admin procs already handle null users + if(user) + add_fingerprint(user) + if(msg) + to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") + if(shrapnel_type && shrapnel_radius) + shrapnel_initialized = TRUE + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) + playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) + active = TRUE + icon_state = initial(icon_state) + "_active" + SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/proc/prime(mob/living/lanced_by) + if(shrapnel_type && shrapnel_radius && !shrapnel_initialized) // add a second check for adding the component in case whatever triggered the grenade went straight to prime (badminnery for example) + shrapnel_initialized = TRUE + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) + + SEND_SIGNAL(src, COMSIG_GRENADE_PRIME, lanced_by) + if(ex_dev || ex_heavy || ex_light || ex_flame) + explosion(loc, ex_dev, ex_heavy, ex_light, flame_range = ex_flame) + +/obj/item/grenade/proc/update_mob() + if(ismob(loc)) + var/mob/M = loc + M.dropItemToGround(src) + +/obj/item/grenade/attackby(obj/item/W, mob/user, params) + if(!active) + if(W.tool_behaviour == TOOL_MULTITOOL) + var/newtime = text2num(stripped_input(user, "Please enter a new detonation time", name)) + if (newtime != null && user.canUseTopic(src, BE_CLOSE)) + if(change_det_time(newtime)) + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + if (round(newtime * 10) != det_time) + to_chat(user, "The new value is out of bounds. The lowest possible time is 3 seconds and highest is 5 seconds. Instant detonations are also possible.") + return + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(change_det_time()) + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + else + return ..() + +/obj/item/grenade/proc/change_det_time(time) //Time uses real time. + . = TRUE + if(time != null) + if(time < 3) + time = 3 + det_time = round(clamp(time * 10, 0, 50)) + else + var/previous_time = det_time + switch(det_time) + if (0) + det_time = 30 + if (30) + det_time = 50 + if (50) + det_time = 0 + if(det_time == previous_time) + det_time = 50 + +/obj/item/grenade/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + var/obj/projectile/P = hitby + if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) + owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") + var/turf/T = get_turf(src) + log_game("A projectile ([hitby]) detonated a grenade held by [key_name(owner)] at [COORD(T)]") + message_admins("A projectile ([hitby]) detonated a grenade held by [key_name_admin(owner)] at [ADMIN_COORDJMP(T)]") + prime() + return TRUE //It hit the grenade, not them + +/obj/item/grenade/afterattack(atom/target, mob/user) + . = ..() + if(active) + user.throw_item(target) diff --git a/code/game/objects/items/grenades/smokebomb.dm b/code/game/objects/items/grenades/smokebomb.dm index 43ece580eb5..b26728bb1ef 100644 --- a/code/game/objects/items/grenades/smokebomb.dm +++ b/code/game/objects/items/grenades/smokebomb.dm @@ -1,33 +1,33 @@ -/** - *This is smoke bomb, mezum koman. It is a grenade subtype. All craftmanship is of the highest quality. - *It menaces with spikes of iron. On it is a depiction of an assistant. - *The assistant is bleeding. The assistant has a painful expression. The assistant is dead. - */ -/obj/item/grenade/smokebomb - name = "smoke grenade" - desc = "Real bruh moment if you ever see this. Probably tell a c*der or something." - icon = 'icons/obj/grenade.dmi' - icon_state = "smokewhite" - inhand_icon_state = "smoke" - slot_flags = ITEM_SLOT_BELT - ///It's extremely important to keep this list up to date. It helps to generate the insightful description of the smokebomb - var/static/list/bruh_moment = list("Dank", "Hip", "Lit", "Based", "Robust", "Bruh", "Nyagger") - -///Here we generate the extremely insightful description. -/obj/item/grenade/smokebomb/Initialize() - . = ..() - desc = "The word '[pick(bruh_moment)]' is scribbled on it in crayon." - -///Here we generate some smoke and also damage blobs??? for some reason. Honestly not sure why we do that. -/obj/item/grenade/smokebomb/prime(mob/living/lanced_by) - . = ..() - update_mob() - playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3) - var/datum/effect_system/smoke_spread/bad/smoke = new - smoke.set_up(4, src) - smoke.start() - qdel(smoke) //And deleted again. Sad really. - for(var/obj/structure/blob/B in view(8,src)) - var/damage = round(30/(get_dist(B,src)+1)) - B.take_damage(damage, BURN, "melee", 0) - qdel(src) +/** + *This is smoke bomb, mezum koman. It is a grenade subtype. All craftmanship is of the highest quality. + *It menaces with spikes of iron. On it is a depiction of an assistant. + *The assistant is bleeding. The assistant has a painful expression. The assistant is dead. + */ +/obj/item/grenade/smokebomb + name = "smoke grenade" + desc = "Real bruh moment if you ever see this. Probably tell a c*der or something." + icon = 'icons/obj/grenade.dmi' + icon_state = "smokewhite" + inhand_icon_state = "smoke" + slot_flags = ITEM_SLOT_BELT + ///It's extremely important to keep this list up to date. It helps to generate the insightful description of the smokebomb + var/static/list/bruh_moment = list("Dank", "Hip", "Lit", "Based", "Robust", "Bruh", "Nyagger") + +///Here we generate the extremely insightful description. +/obj/item/grenade/smokebomb/Initialize() + . = ..() + desc = "The word '[pick(bruh_moment)]' is scribbled on it in crayon." + +///Here we generate some smoke and also damage blobs??? for some reason. Honestly not sure why we do that. +/obj/item/grenade/smokebomb/prime(mob/living/lanced_by) + . = ..() + update_mob() + playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3) + var/datum/effect_system/smoke_spread/bad/smoke = new + smoke.set_up(4, src) + smoke.start() + qdel(smoke) //And deleted again. Sad really. + for(var/obj/structure/blob/B in view(8,src)) + var/damage = round(30/(get_dist(B,src)+1)) + B.take_damage(damage, BURN, "melee", 0) + qdel(src) diff --git a/code/game/objects/items/grenades/spawnergrenade.dm b/code/game/objects/items/grenades/spawnergrenade.dm index 2958f7a5dc3..8102442b9b7 100644 --- a/code/game/objects/items/grenades/spawnergrenade.dm +++ b/code/game/objects/items/grenades/spawnergrenade.dm @@ -1,64 +1,64 @@ -/obj/item/grenade/spawnergrenade - desc = "It will unleash an unspecified anomaly in the surrounding vicinity." - name = "delivery grenade" - icon = 'icons/obj/grenade.dmi' - icon_state = "delivery" - inhand_icon_state = "flashbang" - var/spawner_type = null // must be an object path - var/deliveryamt = 1 // amount of type to deliver - -/obj/item/grenade/spawnergrenade/prime(mob/living/lanced_by) // Prime now just handles the two loops that query for people in lockers and people who can see it. - . = ..() - update_mob() - if(spawner_type && deliveryamt) - // Make a quick flash - var/turf/T = get_turf(src) - playsound(T, 'sound/effects/phasein.ogg', 100, TRUE) - for(var/mob/living/carbon/C in viewers(T, null)) - C.flash_act() - - // Spawn some hostile syndicate critters and spread them out - var/list/spawned = spawn_and_random_walk(spawner_type, T, deliveryamt, walk_chance=50, admin_spawn=((flags_1 & ADMIN_SPAWNED_1) ? TRUE : FALSE)) - afterspawn(spawned) - - qdel(src) - -/obj/item/grenade/spawnergrenade/proc/afterspawn(list/mob/spawned) - return - -/obj/item/grenade/spawnergrenade/manhacks - name = "viscerator delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/viscerator - deliveryamt = 10 - -/obj/item/grenade/spawnergrenade/spesscarp - name = "carp delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/carp - deliveryamt = 5 - -/obj/item/grenade/spawnergrenade/syndiesoap - name = "Mister Scrubby" - spawner_type = /obj/item/soap/syndie - -/obj/item/grenade/spawnergrenade/buzzkill - name = "Buzzkill grenade" - desc = "The label reads: \"WARNING: DEVICE WILL RELEASE LIVE SPECIMENS UPON ACTIVATION. SEAL SUIT BEFORE USE.\" It is warm to the touch and vibrates faintly." - icon_state = "holy_grenade" - spawner_type = /mob/living/simple_animal/hostile/poison/bees/toxin - deliveryamt = 10 - -/obj/item/grenade/spawnergrenade/clown - name = "C.L.U.W.N.E." - desc = "A sleek device often given to clowns on their 10th birthdays for protection. You can hear faint scratching coming from within." - icon_state = "clown_ball" - inhand_icon_state = "clown_ball" - spawner_type = list(/mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/blob, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) - deliveryamt = 1 - -/obj/item/grenade/spawnergrenade/clown_broken - name = "stuffed C.L.U.W.N.E." - desc = "A sleek device often given to clowns on their 10th birthdays for protection. While a typical C.L.U.W.N.E only holds one creature, sometimes foolish young clowns try to cram more in, often to disasterous effect." - icon_state = "clown_broken" - inhand_icon_state = "clown_broken" - spawner_type = /mob/living/simple_animal/hostile/retaliate/clown/mutant - deliveryamt = 5 +/obj/item/grenade/spawnergrenade + desc = "It will unleash an unspecified anomaly in the surrounding vicinity." + name = "delivery grenade" + icon = 'icons/obj/grenade.dmi' + icon_state = "delivery" + inhand_icon_state = "flashbang" + var/spawner_type = null // must be an object path + var/deliveryamt = 1 // amount of type to deliver + +/obj/item/grenade/spawnergrenade/prime(mob/living/lanced_by) // Prime now just handles the two loops that query for people in lockers and people who can see it. + . = ..() + update_mob() + if(spawner_type && deliveryamt) + // Make a quick flash + var/turf/T = get_turf(src) + playsound(T, 'sound/effects/phasein.ogg', 100, TRUE) + for(var/mob/living/carbon/C in viewers(T, null)) + C.flash_act() + + // Spawn some hostile syndicate critters and spread them out + var/list/spawned = spawn_and_random_walk(spawner_type, T, deliveryamt, walk_chance=50, admin_spawn=((flags_1 & ADMIN_SPAWNED_1) ? TRUE : FALSE)) + afterspawn(spawned) + + qdel(src) + +/obj/item/grenade/spawnergrenade/proc/afterspawn(list/mob/spawned) + return + +/obj/item/grenade/spawnergrenade/manhacks + name = "viscerator delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/viscerator + deliveryamt = 10 + +/obj/item/grenade/spawnergrenade/spesscarp + name = "carp delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/carp + deliveryamt = 5 + +/obj/item/grenade/spawnergrenade/syndiesoap + name = "Mister Scrubby" + spawner_type = /obj/item/soap/syndie + +/obj/item/grenade/spawnergrenade/buzzkill + name = "Buzzkill grenade" + desc = "The label reads: \"WARNING: DEVICE WILL RELEASE LIVE SPECIMENS UPON ACTIVATION. SEAL SUIT BEFORE USE.\" It is warm to the touch and vibrates faintly." + icon_state = "holy_grenade" + spawner_type = /mob/living/simple_animal/hostile/poison/bees/toxin + deliveryamt = 10 + +/obj/item/grenade/spawnergrenade/clown + name = "C.L.U.W.N.E." + desc = "A sleek device often given to clowns on their 10th birthdays for protection. You can hear faint scratching coming from within." + icon_state = "clown_ball" + inhand_icon_state = "clown_ball" + spawner_type = list(/mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/blob, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) + deliveryamt = 1 + +/obj/item/grenade/spawnergrenade/clown_broken + name = "stuffed C.L.U.W.N.E." + desc = "A sleek device often given to clowns on their 10th birthdays for protection. While a typical C.L.U.W.N.E only holds one creature, sometimes foolish young clowns try to cram more in, often to disasterous effect." + icon_state = "clown_broken" + inhand_icon_state = "clown_broken" + spawner_type = /mob/living/simple_animal/hostile/retaliate/clown/mutant + deliveryamt = 5 diff --git a/code/game/objects/items/grenades/syndieminibomb.dm b/code/game/objects/items/grenades/syndieminibomb.dm index e978d92a5ce..5d49a3bdb15 100644 --- a/code/game/objects/items/grenades/syndieminibomb.dm +++ b/code/game/objects/items/grenades/syndieminibomb.dm @@ -1,68 +1,68 @@ -/obj/item/grenade/syndieminibomb - desc = "A syndicate manufactured explosive used to sow destruction and chaos." - name = "syndicate minibomb" - icon = 'icons/obj/grenade.dmi' - icon_state = "syndicate" - inhand_icon_state = "flashbang" - ex_dev = 1 - ex_heavy = 2 - ex_light = 4 - ex_flame = 2 - -/obj/item/grenade/syndieminibomb/prime(mob/living/lanced_by) - . = ..() - update_mob() - qdel(src) - -/obj/item/grenade/syndieminibomb/concussion - name = "HE Grenade" - desc = "A compact shrapnel grenade meant to devastate nearby organisms and cause some damage in the process. Pull pin and throw opposite direction." - icon_state = "concussion" - ex_heavy = 2 - ex_light = 3 - ex_flame = 3 - -/obj/item/grenade/frag - name = "frag grenade" - desc = "An anti-personnel fragmentation grenade, this weapon excels at killing soft targets by shredding them with metal shrapnel." - icon_state = "frag" - shrapnel_type = /obj/projectile/bullet/shrapnel - shrapnel_radius = 4 - ex_heavy = 1 - ex_light = 3 - ex_flame = 4 - -/obj/item/grenade/frag/mega - name = "FRAG grenade" - desc = "An anti-everything fragmentation grenade, this weapon excels at killing anything any everything by shredding them with metal shrapnel." - shrapnel_type = /obj/projectile/bullet/shrapnel/mega - shrapnel_radius = 12 - -/obj/item/grenade/frag/prime(mob/living/lanced_by) - . = ..() - update_mob() - qdel(src) - -/obj/item/grenade/gluon - desc = "An advanced grenade that releases a harmful stream of gluons inducing radiation in those nearby. These gluon streams will also make victims feel exhausted, and induce shivering. This extreme coldness will also likely wet any nearby floors." - name = "gluon frag grenade" - icon = 'icons/obj/grenade.dmi' - icon_state = "bluefrag" - inhand_icon_state = "flashbang" - var/freeze_range = 4 - var/rad_damage = 350 - var/stamina_damage = 30 - -/obj/item/grenade/gluon/prime(mob/living/lanced_by) - . = ..() - update_mob() - playsound(loc, 'sound/effects/empulse.ogg', 50, TRUE) - radiation_pulse(src, rad_damage) - for(var/turf/T in view(freeze_range,loc)) - if(isfloorturf(T)) - var/turf/open/floor/F = T - F.MakeSlippery(TURF_WET_PERMAFROST, 6 MINUTES) - for(var/mob/living/carbon/L in T) - L.adjustStaminaLoss(stamina_damage) - L.adjust_bodytemperature(-230) - qdel(src) +/obj/item/grenade/syndieminibomb + desc = "A syndicate manufactured explosive used to sow destruction and chaos." + name = "syndicate minibomb" + icon = 'icons/obj/grenade.dmi' + icon_state = "syndicate" + inhand_icon_state = "flashbang" + ex_dev = 1 + ex_heavy = 2 + ex_light = 4 + ex_flame = 2 + +/obj/item/grenade/syndieminibomb/prime(mob/living/lanced_by) + . = ..() + update_mob() + qdel(src) + +/obj/item/grenade/syndieminibomb/concussion + name = "HE Grenade" + desc = "A compact shrapnel grenade meant to devastate nearby organisms and cause some damage in the process. Pull pin and throw opposite direction." + icon_state = "concussion" + ex_heavy = 2 + ex_light = 3 + ex_flame = 3 + +/obj/item/grenade/frag + name = "frag grenade" + desc = "An anti-personnel fragmentation grenade, this weapon excels at killing soft targets by shredding them with metal shrapnel." + icon_state = "frag" + shrapnel_type = /obj/projectile/bullet/shrapnel + shrapnel_radius = 4 + ex_heavy = 1 + ex_light = 3 + ex_flame = 4 + +/obj/item/grenade/frag/mega + name = "FRAG grenade" + desc = "An anti-everything fragmentation grenade, this weapon excels at killing anything any everything by shredding them with metal shrapnel." + shrapnel_type = /obj/projectile/bullet/shrapnel/mega + shrapnel_radius = 12 + +/obj/item/grenade/frag/prime(mob/living/lanced_by) + . = ..() + update_mob() + qdel(src) + +/obj/item/grenade/gluon + desc = "An advanced grenade that releases a harmful stream of gluons inducing radiation in those nearby. These gluon streams will also make victims feel exhausted, and induce shivering. This extreme coldness will also likely wet any nearby floors." + name = "gluon frag grenade" + icon = 'icons/obj/grenade.dmi' + icon_state = "bluefrag" + inhand_icon_state = "flashbang" + var/freeze_range = 4 + var/rad_damage = 350 + var/stamina_damage = 30 + +/obj/item/grenade/gluon/prime(mob/living/lanced_by) + . = ..() + update_mob() + playsound(loc, 'sound/effects/empulse.ogg', 50, TRUE) + radiation_pulse(src, rad_damage) + for(var/turf/T in view(freeze_range,loc)) + if(isfloorturf(T)) + var/turf/open/floor/F = T + F.MakeSlippery(TURF_WET_PERMAFROST, 6 MINUTES) + for(var/mob/living/carbon/L in T) + L.adjustStaminaLoss(stamina_damage) + L.adjust_bodytemperature(-230) + qdel(src) diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm index 66ee7692882..8c13104e486 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -1,395 +1,395 @@ -/obj/item/restraints - breakouttime = 600 - -/obj/item/restraints/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return(OXYLOSS) - -/obj/item/restraints/Destroy() - if(iscarbon(loc)) - var/mob/living/carbon/M = loc - if(M.handcuffed == src) - M.handcuffed = null - M.update_handcuffed() - if(M.buckled && M.buckled.buckle_requires_restraints) - M.buckled.unbuckle_mob(M) - if(M.legcuffed == src) - M.legcuffed = null - M.update_inv_legcuffed() - return ..() - -//Handcuffs - -/obj/item/restraints/handcuffs - name = "handcuffs" - desc = "Use this to keep prisoners in line." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "handcuff" - worn_icon_state = "handcuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=500) - breakouttime = 1 MINUTES - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - var/cuffsound = 'sound/weapons/handcuffs.ogg' - var/trashtype = null //for disposable cuffs - -/obj/item/restraints/handcuffs/attack(mob/living/carbon/C, mob/living/user) - if(!istype(C)) - return - - if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))) - to_chat(user, "Uh... how do those things work?!") - apply_cuffs(user,user) - return - - // chance of monkey retaliation - if(ismonkey(C) && prob(MONKEY_CUFF_RETALIATION_PROB)) - var/mob/living/carbon/monkey/M - M = C - M.retaliate(user) - - if(!C.handcuffed) - if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()) - C.visible_message("[user] is trying to put [src.name] on [C]!", \ - "[user] is trying to put [src.name] on you!") - - playsound(loc, cuffsound, 30, TRUE, -2) - if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())) - if(iscyborg(user)) - apply_cuffs(C, user, TRUE) - else - apply_cuffs(C, user) - C.visible_message("[user] handcuffs [C].", \ - "[user] handcuffs you.") - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - - log_combat(user, C, "handcuffed") - else - to_chat(user, "You fail to handcuff [C]!") - else - to_chat(user, "[C] doesn't have two hands...") - -/obj/item/restraints/handcuffs/proc/apply_cuffs(mob/living/carbon/target, mob/user, dispense = 0) - if(target.handcuffed) - return - - if(!user.temporarilyRemoveItemFromInventory(src) && !dispense) - return - - var/obj/item/restraints/handcuffs/cuffs = src - if(trashtype) - cuffs = new trashtype() - else if(dispense) - cuffs = new type() - - cuffs.forceMove(target) - target.handcuffed = cuffs - - target.update_handcuffed() - if(trashtype && !dispense) - qdel(src) - return - -/obj/item/restraints/handcuffs/cable/sinew - name = "sinew restraints" - desc = "A pair of restraints fashioned from long strands of flesh." - icon = 'icons/obj/mining.dmi' - icon_state = "sinewcuff" - inhand_icon_state = "sinewcuff" - custom_materials = null - color = null - -/obj/item/restraints/handcuffs/cable - name = "cable restraints" - desc = "Looks like some cables tied together. Could be used to tie something up." - icon_state = "cuff" - inhand_icon_state = "coil" - color = "#ff0000" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - custom_materials = list(/datum/material/iron=150, /datum/material/glass=75) - breakouttime = 30 SECONDS - cuffsound = 'sound/weapons/cablecuff.ogg' - -/obj/item/restraints/handcuffs/cable/red - color = "#ff0000" - -/obj/item/restraints/handcuffs/cable/yellow - color = "#ffff00" - -/obj/item/restraints/handcuffs/cable/blue - color = "#1919c8" - -/obj/item/restraints/handcuffs/cable/green - color = "#00aa00" - -/obj/item/restraints/handcuffs/cable/pink - color = "#ff3ccd" - -/obj/item/restraints/handcuffs/cable/orange - color = "#ff8000" - -/obj/item/restraints/handcuffs/cable/cyan - color = "#00ffff" - -/obj/item/restraints/handcuffs/cable/white - color = null - -/obj/item/restraints/handcuffs/alien - icon_state = "handcuffAlien" - -/obj/item/restraints/handcuffs/fake - name = "fake handcuffs" - desc = "Fake handcuffs meant for gag purposes." - breakouttime = 1 SECONDS - -/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if (R.use(1)) - var/obj/item/wirerod/W = new /obj/item/wirerod - remove_item_from_storage(user) - user.put_in_hands(W) - to_chat(user, "You wrap [src] around the top of [I].") - qdel(src) - else - to_chat(user, "You need one rod to make a wired rod!") - return - else if(istype(I, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = I - if(M.get_amount() < 6) - to_chat(user, "You need at least six metal sheets to make good enough weights!") - return - to_chat(user, "You begin to apply [I] to [src]...") - if(do_after(user, 35, target = src)) - if(M.get_amount() < 6 || !M) - return - var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola - M.use(6) - user.put_in_hands(S) - to_chat(user, "You make some weights out of [I] and tie them to [src].") - remove_item_from_storage(user) - qdel(src) - else - return ..() - -/obj/item/restraints/handcuffs/cable/zipties - name = "zipties" - desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." - icon_state = "cuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - custom_materials = null - breakouttime = 45 SECONDS - trashtype = /obj/item/restraints/handcuffs/cable/zipties/used - color = null - -/obj/item/restraints/handcuffs/cable/zipties/used - desc = "A pair of broken zipties." - icon_state = "cuff_used" - inhand_icon_state = "cuff" - -/obj/item/restraints/handcuffs/cable/zipties/used/attack() - return - -//Legcuffs - -/obj/item/restraints/legcuffs - name = "leg cuffs" - desc = "Use this to keep prisoners in line." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "handcuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - flags_1 = CONDUCT_1 - throwforce = 0 - w_class = WEIGHT_CLASS_NORMAL - slowdown = 7 - breakouttime = 30 SECONDS - -/obj/item/restraints/legcuffs/beartrap - name = "bear trap" - throw_speed = 1 - throw_range = 1 - icon_state = "beartrap" - desc = "A trap used to catch bears and other legged creatures." - var/armed = 0 - var/trap_damage = 20 - -/obj/item/restraints/legcuffs/beartrap/Initialize() - . = ..() - update_icon() - -/obj/item/restraints/legcuffs/beartrap/update_icon_state() - icon_state = "[initial(icon_state)][armed]" - -/obj/item/restraints/legcuffs/beartrap/suicide_act(mob/user) - user.visible_message("[user] is sticking [user.p_their()] head in the [src.name]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/restraints/legcuffs/beartrap/attack_self(mob/user) - ..() - if(ishuman(user) && !user.stat && !user.restrained()) - armed = !armed - update_icon() - to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") - -/obj/item/restraints/legcuffs/beartrap/proc/close_trap() - armed = FALSE - update_icon() - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - -/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) - if(armed && isturf(loc)) - if(isliving(AM)) - var/mob/living/L = AM - var/snap = TRUE - if(istype(L.buckled, /obj/vehicle)) - var/obj/vehicle/ridden_vehicle = L.buckled - if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. - close_trap() - ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") - return ..() - - if(L.movement_type & (FLYING|FLOATING)) //don't close the trap if they're flying/floating over it. - snap = FALSE - - var/def_zone = BODY_ZONE_CHEST - if(snap && iscarbon(L)) - var/mob/living/carbon/C = L - if(C.mobility_flags & MOBILITY_STAND) - def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. - C.legcuffed = src - forceMove(C) - C.update_equipment_speed_mods() - C.update_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - else if(snap && isanimal(L)) - var/mob/living/simple_animal/SA = L - if(SA.mob_size <= MOB_SIZE_TINY) //don't close the trap if they're as small as a mouse. - snap = FALSE - if(snap) - close_trap() - L.visible_message("[L] triggers \the [src].", \ - "You trigger \the [src]!") - L.apply_damage(trap_damage, BRUTE, def_zone) - ..() - -/obj/item/restraints/legcuffs/beartrap/energy - name = "energy snare" - armed = 1 - icon_state = "e_snare" - trap_damage = 0 - breakouttime = 30 - item_flags = DROPDEL - flags_1 = NONE - -/obj/item/restraints/legcuffs/beartrap/energy/Initialize() - . = ..() - addtimer(CALLBACK(src, .proc/dissipate), 100) - -/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate() - if(!ismob(loc)) - do_sparks(1, TRUE, src) - qdel(src) - -/obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user) - Crossed(user) //honk - return ..() - -/obj/item/restraints/legcuffs/beartrap/energy/cyborg - breakouttime = 20 // Cyborgs shouldn't have a strong restraint - -/obj/item/restraints/legcuffs/bola - name = "bola" - desc = "A restraining device designed to be thrown at the target. Upon connecting with said target, it will wrap around their legs, making it difficult for them to move quickly." - icon_state = "bola" - inhand_icon_state = "bola" - lefthand_file = 'icons/mob/inhands/weapons/thrown_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/thrown_righthand.dmi' - breakouttime = 35//easy to apply, easy to break out of - gender = NEUTER - var/knockdown = 0 - -/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, gentle = FALSE, quickstart = TRUE) - if(!..()) - return - playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, TRUE) - -/obj/item/restraints/legcuffs/bola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(..() || !iscarbon(hit_atom))//if it gets caught or the target can't be cuffed, - return//abort - ensnare(hit_atom) - -/** - * Attempts to legcuff someone with the bola - * - * Arguments: - * * C - the carbon that we will try to ensnare - */ -/obj/item/restraints/legcuffs/bola/proc/ensnare(mob/living/carbon/C) - if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) - visible_message("\The [src] ensnares [C]!") - C.legcuffed = src - forceMove(C) - C.update_equipment_speed_mods() - C.update_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - to_chat(C, "\The [src] ensnares you!") - C.Knockdown(knockdown) - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - -/obj/item/restraints/legcuffs/bola/tactical//traitor variant - name = "reinforced bola" - desc = "A strong bola, made with a long steel chain. It looks heavy, enough so that it could trip somebody." - icon_state = "bola_r" - inhand_icon_state = "bola_r" - breakouttime = 70 - knockdown = 35 - -/obj/item/restraints/legcuffs/bola/energy //For Security - name = "energy bola" - desc = "A specialized hard-light bola designed to ensnare fleeing criminals and aid in arrests." - icon_state = "ebola" - inhand_icon_state = "ebola" - hitsound = 'sound/weapons/taserhit.ogg' - w_class = WEIGHT_CLASS_SMALL - breakouttime = 60 - -/obj/item/restraints/legcuffs/bola/energy/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(iscarbon(hit_atom)) - var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(hit_atom)) - B.Crossed(hit_atom) - qdel(src) - ..() - -/obj/item/restraints/legcuffs/bola/gonbola - name = "gonbola" - desc = "Hey, if you have to be hugged in the legs by anything, it might as well be this little guy." - icon_state = "gonbola" - inhand_icon_state = "bola_r" - breakouttime = 300 - slowdown = 0 - var/datum/status_effect/gonbola_pacify/effectReference - -/obj/item/restraints/legcuffs/bola/gonbola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(iscarbon(hit_atom)) - var/mob/living/carbon/C = hit_atom - effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY) - -/obj/item/restraints/legcuffs/bola/gonbola/dropped(mob/user) - . = ..() - if(effectReference) - QDEL_NULL(effectReference) +/obj/item/restraints + breakouttime = 600 + +/obj/item/restraints/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return(OXYLOSS) + +/obj/item/restraints/Destroy() + if(iscarbon(loc)) + var/mob/living/carbon/M = loc + if(M.handcuffed == src) + M.handcuffed = null + M.update_handcuffed() + if(M.buckled && M.buckled.buckle_requires_restraints) + M.buckled.unbuckle_mob(M) + if(M.legcuffed == src) + M.legcuffed = null + M.update_inv_legcuffed() + return ..() + +//Handcuffs + +/obj/item/restraints/handcuffs + name = "handcuffs" + desc = "Use this to keep prisoners in line." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "handcuff" + worn_icon_state = "handcuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=500) + breakouttime = 1 MINUTES + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + var/cuffsound = 'sound/weapons/handcuffs.ogg' + var/trashtype = null //for disposable cuffs + +/obj/item/restraints/handcuffs/attack(mob/living/carbon/C, mob/living/user) + if(!istype(C)) + return + + if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))) + to_chat(user, "Uh... how do those things work?!") + apply_cuffs(user,user) + return + + // chance of monkey retaliation + if(ismonkey(C) && prob(MONKEY_CUFF_RETALIATION_PROB)) + var/mob/living/carbon/monkey/M + M = C + M.retaliate(user) + + if(!C.handcuffed) + if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()) + C.visible_message("[user] is trying to put [src.name] on [C]!", \ + "[user] is trying to put [src.name] on you!") + + playsound(loc, cuffsound, 30, TRUE, -2) + if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())) + if(iscyborg(user)) + apply_cuffs(C, user, TRUE) + else + apply_cuffs(C, user) + C.visible_message("[user] handcuffs [C].", \ + "[user] handcuffs you.") + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + + log_combat(user, C, "handcuffed") + else + to_chat(user, "You fail to handcuff [C]!") + else + to_chat(user, "[C] doesn't have two hands...") + +/obj/item/restraints/handcuffs/proc/apply_cuffs(mob/living/carbon/target, mob/user, dispense = 0) + if(target.handcuffed) + return + + if(!user.temporarilyRemoveItemFromInventory(src) && !dispense) + return + + var/obj/item/restraints/handcuffs/cuffs = src + if(trashtype) + cuffs = new trashtype() + else if(dispense) + cuffs = new type() + + cuffs.forceMove(target) + target.handcuffed = cuffs + + target.update_handcuffed() + if(trashtype && !dispense) + qdel(src) + return + +/obj/item/restraints/handcuffs/cable/sinew + name = "sinew restraints" + desc = "A pair of restraints fashioned from long strands of flesh." + icon = 'icons/obj/mining.dmi' + icon_state = "sinewcuff" + inhand_icon_state = "sinewcuff" + custom_materials = null + color = null + +/obj/item/restraints/handcuffs/cable + name = "cable restraints" + desc = "Looks like some cables tied together. Could be used to tie something up." + icon_state = "cuff" + inhand_icon_state = "coil" + color = "#ff0000" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + custom_materials = list(/datum/material/iron=150, /datum/material/glass=75) + breakouttime = 30 SECONDS + cuffsound = 'sound/weapons/cablecuff.ogg' + +/obj/item/restraints/handcuffs/cable/red + color = "#ff0000" + +/obj/item/restraints/handcuffs/cable/yellow + color = "#ffff00" + +/obj/item/restraints/handcuffs/cable/blue + color = "#1919c8" + +/obj/item/restraints/handcuffs/cable/green + color = "#00aa00" + +/obj/item/restraints/handcuffs/cable/pink + color = "#ff3ccd" + +/obj/item/restraints/handcuffs/cable/orange + color = "#ff8000" + +/obj/item/restraints/handcuffs/cable/cyan + color = "#00ffff" + +/obj/item/restraints/handcuffs/cable/white + color = null + +/obj/item/restraints/handcuffs/alien + icon_state = "handcuffAlien" + +/obj/item/restraints/handcuffs/fake + name = "fake handcuffs" + desc = "Fake handcuffs meant for gag purposes." + breakouttime = 1 SECONDS + +/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if (R.use(1)) + var/obj/item/wirerod/W = new /obj/item/wirerod + remove_item_from_storage(user) + user.put_in_hands(W) + to_chat(user, "You wrap [src] around the top of [I].") + qdel(src) + else + to_chat(user, "You need one rod to make a wired rod!") + return + else if(istype(I, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = I + if(M.get_amount() < 6) + to_chat(user, "You need at least six metal sheets to make good enough weights!") + return + to_chat(user, "You begin to apply [I] to [src]...") + if(do_after(user, 35, target = src)) + if(M.get_amount() < 6 || !M) + return + var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola + M.use(6) + user.put_in_hands(S) + to_chat(user, "You make some weights out of [I] and tie them to [src].") + remove_item_from_storage(user) + qdel(src) + else + return ..() + +/obj/item/restraints/handcuffs/cable/zipties + name = "zipties" + desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." + icon_state = "cuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + custom_materials = null + breakouttime = 45 SECONDS + trashtype = /obj/item/restraints/handcuffs/cable/zipties/used + color = null + +/obj/item/restraints/handcuffs/cable/zipties/used + desc = "A pair of broken zipties." + icon_state = "cuff_used" + inhand_icon_state = "cuff" + +/obj/item/restraints/handcuffs/cable/zipties/used/attack() + return + +//Legcuffs + +/obj/item/restraints/legcuffs + name = "leg cuffs" + desc = "Use this to keep prisoners in line." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "handcuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + flags_1 = CONDUCT_1 + throwforce = 0 + w_class = WEIGHT_CLASS_NORMAL + slowdown = 7 + breakouttime = 30 SECONDS + +/obj/item/restraints/legcuffs/beartrap + name = "bear trap" + throw_speed = 1 + throw_range = 1 + icon_state = "beartrap" + desc = "A trap used to catch bears and other legged creatures." + var/armed = 0 + var/trap_damage = 20 + +/obj/item/restraints/legcuffs/beartrap/Initialize() + . = ..() + update_icon() + +/obj/item/restraints/legcuffs/beartrap/update_icon_state() + icon_state = "[initial(icon_state)][armed]" + +/obj/item/restraints/legcuffs/beartrap/suicide_act(mob/user) + user.visible_message("[user] is sticking [user.p_their()] head in the [src.name]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/restraints/legcuffs/beartrap/attack_self(mob/user) + ..() + if(ishuman(user) && !user.stat && !user.restrained()) + armed = !armed + update_icon() + to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") + +/obj/item/restraints/legcuffs/beartrap/proc/close_trap() + armed = FALSE + update_icon() + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + +/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) + if(armed && isturf(loc)) + if(isliving(AM)) + var/mob/living/L = AM + var/snap = TRUE + if(istype(L.buckled, /obj/vehicle)) + var/obj/vehicle/ridden_vehicle = L.buckled + if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. + close_trap() + ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") + return ..() + + if(L.movement_type & (FLYING|FLOATING)) //don't close the trap if they're flying/floating over it. + snap = FALSE + + var/def_zone = BODY_ZONE_CHEST + if(snap && iscarbon(L)) + var/mob/living/carbon/C = L + if(C.mobility_flags & MOBILITY_STAND) + def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. + C.legcuffed = src + forceMove(C) + C.update_equipment_speed_mods() + C.update_inv_legcuffed() + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + else if(snap && isanimal(L)) + var/mob/living/simple_animal/SA = L + if(SA.mob_size <= MOB_SIZE_TINY) //don't close the trap if they're as small as a mouse. + snap = FALSE + if(snap) + close_trap() + L.visible_message("[L] triggers \the [src].", \ + "You trigger \the [src]!") + L.apply_damage(trap_damage, BRUTE, def_zone) + ..() + +/obj/item/restraints/legcuffs/beartrap/energy + name = "energy snare" + armed = 1 + icon_state = "e_snare" + trap_damage = 0 + breakouttime = 30 + item_flags = DROPDEL + flags_1 = NONE + +/obj/item/restraints/legcuffs/beartrap/energy/Initialize() + . = ..() + addtimer(CALLBACK(src, .proc/dissipate), 100) + +/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate() + if(!ismob(loc)) + do_sparks(1, TRUE, src) + qdel(src) + +/obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user) + Crossed(user) //honk + return ..() + +/obj/item/restraints/legcuffs/beartrap/energy/cyborg + breakouttime = 20 // Cyborgs shouldn't have a strong restraint + +/obj/item/restraints/legcuffs/bola + name = "bola" + desc = "A restraining device designed to be thrown at the target. Upon connecting with said target, it will wrap around their legs, making it difficult for them to move quickly." + icon_state = "bola" + inhand_icon_state = "bola" + lefthand_file = 'icons/mob/inhands/weapons/thrown_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/thrown_righthand.dmi' + breakouttime = 35//easy to apply, easy to break out of + gender = NEUTER + var/knockdown = 0 + +/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, gentle = FALSE, quickstart = TRUE) + if(!..()) + return + playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, TRUE) + +/obj/item/restraints/legcuffs/bola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(..() || !iscarbon(hit_atom))//if it gets caught or the target can't be cuffed, + return//abort + ensnare(hit_atom) + +/** + * Attempts to legcuff someone with the bola + * + * Arguments: + * * C - the carbon that we will try to ensnare + */ +/obj/item/restraints/legcuffs/bola/proc/ensnare(mob/living/carbon/C) + if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) + visible_message("\The [src] ensnares [C]!") + C.legcuffed = src + forceMove(C) + C.update_equipment_speed_mods() + C.update_inv_legcuffed() + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + to_chat(C, "\The [src] ensnares you!") + C.Knockdown(knockdown) + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + +/obj/item/restraints/legcuffs/bola/tactical//traitor variant + name = "reinforced bola" + desc = "A strong bola, made with a long steel chain. It looks heavy, enough so that it could trip somebody." + icon_state = "bola_r" + inhand_icon_state = "bola_r" + breakouttime = 70 + knockdown = 35 + +/obj/item/restraints/legcuffs/bola/energy //For Security + name = "energy bola" + desc = "A specialized hard-light bola designed to ensnare fleeing criminals and aid in arrests." + icon_state = "ebola" + inhand_icon_state = "ebola" + hitsound = 'sound/weapons/taserhit.ogg' + w_class = WEIGHT_CLASS_SMALL + breakouttime = 60 + +/obj/item/restraints/legcuffs/bola/energy/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(iscarbon(hit_atom)) + var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(hit_atom)) + B.Crossed(hit_atom) + qdel(src) + ..() + +/obj/item/restraints/legcuffs/bola/gonbola + name = "gonbola" + desc = "Hey, if you have to be hugged in the legs by anything, it might as well be this little guy." + icon_state = "gonbola" + inhand_icon_state = "bola_r" + breakouttime = 300 + slowdown = 0 + var/datum/status_effect/gonbola_pacify/effectReference + +/obj/item/restraints/legcuffs/bola/gonbola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(iscarbon(hit_atom)) + var/mob/living/carbon/C = hit_atom + effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY) + +/obj/item/restraints/legcuffs/bola/gonbola/dropped(mob/user) + . = ..() + if(effectReference) + QDEL_NULL(effectReference) diff --git a/code/game/objects/items/hot_potato.dm b/code/game/objects/items/hot_potato.dm index 50ca27defc3..3893cc997b4 100644 --- a/code/game/objects/items/hot_potato.dm +++ b/code/game/objects/items/hot_potato.dm @@ -1,174 +1,174 @@ -//CREATOR'S NOTE: DO NOT FUCKING GIVE THIS TO BOTANY! -/obj/item/hot_potato - name = "hot potato" - desc = "A label on the side of this potato reads \"Product of DonkCo Service Wing. Activate far away from populated areas. Device will only attach to sapient creatures.\" You can attack anyone with it to force it on them instead of yourself!" - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "potato" - item_flags = NOBLUDGEON - force = 0 - var/icon_off = "potato" - var/icon_on = "potato_active" - var/detonation_timerid - var/activation_time = 0 - var/timer = 600 //deciseconds - var/show_timer = FALSE - var/reusable = FALSE //absolute madman - var/sticky = TRUE - var/forceful_attachment = TRUE - var/stimulant = TRUE - var/detonate_explosion = TRUE - var/detonate_dev_range = 0 - var/detonate_heavy_range = 0 - var/detonate_light_range = 2 - var/detonate_flash_range = 5 - var/detonate_fire_range = 5 - - var/active = FALSE - - var/color_val = FALSE - - var/datum/weakref/current - -/obj/item/hot_potato/Destroy() - if(active) - deactivate() - return ..() - -/obj/item/hot_potato/proc/colorize(mob/target) - //Clear color from old target - if(current) - var/mob/M = current.resolve() - if(istype(M)) - M.remove_atom_colour(FIXED_COLOUR_PRIORITY) - //Give to new target - current = null - //Swap colors - color_val = !color_val - if(istype(target)) - current = WEAKREF(target) - target.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY) - -/obj/item/hot_potato/proc/detonate() - var/atom/location = loc - location.visible_message("[src] [detonate_explosion? "explodes" : "activates"]!", "[src] activates! You've ran out of time!") - if(detonate_explosion) - explosion(src, detonate_dev_range, detonate_heavy_range, detonate_light_range, detonate_flash_range, flame_range = detonate_fire_range) - deactivate() - if(!reusable) - var/mob/M = loc - if(istype(M)) - M.dropItemToGround(src, TRUE) - qdel(src) - -/obj/item/hot_potato/attack_self(mob/user) - if(activate(timer, user)) - user.visible_message("[user] squeezes [src], which promptly starts to flash red-hot colors!", "You squeeze [src], activating its countdown and attachment mechanism!", - "You hear a mechanical click and a loud beeping!") - return - return ..() - -/obj/item/hot_potato/process() - if(stimulant) - if(isliving(loc)) - var/mob/living/L = loc - L.SetStun(0) - L.SetKnockdown(0) - L.SetSleeping(0) - L.SetImmobilized(0) - L.SetParalyzed(0) - L.SetUnconscious(0) - L.reagents.add_reagent(/datum/reagent/medicine/muscle_stimulant, clamp(5 - L.reagents.get_reagent_amount(/datum/reagent/medicine/muscle_stimulant), 0, 5)) //If you don't have legs or get bola'd, tough luck! - colorize(L) - -/obj/item/hot_potato/examine(mob/user) - . = ..() - if(active) - . += "[src] is flashing red-hot! You should probably get rid of it!" - if(show_timer) - . += "[src]'s timer looks to be at [DisplayTimeText(activation_time - world.time)]!" - -/obj/item/hot_potato/equipped(mob/user) - . = ..() - if(active) - to_chat(user, "You have a really bad feeling about [src]!") - -/obj/item/hot_potato/afterattack(atom/target, mob/user, adjacent, params) - . = ..() - if(!adjacent || !ismob(target)) - return - force_onto(target, user) - -/obj/item/hot_potato/proc/force_onto(mob/living/victim, mob/user) - if(!istype(victim) || user != loc || victim == user) - return FALSE - if(!victim.client) - to_chat(user, "[src] refuses to attach to a non-sapient creature!") - if(victim.stat != CONSCIOUS || !victim.get_num_legs()) - to_chat(user, "[src] refuses to attach to someone incapable of using it!") - user.temporarilyRemoveItemFromInventory(src, TRUE) - . = FALSE - if(!victim.put_in_hands(src)) - if(forceful_attachment) - victim.dropItemToGround(victim.get_inactive_held_item()) - if(!victim.put_in_hands(src)) - victim.dropItemToGround(victim.get_active_held_item()) - if(victim.put_in_hands(src)) - . = TRUE - else - . = TRUE - else - . = TRUE - if(.) - log_combat(user, victim, "forced a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") - user.visible_message("[user] forces [src] onto [victim]!", "You force [src] onto [victim]!", "You hear a mechanical click and a beep.") - colorize(null) - else - log_combat(user, victim, "tried to force a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") - user.visible_message("[user] tried to force [src] onto [victim], but it could not attach!", "You try to force [src] onto [victim], but it is unable to attach!", "You hear a mechanical click and two buzzes.") - user.put_in_hands(src) - -/obj/item/hot_potato/dropped(mob/user) - . = ..() - colorize(null) - -/obj/item/hot_potato/proc/activate(delay, mob/user) - if(active) - return - update_icon() - if(sticky) - ADD_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) - name = "primed [name]" - activation_time = timer + world.time - detonation_timerid = addtimer(CALLBACK(src, .proc/detonate), delay, TIMER_STOPPABLE) - START_PROCESSING(SSfastprocess, src) - if(user) - log_bomber(user, "has primed a", src, "for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") - else - log_bomber(null, null, src, "was primed for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") - active = TRUE - -/obj/item/hot_potato/proc/deactivate() - update_icon() - name = initial(name) - REMOVE_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) - deltimer(detonation_timerid) - STOP_PROCESSING(SSfastprocess, src) - detonation_timerid = null - colorize(null) - active = FALSE - -/obj/item/hot_potato/update_icon_state() - icon_state = active? icon_on : icon_off - -/obj/item/hot_potato/syndicate - detonate_light_range = 4 - detonate_fire_range = 5 - -/obj/item/hot_potato/harmless - detonate_explosion = FALSE - -/obj/item/hot_potato/harmless/toy - desc = "A label on the side of this potato reads \"Product of DonkCo Toys and Recreation department.\" You can attack anyone with it to put it on them instead, if they have a free hand to take it!" - sticky = FALSE - reusable = TRUE - forceful_attachment = FALSE +//CREATOR'S NOTE: DO NOT FUCKING GIVE THIS TO BOTANY! +/obj/item/hot_potato + name = "hot potato" + desc = "A label on the side of this potato reads \"Product of DonkCo Service Wing. Activate far away from populated areas. Device will only attach to sapient creatures.\" You can attack anyone with it to force it on them instead of yourself!" + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "potato" + item_flags = NOBLUDGEON + force = 0 + var/icon_off = "potato" + var/icon_on = "potato_active" + var/detonation_timerid + var/activation_time = 0 + var/timer = 600 //deciseconds + var/show_timer = FALSE + var/reusable = FALSE //absolute madman + var/sticky = TRUE + var/forceful_attachment = TRUE + var/stimulant = TRUE + var/detonate_explosion = TRUE + var/detonate_dev_range = 0 + var/detonate_heavy_range = 0 + var/detonate_light_range = 2 + var/detonate_flash_range = 5 + var/detonate_fire_range = 5 + + var/active = FALSE + + var/color_val = FALSE + + var/datum/weakref/current + +/obj/item/hot_potato/Destroy() + if(active) + deactivate() + return ..() + +/obj/item/hot_potato/proc/colorize(mob/target) + //Clear color from old target + if(current) + var/mob/M = current.resolve() + if(istype(M)) + M.remove_atom_colour(FIXED_COLOUR_PRIORITY) + //Give to new target + current = null + //Swap colors + color_val = !color_val + if(istype(target)) + current = WEAKREF(target) + target.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY) + +/obj/item/hot_potato/proc/detonate() + var/atom/location = loc + location.visible_message("[src] [detonate_explosion? "explodes" : "activates"]!", "[src] activates! You've ran out of time!") + if(detonate_explosion) + explosion(src, detonate_dev_range, detonate_heavy_range, detonate_light_range, detonate_flash_range, flame_range = detonate_fire_range) + deactivate() + if(!reusable) + var/mob/M = loc + if(istype(M)) + M.dropItemToGround(src, TRUE) + qdel(src) + +/obj/item/hot_potato/attack_self(mob/user) + if(activate(timer, user)) + user.visible_message("[user] squeezes [src], which promptly starts to flash red-hot colors!", "You squeeze [src], activating its countdown and attachment mechanism!", + "You hear a mechanical click and a loud beeping!") + return + return ..() + +/obj/item/hot_potato/process() + if(stimulant) + if(isliving(loc)) + var/mob/living/L = loc + L.SetStun(0) + L.SetKnockdown(0) + L.SetSleeping(0) + L.SetImmobilized(0) + L.SetParalyzed(0) + L.SetUnconscious(0) + L.reagents.add_reagent(/datum/reagent/medicine/muscle_stimulant, clamp(5 - L.reagents.get_reagent_amount(/datum/reagent/medicine/muscle_stimulant), 0, 5)) //If you don't have legs or get bola'd, tough luck! + colorize(L) + +/obj/item/hot_potato/examine(mob/user) + . = ..() + if(active) + . += "[src] is flashing red-hot! You should probably get rid of it!" + if(show_timer) + . += "[src]'s timer looks to be at [DisplayTimeText(activation_time - world.time)]!" + +/obj/item/hot_potato/equipped(mob/user) + . = ..() + if(active) + to_chat(user, "You have a really bad feeling about [src]!") + +/obj/item/hot_potato/afterattack(atom/target, mob/user, adjacent, params) + . = ..() + if(!adjacent || !ismob(target)) + return + force_onto(target, user) + +/obj/item/hot_potato/proc/force_onto(mob/living/victim, mob/user) + if(!istype(victim) || user != loc || victim == user) + return FALSE + if(!victim.client) + to_chat(user, "[src] refuses to attach to a non-sapient creature!") + if(victim.stat != CONSCIOUS || !victim.get_num_legs()) + to_chat(user, "[src] refuses to attach to someone incapable of using it!") + user.temporarilyRemoveItemFromInventory(src, TRUE) + . = FALSE + if(!victim.put_in_hands(src)) + if(forceful_attachment) + victim.dropItemToGround(victim.get_inactive_held_item()) + if(!victim.put_in_hands(src)) + victim.dropItemToGround(victim.get_active_held_item()) + if(victim.put_in_hands(src)) + . = TRUE + else + . = TRUE + else + . = TRUE + if(.) + log_combat(user, victim, "forced a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") + user.visible_message("[user] forces [src] onto [victim]!", "You force [src] onto [victim]!", "You hear a mechanical click and a beep.") + colorize(null) + else + log_combat(user, victim, "tried to force a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") + user.visible_message("[user] tried to force [src] onto [victim], but it could not attach!", "You try to force [src] onto [victim], but it is unable to attach!", "You hear a mechanical click and two buzzes.") + user.put_in_hands(src) + +/obj/item/hot_potato/dropped(mob/user) + . = ..() + colorize(null) + +/obj/item/hot_potato/proc/activate(delay, mob/user) + if(active) + return + update_icon() + if(sticky) + ADD_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) + name = "primed [name]" + activation_time = timer + world.time + detonation_timerid = addtimer(CALLBACK(src, .proc/detonate), delay, TIMER_STOPPABLE) + START_PROCESSING(SSfastprocess, src) + if(user) + log_bomber(user, "has primed a", src, "for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") + else + log_bomber(null, null, src, "was primed for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") + active = TRUE + +/obj/item/hot_potato/proc/deactivate() + update_icon() + name = initial(name) + REMOVE_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) + deltimer(detonation_timerid) + STOP_PROCESSING(SSfastprocess, src) + detonation_timerid = null + colorize(null) + active = FALSE + +/obj/item/hot_potato/update_icon_state() + icon_state = active? icon_on : icon_off + +/obj/item/hot_potato/syndicate + detonate_light_range = 4 + detonate_fire_range = 5 + +/obj/item/hot_potato/harmless + detonate_explosion = FALSE + +/obj/item/hot_potato/harmless/toy + desc = "A label on the side of this potato reads \"Product of DonkCo Toys and Recreation department.\" You can attack anyone with it to put it on them instead, if they have a free hand to take it!" + sticky = FALSE + reusable = TRUE + forceful_attachment = FALSE diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm index 07e0ab2c8f4..fd79c112c9b 100644 --- a/code/game/objects/items/implants/implant.dm +++ b/code/game/objects/items/implants/implant.dm @@ -1,114 +1,114 @@ -/obj/item/implant - name = "implant" - icon = 'icons/obj/implants.dmi' - icon_state = "generic" //Shows up as the action button icon - actions_types = list(/datum/action/item_action/hands_free/activate) - var/activated = TRUE //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants - var/mob/living/imp_in = null - var/implant_color = "b" - var/allow_multiple = FALSE - var/uses = -1 - item_flags = DROPDEL - - -/obj/item/implant/proc/trigger(emote, mob/living/carbon/source) - return - -/obj/item/implant/proc/on_death(emote, mob/living/carbon/source) - return - -/obj/item/implant/proc/activate() - SEND_SIGNAL(src, COMSIG_IMPLANT_ACTIVATED) - -/obj/item/implant/ui_action_click() - activate("action_button") - -/obj/item/implant/proc/can_be_implanted_in(mob/living/target) // for human-only and other special requirements - return TRUE - -/mob/living/proc/can_be_implanted() - return TRUE - -/mob/living/silicon/can_be_implanted() - return FALSE - -/mob/living/simple_animal/can_be_implanted() - return healable //Applies to robots and most non-organics, exceptions can override. - - - -//What does the implant do upon injection? -//return 1 if the implant injects -//return 0 if there is no room for implant / it fails -/obj/item/implant/proc/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE) - if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) - return - LAZYINITLIST(target.implants) - if(!force && (!target.can_be_implanted() || !can_be_implanted_in(target))) - return FALSE - for(var/X in target.implants) - var/obj/item/implant/imp_e = X - var/flags = SEND_SIGNAL(imp_e, COMSIG_IMPLANT_OTHER, args, src) - if(flags & COMPONENT_DELETE_NEW_IMPLANT) - UNSETEMPTY(target.implants) - qdel(src) - return TRUE - if(flags & COMPONENT_DELETE_OLD_IMPLANT) - qdel(imp_e) - continue - if(flags & COMPONENT_STOP_IMPLANTING) - UNSETEMPTY(target.implants) - return FALSE - - if(istype(imp_e, type)) - if(!allow_multiple) - if(imp_e.uses < initial(imp_e.uses)*2) - if(uses == -1) - imp_e.uses = -1 - else - imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) - qdel(src) - return TRUE - else - return FALSE - - forceMove(target) - imp_in = target - target.implants += src - if(activated) - for(var/X in actions) - var/datum/action/A = X - A.Grant(target) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - H.sec_hud_set_implants() - - if(user) - log_combat(user, target, "implanted", "\a [name]") - - return TRUE - -/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0) - moveToNullspace() - imp_in = null - source.implants -= src - for(var/X in actions) - var/datum/action/A = X - A.Grant(source) - if(ishuman(source)) - var/mob/living/carbon/human/H = source - H.sec_hud_set_implants() - - return 1 - -/obj/item/implant/Destroy() - if(imp_in) - removed(imp_in) - return ..() - -/obj/item/implant/proc/get_data() - return "No information available" - -/obj/item/implant/dropped(mob/user) - . = 1 - ..() +/obj/item/implant + name = "implant" + icon = 'icons/obj/implants.dmi' + icon_state = "generic" //Shows up as the action button icon + actions_types = list(/datum/action/item_action/hands_free/activate) + var/activated = TRUE //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants + var/mob/living/imp_in = null + var/implant_color = "b" + var/allow_multiple = FALSE + var/uses = -1 + item_flags = DROPDEL + + +/obj/item/implant/proc/trigger(emote, mob/living/carbon/source) + return + +/obj/item/implant/proc/on_death(emote, mob/living/carbon/source) + return + +/obj/item/implant/proc/activate() + SEND_SIGNAL(src, COMSIG_IMPLANT_ACTIVATED) + +/obj/item/implant/ui_action_click() + activate("action_button") + +/obj/item/implant/proc/can_be_implanted_in(mob/living/target) // for human-only and other special requirements + return TRUE + +/mob/living/proc/can_be_implanted() + return TRUE + +/mob/living/silicon/can_be_implanted() + return FALSE + +/mob/living/simple_animal/can_be_implanted() + return healable //Applies to robots and most non-organics, exceptions can override. + + + +//What does the implant do upon injection? +//return 1 if the implant injects +//return 0 if there is no room for implant / it fails +/obj/item/implant/proc/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE) + if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) + return + LAZYINITLIST(target.implants) + if(!force && (!target.can_be_implanted() || !can_be_implanted_in(target))) + return FALSE + for(var/X in target.implants) + var/obj/item/implant/imp_e = X + var/flags = SEND_SIGNAL(imp_e, COMSIG_IMPLANT_OTHER, args, src) + if(flags & COMPONENT_DELETE_NEW_IMPLANT) + UNSETEMPTY(target.implants) + qdel(src) + return TRUE + if(flags & COMPONENT_DELETE_OLD_IMPLANT) + qdel(imp_e) + continue + if(flags & COMPONENT_STOP_IMPLANTING) + UNSETEMPTY(target.implants) + return FALSE + + if(istype(imp_e, type)) + if(!allow_multiple) + if(imp_e.uses < initial(imp_e.uses)*2) + if(uses == -1) + imp_e.uses = -1 + else + imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) + qdel(src) + return TRUE + else + return FALSE + + forceMove(target) + imp_in = target + target.implants += src + if(activated) + for(var/X in actions) + var/datum/action/A = X + A.Grant(target) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + H.sec_hud_set_implants() + + if(user) + log_combat(user, target, "implanted", "\a [name]") + + return TRUE + +/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0) + moveToNullspace() + imp_in = null + source.implants -= src + for(var/X in actions) + var/datum/action/A = X + A.Grant(source) + if(ishuman(source)) + var/mob/living/carbon/human/H = source + H.sec_hud_set_implants() + + return 1 + +/obj/item/implant/Destroy() + if(imp_in) + removed(imp_in) + return ..() + +/obj/item/implant/proc/get_data() + return "No information available" + +/obj/item/implant/dropped(mob/user) + . = 1 + ..() diff --git a/code/game/objects/items/implants/implantcase.dm b/code/game/objects/items/implants/implantcase.dm index 423fd549936..8407ee2f519 100644 --- a/code/game/objects/items/implants/implantcase.dm +++ b/code/game/objects/items/implants/implantcase.dm @@ -1,84 +1,84 @@ -/obj/item/implantcase - name = "implant case" - desc = "A glass case containing an implant." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implantcase-0" - inhand_icon_state = "implantcase" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/glass=500) - var/obj/item/implant/imp = null - var/imp_type - - -/obj/item/implantcase/update_icon_state() - if(imp) - icon_state = "implantcase-[imp.implant_color]" - else - icon_state = "implantcase-0" - - -/obj/item/implantcase/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/t = stripped_input(user, "What would you like the label to be?", name, null) - if(user.get_active_held_item() != W) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(t) - name = "implant case - '[t]'" - else - name = "implant case" - else if(istype(W, /obj/item/implanter)) - var/obj/item/implanter/I = W - if(I.imp) - if(imp || I.imp.imp_in) - return - I.imp.forceMove(src) - imp = I.imp - I.imp = null - update_icon() - reagents = imp.reagents - I.update_icon() - else - if(imp) - if(I.imp) - return - imp.forceMove(I) - I.imp = imp - imp = null - reagents = null - update_icon() - I.update_icon() - - else - return ..() - -/obj/item/implantcase/Initialize(mapload) - . = ..() - if(imp_type) - imp = new imp_type(src) - update_icon() - reagents = imp.reagents - - -/obj/item/implantcase/tracking - name = "implant case - 'Tracking'" - desc = "A glass case containing a tracking implant." - imp_type = /obj/item/implant/tracking - -/obj/item/implantcase/weapons_auth - name = "implant case - 'Firearms Authentication'" - desc = "A glass case containing a firearms authentication implant." - imp_type = /obj/item/implant/weapons_auth - -/obj/item/implantcase/adrenaline - name = "implant case - 'Adrenaline'" - desc = "A glass case containing an adrenaline implant." - imp_type = /obj/item/implant/adrenalin +/obj/item/implantcase + name = "implant case" + desc = "A glass case containing an implant." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implantcase-0" + inhand_icon_state = "implantcase" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/glass=500) + var/obj/item/implant/imp = null + var/imp_type + + +/obj/item/implantcase/update_icon_state() + if(imp) + icon_state = "implantcase-[imp.implant_color]" + else + icon_state = "implantcase-0" + + +/obj/item/implantcase/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/t = stripped_input(user, "What would you like the label to be?", name, null) + if(user.get_active_held_item() != W) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(t) + name = "implant case - '[t]'" + else + name = "implant case" + else if(istype(W, /obj/item/implanter)) + var/obj/item/implanter/I = W + if(I.imp) + if(imp || I.imp.imp_in) + return + I.imp.forceMove(src) + imp = I.imp + I.imp = null + update_icon() + reagents = imp.reagents + I.update_icon() + else + if(imp) + if(I.imp) + return + imp.forceMove(I) + I.imp = imp + imp = null + reagents = null + update_icon() + I.update_icon() + + else + return ..() + +/obj/item/implantcase/Initialize(mapload) + . = ..() + if(imp_type) + imp = new imp_type(src) + update_icon() + reagents = imp.reagents + + +/obj/item/implantcase/tracking + name = "implant case - 'Tracking'" + desc = "A glass case containing a tracking implant." + imp_type = /obj/item/implant/tracking + +/obj/item/implantcase/weapons_auth + name = "implant case - 'Firearms Authentication'" + desc = "A glass case containing a firearms authentication implant." + imp_type = /obj/item/implant/weapons_auth + +/obj/item/implantcase/adrenaline + name = "implant case - 'Adrenaline'" + desc = "A glass case containing an adrenaline implant." + imp_type = /obj/item/implant/adrenalin diff --git a/code/game/objects/items/implants/implantchair.dm b/code/game/objects/items/implants/implantchair.dm index 69d7bb2b1bb..d42d345dfbb 100644 --- a/code/game/objects/items/implants/implantchair.dm +++ b/code/game/objects/items/implants/implantchair.dm @@ -1,200 +1,200 @@ -/obj/machinery/implantchair - name = "mindshield implanter" - desc = "Used to implant occupants with mindshield implants." - icon = 'icons/obj/machines/implantchair.dmi' - icon_state = "implantchair" - density = TRUE - opacity = 0 - ui_x = 375 - ui_y = 280 - - var/ready = TRUE - var/replenishing = FALSE - - var/ready_implants = 5 - var/max_implants = 5 - var/injection_cooldown = 600 - var/replenish_cooldown = 6000 - var/implant_type = /obj/item/implant/mindshield - var/auto_inject = FALSE - var/auto_replenish = TRUE - var/special = FALSE - var/special_name = "special function" - var/message_cooldown - var/breakout_time = 600 - -/obj/machinery/implantchair/Initialize() - . = ..() - open_machine() - update_icon() - - -/obj/machinery/implantchair/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "implantchair", name, ui_x, ui_y, master_ui, state) - ui.open() - - -/obj/machinery/implantchair/ui_data() - var/list/data = list() - data["occupied"] = occupant ? 1 : 0 - data["open"] = state_open - - data["occupant"] = list() - if(occupant) - var/mob/living/mob_occupant = occupant - data["occupant"]["name"] = mob_occupant.name - data["occupant"]["stat"] = mob_occupant.stat - - data["special_name"] = special ? special_name : null - data["ready_implants"] = ready_implants - data["ready"] = ready - data["replenishing"] = replenishing - - return data - -/obj/machinery/implantchair/ui_act(action, params) - if(..()) - return - switch(action) - if("door") - if(state_open) - close_machine() - else - open_machine() - . = TRUE - if("implant") - implant(occupant,usr) - . = TRUE - -/obj/machinery/implantchair/proc/implant(mob/living/M,mob/user) - if (!istype(M)) - return - if(!ready_implants || !ready) - return - if(implant_action(M,user)) - ready_implants-- - if(!replenishing && auto_replenish) - replenishing = TRUE - addtimer(CALLBACK(src,.proc/replenish),replenish_cooldown) - if(injection_cooldown > 0) - ready = FALSE - addtimer(CALLBACK(src,.proc/set_ready),injection_cooldown) - else - playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, TRUE) - update_icon() - -/obj/machinery/implantchair/proc/implant_action(mob/living/M) - var/obj/item/I = new implant_type - if(istype(I, /obj/item/implant)) - var/obj/item/implant/P = I - if(P.implant(M)) - visible_message("[M] is implanted by [src].") - return TRUE - else if(istype(I, /obj/item/organ)) - var/obj/item/organ/P = I - P.Insert(M, FALSE, FALSE) - visible_message("[M] is implanted by [src].") - return TRUE - -/obj/machinery/implantchair/update_icon_state() - icon_state = initial(icon_state) - if(state_open) - icon_state += "_open" - if(occupant) - icon_state += "_occupied" - -/obj/machinery/implantchair/update_overlays() - . = ..() - if(ready) - . += "ready" - -/obj/machinery/implantchair/proc/replenish() - if(ready_implants < max_implants) - ready_implants++ - if(ready_implants < max_implants) - addtimer(CALLBACK(src,"replenish"),replenish_cooldown) - else - replenishing = FALSE - -/obj/machinery/implantchair/proc/set_ready() - ready = TRUE - update_icon() - -/obj/machinery/implantchair/container_resist(mob/living/user) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("You see [user] kicking against the door of [src]!", \ - "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a metallic creaking from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || state_open) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - -/obj/machinery/implantchair/relaymove(mob/user) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - -/obj/machinery/implantchair/MouseDrop_T(mob/target, mob/user) - if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - close_machine(target) - -/obj/machinery/implantchair/close_machine(mob/living/user) - if((isnull(user) || istype(user)) && state_open) - ..(user) - if(auto_inject && ready && ready_implants > 0) - implant(user,null) - -/obj/machinery/implantchair/genepurge - name = "Genetic purifier" - desc = "Used to purge a human genome of foreign influences." - special = TRUE - special_name = "Purge genome" - injection_cooldown = 0 - replenish_cooldown = 300 - -/obj/machinery/implantchair/genepurge/implant_action(mob/living/carbon/human/H,mob/user) - if(!istype(H)) - return 0 - H.set_species(/datum/species/human, 1)//lizards go home - purrbation_remove(H)//remove cats - H.dna.remove_all_mutations()//hulks out - return 1 - - -/obj/machinery/implantchair/brainwash - name = "Neural Imprinter" - desc = "Used to indoctrinate rehabilitate hardened recidivists." - special_name = "Imprint" - injection_cooldown = 3000 - auto_inject = FALSE - auto_replenish = FALSE - special = TRUE - var/objective = "Obey the law. Praise Nanotrasen." - var/custom = FALSE - -/obj/machinery/implantchair/brainwash/implant_action(mob/living/C,mob/user) - if(!istype(C) || !C.mind) // I don't know how this makes any sense for silicons but laws trump objectives anyway. - return FALSE - if(custom) - if(!user || !user.Adjacent(src)) - return FALSE - objective = stripped_input(usr,"What order do you want to imprint on [C]?","Enter the order","",120) - message_admins("[ADMIN_LOOKUPFLW(user)] set brainwash machine objective to '[objective]'.") - log_game("[key_name(user)] set brainwash machine objective to '[objective]'.") - if(HAS_TRAIT(C, TRAIT_MINDSHIELD)) - return FALSE - brainwash(C, objective) - message_admins("[ADMIN_LOOKUPFLW(user)] brainwashed [key_name_admin(C)] with objective '[objective]'.") - log_game("[key_name(user)] brainwashed [key_name(C)] with objective '[objective]'.") - return TRUE +/obj/machinery/implantchair + name = "mindshield implanter" + desc = "Used to implant occupants with mindshield implants." + icon = 'icons/obj/machines/implantchair.dmi' + icon_state = "implantchair" + density = TRUE + opacity = 0 + ui_x = 375 + ui_y = 280 + + var/ready = TRUE + var/replenishing = FALSE + + var/ready_implants = 5 + var/max_implants = 5 + var/injection_cooldown = 600 + var/replenish_cooldown = 6000 + var/implant_type = /obj/item/implant/mindshield + var/auto_inject = FALSE + var/auto_replenish = TRUE + var/special = FALSE + var/special_name = "special function" + var/message_cooldown + var/breakout_time = 600 + +/obj/machinery/implantchair/Initialize() + . = ..() + open_machine() + update_icon() + + +/obj/machinery/implantchair/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "implantchair", name, ui_x, ui_y, master_ui, state) + ui.open() + + +/obj/machinery/implantchair/ui_data() + var/list/data = list() + data["occupied"] = occupant ? 1 : 0 + data["open"] = state_open + + data["occupant"] = list() + if(occupant) + var/mob/living/mob_occupant = occupant + data["occupant"]["name"] = mob_occupant.name + data["occupant"]["stat"] = mob_occupant.stat + + data["special_name"] = special ? special_name : null + data["ready_implants"] = ready_implants + data["ready"] = ready + data["replenishing"] = replenishing + + return data + +/obj/machinery/implantchair/ui_act(action, params) + if(..()) + return + switch(action) + if("door") + if(state_open) + close_machine() + else + open_machine() + . = TRUE + if("implant") + implant(occupant,usr) + . = TRUE + +/obj/machinery/implantchair/proc/implant(mob/living/M,mob/user) + if (!istype(M)) + return + if(!ready_implants || !ready) + return + if(implant_action(M,user)) + ready_implants-- + if(!replenishing && auto_replenish) + replenishing = TRUE + addtimer(CALLBACK(src,.proc/replenish),replenish_cooldown) + if(injection_cooldown > 0) + ready = FALSE + addtimer(CALLBACK(src,.proc/set_ready),injection_cooldown) + else + playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, TRUE) + update_icon() + +/obj/machinery/implantchair/proc/implant_action(mob/living/M) + var/obj/item/I = new implant_type + if(istype(I, /obj/item/implant)) + var/obj/item/implant/P = I + if(P.implant(M)) + visible_message("[M] is implanted by [src].") + return TRUE + else if(istype(I, /obj/item/organ)) + var/obj/item/organ/P = I + P.Insert(M, FALSE, FALSE) + visible_message("[M] is implanted by [src].") + return TRUE + +/obj/machinery/implantchair/update_icon_state() + icon_state = initial(icon_state) + if(state_open) + icon_state += "_open" + if(occupant) + icon_state += "_occupied" + +/obj/machinery/implantchair/update_overlays() + . = ..() + if(ready) + . += "ready" + +/obj/machinery/implantchair/proc/replenish() + if(ready_implants < max_implants) + ready_implants++ + if(ready_implants < max_implants) + addtimer(CALLBACK(src,"replenish"),replenish_cooldown) + else + replenishing = FALSE + +/obj/machinery/implantchair/proc/set_ready() + ready = TRUE + update_icon() + +/obj/machinery/implantchair/container_resist(mob/living/user) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("You see [user] kicking against the door of [src]!", \ + "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a metallic creaking from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src || state_open) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + +/obj/machinery/implantchair/relaymove(mob/user) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + +/obj/machinery/implantchair/MouseDrop_T(mob/target, mob/user) + if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + close_machine(target) + +/obj/machinery/implantchair/close_machine(mob/living/user) + if((isnull(user) || istype(user)) && state_open) + ..(user) + if(auto_inject && ready && ready_implants > 0) + implant(user,null) + +/obj/machinery/implantchair/genepurge + name = "Genetic purifier" + desc = "Used to purge a human genome of foreign influences." + special = TRUE + special_name = "Purge genome" + injection_cooldown = 0 + replenish_cooldown = 300 + +/obj/machinery/implantchair/genepurge/implant_action(mob/living/carbon/human/H,mob/user) + if(!istype(H)) + return 0 + H.set_species(/datum/species/human, 1)//lizards go home + purrbation_remove(H)//remove cats + H.dna.remove_all_mutations()//hulks out + return 1 + + +/obj/machinery/implantchair/brainwash + name = "Neural Imprinter" + desc = "Used to indoctrinate rehabilitate hardened recidivists." + special_name = "Imprint" + injection_cooldown = 3000 + auto_inject = FALSE + auto_replenish = FALSE + special = TRUE + var/objective = "Obey the law. Praise Nanotrasen." + var/custom = FALSE + +/obj/machinery/implantchair/brainwash/implant_action(mob/living/C,mob/user) + if(!istype(C) || !C.mind) // I don't know how this makes any sense for silicons but laws trump objectives anyway. + return FALSE + if(custom) + if(!user || !user.Adjacent(src)) + return FALSE + objective = stripped_input(usr,"What order do you want to imprint on [C]?","Enter the order","",120) + message_admins("[ADMIN_LOOKUPFLW(user)] set brainwash machine objective to '[objective]'.") + log_game("[key_name(user)] set brainwash machine objective to '[objective]'.") + if(HAS_TRAIT(C, TRAIT_MINDSHIELD)) + return FALSE + brainwash(C, objective) + message_admins("[ADMIN_LOOKUPFLW(user)] brainwashed [key_name_admin(C)] with objective '[objective]'.") + log_game("[key_name(user)] brainwashed [key_name(C)] with objective '[objective]'.") + return TRUE diff --git a/code/game/objects/items/implants/implanter.dm b/code/game/objects/items/implants/implanter.dm index 60c092f0cee..08e166a6d9e 100644 --- a/code/game/objects/items/implants/implanter.dm +++ b/code/game/objects/items/implants/implanter.dm @@ -1,65 +1,65 @@ -/obj/item/implanter - name = "implanter" - desc = "A sterile automatic implant injector." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implanter0" - inhand_icon_state = "syringe_0" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=600, /datum/material/glass=200) - var/obj/item/implant/imp = null - var/imp_type = null - - -/obj/item/implanter/update_icon_state() - if(imp) - icon_state = "implanter1" - else - icon_state = "implanter0" - - -/obj/item/implanter/attack(mob/living/M, mob/user) - if(!istype(M)) - return - if(user && imp) - if(M != user) - M.visible_message("[user] is attempting to implant [M].") - - var/turf/T = get_turf(M) - if(T && (M == user || do_mob(user, M, 50))) - if(src && imp) - if(imp.implant(M, user)) - if (M == user) - to_chat(user, "You implant yourself.") - else - M.visible_message("[user] implants [M].", "[user] implants you.") - imp = null - update_icon() - else - to_chat(user, "[src] fails to implant [M].") - -/obj/item/implanter/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You prod at [src] with [W]!") - return - var/t = stripped_input(user, "What would you like the label to be?", name, null) - if(user.get_active_held_item() != W) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(t) - name = "implanter ([t])" - else - name = "implanter" - else - return ..() - -/obj/item/implanter/Initialize(mapload) - . = ..() - if(imp_type) - imp = new imp_type(src) - update_icon() +/obj/item/implanter + name = "implanter" + desc = "A sterile automatic implant injector." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implanter0" + inhand_icon_state = "syringe_0" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=600, /datum/material/glass=200) + var/obj/item/implant/imp = null + var/imp_type = null + + +/obj/item/implanter/update_icon_state() + if(imp) + icon_state = "implanter1" + else + icon_state = "implanter0" + + +/obj/item/implanter/attack(mob/living/M, mob/user) + if(!istype(M)) + return + if(user && imp) + if(M != user) + M.visible_message("[user] is attempting to implant [M].") + + var/turf/T = get_turf(M) + if(T && (M == user || do_mob(user, M, 50))) + if(src && imp) + if(imp.implant(M, user)) + if (M == user) + to_chat(user, "You implant yourself.") + else + M.visible_message("[user] implants [M].", "[user] implants you.") + imp = null + update_icon() + else + to_chat(user, "[src] fails to implant [M].") + +/obj/item/implanter/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You prod at [src] with [W]!") + return + var/t = stripped_input(user, "What would you like the label to be?", name, null) + if(user.get_active_held_item() != W) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(t) + name = "implanter ([t])" + else + name = "implanter" + else + return ..() + +/obj/item/implanter/Initialize(mapload) + . = ..() + if(imp_type) + imp = new imp_type(src) + update_icon() diff --git a/code/game/objects/items/implants/implantpad.dm b/code/game/objects/items/implants/implantpad.dm index de9cbbcdc13..7f43454d03c 100644 --- a/code/game/objects/items/implants/implantpad.dm +++ b/code/game/objects/items/implants/implantpad.dm @@ -1,78 +1,78 @@ -/obj/item/implantpad - name = "implant pad" - desc = "Used to modify implants." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implantpad-0" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - var/obj/item/implantcase/case = null - -/obj/item/implantpad/update_icon_state() - icon_state = "implantpad-[!QDELETED(case)]" - -/obj/item/implantpad/examine(mob/user) - . = ..() - if(Adjacent(user)) - . += "It [case ? "contains \a [case]" : "is currently empty"]." - if(case) - . += "Alt-click to remove [case]." - else - if(case) - . += "There seems to be something inside it, but you can't quite tell what from here..." - -/obj/item/implantpad/handle_atom_del(atom/A) - if(A == case) - case = null - update_icon() - updateSelfDialog() - . = ..() - -/obj/item/implantpad/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(!case) - to_chat(user, "There's no implant to remove from [src].") - return - - user.put_in_hands(case) - - add_fingerprint(user) - case.add_fingerprint(user) - case = null - - updateSelfDialog() - update_icon() - -/obj/item/implantpad/attackby(obj/item/implantcase/C, mob/user, params) - if(istype(C, /obj/item/implantcase) && !case) - if(!user.transferItemToLoc(C, src)) - return - case = C - updateSelfDialog() - update_icon() - else - return ..() - -/obj/item/implantpad/ui_interact(mob/user) - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - user.unset_machine(src) - user << browse(null, "window=implantpad") - return - - user.set_machine(src) - var/dat = "Implant Mini-Computer:
                " - if(case) - if(case.imp) - if(istype(case.imp, /obj/item/implant)) - dat += case.imp.get_data() - else - dat += "The implant casing is empty." - else - dat += "Please insert an implant casing!" - user << browse(dat, "window=implantpad") - onclose(user, "implantpad") +/obj/item/implantpad + name = "implant pad" + desc = "Used to modify implants." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implantpad-0" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + var/obj/item/implantcase/case = null + +/obj/item/implantpad/update_icon_state() + icon_state = "implantpad-[!QDELETED(case)]" + +/obj/item/implantpad/examine(mob/user) + . = ..() + if(Adjacent(user)) + . += "It [case ? "contains \a [case]" : "is currently empty"]." + if(case) + . += "Alt-click to remove [case]." + else + if(case) + . += "There seems to be something inside it, but you can't quite tell what from here..." + +/obj/item/implantpad/handle_atom_del(atom/A) + if(A == case) + case = null + update_icon() + updateSelfDialog() + . = ..() + +/obj/item/implantpad/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(!case) + to_chat(user, "There's no implant to remove from [src].") + return + + user.put_in_hands(case) + + add_fingerprint(user) + case.add_fingerprint(user) + case = null + + updateSelfDialog() + update_icon() + +/obj/item/implantpad/attackby(obj/item/implantcase/C, mob/user, params) + if(istype(C, /obj/item/implantcase) && !case) + if(!user.transferItemToLoc(C, src)) + return + case = C + updateSelfDialog() + update_icon() + else + return ..() + +/obj/item/implantpad/ui_interact(mob/user) + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + user.unset_machine(src) + user << browse(null, "window=implantpad") + return + + user.set_machine(src) + var/dat = "Implant Mini-Computer:
                " + if(case) + if(case.imp) + if(istype(case.imp, /obj/item/implant)) + dat += case.imp.get_data() + else + dat += "The implant casing is empty." + else + dat += "Please insert an implant casing!" + user << browse(dat, "window=implantpad") + onclose(user, "implantpad") diff --git a/code/game/objects/items/implants/implantuplink.dm b/code/game/objects/items/implants/implantuplink.dm index 9000fbbe343..0cac8f838ac 100644 --- a/code/game/objects/items/implants/implantuplink.dm +++ b/code/game/objects/items/implants/implantuplink.dm @@ -1,23 +1,23 @@ -/obj/item/implant/uplink - name = "uplink implant" - desc = "Sneeki breeki." - icon = 'icons/obj/radio.dmi' - icon_state = "radio" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - var/starting_tc = 0 - -/obj/item/implant/uplink/Initialize(mapload, _owner) - . = ..() - AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc) - -/obj/item/implanter/uplink - name = "implanter (uplink)" - imp_type = /obj/item/implant/uplink - -/obj/item/implanter/uplink/precharged - name = "implanter (precharged uplink)" - imp_type = /obj/item/implant/uplink/precharged - -/obj/item/implant/uplink/precharged - starting_tc = 10 +/obj/item/implant/uplink + name = "uplink implant" + desc = "Sneeki breeki." + icon = 'icons/obj/radio.dmi' + icon_state = "radio" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + var/starting_tc = 0 + +/obj/item/implant/uplink/Initialize(mapload, _owner) + . = ..() + AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc) + +/obj/item/implanter/uplink + name = "implanter (uplink)" + imp_type = /obj/item/implant/uplink + +/obj/item/implanter/uplink/precharged + name = "implanter (precharged uplink)" + imp_type = /obj/item/implant/uplink/precharged + +/obj/item/implant/uplink/precharged + starting_tc = 10 diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm index 94c69415658..1d849b6c887 100644 --- a/code/game/objects/items/kitchen.dm +++ b/code/game/objects/items/kitchen.dm @@ -1,279 +1,279 @@ -/* Kitchen tools - * Contains: - * Fork - * Kitchen knives - * Ritual Knife - * Bloodletter - * Butcher's cleaver - * Combat Knife - * Rolling Pins - * Plastic Utensils - */ - -/obj/item/kitchen - icon = 'icons/obj/kitchen.dmi' - lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' - -/obj/item/kitchen/fork - name = "fork" - desc = "Pointy." - icon_state = "fork" - force = 5 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=80) - flags_1 = CONDUCT_1 - attack_verb = list("attacked", "stabbed", "poked") - hitsound = 'sound/weapons/bladeslice.ogg' - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - item_flags = EYE_STAB - var/datum/reagent/forkload //used to eat omelette - -/obj/item/kitchen/fork/suicide_act(mob/living/carbon/user) - user.visible_message("[user] stabs \the [src] into [user.p_their()] chest! It looks like [user.p_theyre()] trying to take a bite out of [user.p_them()]self!") - playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) - return BRUTELOSS - -/obj/item/kitchen/fork/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!istype(M)) - return ..() - - if(forkload) - if(M == user) - M.visible_message("[user] eats a delicious forkful of omelette!") - M.reagents.add_reagent(forkload.type, 1) - else - M.visible_message("[user] feeds [M] a delicious forkful of omelette!") - M.reagents.add_reagent(forkload.type, 1) - icon_state = "fork" - forkload = null - else - return ..() - -/obj/item/kitchen/fork/plastic - name = "plastic fork" - desc = "Really takes you back to highschool lunch." - icon_state = "plastic_fork" - force = 0 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - custom_materials = list(/datum/material/plastic=80) - custom_price = 50 - var/break_chance = 25 - -/obj/item/kitchen/fork/plastic/afterattack(atom/target, mob/user) - .=..() - if(prob(break_chance)) - user.visible_message("[user]'s fork snaps into tiny pieces in their hand.") - qdel(src) - -/obj/item/kitchen/knife - name = "kitchen knife" - icon_state = "knife" - inhand_icon_state = "knife" - desc = "A general purpose Chef's Knife made by SpaceCook Incorporated. Guaranteed to stay sharp for years to come." - flags_1 = CONDUCT_1 - force = 10 - w_class = WEIGHT_CLASS_SMALL - throwforce = 10 - hitsound = 'sound/weapons/bladeslice.ogg' - throw_speed = 3 - throw_range = 6 - custom_materials = list(/datum/material/iron=12000) - attack_verb = list("slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") - sharpness = IS_SHARP_ACCURATE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - item_flags = EYE_STAB - var/bayonet = FALSE //Can this be attached to a gun? - custom_price = 250 - wound_bonus = -5 - bare_wound_bonus = 10 - -/obj/item/kitchen/knife/ComponentInitialize() - . = ..() - set_butchering() - -///Adds the butchering component, used to override stats for special cases -/obj/item/kitchen/knife/proc/set_butchering() - AddComponent(/datum/component/butchering, 80 - force, 100, force - 10) //bonus chance increases depending on force - -/obj/item/kitchen/knife/suicide_act(mob/user) - user.visible_message(pick("[user] is slitting [user.p_their()] wrists with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ - "[user] is slitting [user.p_their()] throat with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ - "[user] is slitting [user.p_their()] stomach open with the [src.name]! It looks like [user.p_theyre()] trying to commit seppuku.")) - return (BRUTELOSS) - -/obj/item/kitchen/knife/plastic - name = "plastic knife" - icon_state = "plastic_knife" - inhand_icon_state = "knife" - desc = "A very safe, barely sharp knife made of plastic. Good for cutting food and not much else." - force = 0 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - throw_range = 5 - custom_materials = list(/datum/material/plastic = 100) - attack_verb = list("prodded", "whiffed","scratched", "poked") - sharpness = IS_SHARP - custom_price = 50 - var/break_chance = 25 - -/obj/item/kitchen/knife/plastic/afterattack(mob/living/carbon/user) - .=..() - if(prob(break_chance)) - user.visible_message("[user]'s knife snaps into tiny pieces in their hand.") - qdel(src) - -/obj/item/kitchen/knife/ritual - name = "ritual knife" - desc = "The unearthly energies that once powered this blade are now dormant." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/kitchen/knife/bloodletter - name = "bloodletter" - desc = "An occult looking dagger that is cold to the touch. Somehow, the flawless orb on the pommel is made entirely of liquid blood." - icon = 'icons/obj/ice_moon/artifacts.dmi' - icon_state = "bloodletter" - w_class = WEIGHT_CLASS_NORMAL - /// Bleed stacks applied when an organic mob target is hit - var/bleed_stacks_per_hit = 3 - -/obj/item/kitchen/knife/bloodletter/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - . = ..() - if(!isliving(target) || !proximity_flag) - return - var/mob/living/M = target - if(!(M.mob_biotypes & MOB_ORGANIC)) - return - var/datum/status_effect/stacking/saw_bleed/bloodletting/B = M.has_status_effect(/datum/status_effect/stacking/saw_bleed/bloodletting) - if(!B) - M.apply_status_effect(/datum/status_effect/stacking/saw_bleed/bloodletting, bleed_stacks_per_hit) - else - B.add_stacks(bleed_stacks_per_hit) - -/obj/item/kitchen/knife/butcher - name = "butcher's cleaver" - icon_state = "butch" - inhand_icon_state = "butch" - desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown by-products." - flags_1 = CONDUCT_1 - force = 15 - throwforce = 10 - custom_materials = list(/datum/material/iron=18000) - attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") - w_class = WEIGHT_CLASS_NORMAL - custom_price = 600 - -/obj/item/kitchen/knife/hunting - name = "hunting knife" - desc = "Despite its name, it's mainly used for cutting meat from dead prey rather than actual hunting." - inhand_icon_state = "huntingknife" - icon_state = "huntingknife" - -/obj/item/kitchen/knife/hunting/set_butchering() - AddComponent(/datum/component/butchering, 80 - force, 100, force + 10) - -/obj/item/kitchen/knife/combat - name = "combat knife" - icon_state = "buckknife" - desc = "A military combat utility survival knife." - embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE) - force = 20 - throwforce = 20 - attack_verb = list("slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "cut") - bayonet = TRUE - -/obj/item/kitchen/knife/combat/survival - name = "survival knife" - icon_state = "survivalknife" - embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) - desc = "A hunting grade survival knife." - force = 15 - throwforce = 15 - bayonet = TRUE - -/obj/item/kitchen/knife/combat/bone - name = "bone dagger" - inhand_icon_state = "bone_dagger" - icon_state = "bone_dagger" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A sharpened bone. The bare minimum in survival." - embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) - force = 15 - throwforce = 15 - custom_materials = null - -/obj/item/kitchen/knife/combat/cyborg - name = "cyborg knife" - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "knife_cyborg" - desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." - -/obj/item/kitchen/knife/shiv - name = "glass shiv" - icon = 'icons/obj/shards.dmi' - icon_state = "shiv" - inhand_icon_state = "shiv" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A makeshift glass shiv." - force = 8 - throwforce = 12 - attack_verb = list("shanked", "shivved") - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - custom_materials = list(/datum/material/glass=400) - -/obj/item/kitchen/knife/shiv/carrot - name = "carrot shiv" - icon_state = "carrotshiv" - inhand_icon_state = "carrotshiv" - icon = 'icons/obj/kitchen.dmi' - desc = "Unlike other carrots, you should probably keep this far away from your eyes." - custom_materials = null - -/obj/item/kitchen/knife/shiv/carrot/suicide_act(mob/living/carbon/user) - user.visible_message("[user] forcefully drives \the [src] into [user.p_their()] eye! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/kitchen/rollingpin - name = "rolling pin" - desc = "Used to knock out the Bartender." - icon_state = "rolling_pin" - force = 8 - throwforce = 5 - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - custom_price = 200 - -/obj/item/kitchen/rollingpin/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins flattening [user.p_their()] head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS -/* Trays moved to /obj/item/storage/bag */ - -/obj/item/kitchen/spoon/plastic - name = "plastic spoon" - desc = "Just be careful your food doesn't melt the spoon first." - icon_state = "plastic_spoon" - force = 0 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - custom_materials = list(/datum/material/plastic=120) - custom_price = 50 - var/break_chance = 25 - -/obj/item/kitchen/spoon/plastic/afterattack(atom/target, mob/user) - .=..() - if(prob(break_chance)) - user.visible_message("[user]'s spoon snaps into tiny pieces in their hand.") - qdel(src) +/* Kitchen tools + * Contains: + * Fork + * Kitchen knives + * Ritual Knife + * Bloodletter + * Butcher's cleaver + * Combat Knife + * Rolling Pins + * Plastic Utensils + */ + +/obj/item/kitchen + icon = 'icons/obj/kitchen.dmi' + lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' + +/obj/item/kitchen/fork + name = "fork" + desc = "Pointy." + icon_state = "fork" + force = 5 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=80) + flags_1 = CONDUCT_1 + attack_verb = list("attacked", "stabbed", "poked") + hitsound = 'sound/weapons/bladeslice.ogg' + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + item_flags = EYE_STAB + var/datum/reagent/forkload //used to eat omelette + +/obj/item/kitchen/fork/suicide_act(mob/living/carbon/user) + user.visible_message("[user] stabs \the [src] into [user.p_their()] chest! It looks like [user.p_theyre()] trying to take a bite out of [user.p_them()]self!") + playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) + return BRUTELOSS + +/obj/item/kitchen/fork/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!istype(M)) + return ..() + + if(forkload) + if(M == user) + M.visible_message("[user] eats a delicious forkful of omelette!") + M.reagents.add_reagent(forkload.type, 1) + else + M.visible_message("[user] feeds [M] a delicious forkful of omelette!") + M.reagents.add_reagent(forkload.type, 1) + icon_state = "fork" + forkload = null + else + return ..() + +/obj/item/kitchen/fork/plastic + name = "plastic fork" + desc = "Really takes you back to highschool lunch." + icon_state = "plastic_fork" + force = 0 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + custom_materials = list(/datum/material/plastic=80) + custom_price = 50 + var/break_chance = 25 + +/obj/item/kitchen/fork/plastic/afterattack(atom/target, mob/user) + .=..() + if(prob(break_chance)) + user.visible_message("[user]'s fork snaps into tiny pieces in their hand.") + qdel(src) + +/obj/item/kitchen/knife + name = "kitchen knife" + icon_state = "knife" + inhand_icon_state = "knife" + desc = "A general purpose Chef's Knife made by SpaceCook Incorporated. Guaranteed to stay sharp for years to come." + flags_1 = CONDUCT_1 + force = 10 + w_class = WEIGHT_CLASS_SMALL + throwforce = 10 + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 3 + throw_range = 6 + custom_materials = list(/datum/material/iron=12000) + attack_verb = list("slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") + sharpness = IS_SHARP_ACCURATE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + item_flags = EYE_STAB + var/bayonet = FALSE //Can this be attached to a gun? + custom_price = 250 + wound_bonus = -5 + bare_wound_bonus = 10 + +/obj/item/kitchen/knife/ComponentInitialize() + . = ..() + set_butchering() + +///Adds the butchering component, used to override stats for special cases +/obj/item/kitchen/knife/proc/set_butchering() + AddComponent(/datum/component/butchering, 80 - force, 100, force - 10) //bonus chance increases depending on force + +/obj/item/kitchen/knife/suicide_act(mob/user) + user.visible_message(pick("[user] is slitting [user.p_their()] wrists with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ + "[user] is slitting [user.p_their()] throat with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ + "[user] is slitting [user.p_their()] stomach open with the [src.name]! It looks like [user.p_theyre()] trying to commit seppuku.")) + return (BRUTELOSS) + +/obj/item/kitchen/knife/plastic + name = "plastic knife" + icon_state = "plastic_knife" + inhand_icon_state = "knife" + desc = "A very safe, barely sharp knife made of plastic. Good for cutting food and not much else." + force = 0 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + throw_range = 5 + custom_materials = list(/datum/material/plastic = 100) + attack_verb = list("prodded", "whiffed","scratched", "poked") + sharpness = IS_SHARP + custom_price = 50 + var/break_chance = 25 + +/obj/item/kitchen/knife/plastic/afterattack(mob/living/carbon/user) + .=..() + if(prob(break_chance)) + user.visible_message("[user]'s knife snaps into tiny pieces in their hand.") + qdel(src) + +/obj/item/kitchen/knife/ritual + name = "ritual knife" + desc = "The unearthly energies that once powered this blade are now dormant." + icon = 'icons/obj/wizard.dmi' + icon_state = "render" + lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/kitchen/knife/bloodletter + name = "bloodletter" + desc = "An occult looking dagger that is cold to the touch. Somehow, the flawless orb on the pommel is made entirely of liquid blood." + icon = 'icons/obj/ice_moon/artifacts.dmi' + icon_state = "bloodletter" + w_class = WEIGHT_CLASS_NORMAL + /// Bleed stacks applied when an organic mob target is hit + var/bleed_stacks_per_hit = 3 + +/obj/item/kitchen/knife/bloodletter/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!isliving(target) || !proximity_flag) + return + var/mob/living/M = target + if(!(M.mob_biotypes & MOB_ORGANIC)) + return + var/datum/status_effect/stacking/saw_bleed/bloodletting/B = M.has_status_effect(/datum/status_effect/stacking/saw_bleed/bloodletting) + if(!B) + M.apply_status_effect(/datum/status_effect/stacking/saw_bleed/bloodletting, bleed_stacks_per_hit) + else + B.add_stacks(bleed_stacks_per_hit) + +/obj/item/kitchen/knife/butcher + name = "butcher's cleaver" + icon_state = "butch" + inhand_icon_state = "butch" + desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown by-products." + flags_1 = CONDUCT_1 + force = 15 + throwforce = 10 + custom_materials = list(/datum/material/iron=18000) + attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") + w_class = WEIGHT_CLASS_NORMAL + custom_price = 600 + +/obj/item/kitchen/knife/hunting + name = "hunting knife" + desc = "Despite its name, it's mainly used for cutting meat from dead prey rather than actual hunting." + inhand_icon_state = "huntingknife" + icon_state = "huntingknife" + +/obj/item/kitchen/knife/hunting/set_butchering() + AddComponent(/datum/component/butchering, 80 - force, 100, force + 10) + +/obj/item/kitchen/knife/combat + name = "combat knife" + icon_state = "buckknife" + desc = "A military combat utility survival knife." + embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE) + force = 20 + throwforce = 20 + attack_verb = list("slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "cut") + bayonet = TRUE + +/obj/item/kitchen/knife/combat/survival + name = "survival knife" + icon_state = "survivalknife" + embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) + desc = "A hunting grade survival knife." + force = 15 + throwforce = 15 + bayonet = TRUE + +/obj/item/kitchen/knife/combat/bone + name = "bone dagger" + inhand_icon_state = "bone_dagger" + icon_state = "bone_dagger" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "A sharpened bone. The bare minimum in survival." + embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) + force = 15 + throwforce = 15 + custom_materials = null + +/obj/item/kitchen/knife/combat/cyborg + name = "cyborg knife" + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "knife_cyborg" + desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." + +/obj/item/kitchen/knife/shiv + name = "glass shiv" + icon = 'icons/obj/shards.dmi' + icon_state = "shiv" + inhand_icon_state = "shiv" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "A makeshift glass shiv." + force = 8 + throwforce = 12 + attack_verb = list("shanked", "shivved") + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + custom_materials = list(/datum/material/glass=400) + +/obj/item/kitchen/knife/shiv/carrot + name = "carrot shiv" + icon_state = "carrotshiv" + inhand_icon_state = "carrotshiv" + icon = 'icons/obj/kitchen.dmi' + desc = "Unlike other carrots, you should probably keep this far away from your eyes." + custom_materials = null + +/obj/item/kitchen/knife/shiv/carrot/suicide_act(mob/living/carbon/user) + user.visible_message("[user] forcefully drives \the [src] into [user.p_their()] eye! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/kitchen/rollingpin + name = "rolling pin" + desc = "Used to knock out the Bartender." + icon_state = "rolling_pin" + force = 8 + throwforce = 5 + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + custom_price = 200 + +/obj/item/kitchen/rollingpin/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins flattening [user.p_their()] head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS +/* Trays moved to /obj/item/storage/bag */ + +/obj/item/kitchen/spoon/plastic + name = "plastic spoon" + desc = "Just be careful your food doesn't melt the spoon first." + icon_state = "plastic_spoon" + force = 0 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + custom_materials = list(/datum/material/plastic=120) + custom_price = 50 + var/break_chance = 25 + +/obj/item/kitchen/spoon/plastic/afterattack(atom/target, mob/user) + .=..() + if(prob(break_chance)) + user.visible_message("[user]'s spoon snaps into tiny pieces in their hand.") + qdel(src) diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm index 66807264205..4951712547d 100644 --- a/code/game/objects/items/latexballoon.dm +++ b/code/game/objects/items/latexballoon.dm @@ -1,58 +1,58 @@ -/obj/item/latexballon - name = "latex glove" - desc = "Sterile and airtight." - icon_state = "latexballon" - inhand_icon_state = "lgloves" - force = 0 - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 1 - throw_range = 7 - var/state - var/datum/gas_mixture/air_contents = null - -/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) - if (icon_state == "latexballon_bursted") - return - icon_state = "latexballon_blow" - inhand_icon_state = "latexballon" - user.update_inv_hands() - to_chat(user, "You blow up [src] with [tank].") - air_contents = tank.remove_air_volume(3) - -/obj/item/latexballon/proc/burst() - if (!air_contents || icon_state != "latexballon_blow") - return - playsound(src, 'sound/weapons/gun/pistol/shot.ogg', 100, TRUE) - icon_state = "latexballon_bursted" - inhand_icon_state = "lgloves" - if(isliving(loc)) - var/mob/living/user = src.loc - user.update_inv_hands() - loc.assume_air(air_contents) - -/obj/item/latexballon/ex_act(severity, target) - burst() - switch(severity) - if (1) - qdel(src) - if (2) - if (prob(50)) - qdel(src) - -/obj/item/latexballon/bullet_act(obj/projectile/P) - if(!P.nodamage) - burst() - return ..() - -/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) - if(temperature > T0C+100) - burst() - -/obj/item/latexballon/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - var/obj/item/tank/T = W - blow(T, user) - return - if (W.get_sharpness() || W.get_temperature() || is_pointed(W)) - burst() +/obj/item/latexballon + name = "latex glove" + desc = "Sterile and airtight." + icon_state = "latexballon" + inhand_icon_state = "lgloves" + force = 0 + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 1 + throw_range = 7 + var/state + var/datum/gas_mixture/air_contents = null + +/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) + if (icon_state == "latexballon_bursted") + return + icon_state = "latexballon_blow" + inhand_icon_state = "latexballon" + user.update_inv_hands() + to_chat(user, "You blow up [src] with [tank].") + air_contents = tank.remove_air_volume(3) + +/obj/item/latexballon/proc/burst() + if (!air_contents || icon_state != "latexballon_blow") + return + playsound(src, 'sound/weapons/gun/pistol/shot.ogg', 100, TRUE) + icon_state = "latexballon_bursted" + inhand_icon_state = "lgloves" + if(isliving(loc)) + var/mob/living/user = src.loc + user.update_inv_hands() + loc.assume_air(air_contents) + +/obj/item/latexballon/ex_act(severity, target) + burst() + switch(severity) + if (1) + qdel(src) + if (2) + if (prob(50)) + qdel(src) + +/obj/item/latexballon/bullet_act(obj/projectile/P) + if(!P.nodamage) + burst() + return ..() + +/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) + if(temperature > T0C+100) + burst() + +/obj/item/latexballon/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + var/obj/item/tank/T = W + blow(T, user) + return + if (W.get_sharpness() || W.get_temperature() || is_pointed(W)) + burst() diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm index 55e57bcdee1..a0aa7e0bd98 100644 --- a/code/game/objects/items/manuals.dm +++ b/code/game/objects/items/manuals.dm @@ -1,451 +1,451 @@ -/*********************MANUALS (BOOKS)***********************/ - -//Oh god what the fuck I am not good at computer -/obj/item/book/manual - icon = 'icons/obj/library.dmi' - due_date = 0 // Game time in 1/10th seconds - unique = TRUE // FALSE - Normal book, TRUE - Should not be treated as normal book, unable to be copied, unable to be modified - -/obj/item/book/manual/hydroponics_pod_people - name = "The Human Harvest: From Seed to Market" - icon_state ="bookHydroponicsPodPeople" - author = "Farmer John" // Whoever wrote the paper or book, can be changed by pen or PC. It is not automatically assigned. - title = "The Human Harvest: From Seed to Market" - //book contents below - dat = {" - - - - - -

                Growing Humans

                - - Why would you want to grow humans? Well, I'm expecting most readers to be in the slave trade, but a few might actually - want to revive fallen comrades. Growing pod people is actually quite simple: -

                -

                  -
                1. Find a dead person who is in need of revival.
                2. -
                3. Take a blood sample with a syringe (samples of their blood taken BEFORE they died will also work).
                4. -
                5. Inject a packet of replica pod seeds (which can be acquired by either mutating cabbages into replica pods (and then harvesting said replica pods) or by purchasing them from certain corporate entities) with the blood sample.
                6. -
                7. It is imperative to understand that injecting the replica pod plant with blood AFTER it has been planted WILL NOT WORK; you have to inject the SEED PACKET, NOT the TRAY.
                8. -
                9. Plant the seeds.
                10. -
                11. Tend to the replica pod's water and nutrition levels until it is time to harvest the podcloned humanoid.
                12. -
                13. Note that if the corpse's mind (or spirit, or soul, or whatever the hell your local chaplain calls it) is already in a new body or has left this plane of existence entirely, you will just receive seed packets upon harvesting the replica pod plant, not a podperson.
                14. -
                -

                - It really is that easy! Good luck! - - - - "} - -/obj/item/book/manual/ripley_build_and_repair - name = "APLU \"Ripley\" Construction and Operation Manual" - icon_state ="book" - author = "Weyland-Yutani Corp" - title = "APLU \"Ripley\" Construction and Operation Manual" - dat = {" - - - - - -

                - Weyland-Yutani - Building Better Worlds -

                Autonomous Power Loader Unit \"Ripley\"

                -
                -

                Specifications:

                -
                  -
                • Class: Autonomous Power Loader
                • -
                • Scope: Logistics and Construction
                • -
                • Weight: 820kg (without operator and with empty cargo compartment)
                • -
                • Height: 2.5m
                • -
                • Width: 1.8m
                • -
                • Top speed: 5km/hour
                • -
                • Operation in vacuum/hostile environment: Possible -
                • Airtank Volume: 500liters
                • -
                • Devices: -
                    -
                  • Hydraulic Clamp
                  • -
                  • High-speed Drill
                  • -
                  -
                • -
                • Propulsion Device: Power cell powered electro-hydraulic system.
                • -
                • Power cell capacity: Varies.
                • -
                - -

                Construction:

                -
                  -
                1. Connect all exosuit parts to the chassis frame
                2. -
                3. Connect all hydraulic fittings and tighten them up with a wrench
                4. -
                5. Adjust the servohydraulics with a screwdriver
                6. -
                7. Wire the chassis. (Cable is not included.)
                8. -
                9. Use the wirecutters to remove the excess cable if needed.
                10. -
                11. Install the central control module (Not included. Use supplied datadisk to create one).
                12. -
                13. Secure the mainboard with a screwdriver.
                14. -
                15. Install the peripherals control module (Not included. Use supplied datadisk to create one).
                16. -
                17. Secure the peripherals control module with a screwdriver
                18. -
                19. Install the internal armor plating (Not included due to Nanotrasen regulations. Can be made using 5 metal sheets.)
                20. -
                21. Secure the internal armor plating with a wrench
                22. -
                23. Weld the internal armor plating to the chassis
                24. -
                25. Install the external reinforced armor plating (Not included due to Nanotrasen regulations. Can be made using 5 reinforced metal sheets.)
                26. -
                27. Secure the external reinforced armor plating with a wrench
                28. -
                29. Weld the external reinforced armor plating to the chassis
                30. -
                - - - -

                Operation

                - Please consult the Nanotrasen compendium "Robotics for Dummies". - "} - -/obj/item/book/manual/chef_recipes - name = "Chef Recipes" - icon_state = "cooked_book" - author = "Lord Frenrir Cageth" - title = "Chef Recipes" - dat = {" - - - - - - -

                Food for Dummies

                - Here is a guide on basic food recipes and also how to not poison your customers accidentally. - - -

                Basic ingredients preparation:

                - - Dough: 10u water + 15u flour for simple dough.
                - 15u egg yolk + 15u flour + 5u sugar for cake batter.
                - Doughs can be transformed by using a knife and rolling pin.
                - All doughs can be microwaved.
                - Bowl: Add water to it for soup preparation.
                - Meat: Microwave it, process it, slice it into microwavable cutlets with your knife, or use it raw.
                - Cheese: Add 5u universal enzyme (catalyst) to milk and soy milk to prepare cheese (sliceable) and tofu.
                - Rice: Mix 10u rice with 10u water in a bowl then microwave it. - -

                Custom food:

                - Add ingredients to a base item to prepare a custom meal.
                - The bases are:
                - - bun (burger)
                - - breadslices(sandwich)
                - - plain bread
                - - plain pie
                - - vanilla cake
                - - empty bowl (salad)
                - - bowl with 10u water (soup)
                - - boiled spaghetti
                - - pizza bread
                - - metal rod (kebab) - -

                Table Craft:

                - Put ingredients on table, then click and drag the table onto yourself to see what recipes you can prepare. - -

                Microwave:

                - Use it to cook or boil food ingredients (meats, doughs, egg, spaghetti, donkpocket, etc...). - It can cook multiple items at once. - -

                Processor:

                - Use it to process certain ingredients (meat into meatballs, doughslice into spaghetti, potato into fries,etc...) - -

                Gibber:

                - Stuff an animal in it to grind it into meat. - -

                Meat spike:

                - Stick an animal on it then begin collecting its meat. - - -

                Example recipes:

                - Vanilla Cake: Microwave cake batter.
                - Burger: 1 bun + 1 meat steak
                - Bread: Microwave dough.
                - Waffles: 2 pastry base
                - Popcorn: Microwave corn.
                - Meat Steak: Microwave meat.
                - Meat Pie: 1 plain pie + 1u black pepper + 1u salt + 2 meat cutlets
                - Boiled Spagetti: Microwave spaghetti.
                - Donuts: 1u sugar + 1 pastry base
                - Fries: Process potato. - -

                Sharing your food:

                - You can put your meals on your kitchen counter or load them in the snack vending machines. - - - "} - -/obj/item/book/manual/nuclear - name = "Fission Mailed: Nuclear Sabotage 101" - icon_state ="bookNuclear" - author = "Syndicate" - title = "Fission Mailed: Nuclear Sabotage 101" - dat = {" - - - - - Nuclear Explosives 101:
                - Hello and thank you for choosing the Syndicate for your nuclear information needs.
                - Today's crash course will deal with the operation of a Fusion Class Nanotrasen made Nuclear Device.
                - First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE.
                - Pressing any button on the compacted bomb will cause it to extend and bolt itself into place.
                - If this is done to unbolt it one must completely log in which at this time may not be possible.
                - To make the nuclear device functional:
                -
              • Place the nuclear device in the designated detonation zone.
              • -
              • Extend and anchor the nuclear device from its interface.
              • -
              • Insert the nuclear authorisation disk into slot.
              • -
              • Type numeric authorisation code into the keypad. This should have been provided. Note: If you make a mistake press R to reset the device. -
              • Press the E button to log onto the device.
              • - You now have activated the device. To deactivate the buttons at anytime for example when you've already prepped the bomb for detonation remove the auth disk OR press the R on the keypad.
                - Now the bomb CAN ONLY be detonated using the timer. Manual detonation is not an option.
                - Note: Nanotrasen is a pain in the neck.
                - Toggle off the SAFETY.
                - Note: You wouldn't believe how many Syndicate Operatives with doctorates have forgotten this step.
                - So use the - - and + + to set a det time between 5 seconds and 10 minutes.
                - Then press the timer toggle button to start the countdown.
                - Now remove the auth. disk so that the buttons deactivate.
                - Note: THE BOMB IS STILL SET AND WILL DETONATE
                - Now before you remove the disk if you need to move the bomb you can:
                - Toggle off the anchor, move it, and re-anchor.

                - Good luck. Remember the order:
                - Disk, Code, Safety, Timer, Disk, RUN!
                - Intelligence Analysts believe that normal Nanotrasen procedure is for the Captain to secure the nuclear authorisation disk.
                - Good luck! - - "} - -// Wiki books that are linked to the configured wiki link. - -// A book that links to the wiki -/obj/item/book/manual/wiki - var/page_link = "" - window_size = "970x710" - -/obj/item/book/manual/wiki/attack_self() - if(!dat) - initialize_wikibook() - return ..() - -/obj/item/book/manual/wiki/proc/initialize_wikibook() - var/wikiurl = CONFIG_GET(string/wikiurl) - if(wikiurl) - dat = {" - - - - - - - - -

                You start skimming through the manual...

                - - - - - - "} - -/obj/item/book/manual/wiki/chemistry - name = "Chemistry Textbook" - icon_state ="chemistrybook" - author = "Nanotrasen" - title = "Chemistry Textbook" - page_link = "Guide_to_chemistry" - -/obj/item/book/manual/wiki/engineering_construction - name = "Station Repairs and Construction" - icon_state ="bookEngineering" - author = "Engineering Encyclopedia" - title = "Station Repairs and Construction" - page_link = "Guide_to_construction" - -/obj/item/book/manual/wiki/engineering_guide - name = "Engineering Textbook" - icon_state ="bookEngineering2" - author = "Engineering Encyclopedia" - title = "Engineering Textbook" - page_link = "Guide_to_engineering" - -/obj/item/book/manual/wiki/engineering_singulo_tesla - name = "Singularity and Tesla for Dummies" - icon_state ="bookEngineeringSingularitySafety" - author = "Engineering Encyclopedia" - title = "Singularity and Tesla for Dummies" - page_link = "Singularity_and_Tesla_engines" - -/obj/item/book/manual/wiki/security_space_law - name = "Space Law" - desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." - icon_state = "bookSpaceLaw" - author = "Nanotrasen" - title = "Space Law" - page_link = "Space_Law" - -/obj/item/book/manual/wiki/security_space_law/suicide_act(mob/living/user) - user.visible_message("[user] pretends to read \the [src] intently... then promptly dies of laughter!") - return OXYLOSS - -/obj/item/book/manual/wiki/infections - name = "Infections - Making your own pandemic!" - icon_state = "bookInfections" - author = "Infections Encyclopedia" - title = "Infections - Making your own pandemic!" - page_link = "Infections" - -/obj/item/book/manual/wiki/telescience - name = "Teleportation Science - Bluespace for dummies!" - icon_state = "book7" - author = "University of Bluespace" - title = "Teleportation Science - Bluespace for dummies!" - page_link = "Guide_to_telescience" - -/obj/item/book/manual/wiki/engineering_hacking - name = "Hacking" - icon_state ="bookHacking" - author = "Engineering Encyclopedia" - title = "Hacking" - page_link = "Hacking" - -/obj/item/book/manual/wiki/detective - name = "The Film Noir: Proper Procedures for Investigations" - icon_state ="bookDetective" - author = "Nanotrasen" - title = "The Film Noir: Proper Procedures for Investigations" - page_link = "Detective" - -/obj/item/book/manual/wiki/barman_recipes - name = "Barman Recipes: Mixing Drinks and Changing Lives" - icon_state = "barbook" - author = "Sir John Rose" - title = "Barman Recipes: Mixing Drinks and Changing Lives" - page_link = "Guide_to_food_and_drinks" - -/obj/item/book/manual/wiki/robotics_cyborgs - name = "Robotics for Dummies" - icon_state = "borgbook" - author = "XISC" - title = "Robotics for Dummies" - page_link = "Guide_to_robotics" - -/obj/item/book/manual/wiki/research_and_development - name = "Research and Development 101" - icon_state = "rdbook" - author = "Dr. L. Ight" - title = "Research and Development 101" - page_link = "Guide_to_Research_and_Development" - -/obj/item/book/manual/wiki/experimentor - name = "Mentoring your Experiments" - icon_state = "rdbook" - author = "Dr. H.P. Kritz" - title = "Mentoring your Experiments" - page_link = "Experimentor" - -/obj/item/book/manual/wiki/cooking_to_serve_man - name = "To Serve Man" - desc = "It's a cookbook!" - icon_state ="cooked_book" - author = "the Kanamitan Empire" - title = "To Serve Man" - page_link = "Guide_to_food_and_drinks" - -/obj/item/book/manual/wiki/tcomms - name = "Subspace Telecommunications And You" - icon_state = "book3" - author = "Engineering Encyclopedia" - title = "Subspace Telecommunications And You" - page_link = "Guide_to_Telecommunications" - -/obj/item/book/manual/wiki/atmospherics - name = "Lexica Atmosia" - icon_state = "book5" - author = "the City-state of Atmosia" - title = "Lexica Atmosia" - page_link = "Guide_to_Atmospherics" - -/obj/item/book/manual/wiki/medicine - name = "Medical Space Compendium, Volume 638" - icon_state = "book8" - author = "Medical Journal" - title = "Medical Space Compendium, Volume 638" - page_link = "Guide_to_medicine" - -/obj/item/book/manual/wiki/surgery - name = "Brain Surgery for Dummies" - icon_state = "book4" - author = "Dr. F. Fran" - title = "Brain Surgery for Dummies" - page_link = "Surgery" - -/obj/item/book/manual/wiki/grenades - name = "DIY Chemical Grenades" - icon_state = "book2" - author = "W. Powell" - title = "DIY Chemical Grenades" - page_link = "Grenade" - -/obj/item/book/manual/wiki/toxins - name = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" - icon_state = "book6" - author = "Cuban Pete" - title = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" - page_link = "Guide_to_toxins" - -/obj/item/book/manual/wiki/toxins/suicide_act(mob/user) - var/mob/living/carbon/human/H = user - user.visible_message("[user] starts dancing to the Rhumba Beat! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) - if (!QDELETED(H)) - H.emote("spin") - sleep(20) - for(var/obj/item/W in H) - H.dropItemToGround(W) - if(prob(50)) - step(W, pick(GLOB.alldirs)) - ADD_TRAIT(H, TRAIT_DISFIGURED, TRAIT_GENERIC) - for(var/i in H.bodyparts) - var/obj/item/bodypart/BP = i - BP.generic_bleedstacks += 5 - H.gib_animation() - sleep(3) - H.adjustBruteLoss(1000) //to make the body super-bloody - H.spawn_gibs() - H.spill_organs() - H.spread_bodyparts() - return (BRUTELOSS) - -/obj/item/book/manual/wiki/plumbing - name = "Chemical Factories Without Narcotics" - icon_state ="plumbingbook" - author = "Nanotrasen" - title = "Chemical Factories Without Narcotics" - page_link = "Guide_to_plumbing" +/*********************MANUALS (BOOKS)***********************/ + +//Oh god what the fuck I am not good at computer +/obj/item/book/manual + icon = 'icons/obj/library.dmi' + due_date = 0 // Game time in 1/10th seconds + unique = TRUE // FALSE - Normal book, TRUE - Should not be treated as normal book, unable to be copied, unable to be modified + +/obj/item/book/manual/hydroponics_pod_people + name = "The Human Harvest: From Seed to Market" + icon_state ="bookHydroponicsPodPeople" + author = "Farmer John" // Whoever wrote the paper or book, can be changed by pen or PC. It is not automatically assigned. + title = "The Human Harvest: From Seed to Market" + //book contents below + dat = {" + + + + + +

                Growing Humans

                + + Why would you want to grow humans? Well, I'm expecting most readers to be in the slave trade, but a few might actually + want to revive fallen comrades. Growing pod people is actually quite simple: +

                +

                  +
                1. Find a dead person who is in need of revival.
                2. +
                3. Take a blood sample with a syringe (samples of their blood taken BEFORE they died will also work).
                4. +
                5. Inject a packet of replica pod seeds (which can be acquired by either mutating cabbages into replica pods (and then harvesting said replica pods) or by purchasing them from certain corporate entities) with the blood sample.
                6. +
                7. It is imperative to understand that injecting the replica pod plant with blood AFTER it has been planted WILL NOT WORK; you have to inject the SEED PACKET, NOT the TRAY.
                8. +
                9. Plant the seeds.
                10. +
                11. Tend to the replica pod's water and nutrition levels until it is time to harvest the podcloned humanoid.
                12. +
                13. Note that if the corpse's mind (or spirit, or soul, or whatever the hell your local chaplain calls it) is already in a new body or has left this plane of existence entirely, you will just receive seed packets upon harvesting the replica pod plant, not a podperson.
                14. +
                +

                + It really is that easy! Good luck! + + + + "} + +/obj/item/book/manual/ripley_build_and_repair + name = "APLU \"Ripley\" Construction and Operation Manual" + icon_state ="book" + author = "Weyland-Yutani Corp" + title = "APLU \"Ripley\" Construction and Operation Manual" + dat = {" + + + + + +

                + Weyland-Yutani - Building Better Worlds +

                Autonomous Power Loader Unit \"Ripley\"

                +
                +

                Specifications:

                +
                  +
                • Class: Autonomous Power Loader
                • +
                • Scope: Logistics and Construction
                • +
                • Weight: 820kg (without operator and with empty cargo compartment)
                • +
                • Height: 2.5m
                • +
                • Width: 1.8m
                • +
                • Top speed: 5km/hour
                • +
                • Operation in vacuum/hostile environment: Possible +
                • Airtank Volume: 500liters
                • +
                • Devices: +
                    +
                  • Hydraulic Clamp
                  • +
                  • High-speed Drill
                  • +
                  +
                • +
                • Propulsion Device: Power cell powered electro-hydraulic system.
                • +
                • Power cell capacity: Varies.
                • +
                + +

                Construction:

                +
                  +
                1. Connect all exosuit parts to the chassis frame
                2. +
                3. Connect all hydraulic fittings and tighten them up with a wrench
                4. +
                5. Adjust the servohydraulics with a screwdriver
                6. +
                7. Wire the chassis. (Cable is not included.)
                8. +
                9. Use the wirecutters to remove the excess cable if needed.
                10. +
                11. Install the central control module (Not included. Use supplied datadisk to create one).
                12. +
                13. Secure the mainboard with a screwdriver.
                14. +
                15. Install the peripherals control module (Not included. Use supplied datadisk to create one).
                16. +
                17. Secure the peripherals control module with a screwdriver
                18. +
                19. Install the internal armor plating (Not included due to Nanotrasen regulations. Can be made using 5 metal sheets.)
                20. +
                21. Secure the internal armor plating with a wrench
                22. +
                23. Weld the internal armor plating to the chassis
                24. +
                25. Install the external reinforced armor plating (Not included due to Nanotrasen regulations. Can be made using 5 reinforced metal sheets.)
                26. +
                27. Secure the external reinforced armor plating with a wrench
                28. +
                29. Weld the external reinforced armor plating to the chassis
                30. +
                + + + +

                Operation

                + Please consult the Nanotrasen compendium "Robotics for Dummies". + "} + +/obj/item/book/manual/chef_recipes + name = "Chef Recipes" + icon_state = "cooked_book" + author = "Lord Frenrir Cageth" + title = "Chef Recipes" + dat = {" + + + + + + +

                Food for Dummies

                + Here is a guide on basic food recipes and also how to not poison your customers accidentally. + + +

                Basic ingredients preparation:

                + + Dough: 10u water + 15u flour for simple dough.
                + 15u egg yolk + 15u flour + 5u sugar for cake batter.
                + Doughs can be transformed by using a knife and rolling pin.
                + All doughs can be microwaved.
                + Bowl: Add water to it for soup preparation.
                + Meat: Microwave it, process it, slice it into microwavable cutlets with your knife, or use it raw.
                + Cheese: Add 5u universal enzyme (catalyst) to milk and soy milk to prepare cheese (sliceable) and tofu.
                + Rice: Mix 10u rice with 10u water in a bowl then microwave it. + +

                Custom food:

                + Add ingredients to a base item to prepare a custom meal.
                + The bases are:
                + - bun (burger)
                + - breadslices(sandwich)
                + - plain bread
                + - plain pie
                + - vanilla cake
                + - empty bowl (salad)
                + - bowl with 10u water (soup)
                + - boiled spaghetti
                + - pizza bread
                + - metal rod (kebab) + +

                Table Craft:

                + Put ingredients on table, then click and drag the table onto yourself to see what recipes you can prepare. + +

                Microwave:

                + Use it to cook or boil food ingredients (meats, doughs, egg, spaghetti, donkpocket, etc...). + It can cook multiple items at once. + +

                Processor:

                + Use it to process certain ingredients (meat into meatballs, doughslice into spaghetti, potato into fries,etc...) + +

                Gibber:

                + Stuff an animal in it to grind it into meat. + +

                Meat spike:

                + Stick an animal on it then begin collecting its meat. + + +

                Example recipes:

                + Vanilla Cake: Microwave cake batter.
                + Burger: 1 bun + 1 meat steak
                + Bread: Microwave dough.
                + Waffles: 2 pastry base
                + Popcorn: Microwave corn.
                + Meat Steak: Microwave meat.
                + Meat Pie: 1 plain pie + 1u black pepper + 1u salt + 2 meat cutlets
                + Boiled Spagetti: Microwave spaghetti.
                + Donuts: 1u sugar + 1 pastry base
                + Fries: Process potato. + +

                Sharing your food:

                + You can put your meals on your kitchen counter or load them in the snack vending machines. + + + "} + +/obj/item/book/manual/nuclear + name = "Fission Mailed: Nuclear Sabotage 101" + icon_state ="bookNuclear" + author = "Syndicate" + title = "Fission Mailed: Nuclear Sabotage 101" + dat = {" + + + + + Nuclear Explosives 101:
                + Hello and thank you for choosing the Syndicate for your nuclear information needs.
                + Today's crash course will deal with the operation of a Fusion Class Nanotrasen made Nuclear Device.
                + First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE.
                + Pressing any button on the compacted bomb will cause it to extend and bolt itself into place.
                + If this is done to unbolt it one must completely log in which at this time may not be possible.
                + To make the nuclear device functional:
                +
              • Place the nuclear device in the designated detonation zone.
              • +
              • Extend and anchor the nuclear device from its interface.
              • +
              • Insert the nuclear authorisation disk into slot.
              • +
              • Type numeric authorisation code into the keypad. This should have been provided. Note: If you make a mistake press R to reset the device. +
              • Press the E button to log onto the device.
              • + You now have activated the device. To deactivate the buttons at anytime for example when you've already prepped the bomb for detonation remove the auth disk OR press the R on the keypad.
                + Now the bomb CAN ONLY be detonated using the timer. Manual detonation is not an option.
                + Note: Nanotrasen is a pain in the neck.
                + Toggle off the SAFETY.
                + Note: You wouldn't believe how many Syndicate Operatives with doctorates have forgotten this step.
                + So use the - - and + + to set a det time between 5 seconds and 10 minutes.
                + Then press the timer toggle button to start the countdown.
                + Now remove the auth. disk so that the buttons deactivate.
                + Note: THE BOMB IS STILL SET AND WILL DETONATE
                + Now before you remove the disk if you need to move the bomb you can:
                + Toggle off the anchor, move it, and re-anchor.

                + Good luck. Remember the order:
                + Disk, Code, Safety, Timer, Disk, RUN!
                + Intelligence Analysts believe that normal Nanotrasen procedure is for the Captain to secure the nuclear authorisation disk.
                + Good luck! + + "} + +// Wiki books that are linked to the configured wiki link. + +// A book that links to the wiki +/obj/item/book/manual/wiki + var/page_link = "" + window_size = "970x710" + +/obj/item/book/manual/wiki/attack_self() + if(!dat) + initialize_wikibook() + return ..() + +/obj/item/book/manual/wiki/proc/initialize_wikibook() + var/wikiurl = CONFIG_GET(string/wikiurl) + if(wikiurl) + dat = {" + + + + + + + + +

                You start skimming through the manual...

                + + + + + + "} + +/obj/item/book/manual/wiki/chemistry + name = "Chemistry Textbook" + icon_state ="chemistrybook" + author = "Nanotrasen" + title = "Chemistry Textbook" + page_link = "Guide_to_chemistry" + +/obj/item/book/manual/wiki/engineering_construction + name = "Station Repairs and Construction" + icon_state ="bookEngineering" + author = "Engineering Encyclopedia" + title = "Station Repairs and Construction" + page_link = "Guide_to_construction" + +/obj/item/book/manual/wiki/engineering_guide + name = "Engineering Textbook" + icon_state ="bookEngineering2" + author = "Engineering Encyclopedia" + title = "Engineering Textbook" + page_link = "Guide_to_engineering" + +/obj/item/book/manual/wiki/engineering_singulo_tesla + name = "Singularity and Tesla for Dummies" + icon_state ="bookEngineeringSingularitySafety" + author = "Engineering Encyclopedia" + title = "Singularity and Tesla for Dummies" + page_link = "Singularity_and_Tesla_engines" + +/obj/item/book/manual/wiki/security_space_law + name = "Space Law" + desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." + icon_state = "bookSpaceLaw" + author = "Nanotrasen" + title = "Space Law" + page_link = "Space_Law" + +/obj/item/book/manual/wiki/security_space_law/suicide_act(mob/living/user) + user.visible_message("[user] pretends to read \the [src] intently... then promptly dies of laughter!") + return OXYLOSS + +/obj/item/book/manual/wiki/infections + name = "Infections - Making your own pandemic!" + icon_state = "bookInfections" + author = "Infections Encyclopedia" + title = "Infections - Making your own pandemic!" + page_link = "Infections" + +/obj/item/book/manual/wiki/telescience + name = "Teleportation Science - Bluespace for dummies!" + icon_state = "book7" + author = "University of Bluespace" + title = "Teleportation Science - Bluespace for dummies!" + page_link = "Guide_to_telescience" + +/obj/item/book/manual/wiki/engineering_hacking + name = "Hacking" + icon_state ="bookHacking" + author = "Engineering Encyclopedia" + title = "Hacking" + page_link = "Hacking" + +/obj/item/book/manual/wiki/detective + name = "The Film Noir: Proper Procedures for Investigations" + icon_state ="bookDetective" + author = "Nanotrasen" + title = "The Film Noir: Proper Procedures for Investigations" + page_link = "Detective" + +/obj/item/book/manual/wiki/barman_recipes + name = "Barman Recipes: Mixing Drinks and Changing Lives" + icon_state = "barbook" + author = "Sir John Rose" + title = "Barman Recipes: Mixing Drinks and Changing Lives" + page_link = "Guide_to_food_and_drinks" + +/obj/item/book/manual/wiki/robotics_cyborgs + name = "Robotics for Dummies" + icon_state = "borgbook" + author = "XISC" + title = "Robotics for Dummies" + page_link = "Guide_to_robotics" + +/obj/item/book/manual/wiki/research_and_development + name = "Research and Development 101" + icon_state = "rdbook" + author = "Dr. L. Ight" + title = "Research and Development 101" + page_link = "Guide_to_Research_and_Development" + +/obj/item/book/manual/wiki/experimentor + name = "Mentoring your Experiments" + icon_state = "rdbook" + author = "Dr. H.P. Kritz" + title = "Mentoring your Experiments" + page_link = "Experimentor" + +/obj/item/book/manual/wiki/cooking_to_serve_man + name = "To Serve Man" + desc = "It's a cookbook!" + icon_state ="cooked_book" + author = "the Kanamitan Empire" + title = "To Serve Man" + page_link = "Guide_to_food_and_drinks" + +/obj/item/book/manual/wiki/tcomms + name = "Subspace Telecommunications And You" + icon_state = "book3" + author = "Engineering Encyclopedia" + title = "Subspace Telecommunications And You" + page_link = "Guide_to_Telecommunications" + +/obj/item/book/manual/wiki/atmospherics + name = "Lexica Atmosia" + icon_state = "book5" + author = "the City-state of Atmosia" + title = "Lexica Atmosia" + page_link = "Guide_to_Atmospherics" + +/obj/item/book/manual/wiki/medicine + name = "Medical Space Compendium, Volume 638" + icon_state = "book8" + author = "Medical Journal" + title = "Medical Space Compendium, Volume 638" + page_link = "Guide_to_medicine" + +/obj/item/book/manual/wiki/surgery + name = "Brain Surgery for Dummies" + icon_state = "book4" + author = "Dr. F. Fran" + title = "Brain Surgery for Dummies" + page_link = "Surgery" + +/obj/item/book/manual/wiki/grenades + name = "DIY Chemical Grenades" + icon_state = "book2" + author = "W. Powell" + title = "DIY Chemical Grenades" + page_link = "Grenade" + +/obj/item/book/manual/wiki/toxins + name = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" + icon_state = "book6" + author = "Cuban Pete" + title = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" + page_link = "Guide_to_toxins" + +/obj/item/book/manual/wiki/toxins/suicide_act(mob/user) + var/mob/living/carbon/human/H = user + user.visible_message("[user] starts dancing to the Rhumba Beat! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) + if (!QDELETED(H)) + H.emote("spin") + sleep(20) + for(var/obj/item/W in H) + H.dropItemToGround(W) + if(prob(50)) + step(W, pick(GLOB.alldirs)) + ADD_TRAIT(H, TRAIT_DISFIGURED, TRAIT_GENERIC) + for(var/i in H.bodyparts) + var/obj/item/bodypart/BP = i + BP.generic_bleedstacks += 5 + H.gib_animation() + sleep(3) + H.adjustBruteLoss(1000) //to make the body super-bloody + H.spawn_gibs() + H.spill_organs() + H.spread_bodyparts() + return (BRUTELOSS) + +/obj/item/book/manual/wiki/plumbing + name = "Chemical Factories Without Narcotics" + icon_state ="plumbingbook" + author = "Nanotrasen" + title = "Chemical Factories Without Narcotics" + page_link = "Guide_to_plumbing" diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index da3ea4930be..a25a7d607b1 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -1,238 +1,238 @@ -/obj/item/melee/transforming/energy - icon = 'icons/obj/transforming_energy.dmi' - hitsound_on = 'sound/weapons/blade1.ogg' - heat = 3500 - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) - resistance_flags = FIRE_PROOF - var/brightness_on = 3 - var/sword_color - -/obj/item/melee/transforming/energy/Initialize() - . = ..() - if(active) - set_light(brightness_on) - START_PROCESSING(SSobj, src) - -/obj/item/melee/transforming/energy/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/melee/transforming/energy/suicide_act(mob/user) - if(!active) - transform_weapon(user, TRUE) - user.visible_message("[user] is [pick("slitting [user.p_their()] stomach open with", "falling on")] [src]! It looks like [user.p_theyre()] trying to commit seppuku!") - return (BRUTELOSS|FIRELOSS) - -/obj/item/melee/transforming/energy/add_blood_DNA(list/blood_dna) - return FALSE - -/obj/item/melee/transforming/energy/get_sharpness() - return active * sharpness - -/obj/item/melee/transforming/energy/process() - open_flame() - -/obj/item/melee/transforming/energy/transform_weapon(mob/living/user, supress_message_text) - . = ..() - if(.) - if(active) - if(sword_color) - icon_state = "sword[sword_color]" - START_PROCESSING(SSobj, src) - set_light(brightness_on) - else - STOP_PROCESSING(SSobj, src) - set_light(0) - -/obj/item/melee/transforming/energy/get_temperature() - return active * heat - -/obj/item/melee/transforming/energy/ignition_effect(atom/A, mob/user) - if(!active) - return "" - - var/in_mouth = "" - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(C.wear_mask) - in_mouth = ", barely missing [C.p_their()] nose" - . = "[user] swings [user.p_their()] [name][in_mouth]. [user.p_they(TRUE)] light[user.p_s()] [user.p_their()] [A.name] in the process." - playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) - add_fingerprint(user) - -/obj/item/melee/transforming/energy/axe - name = "energy axe" - desc = "An energized battle axe." - icon_state = "axe0" - lefthand_file = 'icons/mob/inhands/weapons/axes_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/axes_righthand.dmi' - force = 40 - force_on = 150 - throwforce = 25 - throwforce_on = 30 - hitsound = 'sound/weapons/bladeslice.ogg' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL - w_class_on = WEIGHT_CLASS_HUGE - flags_1 = CONDUCT_1 - armour_penetration = 100 - attack_verb_off = list("attacked", "chopped", "cleaved", "tore", "lacerated", "cut") - attack_verb_on = list() - light_color = "#40ceff" - -/obj/item/melee/transforming/energy/axe/suicide_act(mob/user) - user.visible_message("[user] swings [src] towards [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS|FIRELOSS) - -/obj/item/melee/transforming/energy/sword - name = "energy sword" - desc = "May the force be within you." - icon_state = "sword0" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - force = 3 - throwforce = 5 - hitsound = "swing_hit" //it starts deactivated - attack_verb_off = list("tapped", "poked") - throw_speed = 3 - throw_range = 5 - sharpness = IS_SHARP - embedding = list("embed_chance" = 75, "impact_pain_mult" = 10) - armour_penetration = 35 - block_chance = 50 - -/obj/item/melee/transforming/energy/sword/transform_weapon(mob/living/user, supress_message_text) - . = ..() - if(. && active && sword_color) - icon_state = "sword[sword_color]" - -/obj/item/melee/transforming/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/melee/transforming/energy/sword/cyborg - sword_color = "red" - var/hitcost = 50 - -/obj/item/melee/transforming/energy/sword/cyborg/attack(mob/M, mob/living/silicon/robot/R) - if(R.cell) - var/obj/item/stock_parts/cell/C = R.cell - if(active && !(C.use(hitcost))) - attack_self(R) - to_chat(R, "It's out of charge!") - return - return ..() - -/obj/item/melee/transforming/energy/sword/cyborg/saw //Used by medical Syndicate cyborgs - name = "energy saw" - desc = "For heavy duty cutting. It has a carbon-fiber blade in addition to a toggleable hard-light edge to dramatically increase sharpness." - force_on = 30 - force = 18 //About as much as a spear - hitsound = 'sound/weapons/circsawhit.ogg' - icon = 'icons/obj/surgery.dmi' - icon_state = "esaw_0" - icon_state_on = "esaw_1" - sword_color = null //stops icon from breaking when turned on. - hitcost = 75 //Costs more than a standard cyborg esword - w_class = WEIGHT_CLASS_NORMAL - sharpness = IS_SHARP - light_color = "#40ceff" - tool_behaviour = TOOL_SAW - toolspeed = 0.7 //faster as a saw - -/obj/item/melee/transforming/energy/sword/cyborg/saw/cyborg_unequip(mob/user) - if(!active) - return - transform_weapon(user, TRUE) - -/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/melee/transforming/energy/sword/saber - var/list/possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER) - var/hacked = FALSE - -/obj/item/melee/transforming/energy/sword/saber/Initialize(mapload) - . = ..() - if(LAZYLEN(possible_colors)) - var/set_color = pick(possible_colors) - sword_color = set_color - light_color = possible_colors[set_color] - -/obj/item/melee/transforming/energy/sword/saber/process() - . = ..() - if(hacked) - var/set_color = pick(possible_colors) - light_color = possible_colors[set_color] - update_light() - -/obj/item/melee/transforming/energy/sword/saber/red - possible_colors = list("red" = LIGHT_COLOR_RED) - -/obj/item/melee/transforming/energy/sword/saber/blue - possible_colors = list("blue" = LIGHT_COLOR_LIGHT_CYAN) - -/obj/item/melee/transforming/energy/sword/saber/green - possible_colors = list("green" = LIGHT_COLOR_GREEN) - -/obj/item/melee/transforming/energy/sword/saber/purple - possible_colors = list("purple" = LIGHT_COLOR_LAVENDER) - -/obj/item/melee/transforming/energy/sword/saber/attackby(obj/item/W, mob/living/user, params) - if(W.tool_behaviour == TOOL_MULTITOOL) - if(!hacked) - hacked = TRUE - sword_color = "rainbow" - to_chat(user, "RNBW_ENGAGE") - - if(active) - icon_state = "swordrainbow" - user.update_inv_hands() - else - to_chat(user, "It's already fabulous!") - else - return ..() - -/obj/item/melee/transforming/energy/sword/pirate - name = "energy cutlass" - desc = "Arrrr matey." - icon_state = "cutlass0" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - icon_state_on = "cutlass1" - light_color = "#ff0000" - -/obj/item/melee/transforming/energy/blade - name = "energy blade" - desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." - icon_state = "blade" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - force = 30 //Normal attacks deal esword damage - hitsound = 'sound/weapons/blade1.ogg' - active = 1 - throwforce = 1 //Throwing or dropping the item deletes it. - throw_speed = 3 - throw_range = 1 - w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such. - var/datum/effect_system/spark_spread/spark_system - sharpness = IS_SHARP - -//Most of the other special functions are handled in their own files. aka special snowflake code so kewl -/obj/item/melee/transforming/energy/blade/Initialize() - . = ..() - spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - -/obj/item/melee/transforming/energy/blade/transform_weapon(mob/living/user, supress_message_text) - return - -/obj/item/melee/transforming/energy/blade/hardlight - name = "hardlight blade" - desc = "An extremely sharp blade made out of hard light. Packs quite a punch." - icon_state = "lightblade" - inhand_icon_state = "lightblade" +/obj/item/melee/transforming/energy + icon = 'icons/obj/transforming_energy.dmi' + hitsound_on = 'sound/weapons/blade1.ogg' + heat = 3500 + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) + resistance_flags = FIRE_PROOF + var/brightness_on = 3 + var/sword_color + +/obj/item/melee/transforming/energy/Initialize() + . = ..() + if(active) + set_light(brightness_on) + START_PROCESSING(SSobj, src) + +/obj/item/melee/transforming/energy/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/melee/transforming/energy/suicide_act(mob/user) + if(!active) + transform_weapon(user, TRUE) + user.visible_message("[user] is [pick("slitting [user.p_their()] stomach open with", "falling on")] [src]! It looks like [user.p_theyre()] trying to commit seppuku!") + return (BRUTELOSS|FIRELOSS) + +/obj/item/melee/transforming/energy/add_blood_DNA(list/blood_dna) + return FALSE + +/obj/item/melee/transforming/energy/get_sharpness() + return active * sharpness + +/obj/item/melee/transforming/energy/process() + open_flame() + +/obj/item/melee/transforming/energy/transform_weapon(mob/living/user, supress_message_text) + . = ..() + if(.) + if(active) + if(sword_color) + icon_state = "sword[sword_color]" + START_PROCESSING(SSobj, src) + set_light(brightness_on) + else + STOP_PROCESSING(SSobj, src) + set_light(0) + +/obj/item/melee/transforming/energy/get_temperature() + return active * heat + +/obj/item/melee/transforming/energy/ignition_effect(atom/A, mob/user) + if(!active) + return "" + + var/in_mouth = "" + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(C.wear_mask) + in_mouth = ", barely missing [C.p_their()] nose" + . = "[user] swings [user.p_their()] [name][in_mouth]. [user.p_they(TRUE)] light[user.p_s()] [user.p_their()] [A.name] in the process." + playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) + add_fingerprint(user) + +/obj/item/melee/transforming/energy/axe + name = "energy axe" + desc = "An energized battle axe." + icon_state = "axe0" + lefthand_file = 'icons/mob/inhands/weapons/axes_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/axes_righthand.dmi' + force = 40 + force_on = 150 + throwforce = 25 + throwforce_on = 30 + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + w_class_on = WEIGHT_CLASS_HUGE + flags_1 = CONDUCT_1 + armour_penetration = 100 + attack_verb_off = list("attacked", "chopped", "cleaved", "tore", "lacerated", "cut") + attack_verb_on = list() + light_color = "#40ceff" + +/obj/item/melee/transforming/energy/axe/suicide_act(mob/user) + user.visible_message("[user] swings [src] towards [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS|FIRELOSS) + +/obj/item/melee/transforming/energy/sword + name = "energy sword" + desc = "May the force be within you." + icon_state = "sword0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + force = 3 + throwforce = 5 + hitsound = "swing_hit" //it starts deactivated + attack_verb_off = list("tapped", "poked") + throw_speed = 3 + throw_range = 5 + sharpness = IS_SHARP + embedding = list("embed_chance" = 75, "impact_pain_mult" = 10) + armour_penetration = 35 + block_chance = 50 + +/obj/item/melee/transforming/energy/sword/transform_weapon(mob/living/user, supress_message_text) + . = ..() + if(. && active && sword_color) + icon_state = "sword[sword_color]" + +/obj/item/melee/transforming/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/melee/transforming/energy/sword/cyborg + sword_color = "red" + var/hitcost = 50 + +/obj/item/melee/transforming/energy/sword/cyborg/attack(mob/M, mob/living/silicon/robot/R) + if(R.cell) + var/obj/item/stock_parts/cell/C = R.cell + if(active && !(C.use(hitcost))) + attack_self(R) + to_chat(R, "It's out of charge!") + return + return ..() + +/obj/item/melee/transforming/energy/sword/cyborg/saw //Used by medical Syndicate cyborgs + name = "energy saw" + desc = "For heavy duty cutting. It has a carbon-fiber blade in addition to a toggleable hard-light edge to dramatically increase sharpness." + force_on = 30 + force = 18 //About as much as a spear + hitsound = 'sound/weapons/circsawhit.ogg' + icon = 'icons/obj/surgery.dmi' + icon_state = "esaw_0" + icon_state_on = "esaw_1" + sword_color = null //stops icon from breaking when turned on. + hitcost = 75 //Costs more than a standard cyborg esword + w_class = WEIGHT_CLASS_NORMAL + sharpness = IS_SHARP + light_color = "#40ceff" + tool_behaviour = TOOL_SAW + toolspeed = 0.7 //faster as a saw + +/obj/item/melee/transforming/energy/sword/cyborg/saw/cyborg_unequip(mob/user) + if(!active) + return + transform_weapon(user, TRUE) + +/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/melee/transforming/energy/sword/saber + var/list/possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER) + var/hacked = FALSE + +/obj/item/melee/transforming/energy/sword/saber/Initialize(mapload) + . = ..() + if(LAZYLEN(possible_colors)) + var/set_color = pick(possible_colors) + sword_color = set_color + light_color = possible_colors[set_color] + +/obj/item/melee/transforming/energy/sword/saber/process() + . = ..() + if(hacked) + var/set_color = pick(possible_colors) + light_color = possible_colors[set_color] + update_light() + +/obj/item/melee/transforming/energy/sword/saber/red + possible_colors = list("red" = LIGHT_COLOR_RED) + +/obj/item/melee/transforming/energy/sword/saber/blue + possible_colors = list("blue" = LIGHT_COLOR_LIGHT_CYAN) + +/obj/item/melee/transforming/energy/sword/saber/green + possible_colors = list("green" = LIGHT_COLOR_GREEN) + +/obj/item/melee/transforming/energy/sword/saber/purple + possible_colors = list("purple" = LIGHT_COLOR_LAVENDER) + +/obj/item/melee/transforming/energy/sword/saber/attackby(obj/item/W, mob/living/user, params) + if(W.tool_behaviour == TOOL_MULTITOOL) + if(!hacked) + hacked = TRUE + sword_color = "rainbow" + to_chat(user, "RNBW_ENGAGE") + + if(active) + icon_state = "swordrainbow" + user.update_inv_hands() + else + to_chat(user, "It's already fabulous!") + else + return ..() + +/obj/item/melee/transforming/energy/sword/pirate + name = "energy cutlass" + desc = "Arrrr matey." + icon_state = "cutlass0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + icon_state_on = "cutlass1" + light_color = "#ff0000" + +/obj/item/melee/transforming/energy/blade + name = "energy blade" + desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." + icon_state = "blade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + force = 30 //Normal attacks deal esword damage + hitsound = 'sound/weapons/blade1.ogg' + active = 1 + throwforce = 1 //Throwing or dropping the item deletes it. + throw_speed = 3 + throw_range = 1 + w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such. + var/datum/effect_system/spark_spread/spark_system + sharpness = IS_SHARP + +//Most of the other special functions are handled in their own files. aka special snowflake code so kewl +/obj/item/melee/transforming/energy/blade/Initialize() + . = ..() + spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + +/obj/item/melee/transforming/energy/blade/transform_weapon(mob/living/user, supress_message_text) + return + +/obj/item/melee/transforming/energy/blade/hardlight + name = "hardlight blade" + desc = "An extremely sharp blade made out of hard light. Packs quite a punch." + icon_state = "lightblade" + inhand_icon_state = "lightblade" diff --git a/code/game/objects/items/paiwire.dm b/code/game/objects/items/paiwire.dm index 9c86a22a038..0dbf23e937f 100644 --- a/code/game/objects/items/paiwire.dm +++ b/code/game/objects/items/paiwire.dm @@ -1,13 +1,13 @@ -/obj/item/pai_cable - desc = "A flexible coated cable with a universal jack on one end." - name = "data cable" - icon = 'icons/obj/power.dmi' - icon_state = "wire1" - item_flags = NOBLUDGEON - var/obj/machinery/machine - -/obj/item/pai_cable/proc/plugin(obj/machinery/M, mob/living/user) - if(!user.transferItemToLoc(src, M)) - return - user.visible_message("[user] inserts [src] into a data port on [M].", "You insert [src] into a data port on [M].", "You hear the satisfying click of a wire jack fastening into place.") - machine = M +/obj/item/pai_cable + desc = "A flexible coated cable with a universal jack on one end." + name = "data cable" + icon = 'icons/obj/power.dmi' + icon_state = "wire1" + item_flags = NOBLUDGEON + var/obj/machinery/machine + +/obj/item/pai_cable/proc/plugin(obj/machinery/M, mob/living/user) + if(!user.transferItemToLoc(src, M)) + return + user.visible_message("[user] inserts [src] into a data port on [M].", "You insert [src] into a data port on [M].", "You hear the satisfying click of a wire jack fastening into place.") + machine = M diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 45b8e69bdec..2015902cf02 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -1,936 +1,936 @@ -/********************************************************************** - Cyborg Spec Items -***********************************************************************/ -/obj/item/borg - icon = 'icons/mob/robot_items.dmi' - - -/obj/item/borg/stun - name = "electrically-charged arm" - icon_state = "elecarm" - var/charge_cost = 30 - -/obj/item/borg/stun/attack(mob/living/M, mob/living/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) - playsound(M, 'sound/weapons/genhit.ogg', 50, TRUE) - return FALSE - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(!R.cell.use(charge_cost)) - return - - user.do_attack_animation(M) - M.Paralyze(100) - M.apply_effect(EFFECT_STUTTER, 5) - - M.visible_message("[user] prods [M] with [src]!", \ - "[user] prods you with [src]!") - - playsound(loc, 'sound/weapons/egloves.ogg', 50, TRUE, -1) - - log_combat(user, M, "stunned", src, "(INTENT: [uppertext(user.a_intent)])") - -/obj/item/borg/cyborghug - name = "hugging module" - icon_state = "hugmodule" - desc = "For when a someone really needs a hug." - var/mode = 0 //0 = Hugs 1 = "Hug" 2 = Shock 3 = CRUSH - var/ccooldown = 0 - var/scooldown = 0 - var/shockallowed = FALSE//Can it be a stunarm when emagged. Only PK borgs get this by default. - var/boop = FALSE - -/obj/item/borg/cyborghug/attack_self(mob/living/user) - if(iscyborg(user)) - var/mob/living/silicon/robot/P = user - if(P.emagged&&shockallowed == 1) - if(mode < 3) - mode++ - else - mode = 0 - else if(mode < 1) - mode++ - else - mode = 0 - switch(mode) - if(0) - to_chat(user, "Power reset. Hugs!") - if(1) - to_chat(user, "Power increased!") - if(2) - to_chat(user, "BZZT. Electrifying arms...") - if(3) - to_chat(user, "ERROR: ARM ACTUATORS OVERLOADED.") - -/obj/item/borg/cyborghug/attack(mob/living/M, mob/living/silicon/robot/user) - if(M == user) - return - switch(mode) - if(0) - if(M.health >= 0) - if(user.zone_selected == BODY_ZONE_HEAD) - user.visible_message("[user] playfully boops [M] on the head!", \ - "You playfully boop [M] on the head!") - user.do_attack_animation(M, ATTACK_EFFECT_BOOP) - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) - else if(ishuman(M)) - if(!(user.mobility_flags & MOBILITY_STAND)) - user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ - "You shake [M] trying to get [M.p_them()] up!") - else - user.visible_message("[user] hugs [M] to make [M.p_them()] feel better!", \ - "You hug [M] to make [M.p_them()] feel better!") - if(M.resting) - M.set_resting(FALSE, TRUE) - else - user.visible_message("[user] pets [M]!", \ - "You pet [M]!") - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - if(1) - if(M.health >= 0) - if(ishuman(M)) - if(!(M.mobility_flags & MOBILITY_STAND)) - user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ - "You shake [M] trying to get [M.p_them()] up!") - else if(user.zone_selected == BODY_ZONE_HEAD) - user.visible_message("[user] bops [M] on the head!", \ - "You bop [M] on the head!") - user.do_attack_animation(M, ATTACK_EFFECT_PUNCH) - else - user.visible_message("[user] hugs [M] in a firm bear-hug! [M] looks uncomfortable...", \ - "You hug [M] firmly to make [M.p_them()] feel better! [M] looks uncomfortable...") - if(M.resting) - M.set_resting(FALSE, TRUE) - else - user.visible_message("[user] bops [M] on the head!", \ - "You bop [M] on the head!") - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) - if(2) - if(scooldown < world.time) - if(M.health >= 0) - if(ishuman(M)||ismonkey(M)) - M.electrocute_act(5, "[user]", flags = SHOCK_NOGLOVES) - user.visible_message("[user] electrocutes [M] with [user.p_their()] touch!", \ - "You electrocute [M] with your touch!") - M.update_mobility() - else - if(!iscyborg(M)) - M.adjustFireLoss(10) - user.visible_message("[user] shocks [M]!", \ - "You shock [M]!") - else - user.visible_message("[user] shocks [M]. It does not seem to have an effect", \ - "You shock [M] to no effect.") - playsound(loc, 'sound/effects/sparks2.ogg', 50, TRUE, -1) - user.cell.charge -= 500 - scooldown = world.time + 20 - if(3) - if(ccooldown < world.time) - if(M.health >= 0) - if(ishuman(M)) - user.visible_message("[user] crushes [M] in [user.p_their()] grip!", \ - "You crush [M] in your grip!") - else - user.visible_message("[user] crushes [M]!", \ - "You crush [M]!") - playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE, -1) - M.adjustBruteLoss(15) - user.cell.charge -= 300 - ccooldown = world.time + 10 - -/obj/item/borg/cyborghug/peacekeeper - shockallowed = TRUE - -/obj/item/borg/cyborghug/medical - boop = TRUE - -/obj/item/borg/charger - name = "power connector" - icon_state = "charger_draw" - item_flags = NOBLUDGEON - var/mode = "draw" - var/static/list/charge_machines = typecacheof(list(/obj/machinery/cell_charger, /obj/machinery/recharger, /obj/machinery/recharge_station, /obj/machinery/mech_bay_recharge_port)) - var/static/list/charge_items = typecacheof(list(/obj/item/stock_parts/cell, /obj/item/gun/energy)) - -/obj/item/borg/charger/update_icon_state() - icon_state = "charger_[mode]" - -/obj/item/borg/charger/attack_self(mob/user) - if(mode == "draw") - mode = "charge" - else - mode = "draw" - to_chat(user, "You toggle [src] to \"[mode]\" mode.") - update_icon() - -/obj/item/borg/charger/afterattack(obj/item/target, mob/living/silicon/robot/user, proximity_flag) - . = ..() - if(!proximity_flag || !iscyborg(user)) - return - if(mode == "draw") - if(is_type_in_list(target, charge_machines)) - var/obj/machinery/M = target - if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) - to_chat(user, "[M] is unpowered!") - return - - to_chat(user, "You connect to [M]'s power line...") - while(do_after(user, 15, target = M, progress = 0)) - if(!user || !user.cell || mode != "draw") - return - - if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) - break - - if(!user.cell.give(150)) - break - - M.use_power(200) - - to_chat(user, "You stop charging yourself.") - - else if(is_type_in_list(target, charge_items)) - var/obj/item/stock_parts/cell/cell = target - if(!istype(cell)) - cell = locate(/obj/item/stock_parts/cell) in target - if(!cell) - to_chat(user, "[target] has no power cell!") - return - - if(istype(target, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = target - if(!E.can_charge) - to_chat(user, "[target] has no power port!") - return - - if(!cell.charge) - to_chat(user, "[target] has no power!") - - - to_chat(user, "You connect to [target]'s power port...") - - while(do_after(user, 15, target = target, progress = 0)) - if(!user || !user.cell || mode != "draw") - return - - if(!cell || !target) - return - - if(cell != target && cell.loc != target) - return - - var/draw = min(cell.charge, cell.chargerate*0.5, user.cell.maxcharge-user.cell.charge) - if(!cell.use(draw)) - break - if(!user.cell.give(draw)) - break - target.update_icon() - - to_chat(user, "You stop charging yourself.") - - else if(is_type_in_list(target, charge_items)) - var/obj/item/stock_parts/cell/cell = target - if(!istype(cell)) - cell = locate(/obj/item/stock_parts/cell) in target - if(!cell) - to_chat(user, "[target] has no power cell!") - return - - if(istype(target, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = target - if(!E.can_charge) - to_chat(user, "[target] has no power port!") - return - - if(cell.charge >= cell.maxcharge) - to_chat(user, "[target] is already charged!") - - to_chat(user, "You connect to [target]'s power port...") - - while(do_after(user, 15, target = target, progress = 0)) - if(!user || !user.cell || mode != "charge") - return - - if(!cell || !target) - return - - if(cell != target && cell.loc != target) - return - - var/draw = min(user.cell.charge, cell.chargerate*0.5, cell.maxcharge-cell.charge) - if(!user.cell.use(draw)) - break - if(!cell.give(draw)) - break - target.update_icon() - - to_chat(user, "You stop charging [target].") - -/obj/item/harmalarm - name = "\improper Sonic Harm Prevention Tool" - desc = "Releases a harmless blast that confuses most organics. For when the harm is JUST TOO MUCH." - icon = 'icons/obj/device.dmi' - icon_state = "megaphone" - var/cooldown = 0 - -/obj/item/harmalarm/emag_act(mob/user) - obj_flags ^= EMAGGED - if(obj_flags & EMAGGED) - to_chat(user, "You short out the safeties on [src]!") - else - to_chat(user, "You reset the safeties on [src]!") - -/obj/item/harmalarm/attack_self(mob/user) - var/safety = !(obj_flags & EMAGGED) - if(cooldown > world.time) - to_chat(user, "The device is still recharging!") - return - - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(!R.cell || R.cell.charge < 1200) - to_chat(user, "You don't have enough charge to do this!") - return - R.cell.charge -= 1000 - if(R.emagged) - safety = FALSE - - if(safety == TRUE) - user.visible_message("[user] blares out a near-deafening siren from its speakers!", \ - "The siren pierces your hearing and confuses you!", \ - "The siren pierces your hearing!") - for(var/mob/living/carbon/M in get_hearers_in_view(9, user)) - if(M.get_ear_protection() == FALSE) - M.confused += 6 - audible_message("HUMAN HARM") - playsound(get_turf(src), 'sound/ai/harmalarm.ogg', 70, 3) - cooldown = world.time + 200 - user.log_message("used a Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - to_chat(R.connected_ai, "
                NOTICE - Peacekeeping 'HARM ALARM' used by: [user]
                ") - - return - - if(safety == FALSE) - user.audible_message("BZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZT") - for(var/mob/living/carbon/C in get_hearers_in_view(9, user)) - var/bang_effect = C.soundbang_act(2, 0, 0, 5) - switch(bang_effect) - if(1) - C.confused += 5 - C.stuttering += 10 - C.Jitter(10) - if(2) - C.Paralyze(40) - C.confused += 10 - C.stuttering += 15 - C.Jitter(25) - playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3) - cooldown = world.time + 600 - user.log_message("used an emagged Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) - -#define DISPENSE_LOLLIPOP_MODE 1 -#define THROW_LOLLIPOP_MODE 2 -#define THROW_GUMBALL_MODE 3 -#define DISPENSE_ICECREAM_MODE 4 - -/obj/item/borg/lollipop - name = "treat fabricator" - desc = "Reward humans with various treats. Toggle in-module to switch between dispensing and high velocity ejection modes." - icon_state = "lollipop" - var/candy = 30 - var/candymax = 30 - var/charge_delay = 10 - var/charging = FALSE - var/mode = DISPENSE_LOLLIPOP_MODE - - var/firedelay = 0 - var/hitspeed = 2 - var/hitdamage = 0 - var/emaggedhitdamage = 3 - -/obj/item/borg/lollipop/clown - emaggedhitdamage = 0 - -/obj/item/borg/lollipop/equipped() - . = ..() - check_amount() - -/obj/item/borg/lollipop/dropped() - . = ..() - check_amount() - -/obj/item/borg/lollipop/proc/check_amount() //Doesn't even use processing ticks. - if(charging) - return - if(candy < candymax) - addtimer(CALLBACK(src, .proc/charge_lollipops), charge_delay) - charging = TRUE - -/obj/item/borg/lollipop/proc/charge_lollipops() - candy++ - charging = FALSE - check_amount() - -/obj/item/borg/lollipop/proc/dispense(atom/A, mob/user) - if(candy <= 0) - to_chat(user, "No treats left in storage!") - return FALSE - var/turf/T = get_turf(A) - if(!T || !istype(T) || !isopenturf(T)) - return FALSE - if(isobj(A)) - var/obj/O = A - if(O.density) - return FALSE - - var/obj/item/reagent_containers/food/snacks/L - switch(mode) - if(DISPENSE_LOLLIPOP_MODE) - L = new /obj/item/reagent_containers/food/snacks/chewable/lollipop(T) - if(DISPENSE_ICECREAM_MODE) - L = new /obj/item/reagent_containers/food/snacks/icecream(T) - var/obj/item/reagent_containers/food/snacks/icecream/I = L - I.add_ice_cream("vanilla") - I.desc = "Eat the ice cream." - - var/into_hands = FALSE - if(ismob(A)) - var/mob/M = A - into_hands = M.put_in_hands(L) - - candy-- - check_amount() - - if(into_hands) - user.visible_message("[user] dispenses a treat into the hands of [A].", "You dispense a treat into the hands of [A].", "You hear a click.") - else - user.visible_message("[user] dispenses a treat.", "You dispense a treat.", "You hear a click.") - - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - return TRUE - -/obj/item/borg/lollipop/proc/shootL(atom/target, mob/living/user, params) - if(candy <= 0) - to_chat(user, "Not enough lollipops left!") - return FALSE - candy-- - var/obj/item/ammo_casing/caseless/lollipop/A = new /obj/item/ammo_casing/caseless/lollipop(src) - A.BB.damage = hitdamage - if(hitdamage) - A.BB.nodamage = FALSE - A.BB.speed = 0.5 - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - A.fire_casing(target, user, params, 0, 0, null, 0, src) - user.visible_message("[user] blasts a flying lollipop at [target]!") - check_amount() - -/obj/item/borg/lollipop/proc/shootG(atom/target, mob/living/user, params) //Most certainly a good idea. - if(candy <= 0) - to_chat(user, "Not enough gumballs left!") - return FALSE - candy-- - var/obj/item/ammo_casing/caseless/gumball/A = new /obj/item/ammo_casing/caseless/gumball(src) - A.BB.damage = hitdamage - if(hitdamage) - A.BB.nodamage = FALSE - A.BB.speed = 0.5 - A.BB.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) - playsound(src.loc, 'sound/weapons/bulletflyby3.ogg', 50, TRUE) - A.fire_casing(target, user, params, 0, 0, null, 0, src) - user.visible_message("[user] shoots a high-velocity gumball at [target]!") - check_amount() - -/obj/item/borg/lollipop/afterattack(atom/target, mob/living/user, proximity, click_params) - . = ..() - check_amount() - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(!R.cell.use(12)) - to_chat(user, "Not enough power.") - return FALSE - if(R.emagged) - hitdamage = emaggedhitdamage - switch(mode) - if(DISPENSE_LOLLIPOP_MODE, DISPENSE_ICECREAM_MODE) - if(!proximity) - return FALSE - dispense(target, user) - if(THROW_LOLLIPOP_MODE) - shootL(target, user, click_params) - if(THROW_GUMBALL_MODE) - shootG(target, user, click_params) - hitdamage = initial(hitdamage) - -/obj/item/borg/lollipop/attack_self(mob/living/user) - switch(mode) - if(DISPENSE_LOLLIPOP_MODE) - mode = THROW_LOLLIPOP_MODE - to_chat(user, "Module is now throwing lollipops.") - if(THROW_LOLLIPOP_MODE) - mode = THROW_GUMBALL_MODE - to_chat(user, "Module is now blasting gumballs.") - if(THROW_GUMBALL_MODE) - mode = DISPENSE_ICECREAM_MODE - to_chat(user, "Module is now dispensing ice cream.") - if(DISPENSE_ICECREAM_MODE) - mode = DISPENSE_LOLLIPOP_MODE - to_chat(user, "Module is now dispensing lollipops.") - ..() - -#undef DISPENSE_LOLLIPOP_MODE -#undef THROW_LOLLIPOP_MODE -#undef THROW_GUMBALL_MODE -#undef DISPENSE_ICECREAM_MODE - -/obj/item/ammo_casing/caseless/gumball - name = "Gumball" - desc = "Why are you seeing this?!" - projectile_type = /obj/projectile/bullet/reusable/gumball - click_cooldown_override = 2 - - -/obj/projectile/bullet/reusable/gumball - name = "gumball" - desc = "Oh noes! A fast-moving gumball!" - icon_state = "gumball" - ammo_type = /obj/item/reagent_containers/food/snacks/chewable/gumball/cyborg - nodamage = TRUE - -/obj/projectile/bullet/reusable/gumball/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - var/obj/item/reagent_containers/food/snacks/chewable/gumball/S = new ammo_type(T) - S.color = color - dropped = TRUE - -/obj/item/ammo_casing/caseless/lollipop //NEEDS RANDOMIZED COLOR LOGIC. - name = "Lollipop" - desc = "Why are you seeing this?!" - projectile_type = /obj/projectile/bullet/reusable/lollipop - click_cooldown_override = 2 - -/obj/projectile/bullet/reusable/lollipop - name = "lollipop" - desc = "Oh noes! A fast-moving lollipop!" - icon_state = "lollipop_1" - ammo_type = /obj/item/reagent_containers/food/snacks/chewable/lollipop/cyborg - var/color2 = rgb(0, 0, 0) - nodamage = TRUE - -/obj/projectile/bullet/reusable/lollipop/Initialize() - . = ..() - var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(src) - color2 = S.headcolor - var/mutable_appearance/head = mutable_appearance('icons/obj/projectiles.dmi', "lollipop_2") - head.color = color2 - add_overlay(head) - -/obj/projectile/bullet/reusable/lollipop/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(T) - S.change_head_color(color2) - dropped = TRUE - -#define PKBORG_DAMPEN_CYCLE_DELAY 20 - -//Peacekeeper Cyborg Projectile Dampenening Field -/obj/item/borg/projectile_dampen - name = "\improper Hyperkinetic Dampening projector" - desc = "A device that projects a dampening field that weakens kinetic energy above a certain threshold. Projects a field that drains power per second while active, that will weaken and slow damaging projectiles inside its field. Still being a prototype, it tends to induce a charge on ungrounded metallic surfaces." - icon = 'icons/obj/device.dmi' - icon_state = "shield" - var/maxenergy = 1500 - var/energy = 1500 - var/energy_recharge = 7.5 - var/energy_recharge_cyborg_drain_coefficient = 0.4 - var/cyborg_cell_critical_percentage = 0.05 - var/mob/living/silicon/robot/host = null - var/datum/proximity_monitor/advanced/dampening_field - var/projectile_damage_coefficient = 0.5 - var/projectile_damage_tick_ecost_coefficient = 2 //Lasers get half their damage chopped off, drains 50 power/tick. Note that fields are processed 5 times per second. - var/projectile_speed_coefficient = 1.5 //Higher the coefficient slower the projectile. - var/projectile_tick_speed_ecost = 15 - var/list/obj/projectile/tracked - var/image/projectile_effect - var/field_radius = 3 - var/active = FALSE - var/cycle_delay = 0 - -/obj/item/borg/projectile_dampen/debug - maxenergy = 50000 - energy = 50000 - energy_recharge = 5000 - -/obj/item/borg/projectile_dampen/Initialize() - . = ..() - projectile_effect = image('icons/effects/fields.dmi', "projectile_dampen_effect") - tracked = list() - icon_state = "shield0" - START_PROCESSING(SSfastprocess, src) - host = loc - -/obj/item/borg/projectile_dampen/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/obj/item/borg/projectile_dampen/attack_self(mob/user) - if(cycle_delay > world.time) - to_chat(user, "[src] is still recycling its projectors!") - return - cycle_delay = world.time + PKBORG_DAMPEN_CYCLE_DELAY - if(!active) - if(!user.has_buckled_mobs()) - activate_field() - else - to_chat(user, "[src]'s safety cutoff prevents you from activating it due to living beings being ontop of you!") - else - deactivate_field() - update_icon() - to_chat(user, "You [active? "activate":"deactivate"] [src].") - -/obj/item/borg/projectile_dampen/update_icon_state() - icon_state = "[initial(icon_state)][active]" - -/obj/item/borg/projectile_dampen/proc/activate_field() - if(istype(dampening_field)) - QDEL_NULL(dampening_field) - dampening_field = make_field(/datum/proximity_monitor/advanced/peaceborg_dampener, list("current_range" = field_radius, "host" = src, "projector" = src)) - var/mob/living/silicon/robot/owner = get_host() - if(owner) - owner.module.allow_riding = FALSE - active = TRUE - -/obj/item/borg/projectile_dampen/proc/deactivate_field() - QDEL_NULL(dampening_field) - visible_message("\The [src] shuts off!") - for(var/P in tracked) - restore_projectile(P) - active = FALSE - - var/mob/living/silicon/robot/owner = get_host() - if(owner) - owner.module.allow_riding = TRUE - -/obj/item/borg/projectile_dampen/proc/get_host() - if(istype(host)) - return host - else - if(iscyborg(host.loc)) - return host.loc - return null - -/obj/item/borg/projectile_dampen/dropped() - . = ..() - host = loc - -/obj/item/borg/projectile_dampen/equipped() - . = ..() - host = loc - -/obj/item/borg/projectile_dampen/cyborg_unequip(mob/user) - deactivate_field() - . = ..() - -/obj/item/borg/projectile_dampen/on_mob_death() - deactivate_field() - . = ..() - -/obj/item/borg/projectile_dampen/process() - process_recharge() - process_usage() - update_location() - -/obj/item/borg/projectile_dampen/proc/update_location() - if(dampening_field) - dampening_field.HandleMove() - -/obj/item/borg/projectile_dampen/proc/process_usage() - var/usage = 0 - for(var/I in tracked) - var/obj/projectile/P = I - if(!P.stun && P.nodamage) //No damage - continue - usage += projectile_tick_speed_ecost - usage += (tracked[I] * projectile_damage_tick_ecost_coefficient) - energy = clamp(energy - usage, 0, maxenergy) - if(energy <= 0) - deactivate_field() - visible_message("[src] blinks \"ENERGY DEPLETED\".") - -/obj/item/borg/projectile_dampen/proc/process_recharge() - if(!istype(host)) - if(iscyborg(host.loc)) - host = host.loc - else - energy = clamp(energy + energy_recharge, 0, maxenergy) - return - if(host.cell && (host.cell.charge >= (host.cell.maxcharge * cyborg_cell_critical_percentage)) && (energy < maxenergy)) - host.cell.use(energy_recharge*energy_recharge_cyborg_drain_coefficient) - energy += energy_recharge - -/obj/item/borg/projectile_dampen/proc/dampen_projectile(obj/projectile/P, track_projectile = TRUE) - if(tracked[P]) - return - if(track_projectile) - tracked[P] = P.damage - P.damage *= projectile_damage_coefficient - P.speed *= projectile_speed_coefficient - P.add_overlay(projectile_effect) - -/obj/item/borg/projectile_dampen/proc/restore_projectile(obj/projectile/P) - tracked -= P - P.damage *= (1/projectile_damage_coefficient) - P.speed *= (1/projectile_speed_coefficient) - P.cut_overlay(projectile_effect) - -/********************************************************************** - HUD/SIGHT things -***********************************************************************/ -/obj/item/borg/sight - var/sight_mode = null - - -/obj/item/borg/sight/xray - name = "\proper X-ray vision" - icon = 'icons/obj/decals.dmi' - icon_state = "securearea" - sight_mode = BORGXRAY - -/obj/item/borg/sight/thermal - name = "\proper thermal vision" - sight_mode = BORGTHERM - icon_state = "thermal" - - -/obj/item/borg/sight/meson - name = "\proper meson vision" - sight_mode = BORGMESON - icon_state = "meson" - -/obj/item/borg/sight/material - name = "\proper material vision" - sight_mode = BORGMATERIAL - icon_state = "material" - -/obj/item/borg/sight/hud - name = "hud" - var/obj/item/clothing/glasses/hud/hud = null - - -/obj/item/borg/sight/hud/med - name = "medical hud" - icon_state = "healthhud" - -/obj/item/borg/sight/hud/med/Initialize() - . = ..() - hud = new /obj/item/clothing/glasses/hud/health(src) - - -/obj/item/borg/sight/hud/sec - name = "security hud" - icon_state = "securityhud" - -/obj/item/borg/sight/hud/sec/Initialize() - . = ..() - hud = new /obj/item/clothing/glasses/hud/security(src) - - -/********************************************************************** - Borg apparatus -***********************************************************************/ -//These are tools that can hold only specific items. For example, the mediborg gets one that can only hold beakers and bottles. - -/obj/item/borg/apparatus/ - name = "unknown storage apparatus" - desc = "This device seems nonfunctional." - icon = 'icons/mob/robot_items.dmi' - icon_state = "hugmodule" - var/obj/item/stored - var/list/storable = list() - -/obj/item/borg/apparatus/Initialize() - . = ..() - RegisterSignal(loc.loc, COMSIG_BORG_SAFE_DECONSTRUCT, .proc/safedecon) - -/obj/item/borg/apparatus/Destroy() - if(stored) - qdel(stored) - . = ..() - -///If we're safely deconstructed, we put the item neatly onto the ground, rather than deleting it. -/obj/item/borg/apparatus/proc/safedecon() - if(stored) - stored.forceMove(get_turf(src)) - stored = null - -/obj/item/borg/apparatus/Exited(atom/A) - if(A == stored) //sanity check - UnregisterSignal(stored, COMSIG_ATOM_UPDATE_ICON) - stored = null - update_icon() - . = ..() - -///A right-click verb, for those not using hotkey mode. -/obj/item/borg/apparatus/verb/verb_dropHeld() - set category = "Object" - set name = "Drop" - - if(usr != loc || !stored) - return - stored.forceMove(get_turf(usr)) - return - -/obj/item/borg/apparatus/attack_self(mob/living/silicon/robot/user) - if(!stored) - return ..() - if(user.client?.keys_held["Alt"]) - stored.forceMove(get_turf(user)) - return - stored.attack_self(user) - -/obj/item/borg/apparatus/pre_attack(atom/A, mob/living/user, params) - if(!stored) - var/itemcheck = FALSE - for(var/i in storable) - if(istype(A, i)) - itemcheck = TRUE - break - if(itemcheck) - var/obj/item/O = A - O.forceMove(src) - stored = O - RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) - update_icon() - return - else - stored.melee_attack_chain(user, A, params) - return - . = ..() - -/obj/item/borg/apparatus/attackby(obj/item/W, mob/user, params) - if(stored) - W.melee_attack_chain(user, stored, params) - return - . = ..() - -///////////////// -//beaker holder// -///////////////// - -/obj/item/borg/apparatus/beaker - name = "beaker storage apparatus" - desc = "A special apparatus for carrying beakers without spilling the contents. Alt-Z or right-click to drop the beaker." - icon_state = "borg_beaker_apparatus" - storable = list(/obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle) - -/obj/item/borg/apparatus/beaker/Initialize() - . = ..() - stored = new /obj/item/reagent_containers/glass/beaker/large(src) - RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) - update_icon() - -/obj/item/borg/apparatus/beaker/Destroy() - if(stored) - var/obj/item/reagent_containers/C = stored - C.SplashReagents(get_turf(src)) - qdel(stored) - . = ..() - -/obj/item/borg/apparatus/beaker/examine() - . = ..() - if(stored) - var/obj/item/reagent_containers/C = stored - . += "The apparatus currently has [C] secured, which contains:" - if(length(C.reagents.reagent_list)) - for(var/datum/reagent/R in C.reagents.reagent_list) - . += "[R.volume] units of [R.name]" - else - . += "Nothing." - -/obj/item/borg/apparatus/beaker/update_overlays() - . = ..() - var/mutable_appearance/arm = mutable_appearance(icon = icon, icon_state = "borg_beaker_apparatus_arm") - if(stored) - COMPILE_OVERLAYS(stored) - stored.pixel_x = 0 - stored.pixel_y = 0 - var/mutable_appearance/stored_copy = new /mutable_appearance(stored) - if(istype(stored, /obj/item/reagent_containers/glass/beaker)) - arm.pixel_y = arm.pixel_y - 3 - stored_copy.layer = FLOAT_LAYER - stored_copy.plane = FLOAT_PLANE - . += stored_copy - else - arm.pixel_y = arm.pixel_y - 5 - . += arm - -/obj/item/borg/apparatus/beaker/attack_self(mob/living/silicon/robot/user) - if(stored && !user.client?.keys_held["Alt"] && user.a_intent != "help") - var/obj/item/reagent_containers/C = stored - C.SplashReagents(get_turf(user)) - loc.visible_message("[user] spills the contents of the [C] all over the floor.") - return - . = ..() - -/obj/item/borg/apparatus/beaker/extra - name = "secondary beaker storage apparatus" - desc = "A supplementary beaker storage apparatus." - -/obj/item/borg/apparatus/beaker/service - name = "beverage storage apparatus" - desc = "A special apparatus for carrying drinks without spilling the contents. Alt-Z or right-click to drop the beaker." - icon_state = "borg_beaker_apparatus" - storable = list(/obj/item/reagent_containers/food/drinks/, - /obj/item/reagent_containers/food/condiment) - -/obj/item/borg/apparatus/beaker/service/Initialize() - . = ..() - stored = new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) - update_icon() - -//////////////////// -//engi part holder// -//////////////////// - -/obj/item/borg/apparatus/circuit - name = "circuit manipulation apparatus" - desc = "A special apparatus for carrying and manipulating circuit boards. Alt-Z or right-click to drop the stored object." - icon_state = "borg_hardware_apparatus" - storable = list(/obj/item/circuitboard, - /obj/item/electronics) - -/obj/item/borg/apparatus/circuit/Initialize() - . = ..() - update_icon() - -/obj/item/borg/apparatus/circuit/update_overlays() - . = ..() - var/mutable_appearance/arm = mutable_appearance(icon, "borg_hardware_apparatus_arm1") - if(stored) - COMPILE_OVERLAYS(stored) - stored.pixel_x = -3 - stored.pixel_y = 0 - if(!istype(stored, /obj/item/circuitboard)) - arm.icon_state = "borg_hardware_apparatus_arm2" - var/mutable_appearance/stored_copy = new /mutable_appearance(stored) - stored_copy.layer = FLOAT_LAYER - stored_copy.plane = FLOAT_PLANE - . += stored_copy - . += arm - -/obj/item/borg/apparatus/circuit/examine() - . = ..() - if(stored) - . += "The apparatus currently has [stored] secured." - -/obj/item/borg/apparatus/circuit/pre_attack(atom/A, mob/living/user, params) - . = ..() - if(istype(A, /obj/item/ai_module) && !stored) //If an admin wants a borg to upload laws, who am I to stop them? Otherwise, we can hint that it fails - to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.") +/********************************************************************** + Cyborg Spec Items +***********************************************************************/ +/obj/item/borg + icon = 'icons/mob/robot_items.dmi' + + +/obj/item/borg/stun + name = "electrically-charged arm" + icon_state = "elecarm" + var/charge_cost = 30 + +/obj/item/borg/stun/attack(mob/living/M, mob/living/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) + playsound(M, 'sound/weapons/genhit.ogg', 50, TRUE) + return FALSE + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(!R.cell.use(charge_cost)) + return + + user.do_attack_animation(M) + M.Paralyze(100) + M.apply_effect(EFFECT_STUTTER, 5) + + M.visible_message("[user] prods [M] with [src]!", \ + "[user] prods you with [src]!") + + playsound(loc, 'sound/weapons/egloves.ogg', 50, TRUE, -1) + + log_combat(user, M, "stunned", src, "(INTENT: [uppertext(user.a_intent)])") + +/obj/item/borg/cyborghug + name = "hugging module" + icon_state = "hugmodule" + desc = "For when a someone really needs a hug." + var/mode = 0 //0 = Hugs 1 = "Hug" 2 = Shock 3 = CRUSH + var/ccooldown = 0 + var/scooldown = 0 + var/shockallowed = FALSE//Can it be a stunarm when emagged. Only PK borgs get this by default. + var/boop = FALSE + +/obj/item/borg/cyborghug/attack_self(mob/living/user) + if(iscyborg(user)) + var/mob/living/silicon/robot/P = user + if(P.emagged&&shockallowed == 1) + if(mode < 3) + mode++ + else + mode = 0 + else if(mode < 1) + mode++ + else + mode = 0 + switch(mode) + if(0) + to_chat(user, "Power reset. Hugs!") + if(1) + to_chat(user, "Power increased!") + if(2) + to_chat(user, "BZZT. Electrifying arms...") + if(3) + to_chat(user, "ERROR: ARM ACTUATORS OVERLOADED.") + +/obj/item/borg/cyborghug/attack(mob/living/M, mob/living/silicon/robot/user) + if(M == user) + return + switch(mode) + if(0) + if(M.health >= 0) + if(user.zone_selected == BODY_ZONE_HEAD) + user.visible_message("[user] playfully boops [M] on the head!", \ + "You playfully boop [M] on the head!") + user.do_attack_animation(M, ATTACK_EFFECT_BOOP) + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) + else if(ishuman(M)) + if(!(user.mobility_flags & MOBILITY_STAND)) + user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ + "You shake [M] trying to get [M.p_them()] up!") + else + user.visible_message("[user] hugs [M] to make [M.p_them()] feel better!", \ + "You hug [M] to make [M.p_them()] feel better!") + if(M.resting) + M.set_resting(FALSE, TRUE) + else + user.visible_message("[user] pets [M]!", \ + "You pet [M]!") + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + if(1) + if(M.health >= 0) + if(ishuman(M)) + if(!(M.mobility_flags & MOBILITY_STAND)) + user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ + "You shake [M] trying to get [M.p_them()] up!") + else if(user.zone_selected == BODY_ZONE_HEAD) + user.visible_message("[user] bops [M] on the head!", \ + "You bop [M] on the head!") + user.do_attack_animation(M, ATTACK_EFFECT_PUNCH) + else + user.visible_message("[user] hugs [M] in a firm bear-hug! [M] looks uncomfortable...", \ + "You hug [M] firmly to make [M.p_them()] feel better! [M] looks uncomfortable...") + if(M.resting) + M.set_resting(FALSE, TRUE) + else + user.visible_message("[user] bops [M] on the head!", \ + "You bop [M] on the head!") + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) + if(2) + if(scooldown < world.time) + if(M.health >= 0) + if(ishuman(M)||ismonkey(M)) + M.electrocute_act(5, "[user]", flags = SHOCK_NOGLOVES) + user.visible_message("[user] electrocutes [M] with [user.p_their()] touch!", \ + "You electrocute [M] with your touch!") + M.update_mobility() + else + if(!iscyborg(M)) + M.adjustFireLoss(10) + user.visible_message("[user] shocks [M]!", \ + "You shock [M]!") + else + user.visible_message("[user] shocks [M]. It does not seem to have an effect", \ + "You shock [M] to no effect.") + playsound(loc, 'sound/effects/sparks2.ogg', 50, TRUE, -1) + user.cell.charge -= 500 + scooldown = world.time + 20 + if(3) + if(ccooldown < world.time) + if(M.health >= 0) + if(ishuman(M)) + user.visible_message("[user] crushes [M] in [user.p_their()] grip!", \ + "You crush [M] in your grip!") + else + user.visible_message("[user] crushes [M]!", \ + "You crush [M]!") + playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE, -1) + M.adjustBruteLoss(15) + user.cell.charge -= 300 + ccooldown = world.time + 10 + +/obj/item/borg/cyborghug/peacekeeper + shockallowed = TRUE + +/obj/item/borg/cyborghug/medical + boop = TRUE + +/obj/item/borg/charger + name = "power connector" + icon_state = "charger_draw" + item_flags = NOBLUDGEON + var/mode = "draw" + var/static/list/charge_machines = typecacheof(list(/obj/machinery/cell_charger, /obj/machinery/recharger, /obj/machinery/recharge_station, /obj/machinery/mech_bay_recharge_port)) + var/static/list/charge_items = typecacheof(list(/obj/item/stock_parts/cell, /obj/item/gun/energy)) + +/obj/item/borg/charger/update_icon_state() + icon_state = "charger_[mode]" + +/obj/item/borg/charger/attack_self(mob/user) + if(mode == "draw") + mode = "charge" + else + mode = "draw" + to_chat(user, "You toggle [src] to \"[mode]\" mode.") + update_icon() + +/obj/item/borg/charger/afterattack(obj/item/target, mob/living/silicon/robot/user, proximity_flag) + . = ..() + if(!proximity_flag || !iscyborg(user)) + return + if(mode == "draw") + if(is_type_in_list(target, charge_machines)) + var/obj/machinery/M = target + if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) + to_chat(user, "[M] is unpowered!") + return + + to_chat(user, "You connect to [M]'s power line...") + while(do_after(user, 15, target = M, progress = 0)) + if(!user || !user.cell || mode != "draw") + return + + if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) + break + + if(!user.cell.give(150)) + break + + M.use_power(200) + + to_chat(user, "You stop charging yourself.") + + else if(is_type_in_list(target, charge_items)) + var/obj/item/stock_parts/cell/cell = target + if(!istype(cell)) + cell = locate(/obj/item/stock_parts/cell) in target + if(!cell) + to_chat(user, "[target] has no power cell!") + return + + if(istype(target, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = target + if(!E.can_charge) + to_chat(user, "[target] has no power port!") + return + + if(!cell.charge) + to_chat(user, "[target] has no power!") + + + to_chat(user, "You connect to [target]'s power port...") + + while(do_after(user, 15, target = target, progress = 0)) + if(!user || !user.cell || mode != "draw") + return + + if(!cell || !target) + return + + if(cell != target && cell.loc != target) + return + + var/draw = min(cell.charge, cell.chargerate*0.5, user.cell.maxcharge-user.cell.charge) + if(!cell.use(draw)) + break + if(!user.cell.give(draw)) + break + target.update_icon() + + to_chat(user, "You stop charging yourself.") + + else if(is_type_in_list(target, charge_items)) + var/obj/item/stock_parts/cell/cell = target + if(!istype(cell)) + cell = locate(/obj/item/stock_parts/cell) in target + if(!cell) + to_chat(user, "[target] has no power cell!") + return + + if(istype(target, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = target + if(!E.can_charge) + to_chat(user, "[target] has no power port!") + return + + if(cell.charge >= cell.maxcharge) + to_chat(user, "[target] is already charged!") + + to_chat(user, "You connect to [target]'s power port...") + + while(do_after(user, 15, target = target, progress = 0)) + if(!user || !user.cell || mode != "charge") + return + + if(!cell || !target) + return + + if(cell != target && cell.loc != target) + return + + var/draw = min(user.cell.charge, cell.chargerate*0.5, cell.maxcharge-cell.charge) + if(!user.cell.use(draw)) + break + if(!cell.give(draw)) + break + target.update_icon() + + to_chat(user, "You stop charging [target].") + +/obj/item/harmalarm + name = "\improper Sonic Harm Prevention Tool" + desc = "Releases a harmless blast that confuses most organics. For when the harm is JUST TOO MUCH." + icon = 'icons/obj/device.dmi' + icon_state = "megaphone" + var/cooldown = 0 + +/obj/item/harmalarm/emag_act(mob/user) + obj_flags ^= EMAGGED + if(obj_flags & EMAGGED) + to_chat(user, "You short out the safeties on [src]!") + else + to_chat(user, "You reset the safeties on [src]!") + +/obj/item/harmalarm/attack_self(mob/user) + var/safety = !(obj_flags & EMAGGED) + if(cooldown > world.time) + to_chat(user, "The device is still recharging!") + return + + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(!R.cell || R.cell.charge < 1200) + to_chat(user, "You don't have enough charge to do this!") + return + R.cell.charge -= 1000 + if(R.emagged) + safety = FALSE + + if(safety == TRUE) + user.visible_message("[user] blares out a near-deafening siren from its speakers!", \ + "The siren pierces your hearing and confuses you!", \ + "The siren pierces your hearing!") + for(var/mob/living/carbon/M in get_hearers_in_view(9, user)) + if(M.get_ear_protection() == FALSE) + M.confused += 6 + audible_message("HUMAN HARM") + playsound(get_turf(src), 'sound/ai/harmalarm.ogg', 70, 3) + cooldown = world.time + 200 + user.log_message("used a Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + to_chat(R.connected_ai, "
                NOTICE - Peacekeeping 'HARM ALARM' used by: [user]
                ") + + return + + if(safety == FALSE) + user.audible_message("BZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZT") + for(var/mob/living/carbon/C in get_hearers_in_view(9, user)) + var/bang_effect = C.soundbang_act(2, 0, 0, 5) + switch(bang_effect) + if(1) + C.confused += 5 + C.stuttering += 10 + C.Jitter(10) + if(2) + C.Paralyze(40) + C.confused += 10 + C.stuttering += 15 + C.Jitter(25) + playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3) + cooldown = world.time + 600 + user.log_message("used an emagged Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) + +#define DISPENSE_LOLLIPOP_MODE 1 +#define THROW_LOLLIPOP_MODE 2 +#define THROW_GUMBALL_MODE 3 +#define DISPENSE_ICECREAM_MODE 4 + +/obj/item/borg/lollipop + name = "treat fabricator" + desc = "Reward humans with various treats. Toggle in-module to switch between dispensing and high velocity ejection modes." + icon_state = "lollipop" + var/candy = 30 + var/candymax = 30 + var/charge_delay = 10 + var/charging = FALSE + var/mode = DISPENSE_LOLLIPOP_MODE + + var/firedelay = 0 + var/hitspeed = 2 + var/hitdamage = 0 + var/emaggedhitdamage = 3 + +/obj/item/borg/lollipop/clown + emaggedhitdamage = 0 + +/obj/item/borg/lollipop/equipped() + . = ..() + check_amount() + +/obj/item/borg/lollipop/dropped() + . = ..() + check_amount() + +/obj/item/borg/lollipop/proc/check_amount() //Doesn't even use processing ticks. + if(charging) + return + if(candy < candymax) + addtimer(CALLBACK(src, .proc/charge_lollipops), charge_delay) + charging = TRUE + +/obj/item/borg/lollipop/proc/charge_lollipops() + candy++ + charging = FALSE + check_amount() + +/obj/item/borg/lollipop/proc/dispense(atom/A, mob/user) + if(candy <= 0) + to_chat(user, "No treats left in storage!") + return FALSE + var/turf/T = get_turf(A) + if(!T || !istype(T) || !isopenturf(T)) + return FALSE + if(isobj(A)) + var/obj/O = A + if(O.density) + return FALSE + + var/obj/item/reagent_containers/food/snacks/L + switch(mode) + if(DISPENSE_LOLLIPOP_MODE) + L = new /obj/item/reagent_containers/food/snacks/chewable/lollipop(T) + if(DISPENSE_ICECREAM_MODE) + L = new /obj/item/reagent_containers/food/snacks/icecream(T) + var/obj/item/reagent_containers/food/snacks/icecream/I = L + I.add_ice_cream("vanilla") + I.desc = "Eat the ice cream." + + var/into_hands = FALSE + if(ismob(A)) + var/mob/M = A + into_hands = M.put_in_hands(L) + + candy-- + check_amount() + + if(into_hands) + user.visible_message("[user] dispenses a treat into the hands of [A].", "You dispense a treat into the hands of [A].", "You hear a click.") + else + user.visible_message("[user] dispenses a treat.", "You dispense a treat.", "You hear a click.") + + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + return TRUE + +/obj/item/borg/lollipop/proc/shootL(atom/target, mob/living/user, params) + if(candy <= 0) + to_chat(user, "Not enough lollipops left!") + return FALSE + candy-- + var/obj/item/ammo_casing/caseless/lollipop/A = new /obj/item/ammo_casing/caseless/lollipop(src) + A.BB.damage = hitdamage + if(hitdamage) + A.BB.nodamage = FALSE + A.BB.speed = 0.5 + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + A.fire_casing(target, user, params, 0, 0, null, 0, src) + user.visible_message("[user] blasts a flying lollipop at [target]!") + check_amount() + +/obj/item/borg/lollipop/proc/shootG(atom/target, mob/living/user, params) //Most certainly a good idea. + if(candy <= 0) + to_chat(user, "Not enough gumballs left!") + return FALSE + candy-- + var/obj/item/ammo_casing/caseless/gumball/A = new /obj/item/ammo_casing/caseless/gumball(src) + A.BB.damage = hitdamage + if(hitdamage) + A.BB.nodamage = FALSE + A.BB.speed = 0.5 + A.BB.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) + playsound(src.loc, 'sound/weapons/bulletflyby3.ogg', 50, TRUE) + A.fire_casing(target, user, params, 0, 0, null, 0, src) + user.visible_message("[user] shoots a high-velocity gumball at [target]!") + check_amount() + +/obj/item/borg/lollipop/afterattack(atom/target, mob/living/user, proximity, click_params) + . = ..() + check_amount() + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(!R.cell.use(12)) + to_chat(user, "Not enough power.") + return FALSE + if(R.emagged) + hitdamage = emaggedhitdamage + switch(mode) + if(DISPENSE_LOLLIPOP_MODE, DISPENSE_ICECREAM_MODE) + if(!proximity) + return FALSE + dispense(target, user) + if(THROW_LOLLIPOP_MODE) + shootL(target, user, click_params) + if(THROW_GUMBALL_MODE) + shootG(target, user, click_params) + hitdamage = initial(hitdamage) + +/obj/item/borg/lollipop/attack_self(mob/living/user) + switch(mode) + if(DISPENSE_LOLLIPOP_MODE) + mode = THROW_LOLLIPOP_MODE + to_chat(user, "Module is now throwing lollipops.") + if(THROW_LOLLIPOP_MODE) + mode = THROW_GUMBALL_MODE + to_chat(user, "Module is now blasting gumballs.") + if(THROW_GUMBALL_MODE) + mode = DISPENSE_ICECREAM_MODE + to_chat(user, "Module is now dispensing ice cream.") + if(DISPENSE_ICECREAM_MODE) + mode = DISPENSE_LOLLIPOP_MODE + to_chat(user, "Module is now dispensing lollipops.") + ..() + +#undef DISPENSE_LOLLIPOP_MODE +#undef THROW_LOLLIPOP_MODE +#undef THROW_GUMBALL_MODE +#undef DISPENSE_ICECREAM_MODE + +/obj/item/ammo_casing/caseless/gumball + name = "Gumball" + desc = "Why are you seeing this?!" + projectile_type = /obj/projectile/bullet/reusable/gumball + click_cooldown_override = 2 + + +/obj/projectile/bullet/reusable/gumball + name = "gumball" + desc = "Oh noes! A fast-moving gumball!" + icon_state = "gumball" + ammo_type = /obj/item/reagent_containers/food/snacks/chewable/gumball/cyborg + nodamage = TRUE + +/obj/projectile/bullet/reusable/gumball/handle_drop() + if(!dropped) + var/turf/T = get_turf(src) + var/obj/item/reagent_containers/food/snacks/chewable/gumball/S = new ammo_type(T) + S.color = color + dropped = TRUE + +/obj/item/ammo_casing/caseless/lollipop //NEEDS RANDOMIZED COLOR LOGIC. + name = "Lollipop" + desc = "Why are you seeing this?!" + projectile_type = /obj/projectile/bullet/reusable/lollipop + click_cooldown_override = 2 + +/obj/projectile/bullet/reusable/lollipop + name = "lollipop" + desc = "Oh noes! A fast-moving lollipop!" + icon_state = "lollipop_1" + ammo_type = /obj/item/reagent_containers/food/snacks/chewable/lollipop/cyborg + var/color2 = rgb(0, 0, 0) + nodamage = TRUE + +/obj/projectile/bullet/reusable/lollipop/Initialize() + . = ..() + var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(src) + color2 = S.headcolor + var/mutable_appearance/head = mutable_appearance('icons/obj/projectiles.dmi', "lollipop_2") + head.color = color2 + add_overlay(head) + +/obj/projectile/bullet/reusable/lollipop/handle_drop() + if(!dropped) + var/turf/T = get_turf(src) + var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(T) + S.change_head_color(color2) + dropped = TRUE + +#define PKBORG_DAMPEN_CYCLE_DELAY 20 + +//Peacekeeper Cyborg Projectile Dampenening Field +/obj/item/borg/projectile_dampen + name = "\improper Hyperkinetic Dampening projector" + desc = "A device that projects a dampening field that weakens kinetic energy above a certain threshold. Projects a field that drains power per second while active, that will weaken and slow damaging projectiles inside its field. Still being a prototype, it tends to induce a charge on ungrounded metallic surfaces." + icon = 'icons/obj/device.dmi' + icon_state = "shield" + var/maxenergy = 1500 + var/energy = 1500 + var/energy_recharge = 7.5 + var/energy_recharge_cyborg_drain_coefficient = 0.4 + var/cyborg_cell_critical_percentage = 0.05 + var/mob/living/silicon/robot/host = null + var/datum/proximity_monitor/advanced/dampening_field + var/projectile_damage_coefficient = 0.5 + var/projectile_damage_tick_ecost_coefficient = 2 //Lasers get half their damage chopped off, drains 50 power/tick. Note that fields are processed 5 times per second. + var/projectile_speed_coefficient = 1.5 //Higher the coefficient slower the projectile. + var/projectile_tick_speed_ecost = 15 + var/list/obj/projectile/tracked + var/image/projectile_effect + var/field_radius = 3 + var/active = FALSE + var/cycle_delay = 0 + +/obj/item/borg/projectile_dampen/debug + maxenergy = 50000 + energy = 50000 + energy_recharge = 5000 + +/obj/item/borg/projectile_dampen/Initialize() + . = ..() + projectile_effect = image('icons/effects/fields.dmi', "projectile_dampen_effect") + tracked = list() + icon_state = "shield0" + START_PROCESSING(SSfastprocess, src) + host = loc + +/obj/item/borg/projectile_dampen/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/item/borg/projectile_dampen/attack_self(mob/user) + if(cycle_delay > world.time) + to_chat(user, "[src] is still recycling its projectors!") + return + cycle_delay = world.time + PKBORG_DAMPEN_CYCLE_DELAY + if(!active) + if(!user.has_buckled_mobs()) + activate_field() + else + to_chat(user, "[src]'s safety cutoff prevents you from activating it due to living beings being ontop of you!") + else + deactivate_field() + update_icon() + to_chat(user, "You [active? "activate":"deactivate"] [src].") + +/obj/item/borg/projectile_dampen/update_icon_state() + icon_state = "[initial(icon_state)][active]" + +/obj/item/borg/projectile_dampen/proc/activate_field() + if(istype(dampening_field)) + QDEL_NULL(dampening_field) + dampening_field = make_field(/datum/proximity_monitor/advanced/peaceborg_dampener, list("current_range" = field_radius, "host" = src, "projector" = src)) + var/mob/living/silicon/robot/owner = get_host() + if(owner) + owner.module.allow_riding = FALSE + active = TRUE + +/obj/item/borg/projectile_dampen/proc/deactivate_field() + QDEL_NULL(dampening_field) + visible_message("\The [src] shuts off!") + for(var/P in tracked) + restore_projectile(P) + active = FALSE + + var/mob/living/silicon/robot/owner = get_host() + if(owner) + owner.module.allow_riding = TRUE + +/obj/item/borg/projectile_dampen/proc/get_host() + if(istype(host)) + return host + else + if(iscyborg(host.loc)) + return host.loc + return null + +/obj/item/borg/projectile_dampen/dropped() + . = ..() + host = loc + +/obj/item/borg/projectile_dampen/equipped() + . = ..() + host = loc + +/obj/item/borg/projectile_dampen/cyborg_unequip(mob/user) + deactivate_field() + . = ..() + +/obj/item/borg/projectile_dampen/on_mob_death() + deactivate_field() + . = ..() + +/obj/item/borg/projectile_dampen/process() + process_recharge() + process_usage() + update_location() + +/obj/item/borg/projectile_dampen/proc/update_location() + if(dampening_field) + dampening_field.HandleMove() + +/obj/item/borg/projectile_dampen/proc/process_usage() + var/usage = 0 + for(var/I in tracked) + var/obj/projectile/P = I + if(!P.stun && P.nodamage) //No damage + continue + usage += projectile_tick_speed_ecost + usage += (tracked[I] * projectile_damage_tick_ecost_coefficient) + energy = clamp(energy - usage, 0, maxenergy) + if(energy <= 0) + deactivate_field() + visible_message("[src] blinks \"ENERGY DEPLETED\".") + +/obj/item/borg/projectile_dampen/proc/process_recharge() + if(!istype(host)) + if(iscyborg(host.loc)) + host = host.loc + else + energy = clamp(energy + energy_recharge, 0, maxenergy) + return + if(host.cell && (host.cell.charge >= (host.cell.maxcharge * cyborg_cell_critical_percentage)) && (energy < maxenergy)) + host.cell.use(energy_recharge*energy_recharge_cyborg_drain_coefficient) + energy += energy_recharge + +/obj/item/borg/projectile_dampen/proc/dampen_projectile(obj/projectile/P, track_projectile = TRUE) + if(tracked[P]) + return + if(track_projectile) + tracked[P] = P.damage + P.damage *= projectile_damage_coefficient + P.speed *= projectile_speed_coefficient + P.add_overlay(projectile_effect) + +/obj/item/borg/projectile_dampen/proc/restore_projectile(obj/projectile/P) + tracked -= P + P.damage *= (1/projectile_damage_coefficient) + P.speed *= (1/projectile_speed_coefficient) + P.cut_overlay(projectile_effect) + +/********************************************************************** + HUD/SIGHT things +***********************************************************************/ +/obj/item/borg/sight + var/sight_mode = null + + +/obj/item/borg/sight/xray + name = "\proper X-ray vision" + icon = 'icons/obj/decals.dmi' + icon_state = "securearea" + sight_mode = BORGXRAY + +/obj/item/borg/sight/thermal + name = "\proper thermal vision" + sight_mode = BORGTHERM + icon_state = "thermal" + + +/obj/item/borg/sight/meson + name = "\proper meson vision" + sight_mode = BORGMESON + icon_state = "meson" + +/obj/item/borg/sight/material + name = "\proper material vision" + sight_mode = BORGMATERIAL + icon_state = "material" + +/obj/item/borg/sight/hud + name = "hud" + var/obj/item/clothing/glasses/hud/hud = null + + +/obj/item/borg/sight/hud/med + name = "medical hud" + icon_state = "healthhud" + +/obj/item/borg/sight/hud/med/Initialize() + . = ..() + hud = new /obj/item/clothing/glasses/hud/health(src) + + +/obj/item/borg/sight/hud/sec + name = "security hud" + icon_state = "securityhud" + +/obj/item/borg/sight/hud/sec/Initialize() + . = ..() + hud = new /obj/item/clothing/glasses/hud/security(src) + + +/********************************************************************** + Borg apparatus +***********************************************************************/ +//These are tools that can hold only specific items. For example, the mediborg gets one that can only hold beakers and bottles. + +/obj/item/borg/apparatus/ + name = "unknown storage apparatus" + desc = "This device seems nonfunctional." + icon = 'icons/mob/robot_items.dmi' + icon_state = "hugmodule" + var/obj/item/stored + var/list/storable = list() + +/obj/item/borg/apparatus/Initialize() + . = ..() + RegisterSignal(loc.loc, COMSIG_BORG_SAFE_DECONSTRUCT, .proc/safedecon) + +/obj/item/borg/apparatus/Destroy() + if(stored) + qdel(stored) + . = ..() + +///If we're safely deconstructed, we put the item neatly onto the ground, rather than deleting it. +/obj/item/borg/apparatus/proc/safedecon() + if(stored) + stored.forceMove(get_turf(src)) + stored = null + +/obj/item/borg/apparatus/Exited(atom/A) + if(A == stored) //sanity check + UnregisterSignal(stored, COMSIG_ATOM_UPDATE_ICON) + stored = null + update_icon() + . = ..() + +///A right-click verb, for those not using hotkey mode. +/obj/item/borg/apparatus/verb/verb_dropHeld() + set category = "Object" + set name = "Drop" + + if(usr != loc || !stored) + return + stored.forceMove(get_turf(usr)) + return + +/obj/item/borg/apparatus/attack_self(mob/living/silicon/robot/user) + if(!stored) + return ..() + if(user.client?.keys_held["Alt"]) + stored.forceMove(get_turf(user)) + return + stored.attack_self(user) + +/obj/item/borg/apparatus/pre_attack(atom/A, mob/living/user, params) + if(!stored) + var/itemcheck = FALSE + for(var/i in storable) + if(istype(A, i)) + itemcheck = TRUE + break + if(itemcheck) + var/obj/item/O = A + O.forceMove(src) + stored = O + RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) + update_icon() + return + else + stored.melee_attack_chain(user, A, params) + return + . = ..() + +/obj/item/borg/apparatus/attackby(obj/item/W, mob/user, params) + if(stored) + W.melee_attack_chain(user, stored, params) + return + . = ..() + +///////////////// +//beaker holder// +///////////////// + +/obj/item/borg/apparatus/beaker + name = "beaker storage apparatus" + desc = "A special apparatus for carrying beakers without spilling the contents. Alt-Z or right-click to drop the beaker." + icon_state = "borg_beaker_apparatus" + storable = list(/obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle) + +/obj/item/borg/apparatus/beaker/Initialize() + . = ..() + stored = new /obj/item/reagent_containers/glass/beaker/large(src) + RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) + update_icon() + +/obj/item/borg/apparatus/beaker/Destroy() + if(stored) + var/obj/item/reagent_containers/C = stored + C.SplashReagents(get_turf(src)) + qdel(stored) + . = ..() + +/obj/item/borg/apparatus/beaker/examine() + . = ..() + if(stored) + var/obj/item/reagent_containers/C = stored + . += "The apparatus currently has [C] secured, which contains:" + if(length(C.reagents.reagent_list)) + for(var/datum/reagent/R in C.reagents.reagent_list) + . += "[R.volume] units of [R.name]" + else + . += "Nothing." + +/obj/item/borg/apparatus/beaker/update_overlays() + . = ..() + var/mutable_appearance/arm = mutable_appearance(icon = icon, icon_state = "borg_beaker_apparatus_arm") + if(stored) + COMPILE_OVERLAYS(stored) + stored.pixel_x = 0 + stored.pixel_y = 0 + var/mutable_appearance/stored_copy = new /mutable_appearance(stored) + if(istype(stored, /obj/item/reagent_containers/glass/beaker)) + arm.pixel_y = arm.pixel_y - 3 + stored_copy.layer = FLOAT_LAYER + stored_copy.plane = FLOAT_PLANE + . += stored_copy + else + arm.pixel_y = arm.pixel_y - 5 + . += arm + +/obj/item/borg/apparatus/beaker/attack_self(mob/living/silicon/robot/user) + if(stored && !user.client?.keys_held["Alt"] && user.a_intent != "help") + var/obj/item/reagent_containers/C = stored + C.SplashReagents(get_turf(user)) + loc.visible_message("[user] spills the contents of the [C] all over the floor.") + return + . = ..() + +/obj/item/borg/apparatus/beaker/extra + name = "secondary beaker storage apparatus" + desc = "A supplementary beaker storage apparatus." + +/obj/item/borg/apparatus/beaker/service + name = "beverage storage apparatus" + desc = "A special apparatus for carrying drinks without spilling the contents. Alt-Z or right-click to drop the beaker." + icon_state = "borg_beaker_apparatus" + storable = list(/obj/item/reagent_containers/food/drinks/, + /obj/item/reagent_containers/food/condiment) + +/obj/item/borg/apparatus/beaker/service/Initialize() + . = ..() + stored = new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) + update_icon() + +//////////////////// +//engi part holder// +//////////////////// + +/obj/item/borg/apparatus/circuit + name = "circuit manipulation apparatus" + desc = "A special apparatus for carrying and manipulating circuit boards. Alt-Z or right-click to drop the stored object." + icon_state = "borg_hardware_apparatus" + storable = list(/obj/item/circuitboard, + /obj/item/electronics) + +/obj/item/borg/apparatus/circuit/Initialize() + . = ..() + update_icon() + +/obj/item/borg/apparatus/circuit/update_overlays() + . = ..() + var/mutable_appearance/arm = mutable_appearance(icon, "borg_hardware_apparatus_arm1") + if(stored) + COMPILE_OVERLAYS(stored) + stored.pixel_x = -3 + stored.pixel_y = 0 + if(!istype(stored, /obj/item/circuitboard)) + arm.icon_state = "borg_hardware_apparatus_arm2" + var/mutable_appearance/stored_copy = new /mutable_appearance(stored) + stored_copy.layer = FLOAT_LAYER + stored_copy.plane = FLOAT_PLANE + . += stored_copy + . += arm + +/obj/item/borg/apparatus/circuit/examine() + . = ..() + if(stored) + . += "The apparatus currently has [stored] secured." + +/obj/item/borg/apparatus/circuit/pre_attack(atom/A, mob/living/user, params) + . = ..() + if(istype(A, /obj/item/ai_module) && !stored) //If an admin wants a borg to upload laws, who am I to stop them? Otherwise, we can hint that it fails + to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.") diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index 9e04802698b..86595e46924 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -1,401 +1,401 @@ - - -//The robot bodyparts have been moved to code/module/surgery/bodyparts/robot_bodyparts.dm - - -/obj/item/robot_suit - name = "cyborg endoskeleton" - desc = "A complex metal backbone with standard limb sockets and pseudomuscle anchors." - icon = 'icons/mob/augmentation/augments.dmi' - icon_state = "robo_suit" - var/obj/item/bodypart/l_arm/robot/l_arm = null - var/obj/item/bodypart/r_arm/robot/r_arm = null - var/obj/item/bodypart/l_leg/robot/l_leg = null - var/obj/item/bodypart/r_leg/robot/r_leg = null - var/obj/item/bodypart/chest/robot/chest = null - var/obj/item/bodypart/head/robot/head = null - - var/created_name = "" - var/mob/living/silicon/ai/forced_ai - var/locomotion = 1 - var/lawsync = 1 - var/aisync = 1 - var/panel_locked = TRUE - -/obj/item/robot_suit/Initialize() - . = ..() - update_icon() - -/obj/item/robot_suit/prebuilt/Initialize() - . = ..() - l_arm = new(src) - r_arm = new(src) - l_leg = new(src) - r_leg = new(src) - head = new(src) - head.flash1 = new(head) - head.flash2 = new(head) - chest = new(src) - chest.wired = TRUE - chest.cell = new /obj/item/stock_parts/cell/high/plus(chest) - update_icon() - -/obj/item/robot_suit/update_overlays() - . = ..() - if(l_arm) - . += "[l_arm.icon_state]+o" - if(r_arm) - . += "[r_arm.icon_state]+o" - if(chest) - . += "[chest.icon_state]+o" - if(l_leg) - . += "[l_leg.icon_state]+o" - if(r_leg) - . += "[r_leg.icon_state]+o" - if(head) - . += "[head.icon_state]+o" - -/obj/item/robot_suit/proc/check_completion() - if(src.l_arm && src.r_arm) - if(src.l_leg && src.r_leg) - if(src.chest && src.head) - SSblackbox.record_feedback("amount", "cyborg_frames_built", 1) - return 1 - return 0 - -/obj/item/robot_suit/wrench_act(mob/living/user, obj/item/I) //Deconstucts empty borg shell. Flashes remain unbroken because they haven't been used yet - . = ..() - var/turf/T = get_turf(src) - if(l_leg || r_leg || chest || l_arm || r_arm || head) - if(I.use_tool(src, user, 5, volume=50)) - if(l_leg) - l_leg.forceMove(T) - l_leg = null - if(r_leg) - r_leg.forceMove(T) - r_leg = null - if(chest) - if (chest.cell) //Sanity check. - chest.cell.forceMove(T) - chest.cell = null - chest.forceMove(T) - new /obj/item/stack/cable_coil(T, 1) - chest.wired = FALSE - chest = null - if(l_arm) - l_arm.forceMove(T) - l_arm = null - if(r_arm) - r_arm.forceMove(T) - r_arm = null - if(head) - head.forceMove(T) - head.flash1.forceMove(T) - head.flash1 = null - head.flash2.forceMove(T) - head.flash2 = null - head = null - to_chat(user, "You disassemble the cyborg shell.") - else - to_chat(user, "There is nothing to remove from the endoskeleton!") - update_icon() - -/obj/item/robot_suit/proc/put_in_hand_or_drop(mob/living/user, obj/item/I) //normal put_in_hands() drops the item ontop of the player, this drops it at the suit's loc - if(!user.put_in_hands(I)) - I.forceMove(drop_location()) - return FALSE - return TRUE - -/obj/item/robot_suit/screwdriver_act(mob/living/user, obj/item/I) //Swaps the power cell if you're holding a new one in your other hand. - . = ..() - if(.) - return TRUE - - if(!chest) //can't remove a cell if there's no chest to remove it from. - to_chat(user, "[src] has no attached torso!") - return - - var/obj/item/stock_parts/cell/temp_cell = user.is_holding_item_of_type(/obj/item/stock_parts/cell) - var/swap_failed - if(!temp_cell) //if we're not holding a cell - swap_failed = TRUE - else if(!user.transferItemToLoc(temp_cell, chest)) - swap_failed = TRUE - to_chat(user, "[temp_cell] is stuck to your hand, you can't put it in [src]!") - - if(chest.cell) //drop the chest's current cell no matter what. - put_in_hand_or_drop(user, chest.cell) - - if(swap_failed) //we didn't transfer any new items. - if(chest.cell) //old cell ejected, nothing inserted. - to_chat(user, "You remove [chest.cell] from [src].") - chest.cell = null - else - to_chat(user, "The power cell slot in [src]'s torso is empty!") - return - - to_chat(user, "You [chest.cell ? "replace [src]'s [chest.cell.name] with [temp_cell]" : "insert [temp_cell] into [src]"].") - chest.cell = temp_cell - return TRUE - -/obj/item/robot_suit/attackby(obj/item/W, mob/user, params) - - if(istype(W, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = W - if(!l_arm && !r_arm && !l_leg && !r_leg && !chest && !head) - if (M.use(1)) - var/obj/item/bot_assembly/ed209/B = new - B.forceMove(drop_location()) - to_chat(user, "You arm the robot frame.") - var/holding_this = user.get_inactive_held_item()==src - qdel(src) - if (holding_this) - user.put_in_inactive_hand(B) - else - to_chat(user, "You need one sheet of metal to start building ED-209!") - return - else if(istype(W, /obj/item/bodypart/l_leg/robot)) - if(l_leg) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - l_leg = W - update_icon() - - else if(istype(W, /obj/item/bodypart/r_leg/robot)) - if(src.r_leg) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - r_leg = W - update_icon() - - else if(istype(W, /obj/item/bodypart/l_arm/robot)) - if(l_arm) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - l_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/r_arm/robot)) - if(r_arm) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state)//in case it is a dismembered robotic limb - W.cut_overlays() - r_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/chest/robot)) - var/obj/item/bodypart/chest/robot/CH = W - if(chest) - return - if(CH.wired && CH.cell) - if(!user.transferItemToLoc(CH, src)) - return - CH.icon_state = initial(CH.icon_state) //in case it is a dismembered robotic limb - CH.cut_overlays() - chest = CH - update_icon() - else if(!CH.wired) - to_chat(user, "You need to attach wires to it first!") - else - to_chat(user, "You need to attach a cell to it first!") - - else if(istype(W, /obj/item/bodypart/head/robot)) - var/obj/item/bodypart/head/robot/HD = W - for(var/X in HD.contents) - if(istype(X, /obj/item/organ)) - to_chat(user, "There are organs inside [HD]!") - return - if(head) - return - if(HD.flash2 && HD.flash1) - if(!user.transferItemToLoc(HD, src)) - return - HD.icon_state = initial(HD.icon_state)//in case it is a dismembered robotic limb - HD.cut_overlays() - head = HD - update_icon() - else - to_chat(user, "You need to attach a flash to it first!") - - else if (W.tool_behaviour == TOOL_MULTITOOL) - if(check_completion()) - Interact(user) - else - to_chat(user, "The endoskeleton must be assembled before debugging can begin!") - - else if(istype(W, /obj/item/mmi)) - var/obj/item/mmi/M = W - if(check_completion()) - if(!chest.cell) - to_chat(user, "The endoskeleton still needs a power cell!") - return - if(!isturf(loc)) - to_chat(user, "You can't put [M] in, the frame has to be standing on the ground to be perfectly precise!") - return - if(!M.brain_check(user)) - return - - var/mob/living/brain/B = M.brainmob - if(is_banned_from(B.ckey, "Cyborg") || QDELETED(src) || QDELETED(B) || QDELETED(user) || QDELETED(M) || !Adjacent(user)) - if(!QDELETED(M)) - to_chat(user, "This [M.name] does not seem to fit!") - return - if(!user.temporarilyRemoveItemFromInventory(W)) - return - - var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/nocell(get_turf(loc)) - if(!O) - return - if(M.laws && M.laws.id != DEFAULT_AI_LAWID) - aisync = 0 - lawsync = 0 - O.laws = M.laws - M.laws.associate(O) - - O.invisibility = 0 - //Transfer debug settings to new mob - O.custom_name = created_name - O.locked = panel_locked - if(!aisync) - lawsync = 0 - O.connected_ai = null - else - O.notify_ai(NEW_BORG) - if(forced_ai) - O.connected_ai = forced_ai - if(!lawsync) - O.lawupdate = 0 - if(M.laws.id == DEFAULT_AI_LAWID) - O.make_laws() - - SSticker.mode.remove_antag_for_borging(B.mind) - O.job = "Cyborg" - - O.cell = chest.cell - chest.cell.forceMove(O) - chest.cell = null - W.forceMove(O)//Should fix cybros run time erroring when blown up. It got deleted before, along with the frame. - if(O.mmi) //we delete the mmi created by robot/New() - qdel(O.mmi) - O.mmi = W //and give the real mmi to the borg. - - O.updatename(B.client) - - B.mind.transfer_to(O) - - if(O.mind && O.mind.special_role) - O.mind.store_memory("As a cyborg, you must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") - to_chat(O, "You have been robotized!") - to_chat(O, "You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") - - SSblackbox.record_feedback("amount", "cyborg_birth", 1) - forceMove(O) - O.robot_suit = src - - log_game("[key_name(user)] has put the MMI/posibrain of [key_name(M.brainmob)] into a cyborg shell at [AREACOORD(src)]") - - if(!locomotion) - O.lockcharge = TRUE - O.update_mobility() - to_chat(O, "Error: Servo motors unresponsive.") - - else - to_chat(user, "The MMI must go in after everything else!") - - else if(istype(W, /obj/item/borg/upgrade/ai)) - var/obj/item/borg/upgrade/ai/M = W - if(check_completion()) - if(!isturf(loc)) - to_chat(user, "You cannot install[M], the frame has to be standing on the ground to be perfectly precise!") - return - if(!user.temporarilyRemoveItemFromInventory(M)) - to_chat(user, "[M] is stuck to your hand!") - return - qdel(M) - var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/shell(get_turf(src)) - - if(!aisync) - lawsync = FALSE - O.connected_ai = null - else - if(forced_ai) - O.connected_ai = forced_ai - O.notify_ai(AI_SHELL) - if(!lawsync) - O.lawupdate = FALSE - O.make_laws() - - O.cell = chest.cell - chest.cell.forceMove(O) - chest.cell = null - O.locked = panel_locked - O.job = "Cyborg" - forceMove(O) - O.robot_suit = src - if(!locomotion) - O.lockcharge = TRUE - O.update_mobility() - - else if(istype(W, /obj/item/pen)) - to_chat(user, "You need to use a multitool to name [src]!") - else - return ..() - -/obj/item/robot_suit/proc/Interact(mob/user) - var/t1 = "Designation: [(created_name ? "[created_name]" : "Default Cyborg")]
                \n" - t1 += "Master AI: [(forced_ai ? "[forced_ai.name]" : "Automatic")]

                \n" - - t1 += "LawSync Port: [(lawsync ? "Open" : "Closed")]
                \n" - t1 += "AI Connection Port: [(aisync ? "Open" : "Closed")]
                \n" - t1 += "Servo Motor Functions: [(locomotion ? "Unlocked" : "Locked")]
                \n" - t1 += "Panel Lock: [(panel_locked ? "Engaged" : "Disengaged")]
                \n" - var/datum/browser/popup = new(user, "robotdebug", "Cyborg Boot Debug", 310, 220) - popup.set_content(t1) - popup.open() - -/obj/item/robot_suit/Topic(href, href_list) - if(usr.incapacitated() || !Adjacent(usr)) - return - - var/mob/living/living_user = usr - var/obj/item/item_in_hand = living_user.get_active_held_item() - if(!item_in_hand || item_in_hand.tool_behaviour != TOOL_MULTITOOL) - to_chat(living_user, "You need a multitool!") - return - - if(href_list["Name"]) - var/new_name = reject_bad_name(input(usr, "Enter new designation. Set to blank to reset to default.", "Cyborg Debug", src.created_name),1) - if(!in_range(src, usr) && src.loc != usr) - return - if(new_name) - created_name = new_name - else - created_name = "" - - else if(href_list["Master"]) - forced_ai = select_active_ai(usr, z) - if(!forced_ai) - to_chat(usr, "No active AIs detected.") - - else if(href_list["Law"]) - lawsync = !lawsync - else if(href_list["AI"]) - aisync = !aisync - else if(href_list["Loco"]) - locomotion = !locomotion - else if(href_list["Panel"]) - panel_locked = !panel_locked - - add_fingerprint(usr) - Interact(usr) + + +//The robot bodyparts have been moved to code/module/surgery/bodyparts/robot_bodyparts.dm + + +/obj/item/robot_suit + name = "cyborg endoskeleton" + desc = "A complex metal backbone with standard limb sockets and pseudomuscle anchors." + icon = 'icons/mob/augmentation/augments.dmi' + icon_state = "robo_suit" + var/obj/item/bodypart/l_arm/robot/l_arm = null + var/obj/item/bodypart/r_arm/robot/r_arm = null + var/obj/item/bodypart/l_leg/robot/l_leg = null + var/obj/item/bodypart/r_leg/robot/r_leg = null + var/obj/item/bodypart/chest/robot/chest = null + var/obj/item/bodypart/head/robot/head = null + + var/created_name = "" + var/mob/living/silicon/ai/forced_ai + var/locomotion = 1 + var/lawsync = 1 + var/aisync = 1 + var/panel_locked = TRUE + +/obj/item/robot_suit/Initialize() + . = ..() + update_icon() + +/obj/item/robot_suit/prebuilt/Initialize() + . = ..() + l_arm = new(src) + r_arm = new(src) + l_leg = new(src) + r_leg = new(src) + head = new(src) + head.flash1 = new(head) + head.flash2 = new(head) + chest = new(src) + chest.wired = TRUE + chest.cell = new /obj/item/stock_parts/cell/high/plus(chest) + update_icon() + +/obj/item/robot_suit/update_overlays() + . = ..() + if(l_arm) + . += "[l_arm.icon_state]+o" + if(r_arm) + . += "[r_arm.icon_state]+o" + if(chest) + . += "[chest.icon_state]+o" + if(l_leg) + . += "[l_leg.icon_state]+o" + if(r_leg) + . += "[r_leg.icon_state]+o" + if(head) + . += "[head.icon_state]+o" + +/obj/item/robot_suit/proc/check_completion() + if(src.l_arm && src.r_arm) + if(src.l_leg && src.r_leg) + if(src.chest && src.head) + SSblackbox.record_feedback("amount", "cyborg_frames_built", 1) + return 1 + return 0 + +/obj/item/robot_suit/wrench_act(mob/living/user, obj/item/I) //Deconstucts empty borg shell. Flashes remain unbroken because they haven't been used yet + . = ..() + var/turf/T = get_turf(src) + if(l_leg || r_leg || chest || l_arm || r_arm || head) + if(I.use_tool(src, user, 5, volume=50)) + if(l_leg) + l_leg.forceMove(T) + l_leg = null + if(r_leg) + r_leg.forceMove(T) + r_leg = null + if(chest) + if (chest.cell) //Sanity check. + chest.cell.forceMove(T) + chest.cell = null + chest.forceMove(T) + new /obj/item/stack/cable_coil(T, 1) + chest.wired = FALSE + chest = null + if(l_arm) + l_arm.forceMove(T) + l_arm = null + if(r_arm) + r_arm.forceMove(T) + r_arm = null + if(head) + head.forceMove(T) + head.flash1.forceMove(T) + head.flash1 = null + head.flash2.forceMove(T) + head.flash2 = null + head = null + to_chat(user, "You disassemble the cyborg shell.") + else + to_chat(user, "There is nothing to remove from the endoskeleton!") + update_icon() + +/obj/item/robot_suit/proc/put_in_hand_or_drop(mob/living/user, obj/item/I) //normal put_in_hands() drops the item ontop of the player, this drops it at the suit's loc + if(!user.put_in_hands(I)) + I.forceMove(drop_location()) + return FALSE + return TRUE + +/obj/item/robot_suit/screwdriver_act(mob/living/user, obj/item/I) //Swaps the power cell if you're holding a new one in your other hand. + . = ..() + if(.) + return TRUE + + if(!chest) //can't remove a cell if there's no chest to remove it from. + to_chat(user, "[src] has no attached torso!") + return + + var/obj/item/stock_parts/cell/temp_cell = user.is_holding_item_of_type(/obj/item/stock_parts/cell) + var/swap_failed + if(!temp_cell) //if we're not holding a cell + swap_failed = TRUE + else if(!user.transferItemToLoc(temp_cell, chest)) + swap_failed = TRUE + to_chat(user, "[temp_cell] is stuck to your hand, you can't put it in [src]!") + + if(chest.cell) //drop the chest's current cell no matter what. + put_in_hand_or_drop(user, chest.cell) + + if(swap_failed) //we didn't transfer any new items. + if(chest.cell) //old cell ejected, nothing inserted. + to_chat(user, "You remove [chest.cell] from [src].") + chest.cell = null + else + to_chat(user, "The power cell slot in [src]'s torso is empty!") + return + + to_chat(user, "You [chest.cell ? "replace [src]'s [chest.cell.name] with [temp_cell]" : "insert [temp_cell] into [src]"].") + chest.cell = temp_cell + return TRUE + +/obj/item/robot_suit/attackby(obj/item/W, mob/user, params) + + if(istype(W, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = W + if(!l_arm && !r_arm && !l_leg && !r_leg && !chest && !head) + if (M.use(1)) + var/obj/item/bot_assembly/ed209/B = new + B.forceMove(drop_location()) + to_chat(user, "You arm the robot frame.") + var/holding_this = user.get_inactive_held_item()==src + qdel(src) + if (holding_this) + user.put_in_inactive_hand(B) + else + to_chat(user, "You need one sheet of metal to start building ED-209!") + return + else if(istype(W, /obj/item/bodypart/l_leg/robot)) + if(l_leg) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + l_leg = W + update_icon() + + else if(istype(W, /obj/item/bodypart/r_leg/robot)) + if(src.r_leg) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + r_leg = W + update_icon() + + else if(istype(W, /obj/item/bodypart/l_arm/robot)) + if(l_arm) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + l_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/r_arm/robot)) + if(r_arm) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state)//in case it is a dismembered robotic limb + W.cut_overlays() + r_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/chest/robot)) + var/obj/item/bodypart/chest/robot/CH = W + if(chest) + return + if(CH.wired && CH.cell) + if(!user.transferItemToLoc(CH, src)) + return + CH.icon_state = initial(CH.icon_state) //in case it is a dismembered robotic limb + CH.cut_overlays() + chest = CH + update_icon() + else if(!CH.wired) + to_chat(user, "You need to attach wires to it first!") + else + to_chat(user, "You need to attach a cell to it first!") + + else if(istype(W, /obj/item/bodypart/head/robot)) + var/obj/item/bodypart/head/robot/HD = W + for(var/X in HD.contents) + if(istype(X, /obj/item/organ)) + to_chat(user, "There are organs inside [HD]!") + return + if(head) + return + if(HD.flash2 && HD.flash1) + if(!user.transferItemToLoc(HD, src)) + return + HD.icon_state = initial(HD.icon_state)//in case it is a dismembered robotic limb + HD.cut_overlays() + head = HD + update_icon() + else + to_chat(user, "You need to attach a flash to it first!") + + else if (W.tool_behaviour == TOOL_MULTITOOL) + if(check_completion()) + Interact(user) + else + to_chat(user, "The endoskeleton must be assembled before debugging can begin!") + + else if(istype(W, /obj/item/mmi)) + var/obj/item/mmi/M = W + if(check_completion()) + if(!chest.cell) + to_chat(user, "The endoskeleton still needs a power cell!") + return + if(!isturf(loc)) + to_chat(user, "You can't put [M] in, the frame has to be standing on the ground to be perfectly precise!") + return + if(!M.brain_check(user)) + return + + var/mob/living/brain/B = M.brainmob + if(is_banned_from(B.ckey, "Cyborg") || QDELETED(src) || QDELETED(B) || QDELETED(user) || QDELETED(M) || !Adjacent(user)) + if(!QDELETED(M)) + to_chat(user, "This [M.name] does not seem to fit!") + return + if(!user.temporarilyRemoveItemFromInventory(W)) + return + + var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/nocell(get_turf(loc)) + if(!O) + return + if(M.laws && M.laws.id != DEFAULT_AI_LAWID) + aisync = 0 + lawsync = 0 + O.laws = M.laws + M.laws.associate(O) + + O.invisibility = 0 + //Transfer debug settings to new mob + O.custom_name = created_name + O.locked = panel_locked + if(!aisync) + lawsync = 0 + O.connected_ai = null + else + O.notify_ai(NEW_BORG) + if(forced_ai) + O.connected_ai = forced_ai + if(!lawsync) + O.lawupdate = 0 + if(M.laws.id == DEFAULT_AI_LAWID) + O.make_laws() + + SSticker.mode.remove_antag_for_borging(B.mind) + O.job = "Cyborg" + + O.cell = chest.cell + chest.cell.forceMove(O) + chest.cell = null + W.forceMove(O)//Should fix cybros run time erroring when blown up. It got deleted before, along with the frame. + if(O.mmi) //we delete the mmi created by robot/New() + qdel(O.mmi) + O.mmi = W //and give the real mmi to the borg. + + O.updatename(B.client) + + B.mind.transfer_to(O) + + if(O.mind && O.mind.special_role) + O.mind.store_memory("As a cyborg, you must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") + to_chat(O, "You have been robotized!") + to_chat(O, "You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") + + SSblackbox.record_feedback("amount", "cyborg_birth", 1) + forceMove(O) + O.robot_suit = src + + log_game("[key_name(user)] has put the MMI/posibrain of [key_name(M.brainmob)] into a cyborg shell at [AREACOORD(src)]") + + if(!locomotion) + O.lockcharge = TRUE + O.update_mobility() + to_chat(O, "Error: Servo motors unresponsive.") + + else + to_chat(user, "The MMI must go in after everything else!") + + else if(istype(W, /obj/item/borg/upgrade/ai)) + var/obj/item/borg/upgrade/ai/M = W + if(check_completion()) + if(!isturf(loc)) + to_chat(user, "You cannot install[M], the frame has to be standing on the ground to be perfectly precise!") + return + if(!user.temporarilyRemoveItemFromInventory(M)) + to_chat(user, "[M] is stuck to your hand!") + return + qdel(M) + var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/shell(get_turf(src)) + + if(!aisync) + lawsync = FALSE + O.connected_ai = null + else + if(forced_ai) + O.connected_ai = forced_ai + O.notify_ai(AI_SHELL) + if(!lawsync) + O.lawupdate = FALSE + O.make_laws() + + O.cell = chest.cell + chest.cell.forceMove(O) + chest.cell = null + O.locked = panel_locked + O.job = "Cyborg" + forceMove(O) + O.robot_suit = src + if(!locomotion) + O.lockcharge = TRUE + O.update_mobility() + + else if(istype(W, /obj/item/pen)) + to_chat(user, "You need to use a multitool to name [src]!") + else + return ..() + +/obj/item/robot_suit/proc/Interact(mob/user) + var/t1 = "Designation: [(created_name ? "[created_name]" : "Default Cyborg")]
                \n" + t1 += "Master AI: [(forced_ai ? "[forced_ai.name]" : "Automatic")]

                \n" + + t1 += "LawSync Port: [(lawsync ? "Open" : "Closed")]
                \n" + t1 += "AI Connection Port: [(aisync ? "Open" : "Closed")]
                \n" + t1 += "Servo Motor Functions: [(locomotion ? "Unlocked" : "Locked")]
                \n" + t1 += "Panel Lock: [(panel_locked ? "Engaged" : "Disengaged")]
                \n" + var/datum/browser/popup = new(user, "robotdebug", "Cyborg Boot Debug", 310, 220) + popup.set_content(t1) + popup.open() + +/obj/item/robot_suit/Topic(href, href_list) + if(usr.incapacitated() || !Adjacent(usr)) + return + + var/mob/living/living_user = usr + var/obj/item/item_in_hand = living_user.get_active_held_item() + if(!item_in_hand || item_in_hand.tool_behaviour != TOOL_MULTITOOL) + to_chat(living_user, "You need a multitool!") + return + + if(href_list["Name"]) + var/new_name = reject_bad_name(input(usr, "Enter new designation. Set to blank to reset to default.", "Cyborg Debug", src.created_name),1) + if(!in_range(src, usr) && src.loc != usr) + return + if(new_name) + created_name = new_name + else + created_name = "" + + else if(href_list["Master"]) + forced_ai = select_active_ai(usr, z) + if(!forced_ai) + to_chat(usr, "No active AIs detected.") + + else if(href_list["Law"]) + lawsync = !lawsync + else if(href_list["AI"]) + aisync = !aisync + else if(href_list["Loco"]) + locomotion = !locomotion + else if(href_list["Panel"]) + panel_locked = !panel_locked + + add_fingerprint(usr) + Interact(usr) diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index b734e4389ef..f1227aa6b55 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -1,707 +1,707 @@ -// robot_upgrades.dm -// Contains various borg upgrades. - -/obj/item/borg/upgrade - name = "borg upgrade module." - desc = "Protected by FRM." - icon = 'icons/obj/module.dmi' - icon_state = "cyborg_upgrade" - w_class = WEIGHT_CLASS_SMALL - var/locked = FALSE - var/installed = 0 - var/require_module = 0 - var/list/module_type = null - // if true, is not stored in the robot to be ejected - // if module is reset - var/one_use = FALSE - -/obj/item/borg/upgrade/proc/action(mob/living/silicon/robot/R, user = usr) - if(R.stat == DEAD) - to_chat(user, "[src] will not function on a deceased cyborg!") - return FALSE - if(module_type && !is_type_in_list(R.module, module_type)) - to_chat(R, "Upgrade mounting error! No suitable hardpoint detected.") - to_chat(user, "There's no mounting point for the module!") - return FALSE - return TRUE - -/obj/item/borg/upgrade/proc/deactivate(mob/living/silicon/robot/R, user = usr) - if (!(src in R.upgrades)) - return FALSE - return TRUE - -/obj/item/borg/upgrade/rename - name = "cyborg reclassification board" - desc = "Used to rename a cyborg." - icon_state = "cyborg_upgrade1" - var/heldname = "" - one_use = TRUE - -/obj/item/borg/upgrade/rename/attack_self(mob/user) - heldname = sanitize_name(stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN), allow_numbers = TRUE) - -/obj/item/borg/upgrade/rename/action(mob/living/silicon/robot/R) - . = ..() - if(.) - var/oldname = R.real_name - R.custom_name = heldname - R.updatename() - if(oldname == R.real_name) - R.notify_ai(RENAME, oldname, R.real_name) - -/obj/item/borg/upgrade/restart - name = "cyborg emergency reboot module" - desc = "Used to force a reboot of a disabled-but-repaired cyborg, bringing it back online." - icon_state = "cyborg_upgrade1" - one_use = TRUE - -/obj/item/borg/upgrade/restart/action(mob/living/silicon/robot/R, user = usr) - if(R.health < 0) - to_chat(user, "You have to repair the cyborg before using this module!") - return FALSE - - if(R.mind) - R.mind.grab_ghost() - playsound(loc, 'sound/voice/liveagain.ogg', 75, TRUE) - - R.revive(full_heal = FALSE, admin_revive = FALSE) - -/obj/item/borg/upgrade/disablercooler - name = "cyborg rapid disabler cooling module" - desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/security) - -/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules - if(!T) - to_chat(user, "There's no disabler in this unit!") - return FALSE - if(T.charge_delay <= 2) - to_chat(R, "A cooling unit is already installed!") - to_chat(user, "There's no room for another cooling unit!") - return FALSE - - T.charge_delay = max(2 , T.charge_delay - 4) - -/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules - if(!T) - return FALSE - T.charge_delay = initial(T.charge_delay) - -/obj/item/borg/upgrade/thrusters - name = "ion thruster upgrade" - desc = "An energy-operated thruster system for cyborgs." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/thrusters/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.ionpulse) - to_chat(user, "This unit already has ion thrusters installed!") - return FALSE - - R.ionpulse = TRUE - -/obj/item/borg/upgrade/thrusters/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.ionpulse = FALSE - -/obj/item/borg/upgrade/ddrill - name = "mining cyborg diamond drill" - desc = "A diamond drill replacement for the mining module's standard drill." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/pickaxe/drill/cyborg/D in R.module) - R.module.remove_module(D, TRUE) - for(var/obj/item/shovel/S in R.module) - R.module.remove_module(S, TRUE) - - var/obj/item/pickaxe/drill/cyborg/diamond/DD = new /obj/item/pickaxe/drill/cyborg/diamond(R.module) - R.module.basic_modules += DD - R.module.add_module(DD, FALSE, TRUE) - -/obj/item/borg/upgrade/ddrill/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/pickaxe/drill/cyborg/diamond/DD in R.module) - R.module.remove_module(DD, TRUE) - - var/obj/item/pickaxe/drill/cyborg/D = new (R.module) - R.module.basic_modules += D - R.module.add_module(D, FALSE, TRUE) - var/obj/item/shovel/S = new (R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/soh - name = "mining cyborg satchel of holding" - desc = "A satchel of holding replacement for mining cyborg's ore satchel module." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R) - . = ..() - if(.) - for(var/obj/item/storage/bag/ore/cyborg/S in R.module) - R.module.remove_module(S, TRUE) - - var/obj/item/storage/bag/ore/holding/H = new /obj/item/storage/bag/ore/holding(R.module) - R.module.basic_modules += H - R.module.add_module(H, FALSE, TRUE) - -/obj/item/borg/upgrade/soh/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/storage/bag/ore/holding/H in R.module) - R.module.remove_module(H, TRUE) - - var/obj/item/storage/bag/ore/cyborg/S = new (R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/tboh - name = "janitor cyborg trash bag of holding" - desc = "A trash bag of holding replacement for the janiborg's standard trash bag." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/janitor) - -/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R) - . = ..() - if(.) - for(var/obj/item/storage/bag/trash/cyborg/TB in R.module.modules) - R.module.remove_module(TB, TRUE) - - var/obj/item/storage/bag/trash/bluespace/cyborg/B = new /obj/item/storage/bag/trash/bluespace/cyborg(R.module) - R.module.basic_modules += B - R.module.add_module(B, FALSE, TRUE) - -/obj/item/borg/upgrade/tboh/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/storage/bag/trash/bluespace/cyborg/B in R.module.modules) - R.module.remove_module(B, TRUE) - - var/obj/item/storage/bag/trash/cyborg/TB = new (R.module) - R.module.basic_modules += TB - R.module.add_module(TB, FALSE, TRUE) - -/obj/item/borg/upgrade/amop - name = "janitor cyborg advanced mop" - desc = "An advanced mop replacement for the janiborg's standard mop." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/janitor) - -/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R) - . = ..() - if(.) - for(var/obj/item/mop/cyborg/M in R.module.modules) - R.module.remove_module(M, TRUE) - - var/obj/item/mop/advanced/cyborg/A = new /obj/item/mop/advanced/cyborg(R.module) - R.module.basic_modules += A - R.module.add_module(A, FALSE, TRUE) - -/obj/item/borg/upgrade/amop/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/mop/advanced/cyborg/A in R.module.modules) - R.module.remove_module(A, TRUE) - - var/obj/item/mop/cyborg/M = new (R.module) - R.module.basic_modules += M - R.module.add_module(M, FALSE, TRUE) - -/obj/item/borg/upgrade/syndicate - name = "illegal equipment module" - desc = "Unlocks the hidden, deadlier functions of a cyborg." - icon_state = "cyborg_upgrade3" - require_module = 1 - -/obj/item/borg/upgrade/syndicate/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.emagged) - return FALSE - - R.SetEmagged(1) - - return TRUE - -/obj/item/borg/upgrade/syndicate/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.SetEmagged(FALSE) - -/obj/item/borg/upgrade/lavaproof - name = "mining cyborg lavaproof chassis" - desc = "An upgrade kit to apply specialized coolant systems and insulation layers to a mining cyborg's chassis, enabling them to withstand exposure to molten rock." - icon_state = "ash_plating" - resistance_flags = LAVA_PROOF | FIRE_PROOF - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - R.weather_immunities += "lava" - -/obj/item/borg/upgrade/lavaproof/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.weather_immunities -= "lava" - -/obj/item/borg/upgrade/selfrepair - name = "self-repair module" - desc = "This module will repair the cyborg over time." - icon_state = "cyborg_upgrade5" - require_module = 1 - var/repair_amount = -1 - var/repair_tick = 1 - var/on = FALSE - var/powercost = 10 - var/datum/action/toggle_action - -/obj/item/borg/upgrade/selfrepair/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/upgrade/selfrepair/U = locate() in R - if(U) - to_chat(user, "This unit is already equipped with a self-repair module!") - return FALSE - - icon_state = "selfrepair_off" - toggle_action = new /datum/action/item_action/toggle(src) - toggle_action.Grant(R) - -/obj/item/borg/upgrade/selfrepair/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - toggle_action.Remove(R) - QDEL_NULL(toggle_action) - deactivate_sr() - -/obj/item/borg/upgrade/selfrepair/ui_action_click() - if(on) - to_chat(toggle_action.owner, "You deactivate the self-repair module.") - deactivate_sr() - else - to_chat(toggle_action.owner, "You activate the self-repair module.") - activate_sr() - - -/obj/item/borg/upgrade/selfrepair/update_icon_state() - if(toggle_action) - icon_state = "selfrepair_[on ? "on" : "off"]" - else - icon_state = "cyborg_upgrade5" - -/obj/item/borg/upgrade/selfrepair/proc/activate_sr() - START_PROCESSING(SSobj, src) - on = TRUE - update_icon() - -/obj/item/borg/upgrade/selfrepair/proc/deactivate_sr() - STOP_PROCESSING(SSobj, src) - on = FALSE - update_icon() - -/obj/item/borg/upgrade/selfrepair/process() - if(!repair_tick) - repair_tick = 1 - return - - var/mob/living/silicon/robot/cyborg = toggle_action.owner - - if(istype(cyborg) && (cyborg.stat != DEAD) && on) - if(!cyborg.cell) - to_chat(cyborg, "Self-repair module deactivated. Please insert power cell.") - deactivate_sr() - return - - if(cyborg.cell.charge < powercost * 2) - to_chat(cyborg, "Self-repair module deactivated. Please recharge.") - deactivate_sr() - return - - if(cyborg.health < cyborg.maxHealth) - if(cyborg.health < 0) - repair_amount = -2.5 - powercost = 30 - else - repair_amount = -1 - powercost = 10 - cyborg.adjustBruteLoss(repair_amount) - cyborg.adjustFireLoss(repair_amount) - cyborg.updatehealth() - cyborg.cell.use(powercost) - else - cyborg.cell.use(5) - repair_tick = 0 - - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_BORG_SELF_REPAIR)) - TIMER_COOLDOWN_START(src, COOLDOWN_BORG_SELF_REPAIR, 200 SECONDS) - var/msgmode = "standby" - if(cyborg.health < 0) - msgmode = "critical" - else if(cyborg.health < cyborg.maxHealth) - msgmode = "normal" - to_chat(cyborg, "Self-repair is active in [msgmode] mode.") - else - deactivate_sr() - -/obj/item/borg/upgrade/hypospray - name = "medical cyborg hypospray advanced synthesiser" - desc = "An upgrade to the Medical module cyborg's hypospray, allowing it \ - to produce more advanced and complex medical reagents." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/medical) - var/list/additional_reagents = list() - -/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - if(H.accepts_reagent_upgrades) - for(var/re in additional_reagents) - H.add_reagent(re) - -/obj/item/borg/upgrade/hypospray/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - if(H.accepts_reagent_upgrades) - for(var/re in additional_reagents) - H.del_reagent(re) - -/obj/item/borg/upgrade/hypospray/expanded - name = "medical cyborg expanded hypospray" - desc = "An upgrade to the Medical module's hypospray, allowing it \ - to treat a wider range of conditions and problems." - additional_reagents = list(/datum/reagent/medicine/mannitol, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, - /datum/reagent/medicine/mutadone, /datum/reagent/medicine/haloperidol, /datum/reagent/medicine/oxandrolone, /datum/reagent/medicine/sal_acid, - /datum/reagent/medicine/rezadone, /datum/reagent/medicine/pen_acid) - -/obj/item/borg/upgrade/piercing_hypospray - name = "cyborg piercing hypospray" - desc = "An upgrade to a cyborg's hypospray, allowing it to \ - pierce armor and thick material." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/piercing_hypospray/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/found_hypo = FALSE - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - H.bypass_protection = TRUE - found_hypo = TRUE - - if(!found_hypo) - return FALSE - -/obj/item/borg/upgrade/piercing_hypospray/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - H.bypass_protection = initial(H.bypass_protection) - -/obj/item/borg/upgrade/defib - name = "medical cyborg defibrillator" - desc = "An upgrade to the Medical module, installing a built-in \ - defibrillator, for on the scene revival." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/medical) - var/backpack = FALSE //True if we get the defib from a physical backpack unit rather than an upgrade card, so that we can return that upon deactivate() - -/obj/item/borg/upgrade/defib/backpack - backpack = TRUE - -/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/upgrade/defib/backpack/BP = locate() in R //If a full defib unit was used to upgrade prior, we can just pop it out now and replace - if(BP) - BP.deactivate(R, user) - to_chat(user, "You remove the defibrillator unit to make room for the compact upgrade.") - var/obj/item/shockpaddles/cyborg/S = new(R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/defib/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/shockpaddles/cyborg/S = locate() in R.module - R.module.remove_module(S, TRUE) - if(backpack) - new /obj/item/defibrillator(get_turf(R)) - qdel(src) - - -/obj/item/borg/upgrade/processor - name = "medical cyborg surgical processor" - desc = "An upgrade to the Medical module, installing a processor \ - capable of scanning surgery disks and carrying \ - out procedures" - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) - -/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/surgical_processor/SP = new(R.module) - R.module.basic_modules += SP - R.module.add_module(SP, FALSE, TRUE) - -/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/surgical_processor/SP = locate() in R.module - R.module.remove_module(SP, TRUE) - -/obj/item/borg/upgrade/ai - name = "B.O.R.I.S. module" - desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI." - icon_state = "boris" - -/obj/item/borg/upgrade/ai/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.shell) - to_chat(user, "This unit is already an AI shell!") - return FALSE - if(R.key) //You cannot replace a player unless the key is completely removed. - to_chat(user, "Intelligence patterns detected in this [R.braintype]. Aborting.") - return FALSE - - R.make_shell(src) - -/obj/item/borg/upgrade/ai/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - if(R.shell) - R.undeploy() - R.notify_ai(DISCONNECT) - -/obj/item/borg/upgrade/expand - name = "borg expander" - desc = "A cyborg resizer, it makes a cyborg huge." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/expand/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - if(R.hasExpanded) - to_chat(usr, "This unit already has an expand module installed!") - return FALSE - - R.notransform = TRUE - var/prev_lockcharge = R.lockcharge - R.SetLockdown(1) - R.anchored = TRUE - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(1, R.loc) - smoke.start() - sleep(2) - for(var/i in 1 to 4) - playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1) - sleep(12) - if(!prev_lockcharge) - R.SetLockdown(0) - R.anchored = FALSE - R.notransform = FALSE - R.resize = 2 - R.hasExpanded = TRUE - R.update_transform() - -/obj/item/borg/upgrade/expand/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - if (R.hasExpanded) - R.hasExpanded = FALSE - R.resize = 0.5 - R.update_transform() - -/obj/item/borg/upgrade/rped - name = "engineering cyborg RPED" - desc = "A rapid part exchange device for the engineering cyborg." - icon = 'icons/obj/storage.dmi' - icon_state = "borgrped" - require_module = TRUE - module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) - -/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R - if(RPED) - to_chat(user, "This unit is already equipped with a RPED module!") - return FALSE - - RPED = new(R.module) - R.module.basic_modules += RPED - R.module.add_module(RPED, FALSE, TRUE) - -/obj/item/borg/upgrade/rped/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R.module - if (RPED) - R.module.remove_module(RPED, TRUE) - -/obj/item/borg/upgrade/pinpointer - name = "medical cyborg crew pinpointer" - desc = "A crew pinpointer module for the medical cyborg. Permits remote access to the crew monitor." - icon = 'icons/obj/device.dmi' - icon_state = "pinpointer_crew" - require_module = TRUE - module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) - var/datum/action/crew_monitor - -/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - var/obj/item/pinpointer/crew/PP = locate() in R.module - if(PP) - to_chat(user, "This unit is already equipped with a pinpointer module!") - return FALSE - - PP = new(R.module) - R.module.basic_modules += PP - R.module.add_module(PP, FALSE, TRUE) - crew_monitor = new /datum/action/item_action/crew_monitor(src) - crew_monitor.Grant(R) - icon_state = "scanner" - - -/obj/item/borg/upgrade/pinpointer/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - icon_state = "pinpointer_crew" - crew_monitor.Remove(R) - QDEL_NULL(crew_monitor) - var/obj/item/pinpointer/crew/PP = locate() in R.module - R.module.remove_module(PP, TRUE) - -/obj/item/borg/upgrade/pinpointer/ui_action_click() - if(..()) - return - var/mob/living/silicon/robot/Cyborg = usr - GLOB.crewmonitor.show(Cyborg,Cyborg) - - -/obj/item/borg/upgrade/transform - name = "borg module picker (Standard)" - desc = "Allows you to to turn a cyborg into a standard cyborg." - icon_state = "cyborg_upgrade3" - var/obj/item/robot_module/new_module = /obj/item/robot_module/standard - -/obj/item/borg/upgrade/transform/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - R.module.transform_to(new_module) - -/obj/item/borg/upgrade/transform/clown - name = "borg module picker (Clown)" - desc = "Allows you to to turn a cyborg into a clown, honk." - icon_state = "cyborg_upgrade3" - new_module = /obj/item/robot_module/clown - -/obj/item/borg/upgrade/circuit_app - name = "circuit manipulation apparatus" - desc = "An engineering cyborg upgrade allowing for manipulation of circuit boards." - icon_state = "cyborg_upgrade3" - require_module = TRUE - module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) - -/obj/item/borg/upgrade/circuit_app/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules - if(C) - to_chat(user, "This unit is already equipped with a circuit apparatus!") - return FALSE - - C = new(R.module) - R.module.basic_modules += C - R.module.add_module(C, FALSE, TRUE) - -/obj/item/borg/upgrade/circuit_app/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules - if (C) - R.module.remove_module(C, TRUE) - -/obj/item/borg/upgrade/beaker_app - name = "beaker storage apparatus" - desc = "A supplementary beaker storage apparatus for medical cyborgs." - icon_state = "cyborg_upgrade3" - require_module = TRUE - module_type = list(/obj/item/robot_module/medical) - -/obj/item/borg/upgrade/beaker_app/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules - if(E) - to_chat(user, "This unit has no room for additional beaker storage!") - return FALSE - - E = new(R.module) - R.module.basic_modules += E - R.module.add_module(E, FALSE, TRUE) - -/obj/item/borg/upgrade/beaker_app/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules - if (E) - R.module.remove_module(E, TRUE) - -/obj/item/borg/upgrade/broomer - name = "experimental push broom" - desc = "An experimental push broom used for efficiently pushing refuse." - icon_state = "cyborg_upgrade3" - require_module = TRUE - module_type = list(/obj/item/robot_module/janitor) - -/obj/item/borg/upgrade/broomer/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if (!.) - return - var/obj/item/pushbroom/cyborg/BR = locate() in R.module.modules - if (BR) - to_chat(user, "This janiborg is already equipped with an experimental broom!") - return FALSE - BR = new(R.module) - R.module.basic_modules += BR - R.module.add_module(BR, FALSE, TRUE) - -/obj/item/borg/upgrade/broomer/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (!.) - return - var/obj/item/pushbroom/cyborg/BR = locate() in R.module.modules - if (BR) - R.module.remove_module(BR, TRUE) +// robot_upgrades.dm +// Contains various borg upgrades. + +/obj/item/borg/upgrade + name = "borg upgrade module." + desc = "Protected by FRM." + icon = 'icons/obj/module.dmi' + icon_state = "cyborg_upgrade" + w_class = WEIGHT_CLASS_SMALL + var/locked = FALSE + var/installed = 0 + var/require_module = 0 + var/list/module_type = null + // if true, is not stored in the robot to be ejected + // if module is reset + var/one_use = FALSE + +/obj/item/borg/upgrade/proc/action(mob/living/silicon/robot/R, user = usr) + if(R.stat == DEAD) + to_chat(user, "[src] will not function on a deceased cyborg!") + return FALSE + if(module_type && !is_type_in_list(R.module, module_type)) + to_chat(R, "Upgrade mounting error! No suitable hardpoint detected.") + to_chat(user, "There's no mounting point for the module!") + return FALSE + return TRUE + +/obj/item/borg/upgrade/proc/deactivate(mob/living/silicon/robot/R, user = usr) + if (!(src in R.upgrades)) + return FALSE + return TRUE + +/obj/item/borg/upgrade/rename + name = "cyborg reclassification board" + desc = "Used to rename a cyborg." + icon_state = "cyborg_upgrade1" + var/heldname = "" + one_use = TRUE + +/obj/item/borg/upgrade/rename/attack_self(mob/user) + heldname = sanitize_name(stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN), allow_numbers = TRUE) + +/obj/item/borg/upgrade/rename/action(mob/living/silicon/robot/R) + . = ..() + if(.) + var/oldname = R.real_name + R.custom_name = heldname + R.updatename() + if(oldname == R.real_name) + R.notify_ai(RENAME, oldname, R.real_name) + +/obj/item/borg/upgrade/restart + name = "cyborg emergency reboot module" + desc = "Used to force a reboot of a disabled-but-repaired cyborg, bringing it back online." + icon_state = "cyborg_upgrade1" + one_use = TRUE + +/obj/item/borg/upgrade/restart/action(mob/living/silicon/robot/R, user = usr) + if(R.health < 0) + to_chat(user, "You have to repair the cyborg before using this module!") + return FALSE + + if(R.mind) + R.mind.grab_ghost() + playsound(loc, 'sound/voice/liveagain.ogg', 75, TRUE) + + R.revive(full_heal = FALSE, admin_revive = FALSE) + +/obj/item/borg/upgrade/disablercooler + name = "cyborg rapid disabler cooling module" + desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/security) + +/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules + if(!T) + to_chat(user, "There's no disabler in this unit!") + return FALSE + if(T.charge_delay <= 2) + to_chat(R, "A cooling unit is already installed!") + to_chat(user, "There's no room for another cooling unit!") + return FALSE + + T.charge_delay = max(2 , T.charge_delay - 4) + +/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules + if(!T) + return FALSE + T.charge_delay = initial(T.charge_delay) + +/obj/item/borg/upgrade/thrusters + name = "ion thruster upgrade" + desc = "An energy-operated thruster system for cyborgs." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/thrusters/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.ionpulse) + to_chat(user, "This unit already has ion thrusters installed!") + return FALSE + + R.ionpulse = TRUE + +/obj/item/borg/upgrade/thrusters/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.ionpulse = FALSE + +/obj/item/borg/upgrade/ddrill + name = "mining cyborg diamond drill" + desc = "A diamond drill replacement for the mining module's standard drill." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/pickaxe/drill/cyborg/D in R.module) + R.module.remove_module(D, TRUE) + for(var/obj/item/shovel/S in R.module) + R.module.remove_module(S, TRUE) + + var/obj/item/pickaxe/drill/cyborg/diamond/DD = new /obj/item/pickaxe/drill/cyborg/diamond(R.module) + R.module.basic_modules += DD + R.module.add_module(DD, FALSE, TRUE) + +/obj/item/borg/upgrade/ddrill/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/pickaxe/drill/cyborg/diamond/DD in R.module) + R.module.remove_module(DD, TRUE) + + var/obj/item/pickaxe/drill/cyborg/D = new (R.module) + R.module.basic_modules += D + R.module.add_module(D, FALSE, TRUE) + var/obj/item/shovel/S = new (R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/soh + name = "mining cyborg satchel of holding" + desc = "A satchel of holding replacement for mining cyborg's ore satchel module." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R) + . = ..() + if(.) + for(var/obj/item/storage/bag/ore/cyborg/S in R.module) + R.module.remove_module(S, TRUE) + + var/obj/item/storage/bag/ore/holding/H = new /obj/item/storage/bag/ore/holding(R.module) + R.module.basic_modules += H + R.module.add_module(H, FALSE, TRUE) + +/obj/item/borg/upgrade/soh/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/storage/bag/ore/holding/H in R.module) + R.module.remove_module(H, TRUE) + + var/obj/item/storage/bag/ore/cyborg/S = new (R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/tboh + name = "janitor cyborg trash bag of holding" + desc = "A trash bag of holding replacement for the janiborg's standard trash bag." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/janitor) + +/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R) + . = ..() + if(.) + for(var/obj/item/storage/bag/trash/cyborg/TB in R.module.modules) + R.module.remove_module(TB, TRUE) + + var/obj/item/storage/bag/trash/bluespace/cyborg/B = new /obj/item/storage/bag/trash/bluespace/cyborg(R.module) + R.module.basic_modules += B + R.module.add_module(B, FALSE, TRUE) + +/obj/item/borg/upgrade/tboh/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/storage/bag/trash/bluespace/cyborg/B in R.module.modules) + R.module.remove_module(B, TRUE) + + var/obj/item/storage/bag/trash/cyborg/TB = new (R.module) + R.module.basic_modules += TB + R.module.add_module(TB, FALSE, TRUE) + +/obj/item/borg/upgrade/amop + name = "janitor cyborg advanced mop" + desc = "An advanced mop replacement for the janiborg's standard mop." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/janitor) + +/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R) + . = ..() + if(.) + for(var/obj/item/mop/cyborg/M in R.module.modules) + R.module.remove_module(M, TRUE) + + var/obj/item/mop/advanced/cyborg/A = new /obj/item/mop/advanced/cyborg(R.module) + R.module.basic_modules += A + R.module.add_module(A, FALSE, TRUE) + +/obj/item/borg/upgrade/amop/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/mop/advanced/cyborg/A in R.module.modules) + R.module.remove_module(A, TRUE) + + var/obj/item/mop/cyborg/M = new (R.module) + R.module.basic_modules += M + R.module.add_module(M, FALSE, TRUE) + +/obj/item/borg/upgrade/syndicate + name = "illegal equipment module" + desc = "Unlocks the hidden, deadlier functions of a cyborg." + icon_state = "cyborg_upgrade3" + require_module = 1 + +/obj/item/borg/upgrade/syndicate/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.emagged) + return FALSE + + R.SetEmagged(1) + + return TRUE + +/obj/item/borg/upgrade/syndicate/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.SetEmagged(FALSE) + +/obj/item/borg/upgrade/lavaproof + name = "mining cyborg lavaproof chassis" + desc = "An upgrade kit to apply specialized coolant systems and insulation layers to a mining cyborg's chassis, enabling them to withstand exposure to molten rock." + icon_state = "ash_plating" + resistance_flags = LAVA_PROOF | FIRE_PROOF + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + R.weather_immunities += "lava" + +/obj/item/borg/upgrade/lavaproof/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.weather_immunities -= "lava" + +/obj/item/borg/upgrade/selfrepair + name = "self-repair module" + desc = "This module will repair the cyborg over time." + icon_state = "cyborg_upgrade5" + require_module = 1 + var/repair_amount = -1 + var/repair_tick = 1 + var/on = FALSE + var/powercost = 10 + var/datum/action/toggle_action + +/obj/item/borg/upgrade/selfrepair/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/upgrade/selfrepair/U = locate() in R + if(U) + to_chat(user, "This unit is already equipped with a self-repair module!") + return FALSE + + icon_state = "selfrepair_off" + toggle_action = new /datum/action/item_action/toggle(src) + toggle_action.Grant(R) + +/obj/item/borg/upgrade/selfrepair/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + toggle_action.Remove(R) + QDEL_NULL(toggle_action) + deactivate_sr() + +/obj/item/borg/upgrade/selfrepair/ui_action_click() + if(on) + to_chat(toggle_action.owner, "You deactivate the self-repair module.") + deactivate_sr() + else + to_chat(toggle_action.owner, "You activate the self-repair module.") + activate_sr() + + +/obj/item/borg/upgrade/selfrepair/update_icon_state() + if(toggle_action) + icon_state = "selfrepair_[on ? "on" : "off"]" + else + icon_state = "cyborg_upgrade5" + +/obj/item/borg/upgrade/selfrepair/proc/activate_sr() + START_PROCESSING(SSobj, src) + on = TRUE + update_icon() + +/obj/item/borg/upgrade/selfrepair/proc/deactivate_sr() + STOP_PROCESSING(SSobj, src) + on = FALSE + update_icon() + +/obj/item/borg/upgrade/selfrepair/process() + if(!repair_tick) + repair_tick = 1 + return + + var/mob/living/silicon/robot/cyborg = toggle_action.owner + + if(istype(cyborg) && (cyborg.stat != DEAD) && on) + if(!cyborg.cell) + to_chat(cyborg, "Self-repair module deactivated. Please insert power cell.") + deactivate_sr() + return + + if(cyborg.cell.charge < powercost * 2) + to_chat(cyborg, "Self-repair module deactivated. Please recharge.") + deactivate_sr() + return + + if(cyborg.health < cyborg.maxHealth) + if(cyborg.health < 0) + repair_amount = -2.5 + powercost = 30 + else + repair_amount = -1 + powercost = 10 + cyborg.adjustBruteLoss(repair_amount) + cyborg.adjustFireLoss(repair_amount) + cyborg.updatehealth() + cyborg.cell.use(powercost) + else + cyborg.cell.use(5) + repair_tick = 0 + + if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_BORG_SELF_REPAIR)) + TIMER_COOLDOWN_START(src, COOLDOWN_BORG_SELF_REPAIR, 200 SECONDS) + var/msgmode = "standby" + if(cyborg.health < 0) + msgmode = "critical" + else if(cyborg.health < cyborg.maxHealth) + msgmode = "normal" + to_chat(cyborg, "Self-repair is active in [msgmode] mode.") + else + deactivate_sr() + +/obj/item/borg/upgrade/hypospray + name = "medical cyborg hypospray advanced synthesiser" + desc = "An upgrade to the Medical module cyborg's hypospray, allowing it \ + to produce more advanced and complex medical reagents." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/medical) + var/list/additional_reagents = list() + +/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + if(H.accepts_reagent_upgrades) + for(var/re in additional_reagents) + H.add_reagent(re) + +/obj/item/borg/upgrade/hypospray/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + if(H.accepts_reagent_upgrades) + for(var/re in additional_reagents) + H.del_reagent(re) + +/obj/item/borg/upgrade/hypospray/expanded + name = "medical cyborg expanded hypospray" + desc = "An upgrade to the Medical module's hypospray, allowing it \ + to treat a wider range of conditions and problems." + additional_reagents = list(/datum/reagent/medicine/mannitol, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, + /datum/reagent/medicine/mutadone, /datum/reagent/medicine/haloperidol, /datum/reagent/medicine/oxandrolone, /datum/reagent/medicine/sal_acid, + /datum/reagent/medicine/rezadone, /datum/reagent/medicine/pen_acid) + +/obj/item/borg/upgrade/piercing_hypospray + name = "cyborg piercing hypospray" + desc = "An upgrade to a cyborg's hypospray, allowing it to \ + pierce armor and thick material." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/piercing_hypospray/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/found_hypo = FALSE + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + H.bypass_protection = TRUE + found_hypo = TRUE + + if(!found_hypo) + return FALSE + +/obj/item/borg/upgrade/piercing_hypospray/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + H.bypass_protection = initial(H.bypass_protection) + +/obj/item/borg/upgrade/defib + name = "medical cyborg defibrillator" + desc = "An upgrade to the Medical module, installing a built-in \ + defibrillator, for on the scene revival." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/medical) + var/backpack = FALSE //True if we get the defib from a physical backpack unit rather than an upgrade card, so that we can return that upon deactivate() + +/obj/item/borg/upgrade/defib/backpack + backpack = TRUE + +/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/upgrade/defib/backpack/BP = locate() in R //If a full defib unit was used to upgrade prior, we can just pop it out now and replace + if(BP) + BP.deactivate(R, user) + to_chat(user, "You remove the defibrillator unit to make room for the compact upgrade.") + var/obj/item/shockpaddles/cyborg/S = new(R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/defib/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/shockpaddles/cyborg/S = locate() in R.module + R.module.remove_module(S, TRUE) + if(backpack) + new /obj/item/defibrillator(get_turf(R)) + qdel(src) + + +/obj/item/borg/upgrade/processor + name = "medical cyborg surgical processor" + desc = "An upgrade to the Medical module, installing a processor \ + capable of scanning surgery disks and carrying \ + out procedures" + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) + +/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/surgical_processor/SP = new(R.module) + R.module.basic_modules += SP + R.module.add_module(SP, FALSE, TRUE) + +/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/surgical_processor/SP = locate() in R.module + R.module.remove_module(SP, TRUE) + +/obj/item/borg/upgrade/ai + name = "B.O.R.I.S. module" + desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI." + icon_state = "boris" + +/obj/item/borg/upgrade/ai/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.shell) + to_chat(user, "This unit is already an AI shell!") + return FALSE + if(R.key) //You cannot replace a player unless the key is completely removed. + to_chat(user, "Intelligence patterns detected in this [R.braintype]. Aborting.") + return FALSE + + R.make_shell(src) + +/obj/item/borg/upgrade/ai/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + if(R.shell) + R.undeploy() + R.notify_ai(DISCONNECT) + +/obj/item/borg/upgrade/expand + name = "borg expander" + desc = "A cyborg resizer, it makes a cyborg huge." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/expand/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + if(R.hasExpanded) + to_chat(usr, "This unit already has an expand module installed!") + return FALSE + + R.notransform = TRUE + var/prev_lockcharge = R.lockcharge + R.SetLockdown(1) + R.anchored = TRUE + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(1, R.loc) + smoke.start() + sleep(2) + for(var/i in 1 to 4) + playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1) + sleep(12) + if(!prev_lockcharge) + R.SetLockdown(0) + R.anchored = FALSE + R.notransform = FALSE + R.resize = 2 + R.hasExpanded = TRUE + R.update_transform() + +/obj/item/borg/upgrade/expand/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + if (R.hasExpanded) + R.hasExpanded = FALSE + R.resize = 0.5 + R.update_transform() + +/obj/item/borg/upgrade/rped + name = "engineering cyborg RPED" + desc = "A rapid part exchange device for the engineering cyborg." + icon = 'icons/obj/storage.dmi' + icon_state = "borgrped" + require_module = TRUE + module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) + +/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R + if(RPED) + to_chat(user, "This unit is already equipped with a RPED module!") + return FALSE + + RPED = new(R.module) + R.module.basic_modules += RPED + R.module.add_module(RPED, FALSE, TRUE) + +/obj/item/borg/upgrade/rped/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R.module + if (RPED) + R.module.remove_module(RPED, TRUE) + +/obj/item/borg/upgrade/pinpointer + name = "medical cyborg crew pinpointer" + desc = "A crew pinpointer module for the medical cyborg. Permits remote access to the crew monitor." + icon = 'icons/obj/device.dmi' + icon_state = "pinpointer_crew" + require_module = TRUE + module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) + var/datum/action/crew_monitor + +/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + var/obj/item/pinpointer/crew/PP = locate() in R.module + if(PP) + to_chat(user, "This unit is already equipped with a pinpointer module!") + return FALSE + + PP = new(R.module) + R.module.basic_modules += PP + R.module.add_module(PP, FALSE, TRUE) + crew_monitor = new /datum/action/item_action/crew_monitor(src) + crew_monitor.Grant(R) + icon_state = "scanner" + + +/obj/item/borg/upgrade/pinpointer/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + icon_state = "pinpointer_crew" + crew_monitor.Remove(R) + QDEL_NULL(crew_monitor) + var/obj/item/pinpointer/crew/PP = locate() in R.module + R.module.remove_module(PP, TRUE) + +/obj/item/borg/upgrade/pinpointer/ui_action_click() + if(..()) + return + var/mob/living/silicon/robot/Cyborg = usr + GLOB.crewmonitor.show(Cyborg,Cyborg) + + +/obj/item/borg/upgrade/transform + name = "borg module picker (Standard)" + desc = "Allows you to to turn a cyborg into a standard cyborg." + icon_state = "cyborg_upgrade3" + var/obj/item/robot_module/new_module = /obj/item/robot_module/standard + +/obj/item/borg/upgrade/transform/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + R.module.transform_to(new_module) + +/obj/item/borg/upgrade/transform/clown + name = "borg module picker (Clown)" + desc = "Allows you to to turn a cyborg into a clown, honk." + icon_state = "cyborg_upgrade3" + new_module = /obj/item/robot_module/clown + +/obj/item/borg/upgrade/circuit_app + name = "circuit manipulation apparatus" + desc = "An engineering cyborg upgrade allowing for manipulation of circuit boards." + icon_state = "cyborg_upgrade3" + require_module = TRUE + module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) + +/obj/item/borg/upgrade/circuit_app/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules + if(C) + to_chat(user, "This unit is already equipped with a circuit apparatus!") + return FALSE + + C = new(R.module) + R.module.basic_modules += C + R.module.add_module(C, FALSE, TRUE) + +/obj/item/borg/upgrade/circuit_app/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules + if (C) + R.module.remove_module(C, TRUE) + +/obj/item/borg/upgrade/beaker_app + name = "beaker storage apparatus" + desc = "A supplementary beaker storage apparatus for medical cyborgs." + icon_state = "cyborg_upgrade3" + require_module = TRUE + module_type = list(/obj/item/robot_module/medical) + +/obj/item/borg/upgrade/beaker_app/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules + if(E) + to_chat(user, "This unit has no room for additional beaker storage!") + return FALSE + + E = new(R.module) + R.module.basic_modules += E + R.module.add_module(E, FALSE, TRUE) + +/obj/item/borg/upgrade/beaker_app/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules + if (E) + R.module.remove_module(E, TRUE) + +/obj/item/borg/upgrade/broomer + name = "experimental push broom" + desc = "An experimental push broom used for efficiently pushing refuse." + icon_state = "cyborg_upgrade3" + require_module = TRUE + module_type = list(/obj/item/robot_module/janitor) + +/obj/item/borg/upgrade/broomer/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if (!.) + return + var/obj/item/pushbroom/cyborg/BR = locate() in R.module.modules + if (BR) + to_chat(user, "This janiborg is already equipped with an experimental broom!") + return FALSE + BR = new(R.module) + R.module.basic_modules += BR + R.module.add_module(BR, FALSE, TRUE) + +/obj/item/borg/upgrade/broomer/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (!.) + return + var/obj/item/pushbroom/cyborg/BR = locate() in R.module.modules + if (BR) + R.module.remove_module(BR, TRUE) diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm index 2e4871d6c7c..f441208cfa8 100644 --- a/code/game/objects/items/scrolls.dm +++ b/code/game/objects/items/scrolls.dm @@ -1,73 +1,73 @@ -/obj/item/teleportation_scroll - name = "scroll of teleportation" - desc = "A scroll for moving around." - icon = 'icons/obj/wizard.dmi' - icon_state = "scroll" - var/uses = 4 - w_class = WEIGHT_CLASS_SMALL - inhand_icon_state = "paper" - throw_speed = 3 - throw_range = 7 - resistance_flags = FLAMMABLE - -/obj/item/teleportation_scroll/apprentice - name = "lesser scroll of teleportation" - uses = 1 - - - -/obj/item/teleportation_scroll/attack_self(mob/user) - user.set_machine(src) - var/dat = "Teleportation Scroll:
                " - dat += "Number of uses: [src.uses]
                " - dat += "
                " - dat += "Four uses, use them wisely:
                " - dat += "Teleport
                " - dat += "Kind regards,
                Wizards Federation

                P.S. Don't forget to bring your gear, you'll need it to cast most spells.
                " - user << browse(dat, "window=scroll") - onclose(user, "scroll") - return - -/obj/item/teleportation_scroll/Topic(href, href_list) - ..() - if (usr.stat || usr.restrained() || src.loc != usr) - return - if (!ishuman(usr)) - return 1 - var/mob/living/carbon/human/H = usr - if(H.is_holding(src)) - H.set_machine(src) - if (href_list["spell_teleport"]) - if(uses) - teleportscroll(H) - if(H) - attack_self(H) - return - -/obj/item/teleportation_scroll/proc/teleportscroll(mob/user) - - var/A - - A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in GLOB.teleportlocs - if(!src || QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !A || !uses) - return - var/area/thearea = GLOB.teleportlocs[A] - - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, user.loc) - smoke.attach(user) - smoke.start() - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!is_blocked_turf(T)) - L += T - - if(!L.len) - to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") - return - - if(do_teleport(user, pick(L), forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE)) - smoke.start() - uses-- - else - to_chat(user, "The spell matrix was disrupted by something near the destination.") +/obj/item/teleportation_scroll + name = "scroll of teleportation" + desc = "A scroll for moving around." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll" + var/uses = 4 + w_class = WEIGHT_CLASS_SMALL + inhand_icon_state = "paper" + throw_speed = 3 + throw_range = 7 + resistance_flags = FLAMMABLE + +/obj/item/teleportation_scroll/apprentice + name = "lesser scroll of teleportation" + uses = 1 + + + +/obj/item/teleportation_scroll/attack_self(mob/user) + user.set_machine(src) + var/dat = "Teleportation Scroll:
                " + dat += "Number of uses: [src.uses]
                " + dat += "
                " + dat += "Four uses, use them wisely:
                " + dat += "Teleport
                " + dat += "Kind regards,
                Wizards Federation

                P.S. Don't forget to bring your gear, you'll need it to cast most spells.
                " + user << browse(dat, "window=scroll") + onclose(user, "scroll") + return + +/obj/item/teleportation_scroll/Topic(href, href_list) + ..() + if (usr.stat || usr.restrained() || src.loc != usr) + return + if (!ishuman(usr)) + return 1 + var/mob/living/carbon/human/H = usr + if(H.is_holding(src)) + H.set_machine(src) + if (href_list["spell_teleport"]) + if(uses) + teleportscroll(H) + if(H) + attack_self(H) + return + +/obj/item/teleportation_scroll/proc/teleportscroll(mob/user) + + var/A + + A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in GLOB.teleportlocs + if(!src || QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !A || !uses) + return + var/area/thearea = GLOB.teleportlocs[A] + + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(2, user.loc) + smoke.attach(user) + smoke.start() + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + if(!is_blocked_turf(T)) + L += T + + if(!L.len) + to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") + return + + if(do_teleport(user, pick(L), forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE)) + smoke.start() + uses-- + else + to_chat(user, "The spell matrix was disrupted by something near the destination.") diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm index bc80bc6f2af..a56829f433a 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -1,279 +1,279 @@ -/obj/item/shield - name = "shield" - icon = 'icons/obj/shields.dmi' - block_chance = 50 - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - var/transparent = FALSE // makes beam projectiles pass through the shield - -/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) - return TRUE - -/obj/item/shield/riot - name = "riot shield" - desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." - icon_state = "riot" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - slot_flags = ITEM_SLOT_BACK - force = 10 - throwforce = 5 - throw_speed = 2 - throw_range = 3 - w_class = WEIGHT_CLASS_BULKY - custom_materials = list(/datum/material/glass=7500, /datum/material/iron=1000) - attack_verb = list("shoved", "bashed") - var/cooldown = 0 //shield bash cooldown. based on world.time - transparent = TRUE - max_integrity = 75 - material_flags = MATERIAL_NO_EFFECTS - -/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(transparent && (hitby.pass_flags & PASSGLASS)) - return FALSE - if(attack_type == THROWN_PROJECTILE_ATTACK) - final_block_chance += 30 - if(attack_type == LEAP_ATTACK) - final_block_chance = 100 - . = ..() - if(.) - on_shield_block(owner, hitby, attack_text, damage, attack_type) - -/obj/item/shield/riot/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/melee/baton)) - if(cooldown < world.time - 25) - user.visible_message("[user] bashes [src] with [W]!") - playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, TRUE) - cooldown = world.time - else if(istype(W, /obj/item/stack/sheet/mineral/titanium)) - if (obj_integrity >= max_integrity) - to_chat(user, "[src] is already in perfect condition.") - else - var/obj/item/stack/sheet/mineral/titanium/T = W - T.use(1) - obj_integrity = max_integrity - to_chat(user, "You repair [src] with [T].") - else - return ..() - -/obj/item/shield/riot/examine(mob/user) - . = ..() - var/healthpercent = round((obj_integrity/max_integrity) * 100, 1) - switch(healthpercent) - if(50 to 99) - . += "It looks slightly damaged." - if(25 to 50) - . += "It appears heavily damaged." - if(0 to 25) - . += "It's falling apart!" - -/obj/item/shield/riot/proc/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/glassbr3.ogg', 100) - new /obj/item/shard((get_turf(src))) - -/obj/item/shield/riot/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) - if (obj_integrity <= damage) - var/turf/T = get_turf(owner) - T.visible_message("[hitby] destroys [src]!") - shatter(owner) - qdel(src) - return FALSE - take_damage(damage) - return ..() - -/obj/item/shield/riot/roman - name = "\improper Roman shield" - desc = "Bears an inscription on the inside: \"Romanes venio domus\"." - icon_state = "roman_shield" - inhand_icon_state = "roman_shield" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - transparent = FALSE - custom_materials = list(/datum/material/iron=8500) - max_integrity = 65 - -/obj/item/shield/riot/roman/fake - desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." - block_chance = 0 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - max_integrity = 30 - -/obj/item/shield/riot/roman/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/grillehit.ogg', 100) - new /obj/item/stack/sheet/metal(get_turf(src)) - -/obj/item/shield/riot/buckler - name = "wooden buckler" - desc = "A medieval wooden buckler." - icon_state = "buckler" - inhand_icon_state = "buckler" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 10) - resistance_flags = FLAMMABLE - block_chance = 30 - transparent = FALSE - max_integrity = 55 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/shield/riot/buckler/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/bang.ogg', 50) - new /obj/item/stack/sheet/mineral/wood(get_turf(src)) - -/obj/item/shield/riot/flash - name = "strobe shield" - desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." - icon_state = "flashshield" - inhand_icon_state = "flashshield" - var/obj/item/assembly/flash/handheld/embedded_flash - -/obj/item/shield/riot/flash/Initialize() - . = ..() - embedded_flash = new(src) - -/obj/item/shield/riot/flash/ComponentInitialize() - . = .. () - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/shield/riot/flash/attack(mob/living/M, mob/user) - . = embedded_flash.attack(M, user) - update_icon() - -/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user) - . = embedded_flash.attack_self(user) - update_icon() - -/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - . = ..() - if (. && !embedded_flash.burnt_out) - embedded_flash.activate() - update_icon() - - -/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) - if(istype(W, /obj/item/assembly/flash/handheld)) - var/obj/item/assembly/flash/handheld/flash = W - if(flash.burnt_out) - to_chat(user, "No sense replacing it with a broken bulb!") - return - else - to_chat(user, "You begin to replace the bulb...") - if(do_after(user, 20, target = user)) - if(flash.burnt_out || !flash || QDELETED(flash)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - qdel(embedded_flash) - embedded_flash = flash - flash.forceMove(src) - update_icon() - return - ..() - -/obj/item/shield/riot/flash/emp_act(severity) - . = ..() - embedded_flash.emp_act(severity) - update_icon() - -/obj/item/shield/riot/flash/update_icon_state() - if(!embedded_flash || embedded_flash.burnt_out) - icon_state = "riot" - inhand_icon_state = "riot" - else - icon_state = "flashshield" - inhand_icon_state = "flashshield" - -/obj/item/shield/riot/flash/examine(mob/user) - . = ..() - if (embedded_flash?.burnt_out) - . += "The mounted bulb has burnt out. You can try replacing it with a new one." - -/obj/item/shield/energy - name = "energy combat shield" - desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - attack_verb = list("shoved", "bashed") - throw_range = 5 - force = 3 - throwforce = 3 - throw_speed = 3 - var/base_icon_state = "eshield" // [base_icon_state]1 for expanded, [base_icon_state]0 for contracted - var/on_force = 10 - var/on_throwforce = 8 - var/on_throw_speed = 2 - var/active = 0 - var/clumsy_check = TRUE - -/obj/item/shield/energy/Initialize() - . = ..() - icon_state = "[base_icon_state]0" - -/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/shield/energy/IsReflect() - return (active) - -/obj/item/shield/energy/attack_self(mob/living/carbon/human/user) - if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - to_chat(user, "You beat yourself in the head with [src]!") - user.take_bodypart_damage(5) - active = !active - icon_state = "[base_icon_state][active]" - - if(active) - force = on_force - throwforce = on_throwforce - throw_speed = on_throw_speed - w_class = WEIGHT_CLASS_BULKY - playsound(user, 'sound/weapons/saberon.ogg', 35, TRUE) - to_chat(user, "[src] is now active.") - else - force = initial(force) - throwforce = initial(throwforce) - throw_speed = initial(throw_speed) - w_class = WEIGHT_CLASS_TINY - playsound(user, 'sound/weapons/saberoff.ogg', 35, TRUE) - to_chat(user, "[src] can now be concealed.") - add_fingerprint(user) - -/obj/item/shield/riot/tele - name = "telescopic shield" - desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." - icon_state = "teleriot0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - custom_materials = list(/datum/material/iron = 3600, /datum/material/glass = 3600, /datum/material/silver = 270, /datum/material/titanium = 180) - slot_flags = null - force = 3 - throwforce = 3 - throw_speed = 3 - throw_range = 4 - w_class = WEIGHT_CLASS_NORMAL - var/active = 0 - -/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/shield/riot/tele/attack_self(mob/living/user) - active = !active - icon_state = "teleriot[active]" - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) - - if(active) - force = 8 - throwforce = 5 - throw_speed = 2 - w_class = WEIGHT_CLASS_BULKY - slot_flags = ITEM_SLOT_BACK - to_chat(user, "You extend \the [src].") - else - force = 3 - throwforce = 3 - throw_speed = 3 - w_class = WEIGHT_CLASS_NORMAL - slot_flags = null - to_chat(user, "[src] can now be concealed.") - add_fingerprint(user) +/obj/item/shield + name = "shield" + icon = 'icons/obj/shields.dmi' + block_chance = 50 + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + var/transparent = FALSE // makes beam projectiles pass through the shield + +/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) + return TRUE + +/obj/item/shield/riot + name = "riot shield" + desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." + icon_state = "riot" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + slot_flags = ITEM_SLOT_BACK + force = 10 + throwforce = 5 + throw_speed = 2 + throw_range = 3 + w_class = WEIGHT_CLASS_BULKY + custom_materials = list(/datum/material/glass=7500, /datum/material/iron=1000) + attack_verb = list("shoved", "bashed") + var/cooldown = 0 //shield bash cooldown. based on world.time + transparent = TRUE + max_integrity = 75 + material_flags = MATERIAL_NO_EFFECTS + +/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(transparent && (hitby.pass_flags & PASSGLASS)) + return FALSE + if(attack_type == THROWN_PROJECTILE_ATTACK) + final_block_chance += 30 + if(attack_type == LEAP_ATTACK) + final_block_chance = 100 + . = ..() + if(.) + on_shield_block(owner, hitby, attack_text, damage, attack_type) + +/obj/item/shield/riot/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/melee/baton)) + if(cooldown < world.time - 25) + user.visible_message("[user] bashes [src] with [W]!") + playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, TRUE) + cooldown = world.time + else if(istype(W, /obj/item/stack/sheet/mineral/titanium)) + if (obj_integrity >= max_integrity) + to_chat(user, "[src] is already in perfect condition.") + else + var/obj/item/stack/sheet/mineral/titanium/T = W + T.use(1) + obj_integrity = max_integrity + to_chat(user, "You repair [src] with [T].") + else + return ..() + +/obj/item/shield/riot/examine(mob/user) + . = ..() + var/healthpercent = round((obj_integrity/max_integrity) * 100, 1) + switch(healthpercent) + if(50 to 99) + . += "It looks slightly damaged." + if(25 to 50) + . += "It appears heavily damaged." + if(0 to 25) + . += "It's falling apart!" + +/obj/item/shield/riot/proc/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/glassbr3.ogg', 100) + new /obj/item/shard((get_turf(src))) + +/obj/item/shield/riot/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) + if (obj_integrity <= damage) + var/turf/T = get_turf(owner) + T.visible_message("[hitby] destroys [src]!") + shatter(owner) + qdel(src) + return FALSE + take_damage(damage) + return ..() + +/obj/item/shield/riot/roman + name = "\improper Roman shield" + desc = "Bears an inscription on the inside: \"Romanes venio domus\"." + icon_state = "roman_shield" + inhand_icon_state = "roman_shield" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + transparent = FALSE + custom_materials = list(/datum/material/iron=8500) + max_integrity = 65 + +/obj/item/shield/riot/roman/fake + desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." + block_chance = 0 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + max_integrity = 30 + +/obj/item/shield/riot/roman/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/grillehit.ogg', 100) + new /obj/item/stack/sheet/metal(get_turf(src)) + +/obj/item/shield/riot/buckler + name = "wooden buckler" + desc = "A medieval wooden buckler." + icon_state = "buckler" + inhand_icon_state = "buckler" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 10) + resistance_flags = FLAMMABLE + block_chance = 30 + transparent = FALSE + max_integrity = 55 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/shield/riot/buckler/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/bang.ogg', 50) + new /obj/item/stack/sheet/mineral/wood(get_turf(src)) + +/obj/item/shield/riot/flash + name = "strobe shield" + desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." + icon_state = "flashshield" + inhand_icon_state = "flashshield" + var/obj/item/assembly/flash/handheld/embedded_flash + +/obj/item/shield/riot/flash/Initialize() + . = ..() + embedded_flash = new(src) + +/obj/item/shield/riot/flash/ComponentInitialize() + . = .. () + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/shield/riot/flash/attack(mob/living/M, mob/user) + . = embedded_flash.attack(M, user) + update_icon() + +/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user) + . = embedded_flash.attack_self(user) + update_icon() + +/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + . = ..() + if (. && !embedded_flash.burnt_out) + embedded_flash.activate() + update_icon() + + +/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) + if(istype(W, /obj/item/assembly/flash/handheld)) + var/obj/item/assembly/flash/handheld/flash = W + if(flash.burnt_out) + to_chat(user, "No sense replacing it with a broken bulb!") + return + else + to_chat(user, "You begin to replace the bulb...") + if(do_after(user, 20, target = user)) + if(flash.burnt_out || !flash || QDELETED(flash)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + qdel(embedded_flash) + embedded_flash = flash + flash.forceMove(src) + update_icon() + return + ..() + +/obj/item/shield/riot/flash/emp_act(severity) + . = ..() + embedded_flash.emp_act(severity) + update_icon() + +/obj/item/shield/riot/flash/update_icon_state() + if(!embedded_flash || embedded_flash.burnt_out) + icon_state = "riot" + inhand_icon_state = "riot" + else + icon_state = "flashshield" + inhand_icon_state = "flashshield" + +/obj/item/shield/riot/flash/examine(mob/user) + . = ..() + if (embedded_flash?.burnt_out) + . += "The mounted bulb has burnt out. You can try replacing it with a new one." + +/obj/item/shield/energy + name = "energy combat shield" + desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + attack_verb = list("shoved", "bashed") + throw_range = 5 + force = 3 + throwforce = 3 + throw_speed = 3 + var/base_icon_state = "eshield" // [base_icon_state]1 for expanded, [base_icon_state]0 for contracted + var/on_force = 10 + var/on_throwforce = 8 + var/on_throw_speed = 2 + var/active = 0 + var/clumsy_check = TRUE + +/obj/item/shield/energy/Initialize() + . = ..() + icon_state = "[base_icon_state]0" + +/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/shield/energy/IsReflect() + return (active) + +/obj/item/shield/energy/attack_self(mob/living/carbon/human/user) + if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + to_chat(user, "You beat yourself in the head with [src]!") + user.take_bodypart_damage(5) + active = !active + icon_state = "[base_icon_state][active]" + + if(active) + force = on_force + throwforce = on_throwforce + throw_speed = on_throw_speed + w_class = WEIGHT_CLASS_BULKY + playsound(user, 'sound/weapons/saberon.ogg', 35, TRUE) + to_chat(user, "[src] is now active.") + else + force = initial(force) + throwforce = initial(throwforce) + throw_speed = initial(throw_speed) + w_class = WEIGHT_CLASS_TINY + playsound(user, 'sound/weapons/saberoff.ogg', 35, TRUE) + to_chat(user, "[src] can now be concealed.") + add_fingerprint(user) + +/obj/item/shield/riot/tele + name = "telescopic shield" + desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." + icon_state = "teleriot0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + custom_materials = list(/datum/material/iron = 3600, /datum/material/glass = 3600, /datum/material/silver = 270, /datum/material/titanium = 180) + slot_flags = null + force = 3 + throwforce = 3 + throw_speed = 3 + throw_range = 4 + w_class = WEIGHT_CLASS_NORMAL + var/active = 0 + +/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/shield/riot/tele/attack_self(mob/living/user) + active = !active + icon_state = "teleriot[active]" + playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) + + if(active) + force = 8 + throwforce = 5 + throw_speed = 2 + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK + to_chat(user, "You extend \the [src].") + else + force = 3 + throwforce = 3 + throw_speed = 3 + w_class = WEIGHT_CLASS_NORMAL + slot_flags = null + to_chat(user, "[src] can now be concealed.") + add_fingerprint(user) diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 412e7df3414..51bd0ab4255 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -1,97 +1,97 @@ -/obj/item/target - name = "shooting target" - desc = "A shooting target." - icon = 'icons/obj/objects.dmi' - icon_state = "target_h" - density = FALSE - var/hp = 1800 - var/obj/structure/target_stake/pinnedLoc - -/obj/item/target/Destroy() - removeOverlays() - if(pinnedLoc) - pinnedLoc.nullPinnedTarget() - return ..() - -/obj/item/target/proc/nullPinnedLoc() - pinnedLoc = null - density = FALSE - -/obj/item/target/proc/removeOverlays() - cut_overlays() - -/obj/item/target/Move() - . = ..() - if(pinnedLoc) - pinnedLoc.forceMove(loc) - -/obj/item/target/welder_act(mob/living/user, obj/item/I) - ..() - if(I.use_tool(src, user, 0, volume=40)) - removeOverlays() - to_chat(user, "You slice off [src]'s uneven chunks of aluminium and scorch marks.") - return TRUE - -/obj/item/target/attack_hand(mob/user) - . = ..() - if(.) - return - if(pinnedLoc) - pinnedLoc.removeTarget(user) - -/obj/item/target/syndicate - icon_state = "target_s" - desc = "A shooting target that looks like syndicate scum." - hp = 2600 - -/obj/item/target/alien - icon_state = "target_q" - desc = "A shooting target that looks like a xenomorphic alien." - hp = 2350 - -/obj/item/target/alien/anchored - anchored = TRUE - -/obj/item/target/clown - icon_state = "target_c" - desc = "A shooting target that looks like a useless clown." - hp = 2000 - -#define DECALTYPE_SCORCH 1 -#define DECALTYPE_BULLET 2 - -/obj/item/target/clown/bullet_act(obj/projectile/P) - . = ..() - playsound(src.loc, 'sound/items/bikehorn.ogg', 50, TRUE) - -/obj/item/target/bullet_act(obj/projectile/P) - if(istype(P, /obj/projectile/bullet/reusable)) // If it's a foam dart, don't bother with any of this other shit - return P.on_hit(src, 0) - var/p_x = P.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset P.p_x!" - var/p_y = P.p_y + pick(0,0,0,0,0,-1,1) - var/decaltype = DECALTYPE_SCORCH - if(istype(P, /obj/projectile/bullet)) - decaltype = DECALTYPE_BULLET - var/icon/C = icon(icon,icon_state) - if(C.GetPixel(p_x, p_y) && P.original == src && overlays.len <= 35) // if the located pixel isn't blank (null) - hp -= P.damage - if(hp <= 0) - visible_message("[src] breaks into tiny pieces and collapses!") - qdel(src) - var/image/bullet_hole = image('icons/effects/effects.dmi', "scorch", OBJ_LAYER + 0.5) - bullet_hole.pixel_x = p_x - 1 //offset correction - bullet_hole.pixel_y = p_y - 1 - if(decaltype == DECALTYPE_SCORCH) - bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))// random scorch design - if(P.damage >= 20 || istype(P, /obj/projectile/beam/practice)) - bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST)) - else - bullet_hole.icon_state = "light_scorch" - else - bullet_hole.icon_state = "dent" - add_overlay(bullet_hole) - return BULLET_ACT_HIT - return BULLET_ACT_FORCE_PIERCE - -#undef DECALTYPE_SCORCH -#undef DECALTYPE_BULLET +/obj/item/target + name = "shooting target" + desc = "A shooting target." + icon = 'icons/obj/objects.dmi' + icon_state = "target_h" + density = FALSE + var/hp = 1800 + var/obj/structure/target_stake/pinnedLoc + +/obj/item/target/Destroy() + removeOverlays() + if(pinnedLoc) + pinnedLoc.nullPinnedTarget() + return ..() + +/obj/item/target/proc/nullPinnedLoc() + pinnedLoc = null + density = FALSE + +/obj/item/target/proc/removeOverlays() + cut_overlays() + +/obj/item/target/Move() + . = ..() + if(pinnedLoc) + pinnedLoc.forceMove(loc) + +/obj/item/target/welder_act(mob/living/user, obj/item/I) + ..() + if(I.use_tool(src, user, 0, volume=40)) + removeOverlays() + to_chat(user, "You slice off [src]'s uneven chunks of aluminium and scorch marks.") + return TRUE + +/obj/item/target/attack_hand(mob/user) + . = ..() + if(.) + return + if(pinnedLoc) + pinnedLoc.removeTarget(user) + +/obj/item/target/syndicate + icon_state = "target_s" + desc = "A shooting target that looks like syndicate scum." + hp = 2600 + +/obj/item/target/alien + icon_state = "target_q" + desc = "A shooting target that looks like a xenomorphic alien." + hp = 2350 + +/obj/item/target/alien/anchored + anchored = TRUE + +/obj/item/target/clown + icon_state = "target_c" + desc = "A shooting target that looks like a useless clown." + hp = 2000 + +#define DECALTYPE_SCORCH 1 +#define DECALTYPE_BULLET 2 + +/obj/item/target/clown/bullet_act(obj/projectile/P) + . = ..() + playsound(src.loc, 'sound/items/bikehorn.ogg', 50, TRUE) + +/obj/item/target/bullet_act(obj/projectile/P) + if(istype(P, /obj/projectile/bullet/reusable)) // If it's a foam dart, don't bother with any of this other shit + return P.on_hit(src, 0) + var/p_x = P.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset P.p_x!" + var/p_y = P.p_y + pick(0,0,0,0,0,-1,1) + var/decaltype = DECALTYPE_SCORCH + if(istype(P, /obj/projectile/bullet)) + decaltype = DECALTYPE_BULLET + var/icon/C = icon(icon,icon_state) + if(C.GetPixel(p_x, p_y) && P.original == src && overlays.len <= 35) // if the located pixel isn't blank (null) + hp -= P.damage + if(hp <= 0) + visible_message("[src] breaks into tiny pieces and collapses!") + qdel(src) + var/image/bullet_hole = image('icons/effects/effects.dmi', "scorch", OBJ_LAYER + 0.5) + bullet_hole.pixel_x = p_x - 1 //offset correction + bullet_hole.pixel_y = p_y - 1 + if(decaltype == DECALTYPE_SCORCH) + bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))// random scorch design + if(P.damage >= 20 || istype(P, /obj/projectile/beam/practice)) + bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST)) + else + bullet_hole.icon_state = "light_scorch" + else + bullet_hole.icon_state = "dent" + add_overlay(bullet_hole) + return BULLET_ACT_HIT + return BULLET_ACT_FORCE_PIERCE + +#undef DECALTYPE_SCORCH +#undef DECALTYPE_BULLET diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm index a205a8eab76..699e496c1db 100644 --- a/code/game/objects/items/singularityhammer.dm +++ b/code/game/objects/items/singularityhammer.dm @@ -1,136 +1,136 @@ -/obj/item/singularityhammer - name = "singularity hammer" - desc = "The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows." - icon_state = "singularity_hammer0" - lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - force = 5 - throwforce = 15 - throw_range = 1 - w_class = WEIGHT_CLASS_HUGE - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - force_string = "LORD SINGULOTH HIMSELF" - ///Is it able to pull shit right now? - var/charged = TRUE - ///track wielded status on item - var/wielded = FALSE - -/obj/item/singularityhammer/Initialize() - . = ..() - RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) - RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) - -/obj/item/singularityhammer/ComponentInitialize() - . = ..() - AddComponent(/datum/component/two_handed, force_multiplier=4, icon_wielded="singularity_hammer1") - -///triggered on wield of two handed item -/obj/item/singularityhammer/proc/on_wield(obj/item/source, mob/user) - wielded = TRUE - -///triggered on unwield of two handed item -/obj/item/singularityhammer/proc/on_unwield(obj/item/source, mob/user) - wielded = FALSE - -/obj/item/singularityhammer/update_icon_state() - . = ..() - icon_state = "singularity_hammer0" - -/obj/item/singularityhammer/proc/recharge() - charged = TRUE - -/obj/item/singularityhammer/proc/vortex(turf/pull, mob/wielder) - for(var/atom/X in orange(5,pull)) - if(ismovable(X)) - var/atom/movable/A = X - if(A == wielder) - continue - if(A && !A.anchored && !ishuman(X) && !isobserver(X)) - step_towards(A,pull) - step_towards(A,pull) - step_towards(A,pull) - else if(ishuman(X)) - var/mob/living/carbon/human/H = X - if(istype(H.shoes, /obj/item/clothing/shoes/magboots)) - var/obj/item/clothing/shoes/magboots/M = H.shoes - if(M.magpulse) - continue - H.apply_effect(20, EFFECT_PARALYZE, 0) - step_towards(H,pull) - step_towards(H,pull) - step_towards(H,pull) - -/obj/item/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) - . = ..() - if(!proximity) - return - if(wielded) - if(charged) - charged = FALSE - if(istype(A, /mob/living/)) - var/mob/living/Z = A - Z.take_bodypart_damage(20,0) - playsound(user, 'sound/weapons/marauder.ogg', 50, TRUE) - var/turf/target = get_turf(A) - vortex(target,user) - addtimer(CALLBACK(src, .proc/recharge), 100) - -/obj/item/mjollnir - name = "Mjolnir" - desc = "A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy." - icon_state = "mjollnir0" - lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - force = 5 - throwforce = 30 - throw_range = 7 - w_class = WEIGHT_CLASS_HUGE - var/wielded = FALSE // track wielded status on item - -/obj/item/mjollnir/Initialize() - . = ..() - RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) - RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) - -/obj/item/mjollnir/ComponentInitialize() - . = ..() - AddComponent(/datum/component/two_handed, force_multiplier=5, icon_wielded="mjollnir1", attacksound="sparks") - -/// triggered on wield of two handed item -/obj/item/mjollnir/proc/on_wield(obj/item/source, mob/user) - wielded = TRUE - -/// triggered on unwield of two handed item -/obj/item/mjollnir/proc/on_unwield(obj/item/source, mob/user) - wielded = FALSE - -/obj/item/mjollnir/update_icon_state() - icon_state = "mjollnir0" - -/obj/item/mjollnir/proc/shock(mob/living/target) - target.Stun(1.5 SECONDS) - target.Knockdown(10 SECONDS) - var/datum/effect_system/lightning_spread/s = new /datum/effect_system/lightning_spread - s.set_up(5, 1, target.loc) - s.start() - target.visible_message("[target.name] is shocked by [src]!", \ - "You feel a powerful shock course through your body sending you flying!", \ - "You hear a heavy electrical crack!") - var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src))) - target.throw_at(throw_target, 200, 4) - return - -/obj/item/mjollnir/attack(mob/living/M, mob/user) - ..() - if(wielded) - shock(M) - -/obj/item/mjollnir/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(isliving(hit_atom)) - shock(hit_atom) +/obj/item/singularityhammer + name = "singularity hammer" + desc = "The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows." + icon_state = "singularity_hammer0" + lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + force = 5 + throwforce = 15 + throw_range = 1 + w_class = WEIGHT_CLASS_HUGE + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + force_string = "LORD SINGULOTH HIMSELF" + ///Is it able to pull shit right now? + var/charged = TRUE + ///track wielded status on item + var/wielded = FALSE + +/obj/item/singularityhammer/Initialize() + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + +/obj/item/singularityhammer/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, force_multiplier=4, icon_wielded="singularity_hammer1") + +///triggered on wield of two handed item +/obj/item/singularityhammer/proc/on_wield(obj/item/source, mob/user) + wielded = TRUE + +///triggered on unwield of two handed item +/obj/item/singularityhammer/proc/on_unwield(obj/item/source, mob/user) + wielded = FALSE + +/obj/item/singularityhammer/update_icon_state() + . = ..() + icon_state = "singularity_hammer0" + +/obj/item/singularityhammer/proc/recharge() + charged = TRUE + +/obj/item/singularityhammer/proc/vortex(turf/pull, mob/wielder) + for(var/atom/X in orange(5,pull)) + if(ismovable(X)) + var/atom/movable/A = X + if(A == wielder) + continue + if(A && !A.anchored && !ishuman(X) && !isobserver(X)) + step_towards(A,pull) + step_towards(A,pull) + step_towards(A,pull) + else if(ishuman(X)) + var/mob/living/carbon/human/H = X + if(istype(H.shoes, /obj/item/clothing/shoes/magboots)) + var/obj/item/clothing/shoes/magboots/M = H.shoes + if(M.magpulse) + continue + H.apply_effect(20, EFFECT_PARALYZE, 0) + step_towards(H,pull) + step_towards(H,pull) + step_towards(H,pull) + +/obj/item/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) + . = ..() + if(!proximity) + return + if(wielded) + if(charged) + charged = FALSE + if(istype(A, /mob/living/)) + var/mob/living/Z = A + Z.take_bodypart_damage(20,0) + playsound(user, 'sound/weapons/marauder.ogg', 50, TRUE) + var/turf/target = get_turf(A) + vortex(target,user) + addtimer(CALLBACK(src, .proc/recharge), 100) + +/obj/item/mjollnir + name = "Mjolnir" + desc = "A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy." + icon_state = "mjollnir0" + lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + force = 5 + throwforce = 30 + throw_range = 7 + w_class = WEIGHT_CLASS_HUGE + var/wielded = FALSE // track wielded status on item + +/obj/item/mjollnir/Initialize() + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + +/obj/item/mjollnir/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, force_multiplier=5, icon_wielded="mjollnir1", attacksound="sparks") + +/// triggered on wield of two handed item +/obj/item/mjollnir/proc/on_wield(obj/item/source, mob/user) + wielded = TRUE + +/// triggered on unwield of two handed item +/obj/item/mjollnir/proc/on_unwield(obj/item/source, mob/user) + wielded = FALSE + +/obj/item/mjollnir/update_icon_state() + icon_state = "mjollnir0" + +/obj/item/mjollnir/proc/shock(mob/living/target) + target.Stun(1.5 SECONDS) + target.Knockdown(10 SECONDS) + var/datum/effect_system/lightning_spread/s = new /datum/effect_system/lightning_spread + s.set_up(5, 1, target.loc) + s.start() + target.visible_message("[target.name] is shocked by [src]!", \ + "You feel a powerful shock course through your body sending you flying!", \ + "You hear a heavy electrical crack!") + var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src))) + target.throw_at(throw_target, 200, 4) + return + +/obj/item/mjollnir/attack(mob/living/M, mob/user) + ..() + if(wielded) + shock(M) + +/obj/item/mjollnir/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(isliving(hit_atom)) + shock(hit_atom) diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm index 343411aa51f..e88dd0bd93e 100644 --- a/code/game/objects/items/stacks/bscrystal.dm +++ b/code/game/objects/items/stacks/bscrystal.dm @@ -1,91 +1,91 @@ -//Bluespace crystals, used in telescience and when crushed it will blink you to a random turf. -/obj/item/stack/ore/bluespace_crystal - name = "bluespace crystal" - desc = "A glowing bluespace crystal, not much is known about how they work. It looks very delicate." - icon = 'icons/obj/telescience.dmi' - icon_state = "bluespace_crystal" - singular_name = "bluespace crystal" - dye_color = DYE_COSMIC - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) - points = 50 - var/blink_range = 8 // The teleport range when crushed/thrown at someone. - refined_type = /obj/item/stack/sheet/bluespace_crystal - grind_results = list(/datum/reagent/bluespace = 20) - scan_state = "rock_BScrystal" - -/obj/item/stack/ore/bluespace_crystal/refined - name = "refined bluespace crystal" - points = 0 - refined_type = null - -/obj/item/stack/ore/bluespace_crystal/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/item/stack/ore/bluespace_crystal/get_part_rating() - return 1 - -/obj/item/stack/ore/bluespace_crystal/attack_self(mob/user) - user.visible_message("[user] crushes [src]!", "You crush [src]!") - new /obj/effect/particle_effect/sparks(loc) - playsound(loc, "sparks", 50, TRUE) - blink_mob(user) - use(1) - -/obj/item/stack/ore/bluespace_crystal/proc/blink_mob(mob/living/L) - do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) - -/obj/item/stack/ore/bluespace_crystal/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) // not caught in mid-air - visible_message("[src] fizzles and disappears upon impact!") - var/turf/T = get_turf(hit_atom) - new /obj/effect/particle_effect/sparks(T) - playsound(loc, "sparks", 50, TRUE) - if(isliving(hit_atom)) - blink_mob(hit_atom) - use(1) - -//Artificial bluespace crystal, doesn't give you much research. -/obj/item/stack/ore/bluespace_crystal/artificial - name = "artificial bluespace crystal" - desc = "An artificially made bluespace crystal, it looks delicate." - custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT*0.5) - blink_range = 4 // Not as good as the organic stuff! - points = 0 //nice try - refined_type = null - grind_results = list(/datum/reagent/bluespace = 10, /datum/reagent/silicon = 20) - -//Polycrystals, aka stacks -/obj/item/stack/sheet/bluespace_crystal - name = "bluespace polycrystal" - icon = 'icons/obj/telescience.dmi' - icon_state = "polycrystal" - inhand_icon_state = "sheet-polycrystal" - singular_name = "bluespace polycrystal" - desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off." - custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) - attack_verb = list("bluespace polybashed", "bluespace polybattered", "bluespace polybludgeoned", "bluespace polythrashed", "bluespace polysmashed") - novariants = TRUE - grind_results = list(/datum/reagent/bluespace = 20) - point_value = 30 - var/crystal_type = /obj/item/stack/ore/bluespace_crystal/refined - -/obj/item/stack/sheet/bluespace_crystal/attack_self(mob/user)// to prevent the construction menu from ever happening - to_chat(user, "You cannot crush the polycrystal in-hand, try breaking one off.") - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/stack/sheet/bluespace_crystal/attack_hand(mob/user) - if(user.get_inactive_held_item() == src) - if(zero_amount()) - return - var/BC = new crystal_type(src) - user.put_in_hands(BC) - use(1) - if(!amount) - to_chat(user, "You break the final crystal off.") - else - to_chat(user, "You break off a crystal.") - else - ..() +//Bluespace crystals, used in telescience and when crushed it will blink you to a random turf. +/obj/item/stack/ore/bluespace_crystal + name = "bluespace crystal" + desc = "A glowing bluespace crystal, not much is known about how they work. It looks very delicate." + icon = 'icons/obj/telescience.dmi' + icon_state = "bluespace_crystal" + singular_name = "bluespace crystal" + dye_color = DYE_COSMIC + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) + points = 50 + var/blink_range = 8 // The teleport range when crushed/thrown at someone. + refined_type = /obj/item/stack/sheet/bluespace_crystal + grind_results = list(/datum/reagent/bluespace = 20) + scan_state = "rock_BScrystal" + +/obj/item/stack/ore/bluespace_crystal/refined + name = "refined bluespace crystal" + points = 0 + refined_type = null + +/obj/item/stack/ore/bluespace_crystal/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/item/stack/ore/bluespace_crystal/get_part_rating() + return 1 + +/obj/item/stack/ore/bluespace_crystal/attack_self(mob/user) + user.visible_message("[user] crushes [src]!", "You crush [src]!") + new /obj/effect/particle_effect/sparks(loc) + playsound(loc, "sparks", 50, TRUE) + blink_mob(user) + use(1) + +/obj/item/stack/ore/bluespace_crystal/proc/blink_mob(mob/living/L) + do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) + +/obj/item/stack/ore/bluespace_crystal/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) // not caught in mid-air + visible_message("[src] fizzles and disappears upon impact!") + var/turf/T = get_turf(hit_atom) + new /obj/effect/particle_effect/sparks(T) + playsound(loc, "sparks", 50, TRUE) + if(isliving(hit_atom)) + blink_mob(hit_atom) + use(1) + +//Artificial bluespace crystal, doesn't give you much research. +/obj/item/stack/ore/bluespace_crystal/artificial + name = "artificial bluespace crystal" + desc = "An artificially made bluespace crystal, it looks delicate." + custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT*0.5) + blink_range = 4 // Not as good as the organic stuff! + points = 0 //nice try + refined_type = null + grind_results = list(/datum/reagent/bluespace = 10, /datum/reagent/silicon = 20) + +//Polycrystals, aka stacks +/obj/item/stack/sheet/bluespace_crystal + name = "bluespace polycrystal" + icon = 'icons/obj/telescience.dmi' + icon_state = "polycrystal" + inhand_icon_state = "sheet-polycrystal" + singular_name = "bluespace polycrystal" + desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off." + custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) + attack_verb = list("bluespace polybashed", "bluespace polybattered", "bluespace polybludgeoned", "bluespace polythrashed", "bluespace polysmashed") + novariants = TRUE + grind_results = list(/datum/reagent/bluespace = 20) + point_value = 30 + var/crystal_type = /obj/item/stack/ore/bluespace_crystal/refined + +/obj/item/stack/sheet/bluespace_crystal/attack_self(mob/user)// to prevent the construction menu from ever happening + to_chat(user, "You cannot crush the polycrystal in-hand, try breaking one off.") + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/stack/sheet/bluespace_crystal/attack_hand(mob/user) + if(user.get_inactive_held_item() == src) + if(zero_amount()) + return + var/BC = new crystal_type(src) + user.put_in_hands(BC) + use(1) + if(!amount) + to_chat(user, "You break the final crystal off.") + else + to_chat(user, "You break off a crystal.") + else + ..() diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 345ffdb7abf..dadb5ada594 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -1,419 +1,419 @@ -/obj/item/stack/medical - name = "medical pack" - singular_name = "medical pack" - icon = 'icons/obj/stack_objects.dmi' - amount = 6 - max_amount = 6 - w_class = WEIGHT_CLASS_TINY - full_w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - resistance_flags = FLAMMABLE - max_integrity = 40 - novariants = FALSE - item_flags = NOBLUDGEON - var/self_delay = 50 - var/other_delay = 0 - var/repeating = FALSE - /// How much brute we heal per application - var/heal_brute - /// How much burn we heal per application - var/heal_burn - /// How much we reduce bleeding per application on cut wounds - var/stop_bleeding - /// How much sanitization to apply to burns on application - var/sanitization - /// How much we add to flesh_healing for burn wounds on application - var/flesh_regeneration - -/obj/item/stack/medical/attack(mob/living/M, mob/user) - . = ..() - try_heal(M, user) - - -/obj/item/stack/medical/proc/try_heal(mob/living/M, mob/user, silent = FALSE) - if(!M.can_inject(user, TRUE)) - return - if(M == user) - if(!silent) - user.visible_message("[user] starts to apply \the [src] on [user.p_them()]self...", "You begin applying \the [src] on yourself...") - if(!do_mob(user, M, self_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) - return - else if(other_delay) - if(!silent) - user.visible_message("[user] starts to apply \the [src] on [M].", "You begin applying \the [src] on [M]...") - if(!do_mob(user, M, other_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) - return - - if(heal(M, user)) - log_combat(user, M, "healed", src.name) - use(1) - if(repeating && amount > 0) - try_heal(M, user, TRUE) - -/obj/item/stack/medical/proc/heal(mob/living/M, mob/user) - return - -/obj/item/stack/medical/proc/heal_carbon(mob/living/carbon/C, mob/user, brute, burn) - var/obj/item/bodypart/affecting = C.get_bodypart(check_zone(user.zone_selected)) - if(!affecting) //Missing limb? - to_chat(user, "[C] doesn't have \a [parse_zone(user.zone_selected)]!") - return - if(affecting.status != BODYPART_ORGANIC) //Limb must be organic to be healed - RR - to_chat(user, "\The [src] won't work on a robotic limb!") - return - if(affecting.brute_dam && brute || affecting.burn_dam && burn) - user.visible_message("[user] applies \the [src] on [C]'s [affecting.name].", "You apply \the [src] on [C]'s [affecting.name].") - if(affecting.heal_damage(brute, burn)) - C.update_damage_overlays() - return TRUE - to_chat(user, "[C]'s [affecting.name] can not be healed with \the [src]!") - - -/obj/item/stack/medical/bruise_pack - name = "bruise pack" - singular_name = "bruise pack" - desc = "A therapeutic gel pack and bandages designed to treat blunt-force trauma." - icon_state = "brutepack" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - heal_brute = 40 - self_delay = 40 - other_delay = 20 - grind_results = list(/datum/reagent/medicine/c2/libital = 10) - -/obj/item/stack/medical/bruise_pack/heal(mob/living/M, mob/user) - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(isanimal(M)) - var/mob/living/simple_animal/critter = M - if (!(critter.healable)) - to_chat(user, "You cannot use \the [src] on [M]!") - return FALSE - else if (critter.health == critter.maxHealth) - to_chat(user, "[M] is at full health.") - return FALSE - user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") - M.heal_bodypart_damage((heal_brute/2)) - return TRUE - if(iscarbon(M)) - return heal_carbon(M, user, heal_brute, heal_burn) - to_chat(user, "You can't heal [M] with \the [src]!") - -/obj/item/stack/medical/bruise_pack/suicide_act(mob/user) - user.visible_message("[user] is bludgeoning [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/stack/medical/gauze - name = "medical gauze" - desc = "A roll of elastic cloth, perfect for stabilizing all kinds of wounds, from cuts and burns, to broken bones. " - gender = PLURAL - singular_name = "medical gauze" - icon_state = "gauze" - self_delay = 50 - other_delay = 20 - max_amount = 12 - amount = 6 - grind_results = list(/datum/reagent/cellulose = 2) - custom_price = 100 - absorption_rate = 0.25 - absorption_capacity = 5 - splint_factor = 0.35 - -// gauze is only relevant for wounds, which are handled in the wounds themselves -/obj/item/stack/medical/gauze/try_heal(mob/living/M, mob/user, silent) - var/obj/item/bodypart/limb = M.get_bodypart(check_zone(user.zone_selected)) - if(limb) - if(limb.brute_dam > 40) - to_chat(user, "The bleeding on [user==M ? "your" : "[M]'s"] [limb.name] is from bruising, and cannot be treated with [src]!") - else - to_chat(user, "There's no bleeding on [user==M ? "your" : "[M]'s"] [limb.name]") - -/obj/item/stack/medical/gauze/twelve - amount = 12 - -/obj/item/stack/medical/gauze/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) - if(get_amount() < 2) - to_chat(user, "You need at least two gauzes to do this!") - return - new /obj/item/stack/sheet/cloth(user.drop_location()) - user.visible_message("[user] cuts [src] into pieces of cloth with [I].", \ - "You cut [src] into pieces of cloth with [I].", \ - "You hear cutting.") - use(2) - else - return ..() - -/obj/item/stack/medical/gauze/suicide_act(mob/living/user) - user.visible_message("[user] begins tightening \the [src] around [user.p_their()] neck! It looks like [user.p_they()] forgot how to use medical supplies!") - return OXYLOSS - -/obj/item/stack/medical/gauze/improvised - name = "improvised gauze" - singular_name = "improvised gauze" - desc = "A roll of cloth roughly cut from something that does a decent job of stabilizing wounds, but less efficiently so than real medical gauze." - self_delay = 60 - other_delay = 30 - absorption_rate = 0.15 - absorption_capacity = 4 - -/obj/item/stack/medical/gauze/cyborg - custom_materials = null - is_cyborg = 1 - cost = 250 - -/obj/item/stack/medical/suture - name = "suture" - desc = "Basic sterile sutures used to seal up cuts and lacerations and stop bleeding." - gender = PLURAL - singular_name = "suture" - icon_state = "suture" - self_delay = 30 - other_delay = 10 - amount = 10 - max_amount = 10 - repeating = TRUE - heal_brute = 10 - stop_bleeding = 0.6 - grind_results = list(/datum/reagent/medicine/spaceacillin = 2) - -/obj/item/stack/medical/suture/emergency - name = "emergency suture" - desc = "A value pack of cheap sutures, not very good at repairing damage, but still decent at stopping bleeding." - heal_brute = 5 - amount = 5 - max_amount = 5 - -/obj/item/stack/medical/suture/medicated - name = "medicated suture" - icon_state = "suture_purp" - desc = "A suture infused with drugs that speed up wound healing of the treated laceration." - heal_brute = 15 - stop_bleeding = 0.75 - grind_results = list(/datum/reagent/medicine/polypyr = 2) - -/obj/item/stack/medical/suture/heal(mob/living/M, mob/user) - . = ..() - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(iscarbon(M)) - return heal_carbon(M, user, heal_brute, heal_burn) - if(isanimal(M)) - var/mob/living/simple_animal/critter = M - if (!(critter.healable)) - to_chat(user, "You cannot use \the [src] on [M]!") - return FALSE - else if (critter.health == critter.maxHealth) - to_chat(user, "[M] is at full health.") - return FALSE - user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") - M.heal_bodypart_damage(heal_brute) - return TRUE - - to_chat(user, "You can't heal [M] with \the [src]!") - -/obj/item/stack/medical/ointment - name = "ointment" - desc = "Basic burn ointment, rated effective for second degree burns with proper bandaging, though it's still an effective stabilizer for worse burns. Not terribly good at outright healing burns though." - gender = PLURAL - singular_name = "ointment" - icon_state = "ointment" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - amount = 10 - max_amount = 10 - self_delay = 40 - other_delay = 20 - - heal_burn = 5 - flesh_regeneration = 2.5 - sanitization = 0.3 - grind_results = list(/datum/reagent/medicine/c2/lenturi = 10) - -/obj/item/stack/medical/ointment/heal(mob/living/M, mob/user) - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(iscarbon(M)) - return heal_carbon(M, user, heal_brute, heal_burn) - to_chat(user, "You can't heal [M] with \the [src]!") - -/obj/item/stack/medical/ointment/suicide_act(mob/living/user) - user.visible_message("[user] is squeezing \the [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?") - return TOXLOSS - -/obj/item/stack/medical/mesh - name = "regenerative mesh" - desc = "A bacteriostatic mesh used to dress burns." - gender = PLURAL - singular_name = "regenerative mesh" - icon_state = "regen_mesh" - self_delay = 30 - other_delay = 10 - amount = 15 - heal_burn = 10 - max_amount = 15 - repeating = TRUE - sanitization = 0.75 - flesh_regeneration = 3 - - var/is_open = TRUE ///This var determines if the sterile packaging of the mesh has been opened. - grind_results = list(/datum/reagent/medicine/spaceacillin = 2) - -/obj/item/stack/medical/mesh/Initialize() - . = ..() - if(amount == max_amount) //only seal full mesh packs - is_open = FALSE - update_icon() - -/obj/item/stack/medical/mesh/update_icon_state() - if(!is_open) - icon_state = "regen_mesh_closed" - else - return ..() - -/obj/item/stack/medical/mesh/heal(mob/living/M, mob/user) - . = ..() - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(iscarbon(M)) - return heal_carbon(M, user, heal_brute, heal_burn) - to_chat(user, "You can't heal [M] with \the [src]!") - - -/obj/item/stack/medical/mesh/try_heal(mob/living/M, mob/user, silent = FALSE) - if(!is_open) - to_chat(user, "You need to open [src] first.") - return - . = ..() - -/obj/item/stack/medical/mesh/AltClick(mob/living/user) - if(!is_open) - to_chat(user, "You need to open [src] first.") - return - . = ..() - -/obj/item/stack/medical/mesh/attack_hand(mob/user) - if(!is_open && user.get_inactive_held_item() == src) - to_chat(user, "You need to open [src] first.") - return - . = ..() - -/obj/item/stack/medical/mesh/attack_self(mob/user) - if(!is_open) - is_open = TRUE - to_chat(user, "You open the sterile mesh package.") - update_icon() - playsound(src, 'sound/items/poster_ripped.ogg', 20, TRUE) - return - . = ..() - -/obj/item/stack/medical/mesh/advanced - name = "advanced regenerative mesh" - desc = "An advanced mesh made with aloe extracts and sterilizing chemicals, used to treat burns." - - gender = PLURAL - singular_name = "advanced regenerative mesh" - icon_state = "aloe_mesh" - heal_burn = 15 - sanitization = 1.25 - flesh_regeneration = 3.5 - grind_results = list(/datum/reagent/consumable/aloejuice = 1) - -/obj/item/stack/medical/mesh/advanced/update_icon_state() - if(!is_open) - icon_state = "aloe_mesh_closed" - else - return ..() - -/obj/item/stack/medical/aloe - name = "aloe cream" - desc = "A healing paste you can apply on wounds." - - icon_state = "aloe_paste" - self_delay = 20 - other_delay = 10 - novariants = TRUE - amount = 20 - max_amount = 20 - var/heal = 3 - grind_results = list(/datum/reagent/consumable/aloejuice = 1) - -/obj/item/stack/medical/aloe/heal(mob/living/M, mob/user) - . = ..() - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return FALSE - if(iscarbon(M)) - return heal_carbon(M, user, heal, heal) - if(isanimal(M)) - var/mob/living/simple_animal/critter = M - if (!(critter.healable)) - to_chat(user, "You cannot use \the [src] on [M]!") - return FALSE - else if (critter.health == critter.maxHealth) - to_chat(user, "[M] is at full health.") - return FALSE - user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") - M.heal_bodypart_damage(heal, heal) - return TRUE - - to_chat(user, "You can't heal [M] with the \the [src]!") - - /* - The idea is for these medical devices to work like a hybrid of the old brute packs and tend wounds, - they heal a little at a time, have reduced healing density and does not allow for rapid healing while in combat. - However they provice graunular control of where the healing is directed, this makes them better for curing work-related cuts and scrapes. - - The interesting limb targeting mechanic is retained and i still believe they will be a viable choice, especially when healing others in the field. - */ - -/obj/item/stack/medical/bone_gel - name = "bone gel" - singular_name = "bone gel" - desc = "A potent medical gel that, when applied to a damaged bone in a proper surgical setting, triggers an intense melding reaction to repair the wound. Can be directly applied alongside surgical sticky tape to a broken bone in dire circumstances, though this is very harmful to the patient and not recommended." - - icon = 'icons/obj/surgery.dmi' - icon_state = "bone-gel" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - - amount = 4 - self_delay = 20 - grind_results = list(/datum/reagent/medicine/c2/libital = 10) - novariants = TRUE - -/obj/item/stack/medical/bone_gel/attack(mob/living/M, mob/user) - to_chat(user, "Bone gel can only be used on fractured limbs!") - return - -/obj/item/stack/medical/bone_gel/suicide_act(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.visible_message("[C] is squirting all of \the [src] into [C.p_their()] mouth! That's not proper procedure! It looks like [C.p_theyre()] trying to commit suicide!") - if(do_after(C, 2 SECONDS)) - C.emote("scream") - for(var/i in C.bodyparts) - var/obj/item/bodypart/bone = i - var/datum/wound/brute/bone/severe/oof_ouch = new - oof_ouch.apply_wound(bone) - var/datum/wound/brute/bone/critical/oof_OUCH = new - oof_OUCH.apply_wound(bone) - - for(var/i in C.bodyparts) - var/obj/item/bodypart/bone = i - bone.receive_damage(brute=60) - use(1) - return (BRUTELOSS) - else - C.visible_message("[C] screws up like an idiot and still dies anyway!") - return (BRUTELOSS) - -/obj/item/stack/medical/bone_gel/cyborg - custom_materials = null - is_cyborg = 1 - cost = 250 +/obj/item/stack/medical + name = "medical pack" + singular_name = "medical pack" + icon = 'icons/obj/stack_objects.dmi' + amount = 6 + max_amount = 6 + w_class = WEIGHT_CLASS_TINY + full_w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + resistance_flags = FLAMMABLE + max_integrity = 40 + novariants = FALSE + item_flags = NOBLUDGEON + var/self_delay = 50 + var/other_delay = 0 + var/repeating = FALSE + /// How much brute we heal per application + var/heal_brute + /// How much burn we heal per application + var/heal_burn + /// How much we reduce bleeding per application on cut wounds + var/stop_bleeding + /// How much sanitization to apply to burns on application + var/sanitization + /// How much we add to flesh_healing for burn wounds on application + var/flesh_regeneration + +/obj/item/stack/medical/attack(mob/living/M, mob/user) + . = ..() + try_heal(M, user) + + +/obj/item/stack/medical/proc/try_heal(mob/living/M, mob/user, silent = FALSE) + if(!M.can_inject(user, TRUE)) + return + if(M == user) + if(!silent) + user.visible_message("[user] starts to apply \the [src] on [user.p_them()]self...", "You begin applying \the [src] on yourself...") + if(!do_mob(user, M, self_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) + return + else if(other_delay) + if(!silent) + user.visible_message("[user] starts to apply \the [src] on [M].", "You begin applying \the [src] on [M]...") + if(!do_mob(user, M, other_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) + return + + if(heal(M, user)) + log_combat(user, M, "healed", src.name) + use(1) + if(repeating && amount > 0) + try_heal(M, user, TRUE) + +/obj/item/stack/medical/proc/heal(mob/living/M, mob/user) + return + +/obj/item/stack/medical/proc/heal_carbon(mob/living/carbon/C, mob/user, brute, burn) + var/obj/item/bodypart/affecting = C.get_bodypart(check_zone(user.zone_selected)) + if(!affecting) //Missing limb? + to_chat(user, "[C] doesn't have \a [parse_zone(user.zone_selected)]!") + return + if(affecting.status != BODYPART_ORGANIC) //Limb must be organic to be healed - RR + to_chat(user, "\The [src] won't work on a robotic limb!") + return + if(affecting.brute_dam && brute || affecting.burn_dam && burn) + user.visible_message("[user] applies \the [src] on [C]'s [affecting.name].", "You apply \the [src] on [C]'s [affecting.name].") + if(affecting.heal_damage(brute, burn)) + C.update_damage_overlays() + return TRUE + to_chat(user, "[C]'s [affecting.name] can not be healed with \the [src]!") + + +/obj/item/stack/medical/bruise_pack + name = "bruise pack" + singular_name = "bruise pack" + desc = "A therapeutic gel pack and bandages designed to treat blunt-force trauma." + icon_state = "brutepack" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + heal_brute = 40 + self_delay = 40 + other_delay = 20 + grind_results = list(/datum/reagent/medicine/c2/libital = 10) + +/obj/item/stack/medical/bruise_pack/heal(mob/living/M, mob/user) + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(isanimal(M)) + var/mob/living/simple_animal/critter = M + if (!(critter.healable)) + to_chat(user, "You cannot use \the [src] on [M]!") + return FALSE + else if (critter.health == critter.maxHealth) + to_chat(user, "[M] is at full health.") + return FALSE + user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") + M.heal_bodypart_damage((heal_brute/2)) + return TRUE + if(iscarbon(M)) + return heal_carbon(M, user, heal_brute, heal_burn) + to_chat(user, "You can't heal [M] with \the [src]!") + +/obj/item/stack/medical/bruise_pack/suicide_act(mob/user) + user.visible_message("[user] is bludgeoning [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/stack/medical/gauze + name = "medical gauze" + desc = "A roll of elastic cloth, perfect for stabilizing all kinds of wounds, from cuts and burns, to broken bones. " + gender = PLURAL + singular_name = "medical gauze" + icon_state = "gauze" + self_delay = 50 + other_delay = 20 + max_amount = 12 + amount = 6 + grind_results = list(/datum/reagent/cellulose = 2) + custom_price = 100 + absorption_rate = 0.25 + absorption_capacity = 5 + splint_factor = 0.35 + +// gauze is only relevant for wounds, which are handled in the wounds themselves +/obj/item/stack/medical/gauze/try_heal(mob/living/M, mob/user, silent) + var/obj/item/bodypart/limb = M.get_bodypart(check_zone(user.zone_selected)) + if(limb) + if(limb.brute_dam > 40) + to_chat(user, "The bleeding on [user==M ? "your" : "[M]'s"] [limb.name] is from bruising, and cannot be treated with [src]!") + else + to_chat(user, "There's no bleeding on [user==M ? "your" : "[M]'s"] [limb.name]") + +/obj/item/stack/medical/gauze/twelve + amount = 12 + +/obj/item/stack/medical/gauze/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) + if(get_amount() < 2) + to_chat(user, "You need at least two gauzes to do this!") + return + new /obj/item/stack/sheet/cloth(user.drop_location()) + user.visible_message("[user] cuts [src] into pieces of cloth with [I].", \ + "You cut [src] into pieces of cloth with [I].", \ + "You hear cutting.") + use(2) + else + return ..() + +/obj/item/stack/medical/gauze/suicide_act(mob/living/user) + user.visible_message("[user] begins tightening \the [src] around [user.p_their()] neck! It looks like [user.p_they()] forgot how to use medical supplies!") + return OXYLOSS + +/obj/item/stack/medical/gauze/improvised + name = "improvised gauze" + singular_name = "improvised gauze" + desc = "A roll of cloth roughly cut from something that does a decent job of stabilizing wounds, but less efficiently so than real medical gauze." + self_delay = 60 + other_delay = 30 + absorption_rate = 0.15 + absorption_capacity = 4 + +/obj/item/stack/medical/gauze/cyborg + custom_materials = null + is_cyborg = 1 + cost = 250 + +/obj/item/stack/medical/suture + name = "suture" + desc = "Basic sterile sutures used to seal up cuts and lacerations and stop bleeding." + gender = PLURAL + singular_name = "suture" + icon_state = "suture" + self_delay = 30 + other_delay = 10 + amount = 10 + max_amount = 10 + repeating = TRUE + heal_brute = 10 + stop_bleeding = 0.6 + grind_results = list(/datum/reagent/medicine/spaceacillin = 2) + +/obj/item/stack/medical/suture/emergency + name = "emergency suture" + desc = "A value pack of cheap sutures, not very good at repairing damage, but still decent at stopping bleeding." + heal_brute = 5 + amount = 5 + max_amount = 5 + +/obj/item/stack/medical/suture/medicated + name = "medicated suture" + icon_state = "suture_purp" + desc = "A suture infused with drugs that speed up wound healing of the treated laceration." + heal_brute = 15 + stop_bleeding = 0.75 + grind_results = list(/datum/reagent/medicine/polypyr = 2) + +/obj/item/stack/medical/suture/heal(mob/living/M, mob/user) + . = ..() + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(iscarbon(M)) + return heal_carbon(M, user, heal_brute, heal_burn) + if(isanimal(M)) + var/mob/living/simple_animal/critter = M + if (!(critter.healable)) + to_chat(user, "You cannot use \the [src] on [M]!") + return FALSE + else if (critter.health == critter.maxHealth) + to_chat(user, "[M] is at full health.") + return FALSE + user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") + M.heal_bodypart_damage(heal_brute) + return TRUE + + to_chat(user, "You can't heal [M] with \the [src]!") + +/obj/item/stack/medical/ointment + name = "ointment" + desc = "Basic burn ointment, rated effective for second degree burns with proper bandaging, though it's still an effective stabilizer for worse burns. Not terribly good at outright healing burns though." + gender = PLURAL + singular_name = "ointment" + icon_state = "ointment" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + amount = 10 + max_amount = 10 + self_delay = 40 + other_delay = 20 + + heal_burn = 5 + flesh_regeneration = 2.5 + sanitization = 0.3 + grind_results = list(/datum/reagent/medicine/c2/lenturi = 10) + +/obj/item/stack/medical/ointment/heal(mob/living/M, mob/user) + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(iscarbon(M)) + return heal_carbon(M, user, heal_brute, heal_burn) + to_chat(user, "You can't heal [M] with \the [src]!") + +/obj/item/stack/medical/ointment/suicide_act(mob/living/user) + user.visible_message("[user] is squeezing \the [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?") + return TOXLOSS + +/obj/item/stack/medical/mesh + name = "regenerative mesh" + desc = "A bacteriostatic mesh used to dress burns." + gender = PLURAL + singular_name = "regenerative mesh" + icon_state = "regen_mesh" + self_delay = 30 + other_delay = 10 + amount = 15 + heal_burn = 10 + max_amount = 15 + repeating = TRUE + sanitization = 0.75 + flesh_regeneration = 3 + + var/is_open = TRUE ///This var determines if the sterile packaging of the mesh has been opened. + grind_results = list(/datum/reagent/medicine/spaceacillin = 2) + +/obj/item/stack/medical/mesh/Initialize() + . = ..() + if(amount == max_amount) //only seal full mesh packs + is_open = FALSE + update_icon() + +/obj/item/stack/medical/mesh/update_icon_state() + if(!is_open) + icon_state = "regen_mesh_closed" + else + return ..() + +/obj/item/stack/medical/mesh/heal(mob/living/M, mob/user) + . = ..() + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(iscarbon(M)) + return heal_carbon(M, user, heal_brute, heal_burn) + to_chat(user, "You can't heal [M] with \the [src]!") + + +/obj/item/stack/medical/mesh/try_heal(mob/living/M, mob/user, silent = FALSE) + if(!is_open) + to_chat(user, "You need to open [src] first.") + return + . = ..() + +/obj/item/stack/medical/mesh/AltClick(mob/living/user) + if(!is_open) + to_chat(user, "You need to open [src] first.") + return + . = ..() + +/obj/item/stack/medical/mesh/attack_hand(mob/user) + if(!is_open && user.get_inactive_held_item() == src) + to_chat(user, "You need to open [src] first.") + return + . = ..() + +/obj/item/stack/medical/mesh/attack_self(mob/user) + if(!is_open) + is_open = TRUE + to_chat(user, "You open the sterile mesh package.") + update_icon() + playsound(src, 'sound/items/poster_ripped.ogg', 20, TRUE) + return + . = ..() + +/obj/item/stack/medical/mesh/advanced + name = "advanced regenerative mesh" + desc = "An advanced mesh made with aloe extracts and sterilizing chemicals, used to treat burns." + + gender = PLURAL + singular_name = "advanced regenerative mesh" + icon_state = "aloe_mesh" + heal_burn = 15 + sanitization = 1.25 + flesh_regeneration = 3.5 + grind_results = list(/datum/reagent/consumable/aloejuice = 1) + +/obj/item/stack/medical/mesh/advanced/update_icon_state() + if(!is_open) + icon_state = "aloe_mesh_closed" + else + return ..() + +/obj/item/stack/medical/aloe + name = "aloe cream" + desc = "A healing paste you can apply on wounds." + + icon_state = "aloe_paste" + self_delay = 20 + other_delay = 10 + novariants = TRUE + amount = 20 + max_amount = 20 + var/heal = 3 + grind_results = list(/datum/reagent/consumable/aloejuice = 1) + +/obj/item/stack/medical/aloe/heal(mob/living/M, mob/user) + . = ..() + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return FALSE + if(iscarbon(M)) + return heal_carbon(M, user, heal, heal) + if(isanimal(M)) + var/mob/living/simple_animal/critter = M + if (!(critter.healable)) + to_chat(user, "You cannot use \the [src] on [M]!") + return FALSE + else if (critter.health == critter.maxHealth) + to_chat(user, "[M] is at full health.") + return FALSE + user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") + M.heal_bodypart_damage(heal, heal) + return TRUE + + to_chat(user, "You can't heal [M] with the \the [src]!") + + /* + The idea is for these medical devices to work like a hybrid of the old brute packs and tend wounds, + they heal a little at a time, have reduced healing density and does not allow for rapid healing while in combat. + However they provice graunular control of where the healing is directed, this makes them better for curing work-related cuts and scrapes. + + The interesting limb targeting mechanic is retained and i still believe they will be a viable choice, especially when healing others in the field. + */ + +/obj/item/stack/medical/bone_gel + name = "bone gel" + singular_name = "bone gel" + desc = "A potent medical gel that, when applied to a damaged bone in a proper surgical setting, triggers an intense melding reaction to repair the wound. Can be directly applied alongside surgical sticky tape to a broken bone in dire circumstances, though this is very harmful to the patient and not recommended." + + icon = 'icons/obj/surgery.dmi' + icon_state = "bone-gel" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + + amount = 4 + self_delay = 20 + grind_results = list(/datum/reagent/medicine/c2/libital = 10) + novariants = TRUE + +/obj/item/stack/medical/bone_gel/attack(mob/living/M, mob/user) + to_chat(user, "Bone gel can only be used on fractured limbs!") + return + +/obj/item/stack/medical/bone_gel/suicide_act(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.visible_message("[C] is squirting all of \the [src] into [C.p_their()] mouth! That's not proper procedure! It looks like [C.p_theyre()] trying to commit suicide!") + if(do_after(C, 2 SECONDS)) + C.emote("scream") + for(var/i in C.bodyparts) + var/obj/item/bodypart/bone = i + var/datum/wound/brute/bone/severe/oof_ouch = new + oof_ouch.apply_wound(bone) + var/datum/wound/brute/bone/critical/oof_OUCH = new + oof_OUCH.apply_wound(bone) + + for(var/i in C.bodyparts) + var/obj/item/bodypart/bone = i + bone.receive_damage(brute=60) + use(1) + return (BRUTELOSS) + else + C.visible_message("[C] screws up like an idiot and still dies anyway!") + return (BRUTELOSS) + +/obj/item/stack/medical/bone_gel/cyborg + custom_materials = null + is_cyborg = 1 + cost = 250 diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 482fd3fcb7d..b5142017b14 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -1,109 +1,109 @@ -GLOBAL_LIST_INIT(rod_recipes, list ( \ - new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = TRUE, on_floor = FALSE), \ - new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ - new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 5, one_per_turf = 0), \ - new/datum/stack_recipe("railing", /obj/structure/railing, 3, time = 18, window_checks = TRUE), \ - )) - -/obj/item/stack/rods - name = "metal rod" - desc = "Some rods. Can be used for building or something." - singular_name = "metal rod" - icon_state = "rods" - inhand_icon_state = "rods" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_NORMAL - force = 9 - throwforce = 10 - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=1000) - max_amount = 50 - attack_verb = list("hit", "bludgeoned", "whacked") - hitsound = 'sound/weapons/gun/general/grenade_launch.ogg' - embedding = list() - novariants = TRUE - -/obj/item/stack/rods/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to stuff \the [src] down [user.p_their()] throat! It looks like [user.p_theyre()] trying to commit suicide!")//it looks like theyre ur mum - return BRUTELOSS - -/obj/item/stack/rods/Initialize(mapload, new_amount, merge = TRUE) - . = ..() - update_icon() - -/obj/item/stack/rods/get_main_recipes() - . = ..() - . += GLOB.rod_recipes - -/obj/item/stack/rods/update_icon_state() - var/amount = get_amount() - if(amount <= 5) - icon_state = "rods-[amount]" - else - icon_state = "rods" - -/obj/item/stack/rods/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WELDER) - if(get_amount() < 2) - to_chat(user, "You need at least two rods to do this!") - return - - if(W.use_tool(src, user, 0, volume=40)) - var/obj/item/stack/sheet/metal/new_item = new(usr.loc) - user.visible_message("[user.name] shaped [src] into metal with [W].", \ - "You shape [src] into metal with [W].", \ - "You hear welding.") - var/obj/item/stack/rods/R = src - src = null - var/replace = (user.get_inactive_held_item()==R) - R.use(2) - if (!R && replace) - user.put_in_hands(new_item) - - else if(istype(W, /obj/item/reagent_containers/food/snacks)) - var/obj/item/reagent_containers/food/snacks/S = W - if(amount != 1) - to_chat(user, "You must use a single rod!") - else if(S.w_class > WEIGHT_CLASS_SMALL) - to_chat(user, "The ingredient is too big for [src]!") - else - var/obj/item/reagent_containers/food/snacks/customizable/A = new/obj/item/reagent_containers/food/snacks/customizable/kebab(get_turf(src)) - A.initialize_custom_food(src, S, user) - else - return ..() - -/obj/item/stack/rods/cyborg - custom_materials = null - is_cyborg = 1 - cost = 250 - -/obj/item/stack/rods/cyborg/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/stack/rods/ten - amount = 10 - -/obj/item/stack/rods/twentyfive - amount = 25 - -/obj/item/stack/rods/fifty - amount = 50 - -/obj/item/stack/rods/lava - name = "heat resistant rod" - desc = "Treated, specialized metal rods. When exposed to the vaccum of space their coating breaks off, but they can hold up against the extreme heat of active lava." - singular_name = "heat resistant rod" - icon_state = "rods" - inhand_icon_state = "rods" - color = "#5286b9ff" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=1000, /datum/material/plasma=500, /datum/material/titanium=2000) - max_amount = 30 - resistance_flags = FIRE_PROOF | LAVA_PROOF - -/obj/item/stack/rods/lava/thirty - amount = 30 +GLOBAL_LIST_INIT(rod_recipes, list ( \ + new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = TRUE, on_floor = FALSE), \ + new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ + new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 5, one_per_turf = 0), \ + new/datum/stack_recipe("railing", /obj/structure/railing, 3, time = 18, window_checks = TRUE), \ + )) + +/obj/item/stack/rods + name = "metal rod" + desc = "Some rods. Can be used for building or something." + singular_name = "metal rod" + icon_state = "rods" + inhand_icon_state = "rods" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_NORMAL + force = 9 + throwforce = 10 + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=1000) + max_amount = 50 + attack_verb = list("hit", "bludgeoned", "whacked") + hitsound = 'sound/weapons/gun/general/grenade_launch.ogg' + embedding = list() + novariants = TRUE + +/obj/item/stack/rods/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to stuff \the [src] down [user.p_their()] throat! It looks like [user.p_theyre()] trying to commit suicide!")//it looks like theyre ur mum + return BRUTELOSS + +/obj/item/stack/rods/Initialize(mapload, new_amount, merge = TRUE) + . = ..() + update_icon() + +/obj/item/stack/rods/get_main_recipes() + . = ..() + . += GLOB.rod_recipes + +/obj/item/stack/rods/update_icon_state() + var/amount = get_amount() + if(amount <= 5) + icon_state = "rods-[amount]" + else + icon_state = "rods" + +/obj/item/stack/rods/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WELDER) + if(get_amount() < 2) + to_chat(user, "You need at least two rods to do this!") + return + + if(W.use_tool(src, user, 0, volume=40)) + var/obj/item/stack/sheet/metal/new_item = new(usr.loc) + user.visible_message("[user.name] shaped [src] into metal with [W].", \ + "You shape [src] into metal with [W].", \ + "You hear welding.") + var/obj/item/stack/rods/R = src + src = null + var/replace = (user.get_inactive_held_item()==R) + R.use(2) + if (!R && replace) + user.put_in_hands(new_item) + + else if(istype(W, /obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/S = W + if(amount != 1) + to_chat(user, "You must use a single rod!") + else if(S.w_class > WEIGHT_CLASS_SMALL) + to_chat(user, "The ingredient is too big for [src]!") + else + var/obj/item/reagent_containers/food/snacks/customizable/A = new/obj/item/reagent_containers/food/snacks/customizable/kebab(get_turf(src)) + A.initialize_custom_food(src, S, user) + else + return ..() + +/obj/item/stack/rods/cyborg + custom_materials = null + is_cyborg = 1 + cost = 250 + +/obj/item/stack/rods/cyborg/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/stack/rods/ten + amount = 10 + +/obj/item/stack/rods/twentyfive + amount = 25 + +/obj/item/stack/rods/fifty + amount = 50 + +/obj/item/stack/rods/lava + name = "heat resistant rod" + desc = "Treated, specialized metal rods. When exposed to the vaccum of space their coating breaks off, but they can hold up against the extreme heat of active lava." + singular_name = "heat resistant rod" + icon_state = "rods" + inhand_icon_state = "rods" + color = "#5286b9ff" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=1000, /datum/material/plasma=500, /datum/material/titanium=2000) + max_amount = 30 + resistance_flags = FIRE_PROOF | LAVA_PROOF + +/obj/item/stack/rods/lava/thirty + amount = 30 diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 2543bfd65f5..facca0f589a 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -1,360 +1,360 @@ -/* Glass stack types - * Contains: - * Glass sheets - * Reinforced glass sheets - * Glass shards - TODO: Move this into code/game/object/item/weapons - */ - -/* - * Glass sheets - */ -GLOBAL_LIST_INIT(glass_recipes, list ( \ - new/datum/stack_recipe("directional window", /obj/structure/window/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile window", /obj/structure/window/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/glass - name = "glass" - desc = "HOLY SHEET! That is a lot of glass." - singular_name = "glass sheet" - icon_state = "sheet-glass" - inhand_icon_state = "sheet-glass" - custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/glass - grind_results = list(/datum/reagent/silicon = 20) - material_type = /datum/material/glass - point_value = 1 - tableVariant = /obj/structure/table/glass - -/obj/item/stack/sheet/glass/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to slice [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/stack/sheet/glass/cyborg - custom_materials = null - is_cyborg = 1 - cost = 500 - -/obj/item/stack/sheet/glass/fifty - amount = 50 - -/obj/item/stack/sheet/glass/get_main_recipes() - . = ..() - . += GLOB.glass_recipes - -/obj/item/stack/sheet/glass/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/CC = W - if (get_amount() < 1 || CC.get_amount() < 5) - to_chat(user, "You attach wire to the [name].") - var/obj/item/stack/light_w/new_tile = new(user.loc) - new_tile.add_fingerprint(user) - else if(istype(W, /obj/item/stack/rods)) - var/obj/item/stack/rods/V = W - if (V.get_amount() >= 1 && get_amount() >= 1) - var/obj/item/stack/sheet/rglass/RG = new (get_turf(user)) - RG.add_fingerprint(user) - var/replace = user.get_inactive_held_item()==src - V.use(1) - use(1) - if(QDELETED(src) && replace) - user.put_in_hands(RG) - else - to_chat(user, "You need one rod and one sheet of glass to make reinforced glass!") - return - else - return ..() - - - -GLOBAL_LIST_INIT(pglass_recipes, list ( \ - new/datum/stack_recipe("directional window", /obj/structure/window/plasma/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile window", /obj/structure/window/plasma/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/plasmaglass - name = "plasma glass" - desc = "A glass sheet made out of a plasma-silicate alloy. It looks extremely tough and heavily fire resistant." - singular_name = "plasma glass sheet" - icon_state = "sheet-pglass" - inhand_icon_state = "sheet-pglass" - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/plasmaglass - grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10) - material_flags = MATERIAL_NO_EFFECTS - -/obj/item/stack/sheet/plasmaglass/fifty - amount = 50 - -/obj/item/stack/sheet/plasmaglass/get_main_recipes() - . = ..() - . += GLOB.pglass_recipes - -/obj/item/stack/sheet/plasmaglass/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - - if(istype(W, /obj/item/stack/rods)) - var/obj/item/stack/rods/V = W - if (V.get_amount() >= 1 && get_amount() >= 1) - var/obj/item/stack/sheet/plasmarglass/RG = new (get_turf(user)) - RG.add_fingerprint(user) - var/replace = user.get_inactive_held_item()==src - V.use(1) - use(1) - if(QDELETED(src) && replace) - user.put_in_hands(RG) - else - to_chat(user, "You need one rod and one sheet of plasma glass to make reinforced plasma glass!") - return - else - return ..() - - - -/* - * Reinforced glass sheets - */ -GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ - new/datum/stack_recipe("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_floor = TRUE, window_checks = TRUE), \ - null, \ - new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - - -/obj/item/stack/sheet/rglass - name = "reinforced glass" - desc = "Glass which seems to have rods or something stuck in them." - singular_name = "reinforced glass sheet" - icon_state = "sheet-rglass" - inhand_icon_state = "sheet-rglass" - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/rglass - grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10) - point_value = 4 - -/obj/item/stack/sheet/rglass/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - ..() - -/obj/item/stack/sheet/rglass/cyborg - custom_materials = null - var/datum/robot_energy_storage/glasource - var/metcost = 250 - var/glacost = 500 - -/obj/item/stack/sheet/rglass/cyborg/get_amount() - return min(round(source.energy / metcost), round(glasource.energy / glacost)) - -/obj/item/stack/sheet/rglass/cyborg/use(used, transfer = FALSE) // Requires special checks, because it uses two storages - if(get_amount(used)) //ensure we still have enough energy if called in a do_after chain - source.use_charge(used * metcost) - glasource.use_charge(used * glacost) - return TRUE - -/obj/item/stack/sheet/rglass/cyborg/add(amount) - source.add_charge(amount * metcost) - glasource.add_charge(amount * glacost) - -/obj/item/stack/sheet/rglass/get_main_recipes() - . = ..() - . += GLOB.reinforced_glass_recipes - -GLOBAL_LIST_INIT(prglass_recipes, list ( \ - new/datum/stack_recipe("directional reinforced window", /obj/structure/window/plasma/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/plasma/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/plasmarglass - name = "reinforced plasma glass" - desc = "A glass sheet made out of a plasma-silicate alloy and a rod matrix. It looks hopelessly tough and nearly fire-proof!" - singular_name = "reinforced plasma glass sheet" - icon_state = "sheet-prglass" - inhand_icon_state = "sheet-prglass" - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT, /datum/material/iron = MINERAL_MATERIAL_AMOUNT * 0.5) - armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - resistance_flags = ACID_PROOF - material_flags = MATERIAL_NO_EFFECTS - merge_type = /obj/item/stack/sheet/plasmarglass - grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10, /datum/reagent/iron = 10) - point_value = 23 - -/obj/item/stack/sheet/plasmarglass/get_main_recipes() - . = ..() - . += GLOB.prglass_recipes - -GLOBAL_LIST_INIT(titaniumglass_recipes, list( - new/datum/stack_recipe("shuttle window", /obj/structure/window/shuttle/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) - )) - -/obj/item/stack/sheet/titaniumglass - name = "titanium glass" - desc = "A glass sheet made out of a titanium-silicate alloy." - singular_name = "titanium glass sheet" - icon_state = "sheet-titaniumglass" - inhand_icon_state = "sheet-titaniumglass" - custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/titaniumglass - -/obj/item/stack/sheet/titaniumglass/get_main_recipes() - . = ..() - . += GLOB.titaniumglass_recipes - -GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( - new/datum/stack_recipe("plastitanium window", /obj/structure/window/plasma/reinforced/plastitanium/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) - )) - -/obj/item/stack/sheet/plastitaniumglass - name = "plastitanium glass" - desc = "A glass sheet made out of a plasma-titanium-silicate alloy." - singular_name = "plastitanium glass sheet" - icon_state = "sheet-plastitaniumglass" - inhand_icon_state = "sheet-plastitaniumglass" - custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - material_flags = MATERIAL_NO_EFFECTS - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/plastitaniumglass - -/obj/item/stack/sheet/plastitaniumglass/get_main_recipes() - . = ..() - . += GLOB.plastitaniumglass_recipes - -/obj/item/shard - name = "shard" - desc = "A nasty looking shard of glass." - icon = 'icons/obj/shards.dmi' - icon_state = "large" - w_class = WEIGHT_CLASS_TINY - force = 5 - throwforce = 10 - inhand_icon_state = "shard-glass" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) - attack_verb = list("stabbed", "slashed", "sliced", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - resistance_flags = ACID_PROOF - armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) - max_integrity = 40 - sharpness = IS_SHARP - var/icon_prefix - var/obj/item/stack/sheet/weld_material = /obj/item/stack/sheet/glass - embedding = list("embed_chance" = 65) - - -/obj/item/shard/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shard of glass! It looks like [user.p_theyre()] trying to commit suicide.") - return (BRUTELOSS) - - -/obj/item/shard/Initialize() - . = ..() - AddComponent(/datum/component/caltrop, force) - AddComponent(/datum/component/butchering, 150, 65) - icon_state = pick("large", "medium", "small") - switch(icon_state) - if("small") - pixel_x = rand(-12, 12) - pixel_y = rand(-12, 12) - if("medium") - pixel_x = rand(-8, 8) - pixel_y = rand(-8, 8) - if("large") - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - if (icon_prefix) - icon_state = "[icon_prefix][icon_state]" - - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_created", 1, name) - -/obj/item/shard/Destroy() - . = ..() - - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) - -/obj/item/shard/afterattack(atom/A as mob|obj, mob/user, proximity) - . = ..() - if(!proximity || !(src in user)) - return - if(isturf(A)) - return - if(istype(A, /obj/item/storage)) - return - var/hit_hand = ((user.active_hand_index % 2 == 0) ? "r_" : "l_") + "arm" - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(!H.gloves && !HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) // golems, etc - to_chat(H, "[src] cuts into your hand!") - H.apply_damage(force*0.5, BRUTE, hit_hand) - else if(ismonkey(user)) - var/mob/living/carbon/monkey/M = user - if(!HAS_TRAIT(M, TRAIT_PIERCEIMMUNE)) - to_chat(M, "[src] cuts into your hand!") - M.apply_damage(force*0.5, BRUTE, hit_hand) - -/obj/item/shard/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/lightreplacer)) - var/obj/item/lightreplacer/L = I - L.attackby(src, user) - else if(istype(I, /obj/item/stack/sheet/cloth)) - var/obj/item/stack/sheet/cloth/C = I - to_chat(user, "You begin to wrap the [C] around the [src]...") - if(do_after(user, 35, target = src)) - var/obj/item/kitchen/knife/shiv/S = new /obj/item/kitchen/knife/shiv - C.use(1) - to_chat(user, "You wrap the [C] around the [src] forming a makeshift weapon.") - remove_item_from_storage(src) - qdel(src) - user.put_in_hands(S) - - else - return ..() - -/obj/item/shard/welder_act(mob/living/user, obj/item/I) - ..() - if(I.use_tool(src, user, 0, volume=50)) - var/obj/item/stack/sheet/NG = new weld_material(user.loc) - for(var/obj/item/stack/sheet/G in user.loc) - if(G == NG) - continue - if(G.amount >= G.max_amount) - continue - G.attackby(NG, user) - to_chat(user, "You add the newly-formed [NG.name] to the stack. It now contains [NG.amount] sheet\s.") - qdel(src) - return TRUE - -/obj/item/shard/Crossed(atom/movable/AM) - if(isliving(AM)) - var/mob/living/L = AM - if(!(L.is_flying() || L.is_floating() || L.buckled)) - playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE) - return ..() - -/obj/item/shard/plasma - name = "purple shard" - desc = "A nasty looking shard of plasma glass." - force = 6 - throwforce = 11 - icon_state = "plasmalarge" - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - icon_prefix = "plasma" - weld_material = /obj/item/stack/sheet/plasmaglass +/* Glass stack types + * Contains: + * Glass sheets + * Reinforced glass sheets + * Glass shards - TODO: Move this into code/game/object/item/weapons + */ + +/* + * Glass sheets + */ +GLOBAL_LIST_INIT(glass_recipes, list ( \ + new/datum/stack_recipe("directional window", /obj/structure/window/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile window", /obj/structure/window/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/glass + name = "glass" + desc = "HOLY SHEET! That is a lot of glass." + singular_name = "glass sheet" + icon_state = "sheet-glass" + inhand_icon_state = "sheet-glass" + custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/glass + grind_results = list(/datum/reagent/silicon = 20) + material_type = /datum/material/glass + point_value = 1 + tableVariant = /obj/structure/table/glass + +/obj/item/stack/sheet/glass/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to slice [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/stack/sheet/glass/cyborg + custom_materials = null + is_cyborg = 1 + cost = 500 + +/obj/item/stack/sheet/glass/fifty + amount = 50 + +/obj/item/stack/sheet/glass/get_main_recipes() + . = ..() + . += GLOB.glass_recipes + +/obj/item/stack/sheet/glass/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if (get_amount() < 1 || CC.get_amount() < 5) + to_chat(user, "You attach wire to the [name].") + var/obj/item/stack/light_w/new_tile = new(user.loc) + new_tile.add_fingerprint(user) + else if(istype(W, /obj/item/stack/rods)) + var/obj/item/stack/rods/V = W + if (V.get_amount() >= 1 && get_amount() >= 1) + var/obj/item/stack/sheet/rglass/RG = new (get_turf(user)) + RG.add_fingerprint(user) + var/replace = user.get_inactive_held_item()==src + V.use(1) + use(1) + if(QDELETED(src) && replace) + user.put_in_hands(RG) + else + to_chat(user, "You need one rod and one sheet of glass to make reinforced glass!") + return + else + return ..() + + + +GLOBAL_LIST_INIT(pglass_recipes, list ( \ + new/datum/stack_recipe("directional window", /obj/structure/window/plasma/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile window", /obj/structure/window/plasma/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/plasmaglass + name = "plasma glass" + desc = "A glass sheet made out of a plasma-silicate alloy. It looks extremely tough and heavily fire resistant." + singular_name = "plasma glass sheet" + icon_state = "sheet-pglass" + inhand_icon_state = "sheet-pglass" + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/plasmaglass + grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10) + material_flags = MATERIAL_NO_EFFECTS + +/obj/item/stack/sheet/plasmaglass/fifty + amount = 50 + +/obj/item/stack/sheet/plasmaglass/get_main_recipes() + . = ..() + . += GLOB.pglass_recipes + +/obj/item/stack/sheet/plasmaglass/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + + if(istype(W, /obj/item/stack/rods)) + var/obj/item/stack/rods/V = W + if (V.get_amount() >= 1 && get_amount() >= 1) + var/obj/item/stack/sheet/plasmarglass/RG = new (get_turf(user)) + RG.add_fingerprint(user) + var/replace = user.get_inactive_held_item()==src + V.use(1) + use(1) + if(QDELETED(src) && replace) + user.put_in_hands(RG) + else + to_chat(user, "You need one rod and one sheet of plasma glass to make reinforced plasma glass!") + return + else + return ..() + + + +/* + * Reinforced glass sheets + */ +GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ + new/datum/stack_recipe("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_floor = TRUE, window_checks = TRUE), \ + null, \ + new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + + +/obj/item/stack/sheet/rglass + name = "reinforced glass" + desc = "Glass which seems to have rods or something stuck in them." + singular_name = "reinforced glass sheet" + icon_state = "sheet-rglass" + inhand_icon_state = "sheet-rglass" + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/rglass + grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10) + point_value = 4 + +/obj/item/stack/sheet/rglass/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + ..() + +/obj/item/stack/sheet/rglass/cyborg + custom_materials = null + var/datum/robot_energy_storage/glasource + var/metcost = 250 + var/glacost = 500 + +/obj/item/stack/sheet/rglass/cyborg/get_amount() + return min(round(source.energy / metcost), round(glasource.energy / glacost)) + +/obj/item/stack/sheet/rglass/cyborg/use(used, transfer = FALSE) // Requires special checks, because it uses two storages + if(get_amount(used)) //ensure we still have enough energy if called in a do_after chain + source.use_charge(used * metcost) + glasource.use_charge(used * glacost) + return TRUE + +/obj/item/stack/sheet/rglass/cyborg/add(amount) + source.add_charge(amount * metcost) + glasource.add_charge(amount * glacost) + +/obj/item/stack/sheet/rglass/get_main_recipes() + . = ..() + . += GLOB.reinforced_glass_recipes + +GLOBAL_LIST_INIT(prglass_recipes, list ( \ + new/datum/stack_recipe("directional reinforced window", /obj/structure/window/plasma/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/plasma/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/plasmarglass + name = "reinforced plasma glass" + desc = "A glass sheet made out of a plasma-silicate alloy and a rod matrix. It looks hopelessly tough and nearly fire-proof!" + singular_name = "reinforced plasma glass sheet" + icon_state = "sheet-prglass" + inhand_icon_state = "sheet-prglass" + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT, /datum/material/iron = MINERAL_MATERIAL_AMOUNT * 0.5) + armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + resistance_flags = ACID_PROOF + material_flags = MATERIAL_NO_EFFECTS + merge_type = /obj/item/stack/sheet/plasmarglass + grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10, /datum/reagent/iron = 10) + point_value = 23 + +/obj/item/stack/sheet/plasmarglass/get_main_recipes() + . = ..() + . += GLOB.prglass_recipes + +GLOBAL_LIST_INIT(titaniumglass_recipes, list( + new/datum/stack_recipe("shuttle window", /obj/structure/window/shuttle/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) + )) + +/obj/item/stack/sheet/titaniumglass + name = "titanium glass" + desc = "A glass sheet made out of a titanium-silicate alloy." + singular_name = "titanium glass sheet" + icon_state = "sheet-titaniumglass" + inhand_icon_state = "sheet-titaniumglass" + custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/titaniumglass + +/obj/item/stack/sheet/titaniumglass/get_main_recipes() + . = ..() + . += GLOB.titaniumglass_recipes + +GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( + new/datum/stack_recipe("plastitanium window", /obj/structure/window/plasma/reinforced/plastitanium/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) + )) + +/obj/item/stack/sheet/plastitaniumglass + name = "plastitanium glass" + desc = "A glass sheet made out of a plasma-titanium-silicate alloy." + singular_name = "plastitanium glass sheet" + icon_state = "sheet-plastitaniumglass" + inhand_icon_state = "sheet-plastitaniumglass" + custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + material_flags = MATERIAL_NO_EFFECTS + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/plastitaniumglass + +/obj/item/stack/sheet/plastitaniumglass/get_main_recipes() + . = ..() + . += GLOB.plastitaniumglass_recipes + +/obj/item/shard + name = "shard" + desc = "A nasty looking shard of glass." + icon = 'icons/obj/shards.dmi' + icon_state = "large" + w_class = WEIGHT_CLASS_TINY + force = 5 + throwforce = 10 + inhand_icon_state = "shard-glass" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) + attack_verb = list("stabbed", "slashed", "sliced", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + resistance_flags = ACID_PROOF + armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) + max_integrity = 40 + sharpness = IS_SHARP + var/icon_prefix + var/obj/item/stack/sheet/weld_material = /obj/item/stack/sheet/glass + embedding = list("embed_chance" = 65) + + +/obj/item/shard/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shard of glass! It looks like [user.p_theyre()] trying to commit suicide.") + return (BRUTELOSS) + + +/obj/item/shard/Initialize() + . = ..() + AddComponent(/datum/component/caltrop, force) + AddComponent(/datum/component/butchering, 150, 65) + icon_state = pick("large", "medium", "small") + switch(icon_state) + if("small") + pixel_x = rand(-12, 12) + pixel_y = rand(-12, 12) + if("medium") + pixel_x = rand(-8, 8) + pixel_y = rand(-8, 8) + if("large") + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + if (icon_prefix) + icon_state = "[icon_prefix][icon_state]" + + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_created", 1, name) + +/obj/item/shard/Destroy() + . = ..() + + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) + +/obj/item/shard/afterattack(atom/A as mob|obj, mob/user, proximity) + . = ..() + if(!proximity || !(src in user)) + return + if(isturf(A)) + return + if(istype(A, /obj/item/storage)) + return + var/hit_hand = ((user.active_hand_index % 2 == 0) ? "r_" : "l_") + "arm" + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(!H.gloves && !HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) // golems, etc + to_chat(H, "[src] cuts into your hand!") + H.apply_damage(force*0.5, BRUTE, hit_hand) + else if(ismonkey(user)) + var/mob/living/carbon/monkey/M = user + if(!HAS_TRAIT(M, TRAIT_PIERCEIMMUNE)) + to_chat(M, "[src] cuts into your hand!") + M.apply_damage(force*0.5, BRUTE, hit_hand) + +/obj/item/shard/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/lightreplacer)) + var/obj/item/lightreplacer/L = I + L.attackby(src, user) + else if(istype(I, /obj/item/stack/sheet/cloth)) + var/obj/item/stack/sheet/cloth/C = I + to_chat(user, "You begin to wrap the [C] around the [src]...") + if(do_after(user, 35, target = src)) + var/obj/item/kitchen/knife/shiv/S = new /obj/item/kitchen/knife/shiv + C.use(1) + to_chat(user, "You wrap the [C] around the [src] forming a makeshift weapon.") + remove_item_from_storage(src) + qdel(src) + user.put_in_hands(S) + + else + return ..() + +/obj/item/shard/welder_act(mob/living/user, obj/item/I) + ..() + if(I.use_tool(src, user, 0, volume=50)) + var/obj/item/stack/sheet/NG = new weld_material(user.loc) + for(var/obj/item/stack/sheet/G in user.loc) + if(G == NG) + continue + if(G.amount >= G.max_amount) + continue + G.attackby(NG, user) + to_chat(user, "You add the newly-formed [NG.name] to the stack. It now contains [NG.amount] sheet\s.") + qdel(src) + return TRUE + +/obj/item/shard/Crossed(atom/movable/AM) + if(isliving(AM)) + var/mob/living/L = AM + if(!(L.is_flying() || L.is_floating() || L.buckled)) + playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE) + return ..() + +/obj/item/shard/plasma + name = "purple shard" + desc = "A nasty looking shard of plasma glass." + force = 6 + throwforce = 11 + icon_state = "plasmalarge" + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + icon_prefix = "plasma" + weld_material = /obj/item/stack/sheet/plasmaglass diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index 13093a66b58..0871e4b7877 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -1,257 +1,257 @@ -/obj/item/stack/sheet/animalhide - name = "hide" - desc = "Something went wrong." - icon_state = "sheet-hide" - inhand_icon_state = "sheet-hide" - novariants = TRUE - -/obj/item/stack/sheet/animalhide/human - name = "human skin" - desc = "The by-product of human farming." - singular_name = "human skin piece" - novariants = FALSE - -GLOBAL_LIST_INIT(human_recipes, list( \ - new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/hooded/bloated_human, 5), \ - )) - -/obj/item/stack/sheet/animalhide/human/get_main_recipes() - . = ..() - . += GLOB.human_recipes - -/obj/item/stack/sheet/animalhide/generic - name = "skin" - desc = "A piece of skin." - singular_name = "skin piece" - novariants = FALSE - -/obj/item/stack/sheet/animalhide/corgi - name = "corgi hide" - desc = "The by-product of corgi farming." - singular_name = "corgi hide piece" - icon_state = "sheet-corgi" - inhand_icon_state = "sheet-corgi" - - -GLOBAL_LIST_INIT(gondola_recipes, list ( \ - new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1), \ - new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2), \ - )) - -/obj/item/stack/sheet/animalhide/gondola - name = "gondola hide" - desc = "The extremely valuable product of gondola hunting." - singular_name = "gondola hide piece" - icon_state = "sheet-gondola" - inhand_icon_state = "sheet-gondola" - -/obj/item/stack/sheet/animalhide/gondola/get_main_recipes() - . = ..() - . += GLOB.gondola_recipes - -GLOBAL_LIST_INIT(corgi_recipes, list ( \ - new/datum/stack_recipe("corgi costume", /obj/item/clothing/suit/hooded/ian_costume, 3), \ - )) - -/obj/item/stack/sheet/animalhide/corgi/get_main_recipes() - . = ..() - . += GLOB.corgi_recipes - -/obj/item/stack/sheet/animalhide/cat - name = "cat hide" - desc = "The by-product of cat farming." - singular_name = "cat hide piece" - icon_state = "sheet-cat" - inhand_icon_state = "sheet-cat" - -/obj/item/stack/sheet/animalhide/monkey - name = "monkey hide" - desc = "The by-product of monkey farming." - singular_name = "monkey hide piece" - icon_state = "sheet-monkey" - inhand_icon_state = "sheet-monkey" - -GLOBAL_LIST_INIT(monkey_recipes, list ( \ - new/datum/stack_recipe("monkey mask", /obj/item/clothing/mask/gas/monkeymask, 1), \ - new/datum/stack_recipe("monkey suit", /obj/item/clothing/suit/monkeysuit, 2), \ - )) - -/obj/item/stack/sheet/animalhide/monkey/get_main_recipes() - . = ..() - . += GLOB.monkey_recipes - -/obj/item/stack/sheet/animalhide/lizard - name = "lizard skin" - desc = "Sssssss..." - singular_name = "lizard skin piece" - icon_state = "sheet-lizard" - inhand_icon_state = "sheet-lizard" - -/obj/item/stack/sheet/animalhide/xeno - name = "alien hide" - desc = "The skin of a terrible creature." - singular_name = "alien hide piece" - icon_state = "sheet-xeno" - inhand_icon_state = "sheet-xeno" - -GLOBAL_LIST_INIT(xeno_recipes, list ( \ - new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/xenos, 1), \ - new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/xenos, 2), \ - )) - -/obj/item/stack/sheet/animalhide/xeno/get_main_recipes() - . = ..() - . += GLOB.xeno_recipes - -//don't see anywhere else to put these, maybe together they could be used to make the xenos suit? -/obj/item/stack/sheet/xenochitin - name = "alien chitin" - desc = "A piece of the hide of a terrible creature." - singular_name = "alien hide piece" - icon = 'icons/mob/alien.dmi' - icon_state = "chitin" - novariants = TRUE - -/obj/item/xenos_claw - name = "alien claw" - desc = "The claw of a terrible creature." - icon = 'icons/mob/alien.dmi' - icon_state = "claw" - -/obj/item/weed_extract - name = "weed extract" - desc = "A piece of slimy, purplish weed." - icon = 'icons/mob/alien.dmi' - icon_state = "weed_extract" - -/obj/item/stack/sheet/hairlesshide - name = "hairless hide" - desc = "This hide was stripped of its hair, but still needs washing and tanning." - singular_name = "hairless hide piece" - icon_state = "sheet-hairlesshide" - inhand_icon_state = "sheet-hairlesshide" - -/obj/item/stack/sheet/wethide - name = "wet hide" - desc = "This hide has been cleaned but still needs to be dried." - singular_name = "wet hide piece" - icon_state = "sheet-wetleather" - inhand_icon_state = "sheet-wetleather" - var/wetness = 30 //Reduced when exposed to high temperautres - var/drying_threshold_temperature = 500 //Kelvin to start drying - -/* - * Leather SHeet - */ -/obj/item/stack/sheet/leather - name = "leather" - desc = "The by-product of mob grinding." - singular_name = "leather piece" - icon_state = "sheet-leather" - inhand_icon_state = "sheet-leather" - -GLOBAL_LIST_INIT(leather_recipes, list ( \ - new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1), \ - new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2), \ - new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3), \ - new/datum/stack_recipe("toolbelt", /obj/item/storage/belt/utility, 4), \ - new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel/leather, 5), \ - new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5), \ - new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7), \ - new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2), \ - new/datum/stack_recipe("leather overcoat", /obj/item/clothing/suit/jacket/leather/overcoat, 10), \ - new/datum/stack_recipe("saddle", /obj/item/saddle, 5), \ -)) - -/obj/item/stack/sheet/leather/get_main_recipes() - . = ..() - . += GLOB.leather_recipes -/* - * Sinew - */ -/obj/item/stack/sheet/sinew - name = "watcher sinew" - icon = 'icons/obj/mining.dmi' - desc = "Long stringy filaments which presumably came from a watcher's wings." - singular_name = "watcher sinew" - icon_state = "sinew" - novariants = TRUE - -/obj/item/stack/sheet/sinew/wolf - name = "wolf sinew" - desc = "Long stringy filaments which came from the insides of a wolf." - singular_name = "wolf sinew" - - -GLOBAL_LIST_INIT(sinew_recipes, list ( \ - new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/cable/sinew, 1), \ -)) - -/obj/item/stack/sheet/sinew/get_main_recipes() - . = ..() - . += GLOB.sinew_recipes - - /* - * Plates - */ -/obj/item/stack/sheet/animalhide/goliath_hide - name = "goliath hide plates" - desc = "Pieces of a goliath's rocky hide, these might be able to make your suit a bit more durable to attack from the local fauna." - icon = 'icons/obj/mining.dmi' - icon_state = "goliath_hide" - singular_name = "hide plate" - max_amount = 6 - novariants = FALSE - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_NORMAL - layer = MOB_LAYER - -/obj/item/stack/sheet/animalhide/goliath_hide/polar_bear_hide - name = "polar bear hides" - desc = "Pieces of a polar bear's fur, these might be able to make your suit a bit more durable to attack from the local fauna." - icon_state = "polar_bear_hide" - singular_name = "polar bear hide" - -/obj/item/stack/sheet/animalhide/ashdrake - name = "ash drake hide" - desc = "The strong, scaled hide of an ash drake." - icon = 'icons/obj/mining.dmi' - icon_state = "dragon_hide" - singular_name = "drake plate" - max_amount = 10 - novariants = FALSE - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_NORMAL - layer = MOB_LAYER - - -//Step one - dehairing. - -/obj/item/stack/sheet/animalhide/attackby(obj/item/W, mob/user, params) - if(W.get_sharpness()) - playsound(loc, 'sound/weapons/slice.ogg', 50, TRUE, -1) - user.visible_message("[user] starts cutting hair off \the [src].", "You start cutting the hair off \the [src]...", "You hear the sound of a knife rubbing against flesh.") - if(do_after(user, 50, target = src)) - to_chat(user, "You cut the hair from this [src.singular_name].") - new /obj/item/stack/sheet/hairlesshide(user.drop_location(), 1) - use(1) - else - return ..() - - -//Step two - washing..... it's actually in washing machine code. - -//Step three - drying -/obj/item/stack/sheet/wethide/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature >= drying_threshold_temperature) - wetness-- - if(wetness == 0) - new /obj/item/stack/sheet/leather(drop_location(), 1) - wetness = initial(wetness) - use(1) - -/obj/item/stack/sheet/wethide/microwave_act(obj/machinery/microwave/MW) - ..() - new /obj/item/stack/sheet/leather(drop_location(), amount) - qdel(src) +/obj/item/stack/sheet/animalhide + name = "hide" + desc = "Something went wrong." + icon_state = "sheet-hide" + inhand_icon_state = "sheet-hide" + novariants = TRUE + +/obj/item/stack/sheet/animalhide/human + name = "human skin" + desc = "The by-product of human farming." + singular_name = "human skin piece" + novariants = FALSE + +GLOBAL_LIST_INIT(human_recipes, list( \ + new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/hooded/bloated_human, 5), \ + )) + +/obj/item/stack/sheet/animalhide/human/get_main_recipes() + . = ..() + . += GLOB.human_recipes + +/obj/item/stack/sheet/animalhide/generic + name = "skin" + desc = "A piece of skin." + singular_name = "skin piece" + novariants = FALSE + +/obj/item/stack/sheet/animalhide/corgi + name = "corgi hide" + desc = "The by-product of corgi farming." + singular_name = "corgi hide piece" + icon_state = "sheet-corgi" + inhand_icon_state = "sheet-corgi" + + +GLOBAL_LIST_INIT(gondola_recipes, list ( \ + new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1), \ + new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2), \ + )) + +/obj/item/stack/sheet/animalhide/gondola + name = "gondola hide" + desc = "The extremely valuable product of gondola hunting." + singular_name = "gondola hide piece" + icon_state = "sheet-gondola" + inhand_icon_state = "sheet-gondola" + +/obj/item/stack/sheet/animalhide/gondola/get_main_recipes() + . = ..() + . += GLOB.gondola_recipes + +GLOBAL_LIST_INIT(corgi_recipes, list ( \ + new/datum/stack_recipe("corgi costume", /obj/item/clothing/suit/hooded/ian_costume, 3), \ + )) + +/obj/item/stack/sheet/animalhide/corgi/get_main_recipes() + . = ..() + . += GLOB.corgi_recipes + +/obj/item/stack/sheet/animalhide/cat + name = "cat hide" + desc = "The by-product of cat farming." + singular_name = "cat hide piece" + icon_state = "sheet-cat" + inhand_icon_state = "sheet-cat" + +/obj/item/stack/sheet/animalhide/monkey + name = "monkey hide" + desc = "The by-product of monkey farming." + singular_name = "monkey hide piece" + icon_state = "sheet-monkey" + inhand_icon_state = "sheet-monkey" + +GLOBAL_LIST_INIT(monkey_recipes, list ( \ + new/datum/stack_recipe("monkey mask", /obj/item/clothing/mask/gas/monkeymask, 1), \ + new/datum/stack_recipe("monkey suit", /obj/item/clothing/suit/monkeysuit, 2), \ + )) + +/obj/item/stack/sheet/animalhide/monkey/get_main_recipes() + . = ..() + . += GLOB.monkey_recipes + +/obj/item/stack/sheet/animalhide/lizard + name = "lizard skin" + desc = "Sssssss..." + singular_name = "lizard skin piece" + icon_state = "sheet-lizard" + inhand_icon_state = "sheet-lizard" + +/obj/item/stack/sheet/animalhide/xeno + name = "alien hide" + desc = "The skin of a terrible creature." + singular_name = "alien hide piece" + icon_state = "sheet-xeno" + inhand_icon_state = "sheet-xeno" + +GLOBAL_LIST_INIT(xeno_recipes, list ( \ + new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/xenos, 1), \ + new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/xenos, 2), \ + )) + +/obj/item/stack/sheet/animalhide/xeno/get_main_recipes() + . = ..() + . += GLOB.xeno_recipes + +//don't see anywhere else to put these, maybe together they could be used to make the xenos suit? +/obj/item/stack/sheet/xenochitin + name = "alien chitin" + desc = "A piece of the hide of a terrible creature." + singular_name = "alien hide piece" + icon = 'icons/mob/alien.dmi' + icon_state = "chitin" + novariants = TRUE + +/obj/item/xenos_claw + name = "alien claw" + desc = "The claw of a terrible creature." + icon = 'icons/mob/alien.dmi' + icon_state = "claw" + +/obj/item/weed_extract + name = "weed extract" + desc = "A piece of slimy, purplish weed." + icon = 'icons/mob/alien.dmi' + icon_state = "weed_extract" + +/obj/item/stack/sheet/hairlesshide + name = "hairless hide" + desc = "This hide was stripped of its hair, but still needs washing and tanning." + singular_name = "hairless hide piece" + icon_state = "sheet-hairlesshide" + inhand_icon_state = "sheet-hairlesshide" + +/obj/item/stack/sheet/wethide + name = "wet hide" + desc = "This hide has been cleaned but still needs to be dried." + singular_name = "wet hide piece" + icon_state = "sheet-wetleather" + inhand_icon_state = "sheet-wetleather" + var/wetness = 30 //Reduced when exposed to high temperautres + var/drying_threshold_temperature = 500 //Kelvin to start drying + +/* + * Leather SHeet + */ +/obj/item/stack/sheet/leather + name = "leather" + desc = "The by-product of mob grinding." + singular_name = "leather piece" + icon_state = "sheet-leather" + inhand_icon_state = "sheet-leather" + +GLOBAL_LIST_INIT(leather_recipes, list ( \ + new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1), \ + new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2), \ + new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3), \ + new/datum/stack_recipe("toolbelt", /obj/item/storage/belt/utility, 4), \ + new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel/leather, 5), \ + new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5), \ + new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7), \ + new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2), \ + new/datum/stack_recipe("leather overcoat", /obj/item/clothing/suit/jacket/leather/overcoat, 10), \ + new/datum/stack_recipe("saddle", /obj/item/saddle, 5), \ +)) + +/obj/item/stack/sheet/leather/get_main_recipes() + . = ..() + . += GLOB.leather_recipes +/* + * Sinew + */ +/obj/item/stack/sheet/sinew + name = "watcher sinew" + icon = 'icons/obj/mining.dmi' + desc = "Long stringy filaments which presumably came from a watcher's wings." + singular_name = "watcher sinew" + icon_state = "sinew" + novariants = TRUE + +/obj/item/stack/sheet/sinew/wolf + name = "wolf sinew" + desc = "Long stringy filaments which came from the insides of a wolf." + singular_name = "wolf sinew" + + +GLOBAL_LIST_INIT(sinew_recipes, list ( \ + new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/cable/sinew, 1), \ +)) + +/obj/item/stack/sheet/sinew/get_main_recipes() + . = ..() + . += GLOB.sinew_recipes + + /* + * Plates + */ +/obj/item/stack/sheet/animalhide/goliath_hide + name = "goliath hide plates" + desc = "Pieces of a goliath's rocky hide, these might be able to make your suit a bit more durable to attack from the local fauna." + icon = 'icons/obj/mining.dmi' + icon_state = "goliath_hide" + singular_name = "hide plate" + max_amount = 6 + novariants = FALSE + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_NORMAL + layer = MOB_LAYER + +/obj/item/stack/sheet/animalhide/goliath_hide/polar_bear_hide + name = "polar bear hides" + desc = "Pieces of a polar bear's fur, these might be able to make your suit a bit more durable to attack from the local fauna." + icon_state = "polar_bear_hide" + singular_name = "polar bear hide" + +/obj/item/stack/sheet/animalhide/ashdrake + name = "ash drake hide" + desc = "The strong, scaled hide of an ash drake." + icon = 'icons/obj/mining.dmi' + icon_state = "dragon_hide" + singular_name = "drake plate" + max_amount = 10 + novariants = FALSE + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_NORMAL + layer = MOB_LAYER + + +//Step one - dehairing. + +/obj/item/stack/sheet/animalhide/attackby(obj/item/W, mob/user, params) + if(W.get_sharpness()) + playsound(loc, 'sound/weapons/slice.ogg', 50, TRUE, -1) + user.visible_message("[user] starts cutting hair off \the [src].", "You start cutting the hair off \the [src]...", "You hear the sound of a knife rubbing against flesh.") + if(do_after(user, 50, target = src)) + to_chat(user, "You cut the hair from this [src.singular_name].") + new /obj/item/stack/sheet/hairlesshide(user.drop_location(), 1) + use(1) + else + return ..() + + +//Step two - washing..... it's actually in washing machine code. + +//Step three - drying +/obj/item/stack/sheet/wethide/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature >= drying_threshold_temperature) + wetness-- + if(wetness == 0) + new /obj/item/stack/sheet/leather(drop_location(), 1) + wetness = initial(wetness) + use(1) + +/obj/item/stack/sheet/wethide/microwave_act(obj/machinery/microwave/MW) + ..() + new /obj/item/stack/sheet/leather(drop_location(), amount) + qdel(src) diff --git a/code/game/objects/items/stacks/sheets/light.dm b/code/game/objects/items/stacks/sheets/light.dm index da6cc64c9e3..84062a0ce69 100644 --- a/code/game/objects/items/stacks/sheets/light.dm +++ b/code/game/objects/items/stacks/sheets/light.dm @@ -1,36 +1,36 @@ -/obj/item/stack/light_w - name = "wired glass tile" - singular_name = "wired glass floor tile" - desc = "A glass tile, which is wired, somehow." - icon = 'icons/obj/tiles.dmi' - icon_state = "glass_wire" - w_class = WEIGHT_CLASS_NORMAL - force = 3 - throwforce = 5 - throw_speed = 3 - throw_range = 7 - flags_1 = CONDUCT_1 - max_amount = 60 - grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/copper = 5) - -/obj/item/stack/light_w/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = O - if (M.use(1)) - var/obj/item/L = new /obj/item/stack/tile/light(user.drop_location()) - to_chat(user, "You make a light tile.") - L.add_fingerprint(user) - use(1) - else - to_chat(user, "You need one metal sheet to finish the light tile!") - else - return ..() - -/obj/item/stack/light_w/wirecutter_act(mob/living/user, obj/item/I) - . = ..() - var/atom/Tsec = user.drop_location() - var/obj/item/stack/cable_coil/CC = new (Tsec, 5) - CC.add_fingerprint(user) - var/obj/item/stack/sheet/glass/G = new (Tsec) - G.add_fingerprint(user) - use(1) +/obj/item/stack/light_w + name = "wired glass tile" + singular_name = "wired glass floor tile" + desc = "A glass tile, which is wired, somehow." + icon = 'icons/obj/tiles.dmi' + icon_state = "glass_wire" + w_class = WEIGHT_CLASS_NORMAL + force = 3 + throwforce = 5 + throw_speed = 3 + throw_range = 7 + flags_1 = CONDUCT_1 + max_amount = 60 + grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/copper = 5) + +/obj/item/stack/light_w/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = O + if (M.use(1)) + var/obj/item/L = new /obj/item/stack/tile/light(user.drop_location()) + to_chat(user, "You make a light tile.") + L.add_fingerprint(user) + use(1) + else + to_chat(user, "You need one metal sheet to finish the light tile!") + else + return ..() + +/obj/item/stack/light_w/wirecutter_act(mob/living/user, obj/item/I) + . = ..() + var/atom/Tsec = user.drop_location() + var/obj/item/stack/cable_coil/CC = new (Tsec, 5) + CC.add_fingerprint(user) + var/obj/item/stack/sheet/glass/G = new (Tsec) + G.add_fingerprint(user) + use(1) diff --git a/code/game/objects/items/stacks/sheets/sheets.dm b/code/game/objects/items/stacks/sheets/sheets.dm index 53dfc44ca15..1ec59613752 100644 --- a/code/game/objects/items/stacks/sheets/sheets.dm +++ b/code/game/objects/items/stacks/sheets/sheets.dm @@ -1,21 +1,21 @@ -/obj/item/stack/sheet - name = "sheet" - lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi' - full_w_class = WEIGHT_CLASS_NORMAL - force = 5 - throwforce = 5 - max_amount = 50 - throw_speed = 1 - throw_range = 3 - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") - novariants = FALSE - var/sheettype = null //this is used for girders in the creation of walls/false walls - var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity. - ///What type of wall does this sheet spawn - var/walltype - -/obj/item/stack/sheet/Initialize(mapload, new_amount, merge) - . = ..() - pixel_x = rand(-4, 4) - pixel_y = rand(-4, 4) +/obj/item/stack/sheet + name = "sheet" + lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi' + full_w_class = WEIGHT_CLASS_NORMAL + force = 5 + throwforce = 5 + max_amount = 50 + throw_speed = 1 + throw_range = 3 + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") + novariants = FALSE + var/sheettype = null //this is used for girders in the creation of walls/false walls + var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity. + ///What type of wall does this sheet spawn + var/walltype + +/obj/item/stack/sheet/Initialize(mapload, new_amount, merge) + . = ..() + pixel_x = rand(-4, 4) + pixel_y = rand(-4, 4) diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm index 1d39f887c87..d982db80652 100644 --- a/code/game/objects/items/storage/backpack.dm +++ b/code/game/objects/items/storage/backpack.dm @@ -1,624 +1,624 @@ -/* Backpacks - * Contains: - * Backpack - * Backpack Types - * Satchel Types - */ - -/* - * Backpack - */ - -/obj/item/storage/backpack - name = "backpack" - desc = "You wear this on your back and put items into it." - icon_state = "backpack" - inhand_icon_state = "backpack" - lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - slot_flags = ITEM_SLOT_BACK //ERROOOOO - resistance_flags = NONE - max_integrity = 300 - -/obj/item/storage/backpack/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 21 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_items = 21 - -/* - * Backpack Types - */ - -/obj/item/storage/backpack/old/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 12 - -/obj/item/bag_of_holding_inert - name = "inert bag of holding" - desc = "What is currently a just an unwieldly block of metal with a slot ready to accept a bluespace anomaly core." - icon = 'icons/obj/storage.dmi' - icon_state = "brokenpack" - inhand_icon_state = "brokenpack" - lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - resistance_flags = FIRE_PROOF - item_flags = NO_MAT_REDEMPTION - -/obj/item/storage/backpack/holding - name = "bag of holding" - desc = "A backpack that opens into a localized pocket of bluespace." - icon_state = "holdingpack" - inhand_icon_state = "holdingpack" - resistance_flags = FIRE_PROOF - item_flags = NO_MAT_REDEMPTION - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50) - component_type = /datum/component/storage/concrete/bluespace/bag_of_holding - -/obj/item/storage/backpack/holding/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_big_nesting = TRUE - STR.max_w_class = WEIGHT_CLASS_GIGANTIC - STR.max_combined_w_class = 35 - -/obj/item/storage/backpack/holding/suicide_act(mob/living/user) - user.visible_message("[user] is jumping into [src]! It looks like [user.p_theyre()] trying to commit suicide.") - user.dropItemToGround(src, TRUE) - user.Stun(100, ignore_canstun = TRUE) - sleep(20) - playsound(src, "rustle", 50, TRUE, -5) - qdel(user) - -/obj/item/storage/backpack/santabag - name = "Santa's Gift Bag" - desc = "Space Santa uses this to deliver presents to all the nice children in space in Christmas! Wow, it's pretty big!" - icon_state = "giftbag0" - inhand_icon_state = "giftbag" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/backpack/santabag/Initialize() - . = ..() - regenerate_presents() - -/obj/item/storage/backpack/santabag/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 60 - -/obj/item/storage/backpack/santabag/suicide_act(mob/user) - user.visible_message("[user] places [src] over [user.p_their()] head and pulls it tight! It looks like [user.p_they()] [user.p_are()]n't in the Christmas spirit...") - return (OXYLOSS) - -/obj/item/storage/backpack/santabag/proc/regenerate_presents() - addtimer(CALLBACK(src, .proc/regenerate_presents), 30 SECONDS) - - var/mob/M = get(loc, /mob) - if(!istype(M)) - return - if(M.mind && HAS_TRAIT(M.mind, TRAIT_CANNOT_OPEN_PRESENTS)) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - var/turf/floor = get_turf(src) - var/obj/item/I = new /obj/item/a_gift/anything(floor) - if(STR.can_be_inserted(I, stop_messages=TRUE)) - STR.handle_item_insertion(I, prevent_warning=TRUE) - else - qdel(I) - - -/obj/item/storage/backpack/cultpack - name = "trophy rack" - desc = "It's useful for both carrying extra gear and proudly declaring your insanity." - icon_state = "cultpack" - inhand_icon_state = "backpack" - -/obj/item/storage/backpack/clown - name = "Giggles von Honkerton" - desc = "It's a backpack made by Honk! Co." - icon_state = "clownpack" - inhand_icon_state = "clownpack" - -/obj/item/storage/backpack/explorer - name = "explorer bag" - desc = "A robust backpack for stashing your loot." - icon_state = "explorerpack" - inhand_icon_state = "explorerpack" - -/obj/item/storage/backpack/mime - name = "Parcel Parceaux" - desc = "A silent backpack made for those silent workers. Silence Co." - icon_state = "mimepack" - inhand_icon_state = "mimepack" - -/obj/item/storage/backpack/medic - name = "medical backpack" - desc = "It's a backpack especially designed for use in a sterile environment." - icon_state = "medicalpack" - inhand_icon_state = "medicalpack" - -/obj/item/storage/backpack/security - name = "security backpack" - desc = "It's a very robust backpack." - icon_state = "securitypack" - inhand_icon_state = "securitypack" - -/obj/item/storage/backpack/captain - name = "captain's backpack" - desc = "It's a special backpack made exclusively for Nanotrasen officers." - icon_state = "captainpack" - inhand_icon_state = "captainpack" - -/obj/item/storage/backpack/industrial - name = "industrial backpack" - desc = "It's a tough backpack for the daily grind of station life." - icon_state = "engiepack" - inhand_icon_state = "engiepack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/botany - name = "botany backpack" - desc = "It's a backpack made of all-natural fibers." - icon_state = "botpack" - inhand_icon_state = "botpack" - -/obj/item/storage/backpack/chemistry - name = "chemistry backpack" - desc = "A backpack specially designed to repel stains and hazardous liquids." - icon_state = "chempack" - inhand_icon_state = "chempack" - -/obj/item/storage/backpack/genetics - name = "genetics backpack" - desc = "A bag designed to be super tough, just in case someone hulks out on you." - icon_state = "genepack" - inhand_icon_state = "genepack" - -/obj/item/storage/backpack/science - name = "science backpack" - desc = "A specially designed backpack. It's fire resistant and smells vaguely of plasma." - icon_state = "toxpack" - inhand_icon_state = "toxpack" - -/obj/item/storage/backpack/virology - name = "virology backpack" - desc = "A backpack made of hypo-allergenic fibers. It's designed to help prevent the spread of disease. Smells like monkey." - icon_state = "viropack" - inhand_icon_state = "viropack" - -/obj/item/storage/backpack/ert - name = "emergency response team commander backpack" - desc = "A spacious backpack with lots of pockets, worn by the Commander of an Emergency Response Team." - icon_state = "ert_commander" - inhand_icon_state = "securitypack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/ert/security - name = "emergency response team security backpack" - desc = "A spacious backpack with lots of pockets, worn by Security Officers of an Emergency Response Team." - icon_state = "ert_security" - -/obj/item/storage/backpack/ert/medical - name = "emergency response team medical backpack" - desc = "A spacious backpack with lots of pockets, worn by Medical Officers of an Emergency Response Team." - icon_state = "ert_medical" - -/obj/item/storage/backpack/ert/engineer - name = "emergency response team engineer backpack" - desc = "A spacious backpack with lots of pockets, worn by Engineers of an Emergency Response Team." - icon_state = "ert_engineering" - -/obj/item/storage/backpack/ert/janitor - name = "emergency response team janitor backpack" - desc = "A spacious backpack with lots of pockets, worn by Janitors of an Emergency Response Team." - icon_state = "ert_janitor" - -/obj/item/storage/backpack/ert/clown - name = "emergency response team clown backpack" - desc = "A spacious backpack with lots of pockets, worn by Clowns of an Emergency Response Team." - icon_state = "ert_clown" -/* - * Satchel Types - */ - -/obj/item/storage/backpack/satchel - name = "satchel" - desc = "A trendy looking satchel." - icon_state = "satchel-norm" - inhand_icon_state = "satchel-norm" - -/obj/item/storage/backpack/satchel/leather - name = "leather satchel" - desc = "It's a very fancy satchel made with fine leather." - icon_state = "satchel" - inhand_icon_state = "satchel" - -/obj/item/storage/backpack/satchel/leather/withwallet/PopulateContents() - new /obj/item/storage/wallet/random(src) - -/obj/item/storage/backpack/satchel/fireproof - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel/eng - name = "industrial satchel" - desc = "A tough satchel with extra pockets." - icon_state = "satchel-eng" - inhand_icon_state = "satchel-eng" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel/med - name = "medical satchel" - desc = "A sterile satchel used in medical departments." - icon_state = "satchel-med" - inhand_icon_state = "satchel-med" - -/obj/item/storage/backpack/satchel/vir - name = "virologist satchel" - desc = "A sterile satchel with virologist colours." - icon_state = "satchel-vir" - inhand_icon_state = "satchel-vir" - -/obj/item/storage/backpack/satchel/chem - name = "chemist satchel" - desc = "A sterile satchel with chemist colours." - icon_state = "satchel-chem" - inhand_icon_state = "satchel-chem" - -/obj/item/storage/backpack/satchel/gen - name = "geneticist satchel" - desc = "A sterile satchel with geneticist colours." - icon_state = "satchel-gen" - inhand_icon_state = "satchel-gen" - -/obj/item/storage/backpack/satchel/tox - name = "scientist satchel" - desc = "Useful for holding research materials." - icon_state = "satchel-tox" - inhand_icon_state = "satchel-tox" - -/obj/item/storage/backpack/satchel/hyd - name = "botanist satchel" - desc = "A satchel made of all natural fibers." - icon_state = "satchel-hyd" - inhand_icon_state = "satchel-hyd" - -/obj/item/storage/backpack/satchel/sec - name = "security satchel" - desc = "A robust satchel for security related needs." - icon_state = "satchel-sec" - inhand_icon_state = "satchel-sec" - -/obj/item/storage/backpack/satchel/explorer - name = "explorer satchel" - desc = "A robust satchel for stashing your loot." - icon_state = "satchel-explorer" - inhand_icon_state = "satchel-explorer" - -/obj/item/storage/backpack/satchel/cap - name = "captain's satchel" - desc = "An exclusive satchel for Nanotrasen officers." - icon_state = "satchel-cap" - inhand_icon_state = "satchel-cap" - -/obj/item/storage/backpack/satchel/flat - name = "smuggler's satchel" - desc = "A very slim satchel that can easily fit into tight spaces." - icon_state = "satchel-flat" - inhand_icon_state = "satchel-flat" - w_class = WEIGHT_CLASS_NORMAL //Can fit in backpacks itself. - -/obj/item/storage/backpack/satchel/flat/Initialize(mapload) - . = ..() - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, INVISIBILITY_OBSERVER, use_anchor = TRUE) - -/obj/item/storage/backpack/satchel/flat/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 15 - STR.set_holdable(null, list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks) - -/obj/item/storage/backpack/satchel/flat/PopulateContents() - var/datum/supply_pack/costumes_toys/randomised/contraband/C = new - for(var/i in 1 to 2) - var/ctype = pick(C.contains) - new ctype(src) - - qdel(C) - -/obj/item/storage/backpack/satchel/flat/with_tools/PopulateContents() - new /obj/item/stack/tile/plasteel(src) - new /obj/item/crowbar(src) - - ..() - -/obj/item/storage/backpack/satchel/flat/empty/PopulateContents() - return - -/obj/item/storage/backpack/duffelbag - name = "duffel bag" - desc = "A large duffel bag for holding extra things." - icon_state = "duffel" - inhand_icon_state = "duffel" - slowdown = 1 - -/obj/item/storage/backpack/duffelbag/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 30 - -/obj/item/storage/backpack/duffelbag/captain - name = "captain's duffel bag" - desc = "A large duffel bag for holding extra captainly goods." - icon_state = "duffel-captain" - inhand_icon_state = "duffel-captain" - -/obj/item/storage/backpack/duffelbag/med - name = "medical duffel bag" - desc = "A large duffel bag for holding extra medical supplies." - icon_state = "duffel-med" - inhand_icon_state = "duffel-med" - -/obj/item/storage/backpack/duffelbag/med/surgery - name = "surgical duffel bag" - desc = "A large duffel bag for holding extra medical supplies - this one seems to be designed for holding surgical tools." - -/obj/item/storage/backpack/duffelbag/med/surgery/PopulateContents() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/circular_saw(src) - new /obj/item/surgicaldrill(src) - new /obj/item/cautery(src) - new /obj/item/bonesetter(src) - new /obj/item/surgical_drapes(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/razor(src) - -/obj/item/storage/backpack/duffelbag/sec - name = "security duffel bag" - desc = "A large duffel bag for holding extra security supplies and ammunition." - icon_state = "duffel-sec" - inhand_icon_state = "duffel-sec" - -/obj/item/storage/backpack/duffelbag/sec/surgery - name = "surgical duffel bag" - desc = "A large duffel bag for holding extra supplies - this one has a material inlay with space for various sharp-looking tools." - -/obj/item/storage/backpack/duffelbag/sec/surgery/PopulateContents() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/circular_saw(src) - new /obj/item/bonesetter(src) - new /obj/item/surgicaldrill(src) - new /obj/item/cautery(src) - new /obj/item/surgical_drapes(src) - new /obj/item/clothing/mask/surgical(src) - -/obj/item/storage/backpack/duffelbag/engineering - name = "industrial duffel bag" - desc = "A large duffel bag for holding extra tools and supplies." - icon_state = "duffel-eng" - inhand_icon_state = "duffel-eng" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/drone - name = "drone duffel bag" - desc = "A large duffel bag for holding tools and hats." - icon_state = "duffel-drone" - inhand_icon_state = "duffel-drone" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/drone/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - -/obj/item/storage/backpack/duffelbag/clown - name = "clown's duffel bag" - desc = "A large duffel bag for holding lots of funny gags!" - icon_state = "duffel-clown" - inhand_icon_state = "duffel-clown" - -/obj/item/storage/backpack/duffelbag/clown/cream_pie/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/reagent_containers/food/snacks/pie/cream(src) - -/obj/item/storage/backpack/fireproof - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/syndie - name = "suspicious looking duffel bag" - desc = "A large duffel bag for holding extra tactical supplies." - icon_state = "duffel-syndie" - inhand_icon_state = "duffel-syndieammo" - slowdown = 0 - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/syndie/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.silent = TRUE - -/obj/item/storage/backpack/duffelbag/syndie/hitman - desc = "A large duffel bag for holding extra things. There is a Nanotrasen logo on the back." - icon_state = "duffel-syndieammo" - inhand_icon_state = "duffel-syndieammo" - -/obj/item/storage/backpack/duffelbag/syndie/hitman/PopulateContents() - new /obj/item/clothing/under/suit/black(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/suit/toggle/lawyer/black(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/clothing/gloves/color/black(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/head/fedora(src) - -/obj/item/storage/backpack/duffelbag/syndie/med - name = "medical duffel bag" - desc = "A large duffel bag for holding extra tactical medical supplies." - icon_state = "duffel-syndiemed" - inhand_icon_state = "duffel-syndiemed" - -/obj/item/storage/backpack/duffelbag/syndie/surgery - name = "surgery duffel bag" - desc = "A suspicious looking duffel bag for holding surgery tools." - icon_state = "duffel-syndiemed" - inhand_icon_state = "duffel-syndiemed" - -/obj/item/storage/backpack/duffelbag/syndie/surgery/PopulateContents() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/circular_saw(src) - new /obj/item/bonesetter(src) - new /obj/item/surgicaldrill(src) - new /obj/item/cautery(src) - new /obj/item/surgical_drapes(src) - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/clothing/mask/muzzle(src) - new /obj/item/mmi/syndie(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo - name = "ammunition duffel bag" - desc = "A large duffel bag for holding extra weapons ammunition and supplies." - icon_state = "duffel-syndieammo" - inhand_icon_state = "duffel-syndieammo" - -/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun - desc = "A large duffel bag, packed to the brim with Bulldog shotgun magazines." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g/slug(src) - new /obj/item/ammo_box/magazine/m12g/slug(src) - new /obj/item/ammo_box/magazine/m12g/dragon(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/smg - desc = "A large duffel bag, packed to the brim with C-20r magazines." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/smg/PopulateContents() - for(var/i in 1 to 9) - new /obj/item/ammo_box/magazine/smgm45(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mech - desc = "A large duffel bag, packed to the brim with various exosuit ammo." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mech/PopulateContents() - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/storage/belt/utility/syndicate(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler - desc = "A large duffel bag, packed to the brim with various exosuit ammo." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler/PopulateContents() - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/missiles_he(src) - new /obj/item/mecha_ammo/missiles_he(src) - new /obj/item/mecha_ammo/missiles_he(src) - -/obj/item/storage/backpack/duffelbag/syndie/c20rbundle - desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." - -/obj/item/storage/backpack/duffelbag/syndie/c20rbundle/PopulateContents() - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/gun/ballistic/automatic/c20r(src) - new /obj/item/suppressor/specialoffer(src) - -/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle - desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." - -/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle/PopulateContents() - new /obj/item/gun/ballistic/shotgun/bulldog(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/clothing/glasses/thermal/syndi(src) - -/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle - desc = "A large duffel bag containing a medical equipment, a Donksoft LMG, a big jumbo box of riot darts, and a knock-off pair of magboots." - -/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle/PopulateContents() - new /obj/item/clothing/shoes/magboots/syndie(src) - new /obj/item/storage/firstaid/tactical(src) - new /obj/item/gun/ballistic/automatic/l6_saw/toy(src) - new /obj/item/ammo_box/foambox/riot(src) - -/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle - desc = "A large duffel bag containing deadly chemicals, a handheld chem sprayer, Bioterror foam grenade, a Donksoft assault rifle, box of riot grade darts, a dart pistol, and a box of syringes." - -/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle/PopulateContents() - new /obj/item/reagent_containers/spray/chemsprayer/bioterror(src) - new /obj/item/storage/box/syndie_kit/chemical(src) - new /obj/item/gun/syringe/syndicate(src) - new /obj/item/gun/ballistic/automatic/c20r/toy(src) - new /obj/item/storage/box/syringes(src) - new /obj/item/ammo_box/foambox/riot(src) - new /obj/item/grenade/chem_grenade/bioterrorfoam(src) - if(prob(5)) - new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) - -/obj/item/storage/backpack/duffelbag/syndie/c4/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/grenade/c4(src) - -/obj/item/storage/backpack/duffelbag/syndie/x4/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/grenade/c4/x4(src) - -/obj/item/storage/backpack/duffelbag/syndie/firestarter - desc = "A large duffel bag containing a New Russian pyro backpack sprayer, Elite hardsuit, a Stechkin APS pistol, minibomb, ammo, and other equipment." - -/obj/item/storage/backpack/duffelbag/syndie/firestarter/PopulateContents() - new /obj/item/clothing/under/syndicate/soviet(src) - new /obj/item/watertank/op(src) - new /obj/item/clothing/suit/space/hardsuit/syndi/elite(src) - new /obj/item/gun/ballistic/automatic/pistol/aps(src) - new /obj/item/ammo_box/magazine/m9mm_aps/fire(src) - new /obj/item/ammo_box/magazine/m9mm_aps/fire(src) - new /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka(src) - new /obj/item/reagent_containers/hypospray/medipen/stimulants(src) - new /obj/item/grenade/syndieminibomb(src) - -// For ClownOps. -/obj/item/storage/backpack/duffelbag/clown/syndie/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - slowdown = 0 - STR.silent = TRUE - -/obj/item/storage/backpack/duffelbag/clown/syndie/PopulateContents() - new /obj/item/pda/clown(src) - new /obj/item/clothing/under/rank/civilian/clown(src) - new /obj/item/clothing/shoes/clown_shoes(src) - new /obj/item/clothing/mask/gas/clown_hat(src) - new /obj/item/bikehorn(src) - new /obj/item/implanter/sad_trombone(src) - -/obj/item/storage/backpack/henchmen - name = "wings" - desc = "Granted to the henchmen who deserve it. This probably doesn't include you." - icon_state = "henchmen" - inhand_icon_state = "henchmen" - -/obj/item/storage/backpack/duffelbag/cops - name = "police bag" - desc = "A large duffel bag for holding extra police gear." - slowdown = 0 +/* Backpacks + * Contains: + * Backpack + * Backpack Types + * Satchel Types + */ + +/* + * Backpack + */ + +/obj/item/storage/backpack + name = "backpack" + desc = "You wear this on your back and put items into it." + icon_state = "backpack" + inhand_icon_state = "backpack" + lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK //ERROOOOO + resistance_flags = NONE + max_integrity = 300 + +/obj/item/storage/backpack/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 21 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_items = 21 + +/* + * Backpack Types + */ + +/obj/item/storage/backpack/old/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 12 + +/obj/item/bag_of_holding_inert + name = "inert bag of holding" + desc = "What is currently a just an unwieldly block of metal with a slot ready to accept a bluespace anomaly core." + icon = 'icons/obj/storage.dmi' + icon_state = "brokenpack" + inhand_icon_state = "brokenpack" + lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FIRE_PROOF + item_flags = NO_MAT_REDEMPTION + +/obj/item/storage/backpack/holding + name = "bag of holding" + desc = "A backpack that opens into a localized pocket of bluespace." + icon_state = "holdingpack" + inhand_icon_state = "holdingpack" + resistance_flags = FIRE_PROOF + item_flags = NO_MAT_REDEMPTION + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50) + component_type = /datum/component/storage/concrete/bluespace/bag_of_holding + +/obj/item/storage/backpack/holding/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_big_nesting = TRUE + STR.max_w_class = WEIGHT_CLASS_GIGANTIC + STR.max_combined_w_class = 35 + +/obj/item/storage/backpack/holding/suicide_act(mob/living/user) + user.visible_message("[user] is jumping into [src]! It looks like [user.p_theyre()] trying to commit suicide.") + user.dropItemToGround(src, TRUE) + user.Stun(100, ignore_canstun = TRUE) + sleep(20) + playsound(src, "rustle", 50, TRUE, -5) + qdel(user) + +/obj/item/storage/backpack/santabag + name = "Santa's Gift Bag" + desc = "Space Santa uses this to deliver presents to all the nice children in space in Christmas! Wow, it's pretty big!" + icon_state = "giftbag0" + inhand_icon_state = "giftbag" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/backpack/santabag/Initialize() + . = ..() + regenerate_presents() + +/obj/item/storage/backpack/santabag/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 60 + +/obj/item/storage/backpack/santabag/suicide_act(mob/user) + user.visible_message("[user] places [src] over [user.p_their()] head and pulls it tight! It looks like [user.p_they()] [user.p_are()]n't in the Christmas spirit...") + return (OXYLOSS) + +/obj/item/storage/backpack/santabag/proc/regenerate_presents() + addtimer(CALLBACK(src, .proc/regenerate_presents), 30 SECONDS) + + var/mob/M = get(loc, /mob) + if(!istype(M)) + return + if(M.mind && HAS_TRAIT(M.mind, TRAIT_CANNOT_OPEN_PRESENTS)) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + var/turf/floor = get_turf(src) + var/obj/item/I = new /obj/item/a_gift/anything(floor) + if(STR.can_be_inserted(I, stop_messages=TRUE)) + STR.handle_item_insertion(I, prevent_warning=TRUE) + else + qdel(I) + + +/obj/item/storage/backpack/cultpack + name = "trophy rack" + desc = "It's useful for both carrying extra gear and proudly declaring your insanity." + icon_state = "cultpack" + inhand_icon_state = "backpack" + +/obj/item/storage/backpack/clown + name = "Giggles von Honkerton" + desc = "It's a backpack made by Honk! Co." + icon_state = "clownpack" + inhand_icon_state = "clownpack" + +/obj/item/storage/backpack/explorer + name = "explorer bag" + desc = "A robust backpack for stashing your loot." + icon_state = "explorerpack" + inhand_icon_state = "explorerpack" + +/obj/item/storage/backpack/mime + name = "Parcel Parceaux" + desc = "A silent backpack made for those silent workers. Silence Co." + icon_state = "mimepack" + inhand_icon_state = "mimepack" + +/obj/item/storage/backpack/medic + name = "medical backpack" + desc = "It's a backpack especially designed for use in a sterile environment." + icon_state = "medicalpack" + inhand_icon_state = "medicalpack" + +/obj/item/storage/backpack/security + name = "security backpack" + desc = "It's a very robust backpack." + icon_state = "securitypack" + inhand_icon_state = "securitypack" + +/obj/item/storage/backpack/captain + name = "captain's backpack" + desc = "It's a special backpack made exclusively for Nanotrasen officers." + icon_state = "captainpack" + inhand_icon_state = "captainpack" + +/obj/item/storage/backpack/industrial + name = "industrial backpack" + desc = "It's a tough backpack for the daily grind of station life." + icon_state = "engiepack" + inhand_icon_state = "engiepack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/botany + name = "botany backpack" + desc = "It's a backpack made of all-natural fibers." + icon_state = "botpack" + inhand_icon_state = "botpack" + +/obj/item/storage/backpack/chemistry + name = "chemistry backpack" + desc = "A backpack specially designed to repel stains and hazardous liquids." + icon_state = "chempack" + inhand_icon_state = "chempack" + +/obj/item/storage/backpack/genetics + name = "genetics backpack" + desc = "A bag designed to be super tough, just in case someone hulks out on you." + icon_state = "genepack" + inhand_icon_state = "genepack" + +/obj/item/storage/backpack/science + name = "science backpack" + desc = "A specially designed backpack. It's fire resistant and smells vaguely of plasma." + icon_state = "toxpack" + inhand_icon_state = "toxpack" + +/obj/item/storage/backpack/virology + name = "virology backpack" + desc = "A backpack made of hypo-allergenic fibers. It's designed to help prevent the spread of disease. Smells like monkey." + icon_state = "viropack" + inhand_icon_state = "viropack" + +/obj/item/storage/backpack/ert + name = "emergency response team commander backpack" + desc = "A spacious backpack with lots of pockets, worn by the Commander of an Emergency Response Team." + icon_state = "ert_commander" + inhand_icon_state = "securitypack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/ert/security + name = "emergency response team security backpack" + desc = "A spacious backpack with lots of pockets, worn by Security Officers of an Emergency Response Team." + icon_state = "ert_security" + +/obj/item/storage/backpack/ert/medical + name = "emergency response team medical backpack" + desc = "A spacious backpack with lots of pockets, worn by Medical Officers of an Emergency Response Team." + icon_state = "ert_medical" + +/obj/item/storage/backpack/ert/engineer + name = "emergency response team engineer backpack" + desc = "A spacious backpack with lots of pockets, worn by Engineers of an Emergency Response Team." + icon_state = "ert_engineering" + +/obj/item/storage/backpack/ert/janitor + name = "emergency response team janitor backpack" + desc = "A spacious backpack with lots of pockets, worn by Janitors of an Emergency Response Team." + icon_state = "ert_janitor" + +/obj/item/storage/backpack/ert/clown + name = "emergency response team clown backpack" + desc = "A spacious backpack with lots of pockets, worn by Clowns of an Emergency Response Team." + icon_state = "ert_clown" +/* + * Satchel Types + */ + +/obj/item/storage/backpack/satchel + name = "satchel" + desc = "A trendy looking satchel." + icon_state = "satchel-norm" + inhand_icon_state = "satchel-norm" + +/obj/item/storage/backpack/satchel/leather + name = "leather satchel" + desc = "It's a very fancy satchel made with fine leather." + icon_state = "satchel" + inhand_icon_state = "satchel" + +/obj/item/storage/backpack/satchel/leather/withwallet/PopulateContents() + new /obj/item/storage/wallet/random(src) + +/obj/item/storage/backpack/satchel/fireproof + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel/eng + name = "industrial satchel" + desc = "A tough satchel with extra pockets." + icon_state = "satchel-eng" + inhand_icon_state = "satchel-eng" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel/med + name = "medical satchel" + desc = "A sterile satchel used in medical departments." + icon_state = "satchel-med" + inhand_icon_state = "satchel-med" + +/obj/item/storage/backpack/satchel/vir + name = "virologist satchel" + desc = "A sterile satchel with virologist colours." + icon_state = "satchel-vir" + inhand_icon_state = "satchel-vir" + +/obj/item/storage/backpack/satchel/chem + name = "chemist satchel" + desc = "A sterile satchel with chemist colours." + icon_state = "satchel-chem" + inhand_icon_state = "satchel-chem" + +/obj/item/storage/backpack/satchel/gen + name = "geneticist satchel" + desc = "A sterile satchel with geneticist colours." + icon_state = "satchel-gen" + inhand_icon_state = "satchel-gen" + +/obj/item/storage/backpack/satchel/tox + name = "scientist satchel" + desc = "Useful for holding research materials." + icon_state = "satchel-tox" + inhand_icon_state = "satchel-tox" + +/obj/item/storage/backpack/satchel/hyd + name = "botanist satchel" + desc = "A satchel made of all natural fibers." + icon_state = "satchel-hyd" + inhand_icon_state = "satchel-hyd" + +/obj/item/storage/backpack/satchel/sec + name = "security satchel" + desc = "A robust satchel for security related needs." + icon_state = "satchel-sec" + inhand_icon_state = "satchel-sec" + +/obj/item/storage/backpack/satchel/explorer + name = "explorer satchel" + desc = "A robust satchel for stashing your loot." + icon_state = "satchel-explorer" + inhand_icon_state = "satchel-explorer" + +/obj/item/storage/backpack/satchel/cap + name = "captain's satchel" + desc = "An exclusive satchel for Nanotrasen officers." + icon_state = "satchel-cap" + inhand_icon_state = "satchel-cap" + +/obj/item/storage/backpack/satchel/flat + name = "smuggler's satchel" + desc = "A very slim satchel that can easily fit into tight spaces." + icon_state = "satchel-flat" + inhand_icon_state = "satchel-flat" + w_class = WEIGHT_CLASS_NORMAL //Can fit in backpacks itself. + +/obj/item/storage/backpack/satchel/flat/Initialize(mapload) + . = ..() + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, INVISIBILITY_OBSERVER, use_anchor = TRUE) + +/obj/item/storage/backpack/satchel/flat/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 15 + STR.set_holdable(null, list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks) + +/obj/item/storage/backpack/satchel/flat/PopulateContents() + var/datum/supply_pack/costumes_toys/randomised/contraband/C = new + for(var/i in 1 to 2) + var/ctype = pick(C.contains) + new ctype(src) + + qdel(C) + +/obj/item/storage/backpack/satchel/flat/with_tools/PopulateContents() + new /obj/item/stack/tile/plasteel(src) + new /obj/item/crowbar(src) + + ..() + +/obj/item/storage/backpack/satchel/flat/empty/PopulateContents() + return + +/obj/item/storage/backpack/duffelbag + name = "duffel bag" + desc = "A large duffel bag for holding extra things." + icon_state = "duffel" + inhand_icon_state = "duffel" + slowdown = 1 + +/obj/item/storage/backpack/duffelbag/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 30 + +/obj/item/storage/backpack/duffelbag/captain + name = "captain's duffel bag" + desc = "A large duffel bag for holding extra captainly goods." + icon_state = "duffel-captain" + inhand_icon_state = "duffel-captain" + +/obj/item/storage/backpack/duffelbag/med + name = "medical duffel bag" + desc = "A large duffel bag for holding extra medical supplies." + icon_state = "duffel-med" + inhand_icon_state = "duffel-med" + +/obj/item/storage/backpack/duffelbag/med/surgery + name = "surgical duffel bag" + desc = "A large duffel bag for holding extra medical supplies - this one seems to be designed for holding surgical tools." + +/obj/item/storage/backpack/duffelbag/med/surgery/PopulateContents() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/circular_saw(src) + new /obj/item/surgicaldrill(src) + new /obj/item/cautery(src) + new /obj/item/bonesetter(src) + new /obj/item/surgical_drapes(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/razor(src) + +/obj/item/storage/backpack/duffelbag/sec + name = "security duffel bag" + desc = "A large duffel bag for holding extra security supplies and ammunition." + icon_state = "duffel-sec" + inhand_icon_state = "duffel-sec" + +/obj/item/storage/backpack/duffelbag/sec/surgery + name = "surgical duffel bag" + desc = "A large duffel bag for holding extra supplies - this one has a material inlay with space for various sharp-looking tools." + +/obj/item/storage/backpack/duffelbag/sec/surgery/PopulateContents() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/circular_saw(src) + new /obj/item/bonesetter(src) + new /obj/item/surgicaldrill(src) + new /obj/item/cautery(src) + new /obj/item/surgical_drapes(src) + new /obj/item/clothing/mask/surgical(src) + +/obj/item/storage/backpack/duffelbag/engineering + name = "industrial duffel bag" + desc = "A large duffel bag for holding extra tools and supplies." + icon_state = "duffel-eng" + inhand_icon_state = "duffel-eng" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/drone + name = "drone duffel bag" + desc = "A large duffel bag for holding tools and hats." + icon_state = "duffel-drone" + inhand_icon_state = "duffel-drone" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/drone/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + +/obj/item/storage/backpack/duffelbag/clown + name = "clown's duffel bag" + desc = "A large duffel bag for holding lots of funny gags!" + icon_state = "duffel-clown" + inhand_icon_state = "duffel-clown" + +/obj/item/storage/backpack/duffelbag/clown/cream_pie/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/reagent_containers/food/snacks/pie/cream(src) + +/obj/item/storage/backpack/fireproof + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/syndie + name = "suspicious looking duffel bag" + desc = "A large duffel bag for holding extra tactical supplies." + icon_state = "duffel-syndie" + inhand_icon_state = "duffel-syndieammo" + slowdown = 0 + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/syndie/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.silent = TRUE + +/obj/item/storage/backpack/duffelbag/syndie/hitman + desc = "A large duffel bag for holding extra things. There is a Nanotrasen logo on the back." + icon_state = "duffel-syndieammo" + inhand_icon_state = "duffel-syndieammo" + +/obj/item/storage/backpack/duffelbag/syndie/hitman/PopulateContents() + new /obj/item/clothing/under/suit/black(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/suit/toggle/lawyer/black(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/clothing/gloves/color/black(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/head/fedora(src) + +/obj/item/storage/backpack/duffelbag/syndie/med + name = "medical duffel bag" + desc = "A large duffel bag for holding extra tactical medical supplies." + icon_state = "duffel-syndiemed" + inhand_icon_state = "duffel-syndiemed" + +/obj/item/storage/backpack/duffelbag/syndie/surgery + name = "surgery duffel bag" + desc = "A suspicious looking duffel bag for holding surgery tools." + icon_state = "duffel-syndiemed" + inhand_icon_state = "duffel-syndiemed" + +/obj/item/storage/backpack/duffelbag/syndie/surgery/PopulateContents() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/circular_saw(src) + new /obj/item/bonesetter(src) + new /obj/item/surgicaldrill(src) + new /obj/item/cautery(src) + new /obj/item/surgical_drapes(src) + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/clothing/mask/muzzle(src) + new /obj/item/mmi/syndie(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo + name = "ammunition duffel bag" + desc = "A large duffel bag for holding extra weapons ammunition and supplies." + icon_state = "duffel-syndieammo" + inhand_icon_state = "duffel-syndieammo" + +/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun + desc = "A large duffel bag, packed to the brim with Bulldog shotgun magazines." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g/slug(src) + new /obj/item/ammo_box/magazine/m12g/slug(src) + new /obj/item/ammo_box/magazine/m12g/dragon(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/smg + desc = "A large duffel bag, packed to the brim with C-20r magazines." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/smg/PopulateContents() + for(var/i in 1 to 9) + new /obj/item/ammo_box/magazine/smgm45(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mech + desc = "A large duffel bag, packed to the brim with various exosuit ammo." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mech/PopulateContents() + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/storage/belt/utility/syndicate(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler + desc = "A large duffel bag, packed to the brim with various exosuit ammo." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler/PopulateContents() + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/missiles_he(src) + new /obj/item/mecha_ammo/missiles_he(src) + new /obj/item/mecha_ammo/missiles_he(src) + +/obj/item/storage/backpack/duffelbag/syndie/c20rbundle + desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." + +/obj/item/storage/backpack/duffelbag/syndie/c20rbundle/PopulateContents() + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/gun/ballistic/automatic/c20r(src) + new /obj/item/suppressor/specialoffer(src) + +/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle + desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." + +/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle/PopulateContents() + new /obj/item/gun/ballistic/shotgun/bulldog(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/clothing/glasses/thermal/syndi(src) + +/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle + desc = "A large duffel bag containing a medical equipment, a Donksoft LMG, a big jumbo box of riot darts, and a knock-off pair of magboots." + +/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle/PopulateContents() + new /obj/item/clothing/shoes/magboots/syndie(src) + new /obj/item/storage/firstaid/tactical(src) + new /obj/item/gun/ballistic/automatic/l6_saw/toy(src) + new /obj/item/ammo_box/foambox/riot(src) + +/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle + desc = "A large duffel bag containing deadly chemicals, a handheld chem sprayer, Bioterror foam grenade, a Donksoft assault rifle, box of riot grade darts, a dart pistol, and a box of syringes." + +/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle/PopulateContents() + new /obj/item/reagent_containers/spray/chemsprayer/bioterror(src) + new /obj/item/storage/box/syndie_kit/chemical(src) + new /obj/item/gun/syringe/syndicate(src) + new /obj/item/gun/ballistic/automatic/c20r/toy(src) + new /obj/item/storage/box/syringes(src) + new /obj/item/ammo_box/foambox/riot(src) + new /obj/item/grenade/chem_grenade/bioterrorfoam(src) + if(prob(5)) + new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) + +/obj/item/storage/backpack/duffelbag/syndie/c4/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/grenade/c4(src) + +/obj/item/storage/backpack/duffelbag/syndie/x4/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/grenade/c4/x4(src) + +/obj/item/storage/backpack/duffelbag/syndie/firestarter + desc = "A large duffel bag containing a New Russian pyro backpack sprayer, Elite hardsuit, a Stechkin APS pistol, minibomb, ammo, and other equipment." + +/obj/item/storage/backpack/duffelbag/syndie/firestarter/PopulateContents() + new /obj/item/clothing/under/syndicate/soviet(src) + new /obj/item/watertank/op(src) + new /obj/item/clothing/suit/space/hardsuit/syndi/elite(src) + new /obj/item/gun/ballistic/automatic/pistol/aps(src) + new /obj/item/ammo_box/magazine/m9mm_aps/fire(src) + new /obj/item/ammo_box/magazine/m9mm_aps/fire(src) + new /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka(src) + new /obj/item/reagent_containers/hypospray/medipen/stimulants(src) + new /obj/item/grenade/syndieminibomb(src) + +// For ClownOps. +/obj/item/storage/backpack/duffelbag/clown/syndie/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + slowdown = 0 + STR.silent = TRUE + +/obj/item/storage/backpack/duffelbag/clown/syndie/PopulateContents() + new /obj/item/pda/clown(src) + new /obj/item/clothing/under/rank/civilian/clown(src) + new /obj/item/clothing/shoes/clown_shoes(src) + new /obj/item/clothing/mask/gas/clown_hat(src) + new /obj/item/bikehorn(src) + new /obj/item/implanter/sad_trombone(src) + +/obj/item/storage/backpack/henchmen + name = "wings" + desc = "Granted to the henchmen who deserve it. This probably doesn't include you." + icon_state = "henchmen" + inhand_icon_state = "henchmen" + +/obj/item/storage/backpack/duffelbag/cops + name = "police bag" + desc = "A large duffel bag for holding extra police gear." + slowdown = 0 diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index 76bf10f1c0a..a54d7870614 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -1,449 +1,449 @@ -/* - * These absorb the functionality of the plant bag, ore satchel, etc. - * They use the use_to_pickup, quick_gather, and quick_empty functions - * that were already defined in weapon/storage, but which had been - * re-implemented in other classes. - * - * Contains: - * Trash Bag - * Mining Satchel - * Plant Bag - * Sheet Snatcher - * Book Bag - * Biowaste Bag - * - * -Sayu - */ - -// Generic non-item -/obj/item/storage/bag - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_quick_gather = TRUE - STR.allow_quick_empty = TRUE - STR.display_numerical_stacking = TRUE - STR.click_gather = TRUE - -// ----------------------------- -// Trash bag -// ----------------------------- -/obj/item/storage/bag/trash - name = "trash bag" - desc = "It's the heavy-duty black polymer kind. Time to take out the trash!" - icon = 'icons/obj/janitor.dmi' - icon_state = "trashbag" - inhand_icon_state = "trashbag" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - slot_flags = null - var/insertable = TRUE - -/obj/item/storage/bag/trash/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_combined_w_class = 30 - STR.max_items = 30 - STR.set_holdable(null, list(/obj/item/disk/nuclear)) - -/obj/item/storage/bag/trash/suicide_act(mob/user) - user.visible_message("[user] puts [src] over [user.p_their()] head and starts chomping at the insides! Disgusting!") - playsound(loc, 'sound/items/eatfood.ogg', 50, TRUE, -1) - return (TOXLOSS) - -/obj/item/storage/bag/trash/update_icon_state() - switch(contents.len) - if(20 to INFINITY) - icon_state = "[initial(icon_state)]3" - if(11 to 20) - icon_state = "[initial(icon_state)]2" - if(1 to 11) - icon_state = "[initial(icon_state)]1" - else - icon_state = "[initial(icon_state)]" - -/obj/item/storage/bag/trash/cyborg - insertable = FALSE - -/obj/item/storage/bag/trash/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - if(insertable) - J.put_in_cart(src, user) - J.mybag=src - J.update_icon() - else - to_chat(user, "You are unable to fit your [name] into the [J.name].") - return - -/obj/item/storage/bag/trash/bluespace - name = "trash bag of holding" - desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage." - icon_state = "bluetrashbag" - item_flags = NO_MAT_REDEMPTION - -/obj/item/storage/bag/trash/bluespace/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 60 - STR.max_items = 60 - -/obj/item/storage/bag/trash/bluespace/cyborg - insertable = FALSE - -// ----------------------------- -// Mining Satchel -// ----------------------------- - -/obj/item/storage/bag/ore - name = "mining satchel" - desc = "This little bugger can be used to store and transport ores." - icon = 'icons/obj/mining.dmi' - icon_state = "satchel" - worn_icon_state = "satchel" - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKETS - w_class = WEIGHT_CLASS_NORMAL - component_type = /datum/component/storage/concrete/stack - var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it - var/mob/listeningTo - -/obj/item/storage/bag/ore/ComponentInitialize() - . = ..() - AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.allow_quick_empty = TRUE - STR.set_holdable(list(/obj/item/stack/ore)) - STR.max_w_class = WEIGHT_CLASS_HUGE - STR.max_combined_stack_amount = 50 - -/obj/item/storage/bag/ore/equipped(mob/user) - . = ..() - if(listeningTo == user) - return - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) - listeningTo = user - -/obj/item/storage/bag/ore/dropped() - . = ..() - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) - listeningTo = null - -/obj/item/storage/bag/ore/proc/Pickup_ores(mob/living/user) - var/show_message = FALSE - var/obj/structure/ore_box/box - var/turf/tile = user.loc - if (!isturf(tile)) - return - if (istype(user.pulling, /obj/structure/ore_box)) - box = user.pulling - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - if(STR) - for(var/A in tile) - if (!is_type_in_typecache(A, STR.can_hold)) - continue - if (box) - user.transferItemToLoc(A, box) - show_message = TRUE - else if(SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, A, user, TRUE)) - show_message = TRUE - else - if(!spam_protection) - to_chat(user, "Your [name] is full and can't hold any more!") - spam_protection = TRUE - continue - if(show_message) - playsound(user, "rustle", 50, TRUE) - if (box) - user.visible_message("[user] offloads the ores beneath [user.p_them()] into [box].", \ - "You offload the ores beneath you into your [box].") - else - user.visible_message("[user] scoops up the ores beneath [user.p_them()].", \ - "You scoop up the ores beneath you with your [name].") - spam_protection = FALSE - -/obj/item/storage/bag/ore/cyborg - name = "cyborg mining satchel" - -/obj/item/storage/bag/ore/holding //miners, your messiah has arrived - name = "mining satchel of holding" - desc = "A revolution in convenience, this satchel allows for huge amounts of ore storage. It's been outfitted with anti-malfunction safety measures." - icon_state = "satchel_bspace" - -/obj/item/storage/bag/ore/holding/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.max_items = INFINITY - STR.max_combined_w_class = INFINITY - STR.max_combined_stack_amount = INFINITY - -// ----------------------------- -// Plant bag -// ----------------------------- - -/obj/item/storage/bag/plants - name = "plant bag" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "plantbag" - worn_icon_state = "plantbag" - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/plants/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 100 - STR.max_items = 100 - STR.set_holdable(list( - /obj/item/reagent_containers/food/snacks/grown, - /obj/item/seeds, - /obj/item/grown, - /obj/item/reagent_containers/honeycomb, - /obj/item/graft, - )) -//////// - -/obj/item/storage/bag/plants/portaseeder - name = "portable seed extractor" - desc = "For the enterprising botanist on the go. Less efficient than the stationary model, it creates one seed per plant." - icon_state = "portaseeder" - -/obj/item/storage/bag/plants/portaseeder/verb/dissolve_contents() - set name = "Activate Seed Extraction" - set category = "Object" - set desc = "Activate to convert your plants into plantable seeds." - if(usr.incapacitated()) - return - for(var/obj/item/O in contents) - seedify(O, 1) - -// ----------------------------- -// Sheet Snatcher -// ----------------------------- -// Because it stacks stacks, this doesn't operate normally. -// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu - -/obj/item/storage/bag/sheetsnatcher - name = "sheet snatcher" - desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." - icon = 'icons/obj/mining.dmi' - icon_state = "sheetsnatcher" - worn_icon_state = "satchel" - - var/capacity = 300; //the number of sheets it can carry. - component_type = /datum/component/storage/concrete/stack - -/obj/item/storage/bag/sheetsnatcher/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.allow_quick_empty = TRUE - STR.set_holdable(list( - /obj/item/stack/sheet, - /obj/item/stack/tile/bronze - ), - list( - /obj/item/stack/sheet/mineral/sandstone, - /obj/item/stack/sheet/mineral/wood - )) - STR.max_combined_stack_amount = 300 - -// ----------------------------- -// Sheet Snatcher (Cyborg) -// ----------------------------- - -/obj/item/storage/bag/sheetsnatcher/borg - name = "sheet snatcher 9000" - desc = "" - capacity = 500//Borgs get more because >specialization - -/obj/item/storage/bag/sheetsnatcher/borg/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.max_combined_stack_amount = 500 - -// ----------------------------- -// Book bag -// ----------------------------- - -/obj/item/storage/bag/books - name = "book bag" - desc = "A bag for books." - icon = 'icons/obj/library.dmi' - icon_state = "bookbag" - worn_icon_state = "bookbag" - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/books/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - STR.max_items = 7 - STR.display_numerical_stacking = FALSE - STR.set_holdable(list( - /obj/item/book, - /obj/item/storage/book, - /obj/item/spellbook - )) - -/* - * Trays - Agouri - */ -/obj/item/storage/bag/tray - name = "serving tray" - icon = 'icons/obj/food/containers.dmi' - icon_state = "tray" - desc = "A metal tray to lay food on." - force = 5 - throwforce = 10 - throw_speed = 3 - throw_range = 5 - flags_1 = CONDUCT_1 - custom_materials = list(/datum/material/iron=3000) - -/obj/item/storage/bag/tray/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.insert_preposition = "on" - -/obj/item/storage/bag/tray/attack(mob/living/M, mob/living/user) - . = ..() - // Drop all the things. All of them. - var/list/obj/item/oldContents = contents.Copy() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_QUICK_EMPTY) - // Make each item scatter a bit - for(var/obj/item/I in oldContents) - INVOKE_ASYNC(src, .proc/do_scatter, I) - - if(prob(50)) - playsound(M, 'sound/items/trayhit1.ogg', 50, TRUE) - else - playsound(M, 'sound/items/trayhit2.ogg', 50, TRUE) - - if(ishuman(M) || ismonkey(M)) - if(prob(10)) - M.Paralyze(40) - update_icon() - -/obj/item/storage/bag/tray/proc/do_scatter(obj/item/I) - for(var/i in 1 to rand(1,2)) - if(I) - step(I, pick(NORTH,SOUTH,EAST,WEST)) - sleep(rand(2,4)) - -/obj/item/storage/bag/tray/update_overlays() - . = ..() - for(var/obj/item/I in contents) - var/mutable_appearance/I_copy = new(I) - I_copy.plane = FLOAT_PLANE - I_copy.layer = FLOAT_LAYER - . += I_copy - -/obj/item/storage/bag/tray/Entered() - . = ..() - update_icon() - -/obj/item/storage/bag/tray/Exited() - . = ..() - update_icon() - -/obj/item/storage/bag/tray/cafeteria - name = "cafeteria tray" - icon = 'icons/obj/food/containers.dmi' - icon_state = "foodtray" - desc = "A cheap metal tray to pile today's meal onto." - -/* - * Chemistry bag - */ - -/obj/item/storage/bag/chemistry - name = "chemistry bag" - icon = 'icons/obj/chemical.dmi' - icon_state = "bag" - worn_icon_state = "chembag" - desc = "A bag for storing pills, patches, and bottles." - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/chemistry/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 200 - STR.max_items = 50 - STR.insert_preposition = "in" - STR.set_holdable(list( - /obj/item/reagent_containers/pill, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/food/drinks/waterbottle, - /obj/item/reagent_containers/medigel, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/chem_pack - )) - -/* - * Biowaste bag (mostly for xenobiologists) - */ - -/obj/item/storage/bag/bio - name = "bio bag" - icon = 'icons/obj/chemical.dmi' - icon_state = "biobag" - worn_icon_state = "biobag" - desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/bio/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 200 - STR.max_items = 25 - STR.insert_preposition = "in" - STR.set_holdable(list( - /obj/item/slime_extract, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/blood, - /obj/item/reagent_containers/hypospray/medipen, - /obj/item/reagent_containers/food/snacks/deadmouse, - /obj/item/reagent_containers/food/snacks/monkeycube, - /obj/item/organ, - /obj/item/bodypart - )) - -/* - * Construction bag (for engineering, holds stock parts and electronics) - */ - -/obj/item/storage/bag/construction - name = "construction bag" - icon = 'icons/obj/tools.dmi' - icon_state = "construction_bag" - worn_icon_state = "construction_bag" - desc = "A bag for storing small construction components." - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/construction/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 100 - STR.max_items = 50 - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.insert_preposition = "in" - STR.set_holdable(list( - /obj/item/stack/ore/bluespace_crystal, - /obj/item/assembly, - /obj/item/stock_parts, - /obj/item/reagent_containers/glass/beaker, - /obj/item/stack/cable_coil, - /obj/item/circuitboard, - /obj/item/electronics, - /obj/item/wallframe/camera - )) +/* + * These absorb the functionality of the plant bag, ore satchel, etc. + * They use the use_to_pickup, quick_gather, and quick_empty functions + * that were already defined in weapon/storage, but which had been + * re-implemented in other classes. + * + * Contains: + * Trash Bag + * Mining Satchel + * Plant Bag + * Sheet Snatcher + * Book Bag + * Biowaste Bag + * + * -Sayu + */ + +// Generic non-item +/obj/item/storage/bag + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_quick_gather = TRUE + STR.allow_quick_empty = TRUE + STR.display_numerical_stacking = TRUE + STR.click_gather = TRUE + +// ----------------------------- +// Trash bag +// ----------------------------- +/obj/item/storage/bag/trash + name = "trash bag" + desc = "It's the heavy-duty black polymer kind. Time to take out the trash!" + icon = 'icons/obj/janitor.dmi' + icon_state = "trashbag" + inhand_icon_state = "trashbag" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + slot_flags = null + var/insertable = TRUE + +/obj/item/storage/bag/trash/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_combined_w_class = 30 + STR.max_items = 30 + STR.set_holdable(null, list(/obj/item/disk/nuclear)) + +/obj/item/storage/bag/trash/suicide_act(mob/user) + user.visible_message("[user] puts [src] over [user.p_their()] head and starts chomping at the insides! Disgusting!") + playsound(loc, 'sound/items/eatfood.ogg', 50, TRUE, -1) + return (TOXLOSS) + +/obj/item/storage/bag/trash/update_icon_state() + switch(contents.len) + if(20 to INFINITY) + icon_state = "[initial(icon_state)]3" + if(11 to 20) + icon_state = "[initial(icon_state)]2" + if(1 to 11) + icon_state = "[initial(icon_state)]1" + else + icon_state = "[initial(icon_state)]" + +/obj/item/storage/bag/trash/cyborg + insertable = FALSE + +/obj/item/storage/bag/trash/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + if(insertable) + J.put_in_cart(src, user) + J.mybag=src + J.update_icon() + else + to_chat(user, "You are unable to fit your [name] into the [J.name].") + return + +/obj/item/storage/bag/trash/bluespace + name = "trash bag of holding" + desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage." + icon_state = "bluetrashbag" + item_flags = NO_MAT_REDEMPTION + +/obj/item/storage/bag/trash/bluespace/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 60 + STR.max_items = 60 + +/obj/item/storage/bag/trash/bluespace/cyborg + insertable = FALSE + +// ----------------------------- +// Mining Satchel +// ----------------------------- + +/obj/item/storage/bag/ore + name = "mining satchel" + desc = "This little bugger can be used to store and transport ores." + icon = 'icons/obj/mining.dmi' + icon_state = "satchel" + worn_icon_state = "satchel" + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKETS + w_class = WEIGHT_CLASS_NORMAL + component_type = /datum/component/storage/concrete/stack + var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it + var/mob/listeningTo + +/obj/item/storage/bag/ore/ComponentInitialize() + . = ..() + AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.allow_quick_empty = TRUE + STR.set_holdable(list(/obj/item/stack/ore)) + STR.max_w_class = WEIGHT_CLASS_HUGE + STR.max_combined_stack_amount = 50 + +/obj/item/storage/bag/ore/equipped(mob/user) + . = ..() + if(listeningTo == user) + return + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) + listeningTo = user + +/obj/item/storage/bag/ore/dropped() + . = ..() + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + listeningTo = null + +/obj/item/storage/bag/ore/proc/Pickup_ores(mob/living/user) + var/show_message = FALSE + var/obj/structure/ore_box/box + var/turf/tile = user.loc + if (!isturf(tile)) + return + if (istype(user.pulling, /obj/structure/ore_box)) + box = user.pulling + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + if(STR) + for(var/A in tile) + if (!is_type_in_typecache(A, STR.can_hold)) + continue + if (box) + user.transferItemToLoc(A, box) + show_message = TRUE + else if(SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, A, user, TRUE)) + show_message = TRUE + else + if(!spam_protection) + to_chat(user, "Your [name] is full and can't hold any more!") + spam_protection = TRUE + continue + if(show_message) + playsound(user, "rustle", 50, TRUE) + if (box) + user.visible_message("[user] offloads the ores beneath [user.p_them()] into [box].", \ + "You offload the ores beneath you into your [box].") + else + user.visible_message("[user] scoops up the ores beneath [user.p_them()].", \ + "You scoop up the ores beneath you with your [name].") + spam_protection = FALSE + +/obj/item/storage/bag/ore/cyborg + name = "cyborg mining satchel" + +/obj/item/storage/bag/ore/holding //miners, your messiah has arrived + name = "mining satchel of holding" + desc = "A revolution in convenience, this satchel allows for huge amounts of ore storage. It's been outfitted with anti-malfunction safety measures." + icon_state = "satchel_bspace" + +/obj/item/storage/bag/ore/holding/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.max_items = INFINITY + STR.max_combined_w_class = INFINITY + STR.max_combined_stack_amount = INFINITY + +// ----------------------------- +// Plant bag +// ----------------------------- + +/obj/item/storage/bag/plants + name = "plant bag" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "plantbag" + worn_icon_state = "plantbag" + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/plants/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 100 + STR.max_items = 100 + STR.set_holdable(list( + /obj/item/reagent_containers/food/snacks/grown, + /obj/item/seeds, + /obj/item/grown, + /obj/item/reagent_containers/honeycomb, + /obj/item/graft, + )) +//////// + +/obj/item/storage/bag/plants/portaseeder + name = "portable seed extractor" + desc = "For the enterprising botanist on the go. Less efficient than the stationary model, it creates one seed per plant." + icon_state = "portaseeder" + +/obj/item/storage/bag/plants/portaseeder/verb/dissolve_contents() + set name = "Activate Seed Extraction" + set category = "Object" + set desc = "Activate to convert your plants into plantable seeds." + if(usr.incapacitated()) + return + for(var/obj/item/O in contents) + seedify(O, 1) + +// ----------------------------- +// Sheet Snatcher +// ----------------------------- +// Because it stacks stacks, this doesn't operate normally. +// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu + +/obj/item/storage/bag/sheetsnatcher + name = "sheet snatcher" + desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." + icon = 'icons/obj/mining.dmi' + icon_state = "sheetsnatcher" + worn_icon_state = "satchel" + + var/capacity = 300; //the number of sheets it can carry. + component_type = /datum/component/storage/concrete/stack + +/obj/item/storage/bag/sheetsnatcher/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.allow_quick_empty = TRUE + STR.set_holdable(list( + /obj/item/stack/sheet, + /obj/item/stack/tile/bronze + ), + list( + /obj/item/stack/sheet/mineral/sandstone, + /obj/item/stack/sheet/mineral/wood + )) + STR.max_combined_stack_amount = 300 + +// ----------------------------- +// Sheet Snatcher (Cyborg) +// ----------------------------- + +/obj/item/storage/bag/sheetsnatcher/borg + name = "sheet snatcher 9000" + desc = "" + capacity = 500//Borgs get more because >specialization + +/obj/item/storage/bag/sheetsnatcher/borg/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.max_combined_stack_amount = 500 + +// ----------------------------- +// Book bag +// ----------------------------- + +/obj/item/storage/bag/books + name = "book bag" + desc = "A bag for books." + icon = 'icons/obj/library.dmi' + icon_state = "bookbag" + worn_icon_state = "bookbag" + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/books/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + STR.max_items = 7 + STR.display_numerical_stacking = FALSE + STR.set_holdable(list( + /obj/item/book, + /obj/item/storage/book, + /obj/item/spellbook + )) + +/* + * Trays - Agouri + */ +/obj/item/storage/bag/tray + name = "serving tray" + icon = 'icons/obj/food/containers.dmi' + icon_state = "tray" + desc = "A metal tray to lay food on." + force = 5 + throwforce = 10 + throw_speed = 3 + throw_range = 5 + flags_1 = CONDUCT_1 + custom_materials = list(/datum/material/iron=3000) + +/obj/item/storage/bag/tray/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.insert_preposition = "on" + +/obj/item/storage/bag/tray/attack(mob/living/M, mob/living/user) + . = ..() + // Drop all the things. All of them. + var/list/obj/item/oldContents = contents.Copy() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_QUICK_EMPTY) + // Make each item scatter a bit + for(var/obj/item/I in oldContents) + INVOKE_ASYNC(src, .proc/do_scatter, I) + + if(prob(50)) + playsound(M, 'sound/items/trayhit1.ogg', 50, TRUE) + else + playsound(M, 'sound/items/trayhit2.ogg', 50, TRUE) + + if(ishuman(M) || ismonkey(M)) + if(prob(10)) + M.Paralyze(40) + update_icon() + +/obj/item/storage/bag/tray/proc/do_scatter(obj/item/I) + for(var/i in 1 to rand(1,2)) + if(I) + step(I, pick(NORTH,SOUTH,EAST,WEST)) + sleep(rand(2,4)) + +/obj/item/storage/bag/tray/update_overlays() + . = ..() + for(var/obj/item/I in contents) + var/mutable_appearance/I_copy = new(I) + I_copy.plane = FLOAT_PLANE + I_copy.layer = FLOAT_LAYER + . += I_copy + +/obj/item/storage/bag/tray/Entered() + . = ..() + update_icon() + +/obj/item/storage/bag/tray/Exited() + . = ..() + update_icon() + +/obj/item/storage/bag/tray/cafeteria + name = "cafeteria tray" + icon = 'icons/obj/food/containers.dmi' + icon_state = "foodtray" + desc = "A cheap metal tray to pile today's meal onto." + +/* + * Chemistry bag + */ + +/obj/item/storage/bag/chemistry + name = "chemistry bag" + icon = 'icons/obj/chemical.dmi' + icon_state = "bag" + worn_icon_state = "chembag" + desc = "A bag for storing pills, patches, and bottles." + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/chemistry/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 200 + STR.max_items = 50 + STR.insert_preposition = "in" + STR.set_holdable(list( + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/food/drinks/waterbottle, + /obj/item/reagent_containers/medigel, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/chem_pack + )) + +/* + * Biowaste bag (mostly for xenobiologists) + */ + +/obj/item/storage/bag/bio + name = "bio bag" + icon = 'icons/obj/chemical.dmi' + icon_state = "biobag" + worn_icon_state = "biobag" + desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/bio/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 200 + STR.max_items = 25 + STR.insert_preposition = "in" + STR.set_holdable(list( + /obj/item/slime_extract, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/blood, + /obj/item/reagent_containers/hypospray/medipen, + /obj/item/reagent_containers/food/snacks/deadmouse, + /obj/item/reagent_containers/food/snacks/monkeycube, + /obj/item/organ, + /obj/item/bodypart + )) + +/* + * Construction bag (for engineering, holds stock parts and electronics) + */ + +/obj/item/storage/bag/construction + name = "construction bag" + icon = 'icons/obj/tools.dmi' + icon_state = "construction_bag" + worn_icon_state = "construction_bag" + desc = "A bag for storing small construction components." + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/construction/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 100 + STR.max_items = 50 + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.insert_preposition = "in" + STR.set_holdable(list( + /obj/item/stack/ore/bluespace_crystal, + /obj/item/assembly, + /obj/item/stock_parts, + /obj/item/reagent_containers/glass/beaker, + /obj/item/stack/cable_coil, + /obj/item/circuitboard, + /obj/item/electronics, + /obj/item/wallframe/camera + )) diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 0507bf2679a..a5f39ce3522 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -1,735 +1,735 @@ -/obj/item/storage/belt - name = "belt" - desc = "Can hold various things." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "utility" - inhand_icon_state = "utility" - worn_icon_state = "utility" - lefthand_file = 'icons/mob/inhands/equipment/belt_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/belt_righthand.dmi' - slot_flags = ITEM_SLOT_BELT - attack_verb = list("whipped", "lashed", "disciplined") - max_integrity = 300 - equip_sound = 'sound/items/equip/toolbelt_equip.ogg' - var/content_overlays = FALSE //If this is true, the belt will gain overlays based on what it's holding - -/obj/item/storage/belt/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins belting [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/belt/update_overlays() - . = ..() - if(content_overlays) - for(var/obj/item/I in contents) - . += I.get_belt_overlay() - -/obj/item/storage/belt/Initialize() - . = ..() - update_icon() - -/obj/item/storage/belt/utility - name = "toolbelt" //Carn: utility belt is nicer, but it bamboozles the text parsing. - desc = "Holds tools." - icon_state = "utility" - inhand_icon_state = "utility" - worn_icon_state = "utility" - content_overlays = TRUE - custom_premium_price = 300 - drop_sound = 'sound/items/handling/toolbelt_drop.ogg' - pickup_sound = 'sound/items/handling/toolbelt_pickup.ogg' - -/obj/item/storage/belt/utility/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - STR.set_holdable(list( - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/t_scanner, - /obj/item/analyzer, - /obj/item/geiger_counter, - /obj/item/extinguisher/mini, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/holosign_creator/atmos, - /obj/item/holosign_creator/engineering, - /obj/item/forcefield_projector, - /obj/item/assembly/signaler, - /obj/item/lightreplacer, - /obj/item/construction/rcd, - /obj/item/pipe_dispenser, - /obj/item/inducer, - /obj/item/plunger - )) - -/obj/item/storage/belt/utility/chief - name = "\improper Chief Engineer's toolbelt" //"the Chief Engineer's toolbelt", because "Chief Engineer's toolbelt" is not a proper noun - desc = "Holds tools, looks snazzy." - icon_state = "utility_ce" - inhand_icon_state = "utility_ce" - worn_icon_state = "utility_ce" - -/obj/item/storage/belt/utility/chief/full/PopulateContents() - new /obj/item/screwdriver/power(src) - new /obj/item/crowbar/power(src) - new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src) - new /obj/item/extinguisher/mini(src) - new /obj/item/analyzer(src) - //much roomier now that we've managed to remove two tools - -/obj/item/storage/belt/utility/full/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src) - -/obj/item/storage/belt/utility/full/engi/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src) - - -/obj/item/storage/belt/utility/atmostech/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/t_scanner(src) - new /obj/item/extinguisher/mini(src) - -/obj/item/storage/belt/utility/syndicate/PopulateContents() - new /obj/item/screwdriver/nuke(src) - new /obj/item/wrench/combat(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/inducer/syndicate(src) - -/obj/item/storage/belt/medical - name = "medical belt" - desc = "Can hold various medical equipment." - icon_state = "medical" - inhand_icon_state = "medical" - worn_icon_state = "medical" - -/obj/item/storage/belt/medical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - STR.set_holdable(list( - /obj/item/healthanalyzer, - /obj/item/dnainjector, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/pill, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/medigel, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/storage/pill_bottle, - /obj/item/stack/medical, - /obj/item/flashlight/pen, - /obj/item/extinguisher/mini, - /obj/item/reagent_containers/hypospray, - /obj/item/sensor_device, - /obj/item/radio, - /obj/item/clothing/gloves/, - /obj/item/lazarus_injector, - /obj/item/bikehorn/rubberducky, - /obj/item/clothing/mask/surgical, - /obj/item/clothing/mask/breath, - /obj/item/clothing/mask/breath/medical, - /obj/item/surgical_drapes, //for true paramedics - /obj/item/scalpel, - /obj/item/circular_saw, - /obj/item/bonesetter, - /obj/item/surgicaldrill, - /obj/item/retractor, - /obj/item/cautery, - /obj/item/hemostat, - /obj/item/geiger_counter, - /obj/item/clothing/neck/stethoscope, - /obj/item/stamp, - /obj/item/clothing/glasses, - /obj/item/wrench/medical, - /obj/item/clothing/mask/muzzle, - /obj/item/reagent_containers/blood, - /obj/item/tank/internals/emergency_oxygen, - /obj/item/gun/syringe/syndicate, - /obj/item/implantcase, - /obj/item/implant, - /obj/item/implanter, - /obj/item/pinpointer/crew, - /obj/item/holosign_creator/medical, - /obj/item/construction/plumbing, - /obj/item/plunger, - /obj/item/reagent_containers/spray, - /obj/item/shears, - /obj/item/stack/sticky_tape //surgical tape - )) - -/obj/item/storage/belt/medical/paramedic/PopulateContents() - new /obj/item/sensor_device(src) - new /obj/item/pinpointer/crew/prox(src) - new /obj/item/stack/medical/gauze/twelve(src) - new /obj/item/reagent_containers/syringe(src) - new /obj/item/stack/medical/bone_gel(src) - new /obj/item/stack/sticky_tape/surgical(src) - new /obj/item/reagent_containers/glass/bottle/calomel(src) - update_icon() - -/obj/item/storage/belt/security - name = "security belt" - desc = "Can hold security gear like handcuffs and flashes." - icon_state = "security" - inhand_icon_state = "security"//Could likely use a better one. - worn_icon_state = "security" - content_overlays = TRUE - -/obj/item/storage/belt/security/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.set_holdable(list( - /obj/item/melee/baton, - /obj/item/melee/classic_baton, - /obj/item/grenade, - /obj/item/reagent_containers/spray/pepper, - /obj/item/restraints/handcuffs, - /obj/item/assembly/flash/handheld, - /obj/item/clothing/glasses, - /obj/item/ammo_casing/shotgun, - /obj/item/ammo_box, - /obj/item/reagent_containers/food/snacks/donut, - /obj/item/kitchen/knife/combat, - /obj/item/flashlight/seclite, - /obj/item/melee/classic_baton/telescopic, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/restraints/legcuffs/bola, - /obj/item/holosign_creator/security - )) - -/obj/item/storage/belt/security/full/PopulateContents() - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/melee/baton/loaded(src) - update_icon() - -/obj/item/storage/belt/security/webbing - name = "security webbing" - desc = "Unique and versatile chest rig, can hold security gear." - icon_state = "securitywebbing" - inhand_icon_state = "securitywebbing" - worn_icon_state = "securitywebbing" - content_overlays = FALSE - custom_premium_price = 900 - -/obj/item/storage/belt/security/webbing/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - -/obj/item/storage/belt/mining - name = "explorer's webbing" - desc = "A versatile chest rig, cherished by miners and hunters alike." - icon_state = "explorer1" - inhand_icon_state = "explorer1" - worn_icon_state = "explorer1" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/belt/mining/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 20 - STR.set_holdable(list( - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/analyzer, - /obj/item/extinguisher/mini, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/resonator, - /obj/item/mining_scanner, - /obj/item/pickaxe, - /obj/item/shovel, - /obj/item/stack/sheet/animalhide, - /obj/item/stack/sheet/sinew, - /obj/item/stack/sheet/bone, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/reagent_containers/food/drinks/bottle, - /obj/item/stack/medical, - /obj/item/kitchen/knife, - /obj/item/reagent_containers/hypospray, - /obj/item/gps, - /obj/item/storage/bag/ore, - /obj/item/survivalcapsule, - /obj/item/t_scanner/adv_mining_scanner, - /obj/item/reagent_containers/pill, - /obj/item/storage/pill_bottle, - /obj/item/stack/ore, - /obj/item/reagent_containers/food/drinks, - /obj/item/organ/regenerative_core, - /obj/item/wormhole_jaunter, - /obj/item/stack/marker_beacon, - /obj/item/key/lasso - )) - - -/obj/item/storage/belt/mining/vendor - contents = newlist(/obj/item/survivalcapsule) - -/obj/item/storage/belt/mining/alt - icon_state = "explorer2" - inhand_icon_state = "explorer2" - worn_icon_state = "explorer2" - -/obj/item/storage/belt/mining/primitive - name = "hunter's belt" - desc = "A versatile belt, woven from sinew." - icon_state = "ebelt" - inhand_icon_state = "ebelt" - worn_icon_state = "ebelt" - -/obj/item/storage/belt/mining/primitive/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - -/obj/item/storage/belt/soulstone - name = "soul stone belt" - desc = "Designed for ease of access to the shards during a fight, as to not let a single enemy spirit slip away." - icon_state = "soulstonebelt" - inhand_icon_state = "soulstonebelt" - worn_icon_state = "soulstonebelt" - -/obj/item/storage/belt/soulstone/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list( - /obj/item/soulstone - )) - -/obj/item/storage/belt/soulstone/full/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/soulstone(src) - -/obj/item/storage/belt/soulstone/full/chappy/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/soulstone/anybody/chaplain(src) - -/obj/item/storage/belt/champion - name = "championship belt" - desc = "Proves to the world that you are the strongest!" - icon_state = "championbelt" - inhand_icon_state = "championbelt" - worn_icon_state = "championbelt" - custom_materials = list(/datum/material/gold=400) - -/obj/item/storage/belt/champion/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 1 - STR.set_holdable(list( - /obj/item/clothing/mask/luchador - )) - -/obj/item/storage/belt/military - name = "chest rig" - desc = "A set of tactical webbing worn by Syndicate boarding parties." - icon_state = "militarywebbing" - inhand_icon_state = "militarywebbing" - worn_icon_state = "militarywebbing" - resistance_flags = FIRE_PROOF - -/obj/item/storage/belt/military/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/belt/military/snack - name = "tactical snack rig" - -/obj/item/storage/belt/military/snack/Initialize() - . = ..() - var/sponsor = pick("DonkCo", "Waffle Co.", "Roffle Co.", "Gorlax Marauders", "Tiger Cooperative") - desc = "A set of snack-tical webbing worn by athletes of the [sponsor] VR sports division." - -/obj/item/storage/belt/military/snack/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.set_holdable(list( - /obj/item/reagent_containers/food/snacks, - /obj/item/reagent_containers/food/drinks - )) - - var/amount = 5 - var/rig_snacks - while(contents.len <= amount) - rig_snacks = pick(list( - /obj/item/reagent_containers/food/snacks/candy, - /obj/item/reagent_containers/food/drinks/dry_ramen, - /obj/item/reagent_containers/food/snacks/chips, - /obj/item/reagent_containers/food/snacks/sosjerky, - /obj/item/reagent_containers/food/snacks/syndicake, - /obj/item/reagent_containers/food/snacks/spacetwinkie, - /obj/item/reagent_containers/food/snacks/cheesiehonkers, - /obj/item/reagent_containers/food/snacks/nachos, - /obj/item/reagent_containers/food/snacks/cheesynachos, - /obj/item/reagent_containers/food/snacks/cubannachos, - /obj/item/reagent_containers/food/snacks/nugget, - /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato, - /obj/item/reagent_containers/food/snacks/rofflewaffles, - /obj/item/reagent_containers/food/snacks/donkpocket, - /obj/item/reagent_containers/food/drinks/soda_cans/cola, - /obj/item/reagent_containers/food/drinks/soda_cans/space_mountain_wind, - /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb, - /obj/item/reagent_containers/food/drinks/soda_cans/starkist, - /obj/item/reagent_containers/food/drinks/soda_cans/space_up, - /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game, - /obj/item/reagent_containers/food/drinks/soda_cans/lemon_lime, - /obj/item/reagent_containers/food/drinks/drinkingglass/filled/nuka_cola - )) - new rig_snacks(src) - -/obj/item/storage/belt/military/abductor - name = "agent belt" - desc = "A belt used by abductor agents." - icon = 'icons/obj/abductor.dmi' - icon_state = "belt" - inhand_icon_state = "security" - worn_icon_state = "security" - -/obj/item/storage/belt/military/abductor/full/PopulateContents() - new /obj/item/screwdriver/abductor(src) - new /obj/item/wrench/abductor(src) - new /obj/item/weldingtool/abductor(src) - new /obj/item/crowbar/abductor(src) - new /obj/item/wirecutters/abductor(src) - new /obj/item/multitool/abductor(src) - new /obj/item/stack/cable_coil(src) - -/obj/item/storage/belt/military/army - name = "army belt" - desc = "A belt used by military forces." - icon_state = "grenadebeltold" - inhand_icon_state = "security" - worn_icon_state = "grenadebeltold" - -/obj/item/storage/belt/military/assault - name = "assault belt" - desc = "A tactical assault belt." - icon_state = "assaultbelt" - inhand_icon_state = "security" - worn_icon_state = "assault" - -/obj/item/storage/belt/military/assault/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - -/obj/item/storage/belt/grenade - name = "grenadier belt" - desc = "A belt for holding grenades." - icon_state = "grenadebeltnew" - inhand_icon_state = "security" - worn_icon_state = "grenadebeltnew" - -/obj/item/storage/belt/grenade/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 30 - STR.display_numerical_stacking = TRUE - STR.max_combined_w_class = 60 - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.set_holdable(list( - /obj/item/grenade, - /obj/item/screwdriver, - /obj/item/lighter, - /obj/item/multitool, - /obj/item/reagent_containers/food/drinks/bottle/molotov, - /obj/item/grenade/c4, - /obj/item/reagent_containers/food/snacks/grown/cherry_bomb, - /obj/item/reagent_containers/food/snacks/grown/firelemon - )) - -/obj/item/storage/belt/grenade/full/PopulateContents() - var/static/items_inside = list( - /obj/item/grenade/flashbang = 1, - /obj/item/grenade/smokebomb = 4, - /obj/item/grenade/empgrenade = 1, - /obj/item/grenade/empgrenade = 1, - /obj/item/grenade/frag = 10, - /obj/item/grenade/gluon = 4, - /obj/item/grenade/chem_grenade/incendiary = 2, - /obj/item/grenade/chem_grenade/facid = 1, - /obj/item/grenade/syndieminibomb = 2, - /obj/item/screwdriver = 1, - /obj/item/multitool = 1) - generate_items_inside(items_inside,src) - - -/obj/item/storage/belt/wands - name = "wand belt" - desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." - icon_state = "soulstonebelt" - inhand_icon_state = "soulstonebelt" - worn_icon_state = "soulstonebelt" - -/obj/item/storage/belt/wands/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list( - /obj/item/gun/magic/wand - )) - -/obj/item/storage/belt/wands/full/PopulateContents() - new /obj/item/gun/magic/wand/death(src) - new /obj/item/gun/magic/wand/resurrection(src) - new /obj/item/gun/magic/wand/polymorph(src) - new /obj/item/gun/magic/wand/teleport(src) - new /obj/item/gun/magic/wand/door(src) - new /obj/item/gun/magic/wand/fireball(src) - - for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition - W.max_charges = initial(W.max_charges) - W.charges = W.max_charges - -/obj/item/storage/belt/janitor - name = "janibelt" - desc = "A belt used to hold most janitorial supplies." - icon_state = "janibelt" - inhand_icon_state = "janibelt" - worn_icon_state = "janibelt" - -/obj/item/storage/belt/janitor/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_NORMAL // Set to this so the light replacer can fit. - STR.set_holdable(list( - /obj/item/grenade/chem_grenade, - /obj/item/lightreplacer, - /obj/item/flashlight, - /obj/item/reagent_containers/spray, - /obj/item/soap, - /obj/item/holosign_creator, - /obj/item/forcefield_projector, - /obj/item/key/janitor, - /obj/item/clothing/gloves, - /obj/item/melee/flyswatter, - /obj/item/assembly/mousetrap, - /obj/item/paint/paint_remover, - /obj/item/pushbroom - )) - -/obj/item/storage/belt/janitor/full/PopulateContents() - new /obj/item/lightreplacer(src) - new /obj/item/reagent_containers/spray/cleaner(src) - new /obj/item/soap/nanotrasen(src) - new /obj/item/holosign_creator(src) - new /obj/item/melee/flyswatter(src) - -/obj/item/storage/belt/bandolier - name = "bandolier" - desc = "A bandolier for holding shotgun ammunition." - icon_state = "bandolier" - inhand_icon_state = "bandolier" - worn_icon_state = "bandolier" - -/obj/item/storage/belt/bandolier/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 18 - STR.max_combined_w_class = 18 - STR.display_numerical_stacking = TRUE - STR.set_holdable(list( - /obj/item/ammo_casing/shotgun - )) - -/obj/item/storage/belt/fannypack - name = "fannypack" - desc = "A dorky fannypack for keeping small items in." - icon_state = "fannypack_leather" - inhand_icon_state = "fannypack_leather" - worn_icon_state = "fannypack_leather" - dying_key = DYE_REGISTRY_FANNYPACK - custom_price = 100 - -/obj/item/storage/belt/fannypack/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 3 - STR.max_w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/belt/fannypack/black - name = "black fannypack" - icon_state = "fannypack_black" - inhand_icon_state = "fannypack_black" - worn_icon_state = "fannypack_black" - -/obj/item/storage/belt/fannypack/red - name = "red fannypack" - icon_state = "fannypack_red" - inhand_icon_state = "fannypack_red" - worn_icon_state = "fannypack_red" - -/obj/item/storage/belt/fannypack/purple - name = "purple fannypack" - icon_state = "fannypack_purple" - inhand_icon_state = "fannypack_purple" - worn_icon_state = "fannypack_purple" - -/obj/item/storage/belt/fannypack/blue - name = "blue fannypack" - icon_state = "fannypack_blue" - inhand_icon_state = "fannypack_blue" - worn_icon_state = "fannypack_blue" - -/obj/item/storage/belt/fannypack/orange - name = "orange fannypack" - icon_state = "fannypack_orange" - inhand_icon_state = "fannypack_orange" - worn_icon_state = "fannypack_orange" - -/obj/item/storage/belt/fannypack/white - name = "white fannypack" - icon_state = "fannypack_white" - inhand_icon_state = "fannypack_white" - worn_icon_state = "fannypack_white" - -/obj/item/storage/belt/fannypack/green - name = "green fannypack" - icon_state = "fannypack_green" - inhand_icon_state = "fannypack_green" - worn_icon_state = "fannypack_green" - -/obj/item/storage/belt/fannypack/pink - name = "pink fannypack" - icon_state = "fannypack_pink" - inhand_icon_state = "fannypack_pink" - worn_icon_state = "fannypack_pink" - -/obj/item/storage/belt/fannypack/cyan - name = "cyan fannypack" - icon_state = "fannypack_cyan" - inhand_icon_state = "fannypack_cyan" - worn_icon_state = "fannypack_cyan" - -/obj/item/storage/belt/fannypack/yellow - name = "yellow fannypack" - icon_state = "fannypack_yellow" - inhand_icon_state = "fannypack_yellow" - worn_icon_state = "fannypack_yellow" - -/obj/item/storage/belt/sabre - name = "sabre sheath" - desc = "An ornate sheath designed to hold an officer's blade." - icon_state = "sheath" - inhand_icon_state = "sheath" - worn_icon_state = "sheath" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/belt/sabre/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 1 - STR.rustle_sound = FALSE - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.set_holdable(list( - /obj/item/melee/sabre - )) - -/obj/item/storage/belt/sabre/examine(mob/user) - . = ..() - if(length(contents)) - . += "Alt-click it to quickly draw the blade." - -/obj/item/storage/belt/sabre/AltClick(mob/user) - if(!iscarbon(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - if(length(contents)) - var/obj/item/I = contents[1] - user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].") - user.put_in_hands(I) - update_icon() - else - to_chat(user, "[src] is empty!") - -/obj/item/storage/belt/sabre/update_icon_state() - icon_state = initial(inhand_icon_state) - inhand_icon_state = initial(inhand_icon_state) - worn_icon_state = initial(worn_icon_state) - if(contents.len) - icon_state += "-sabre" - inhand_icon_state += "-sabre" - worn_icon_state += "-sabre" - -/obj/item/storage/belt/sabre/PopulateContents() - new /obj/item/melee/sabre(src) - update_icon() - -/obj/item/storage/belt/plant - name = "botanical belt" - desc = "A belt used to hold most hydroponics supplies. Suprisingly, not green." - icon_state = "plantbelt" - inhand_icon_state = "plantbelt" - worn_icon_state = "plantbelt" - content_overlays = TRUE - -/obj/item/storage/belt/plant/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.set_holdable(list( - /obj/item/reagent_containers/spray/plantbgone, - /obj/item/plant_analyzer, - /obj/item/seeds, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/glass/beaker, - /obj/item/cultivator, - /obj/item/reagent_containers/spray/pestspray, - /obj/item/hatchet, - /obj/item/graft, - /obj/item/secateurs, - /obj/item/geneshears, - /obj/item/shovel/spade - )) +/obj/item/storage/belt + name = "belt" + desc = "Can hold various things." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "utility" + inhand_icon_state = "utility" + worn_icon_state = "utility" + lefthand_file = 'icons/mob/inhands/equipment/belt_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/belt_righthand.dmi' + slot_flags = ITEM_SLOT_BELT + attack_verb = list("whipped", "lashed", "disciplined") + max_integrity = 300 + equip_sound = 'sound/items/equip/toolbelt_equip.ogg' + var/content_overlays = FALSE //If this is true, the belt will gain overlays based on what it's holding + +/obj/item/storage/belt/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins belting [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/belt/update_overlays() + . = ..() + if(content_overlays) + for(var/obj/item/I in contents) + . += I.get_belt_overlay() + +/obj/item/storage/belt/Initialize() + . = ..() + update_icon() + +/obj/item/storage/belt/utility + name = "toolbelt" //Carn: utility belt is nicer, but it bamboozles the text parsing. + desc = "Holds tools." + icon_state = "utility" + inhand_icon_state = "utility" + worn_icon_state = "utility" + content_overlays = TRUE + custom_premium_price = 300 + drop_sound = 'sound/items/handling/toolbelt_drop.ogg' + pickup_sound = 'sound/items/handling/toolbelt_pickup.ogg' + +/obj/item/storage/belt/utility/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + STR.set_holdable(list( + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/t_scanner, + /obj/item/analyzer, + /obj/item/geiger_counter, + /obj/item/extinguisher/mini, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/holosign_creator/atmos, + /obj/item/holosign_creator/engineering, + /obj/item/forcefield_projector, + /obj/item/assembly/signaler, + /obj/item/lightreplacer, + /obj/item/construction/rcd, + /obj/item/pipe_dispenser, + /obj/item/inducer, + /obj/item/plunger + )) + +/obj/item/storage/belt/utility/chief + name = "\improper Chief Engineer's toolbelt" //"the Chief Engineer's toolbelt", because "Chief Engineer's toolbelt" is not a proper noun + desc = "Holds tools, looks snazzy." + icon_state = "utility_ce" + inhand_icon_state = "utility_ce" + worn_icon_state = "utility_ce" + +/obj/item/storage/belt/utility/chief/full/PopulateContents() + new /obj/item/screwdriver/power(src) + new /obj/item/crowbar/power(src) + new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/extinguisher/mini(src) + new /obj/item/analyzer(src) + //much roomier now that we've managed to remove two tools + +/obj/item/storage/belt/utility/full/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src) + +/obj/item/storage/belt/utility/full/engi/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src) + + +/obj/item/storage/belt/utility/atmostech/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/t_scanner(src) + new /obj/item/extinguisher/mini(src) + +/obj/item/storage/belt/utility/syndicate/PopulateContents() + new /obj/item/screwdriver/nuke(src) + new /obj/item/wrench/combat(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/inducer/syndicate(src) + +/obj/item/storage/belt/medical + name = "medical belt" + desc = "Can hold various medical equipment." + icon_state = "medical" + inhand_icon_state = "medical" + worn_icon_state = "medical" + +/obj/item/storage/belt/medical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + STR.set_holdable(list( + /obj/item/healthanalyzer, + /obj/item/dnainjector, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/medigel, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/storage/pill_bottle, + /obj/item/stack/medical, + /obj/item/flashlight/pen, + /obj/item/extinguisher/mini, + /obj/item/reagent_containers/hypospray, + /obj/item/sensor_device, + /obj/item/radio, + /obj/item/clothing/gloves/, + /obj/item/lazarus_injector, + /obj/item/bikehorn/rubberducky, + /obj/item/clothing/mask/surgical, + /obj/item/clothing/mask/breath, + /obj/item/clothing/mask/breath/medical, + /obj/item/surgical_drapes, //for true paramedics + /obj/item/scalpel, + /obj/item/circular_saw, + /obj/item/bonesetter, + /obj/item/surgicaldrill, + /obj/item/retractor, + /obj/item/cautery, + /obj/item/hemostat, + /obj/item/geiger_counter, + /obj/item/clothing/neck/stethoscope, + /obj/item/stamp, + /obj/item/clothing/glasses, + /obj/item/wrench/medical, + /obj/item/clothing/mask/muzzle, + /obj/item/reagent_containers/blood, + /obj/item/tank/internals/emergency_oxygen, + /obj/item/gun/syringe/syndicate, + /obj/item/implantcase, + /obj/item/implant, + /obj/item/implanter, + /obj/item/pinpointer/crew, + /obj/item/holosign_creator/medical, + /obj/item/construction/plumbing, + /obj/item/plunger, + /obj/item/reagent_containers/spray, + /obj/item/shears, + /obj/item/stack/sticky_tape //surgical tape + )) + +/obj/item/storage/belt/medical/paramedic/PopulateContents() + new /obj/item/sensor_device(src) + new /obj/item/pinpointer/crew/prox(src) + new /obj/item/stack/medical/gauze/twelve(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/stack/medical/bone_gel(src) + new /obj/item/stack/sticky_tape/surgical(src) + new /obj/item/reagent_containers/glass/bottle/calomel(src) + update_icon() + +/obj/item/storage/belt/security + name = "security belt" + desc = "Can hold security gear like handcuffs and flashes." + icon_state = "security" + inhand_icon_state = "security"//Could likely use a better one. + worn_icon_state = "security" + content_overlays = TRUE + +/obj/item/storage/belt/security/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.set_holdable(list( + /obj/item/melee/baton, + /obj/item/melee/classic_baton, + /obj/item/grenade, + /obj/item/reagent_containers/spray/pepper, + /obj/item/restraints/handcuffs, + /obj/item/assembly/flash/handheld, + /obj/item/clothing/glasses, + /obj/item/ammo_casing/shotgun, + /obj/item/ammo_box, + /obj/item/reagent_containers/food/snacks/donut, + /obj/item/kitchen/knife/combat, + /obj/item/flashlight/seclite, + /obj/item/melee/classic_baton/telescopic, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/restraints/legcuffs/bola, + /obj/item/holosign_creator/security + )) + +/obj/item/storage/belt/security/full/PopulateContents() + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/melee/baton/loaded(src) + update_icon() + +/obj/item/storage/belt/security/webbing + name = "security webbing" + desc = "Unique and versatile chest rig, can hold security gear." + icon_state = "securitywebbing" + inhand_icon_state = "securitywebbing" + worn_icon_state = "securitywebbing" + content_overlays = FALSE + custom_premium_price = 900 + +/obj/item/storage/belt/security/webbing/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + +/obj/item/storage/belt/mining + name = "explorer's webbing" + desc = "A versatile chest rig, cherished by miners and hunters alike." + icon_state = "explorer1" + inhand_icon_state = "explorer1" + worn_icon_state = "explorer1" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/belt/mining/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 20 + STR.set_holdable(list( + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/analyzer, + /obj/item/extinguisher/mini, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/resonator, + /obj/item/mining_scanner, + /obj/item/pickaxe, + /obj/item/shovel, + /obj/item/stack/sheet/animalhide, + /obj/item/stack/sheet/sinew, + /obj/item/stack/sheet/bone, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/reagent_containers/food/drinks/bottle, + /obj/item/stack/medical, + /obj/item/kitchen/knife, + /obj/item/reagent_containers/hypospray, + /obj/item/gps, + /obj/item/storage/bag/ore, + /obj/item/survivalcapsule, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/reagent_containers/pill, + /obj/item/storage/pill_bottle, + /obj/item/stack/ore, + /obj/item/reagent_containers/food/drinks, + /obj/item/organ/regenerative_core, + /obj/item/wormhole_jaunter, + /obj/item/stack/marker_beacon, + /obj/item/key/lasso + )) + + +/obj/item/storage/belt/mining/vendor + contents = newlist(/obj/item/survivalcapsule) + +/obj/item/storage/belt/mining/alt + icon_state = "explorer2" + inhand_icon_state = "explorer2" + worn_icon_state = "explorer2" + +/obj/item/storage/belt/mining/primitive + name = "hunter's belt" + desc = "A versatile belt, woven from sinew." + icon_state = "ebelt" + inhand_icon_state = "ebelt" + worn_icon_state = "ebelt" + +/obj/item/storage/belt/mining/primitive/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + +/obj/item/storage/belt/soulstone + name = "soul stone belt" + desc = "Designed for ease of access to the shards during a fight, as to not let a single enemy spirit slip away." + icon_state = "soulstonebelt" + inhand_icon_state = "soulstonebelt" + worn_icon_state = "soulstonebelt" + +/obj/item/storage/belt/soulstone/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list( + /obj/item/soulstone + )) + +/obj/item/storage/belt/soulstone/full/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/soulstone(src) + +/obj/item/storage/belt/soulstone/full/chappy/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/soulstone/anybody/chaplain(src) + +/obj/item/storage/belt/champion + name = "championship belt" + desc = "Proves to the world that you are the strongest!" + icon_state = "championbelt" + inhand_icon_state = "championbelt" + worn_icon_state = "championbelt" + custom_materials = list(/datum/material/gold=400) + +/obj/item/storage/belt/champion/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 1 + STR.set_holdable(list( + /obj/item/clothing/mask/luchador + )) + +/obj/item/storage/belt/military + name = "chest rig" + desc = "A set of tactical webbing worn by Syndicate boarding parties." + icon_state = "militarywebbing" + inhand_icon_state = "militarywebbing" + worn_icon_state = "militarywebbing" + resistance_flags = FIRE_PROOF + +/obj/item/storage/belt/military/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/belt/military/snack + name = "tactical snack rig" + +/obj/item/storage/belt/military/snack/Initialize() + . = ..() + var/sponsor = pick("DonkCo", "Waffle Co.", "Roffle Co.", "Gorlax Marauders", "Tiger Cooperative") + desc = "A set of snack-tical webbing worn by athletes of the [sponsor] VR sports division." + +/obj/item/storage/belt/military/snack/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.set_holdable(list( + /obj/item/reagent_containers/food/snacks, + /obj/item/reagent_containers/food/drinks + )) + + var/amount = 5 + var/rig_snacks + while(contents.len <= amount) + rig_snacks = pick(list( + /obj/item/reagent_containers/food/snacks/candy, + /obj/item/reagent_containers/food/drinks/dry_ramen, + /obj/item/reagent_containers/food/snacks/chips, + /obj/item/reagent_containers/food/snacks/sosjerky, + /obj/item/reagent_containers/food/snacks/syndicake, + /obj/item/reagent_containers/food/snacks/spacetwinkie, + /obj/item/reagent_containers/food/snacks/cheesiehonkers, + /obj/item/reagent_containers/food/snacks/nachos, + /obj/item/reagent_containers/food/snacks/cheesynachos, + /obj/item/reagent_containers/food/snacks/cubannachos, + /obj/item/reagent_containers/food/snacks/nugget, + /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato, + /obj/item/reagent_containers/food/snacks/rofflewaffles, + /obj/item/reagent_containers/food/snacks/donkpocket, + /obj/item/reagent_containers/food/drinks/soda_cans/cola, + /obj/item/reagent_containers/food/drinks/soda_cans/space_mountain_wind, + /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb, + /obj/item/reagent_containers/food/drinks/soda_cans/starkist, + /obj/item/reagent_containers/food/drinks/soda_cans/space_up, + /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game, + /obj/item/reagent_containers/food/drinks/soda_cans/lemon_lime, + /obj/item/reagent_containers/food/drinks/drinkingglass/filled/nuka_cola + )) + new rig_snacks(src) + +/obj/item/storage/belt/military/abductor + name = "agent belt" + desc = "A belt used by abductor agents." + icon = 'icons/obj/abductor.dmi' + icon_state = "belt" + inhand_icon_state = "security" + worn_icon_state = "security" + +/obj/item/storage/belt/military/abductor/full/PopulateContents() + new /obj/item/screwdriver/abductor(src) + new /obj/item/wrench/abductor(src) + new /obj/item/weldingtool/abductor(src) + new /obj/item/crowbar/abductor(src) + new /obj/item/wirecutters/abductor(src) + new /obj/item/multitool/abductor(src) + new /obj/item/stack/cable_coil(src) + +/obj/item/storage/belt/military/army + name = "army belt" + desc = "A belt used by military forces." + icon_state = "grenadebeltold" + inhand_icon_state = "security" + worn_icon_state = "grenadebeltold" + +/obj/item/storage/belt/military/assault + name = "assault belt" + desc = "A tactical assault belt." + icon_state = "assaultbelt" + inhand_icon_state = "security" + worn_icon_state = "assault" + +/obj/item/storage/belt/military/assault/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + +/obj/item/storage/belt/grenade + name = "grenadier belt" + desc = "A belt for holding grenades." + icon_state = "grenadebeltnew" + inhand_icon_state = "security" + worn_icon_state = "grenadebeltnew" + +/obj/item/storage/belt/grenade/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 30 + STR.display_numerical_stacking = TRUE + STR.max_combined_w_class = 60 + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.set_holdable(list( + /obj/item/grenade, + /obj/item/screwdriver, + /obj/item/lighter, + /obj/item/multitool, + /obj/item/reagent_containers/food/drinks/bottle/molotov, + /obj/item/grenade/c4, + /obj/item/reagent_containers/food/snacks/grown/cherry_bomb, + /obj/item/reagent_containers/food/snacks/grown/firelemon + )) + +/obj/item/storage/belt/grenade/full/PopulateContents() + var/static/items_inside = list( + /obj/item/grenade/flashbang = 1, + /obj/item/grenade/smokebomb = 4, + /obj/item/grenade/empgrenade = 1, + /obj/item/grenade/empgrenade = 1, + /obj/item/grenade/frag = 10, + /obj/item/grenade/gluon = 4, + /obj/item/grenade/chem_grenade/incendiary = 2, + /obj/item/grenade/chem_grenade/facid = 1, + /obj/item/grenade/syndieminibomb = 2, + /obj/item/screwdriver = 1, + /obj/item/multitool = 1) + generate_items_inside(items_inside,src) + + +/obj/item/storage/belt/wands + name = "wand belt" + desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." + icon_state = "soulstonebelt" + inhand_icon_state = "soulstonebelt" + worn_icon_state = "soulstonebelt" + +/obj/item/storage/belt/wands/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list( + /obj/item/gun/magic/wand + )) + +/obj/item/storage/belt/wands/full/PopulateContents() + new /obj/item/gun/magic/wand/death(src) + new /obj/item/gun/magic/wand/resurrection(src) + new /obj/item/gun/magic/wand/polymorph(src) + new /obj/item/gun/magic/wand/teleport(src) + new /obj/item/gun/magic/wand/door(src) + new /obj/item/gun/magic/wand/fireball(src) + + for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition + W.max_charges = initial(W.max_charges) + W.charges = W.max_charges + +/obj/item/storage/belt/janitor + name = "janibelt" + desc = "A belt used to hold most janitorial supplies." + icon_state = "janibelt" + inhand_icon_state = "janibelt" + worn_icon_state = "janibelt" + +/obj/item/storage/belt/janitor/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_NORMAL // Set to this so the light replacer can fit. + STR.set_holdable(list( + /obj/item/grenade/chem_grenade, + /obj/item/lightreplacer, + /obj/item/flashlight, + /obj/item/reagent_containers/spray, + /obj/item/soap, + /obj/item/holosign_creator, + /obj/item/forcefield_projector, + /obj/item/key/janitor, + /obj/item/clothing/gloves, + /obj/item/melee/flyswatter, + /obj/item/assembly/mousetrap, + /obj/item/paint/paint_remover, + /obj/item/pushbroom + )) + +/obj/item/storage/belt/janitor/full/PopulateContents() + new /obj/item/lightreplacer(src) + new /obj/item/reagent_containers/spray/cleaner(src) + new /obj/item/soap/nanotrasen(src) + new /obj/item/holosign_creator(src) + new /obj/item/melee/flyswatter(src) + +/obj/item/storage/belt/bandolier + name = "bandolier" + desc = "A bandolier for holding shotgun ammunition." + icon_state = "bandolier" + inhand_icon_state = "bandolier" + worn_icon_state = "bandolier" + +/obj/item/storage/belt/bandolier/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 18 + STR.max_combined_w_class = 18 + STR.display_numerical_stacking = TRUE + STR.set_holdable(list( + /obj/item/ammo_casing/shotgun + )) + +/obj/item/storage/belt/fannypack + name = "fannypack" + desc = "A dorky fannypack for keeping small items in." + icon_state = "fannypack_leather" + inhand_icon_state = "fannypack_leather" + worn_icon_state = "fannypack_leather" + dying_key = DYE_REGISTRY_FANNYPACK + custom_price = 100 + +/obj/item/storage/belt/fannypack/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 3 + STR.max_w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/belt/fannypack/black + name = "black fannypack" + icon_state = "fannypack_black" + inhand_icon_state = "fannypack_black" + worn_icon_state = "fannypack_black" + +/obj/item/storage/belt/fannypack/red + name = "red fannypack" + icon_state = "fannypack_red" + inhand_icon_state = "fannypack_red" + worn_icon_state = "fannypack_red" + +/obj/item/storage/belt/fannypack/purple + name = "purple fannypack" + icon_state = "fannypack_purple" + inhand_icon_state = "fannypack_purple" + worn_icon_state = "fannypack_purple" + +/obj/item/storage/belt/fannypack/blue + name = "blue fannypack" + icon_state = "fannypack_blue" + inhand_icon_state = "fannypack_blue" + worn_icon_state = "fannypack_blue" + +/obj/item/storage/belt/fannypack/orange + name = "orange fannypack" + icon_state = "fannypack_orange" + inhand_icon_state = "fannypack_orange" + worn_icon_state = "fannypack_orange" + +/obj/item/storage/belt/fannypack/white + name = "white fannypack" + icon_state = "fannypack_white" + inhand_icon_state = "fannypack_white" + worn_icon_state = "fannypack_white" + +/obj/item/storage/belt/fannypack/green + name = "green fannypack" + icon_state = "fannypack_green" + inhand_icon_state = "fannypack_green" + worn_icon_state = "fannypack_green" + +/obj/item/storage/belt/fannypack/pink + name = "pink fannypack" + icon_state = "fannypack_pink" + inhand_icon_state = "fannypack_pink" + worn_icon_state = "fannypack_pink" + +/obj/item/storage/belt/fannypack/cyan + name = "cyan fannypack" + icon_state = "fannypack_cyan" + inhand_icon_state = "fannypack_cyan" + worn_icon_state = "fannypack_cyan" + +/obj/item/storage/belt/fannypack/yellow + name = "yellow fannypack" + icon_state = "fannypack_yellow" + inhand_icon_state = "fannypack_yellow" + worn_icon_state = "fannypack_yellow" + +/obj/item/storage/belt/sabre + name = "sabre sheath" + desc = "An ornate sheath designed to hold an officer's blade." + icon_state = "sheath" + inhand_icon_state = "sheath" + worn_icon_state = "sheath" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/belt/sabre/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 1 + STR.rustle_sound = FALSE + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.set_holdable(list( + /obj/item/melee/sabre + )) + +/obj/item/storage/belt/sabre/examine(mob/user) + . = ..() + if(length(contents)) + . += "Alt-click it to quickly draw the blade." + +/obj/item/storage/belt/sabre/AltClick(mob/user) + if(!iscarbon(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + if(length(contents)) + var/obj/item/I = contents[1] + user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].") + user.put_in_hands(I) + update_icon() + else + to_chat(user, "[src] is empty!") + +/obj/item/storage/belt/sabre/update_icon_state() + icon_state = initial(inhand_icon_state) + inhand_icon_state = initial(inhand_icon_state) + worn_icon_state = initial(worn_icon_state) + if(contents.len) + icon_state += "-sabre" + inhand_icon_state += "-sabre" + worn_icon_state += "-sabre" + +/obj/item/storage/belt/sabre/PopulateContents() + new /obj/item/melee/sabre(src) + update_icon() + +/obj/item/storage/belt/plant + name = "botanical belt" + desc = "A belt used to hold most hydroponics supplies. Suprisingly, not green." + icon_state = "plantbelt" + inhand_icon_state = "plantbelt" + worn_icon_state = "plantbelt" + content_overlays = TRUE + +/obj/item/storage/belt/plant/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.set_holdable(list( + /obj/item/reagent_containers/spray/plantbgone, + /obj/item/plant_analyzer, + /obj/item/seeds, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/glass/beaker, + /obj/item/cultivator, + /obj/item/reagent_containers/spray/pestspray, + /obj/item/hatchet, + /obj/item/graft, + /obj/item/secateurs, + /obj/item/geneshears, + /obj/item/shovel/spade + )) diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index d7f84dd3044..fde6d8dbc05 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -1,1362 +1,1362 @@ -/* - * Everything derived from the common cardboard box. - * Basically everything except the original is a kit (starts full). - * - * Contains: - * Empty box, starter boxes (survival/engineer), - * Latex glove and sterile mask boxes, - * Syringe, beaker, dna injector boxes, - * Blanks, flashbangs, and EMP grenade boxes, - * Tracking and chemical implant boxes, - * Prescription glasses and drinking glass boxes, - * Condiment bottle and silly cup boxes, - * Donkpocket and monkeycube boxes, - * ID and security PDA cart boxes, - * Handcuff, mousetrap, and pillbottle boxes, - * Snap-pops and matchboxes, - * Replacement light boxes. - * Action Figure Boxes - * Various paper bags. - * - * For syndicate call-ins see uplink_kits.dm - */ - -/obj/item/storage/box - name = "box" - desc = "It's just an ordinary box." - icon_state = "box" - inhand_icon_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - resistance_flags = FLAMMABLE - drop_sound = 'sound/items/handling/cardboardbox_drop.ogg' - pickup_sound = 'sound/items/handling/cardboardbox_pickup.ogg' - var/foldable = /obj/item/stack/sheet/cardboard - var/illustration = "writing" - -/obj/item/storage/box/Initialize(mapload) - . = ..() - update_icon() - -/obj/item/storage/box/suicide_act(mob/living/carbon/user) - var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) - if(myhead) - user.visible_message("[user] puts [user.p_their()] head into \the [src], and begins closing it! It looks like [user.p_theyre()] trying to commit suicide!") - myhead.dismember() - myhead.forceMove(src)//force your enemies to kill themselves with your head collection box! - playsound(user, "desecration-01.ogg", 50, TRUE, -1) - return BRUTELOSS - user.visible_message("[user] beating [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/box/update_overlays() - . = ..() - if(illustration) - . += illustration - -/obj/item/storage/box/attack_self(mob/user) - ..() - - if(!foldable) - return - if(contents.len) - to_chat(user, "You can't fold this box with items still inside!") - return - if(!ispath(foldable)) - return - - to_chat(user, "You fold [src] flat.") - var/obj/item/I = new foldable - qdel(src) - user.put_in_hands(I) - -/obj/item/storage/box/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stack/package_wrap)) - return 0 - return ..() - -//Mime spell boxes - -/obj/item/storage/box/mime - name = "invisible box" - desc = "Unfortunately not large enough to trap the mime." - foldable = null - icon_state = "box" - inhand_icon_state = null - alpha = 0 - -/obj/item/storage/box/mime/attack_hand(mob/user) - ..() - if(user.mind.miming) - alpha = 255 - -/obj/item/storage/box/mime/Moved(oldLoc, dir) - if (iscarbon(oldLoc)) - alpha = 0 - ..() - -//Disk boxes - -/obj/item/storage/box/disks - name = "diskette box" - illustration = "disk_kit" - -/obj/item/storage/box/disks/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/data(src) - -/obj/item/storage/box/disks_nanite - name = "nanite program disks box" - illustration = "disk_kit" - -/obj/item/storage/box/disks_nanite/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/nanite_program(src) - -// Ordinary survival box -/obj/item/storage/box/survival - var/mask_type = /obj/item/clothing/mask/breath - var/internal_type = /obj/item/tank/internals/emergency_oxygen - var/medipen_type = /obj/item/reagent_containers/hypospray/medipen - -/obj/item/storage/box/survival/PopulateContents() - new mask_type(src) - if(!isnull(medipen_type)) - new medipen_type(src) - - if(!isplasmaman(loc)) - new internal_type(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/survival/radio/PopulateContents() - ..() // we want the survival stuff too. - new /obj/item/radio/off(src) - -// Mining survival box -/obj/item/storage/box/survival/mining - mask_type = /obj/item/clothing/mask/gas/explorer - -/obj/item/storage/box/survival/mining/PopulateContents() - ..() - new /obj/item/crowbar/red(src) - -// Engineer survival box -/obj/item/storage/box/survival/engineer - internal_type = /obj/item/tank/internals/emergency_oxygen/engi - -/obj/item/storage/box/survival/engineer/radio/PopulateContents() - ..() // we want the regular items too. - new /obj/item/radio/off(src) - -// Syndie survival box -/obj/item/storage/box/survival/syndie - mask_type = /obj/item/clothing/mask/gas/syndicate - internal_type = /obj/item/tank/internals/emergency_oxygen/engi - medipen_type = null - -// Security survival box -/obj/item/storage/box/survival/security - mask_type = /obj/item/clothing/mask/gas/sechailer - -/obj/item/storage/box/survival/security/radio/PopulateContents() - ..() // we want the regular stuff too - new /obj/item/radio/off(src) - -// Medical survival box -/obj/item/storage/box/survival/medical - mask_type = /obj/item/clothing/mask/breath/medical - -/obj/item/storage/box/gloves - name = "box of latex gloves" - desc = "Contains sterile latex gloves." - illustration = "latex" - -/obj/item/storage/box/gloves/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/gloves/color/latex(src) - -/obj/item/storage/box/masks - name = "box of sterile masks" - desc = "This box contains sterile medical masks." - illustration = "sterile" - -/obj/item/storage/box/masks/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/mask/surgical(src) - -/obj/item/storage/box/syringes - name = "box of syringes" - desc = "A box full of syringes." - illustration = "syringe" - -/obj/item/storage/box/syringes/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/syringe(src) - -/obj/item/storage/box/syringes/variety - name = "syringe variety box" - -/obj/item/storage/box/syringes/variety/PopulateContents() - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/syringe/lethal(src) - new /obj/item/reagent_containers/syringe/piercing(src) - new /obj/item/reagent_containers/syringe/bluespace(src) - -/obj/item/storage/box/medipens - name = "box of medipens" - desc = "A box full of epinephrine MediPens." - illustration = "syringe" - -/obj/item/storage/box/medipens/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/hypospray/medipen(src) - -/obj/item/storage/box/medipens/utility - name = "stimpack value kit" - desc = "A box with several stimpack medipens for the economical miner." - illustration = "syringe" - -/obj/item/storage/box/medipens/utility/PopulateContents() - ..() // includes regular medipens. - for(var/i in 1 to 5) - new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) - -/obj/item/storage/box/beakers - name = "box of beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker( src ) - -/obj/item/storage/box/beakers/bluespace - name = "box of bluespace beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/bluespace/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/beakers/variety - name = "beaker variety box" - -/obj/item/storage/box/beakers/variety/PopulateContents() - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/beaker/large(src) - new /obj/item/reagent_containers/glass/beaker/plastic(src) - new /obj/item/reagent_containers/glass/beaker/meta(src) - new /obj/item/reagent_containers/glass/beaker/noreact(src) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/medigels - name = "box of medical gels" - desc = "A box full of medical gel applicators, with unscrewable caps and precision spray heads." - illustration = "medgel" - -/obj/item/storage/box/medigels/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/medigel( src ) - -/obj/item/storage/box/injectors - name = "box of DNA injectors" - desc = "This box contains injectors, it seems." - illustration = "dna" - -/obj/item/storage/box/injectors/PopulateContents() - var/static/items_inside = list( - /obj/item/dnainjector/h2m = 3, - /obj/item/dnainjector/m2h = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/flashbangs - name = "box of flashbangs (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." - icon_state = "secbox" - illustration = "flashbang" - -/obj/item/storage/box/flashbangs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/flashbang(src) - -/obj/item/storage/box/stingbangs - name = "box of stingbangs (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause severe injuries or death in repeated use." - icon_state = "secbox" - illustration = "flashbang" - -/obj/item/storage/box/stingbangs/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/grenade/stingbang(src) - -/obj/item/storage/box/flashes - name = "box of flashbulbs" - desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." - icon_state = "secbox" - illustration = "flash" - -/obj/item/storage/box/flashes/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/assembly/flash/handheld(src) - -/obj/item/storage/box/wall_flash - name = "wall-mounted flash kit" - desc = "This box contains everything necessary to build a wall-mounted flash. WARNING: Flashes can cause serious eye damage, protective eyewear is required." - icon_state = "secbox" - illustration = "flash" - -/obj/item/storage/box/wall_flash/PopulateContents() - var/id = rand(1000, 9999) - // FIXME what if this conflicts with an existing one? - - new /obj/item/wallframe/button(src) - new /obj/item/electronics/airlock(src) - var/obj/item/assembly/control/flasher/remote = new(src) - remote.id = id - var/obj/item/wallframe/flasher/frame = new(src) - frame.id = id - new /obj/item/assembly/flash/handheld(src) - new /obj/item/screwdriver(src) - - -/obj/item/storage/box/teargas - name = "box of tear gas grenades (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause blindness and skin irritation." - icon_state = "secbox" - illustration = "grenade" - -/obj/item/storage/box/teargas/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/chem_grenade/teargas(src) - -/obj/item/storage/box/emps - name = "box of emp grenades" - desc = "A box with 5 emp grenades." - illustration = "emp" - -/obj/item/storage/box/emps/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/grenade/empgrenade(src) - -/obj/item/storage/box/trackimp - name = "boxed tracking implant kit" - desc = "Box full of scum-bag tracking utensils." - icon_state = "secbox" - illustration = "implant" - -/obj/item/storage/box/trackimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/tracking = 4, - /obj/item/implanter = 1, - /obj/item/implantpad = 1, - /obj/item/locator = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/minertracker - name = "boxed tracking implant kit" - desc = "For finding those who have died on the accursed lavaworld." - illustration = "implant" - -/obj/item/storage/box/minertracker/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/tracking = 3, - /obj/item/implanter = 1, - /obj/item/implantpad = 1, - /obj/item/locator = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/chemimp - name = "boxed chemical implant kit" - desc = "Box of stuff used to implant chemicals." - illustration = "implant" - -/obj/item/storage/box/chemimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/chem = 5, - /obj/item/implanter = 1, - /obj/item/implantpad = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/exileimp - name = "boxed exile implant kit" - desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." - illustration = "implant" - -/obj/item/storage/box/exileimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/exile = 5, - /obj/item/implanter = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/bodybags - name = "body bags" - desc = "The label indicates that it contains body bags." - illustration = "bodybags" - -/obj/item/storage/box/bodybags/PopulateContents() - ..() - for(var/i in 1 to 7) - new /obj/item/bodybag(src) - -/obj/item/storage/box/rxglasses - name = "box of prescription glasses" - desc = "This box contains nerd glasses." - illustration = "glasses" - -/obj/item/storage/box/rxglasses/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/glasses/regular(src) - -/obj/item/storage/box/drinkingglasses - name = "box of drinking glasses" - desc = "It has a picture of drinking glasses on it." - illustration = "drinkglass" - -/obj/item/storage/box/drinkingglasses/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - -/obj/item/storage/box/condimentbottles - name = "box of condiment bottles" - desc = "It has a large ketchup smear on it." - illustration = "condiment" - -/obj/item/storage/box/condimentbottles/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/condiment(src) - -/obj/item/storage/box/cups - name = "box of paper cups" - desc = "It has pictures of paper cups on the front." - illustration = "cup" - -/obj/item/storage/box/cups/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - -/obj/item/storage/box/donkpockets - name = "box of donk-pockets" - desc = "Instructions: Heat in microwave. Product will cool if not eaten within seven minutes." - icon_state = "donkpocketbox" - illustration=null - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket - -/obj/item/storage/box/donkpockets/PopulateContents() - for(var/i in 1 to 6) - new donktype(src) - -/obj/item/storage/box/donkpockets/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donkpocket)) - -/obj/item/storage/box/donkpockets/donkpocketspicy - name = "box of spicy-flavoured donk-pockets" - icon_state = "donkpocketboxspicy" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/spicy - -/obj/item/storage/box/donkpockets/donkpocketteriyaki - name = "box of teriyaki-flavoured donk-pockets" - icon_state = "donkpocketboxteriyaki" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki - -/obj/item/storage/box/donkpockets/donkpocketpizza - name = "box of pizza-flavoured donk-pockets" - icon_state = "donkpocketboxpizza" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/pizza - -/obj/item/storage/box/donkpockets/donkpocketgondola - name = "box of gondola-flavoured donk-pockets" - icon_state = "donkpocketboxgondola" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/gondola - -/obj/item/storage/box/donkpockets/donkpocketberry - name = "box of berry-flavoured donk-pockets" - icon_state = "donkpocketboxberry" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/berry - -/obj/item/storage/box/donkpockets/donkpockethonk - name = "box of banana-flavoured donk-pockets" - icon_state = "donkpocketboxbanana" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/honk - -/obj/item/storage/box/monkeycubes - name = "monkey cube box" - desc = "Drymate brand monkey cubes. Just add water!" - icon_state = "monkeycubebox" - illustration = null - var/cube_type = /obj/item/reagent_containers/food/snacks/monkeycube - -/obj/item/storage/box/monkeycubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 7 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/monkeycubes/PopulateContents() - for(var/i in 1 to 5) - new cube_type(src) - -/obj/item/storage/box/monkeycubes/syndicate - desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" - cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate - -/obj/item/storage/box/gorillacubes - name = "gorilla cube box" - desc = "Waffle Co. brand gorilla cubes. Do not taunt." - icon_state = "monkeycubebox" - illustration = null - -/obj/item/storage/box/gorillacubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 3 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/gorillacubes/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/monkeycube/gorilla(src) - -/obj/item/storage/box/ids - name = "box of spare IDs" - desc = "Has so many empty IDs." - illustration = "id" - -/obj/item/storage/box/ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id(src) - -//Some spare PDAs in a box -/obj/item/storage/box/pdas - name = "spare PDAs" - desc = "A box of spare PDA microcomputers." - illustration = "pda" - -/obj/item/storage/box/pdas/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/pda(src) - new /obj/item/cartridge/head(src) - - var/newcart = pick( /obj/item/cartridge/engineering, - /obj/item/cartridge/security, - /obj/item/cartridge/medical, - /obj/item/cartridge/signal/toxins, - /obj/item/cartridge/quartermaster) - new newcart(src) - -/obj/item/storage/box/silver_ids - name = "box of spare silver IDs" - desc = "Shiny IDs for important people." - illustration = "id" - -/obj/item/storage/box/silver_ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id/silver(src) - -/obj/item/storage/box/prisoner - name = "box of prisoner IDs" - desc = "Take away their last shred of dignity, their name." - icon_state = "secbox" - illustration = "id" - -/obj/item/storage/box/prisoner/PopulateContents() - ..() - new /obj/item/card/id/prisoner/one(src) - new /obj/item/card/id/prisoner/two(src) - new /obj/item/card/id/prisoner/three(src) - new /obj/item/card/id/prisoner/four(src) - new /obj/item/card/id/prisoner/five(src) - new /obj/item/card/id/prisoner/six(src) - new /obj/item/card/id/prisoner/seven(src) - -/obj/item/storage/box/seccarts - name = "box of PDA security cartridges" - desc = "A box full of PDA cartridges used by Security." - icon_state = "secbox" - illustration = "pda" - -/obj/item/storage/box/seccarts/PopulateContents() - new /obj/item/cartridge/detective(src) - for(var/i in 1 to 6) - new /obj/item/cartridge/security(src) - -/obj/item/storage/box/firingpins - name = "box of standard firing pins" - desc = "A box full of standard firing pins, to allow newly-developed firearms to operate." - icon_state = "secbox" - illustration = "firingpin" - -/obj/item/storage/box/firingpins/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/firing_pin(src) - -/obj/item/storage/box/firingpins/paywall - name = "box of paywall firing pins" - desc = "A box full of paywall firing pins, to allow newly-developed firearms to operate behind a custom-set paywall." - illustration = "firingpin" - -/obj/item/storage/box/firingpins/paywall/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/firing_pin/paywall(src) - -/obj/item/storage/box/lasertagpins - name = "box of laser tag firing pins" - desc = "A box full of laser tag firing pins, to allow newly-developed firearms to require wearing brightly coloured plastic armor before being able to be used." - illustration = "firingpin" - -/obj/item/storage/box/lasertagpins/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/firing_pin/tag/red(src) - new /obj/item/firing_pin/tag/blue(src) - -/obj/item/storage/box/handcuffs - name = "box of spare handcuffs" - desc = "A box full of handcuffs." - icon_state = "secbox" - illustration = "handcuff" - -/obj/item/storage/box/handcuffs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs(src) - -/obj/item/storage/box/zipties - name = "box of spare zipties" - desc = "A box full of zipties." - icon_state = "secbox" - illustration = "handcuff" - -/obj/item/storage/box/zipties/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs/cable/zipties(src) - -/obj/item/storage/box/alienhandcuffs - name = "box of spare handcuffs" - desc = "A box full of handcuffs." - icon_state = "alienbox" - illustration = "handcuff" - -/obj/item/storage/box/alienhandcuffs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs/alien(src) - -/obj/item/storage/box/fakesyndiesuit - name = "boxed space suit and helmet" - desc = "A sleek, sturdy box used to hold replica spacesuits." - icon_state = "syndiebox" - illustration = "syndiesuit" - -/obj/item/storage/box/fakesyndiesuit/PopulateContents() - new /obj/item/clothing/head/syndicatefake(src) - new /obj/item/clothing/suit/syndicatefake(src) - -/obj/item/storage/box/mousetraps - name = "box of Pest-B-Gon mousetraps" - desc = "Keep out of reach of children." - illustration = "mousetrap" - -/obj/item/storage/box/mousetraps/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/assembly/mousetrap(src) - -/obj/item/storage/box/pillbottles - name = "box of pill bottles" - desc = "It has pictures of pill bottles on its front." - illustration = "pillbox" - -/obj/item/storage/box/pillbottles/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/storage/pill_bottle(src) - -/obj/item/storage/box/snappops - name = "snap pop box" - desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." - icon = 'icons/obj/toy.dmi' - icon_state = "spbox" - -/obj/item/storage/box/snappops/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list(/obj/item/toy/snappop)) - STR.max_items = 8 - -/obj/item/storage/box/snappops/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) - -/obj/item/storage/box/matches - name = "matchbox" - desc = "A small box of Almost But Not Quite Plasma Premium Matches." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "matchbox" - inhand_icon_state = "zippo" - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BELT - drop_sound = 'sound/items/handling/matchbox_drop.ogg' - pickup_sound = 'sound/items/handling/matchbox_pickup.ogg' - custom_price = 20 - -/obj/item/storage/box/matches/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.set_holdable(list(/obj/item/match)) - -/obj/item/storage/box/matches/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) - -/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/match)) - W.matchignite() - -/obj/item/storage/box/lights - name = "box of replacement bulbs" - icon = 'icons/obj/storage.dmi' - illustration = "light" - desc = "This box is shaped on the inside so that only light tubes and bulbs fit." - inhand_icon_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - foldable = /obj/item/stack/sheet/cardboard //BubbleWrap - -/obj/item/storage/box/lights/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 21 - STR.set_holdable(list(/obj/item/light/tube, /obj/item/light/bulb)) - STR.max_combined_w_class = 21 - STR.click_gather = FALSE //temp workaround to re-enable filling the light replacer with the box - -/obj/item/storage/box/lights/bulbs/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/bulb(src) - -/obj/item/storage/box/lights/tubes - name = "box of replacement tubes" - illustration = "lighttube" - -/obj/item/storage/box/lights/tubes/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/tube(src) - -/obj/item/storage/box/lights/mixed - name = "box of replacement lights" - illustration = "lightmixed" - -/obj/item/storage/box/lights/mixed/PopulateContents() - for(var/i in 1 to 14) - new /obj/item/light/tube(src) - for(var/i in 1 to 7) - new /obj/item/light/bulb(src) - - -/obj/item/storage/box/deputy - name = "box of deputy armbands" - desc = "To be issued to those authorized to act as deputy of security." - icon_state = "secbox" - illustration = "depband" - -/obj/item/storage/box/deputy/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/accessory/armband/deputy(src) - -/obj/item/storage/box/metalfoam - name = "box of metal foam grenades" - desc = "To be used to rapidly seal hull breaches." - illustration = "grenade" - -/obj/item/storage/box/metalfoam/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/chem_grenade/metalfoam(src) - -/obj/item/storage/box/smart_metal_foam - name = "box of smart metal foam grenades" - desc = "Used to rapidly seal hull breaches. This variety conforms to the walls of its area." - illustration = "grenade" - -/obj/item/storage/box/smart_metal_foam/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/chem_grenade/smart_metal_foam(src) - -/obj/item/storage/box/hug - name = "box of hugs" - desc = "A special box for sensitive people." - icon_state = "hugbox" - illustration = "heart" - foldable = null - -/obj/item/storage/box/hug/suicide_act(mob/user) - user.visible_message("[user] clamps the box of hugs on [user.p_their()] jugular! Guess it wasn't such a hugbox after all..") - return (BRUTELOSS) - -/obj/item/storage/box/hug/attack_self(mob/user) - ..() - user.changeNext_move(CLICK_CD_MELEE) - playsound(loc, "rustle", 50, TRUE, -5) - user.visible_message("[user] hugs \the [src].","You hug \the [src].") - -/////clown box & honkbot assembly -/obj/item/storage/box/clown - name = "clown box" - desc = "A colorful cardboard box for the clown" - illustration = "clown" - -/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) - if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) - if(contents.len) //prevent accidently deleting contents - to_chat(user, "You need to empty [src] out first!") - return - if(!user.temporarilyRemoveItemFromInventory(I)) - return - qdel(I) - to_chat(user, "You add some wheels to the [src]! You've got a honkbot assembly now! Honk!") - var/obj/item/bot_assembly/honkbot/A = new - qdel(src) - user.put_in_hands(A) - else - return ..() - -////// -/obj/item/storage/box/hug/medical/PopulateContents() - new /obj/item/stack/medical/bruise_pack(src) - new /obj/item/stack/medical/ointment(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - -// Clown survival box -/obj/item/storage/box/hug/survival/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/rubbershot - name = "box of rubber shots" - desc = "A box full of rubber shots, designed for riot shotguns." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/rubbershot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/rubbershot(src) - -/obj/item/storage/box/lethalshot - name = "box of lethal shotgun shots" - desc = "A box full of lethal shots, designed for riot shotguns." - icon_state = "lethalshot_box" - illustration = null - -/obj/item/storage/box/lethalshot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/buckshot(src) - -/obj/item/storage/box/beanbag - name = "box of beanbags" - desc = "A box full of beanbag shells." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/beanbag/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/ammo_casing/shotgun/beanbag(src) - -/obj/item/storage/box/actionfigure - name = "box of action figures" - desc = "The latest set of collectable action figures." - icon_state = "box" - -/obj/item/storage/box/actionfigure/PopulateContents() - for(var/i in 1 to 4) - var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) - new randomFigure(src) - -/obj/item/storage/box/papersack - name = "paper sack" - desc = "A sack neatly crafted out of paper." - icon_state = "paperbag_None" - inhand_icon_state = "paperbag_None" - illustration = null - resistance_flags = FLAMMABLE - foldable = null - /// A list of all available papersack reskins - var/list/papersack_designs = list() - -/obj/item/storage/box/papersack/Initialize(mapload) - . = ..() - papersack_designs = sortList(list( - "None" = image(icon = src.icon, icon_state = "paperbag_None"), - "NanotrasenStandard" = image(icon = src.icon, icon_state = "paperbag_NanotrasenStandard"), - "SyndiSnacks" = image(icon = src.icon, icon_state = "paperbag_SyndiSnacks"), - "Heart" = image(icon = src.icon, icon_state = "paperbag_Heart"), - "SmileyFace" = image(icon = src.icon, icon_state = "paperbag_SmileyFace") - )) - -/obj/item/storage/box/papersack/update_icon_state() - if(contents.len == 0) - icon_state = "[inhand_icon_state]" - else - icon_state = "[inhand_icon_state]_closed" - -/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - var/choice = show_radial_menu(user, src , papersack_designs, custom_check = CALLBACK(src, .proc/check_menu, user, W), radius = 36, require_near = TRUE) - if(!choice) - return FALSE - if(icon_state == "paperbag_[choice]") - return FALSE - switch(choice) - if("None") - desc = "A sack neatly crafted out of paper." - if("NanotrasenStandard") - desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." - if("SyndiSnacks") - desc = "The design on this paper sack is a remnant of the notorious 'SyndieSnacks' program." - if("Heart") - desc = "A paper sack with a heart etched onto the side." - if("SmileyFace") - desc = "A paper sack with a crude smile etched onto the side." - else - return FALSE - to_chat(user, "You make some modifications to [src] using your pen.") - icon_state = "paperbag_[choice]" - inhand_icon_state = "paperbag_[choice]" - return FALSE - else if(W.get_sharpness()) - if(!contents.len) - if(inhand_icon_state == "paperbag_None") - user.show_message("You cut eyeholes into [src].", MSG_VISUAL) - new /obj/item/clothing/head/papersack(user.loc) - qdel(src) - return FALSE - else if(inhand_icon_state == "paperbag_SmileyFace") - user.show_message("You cut eyeholes into [src] and modify the design.", MSG_VISUAL) - new /obj/item/clothing/head/papersack/smiley(user.loc) - qdel(src) - return FALSE - return ..() - -/** - * check_menu: Checks if we are allowed to interact with a radial menu - * - * Arguments: - * * user The mob interacting with a menu - * * P The pen used to interact with a menu - */ -/obj/item/storage/box/papersack/proc/check_menu(mob/user, obj/item/pen/P) - if(!istype(user)) - return FALSE - if(user.incapacitated()) - return FALSE - if(contents.len) - to_chat(user, "You can't modify [src] with items still inside!") - return FALSE - if(!P || !user.is_holding(P)) - to_chat(user, "You need a pen to modify [src]!") - return FALSE - return TRUE - -/obj/item/storage/box/papersack/meat - desc = "It's slightly moist and smells like a slaughterhouse." - -/obj/item/storage/box/papersack/meat/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/food/snacks/meat/slab(src) - -/obj/item/storage/box/ingredients //This box is for the randomely chosen version the chef spawns with, it shouldn't actually exist. - name = "ingredients box" - illustration = "fruit" - var/theme_name - -/obj/item/storage/box/ingredients/Initialize() - . = ..() - if(theme_name) - name = "[name] ([theme_name])" - desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." - inhand_icon_state = "syringe_kit" - -/obj/item/storage/box/ingredients/wildcard - theme_name = "wildcard" - -/obj/item/storage/box/ingredients/wildcard/PopulateContents() - for(var/i in 1 to 7) - var/randomFood = pick(/obj/item/reagent_containers/food/snacks/grown/chili, - /obj/item/reagent_containers/food/snacks/grown/tomato, - /obj/item/reagent_containers/food/snacks/grown/carrot, - /obj/item/reagent_containers/food/snacks/grown/potato, - /obj/item/reagent_containers/food/snacks/grown/potato/sweet, - /obj/item/reagent_containers/food/snacks/grown/apple, - /obj/item/reagent_containers/food/snacks/chocolatebar, - /obj/item/reagent_containers/food/snacks/grown/cherries, - /obj/item/reagent_containers/food/snacks/grown/banana, - /obj/item/reagent_containers/food/snacks/grown/cabbage, - /obj/item/reagent_containers/food/snacks/grown/soybeans, - /obj/item/reagent_containers/food/snacks/grown/corn, - /obj/item/reagent_containers/food/snacks/grown/mushroom/plumphelmet, - /obj/item/reagent_containers/food/snacks/grown/mushroom/chanterelle) - new randomFood(src) - -/obj/item/storage/box/ingredients/fiesta - theme_name = "fiesta" - -/obj/item/storage/box/ingredients/fiesta/PopulateContents() - new /obj/item/reagent_containers/food/snacks/tortilla(src) - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - -/obj/item/storage/box/ingredients/italian - theme_name = "italian" - -/obj/item/storage/box/ingredients/italian/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - new /obj/item/reagent_containers/food/drinks/bottle/wine(src) - -/obj/item/storage/box/ingredients/vegetarian - theme_name = "vegetarian" - -/obj/item/storage/box/ingredients/vegetarian/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/carrot(src) - new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - -/obj/item/storage/box/ingredients/american - theme_name = "american" - -/obj/item/storage/box/ingredients/american/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - -/obj/item/storage/box/ingredients/fruity - theme_name = "fruity" - -/obj/item/storage/box/ingredients/fruity/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) - new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) - -/obj/item/storage/box/ingredients/sweets - theme_name = "sweets" - -/obj/item/storage/box/ingredients/sweets/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/cherries(src) - new /obj/item/reagent_containers/food/snacks/grown/banana(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - -/obj/item/storage/box/ingredients/delights - theme_name = "delights" - -/obj/item/storage/box/ingredients/delights/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) - new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) - new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/berries(src) - -/obj/item/storage/box/ingredients/grains - theme_name = "grains" - -/obj/item/storage/box/ingredients/grains/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/grown/oat(src) - new /obj/item/reagent_containers/food/snacks/grown/wheat(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/honeycomb(src) - new /obj/item/seeds/poppy(src) - -/obj/item/storage/box/ingredients/carnivore - theme_name = "carnivore" - -/obj/item/storage/box/ingredients/carnivore/PopulateContents() - new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) - new /obj/item/reagent_containers/food/snacks/spidereggs(src) - new /obj/item/reagent_containers/food/snacks/carpmeat(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - -/obj/item/storage/box/ingredients/exotic - theme_name = "exotic" - -/obj/item/storage/box/ingredients/exotic/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/carpmeat(src) - new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) - new /obj/item/reagent_containers/food/snacks/grown/cabbage(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - -/obj/item/storage/box/emptysandbags - name = "box of empty sandbags" - illustration = "sandbag" - -/obj/item/storage/box/emptysandbags/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/emptysandbag(src) - -/obj/item/storage/box/rndboards - name = "\proper the liberator's legacy" - desc = "A box containing a gift for worthy golems." - illustration = "scicircuit" - -/obj/item/storage/box/rndboards/PopulateContents() - new /obj/item/circuitboard/machine/protolathe(src) - new /obj/item/circuitboard/machine/destructive_analyzer(src) - new /obj/item/circuitboard/machine/circuit_imprinter(src) - new /obj/item/circuitboard/computer/rdconsole(src) - -/obj/item/storage/box/silver_sulf - name = "box of silver sulfadiazine patches" - desc = "Contains patches used to treat burns." - illustration = "firepatch" - -/obj/item/storage/box/silver_sulf/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/patch/aiuri(src) - -/obj/item/storage/box/fountainpens - name = "box of fountain pens" - illustration = "fpen" - -/obj/item/storage/box/fountainpens/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/pen/fountain(src) - -/obj/item/storage/box/holy_grenades - name = "box of holy hand grenades" - desc = "Contains several grenades used to rapidly purge heresy." - illustration = "grenade" - -/obj/item/storage/box/holy_grenades/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/chem_grenade/holy(src) - -/obj/item/storage/box/stockparts/basic //for ruins where it's a bad idea to give access to an autolathe/protolathe, but still want to make stock parts accessible - name = "box of stock parts" - desc = "Contains a variety of basic stock parts." - -/obj/item/storage/box/stockparts/basic/PopulateContents() - var/static/items_inside = list( - /obj/item/stock_parts/capacitor = 3, - /obj/item/stock_parts/scanning_module = 3, - /obj/item/stock_parts/manipulator = 3, - /obj/item/stock_parts/micro_laser = 3, - /obj/item/stock_parts/matter_bin = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/stockparts/deluxe - name = "box of deluxe stock parts" - desc = "Contains a variety of deluxe stock parts." - icon_state = "syndiebox" - -/obj/item/storage/box/stockparts/deluxe/PopulateContents() - var/static/items_inside = list( - /obj/item/stock_parts/capacitor/quadratic = 3, - /obj/item/stock_parts/scanning_module/triphasic = 3, - /obj/item/stock_parts/manipulator/femto = 3, - /obj/item/stock_parts/micro_laser/quadultra = 3, - /obj/item/stock_parts/matter_bin/bluespace = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/dishdrive - name = "DIY Dish Drive Kit" - desc = "Contains everything you need to build your own Dish Drive!" - custom_premium_price = 1000 - -/obj/item/storage/box/dishdrive/PopulateContents() - var/static/items_inside = list( - /obj/item/stack/sheet/metal/five = 1, - /obj/item/stack/cable_coil/five = 1, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/matter_bin = 2, - /obj/item/screwdriver = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/material - name = "box of materials" - illustration = "implant" - -/obj/item/storage/box/material/PopulateContents() //less uranium because radioactive - var/static/items_inside = list( - /obj/item/stack/sheet/metal/fifty=1,\ - /obj/item/stack/sheet/glass/fifty=1,\ - /obj/item/stack/sheet/rglass=50,\ - /obj/item/stack/sheet/plasmaglass=50,\ - /obj/item/stack/sheet/titaniumglass=50,\ - /obj/item/stack/sheet/plastitaniumglass=50,\ - /obj/item/stack/sheet/plasteel=50,\ - /obj/item/stack/sheet/mineral/plastitanium=50,\ - /obj/item/stack/sheet/mineral/titanium=50,\ - /obj/item/stack/sheet/mineral/gold=50,\ - /obj/item/stack/sheet/mineral/silver=50,\ - /obj/item/stack/sheet/mineral/plasma=50,\ - /obj/item/stack/sheet/mineral/uranium=20,\ - /obj/item/stack/sheet/mineral/diamond=50,\ - /obj/item/stack/sheet/bluespace_crystal=50,\ - /obj/item/stack/sheet/mineral/bananium=50,\ - /obj/item/stack/sheet/mineral/wood=50,\ - /obj/item/stack/sheet/plastic/fifty=1,\ - /obj/item/stack/sheet/runed_metal/fifty=1 - ) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/debugtools - name = "box of debug tools" - icon_state = "syndiebox" - -/obj/item/storage/box/debugtools/PopulateContents() - var/static/items_inside = list( - /obj/item/flashlight/emp/debug=1,\ - /obj/item/pda=1,\ - /obj/item/modular_computer/tablet/preset/advanced=1,\ - /obj/item/geiger_counter=1,\ - /obj/item/construction/rcd/combat/admin=1,\ - /obj/item/pipe_dispenser=1,\ - /obj/item/card/emag=1,\ - /obj/item/stack/spacecash/c1000=50,\ - /obj/item/healthanalyzer/advanced=1,\ - /obj/item/disk/tech_disk/debug=1,\ - /obj/item/uplink/debug=1,\ - /obj/item/uplink/nuclear/debug=1,\ - /obj/item/storage/box/beakers/bluespace=1,\ - /obj/item/storage/box/beakers/variety=1,\ - /obj/item/storage/box/material=1 - ) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/plastic - name = "plastic box" - desc = "It's a solid, plastic shell box." - icon_state = "plasticbox" - foldable = null - illustration = "writing" - custom_materials = list(/datum/material/plastic = 1000) //You lose most if recycled. - - -/obj/item/storage/box/fireworks - name = "box of fireworks" - desc = "Contains an assortment of fireworks." - illustration = "sparkler" - -/obj/item/storage/box/fireworks/PopulateContents() - for(var/i in 1 to 3) - new/obj/item/sparkler(src) - new/obj/item/grenade/firecracker(src) - new /obj/item/toy/snappop(src) - -/obj/item/storage/box/fireworks/dangerous - -/obj/item/storage/box/fireworks/dangerous/PopulateContents() - for(var/i in 1 to 3) - new/obj/item/sparkler(src) - new/obj/item/grenade/firecracker(src) - if(prob(20)) - new /obj/item/grenade/frag(src) - else - new /obj/item/toy/snappop(src) - -/obj/item/storage/box/firecrackers - name = "box of firecrackers" - desc = "A box filled with illegal firecracker. You wonder who still makes these." - icon_state = "syndiebox" - illustration = "firecracker" - -/obj/item/storage/box/firecrackers/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/firecracker(src) - -/obj/item/storage/box/sparklers - name = "box of sparklers" - desc = "A box of NT brand sparklers, burns hot even in the cold of space-winter." - illustration = "sparkler" - -/obj/item/storage/box/sparklers/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/sparkler(src) - -/obj/item/storage/box/gum - name = "bubblegum packet" - desc = "The packaging is entirely in japanese, apparently. You can't make out a single word of it." - icon_state = "bubblegum_generic" - w_class = WEIGHT_CLASS_TINY - illustration = null - foldable = null - custom_price = 120 - -/obj/item/storage/box/gum/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/chewable/bubblegum)) - STR.max_items = 4 - -/obj/item/storage/box/gum/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum(src) - -/obj/item/storage/box/gum/nicotine - name = "nicotine gum packet" - desc = "Designed to help with nicotine addiction and oral fixation all at once without destroying your lungs in the process. Mint flavored!" - icon_state = "bubblegum_nicotine" - custom_premium_price = 275 - -/obj/item/storage/box/gum/nicotine/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/nicotine(src) - -/obj/item/storage/box/gum/happiness - name = "HP+ gum packet" - desc = "A seemingly homemade packaging with an odd smell. It has a weird drawing of a smiling face sticking out its tongue." - icon_state = "bubblegum_happiness" - custom_price = 300 - custom_premium_price = 300 - -/obj/item/storage/box/gum/happiness/Initialize() - . = ..() - if (prob(25)) - desc += "You can faintly make out the word 'Hemopagopril' was once scribbled on it." - -/obj/item/storage/box/gum/happiness/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/happiness(src) - -/obj/item/storage/box/gum/bubblegum - name = "bubblegum gum packet" - desc = "The packaging is entirely in Demonic, apparently. You feel like even opening this would be a sin." - icon_state = "bubblegum_bubblegum" - -/obj/item/storage/box/gum/bubblegum/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/bubblegum(src) - -/obj/item/storage/box/shipping - name = "box of shipping supplies" - desc = "Contains several scanners and labelers for shipping things. Wrapping Paper not included." - illustration = "shipping" - -/obj/item/storage/box/shipping/PopulateContents() - var/static/items_inside = list( - /obj/item/dest_tagger=1,\ - /obj/item/sales_tagger=1,\ - /obj/item/export_scanner=1,\ - /obj/item/stack/package_wrap/small=2,\ - /obj/item/stack/wrapping_paper/small=1 - ) - generate_items_inside(items_inside,src) +/* + * Everything derived from the common cardboard box. + * Basically everything except the original is a kit (starts full). + * + * Contains: + * Empty box, starter boxes (survival/engineer), + * Latex glove and sterile mask boxes, + * Syringe, beaker, dna injector boxes, + * Blanks, flashbangs, and EMP grenade boxes, + * Tracking and chemical implant boxes, + * Prescription glasses and drinking glass boxes, + * Condiment bottle and silly cup boxes, + * Donkpocket and monkeycube boxes, + * ID and security PDA cart boxes, + * Handcuff, mousetrap, and pillbottle boxes, + * Snap-pops and matchboxes, + * Replacement light boxes. + * Action Figure Boxes + * Various paper bags. + * + * For syndicate call-ins see uplink_kits.dm + */ + +/obj/item/storage/box + name = "box" + desc = "It's just an ordinary box." + icon_state = "box" + inhand_icon_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + resistance_flags = FLAMMABLE + drop_sound = 'sound/items/handling/cardboardbox_drop.ogg' + pickup_sound = 'sound/items/handling/cardboardbox_pickup.ogg' + var/foldable = /obj/item/stack/sheet/cardboard + var/illustration = "writing" + +/obj/item/storage/box/Initialize(mapload) + . = ..() + update_icon() + +/obj/item/storage/box/suicide_act(mob/living/carbon/user) + var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) + if(myhead) + user.visible_message("[user] puts [user.p_their()] head into \the [src], and begins closing it! It looks like [user.p_theyre()] trying to commit suicide!") + myhead.dismember() + myhead.forceMove(src)//force your enemies to kill themselves with your head collection box! + playsound(user, "desecration-01.ogg", 50, TRUE, -1) + return BRUTELOSS + user.visible_message("[user] beating [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/box/update_overlays() + . = ..() + if(illustration) + . += illustration + +/obj/item/storage/box/attack_self(mob/user) + ..() + + if(!foldable) + return + if(contents.len) + to_chat(user, "You can't fold this box with items still inside!") + return + if(!ispath(foldable)) + return + + to_chat(user, "You fold [src] flat.") + var/obj/item/I = new foldable + qdel(src) + user.put_in_hands(I) + +/obj/item/storage/box/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stack/package_wrap)) + return 0 + return ..() + +//Mime spell boxes + +/obj/item/storage/box/mime + name = "invisible box" + desc = "Unfortunately not large enough to trap the mime." + foldable = null + icon_state = "box" + inhand_icon_state = null + alpha = 0 + +/obj/item/storage/box/mime/attack_hand(mob/user) + ..() + if(user.mind.miming) + alpha = 255 + +/obj/item/storage/box/mime/Moved(oldLoc, dir) + if (iscarbon(oldLoc)) + alpha = 0 + ..() + +//Disk boxes + +/obj/item/storage/box/disks + name = "diskette box" + illustration = "disk_kit" + +/obj/item/storage/box/disks/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/data(src) + +/obj/item/storage/box/disks_nanite + name = "nanite program disks box" + illustration = "disk_kit" + +/obj/item/storage/box/disks_nanite/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/nanite_program(src) + +// Ordinary survival box +/obj/item/storage/box/survival + var/mask_type = /obj/item/clothing/mask/breath + var/internal_type = /obj/item/tank/internals/emergency_oxygen + var/medipen_type = /obj/item/reagent_containers/hypospray/medipen + +/obj/item/storage/box/survival/PopulateContents() + new mask_type(src) + if(!isnull(medipen_type)) + new medipen_type(src) + + if(!isplasmaman(loc)) + new internal_type(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/survival/radio/PopulateContents() + ..() // we want the survival stuff too. + new /obj/item/radio/off(src) + +// Mining survival box +/obj/item/storage/box/survival/mining + mask_type = /obj/item/clothing/mask/gas/explorer + +/obj/item/storage/box/survival/mining/PopulateContents() + ..() + new /obj/item/crowbar/red(src) + +// Engineer survival box +/obj/item/storage/box/survival/engineer + internal_type = /obj/item/tank/internals/emergency_oxygen/engi + +/obj/item/storage/box/survival/engineer/radio/PopulateContents() + ..() // we want the regular items too. + new /obj/item/radio/off(src) + +// Syndie survival box +/obj/item/storage/box/survival/syndie + mask_type = /obj/item/clothing/mask/gas/syndicate + internal_type = /obj/item/tank/internals/emergency_oxygen/engi + medipen_type = null + +// Security survival box +/obj/item/storage/box/survival/security + mask_type = /obj/item/clothing/mask/gas/sechailer + +/obj/item/storage/box/survival/security/radio/PopulateContents() + ..() // we want the regular stuff too + new /obj/item/radio/off(src) + +// Medical survival box +/obj/item/storage/box/survival/medical + mask_type = /obj/item/clothing/mask/breath/medical + +/obj/item/storage/box/gloves + name = "box of latex gloves" + desc = "Contains sterile latex gloves." + illustration = "latex" + +/obj/item/storage/box/gloves/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/gloves/color/latex(src) + +/obj/item/storage/box/masks + name = "box of sterile masks" + desc = "This box contains sterile medical masks." + illustration = "sterile" + +/obj/item/storage/box/masks/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/mask/surgical(src) + +/obj/item/storage/box/syringes + name = "box of syringes" + desc = "A box full of syringes." + illustration = "syringe" + +/obj/item/storage/box/syringes/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/syringe(src) + +/obj/item/storage/box/syringes/variety + name = "syringe variety box" + +/obj/item/storage/box/syringes/variety/PopulateContents() + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/syringe/lethal(src) + new /obj/item/reagent_containers/syringe/piercing(src) + new /obj/item/reagent_containers/syringe/bluespace(src) + +/obj/item/storage/box/medipens + name = "box of medipens" + desc = "A box full of epinephrine MediPens." + illustration = "syringe" + +/obj/item/storage/box/medipens/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/hypospray/medipen(src) + +/obj/item/storage/box/medipens/utility + name = "stimpack value kit" + desc = "A box with several stimpack medipens for the economical miner." + illustration = "syringe" + +/obj/item/storage/box/medipens/utility/PopulateContents() + ..() // includes regular medipens. + for(var/i in 1 to 5) + new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) + +/obj/item/storage/box/beakers + name = "box of beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker( src ) + +/obj/item/storage/box/beakers/bluespace + name = "box of bluespace beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/bluespace/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/beakers/variety + name = "beaker variety box" + +/obj/item/storage/box/beakers/variety/PopulateContents() + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/beaker/large(src) + new /obj/item/reagent_containers/glass/beaker/plastic(src) + new /obj/item/reagent_containers/glass/beaker/meta(src) + new /obj/item/reagent_containers/glass/beaker/noreact(src) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/medigels + name = "box of medical gels" + desc = "A box full of medical gel applicators, with unscrewable caps and precision spray heads." + illustration = "medgel" + +/obj/item/storage/box/medigels/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/medigel( src ) + +/obj/item/storage/box/injectors + name = "box of DNA injectors" + desc = "This box contains injectors, it seems." + illustration = "dna" + +/obj/item/storage/box/injectors/PopulateContents() + var/static/items_inside = list( + /obj/item/dnainjector/h2m = 3, + /obj/item/dnainjector/m2h = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/flashbangs + name = "box of flashbangs (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." + icon_state = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/flashbangs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/flashbang(src) + +/obj/item/storage/box/stingbangs + name = "box of stingbangs (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause severe injuries or death in repeated use." + icon_state = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/stingbangs/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/grenade/stingbang(src) + +/obj/item/storage/box/flashes + name = "box of flashbulbs" + desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." + icon_state = "secbox" + illustration = "flash" + +/obj/item/storage/box/flashes/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/assembly/flash/handheld(src) + +/obj/item/storage/box/wall_flash + name = "wall-mounted flash kit" + desc = "This box contains everything necessary to build a wall-mounted flash. WARNING: Flashes can cause serious eye damage, protective eyewear is required." + icon_state = "secbox" + illustration = "flash" + +/obj/item/storage/box/wall_flash/PopulateContents() + var/id = rand(1000, 9999) + // FIXME what if this conflicts with an existing one? + + new /obj/item/wallframe/button(src) + new /obj/item/electronics/airlock(src) + var/obj/item/assembly/control/flasher/remote = new(src) + remote.id = id + var/obj/item/wallframe/flasher/frame = new(src) + frame.id = id + new /obj/item/assembly/flash/handheld(src) + new /obj/item/screwdriver(src) + + +/obj/item/storage/box/teargas + name = "box of tear gas grenades (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause blindness and skin irritation." + icon_state = "secbox" + illustration = "grenade" + +/obj/item/storage/box/teargas/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/chem_grenade/teargas(src) + +/obj/item/storage/box/emps + name = "box of emp grenades" + desc = "A box with 5 emp grenades." + illustration = "emp" + +/obj/item/storage/box/emps/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/grenade/empgrenade(src) + +/obj/item/storage/box/trackimp + name = "boxed tracking implant kit" + desc = "Box full of scum-bag tracking utensils." + icon_state = "secbox" + illustration = "implant" + +/obj/item/storage/box/trackimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/tracking = 4, + /obj/item/implanter = 1, + /obj/item/implantpad = 1, + /obj/item/locator = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/minertracker + name = "boxed tracking implant kit" + desc = "For finding those who have died on the accursed lavaworld." + illustration = "implant" + +/obj/item/storage/box/minertracker/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/tracking = 3, + /obj/item/implanter = 1, + /obj/item/implantpad = 1, + /obj/item/locator = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/chemimp + name = "boxed chemical implant kit" + desc = "Box of stuff used to implant chemicals." + illustration = "implant" + +/obj/item/storage/box/chemimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/chem = 5, + /obj/item/implanter = 1, + /obj/item/implantpad = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/exileimp + name = "boxed exile implant kit" + desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." + illustration = "implant" + +/obj/item/storage/box/exileimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/exile = 5, + /obj/item/implanter = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/bodybags + name = "body bags" + desc = "The label indicates that it contains body bags." + illustration = "bodybags" + +/obj/item/storage/box/bodybags/PopulateContents() + ..() + for(var/i in 1 to 7) + new /obj/item/bodybag(src) + +/obj/item/storage/box/rxglasses + name = "box of prescription glasses" + desc = "This box contains nerd glasses." + illustration = "glasses" + +/obj/item/storage/box/rxglasses/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/glasses/regular(src) + +/obj/item/storage/box/drinkingglasses + name = "box of drinking glasses" + desc = "It has a picture of drinking glasses on it." + illustration = "drinkglass" + +/obj/item/storage/box/drinkingglasses/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + +/obj/item/storage/box/condimentbottles + name = "box of condiment bottles" + desc = "It has a large ketchup smear on it." + illustration = "condiment" + +/obj/item/storage/box/condimentbottles/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/condiment(src) + +/obj/item/storage/box/cups + name = "box of paper cups" + desc = "It has pictures of paper cups on the front." + illustration = "cup" + +/obj/item/storage/box/cups/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + +/obj/item/storage/box/donkpockets + name = "box of donk-pockets" + desc = "Instructions: Heat in microwave. Product will cool if not eaten within seven minutes." + icon_state = "donkpocketbox" + illustration=null + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket + +/obj/item/storage/box/donkpockets/PopulateContents() + for(var/i in 1 to 6) + new donktype(src) + +/obj/item/storage/box/donkpockets/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donkpocket)) + +/obj/item/storage/box/donkpockets/donkpocketspicy + name = "box of spicy-flavoured donk-pockets" + icon_state = "donkpocketboxspicy" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/spicy + +/obj/item/storage/box/donkpockets/donkpocketteriyaki + name = "box of teriyaki-flavoured donk-pockets" + icon_state = "donkpocketboxteriyaki" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki + +/obj/item/storage/box/donkpockets/donkpocketpizza + name = "box of pizza-flavoured donk-pockets" + icon_state = "donkpocketboxpizza" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/pizza + +/obj/item/storage/box/donkpockets/donkpocketgondola + name = "box of gondola-flavoured donk-pockets" + icon_state = "donkpocketboxgondola" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/gondola + +/obj/item/storage/box/donkpockets/donkpocketberry + name = "box of berry-flavoured donk-pockets" + icon_state = "donkpocketboxberry" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/berry + +/obj/item/storage/box/donkpockets/donkpockethonk + name = "box of banana-flavoured donk-pockets" + icon_state = "donkpocketboxbanana" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/honk + +/obj/item/storage/box/monkeycubes + name = "monkey cube box" + desc = "Drymate brand monkey cubes. Just add water!" + icon_state = "monkeycubebox" + illustration = null + var/cube_type = /obj/item/reagent_containers/food/snacks/monkeycube + +/obj/item/storage/box/monkeycubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 7 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/monkeycubes/PopulateContents() + for(var/i in 1 to 5) + new cube_type(src) + +/obj/item/storage/box/monkeycubes/syndicate + desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" + cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate + +/obj/item/storage/box/gorillacubes + name = "gorilla cube box" + desc = "Waffle Co. brand gorilla cubes. Do not taunt." + icon_state = "monkeycubebox" + illustration = null + +/obj/item/storage/box/gorillacubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 3 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/gorillacubes/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/monkeycube/gorilla(src) + +/obj/item/storage/box/ids + name = "box of spare IDs" + desc = "Has so many empty IDs." + illustration = "id" + +/obj/item/storage/box/ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id(src) + +//Some spare PDAs in a box +/obj/item/storage/box/pdas + name = "spare PDAs" + desc = "A box of spare PDA microcomputers." + illustration = "pda" + +/obj/item/storage/box/pdas/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/pda(src) + new /obj/item/cartridge/head(src) + + var/newcart = pick( /obj/item/cartridge/engineering, + /obj/item/cartridge/security, + /obj/item/cartridge/medical, + /obj/item/cartridge/signal/toxins, + /obj/item/cartridge/quartermaster) + new newcart(src) + +/obj/item/storage/box/silver_ids + name = "box of spare silver IDs" + desc = "Shiny IDs for important people." + illustration = "id" + +/obj/item/storage/box/silver_ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id/silver(src) + +/obj/item/storage/box/prisoner + name = "box of prisoner IDs" + desc = "Take away their last shred of dignity, their name." + icon_state = "secbox" + illustration = "id" + +/obj/item/storage/box/prisoner/PopulateContents() + ..() + new /obj/item/card/id/prisoner/one(src) + new /obj/item/card/id/prisoner/two(src) + new /obj/item/card/id/prisoner/three(src) + new /obj/item/card/id/prisoner/four(src) + new /obj/item/card/id/prisoner/five(src) + new /obj/item/card/id/prisoner/six(src) + new /obj/item/card/id/prisoner/seven(src) + +/obj/item/storage/box/seccarts + name = "box of PDA security cartridges" + desc = "A box full of PDA cartridges used by Security." + icon_state = "secbox" + illustration = "pda" + +/obj/item/storage/box/seccarts/PopulateContents() + new /obj/item/cartridge/detective(src) + for(var/i in 1 to 6) + new /obj/item/cartridge/security(src) + +/obj/item/storage/box/firingpins + name = "box of standard firing pins" + desc = "A box full of standard firing pins, to allow newly-developed firearms to operate." + icon_state = "secbox" + illustration = "firingpin" + +/obj/item/storage/box/firingpins/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/firing_pin(src) + +/obj/item/storage/box/firingpins/paywall + name = "box of paywall firing pins" + desc = "A box full of paywall firing pins, to allow newly-developed firearms to operate behind a custom-set paywall." + illustration = "firingpin" + +/obj/item/storage/box/firingpins/paywall/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/firing_pin/paywall(src) + +/obj/item/storage/box/lasertagpins + name = "box of laser tag firing pins" + desc = "A box full of laser tag firing pins, to allow newly-developed firearms to require wearing brightly coloured plastic armor before being able to be used." + illustration = "firingpin" + +/obj/item/storage/box/lasertagpins/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/firing_pin/tag/red(src) + new /obj/item/firing_pin/tag/blue(src) + +/obj/item/storage/box/handcuffs + name = "box of spare handcuffs" + desc = "A box full of handcuffs." + icon_state = "secbox" + illustration = "handcuff" + +/obj/item/storage/box/handcuffs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs(src) + +/obj/item/storage/box/zipties + name = "box of spare zipties" + desc = "A box full of zipties." + icon_state = "secbox" + illustration = "handcuff" + +/obj/item/storage/box/zipties/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs/cable/zipties(src) + +/obj/item/storage/box/alienhandcuffs + name = "box of spare handcuffs" + desc = "A box full of handcuffs." + icon_state = "alienbox" + illustration = "handcuff" + +/obj/item/storage/box/alienhandcuffs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs/alien(src) + +/obj/item/storage/box/fakesyndiesuit + name = "boxed space suit and helmet" + desc = "A sleek, sturdy box used to hold replica spacesuits." + icon_state = "syndiebox" + illustration = "syndiesuit" + +/obj/item/storage/box/fakesyndiesuit/PopulateContents() + new /obj/item/clothing/head/syndicatefake(src) + new /obj/item/clothing/suit/syndicatefake(src) + +/obj/item/storage/box/mousetraps + name = "box of Pest-B-Gon mousetraps" + desc = "Keep out of reach of children." + illustration = "mousetrap" + +/obj/item/storage/box/mousetraps/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/assembly/mousetrap(src) + +/obj/item/storage/box/pillbottles + name = "box of pill bottles" + desc = "It has pictures of pill bottles on its front." + illustration = "pillbox" + +/obj/item/storage/box/pillbottles/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/storage/pill_bottle(src) + +/obj/item/storage/box/snappops + name = "snap pop box" + desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." + icon = 'icons/obj/toy.dmi' + icon_state = "spbox" + +/obj/item/storage/box/snappops/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list(/obj/item/toy/snappop)) + STR.max_items = 8 + +/obj/item/storage/box/snappops/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) + +/obj/item/storage/box/matches + name = "matchbox" + desc = "A small box of Almost But Not Quite Plasma Premium Matches." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "matchbox" + inhand_icon_state = "zippo" + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BELT + drop_sound = 'sound/items/handling/matchbox_drop.ogg' + pickup_sound = 'sound/items/handling/matchbox_pickup.ogg' + custom_price = 20 + +/obj/item/storage/box/matches/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.set_holdable(list(/obj/item/match)) + +/obj/item/storage/box/matches/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) + +/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/match)) + W.matchignite() + +/obj/item/storage/box/lights + name = "box of replacement bulbs" + icon = 'icons/obj/storage.dmi' + illustration = "light" + desc = "This box is shaped on the inside so that only light tubes and bulbs fit." + inhand_icon_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + foldable = /obj/item/stack/sheet/cardboard //BubbleWrap + +/obj/item/storage/box/lights/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 21 + STR.set_holdable(list(/obj/item/light/tube, /obj/item/light/bulb)) + STR.max_combined_w_class = 21 + STR.click_gather = FALSE //temp workaround to re-enable filling the light replacer with the box + +/obj/item/storage/box/lights/bulbs/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/bulb(src) + +/obj/item/storage/box/lights/tubes + name = "box of replacement tubes" + illustration = "lighttube" + +/obj/item/storage/box/lights/tubes/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/tube(src) + +/obj/item/storage/box/lights/mixed + name = "box of replacement lights" + illustration = "lightmixed" + +/obj/item/storage/box/lights/mixed/PopulateContents() + for(var/i in 1 to 14) + new /obj/item/light/tube(src) + for(var/i in 1 to 7) + new /obj/item/light/bulb(src) + + +/obj/item/storage/box/deputy + name = "box of deputy armbands" + desc = "To be issued to those authorized to act as deputy of security." + icon_state = "secbox" + illustration = "depband" + +/obj/item/storage/box/deputy/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/accessory/armband/deputy(src) + +/obj/item/storage/box/metalfoam + name = "box of metal foam grenades" + desc = "To be used to rapidly seal hull breaches." + illustration = "grenade" + +/obj/item/storage/box/metalfoam/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/chem_grenade/metalfoam(src) + +/obj/item/storage/box/smart_metal_foam + name = "box of smart metal foam grenades" + desc = "Used to rapidly seal hull breaches. This variety conforms to the walls of its area." + illustration = "grenade" + +/obj/item/storage/box/smart_metal_foam/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/chem_grenade/smart_metal_foam(src) + +/obj/item/storage/box/hug + name = "box of hugs" + desc = "A special box for sensitive people." + icon_state = "hugbox" + illustration = "heart" + foldable = null + +/obj/item/storage/box/hug/suicide_act(mob/user) + user.visible_message("[user] clamps the box of hugs on [user.p_their()] jugular! Guess it wasn't such a hugbox after all..") + return (BRUTELOSS) + +/obj/item/storage/box/hug/attack_self(mob/user) + ..() + user.changeNext_move(CLICK_CD_MELEE) + playsound(loc, "rustle", 50, TRUE, -5) + user.visible_message("[user] hugs \the [src].","You hug \the [src].") + +/////clown box & honkbot assembly +/obj/item/storage/box/clown + name = "clown box" + desc = "A colorful cardboard box for the clown" + illustration = "clown" + +/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) + if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) + if(contents.len) //prevent accidently deleting contents + to_chat(user, "You need to empty [src] out first!") + return + if(!user.temporarilyRemoveItemFromInventory(I)) + return + qdel(I) + to_chat(user, "You add some wheels to the [src]! You've got a honkbot assembly now! Honk!") + var/obj/item/bot_assembly/honkbot/A = new + qdel(src) + user.put_in_hands(A) + else + return ..() + +////// +/obj/item/storage/box/hug/medical/PopulateContents() + new /obj/item/stack/medical/bruise_pack(src) + new /obj/item/stack/medical/ointment(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + +// Clown survival box +/obj/item/storage/box/hug/survival/PopulateContents() + new /obj/item/clothing/mask/breath(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/rubbershot + name = "box of rubber shots" + desc = "A box full of rubber shots, designed for riot shotguns." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/rubbershot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/rubbershot(src) + +/obj/item/storage/box/lethalshot + name = "box of lethal shotgun shots" + desc = "A box full of lethal shots, designed for riot shotguns." + icon_state = "lethalshot_box" + illustration = null + +/obj/item/storage/box/lethalshot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/buckshot(src) + +/obj/item/storage/box/beanbag + name = "box of beanbags" + desc = "A box full of beanbag shells." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/beanbag/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/ammo_casing/shotgun/beanbag(src) + +/obj/item/storage/box/actionfigure + name = "box of action figures" + desc = "The latest set of collectable action figures." + icon_state = "box" + +/obj/item/storage/box/actionfigure/PopulateContents() + for(var/i in 1 to 4) + var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) + new randomFigure(src) + +/obj/item/storage/box/papersack + name = "paper sack" + desc = "A sack neatly crafted out of paper." + icon_state = "paperbag_None" + inhand_icon_state = "paperbag_None" + illustration = null + resistance_flags = FLAMMABLE + foldable = null + /// A list of all available papersack reskins + var/list/papersack_designs = list() + +/obj/item/storage/box/papersack/Initialize(mapload) + . = ..() + papersack_designs = sortList(list( + "None" = image(icon = src.icon, icon_state = "paperbag_None"), + "NanotrasenStandard" = image(icon = src.icon, icon_state = "paperbag_NanotrasenStandard"), + "SyndiSnacks" = image(icon = src.icon, icon_state = "paperbag_SyndiSnacks"), + "Heart" = image(icon = src.icon, icon_state = "paperbag_Heart"), + "SmileyFace" = image(icon = src.icon, icon_state = "paperbag_SmileyFace") + )) + +/obj/item/storage/box/papersack/update_icon_state() + if(contents.len == 0) + icon_state = "[inhand_icon_state]" + else + icon_state = "[inhand_icon_state]_closed" + +/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + var/choice = show_radial_menu(user, src , papersack_designs, custom_check = CALLBACK(src, .proc/check_menu, user, W), radius = 36, require_near = TRUE) + if(!choice) + return FALSE + if(icon_state == "paperbag_[choice]") + return FALSE + switch(choice) + if("None") + desc = "A sack neatly crafted out of paper." + if("NanotrasenStandard") + desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." + if("SyndiSnacks") + desc = "The design on this paper sack is a remnant of the notorious 'SyndieSnacks' program." + if("Heart") + desc = "A paper sack with a heart etched onto the side." + if("SmileyFace") + desc = "A paper sack with a crude smile etched onto the side." + else + return FALSE + to_chat(user, "You make some modifications to [src] using your pen.") + icon_state = "paperbag_[choice]" + inhand_icon_state = "paperbag_[choice]" + return FALSE + else if(W.get_sharpness()) + if(!contents.len) + if(inhand_icon_state == "paperbag_None") + user.show_message("You cut eyeholes into [src].", MSG_VISUAL) + new /obj/item/clothing/head/papersack(user.loc) + qdel(src) + return FALSE + else if(inhand_icon_state == "paperbag_SmileyFace") + user.show_message("You cut eyeholes into [src] and modify the design.", MSG_VISUAL) + new /obj/item/clothing/head/papersack/smiley(user.loc) + qdel(src) + return FALSE + return ..() + +/** + * check_menu: Checks if we are allowed to interact with a radial menu + * + * Arguments: + * * user The mob interacting with a menu + * * P The pen used to interact with a menu + */ +/obj/item/storage/box/papersack/proc/check_menu(mob/user, obj/item/pen/P) + if(!istype(user)) + return FALSE + if(user.incapacitated()) + return FALSE + if(contents.len) + to_chat(user, "You can't modify [src] with items still inside!") + return FALSE + if(!P || !user.is_holding(P)) + to_chat(user, "You need a pen to modify [src]!") + return FALSE + return TRUE + +/obj/item/storage/box/papersack/meat + desc = "It's slightly moist and smells like a slaughterhouse." + +/obj/item/storage/box/papersack/meat/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/food/snacks/meat/slab(src) + +/obj/item/storage/box/ingredients //This box is for the randomely chosen version the chef spawns with, it shouldn't actually exist. + name = "ingredients box" + illustration = "fruit" + var/theme_name + +/obj/item/storage/box/ingredients/Initialize() + . = ..() + if(theme_name) + name = "[name] ([theme_name])" + desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." + inhand_icon_state = "syringe_kit" + +/obj/item/storage/box/ingredients/wildcard + theme_name = "wildcard" + +/obj/item/storage/box/ingredients/wildcard/PopulateContents() + for(var/i in 1 to 7) + var/randomFood = pick(/obj/item/reagent_containers/food/snacks/grown/chili, + /obj/item/reagent_containers/food/snacks/grown/tomato, + /obj/item/reagent_containers/food/snacks/grown/carrot, + /obj/item/reagent_containers/food/snacks/grown/potato, + /obj/item/reagent_containers/food/snacks/grown/potato/sweet, + /obj/item/reagent_containers/food/snacks/grown/apple, + /obj/item/reagent_containers/food/snacks/chocolatebar, + /obj/item/reagent_containers/food/snacks/grown/cherries, + /obj/item/reagent_containers/food/snacks/grown/banana, + /obj/item/reagent_containers/food/snacks/grown/cabbage, + /obj/item/reagent_containers/food/snacks/grown/soybeans, + /obj/item/reagent_containers/food/snacks/grown/corn, + /obj/item/reagent_containers/food/snacks/grown/mushroom/plumphelmet, + /obj/item/reagent_containers/food/snacks/grown/mushroom/chanterelle) + new randomFood(src) + +/obj/item/storage/box/ingredients/fiesta + theme_name = "fiesta" + +/obj/item/storage/box/ingredients/fiesta/PopulateContents() + new /obj/item/reagent_containers/food/snacks/tortilla(src) + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + +/obj/item/storage/box/ingredients/italian + theme_name = "italian" + +/obj/item/storage/box/ingredients/italian/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + new /obj/item/reagent_containers/food/drinks/bottle/wine(src) + +/obj/item/storage/box/ingredients/vegetarian + theme_name = "vegetarian" + +/obj/item/storage/box/ingredients/vegetarian/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/carrot(src) + new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + +/obj/item/storage/box/ingredients/american + theme_name = "american" + +/obj/item/storage/box/ingredients/american/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + +/obj/item/storage/box/ingredients/fruity + theme_name = "fruity" + +/obj/item/storage/box/ingredients/fruity/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) + new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) + +/obj/item/storage/box/ingredients/sweets + theme_name = "sweets" + +/obj/item/storage/box/ingredients/sweets/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/cherries(src) + new /obj/item/reagent_containers/food/snacks/grown/banana(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + +/obj/item/storage/box/ingredients/delights + theme_name = "delights" + +/obj/item/storage/box/ingredients/delights/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) + new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) + new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/berries(src) + +/obj/item/storage/box/ingredients/grains + theme_name = "grains" + +/obj/item/storage/box/ingredients/grains/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/grown/oat(src) + new /obj/item/reagent_containers/food/snacks/grown/wheat(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/honeycomb(src) + new /obj/item/seeds/poppy(src) + +/obj/item/storage/box/ingredients/carnivore + theme_name = "carnivore" + +/obj/item/storage/box/ingredients/carnivore/PopulateContents() + new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) + new /obj/item/reagent_containers/food/snacks/spidereggs(src) + new /obj/item/reagent_containers/food/snacks/carpmeat(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + +/obj/item/storage/box/ingredients/exotic + theme_name = "exotic" + +/obj/item/storage/box/ingredients/exotic/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/carpmeat(src) + new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) + new /obj/item/reagent_containers/food/snacks/grown/cabbage(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + +/obj/item/storage/box/emptysandbags + name = "box of empty sandbags" + illustration = "sandbag" + +/obj/item/storage/box/emptysandbags/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/emptysandbag(src) + +/obj/item/storage/box/rndboards + name = "\proper the liberator's legacy" + desc = "A box containing a gift for worthy golems." + illustration = "scicircuit" + +/obj/item/storage/box/rndboards/PopulateContents() + new /obj/item/circuitboard/machine/protolathe(src) + new /obj/item/circuitboard/machine/destructive_analyzer(src) + new /obj/item/circuitboard/machine/circuit_imprinter(src) + new /obj/item/circuitboard/computer/rdconsole(src) + +/obj/item/storage/box/silver_sulf + name = "box of silver sulfadiazine patches" + desc = "Contains patches used to treat burns." + illustration = "firepatch" + +/obj/item/storage/box/silver_sulf/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/patch/aiuri(src) + +/obj/item/storage/box/fountainpens + name = "box of fountain pens" + illustration = "fpen" + +/obj/item/storage/box/fountainpens/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/pen/fountain(src) + +/obj/item/storage/box/holy_grenades + name = "box of holy hand grenades" + desc = "Contains several grenades used to rapidly purge heresy." + illustration = "grenade" + +/obj/item/storage/box/holy_grenades/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/chem_grenade/holy(src) + +/obj/item/storage/box/stockparts/basic //for ruins where it's a bad idea to give access to an autolathe/protolathe, but still want to make stock parts accessible + name = "box of stock parts" + desc = "Contains a variety of basic stock parts." + +/obj/item/storage/box/stockparts/basic/PopulateContents() + var/static/items_inside = list( + /obj/item/stock_parts/capacitor = 3, + /obj/item/stock_parts/scanning_module = 3, + /obj/item/stock_parts/manipulator = 3, + /obj/item/stock_parts/micro_laser = 3, + /obj/item/stock_parts/matter_bin = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/stockparts/deluxe + name = "box of deluxe stock parts" + desc = "Contains a variety of deluxe stock parts." + icon_state = "syndiebox" + +/obj/item/storage/box/stockparts/deluxe/PopulateContents() + var/static/items_inside = list( + /obj/item/stock_parts/capacitor/quadratic = 3, + /obj/item/stock_parts/scanning_module/triphasic = 3, + /obj/item/stock_parts/manipulator/femto = 3, + /obj/item/stock_parts/micro_laser/quadultra = 3, + /obj/item/stock_parts/matter_bin/bluespace = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/dishdrive + name = "DIY Dish Drive Kit" + desc = "Contains everything you need to build your own Dish Drive!" + custom_premium_price = 1000 + +/obj/item/storage/box/dishdrive/PopulateContents() + var/static/items_inside = list( + /obj/item/stack/sheet/metal/five = 1, + /obj/item/stack/cable_coil/five = 1, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/matter_bin = 2, + /obj/item/screwdriver = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/material + name = "box of materials" + illustration = "implant" + +/obj/item/storage/box/material/PopulateContents() //less uranium because radioactive + var/static/items_inside = list( + /obj/item/stack/sheet/metal/fifty=1,\ + /obj/item/stack/sheet/glass/fifty=1,\ + /obj/item/stack/sheet/rglass=50,\ + /obj/item/stack/sheet/plasmaglass=50,\ + /obj/item/stack/sheet/titaniumglass=50,\ + /obj/item/stack/sheet/plastitaniumglass=50,\ + /obj/item/stack/sheet/plasteel=50,\ + /obj/item/stack/sheet/mineral/plastitanium=50,\ + /obj/item/stack/sheet/mineral/titanium=50,\ + /obj/item/stack/sheet/mineral/gold=50,\ + /obj/item/stack/sheet/mineral/silver=50,\ + /obj/item/stack/sheet/mineral/plasma=50,\ + /obj/item/stack/sheet/mineral/uranium=20,\ + /obj/item/stack/sheet/mineral/diamond=50,\ + /obj/item/stack/sheet/bluespace_crystal=50,\ + /obj/item/stack/sheet/mineral/bananium=50,\ + /obj/item/stack/sheet/mineral/wood=50,\ + /obj/item/stack/sheet/plastic/fifty=1,\ + /obj/item/stack/sheet/runed_metal/fifty=1 + ) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/debugtools + name = "box of debug tools" + icon_state = "syndiebox" + +/obj/item/storage/box/debugtools/PopulateContents() + var/static/items_inside = list( + /obj/item/flashlight/emp/debug=1,\ + /obj/item/pda=1,\ + /obj/item/modular_computer/tablet/preset/advanced=1,\ + /obj/item/geiger_counter=1,\ + /obj/item/construction/rcd/combat/admin=1,\ + /obj/item/pipe_dispenser=1,\ + /obj/item/card/emag=1,\ + /obj/item/stack/spacecash/c1000=50,\ + /obj/item/healthanalyzer/advanced=1,\ + /obj/item/disk/tech_disk/debug=1,\ + /obj/item/uplink/debug=1,\ + /obj/item/uplink/nuclear/debug=1,\ + /obj/item/storage/box/beakers/bluespace=1,\ + /obj/item/storage/box/beakers/variety=1,\ + /obj/item/storage/box/material=1 + ) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/plastic + name = "plastic box" + desc = "It's a solid, plastic shell box." + icon_state = "plasticbox" + foldable = null + illustration = "writing" + custom_materials = list(/datum/material/plastic = 1000) //You lose most if recycled. + + +/obj/item/storage/box/fireworks + name = "box of fireworks" + desc = "Contains an assortment of fireworks." + illustration = "sparkler" + +/obj/item/storage/box/fireworks/PopulateContents() + for(var/i in 1 to 3) + new/obj/item/sparkler(src) + new/obj/item/grenade/firecracker(src) + new /obj/item/toy/snappop(src) + +/obj/item/storage/box/fireworks/dangerous + +/obj/item/storage/box/fireworks/dangerous/PopulateContents() + for(var/i in 1 to 3) + new/obj/item/sparkler(src) + new/obj/item/grenade/firecracker(src) + if(prob(20)) + new /obj/item/grenade/frag(src) + else + new /obj/item/toy/snappop(src) + +/obj/item/storage/box/firecrackers + name = "box of firecrackers" + desc = "A box filled with illegal firecracker. You wonder who still makes these." + icon_state = "syndiebox" + illustration = "firecracker" + +/obj/item/storage/box/firecrackers/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/firecracker(src) + +/obj/item/storage/box/sparklers + name = "box of sparklers" + desc = "A box of NT brand sparklers, burns hot even in the cold of space-winter." + illustration = "sparkler" + +/obj/item/storage/box/sparklers/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/sparkler(src) + +/obj/item/storage/box/gum + name = "bubblegum packet" + desc = "The packaging is entirely in japanese, apparently. You can't make out a single word of it." + icon_state = "bubblegum_generic" + w_class = WEIGHT_CLASS_TINY + illustration = null + foldable = null + custom_price = 120 + +/obj/item/storage/box/gum/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/chewable/bubblegum)) + STR.max_items = 4 + +/obj/item/storage/box/gum/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum(src) + +/obj/item/storage/box/gum/nicotine + name = "nicotine gum packet" + desc = "Designed to help with nicotine addiction and oral fixation all at once without destroying your lungs in the process. Mint flavored!" + icon_state = "bubblegum_nicotine" + custom_premium_price = 275 + +/obj/item/storage/box/gum/nicotine/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/nicotine(src) + +/obj/item/storage/box/gum/happiness + name = "HP+ gum packet" + desc = "A seemingly homemade packaging with an odd smell. It has a weird drawing of a smiling face sticking out its tongue." + icon_state = "bubblegum_happiness" + custom_price = 300 + custom_premium_price = 300 + +/obj/item/storage/box/gum/happiness/Initialize() + . = ..() + if (prob(25)) + desc += "You can faintly make out the word 'Hemopagopril' was once scribbled on it." + +/obj/item/storage/box/gum/happiness/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/happiness(src) + +/obj/item/storage/box/gum/bubblegum + name = "bubblegum gum packet" + desc = "The packaging is entirely in Demonic, apparently. You feel like even opening this would be a sin." + icon_state = "bubblegum_bubblegum" + +/obj/item/storage/box/gum/bubblegum/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/bubblegum(src) + +/obj/item/storage/box/shipping + name = "box of shipping supplies" + desc = "Contains several scanners and labelers for shipping things. Wrapping Paper not included." + illustration = "shipping" + +/obj/item/storage/box/shipping/PopulateContents() + var/static/items_inside = list( + /obj/item/dest_tagger=1,\ + /obj/item/sales_tagger=1,\ + /obj/item/export_scanner=1,\ + /obj/item/stack/package_wrap/small=2,\ + /obj/item/stack/wrapping_paper/small=1 + ) + generate_items_inside(items_inside,src) diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm index 62d2227d791..bc08ec556e1 100644 --- a/code/game/objects/items/storage/briefcase.dm +++ b/code/game/objects/items/storage/briefcase.dm @@ -1,49 +1,49 @@ -/obj/item/storage/briefcase - name = "briefcase" - desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." - icon_state = "briefcase" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - flags_1 = CONDUCT_1 - force = 8 - hitsound = "swing_hit" - throw_speed = 2 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - resistance_flags = FLAMMABLE - max_integrity = 150 - var/folder_path = /obj/item/folder //this is the path of the folder that gets spawned in New() - -/obj/item/storage/briefcase/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - -/obj/item/storage/briefcase/PopulateContents() - new /obj/item/pen(src) - var/obj/item/folder/folder = new folder_path(src) - for(var/i in 1 to 6) - new /obj/item/paper(folder) - -/obj/item/storage/briefcase/lawyer - folder_path = /obj/item/folder/blue - -/obj/item/storage/briefcase/lawyer/PopulateContents() - new /obj/item/stamp/law(src) - ..() - -/obj/item/storage/briefcase/sniperbundle - desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." - force = 10 - -/obj/item/storage/briefcase/sniperbundle/PopulateContents() - ..() // in case you need any paperwork done after your rampage - new /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate(src) - new /obj/item/clothing/neck/tie/red(src) - new /obj/item/clothing/under/syndicate/sniper(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/suppressor/specialoffer(src) - +/obj/item/storage/briefcase + name = "briefcase" + desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." + icon_state = "briefcase" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + flags_1 = CONDUCT_1 + force = 8 + hitsound = "swing_hit" + throw_speed = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + resistance_flags = FLAMMABLE + max_integrity = 150 + var/folder_path = /obj/item/folder //this is the path of the folder that gets spawned in New() + +/obj/item/storage/briefcase/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + +/obj/item/storage/briefcase/PopulateContents() + new /obj/item/pen(src) + var/obj/item/folder/folder = new folder_path(src) + for(var/i in 1 to 6) + new /obj/item/paper(folder) + +/obj/item/storage/briefcase/lawyer + folder_path = /obj/item/folder/blue + +/obj/item/storage/briefcase/lawyer/PopulateContents() + new /obj/item/stamp/law(src) + ..() + +/obj/item/storage/briefcase/sniperbundle + desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." + force = 10 + +/obj/item/storage/briefcase/sniperbundle/PopulateContents() + ..() // in case you need any paperwork done after your rampage + new /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate(src) + new /obj/item/clothing/neck/tie/red(src) + new /obj/item/clothing/under/syndicate/sniper(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/suppressor/specialoffer(src) + diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm index 9b474da405c..9d4d44c9d85 100644 --- a/code/game/objects/items/storage/fancy.dm +++ b/code/game/objects/items/storage/fancy.dm @@ -1,449 +1,449 @@ -/* - * The 'fancy' path is for objects like donut boxes that show how many items are in the storage item on the sprite itself - * .. Sorry for the shitty path name, I couldnt think of a better one. - * - * WARNING: var/icon_type is used for both examine text and sprite name. Please look at the procs below and adjust your sprite names accordingly - * TODO: Cigarette boxes should be ported to this standard - * - * Contains: - * Donut Box - * Egg Box - * Candle Box - * Cigarette Box - * Cigar Case - * Heart Shaped Box w/ Chocolates - */ - -/obj/item/storage/fancy - icon = 'icons/obj/food/containers.dmi' - resistance_flags = FLAMMABLE - var/icon_type = "donut" - var/spawn_type = null - var/fancy_open = FALSE - -/obj/item/storage/fancy/PopulateContents() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - for(var/i = 1 to STR.max_items) - new spawn_type(src) - -/obj/item/storage/fancy/update_icon_state() - if(fancy_open) - icon_state = "[icon_type]box[contents.len]" - else - icon_state = "[icon_type]box" - -/obj/item/storage/fancy/examine(mob/user) - . = ..() - if(fancy_open) - if(length(contents) == 1) - . += "There is one [icon_type] left." - else - . += "There are [contents.len <= 0 ? "no" : "[contents.len]"] [icon_type]s left." - -/obj/item/storage/fancy/attack_self(mob/user) - fancy_open = !fancy_open - update_icon() - . = ..() - -/obj/item/storage/fancy/Exited() - . = ..() - fancy_open = TRUE - update_icon() - -/obj/item/storage/fancy/Entered() - . = ..() - fancy_open = TRUE - update_icon() - -#define DONUT_INBOX_SPRITE_WIDTH 3 - -/* - * Donut Box - */ - -/obj/item/storage/fancy/donut_box - name = "donut box" - desc = "Mmm. Donuts." - icon = 'icons/obj/food/donuts.dmi' - icon_state = "donutbox_inner" - icon_type = "donut" - spawn_type = /obj/item/reagent_containers/food/snacks/donut - fancy_open = TRUE - appearance_flags = KEEP_TOGETHER - -/obj/item/storage/fancy/donut_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donut)) - -/obj/item/storage/fancy/donut_box/PopulateContents() - . = ..() - update_icon() - -/obj/item/storage/fancy/donut_box/update_icon_state() - if(fancy_open) - icon_state = "donutbox_inner" - else - icon_state = "donutbox" - -/obj/item/storage/fancy/donut_box/update_overlays() - . = ..() - - if (!fancy_open) - return - - var/donuts = 0 - - for (var/_donut in contents) - var/obj/item/reagent_containers/food/snacks/donut/donut = _donut - if (!istype(donut)) - continue - - . += image(icon = initial(icon), icon_state = donut.in_box_sprite(), pixel_x = donuts * DONUT_INBOX_SPRITE_WIDTH) - donuts += 1 - - . += image(icon = initial(icon), icon_state = "donutbox_top") - -#undef DONUT_INBOX_SPRITE_WIDTH - -/* - * Egg Box - */ - -/obj/item/storage/fancy/egg_box - icon = 'icons/obj/food/containers.dmi' - inhand_icon_state = "eggbox" - icon_state = "eggbox" - icon_type = "egg" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - name = "egg box" - desc = "A carton for containing eggs." - spawn_type = /obj/item/reagent_containers/food/snacks/egg - -/obj/item/storage/fancy/egg_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 12 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/egg)) - -/* - * Candle Box - */ - -/obj/item/storage/fancy/candle_box - name = "candle pack" - desc = "A pack of red candles." - icon = 'icons/obj/candle.dmi' - icon_state = "candlebox5" - icon_type = "candle" - inhand_icon_state = "candlebox5" - throwforce = 2 - slot_flags = ITEM_SLOT_BELT - spawn_type = /obj/item/candle - fancy_open = TRUE - -/obj/item/storage/fancy/candle_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - -/obj/item/storage/fancy/candle_box/attack_self(mob_user) - return - -//////////// -//CIG PACK// -//////////// -/obj/item/storage/fancy/cigarettes - name = "\improper Space Cigarettes packet" - desc = "The most popular brand of cigarettes, sponsors of the Space Olympics." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig" - inhand_icon_state = "cigpacket" - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - slot_flags = ITEM_SLOT_BELT - icon_type = "cigarette" - spawn_type = /obj/item/clothing/mask/cigarette/space_cigarette - custom_price = 75 - age_restricted = TRUE - ///for cigarette overlay - var/candy = FALSE - /// Does this cigarette packet come with a coupon attached? - var/spawn_coupon = TRUE - /// For VV'ing, set this to true if you want to force the coupon to give an omen - var/rigged_omen = FALSE - -/obj/item/storage/fancy/cigarettes/attack_self(mob/user) - if(contents.len == 0 && spawn_coupon) - to_chat(user, "You rip the back off \the [src] and get a coupon!") - var/obj/item/coupon/attached_coupon = new - user.put_in_hands(attached_coupon) - attached_coupon.generate(rigged_omen) - attached_coupon = null - spawn_coupon = FALSE - name = "discarded cigarette packet" - desc = "An old cigarette packet with the back torn off, worth less than nothing now." - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 0 - return - return ..() - -/obj/item/storage/fancy/cigarettes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) - -/obj/item/storage/fancy/cigarettes/examine(mob/user) - . = ..() - - . += "Alt-click to extract contents." - if(spawn_coupon) - . += "There's a coupon on the back of the pack! You can tear it off once it's empty." - -/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents - if(W && contents.len > 0) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user) - user.put_in_hands(W) - contents -= W - to_chat(user, "You take \a [W] out of the pack.") - else - to_chat(user, "There are no [icon_type]s left in the pack.") - -/obj/item/storage/fancy/cigarettes/update_icon_state() - if(fancy_open || !contents.len) - if(!contents.len) - icon_state = "[initial(icon_state)]_empty" - else - icon_state = initial(icon_state) - -/obj/item/storage/fancy/cigarettes/update_overlays() - . = ..() - if(fancy_open && contents.len) - . += "[icon_state]_open" - var/cig_position = 1 - for(var/C in contents) - var/mutable_appearance/inserted_overlay = mutable_appearance(icon) - - if(istype(C, /obj/item/lighter/greyscale)) - inserted_overlay.icon_state = "lighter_in" - else if(istype(C, /obj/item/lighter)) - inserted_overlay.icon_state = "zippo_in" - else if(candy) - inserted_overlay.icon_state = "candy" - else - inserted_overlay.icon_state = "cigarette" - - inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" - . += inserted_overlay - cig_position++ - -/obj/item/storage/fancy/cigarettes/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) - if(!ismob(M)) - return - var/obj/item/clothing/mask/cigarette/cig = locate(/obj/item/clothing/mask/cigarette) in contents - if(cig) - if(M == user && contents.len > 0 && !user.wear_mask) - var/obj/item/clothing/mask/cigarette/W = cig - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M) - M.equip_to_slot_if_possible(W, ITEM_SLOT_MASK) - contents -= W - to_chat(user, "You take \a [W] out of the pack.") - else - ..() - else - to_chat(user, "There are no [icon_type]s left in the pack.") - -/obj/item/storage/fancy/cigarettes/dromedaryco - name = "\improper DromedaryCo packet" - desc = "A packet of six imported DromedaryCo cancer sticks. A label on the packaging reads, \"Wouldn't a slow death make a change?\"" - icon_state = "dromedary" - spawn_type = /obj/item/clothing/mask/cigarette/dromedary - -/obj/item/storage/fancy/cigarettes/cigpack_uplift - name = "\improper Uplift Smooth packet" - desc = "Your favorite brand, now menthol flavored." - icon_state = "uplift" - spawn_type = /obj/item/clothing/mask/cigarette/uplift - -/obj/item/storage/fancy/cigarettes/cigpack_robust - name = "\improper Robust packet" - desc = "Smoked by the robust." - icon_state = "robust" - spawn_type = /obj/item/clothing/mask/cigarette/robust - -/obj/item/storage/fancy/cigarettes/cigpack_robustgold - name = "\improper Robust Gold packet" - desc = "Smoked by the truly robust." - icon_state = "robustg" - spawn_type = /obj/item/clothing/mask/cigarette/robustgold - -/obj/item/storage/fancy/cigarettes/cigpack_carp - name = "\improper Carp Classic packet" - desc = "Since 2313." - icon_state = "carp" - spawn_type = /obj/item/clothing/mask/cigarette/carp - -/obj/item/storage/fancy/cigarettes/cigpack_syndicate - name = "cigarette packet" - desc = "An obscure brand of cigarettes." - icon_state = "syndie" - spawn_type = /obj/item/clothing/mask/cigarette/syndicate - -/obj/item/storage/fancy/cigarettes/cigpack_midori - name = "\improper Midori Tabako packet" - desc = "You can't understand the runes, but the packet smells funny." - icon_state = "midori" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/nicotine - -/obj/item/storage/fancy/cigarettes/cigpack_candy - name = "\improper Timmy's First Candy Smokes packet" - desc = "Unsure about smoking? Want to bring your children safely into the family tradition? Look no more with this special packet! Includes 100%* Nicotine-Free candy cigarettes." - icon_state = "candy" - icon_type = "candy cigarette" - spawn_type = /obj/item/clothing/mask/cigarette/candy - candy = TRUE - age_restricted = FALSE - -/obj/item/storage/fancy/cigarettes/cigpack_candy/Initialize() - . = ..() - if(prob(7)) - spawn_type = /obj/item/clothing/mask/cigarette/candy/nicotine //uh oh! - -/obj/item/storage/fancy/cigarettes/cigpack_shadyjims - name = "\improper Shady Jim's Super Slims packet" - desc = "Is your weight slowing you down? Having trouble running away from gravitational singularities? Can't stop stuffing your mouth? Smoke Shady Jim's Super Slims and watch all that fat burn away. Guaranteed results!" - icon_state = "shadyjim" - spawn_type = /obj/item/clothing/mask/cigarette/shadyjims - -/obj/item/storage/fancy/cigarettes/cigpack_xeno - name = "\improper Xeno Filtered packet" - desc = "Loaded with 100% pure slime. And also nicotine." - icon_state = "slime" - spawn_type = /obj/item/clothing/mask/cigarette/xeno - -/obj/item/storage/fancy/cigarettes/cigpack_cannabis - name = "\improper Freak Brothers' Special packet" - desc = "A label on the packaging reads, \"Endorsed by Phineas, Freddy and Franklin.\"" - icon_state = "midori" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/cannabis - -/obj/item/storage/fancy/cigarettes/cigpack_mindbreaker - name = "\improper Leary's Delight packet" - desc = "Banned in over 36 galaxies." - icon_state = "shadyjim" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/mindbreaker - -/obj/item/storage/fancy/rollingpapers - name = "rolling paper pack" - desc = "A pack of Nanotrasen brand rolling papers." - w_class = WEIGHT_CLASS_TINY - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig_paper_pack" - ///The value in here has NOTHING to do with icons. It needs to be this for the proper examine. - icon_type = "rolling paper" - spawn_type = /obj/item/rollingpaper - custom_price = 25 - -/obj/item/storage/fancy/rollingpapers/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.set_holdable(list(/obj/item/rollingpaper)) - -///Overrides to do nothing because fancy boxes are fucking insane. -/obj/item/storage/fancy/rollingpapers/update_icon_state() - return - -/obj/item/storage/fancy/rollingpapers/update_overlays() - . = ..() - if(!contents.len) - . += "[icon_state]_empty" - -///////////// -//CIGAR BOX// -///////////// - -/obj/item/storage/fancy/cigarettes/cigars - name = "\improper premium cigar case" - desc = "A case of premium cigars. Very expensive." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cigarcase" - w_class = WEIGHT_CLASS_NORMAL - icon_type = "premium cigar" - spawn_type = /obj/item/clothing/mask/cigarette/cigar - spawn_coupon = FALSE - -/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - STR.set_holdable(list(/obj/item/clothing/mask/cigarette/cigar)) - -/obj/item/storage/fancy/cigarettes/cigars/update_icon_state() - if(fancy_open) - icon_state = "[initial(icon_state)]_open" - else - icon_state = "[initial(icon_state)]" - -/obj/item/storage/fancy/cigarettes/cigars/update_overlays() - . = ..() - if(fancy_open) - var/cigar_position = 1 //generate sprites for cigars in the box - for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) - var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") - . += cigar_overlay - cigar_position++ - -/obj/item/storage/fancy/cigarettes/cigars/cohiba - name = "\improper Cohiba Robusto cigar case" - desc = "A case of imported Cohiba cigars, renowned for their strong flavor." - icon_state = "cohibacase" - spawn_type = /obj/item/clothing/mask/cigarette/cigar/cohiba - -/obj/item/storage/fancy/cigarettes/cigars/havana - name = "\improper premium Havanian cigar case" - desc = "A case of classy Havanian cigars." - icon_state = "cohibacase" - spawn_type = /obj/item/clothing/mask/cigarette/cigar/havana - -/* - * Heart Shaped Box w/ Chocolates - */ - -/obj/item/storage/fancy/heart_box - name = "heart-shaped box" - desc = "A heart-shaped box for holding tiny chocolates." - icon = 'icons/obj/food/containers.dmi' - inhand_icon_state = "chocolatebox" - icon_state = "chocolatebox" - icon_type = "chocolate" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - spawn_type = /obj/item/reagent_containers/food/snacks/tinychocolate - -/obj/item/storage/fancy/heart_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 8 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) - - -/obj/item/storage/fancy/nugget_box - name = "nugget box" - desc = "A cardboard box used for holding chicken nuggies." - icon = 'icons/obj/food/containers.dmi' - icon_state = "nuggetbox" - icon_type = "nugget" - spawn_type = /obj/item/reagent_containers/food/snacks/nugget - -/obj/item/storage/fancy/nugget_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/nugget)) +/* + * The 'fancy' path is for objects like donut boxes that show how many items are in the storage item on the sprite itself + * .. Sorry for the shitty path name, I couldnt think of a better one. + * + * WARNING: var/icon_type is used for both examine text and sprite name. Please look at the procs below and adjust your sprite names accordingly + * TODO: Cigarette boxes should be ported to this standard + * + * Contains: + * Donut Box + * Egg Box + * Candle Box + * Cigarette Box + * Cigar Case + * Heart Shaped Box w/ Chocolates + */ + +/obj/item/storage/fancy + icon = 'icons/obj/food/containers.dmi' + resistance_flags = FLAMMABLE + var/icon_type = "donut" + var/spawn_type = null + var/fancy_open = FALSE + +/obj/item/storage/fancy/PopulateContents() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + for(var/i = 1 to STR.max_items) + new spawn_type(src) + +/obj/item/storage/fancy/update_icon_state() + if(fancy_open) + icon_state = "[icon_type]box[contents.len]" + else + icon_state = "[icon_type]box" + +/obj/item/storage/fancy/examine(mob/user) + . = ..() + if(fancy_open) + if(length(contents) == 1) + . += "There is one [icon_type] left." + else + . += "There are [contents.len <= 0 ? "no" : "[contents.len]"] [icon_type]s left." + +/obj/item/storage/fancy/attack_self(mob/user) + fancy_open = !fancy_open + update_icon() + . = ..() + +/obj/item/storage/fancy/Exited() + . = ..() + fancy_open = TRUE + update_icon() + +/obj/item/storage/fancy/Entered() + . = ..() + fancy_open = TRUE + update_icon() + +#define DONUT_INBOX_SPRITE_WIDTH 3 + +/* + * Donut Box + */ + +/obj/item/storage/fancy/donut_box + name = "donut box" + desc = "Mmm. Donuts." + icon = 'icons/obj/food/donuts.dmi' + icon_state = "donutbox_inner" + icon_type = "donut" + spawn_type = /obj/item/reagent_containers/food/snacks/donut + fancy_open = TRUE + appearance_flags = KEEP_TOGETHER + +/obj/item/storage/fancy/donut_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donut)) + +/obj/item/storage/fancy/donut_box/PopulateContents() + . = ..() + update_icon() + +/obj/item/storage/fancy/donut_box/update_icon_state() + if(fancy_open) + icon_state = "donutbox_inner" + else + icon_state = "donutbox" + +/obj/item/storage/fancy/donut_box/update_overlays() + . = ..() + + if (!fancy_open) + return + + var/donuts = 0 + + for (var/_donut in contents) + var/obj/item/reagent_containers/food/snacks/donut/donut = _donut + if (!istype(donut)) + continue + + . += image(icon = initial(icon), icon_state = donut.in_box_sprite(), pixel_x = donuts * DONUT_INBOX_SPRITE_WIDTH) + donuts += 1 + + . += image(icon = initial(icon), icon_state = "donutbox_top") + +#undef DONUT_INBOX_SPRITE_WIDTH + +/* + * Egg Box + */ + +/obj/item/storage/fancy/egg_box + icon = 'icons/obj/food/containers.dmi' + inhand_icon_state = "eggbox" + icon_state = "eggbox" + icon_type = "egg" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + name = "egg box" + desc = "A carton for containing eggs." + spawn_type = /obj/item/reagent_containers/food/snacks/egg + +/obj/item/storage/fancy/egg_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 12 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/egg)) + +/* + * Candle Box + */ + +/obj/item/storage/fancy/candle_box + name = "candle pack" + desc = "A pack of red candles." + icon = 'icons/obj/candle.dmi' + icon_state = "candlebox5" + icon_type = "candle" + inhand_icon_state = "candlebox5" + throwforce = 2 + slot_flags = ITEM_SLOT_BELT + spawn_type = /obj/item/candle + fancy_open = TRUE + +/obj/item/storage/fancy/candle_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + +/obj/item/storage/fancy/candle_box/attack_self(mob_user) + return + +//////////// +//CIG PACK// +//////////// +/obj/item/storage/fancy/cigarettes + name = "\improper Space Cigarettes packet" + desc = "The most popular brand of cigarettes, sponsors of the Space Olympics." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig" + inhand_icon_state = "cigpacket" + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + slot_flags = ITEM_SLOT_BELT + icon_type = "cigarette" + spawn_type = /obj/item/clothing/mask/cigarette/space_cigarette + custom_price = 75 + age_restricted = TRUE + ///for cigarette overlay + var/candy = FALSE + /// Does this cigarette packet come with a coupon attached? + var/spawn_coupon = TRUE + /// For VV'ing, set this to true if you want to force the coupon to give an omen + var/rigged_omen = FALSE + +/obj/item/storage/fancy/cigarettes/attack_self(mob/user) + if(contents.len == 0 && spawn_coupon) + to_chat(user, "You rip the back off \the [src] and get a coupon!") + var/obj/item/coupon/attached_coupon = new + user.put_in_hands(attached_coupon) + attached_coupon.generate(rigged_omen) + attached_coupon = null + spawn_coupon = FALSE + name = "discarded cigarette packet" + desc = "An old cigarette packet with the back torn off, worth less than nothing now." + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 0 + return + return ..() + +/obj/item/storage/fancy/cigarettes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) + +/obj/item/storage/fancy/cigarettes/examine(mob/user) + . = ..() + + . += "Alt-click to extract contents." + if(spawn_coupon) + . += "There's a coupon on the back of the pack! You can tear it off once it's empty." + +/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user) + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents + if(W && contents.len > 0) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user) + user.put_in_hands(W) + contents -= W + to_chat(user, "You take \a [W] out of the pack.") + else + to_chat(user, "There are no [icon_type]s left in the pack.") + +/obj/item/storage/fancy/cigarettes/update_icon_state() + if(fancy_open || !contents.len) + if(!contents.len) + icon_state = "[initial(icon_state)]_empty" + else + icon_state = initial(icon_state) + +/obj/item/storage/fancy/cigarettes/update_overlays() + . = ..() + if(fancy_open && contents.len) + . += "[icon_state]_open" + var/cig_position = 1 + for(var/C in contents) + var/mutable_appearance/inserted_overlay = mutable_appearance(icon) + + if(istype(C, /obj/item/lighter/greyscale)) + inserted_overlay.icon_state = "lighter_in" + else if(istype(C, /obj/item/lighter)) + inserted_overlay.icon_state = "zippo_in" + else if(candy) + inserted_overlay.icon_state = "candy" + else + inserted_overlay.icon_state = "cigarette" + + inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" + . += inserted_overlay + cig_position++ + +/obj/item/storage/fancy/cigarettes/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) + if(!ismob(M)) + return + var/obj/item/clothing/mask/cigarette/cig = locate(/obj/item/clothing/mask/cigarette) in contents + if(cig) + if(M == user && contents.len > 0 && !user.wear_mask) + var/obj/item/clothing/mask/cigarette/W = cig + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M) + M.equip_to_slot_if_possible(W, ITEM_SLOT_MASK) + contents -= W + to_chat(user, "You take \a [W] out of the pack.") + else + ..() + else + to_chat(user, "There are no [icon_type]s left in the pack.") + +/obj/item/storage/fancy/cigarettes/dromedaryco + name = "\improper DromedaryCo packet" + desc = "A packet of six imported DromedaryCo cancer sticks. A label on the packaging reads, \"Wouldn't a slow death make a change?\"" + icon_state = "dromedary" + spawn_type = /obj/item/clothing/mask/cigarette/dromedary + +/obj/item/storage/fancy/cigarettes/cigpack_uplift + name = "\improper Uplift Smooth packet" + desc = "Your favorite brand, now menthol flavored." + icon_state = "uplift" + spawn_type = /obj/item/clothing/mask/cigarette/uplift + +/obj/item/storage/fancy/cigarettes/cigpack_robust + name = "\improper Robust packet" + desc = "Smoked by the robust." + icon_state = "robust" + spawn_type = /obj/item/clothing/mask/cigarette/robust + +/obj/item/storage/fancy/cigarettes/cigpack_robustgold + name = "\improper Robust Gold packet" + desc = "Smoked by the truly robust." + icon_state = "robustg" + spawn_type = /obj/item/clothing/mask/cigarette/robustgold + +/obj/item/storage/fancy/cigarettes/cigpack_carp + name = "\improper Carp Classic packet" + desc = "Since 2313." + icon_state = "carp" + spawn_type = /obj/item/clothing/mask/cigarette/carp + +/obj/item/storage/fancy/cigarettes/cigpack_syndicate + name = "cigarette packet" + desc = "An obscure brand of cigarettes." + icon_state = "syndie" + spawn_type = /obj/item/clothing/mask/cigarette/syndicate + +/obj/item/storage/fancy/cigarettes/cigpack_midori + name = "\improper Midori Tabako packet" + desc = "You can't understand the runes, but the packet smells funny." + icon_state = "midori" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/nicotine + +/obj/item/storage/fancy/cigarettes/cigpack_candy + name = "\improper Timmy's First Candy Smokes packet" + desc = "Unsure about smoking? Want to bring your children safely into the family tradition? Look no more with this special packet! Includes 100%* Nicotine-Free candy cigarettes." + icon_state = "candy" + icon_type = "candy cigarette" + spawn_type = /obj/item/clothing/mask/cigarette/candy + candy = TRUE + age_restricted = FALSE + +/obj/item/storage/fancy/cigarettes/cigpack_candy/Initialize() + . = ..() + if(prob(7)) + spawn_type = /obj/item/clothing/mask/cigarette/candy/nicotine //uh oh! + +/obj/item/storage/fancy/cigarettes/cigpack_shadyjims + name = "\improper Shady Jim's Super Slims packet" + desc = "Is your weight slowing you down? Having trouble running away from gravitational singularities? Can't stop stuffing your mouth? Smoke Shady Jim's Super Slims and watch all that fat burn away. Guaranteed results!" + icon_state = "shadyjim" + spawn_type = /obj/item/clothing/mask/cigarette/shadyjims + +/obj/item/storage/fancy/cigarettes/cigpack_xeno + name = "\improper Xeno Filtered packet" + desc = "Loaded with 100% pure slime. And also nicotine." + icon_state = "slime" + spawn_type = /obj/item/clothing/mask/cigarette/xeno + +/obj/item/storage/fancy/cigarettes/cigpack_cannabis + name = "\improper Freak Brothers' Special packet" + desc = "A label on the packaging reads, \"Endorsed by Phineas, Freddy and Franklin.\"" + icon_state = "midori" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/cannabis + +/obj/item/storage/fancy/cigarettes/cigpack_mindbreaker + name = "\improper Leary's Delight packet" + desc = "Banned in over 36 galaxies." + icon_state = "shadyjim" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/mindbreaker + +/obj/item/storage/fancy/rollingpapers + name = "rolling paper pack" + desc = "A pack of Nanotrasen brand rolling papers." + w_class = WEIGHT_CLASS_TINY + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig_paper_pack" + ///The value in here has NOTHING to do with icons. It needs to be this for the proper examine. + icon_type = "rolling paper" + spawn_type = /obj/item/rollingpaper + custom_price = 25 + +/obj/item/storage/fancy/rollingpapers/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.set_holdable(list(/obj/item/rollingpaper)) + +///Overrides to do nothing because fancy boxes are fucking insane. +/obj/item/storage/fancy/rollingpapers/update_icon_state() + return + +/obj/item/storage/fancy/rollingpapers/update_overlays() + . = ..() + if(!contents.len) + . += "[icon_state]_empty" + +///////////// +//CIGAR BOX// +///////////// + +/obj/item/storage/fancy/cigarettes/cigars + name = "\improper premium cigar case" + desc = "A case of premium cigars. Very expensive." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cigarcase" + w_class = WEIGHT_CLASS_NORMAL + icon_type = "premium cigar" + spawn_type = /obj/item/clothing/mask/cigarette/cigar + spawn_coupon = FALSE + +/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + STR.set_holdable(list(/obj/item/clothing/mask/cigarette/cigar)) + +/obj/item/storage/fancy/cigarettes/cigars/update_icon_state() + if(fancy_open) + icon_state = "[initial(icon_state)]_open" + else + icon_state = "[initial(icon_state)]" + +/obj/item/storage/fancy/cigarettes/cigars/update_overlays() + . = ..() + if(fancy_open) + var/cigar_position = 1 //generate sprites for cigars in the box + for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) + var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") + . += cigar_overlay + cigar_position++ + +/obj/item/storage/fancy/cigarettes/cigars/cohiba + name = "\improper Cohiba Robusto cigar case" + desc = "A case of imported Cohiba cigars, renowned for their strong flavor." + icon_state = "cohibacase" + spawn_type = /obj/item/clothing/mask/cigarette/cigar/cohiba + +/obj/item/storage/fancy/cigarettes/cigars/havana + name = "\improper premium Havanian cigar case" + desc = "A case of classy Havanian cigars." + icon_state = "cohibacase" + spawn_type = /obj/item/clothing/mask/cigarette/cigar/havana + +/* + * Heart Shaped Box w/ Chocolates + */ + +/obj/item/storage/fancy/heart_box + name = "heart-shaped box" + desc = "A heart-shaped box for holding tiny chocolates." + icon = 'icons/obj/food/containers.dmi' + inhand_icon_state = "chocolatebox" + icon_state = "chocolatebox" + icon_type = "chocolate" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + spawn_type = /obj/item/reagent_containers/food/snacks/tinychocolate + +/obj/item/storage/fancy/heart_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 8 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) + + +/obj/item/storage/fancy/nugget_box + name = "nugget box" + desc = "A cardboard box used for holding chicken nuggies." + icon = 'icons/obj/food/containers.dmi' + icon_state = "nuggetbox" + icon_type = "nugget" + spawn_type = /obj/item/reagent_containers/food/snacks/nugget + +/obj/item/storage/fancy/nugget_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/nugget)) diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm index 6c0da81215a..028b4036571 100644 --- a/code/game/objects/items/storage/firstaid.dm +++ b/code/game/objects/items/storage/firstaid.dm @@ -1,635 +1,635 @@ -/* First aid storage - * Contains: - * First Aid Kits - * Pill Bottles - * Dice Pack (in a pill bottle) - */ - -/* - * First Aid Kits - */ -/obj/item/storage/firstaid - name = "first-aid kit" - desc = "It's an emergency medical kit for those serious boo-boos." - icon_state = "firstaid" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 7 - var/empty = FALSE - var/damagetype_healed //defines damage type of the medkit. General ones stay null. Used for medibot healing bonuses - -/obj/item/storage/firstaid/regular - icon_state = "firstaid" - desc = "A first aid kit with the ability to heal common types of injuries." - -/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/firstaid/regular/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/suture = 2, - /obj/item/stack/medical/mesh = 2, - /obj/item/reagent_containers/hypospray/medipen = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/emergency - icon_state = "medbriefcase" - name = "emergency first-aid kit" - desc = "A very simple first aid kit meant to secure and stabilize serious wounds for later treatment." - -/obj/item/storage/firstaid/emergency/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/healthanalyzer/wound = 1, - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/suture/emergency = 1, - /obj/item/stack/medical/ointment = 1, - /obj/item/reagent_containers/hypospray/medipen/ekit = 2, - /obj/item/storage/pill_bottle/iron = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/medical - name = "medical aid kit" - icon_state = "firstaid_surgery" - inhand_icon_state = "firstaid" - desc = "A high capacity aid kit for doctors, full of medical supplies and basic surgical equipment" - -/obj/item/storage/firstaid/medical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL //holds the same equipment as a medibelt - STR.max_items = 12 - STR.max_combined_w_class = 24 - STR.set_holdable(list( - /obj/item/healthanalyzer, - /obj/item/dnainjector, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/pill, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/medigel, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/storage/pill_bottle, - /obj/item/stack/medical, - /obj/item/flashlight/pen, - /obj/item/extinguisher/mini, - /obj/item/reagent_containers/hypospray, - /obj/item/sensor_device, - /obj/item/radio, - /obj/item/clothing/gloves/, - /obj/item/lazarus_injector, - /obj/item/bikehorn/rubberducky, - /obj/item/clothing/mask/surgical, - /obj/item/clothing/mask/breath, - /obj/item/clothing/mask/breath/medical, - /obj/item/surgical_drapes, //for true paramedics - /obj/item/scalpel, - /obj/item/circular_saw, - /obj/item/bonesetter, - /obj/item/surgicaldrill, - /obj/item/retractor, - /obj/item/cautery, - /obj/item/hemostat, - /obj/item/geiger_counter, - /obj/item/clothing/neck/stethoscope, - /obj/item/stamp, - /obj/item/clothing/glasses, - /obj/item/wrench/medical, - /obj/item/clothing/mask/muzzle, - /obj/item/storage/bag/chemistry, - /obj/item/storage/bag/bio, - /obj/item/reagent_containers/blood, - /obj/item/tank/internals/emergency_oxygen, - /obj/item/gun/syringe/syndicate, - /obj/item/implantcase, - /obj/item/implant, - /obj/item/implanter, - /obj/item/pinpointer/crew, - /obj/item/holosign_creator/medical - )) - -/obj/item/storage/firstaid/medical/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/healthanalyzer = 1, - /obj/item/stack/medical/gauze/twelve = 1, - /obj/item/stack/medical/suture = 2, - /obj/item/stack/medical/mesh = 2, - /obj/item/reagent_containers/hypospray/medipen/ekit = 1, - /obj/item/surgical_drapes = 1, - /obj/item/scalpel = 1, - /obj/item/hemostat = 1, - /obj/item/cautery = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/ancient - icon_state = "oldfirstaid" - desc = "A first aid kit with the ability to heal common types of injuries." - -/obj/item/storage/firstaid/ancient/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/bruise_pack = 3, - /obj/item/stack/medical/ointment= 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/ancient/heirloom - desc = "A first aid kit with the ability to heal common types of injuries. You start thinking of the good old days just by looking at it." - empty = TRUE // long since been ransacked by hungry powergaming assistants breaking into med storage - -/obj/item/storage/firstaid/fire - name = "burn treatment kit" - desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down." - icon_state = "ointment" - inhand_icon_state = "firstaid-ointment" - damagetype_healed = BURN - -/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!") - return FIRELOSS - -/obj/item/storage/firstaid/fire/Initialize(mapload) - . = ..() - icon_state = pick("ointment","firefirstaid") - -/obj/item/storage/firstaid/fire/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/aiuri = 3, - /obj/item/reagent_containers/spray/hercuri = 1, - /obj/item/reagent_containers/hypospray/medipen/oxandrolone = 1, - /obj/item/reagent_containers/hypospray/medipen = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/toxin - name = "toxin treatment kit" - desc = "Used to treat toxic blood content and radiation poisoning." - icon_state = "antitoxin" - inhand_icon_state = "firstaid-toxin" - damagetype_healed = TOX - -/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return TOXLOSS - -/obj/item/storage/firstaid/toxin/Initialize(mapload) - . = ..() - icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2") - -/obj/item/storage/firstaid/toxin/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/storage/pill_bottle/multiver/less = 1, - /obj/item/reagent_containers/syringe/syriniver = 3, - /obj/item/storage/pill_bottle/potassiodide = 1, - /obj/item/reagent_containers/hypospray/medipen/penacid = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/o2 - name = "oxygen deprivation treatment kit" - desc = "A box full of oxygen goodies." - icon_state = "o2" - inhand_icon_state = "firstaid-o2" - damagetype_healed = OXY - -/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/storage/firstaid/o2/Initialize(mapload) - . = ..() - icon_state = pick("o2","o2second") - -/obj/item/storage/firstaid/o2/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/syringe/convermol = 3, - /obj/item/reagent_containers/hypospray/medipen/salbutamol = 1, - /obj/item/reagent_containers/hypospray/medipen = 1, - /obj/item/storage/pill_bottle/iron = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/brute - name = "brute trauma treatment kit" - desc = "A first aid kit for when you get toolboxed." - icon_state = "brute" - inhand_icon_state = "firstaid-brute" - damagetype_healed = BRUTE - -/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/firstaid/brute/Initialize(mapload) - . = ..() - icon_state = pick("brute","brute2") - -/obj/item/storage/firstaid/brute/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/libital = 3, - /obj/item/stack/medical/gauze = 1, - /obj/item/storage/pill_bottle/probital = 1, - /obj/item/reagent_containers/hypospray/medipen/salacid = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/advanced - name = "advanced first aid kit" - desc = "An advanced kit to help deal with advanced wounds." - icon_state = "radfirstaid" - inhand_icon_state = "firstaid-rad" - custom_premium_price = 1100 - damagetype_healed = "all" - -/obj/item/storage/firstaid/advanced/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/instabitaluri = 3, - /obj/item/reagent_containers/hypospray/medipen/atropine = 2, - /obj/item/stack/medical/gauze = 1, - /obj/item/storage/pill_bottle/penacid = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/tactical - name = "combat medical kit" - desc = "I hope you've got insurance." - icon_state = "bezerk" - damagetype_healed = "all" - -/obj/item/storage/firstaid/tactical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - -/obj/item/storage/firstaid/tactical/PopulateContents() - if(empty) - return - new /obj/item/stack/medical/gauze(src) - new /obj/item/defibrillator/compact/combat/loaded(src) - new /obj/item/reagent_containers/hypospray/combat(src) - new /obj/item/reagent_containers/pill/patch/libital(src) - new /obj/item/reagent_containers/pill/patch/libital(src) - new /obj/item/reagent_containers/pill/patch/aiuri(src) - new /obj/item/reagent_containers/pill/patch/aiuri(src) - new /obj/item/clothing/glasses/hud/health/night(src) - -//medibot assembly -/obj/item/storage/firstaid/attackby(obj/item/bodypart/S, mob/user, params) - if((!istype(S, /obj/item/bodypart/l_arm/robot)) && (!istype(S, /obj/item/bodypart/r_arm/robot))) - return ..() - - //Making a medibot! - if(contents.len >= 1) - to_chat(user, "You need to empty [src] out first!") - return - - var/obj/item/bot_assembly/medbot/A = new - if(istype(src, /obj/item/storage/firstaid/fire)) - A.set_skin("ointment") - else if(istype(src, /obj/item/storage/firstaid/toxin)) - A.set_skin("tox") - else if(istype(src, /obj/item/storage/firstaid/o2)) - A.set_skin("o2") - else if(istype(src, /obj/item/storage/firstaid/brute)) - A.set_skin("brute") - user.put_in_hands(A) - to_chat(user, "You add [S] to [src].") - A.robot_arm = S.type - A.firstaid = type - qdel(S) - qdel(src) - -/* - * Pill Bottles - */ - -/obj/item/storage/pill_bottle - name = "pill bottle" - desc = "It's an airtight container for storing medication." - icon_state = "pill_canister" - icon = 'icons/obj/chemical.dmi' - inhand_icon_state = "contsolid" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/pill_bottle/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_quick_gather = TRUE - STR.click_gather = TRUE - STR.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/dice)) - -/obj/item/storage/pill_bottle/suicide_act(mob/user) - user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (TOXLOSS) - -/obj/item/storage/pill_bottle/multiver - name = "bottle of multiver pills" - desc = "Contains pills used to counter toxins." - -/obj/item/storage/pill_bottle/multiver/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/multiver(src) - -/obj/item/storage/pill_bottle/multiver/less - -/obj/item/storage/pill_bottle/multiver/less/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/multiver(src) - -/obj/item/storage/pill_bottle/epinephrine - name = "bottle of epinephrine pills" - desc = "Contains pills used to stabilize patients." - -/obj/item/storage/pill_bottle/epinephrine/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/epinephrine(src) - -/obj/item/storage/pill_bottle/mutadone - name = "bottle of mutadone pills" - desc = "Contains pills used to treat genetic abnormalities." - -/obj/item/storage/pill_bottle/mutadone/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/mutadone(src) - -/obj/item/storage/pill_bottle/potassiodide - name = "bottle of potassium iodide pills" - desc = "Contains pills used to reduce radiation damage." - -/obj/item/storage/pill_bottle/potassiodide/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/potassiodide(src) - -/obj/item/storage/pill_bottle/probital - name = "bottle of probital pills" - desc = "Contains pills used to treat brute damage.The tag in the bottle states 'Eat before ingesting, may cause fatigue'." - -/obj/item/storage/pill_bottle/probital/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/reagent_containers/pill/probital(src) - -/obj/item/storage/pill_bottle/iron - name = "bottle of iron pills" - desc = "Contains pills used to reduce blood loss slowly.The tag in the bottle states 'Only take one each five minutes'." - -/obj/item/storage/pill_bottle/iron/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/reagent_containers/pill/iron(src) - -/obj/item/storage/pill_bottle/mannitol - name = "bottle of mannitol pills" - desc = "Contains pills used to treat brain damage." - -/obj/item/storage/pill_bottle/mannitol/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/mannitol(src) - -//Contains 4 pills instead of 7, and 5u pills instead of 50u (50u pills heal 250 brain damage, 5u pills heal 25) -/obj/item/storage/pill_bottle/mannitol/braintumor - desc = "Contains diluted pills used to treat brain tumor symptoms. Take one when feeling lightheaded." - -/obj/item/storage/pill_bottle/mannitol/braintumor/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/reagent_containers/pill/mannitol/braintumor(src) - -/obj/item/storage/pill_bottle/stimulant - name = "bottle of stimulant pills" - desc = "Guaranteed to give you that extra burst of energy during a long shift!" - -/obj/item/storage/pill_bottle/stimulant/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/stimulant(src) - -/obj/item/storage/pill_bottle/mining - name = "bottle of patches" - desc = "Contains patches used to treat brute and burn damage." - -/obj/item/storage/pill_bottle/mining/PopulateContents() - new /obj/item/reagent_containers/pill/patch/aiuri(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/patch/libital(src) - -/obj/item/storage/pill_bottle/zoom - name = "suspicious pill bottle" - desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds." - -/obj/item/storage/pill_bottle/zoom/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/zoom(src) - -/obj/item/storage/pill_bottle/happy - name = "suspicious pill bottle" - desc = "There is a smiley on the top." - -/obj/item/storage/pill_bottle/happy/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/happy(src) - -/obj/item/storage/pill_bottle/lsd - name = "suspicious pill bottle" - desc = "There is a crude drawing which could be either a mushroom, or a deformed moon." - -/obj/item/storage/pill_bottle/lsd/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/lsd(src) - -/obj/item/storage/pill_bottle/aranesp - name = "suspicious pill bottle" - desc = "The label has 'fuck disablers' hastily scrawled in black marker." - -/obj/item/storage/pill_bottle/aranesp/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/aranesp(src) - -/obj/item/storage/pill_bottle/psicodine - name = "bottle of psicodine pills" - desc = "Contains pills used to treat mental distress and traumas." - -/obj/item/storage/pill_bottle/psicodine/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/psicodine(src) - -/obj/item/storage/pill_bottle/penacid - name = "bottle of pentetic acid pills" - desc = "Contains pills to expunge radiation and toxins." - -/obj/item/storage/pill_bottle/penacid/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/penacid(src) - - -/obj/item/storage/pill_bottle/neurine - name = "bottle of neurine pills" - desc = "Contains pills to treat non-severe mental traumas." - -/obj/item/storage/pill_bottle/neurine/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/neurine(src) - -/obj/item/storage/pill_bottle/floorpill - name = "bottle of floorpills" - desc = "An old pill bottle. It smells musty." - -/obj/item/storage/pill_bottle/floorpill/Initialize() - . = ..() - var/obj/item/reagent_containers/pill/P = locate() in src - name = "bottle of [P.name]s" - -/obj/item/storage/pill_bottle/floorpill/PopulateContents() - for(var/i in 1 to rand(1,7)) - new /obj/item/reagent_containers/pill/floorpill(src) - -/obj/item/storage/pill_bottle/floorpill/full/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/floorpill(src) - -///////////////////////////////////////// Psychologist inventory pillbottles -/obj/item/storage/pill_bottle/happinesspsych - name = "happiness pills" - desc = "Contains pills used as a last resort means to temporarily stabilize depression and anxiety. WARNING: side effects may include slurred speech, drooling, and severe addiction." - -/obj/item/storage/pill_bottle/happinesspsych/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/happinesspsych(src) - -/obj/item/storage/pill_bottle/lsdpsych - name = "mindbreaker toxin pills" - desc = "!FOR THERAPEUTIC USE ONLY! Contains pills used to alleviate the symptoms of Reality Dissociation Syndrome." - -/obj/item/storage/pill_bottle/lsdpsych/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/lsdpsych(src) - -/obj/item/storage/pill_bottle/paxpsych - name = "pax pills" - desc = "Contains pills used to temporarily pacify patients that are deemed a harm to themselves or others." - -/obj/item/storage/pill_bottle/paxpsych/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/paxpsych(src) - -/obj/item/storage/organbox - name = "organ transport box" - desc = "An advanced box with an cooling mechanism that uses cryostylane or other cold reagents to keep the organs or bodyparts inside preserved." - icon_state = "organbox" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 7 - custom_premium_price = 1100 - /// var to prevent it freezing the same things over and over - var/cooling = FALSE - -/obj/item/storage/organbox/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_BULKY /// you have to remove it from your bag before opening it but I think that's fine - STR.max_combined_w_class = 21 - STR.set_holdable(list( - /obj/item/organ, - /obj/item/bodypart, - /obj/item/reagent_containers/food/snacks/icecream - )) - -/obj/item/storage/organbox/Initialize() - . = ..() - create_reagents(100, TRANSPARENT) - RegisterSignal(src, COMSIG_ATOM_ENTERED, .proc/freeze) - RegisterSignal(src, COMSIG_TRY_STORAGE_TAKE, .proc/unfreeze) - START_PROCESSING(SSobj, src) - -/obj/item/storage/organbox/process() - ///if there is enough coolant var - var/cool = FALSE - var/amount = reagents.get_reagent_amount(/datum/reagent/cryostylane) - if(amount >= 0.1) - reagents.remove_reagent(/datum/reagent/cryostylane, 0.1) - cool = TRUE - else - amount = reagents.get_reagent_amount(/datum/reagent/consumable/ice) - if(amount >= 0.3) - reagents.remove_reagent(/datum/reagent/consumable/ice, 0.2) - cool = TRUE - if(!cooling && cool) - cooling = TRUE - update_icon() - for(var/C in contents) - freeze(C) - return - if(cooling && !cool) - cooling = FALSE - update_icon() - for(var/C in contents) - unfreeze(C) - -/obj/item/storage/organbox/update_icon() - . = ..() - if(cooling) - icon_state = "organbox-working" - else - icon_state = "organbox" - -///freezes the organ and loops bodyparts like heads -/obj/item/storage/organbox/proc/freeze(datum/source, obj/item/I) - if(isorgan(I)) - var/obj/item/organ/organ = I - organ.organ_flags |= ORGAN_FROZEN - return - if(istype(I, /obj/item/bodypart)) - var/obj/item/bodypart/B = I - for(var/O in B.contents) - if(isorgan(O)) - var/obj/item/organ/organ = O - organ.organ_flags |= ORGAN_FROZEN - -///unfreezes the organ and loops bodyparts like heads -/obj/item/storage/organbox/proc/unfreeze(datum/source, obj/item/I) - if(isorgan(I)) - var/obj/item/organ/organ = I - organ.organ_flags &= ~ORGAN_FROZEN - return - if(istype(I, /obj/item/bodypart)) - var/obj/item/bodypart/B = I - for(var/O in B.contents) - if(isorgan(O)) - var/obj/item/organ/organ = O - organ.organ_flags &= ~ORGAN_FROZEN - -/obj/item/storage/organbox/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers) && I.is_open_container()) - var/obj/item/reagent_containers/RC = I - var/units = RC.reagents.trans_to(src, RC.amount_per_transfer_from_this, transfered_by = user) - if(units) - to_chat(user, "You transfer [units] units of the solution to [src].") - return - if(istype(I, /obj/item/plunger)) - to_chat(user, "You start furiously plunging [name].") - if(do_after(user, 10, target = src)) - to_chat(user, "You finish plunging the [name].") - reagents.clear_reagents() - return - return ..() - -/obj/item/storage/organbox/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is putting [user.p_theyre()] head inside the [src], it looks like [user.p_theyre()] trying to commit suicide!") - user.adjust_bodytemperature(-300) - user.apply_status_effect(/datum/status_effect/freon) - return (OXYLOSS) +/* First aid storage + * Contains: + * First Aid Kits + * Pill Bottles + * Dice Pack (in a pill bottle) + */ + +/* + * First Aid Kits + */ +/obj/item/storage/firstaid + name = "first-aid kit" + desc = "It's an emergency medical kit for those serious boo-boos." + icon_state = "firstaid" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 7 + var/empty = FALSE + var/damagetype_healed //defines damage type of the medkit. General ones stay null. Used for medibot healing bonuses + +/obj/item/storage/firstaid/regular + icon_state = "firstaid" + desc = "A first aid kit with the ability to heal common types of injuries." + +/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/firstaid/regular/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/suture = 2, + /obj/item/stack/medical/mesh = 2, + /obj/item/reagent_containers/hypospray/medipen = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/emergency + icon_state = "medbriefcase" + name = "emergency first-aid kit" + desc = "A very simple first aid kit meant to secure and stabilize serious wounds for later treatment." + +/obj/item/storage/firstaid/emergency/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/healthanalyzer/wound = 1, + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/suture/emergency = 1, + /obj/item/stack/medical/ointment = 1, + /obj/item/reagent_containers/hypospray/medipen/ekit = 2, + /obj/item/storage/pill_bottle/iron = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/medical + name = "medical aid kit" + icon_state = "firstaid_surgery" + inhand_icon_state = "firstaid" + desc = "A high capacity aid kit for doctors, full of medical supplies and basic surgical equipment" + +/obj/item/storage/firstaid/medical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL //holds the same equipment as a medibelt + STR.max_items = 12 + STR.max_combined_w_class = 24 + STR.set_holdable(list( + /obj/item/healthanalyzer, + /obj/item/dnainjector, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/medigel, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/storage/pill_bottle, + /obj/item/stack/medical, + /obj/item/flashlight/pen, + /obj/item/extinguisher/mini, + /obj/item/reagent_containers/hypospray, + /obj/item/sensor_device, + /obj/item/radio, + /obj/item/clothing/gloves/, + /obj/item/lazarus_injector, + /obj/item/bikehorn/rubberducky, + /obj/item/clothing/mask/surgical, + /obj/item/clothing/mask/breath, + /obj/item/clothing/mask/breath/medical, + /obj/item/surgical_drapes, //for true paramedics + /obj/item/scalpel, + /obj/item/circular_saw, + /obj/item/bonesetter, + /obj/item/surgicaldrill, + /obj/item/retractor, + /obj/item/cautery, + /obj/item/hemostat, + /obj/item/geiger_counter, + /obj/item/clothing/neck/stethoscope, + /obj/item/stamp, + /obj/item/clothing/glasses, + /obj/item/wrench/medical, + /obj/item/clothing/mask/muzzle, + /obj/item/storage/bag/chemistry, + /obj/item/storage/bag/bio, + /obj/item/reagent_containers/blood, + /obj/item/tank/internals/emergency_oxygen, + /obj/item/gun/syringe/syndicate, + /obj/item/implantcase, + /obj/item/implant, + /obj/item/implanter, + /obj/item/pinpointer/crew, + /obj/item/holosign_creator/medical + )) + +/obj/item/storage/firstaid/medical/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/healthanalyzer = 1, + /obj/item/stack/medical/gauze/twelve = 1, + /obj/item/stack/medical/suture = 2, + /obj/item/stack/medical/mesh = 2, + /obj/item/reagent_containers/hypospray/medipen/ekit = 1, + /obj/item/surgical_drapes = 1, + /obj/item/scalpel = 1, + /obj/item/hemostat = 1, + /obj/item/cautery = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/ancient + icon_state = "oldfirstaid" + desc = "A first aid kit with the ability to heal common types of injuries." + +/obj/item/storage/firstaid/ancient/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/bruise_pack = 3, + /obj/item/stack/medical/ointment= 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/ancient/heirloom + desc = "A first aid kit with the ability to heal common types of injuries. You start thinking of the good old days just by looking at it." + empty = TRUE // long since been ransacked by hungry powergaming assistants breaking into med storage + +/obj/item/storage/firstaid/fire + name = "burn treatment kit" + desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down." + icon_state = "ointment" + inhand_icon_state = "firstaid-ointment" + damagetype_healed = BURN + +/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!") + return FIRELOSS + +/obj/item/storage/firstaid/fire/Initialize(mapload) + . = ..() + icon_state = pick("ointment","firefirstaid") + +/obj/item/storage/firstaid/fire/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/aiuri = 3, + /obj/item/reagent_containers/spray/hercuri = 1, + /obj/item/reagent_containers/hypospray/medipen/oxandrolone = 1, + /obj/item/reagent_containers/hypospray/medipen = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/toxin + name = "toxin treatment kit" + desc = "Used to treat toxic blood content and radiation poisoning." + icon_state = "antitoxin" + inhand_icon_state = "firstaid-toxin" + damagetype_healed = TOX + +/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return TOXLOSS + +/obj/item/storage/firstaid/toxin/Initialize(mapload) + . = ..() + icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2") + +/obj/item/storage/firstaid/toxin/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/storage/pill_bottle/multiver/less = 1, + /obj/item/reagent_containers/syringe/syriniver = 3, + /obj/item/storage/pill_bottle/potassiodide = 1, + /obj/item/reagent_containers/hypospray/medipen/penacid = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/o2 + name = "oxygen deprivation treatment kit" + desc = "A box full of oxygen goodies." + icon_state = "o2" + inhand_icon_state = "firstaid-o2" + damagetype_healed = OXY + +/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/storage/firstaid/o2/Initialize(mapload) + . = ..() + icon_state = pick("o2","o2second") + +/obj/item/storage/firstaid/o2/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/syringe/convermol = 3, + /obj/item/reagent_containers/hypospray/medipen/salbutamol = 1, + /obj/item/reagent_containers/hypospray/medipen = 1, + /obj/item/storage/pill_bottle/iron = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/brute + name = "brute trauma treatment kit" + desc = "A first aid kit for when you get toolboxed." + icon_state = "brute" + inhand_icon_state = "firstaid-brute" + damagetype_healed = BRUTE + +/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/firstaid/brute/Initialize(mapload) + . = ..() + icon_state = pick("brute","brute2") + +/obj/item/storage/firstaid/brute/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/libital = 3, + /obj/item/stack/medical/gauze = 1, + /obj/item/storage/pill_bottle/probital = 1, + /obj/item/reagent_containers/hypospray/medipen/salacid = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/advanced + name = "advanced first aid kit" + desc = "An advanced kit to help deal with advanced wounds." + icon_state = "radfirstaid" + inhand_icon_state = "firstaid-rad" + custom_premium_price = 1100 + damagetype_healed = "all" + +/obj/item/storage/firstaid/advanced/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/instabitaluri = 3, + /obj/item/reagent_containers/hypospray/medipen/atropine = 2, + /obj/item/stack/medical/gauze = 1, + /obj/item/storage/pill_bottle/penacid = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/tactical + name = "combat medical kit" + desc = "I hope you've got insurance." + icon_state = "bezerk" + damagetype_healed = "all" + +/obj/item/storage/firstaid/tactical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + +/obj/item/storage/firstaid/tactical/PopulateContents() + if(empty) + return + new /obj/item/stack/medical/gauze(src) + new /obj/item/defibrillator/compact/combat/loaded(src) + new /obj/item/reagent_containers/hypospray/combat(src) + new /obj/item/reagent_containers/pill/patch/libital(src) + new /obj/item/reagent_containers/pill/patch/libital(src) + new /obj/item/reagent_containers/pill/patch/aiuri(src) + new /obj/item/reagent_containers/pill/patch/aiuri(src) + new /obj/item/clothing/glasses/hud/health/night(src) + +//medibot assembly +/obj/item/storage/firstaid/attackby(obj/item/bodypart/S, mob/user, params) + if((!istype(S, /obj/item/bodypart/l_arm/robot)) && (!istype(S, /obj/item/bodypart/r_arm/robot))) + return ..() + + //Making a medibot! + if(contents.len >= 1) + to_chat(user, "You need to empty [src] out first!") + return + + var/obj/item/bot_assembly/medbot/A = new + if(istype(src, /obj/item/storage/firstaid/fire)) + A.set_skin("ointment") + else if(istype(src, /obj/item/storage/firstaid/toxin)) + A.set_skin("tox") + else if(istype(src, /obj/item/storage/firstaid/o2)) + A.set_skin("o2") + else if(istype(src, /obj/item/storage/firstaid/brute)) + A.set_skin("brute") + user.put_in_hands(A) + to_chat(user, "You add [S] to [src].") + A.robot_arm = S.type + A.firstaid = type + qdel(S) + qdel(src) + +/* + * Pill Bottles + */ + +/obj/item/storage/pill_bottle + name = "pill bottle" + desc = "It's an airtight container for storing medication." + icon_state = "pill_canister" + icon = 'icons/obj/chemical.dmi' + inhand_icon_state = "contsolid" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/pill_bottle/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_quick_gather = TRUE + STR.click_gather = TRUE + STR.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/dice)) + +/obj/item/storage/pill_bottle/suicide_act(mob/user) + user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (TOXLOSS) + +/obj/item/storage/pill_bottle/multiver + name = "bottle of multiver pills" + desc = "Contains pills used to counter toxins." + +/obj/item/storage/pill_bottle/multiver/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/multiver(src) + +/obj/item/storage/pill_bottle/multiver/less + +/obj/item/storage/pill_bottle/multiver/less/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/multiver(src) + +/obj/item/storage/pill_bottle/epinephrine + name = "bottle of epinephrine pills" + desc = "Contains pills used to stabilize patients." + +/obj/item/storage/pill_bottle/epinephrine/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/epinephrine(src) + +/obj/item/storage/pill_bottle/mutadone + name = "bottle of mutadone pills" + desc = "Contains pills used to treat genetic abnormalities." + +/obj/item/storage/pill_bottle/mutadone/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/mutadone(src) + +/obj/item/storage/pill_bottle/potassiodide + name = "bottle of potassium iodide pills" + desc = "Contains pills used to reduce radiation damage." + +/obj/item/storage/pill_bottle/potassiodide/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/potassiodide(src) + +/obj/item/storage/pill_bottle/probital + name = "bottle of probital pills" + desc = "Contains pills used to treat brute damage.The tag in the bottle states 'Eat before ingesting, may cause fatigue'." + +/obj/item/storage/pill_bottle/probital/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/reagent_containers/pill/probital(src) + +/obj/item/storage/pill_bottle/iron + name = "bottle of iron pills" + desc = "Contains pills used to reduce blood loss slowly.The tag in the bottle states 'Only take one each five minutes'." + +/obj/item/storage/pill_bottle/iron/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/reagent_containers/pill/iron(src) + +/obj/item/storage/pill_bottle/mannitol + name = "bottle of mannitol pills" + desc = "Contains pills used to treat brain damage." + +/obj/item/storage/pill_bottle/mannitol/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/mannitol(src) + +//Contains 4 pills instead of 7, and 5u pills instead of 50u (50u pills heal 250 brain damage, 5u pills heal 25) +/obj/item/storage/pill_bottle/mannitol/braintumor + desc = "Contains diluted pills used to treat brain tumor symptoms. Take one when feeling lightheaded." + +/obj/item/storage/pill_bottle/mannitol/braintumor/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/reagent_containers/pill/mannitol/braintumor(src) + +/obj/item/storage/pill_bottle/stimulant + name = "bottle of stimulant pills" + desc = "Guaranteed to give you that extra burst of energy during a long shift!" + +/obj/item/storage/pill_bottle/stimulant/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/stimulant(src) + +/obj/item/storage/pill_bottle/mining + name = "bottle of patches" + desc = "Contains patches used to treat brute and burn damage." + +/obj/item/storage/pill_bottle/mining/PopulateContents() + new /obj/item/reagent_containers/pill/patch/aiuri(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/patch/libital(src) + +/obj/item/storage/pill_bottle/zoom + name = "suspicious pill bottle" + desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds." + +/obj/item/storage/pill_bottle/zoom/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/zoom(src) + +/obj/item/storage/pill_bottle/happy + name = "suspicious pill bottle" + desc = "There is a smiley on the top." + +/obj/item/storage/pill_bottle/happy/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/happy(src) + +/obj/item/storage/pill_bottle/lsd + name = "suspicious pill bottle" + desc = "There is a crude drawing which could be either a mushroom, or a deformed moon." + +/obj/item/storage/pill_bottle/lsd/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/lsd(src) + +/obj/item/storage/pill_bottle/aranesp + name = "suspicious pill bottle" + desc = "The label has 'fuck disablers' hastily scrawled in black marker." + +/obj/item/storage/pill_bottle/aranesp/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/aranesp(src) + +/obj/item/storage/pill_bottle/psicodine + name = "bottle of psicodine pills" + desc = "Contains pills used to treat mental distress and traumas." + +/obj/item/storage/pill_bottle/psicodine/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/psicodine(src) + +/obj/item/storage/pill_bottle/penacid + name = "bottle of pentetic acid pills" + desc = "Contains pills to expunge radiation and toxins." + +/obj/item/storage/pill_bottle/penacid/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/penacid(src) + + +/obj/item/storage/pill_bottle/neurine + name = "bottle of neurine pills" + desc = "Contains pills to treat non-severe mental traumas." + +/obj/item/storage/pill_bottle/neurine/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/neurine(src) + +/obj/item/storage/pill_bottle/floorpill + name = "bottle of floorpills" + desc = "An old pill bottle. It smells musty." + +/obj/item/storage/pill_bottle/floorpill/Initialize() + . = ..() + var/obj/item/reagent_containers/pill/P = locate() in src + name = "bottle of [P.name]s" + +/obj/item/storage/pill_bottle/floorpill/PopulateContents() + for(var/i in 1 to rand(1,7)) + new /obj/item/reagent_containers/pill/floorpill(src) + +/obj/item/storage/pill_bottle/floorpill/full/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/floorpill(src) + +///////////////////////////////////////// Psychologist inventory pillbottles +/obj/item/storage/pill_bottle/happinesspsych + name = "happiness pills" + desc = "Contains pills used as a last resort means to temporarily stabilize depression and anxiety. WARNING: side effects may include slurred speech, drooling, and severe addiction." + +/obj/item/storage/pill_bottle/happinesspsych/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/happinesspsych(src) + +/obj/item/storage/pill_bottle/lsdpsych + name = "mindbreaker toxin pills" + desc = "!FOR THERAPEUTIC USE ONLY! Contains pills used to alleviate the symptoms of Reality Dissociation Syndrome." + +/obj/item/storage/pill_bottle/lsdpsych/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/lsdpsych(src) + +/obj/item/storage/pill_bottle/paxpsych + name = "pax pills" + desc = "Contains pills used to temporarily pacify patients that are deemed a harm to themselves or others." + +/obj/item/storage/pill_bottle/paxpsych/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/paxpsych(src) + +/obj/item/storage/organbox + name = "organ transport box" + desc = "An advanced box with an cooling mechanism that uses cryostylane or other cold reagents to keep the organs or bodyparts inside preserved." + icon_state = "organbox" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 7 + custom_premium_price = 1100 + /// var to prevent it freezing the same things over and over + var/cooling = FALSE + +/obj/item/storage/organbox/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_BULKY /// you have to remove it from your bag before opening it but I think that's fine + STR.max_combined_w_class = 21 + STR.set_holdable(list( + /obj/item/organ, + /obj/item/bodypart, + /obj/item/reagent_containers/food/snacks/icecream + )) + +/obj/item/storage/organbox/Initialize() + . = ..() + create_reagents(100, TRANSPARENT) + RegisterSignal(src, COMSIG_ATOM_ENTERED, .proc/freeze) + RegisterSignal(src, COMSIG_TRY_STORAGE_TAKE, .proc/unfreeze) + START_PROCESSING(SSobj, src) + +/obj/item/storage/organbox/process() + ///if there is enough coolant var + var/cool = FALSE + var/amount = reagents.get_reagent_amount(/datum/reagent/cryostylane) + if(amount >= 0.1) + reagents.remove_reagent(/datum/reagent/cryostylane, 0.1) + cool = TRUE + else + amount = reagents.get_reagent_amount(/datum/reagent/consumable/ice) + if(amount >= 0.3) + reagents.remove_reagent(/datum/reagent/consumable/ice, 0.2) + cool = TRUE + if(!cooling && cool) + cooling = TRUE + update_icon() + for(var/C in contents) + freeze(C) + return + if(cooling && !cool) + cooling = FALSE + update_icon() + for(var/C in contents) + unfreeze(C) + +/obj/item/storage/organbox/update_icon() + . = ..() + if(cooling) + icon_state = "organbox-working" + else + icon_state = "organbox" + +///freezes the organ and loops bodyparts like heads +/obj/item/storage/organbox/proc/freeze(datum/source, obj/item/I) + if(isorgan(I)) + var/obj/item/organ/organ = I + organ.organ_flags |= ORGAN_FROZEN + return + if(istype(I, /obj/item/bodypart)) + var/obj/item/bodypart/B = I + for(var/O in B.contents) + if(isorgan(O)) + var/obj/item/organ/organ = O + organ.organ_flags |= ORGAN_FROZEN + +///unfreezes the organ and loops bodyparts like heads +/obj/item/storage/organbox/proc/unfreeze(datum/source, obj/item/I) + if(isorgan(I)) + var/obj/item/organ/organ = I + organ.organ_flags &= ~ORGAN_FROZEN + return + if(istype(I, /obj/item/bodypart)) + var/obj/item/bodypart/B = I + for(var/O in B.contents) + if(isorgan(O)) + var/obj/item/organ/organ = O + organ.organ_flags &= ~ORGAN_FROZEN + +/obj/item/storage/organbox/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers) && I.is_open_container()) + var/obj/item/reagent_containers/RC = I + var/units = RC.reagents.trans_to(src, RC.amount_per_transfer_from_this, transfered_by = user) + if(units) + to_chat(user, "You transfer [units] units of the solution to [src].") + return + if(istype(I, /obj/item/plunger)) + to_chat(user, "You start furiously plunging [name].") + if(do_after(user, 10, target = src)) + to_chat(user, "You finish plunging the [name].") + reagents.clear_reagents() + return + return ..() + +/obj/item/storage/organbox/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is putting [user.p_theyre()] head inside the [src], it looks like [user.p_theyre()] trying to commit suicide!") + user.adjust_bodytemperature(-300) + user.apply_status_effect(/datum/status_effect/freon) + return (OXYLOSS) diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index 73738ce114c..aa02aad30e9 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -1,226 +1,226 @@ -/obj/item/storage/lockbox - name = "lockbox" - desc = "A locked box." - icon_state = "lockbox+l" - inhand_icon_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - req_access = list(ACCESS_ARMORY) - var/broken = FALSE - var/open = FALSE - var/icon_locked = "lockbox+l" - var/icon_closed = "lockbox" - var/icon_broken = "lockbox+b" - -/obj/item/storage/lockbox/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 14 - STR.max_items = 4 - STR.locked = TRUE - -/obj/item/storage/lockbox/attackby(obj/item/W, mob/user, params) - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(W.GetID()) - if(broken) - to_chat(user, "It appears to be broken.") - return - if(allowed(user)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, !locked) - locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - icon_state = icon_locked - to_chat(user, "You lock the [src.name]!") - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_ALL) - return - else - icon_state = icon_closed - to_chat(user, "You unlock the [src.name]!") - return - else - to_chat(user, "Access Denied.") - return - if(!locked) - return ..() - else - to_chat(user, "It's locked!") - -/obj/item/storage/lockbox/emag_act(mob/user) - if(!broken) - broken = TRUE - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) - desc += "It appears to be broken." - icon_state = src.icon_broken - if(user) - visible_message("\The [src] is broken by [user] with an electromagnetic card!") - return - -/obj/item/storage/lockbox/Entered() - . = ..() - open = TRUE - update_icon() - -/obj/item/storage/lockbox/Exited() - . = ..() - open = TRUE - update_icon() - -/obj/item/storage/lockbox/loyalty - name = "lockbox of mindshield implants" - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/loyalty/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/implantcase/mindshield(src) - new /obj/item/implanter/mindshield(src) - -/obj/item/storage/lockbox/clusterbang - name = "lockbox of clusterbangs" - desc = "You have a bad feeling about opening this." - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/clusterbang/PopulateContents() - new /obj/item/grenade/clusterbuster(src) - -/obj/item/storage/lockbox/medal - name = "medal box" - desc = "A locked box used to store medals of honor." - icon_state = "medalbox+l" - inhand_icon_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_NORMAL - req_access = list(ACCESS_CAPTAIN) - icon_locked = "medalbox+l" - icon_closed = "medalbox" - icon_broken = "medalbox+b" - -/obj/item/storage/lockbox/medal/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_items = 10 - STR.max_combined_w_class = 20 - STR.set_holdable(list(/obj/item/clothing/accessory/medal)) - -/obj/item/storage/lockbox/medal/examine(mob/user) - . = ..() - if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - . += "Alt-click to [open ? "close":"open"] it." - -/obj/item/storage/lockbox/medal/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE)) - if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - open = (open ? FALSE : TRUE) - update_icon() - ..() - -/obj/item/storage/lockbox/medal/PopulateContents() - new /obj/item/clothing/accessory/medal/gold/captain(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/security(src) - new /obj/item/clothing/accessory/medal/bronze_heart(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/conduct(src) - -/obj/item/storage/lockbox/medal/update_icon_state() - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - icon_state = "medalbox+l" - else - icon_state = "medalbox" - if(open) - icon_state += "open" - if(broken) - icon_state += "+b" - -/obj/item/storage/lockbox/medal/update_overlays() - . = ..() - if(!contents || !open) - return - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - return - for(var/i in 1 to contents.len) - var/obj/item/clothing/accessory/medal/M = contents[i] - var/mutable_appearance/medalicon = mutable_appearance(initial(icon), M.medaltype) - if(i > 1 && i <= 5) - medalicon.pixel_x += ((i-1)*3) - else if(i > 5) - medalicon.pixel_y -= 7 - medalicon.pixel_x -= 2 - medalicon.pixel_x += ((i-6)*3) - . += medalicon - -/obj/item/storage/lockbox/medal/sec - name = "security medal box" - desc = "A locked box used to store medals to be given to members of the security department." - req_access = list(ACCESS_HOS) - -/obj/item/storage/lockbox/medal/sec/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/silver/security(src) - -/obj/item/storage/lockbox/medal/cargo - name = "cargo award box" - desc = "A locked box used to store awards to be given to members of the cargo department." - req_access = list(ACCESS_QM) - -/obj/item/storage/lockbox/medal/cargo/PopulateContents() - new /obj/item/clothing/accessory/medal/ribbon/cargo(src) - -/obj/item/storage/lockbox/medal/service - name = "service award box" - desc = "A locked box used to store awards to be given to members of the service department." - req_access = list(ACCESS_HOP) - -/obj/item/storage/lockbox/medal/service/PopulateContents() - new /obj/item/clothing/accessory/medal/silver/excellence(src) - -/obj/item/storage/lockbox/medal/sci - name = "science medal box" - desc = "A locked box used to store medals to be given to members of the science department." - req_access = list(ACCESS_RD) - -/obj/item/storage/lockbox/medal/sci/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - -/obj/item/storage/lockbox/order - name = "order lockbox" - desc = "A box used to secure small cargo orders from being looted by those who didn't order it. Yeah, cargo tech, that means you." - icon = 'icons/obj/storage.dmi' - icon_state = "secure" - inhand_icon_state = "sec-case" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - w_class = WEIGHT_CLASS_HUGE - var/datum/bank_account/buyer_account - var/privacy_lock = TRUE - -/obj/item/storage/lockbox/order/Initialize(datum/bank_account/_buyer_account) - . = ..() - buyer_account = _buyer_account - -/obj/item/storage/lockbox/order/attackby(obj/item/W, mob/user, params) - if(!istype(W, /obj/item/card/id)) - return ..() - - var/obj/item/card/id/id_card = W - if(iscarbon(user)) - add_fingerprint(user) - - if(id_card.registered_account != buyer_account) - to_chat(user, "Bank account does not match with buyer![user] [privacy_lock ? "" : "un"]locks [src]'s privacy lock.", - "You [privacy_lock ? "" : "un"]lock [src]'s privacy lock.") - +/obj/item/storage/lockbox + name = "lockbox" + desc = "A locked box." + icon_state = "lockbox+l" + inhand_icon_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + req_access = list(ACCESS_ARMORY) + var/broken = FALSE + var/open = FALSE + var/icon_locked = "lockbox+l" + var/icon_closed = "lockbox" + var/icon_broken = "lockbox+b" + +/obj/item/storage/lockbox/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 14 + STR.max_items = 4 + STR.locked = TRUE + +/obj/item/storage/lockbox/attackby(obj/item/W, mob/user, params) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(W.GetID()) + if(broken) + to_chat(user, "It appears to be broken.") + return + if(allowed(user)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, !locked) + locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = icon_locked + to_chat(user, "You lock the [src.name]!") + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_ALL) + return + else + icon_state = icon_closed + to_chat(user, "You unlock the [src.name]!") + return + else + to_chat(user, "Access Denied.") + return + if(!locked) + return ..() + else + to_chat(user, "It's locked!") + +/obj/item/storage/lockbox/emag_act(mob/user) + if(!broken) + broken = TRUE + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) + desc += "It appears to be broken." + icon_state = src.icon_broken + if(user) + visible_message("\The [src] is broken by [user] with an electromagnetic card!") + return + +/obj/item/storage/lockbox/Entered() + . = ..() + open = TRUE + update_icon() + +/obj/item/storage/lockbox/Exited() + . = ..() + open = TRUE + update_icon() + +/obj/item/storage/lockbox/loyalty + name = "lockbox of mindshield implants" + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/loyalty/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/implantcase/mindshield(src) + new /obj/item/implanter/mindshield(src) + +/obj/item/storage/lockbox/clusterbang + name = "lockbox of clusterbangs" + desc = "You have a bad feeling about opening this." + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/clusterbang/PopulateContents() + new /obj/item/grenade/clusterbuster(src) + +/obj/item/storage/lockbox/medal + name = "medal box" + desc = "A locked box used to store medals of honor." + icon_state = "medalbox+l" + inhand_icon_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + req_access = list(ACCESS_CAPTAIN) + icon_locked = "medalbox+l" + icon_closed = "medalbox" + icon_broken = "medalbox+b" + +/obj/item/storage/lockbox/medal/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_items = 10 + STR.max_combined_w_class = 20 + STR.set_holdable(list(/obj/item/clothing/accessory/medal)) + +/obj/item/storage/lockbox/medal/examine(mob/user) + . = ..() + if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + . += "Alt-click to [open ? "close":"open"] it." + +/obj/item/storage/lockbox/medal/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE)) + if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + open = (open ? FALSE : TRUE) + update_icon() + ..() + +/obj/item/storage/lockbox/medal/PopulateContents() + new /obj/item/clothing/accessory/medal/gold/captain(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/security(src) + new /obj/item/clothing/accessory/medal/bronze_heart(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/conduct(src) + +/obj/item/storage/lockbox/medal/update_icon_state() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = "medalbox+l" + else + icon_state = "medalbox" + if(open) + icon_state += "open" + if(broken) + icon_state += "+b" + +/obj/item/storage/lockbox/medal/update_overlays() + . = ..() + if(!contents || !open) + return + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + return + for(var/i in 1 to contents.len) + var/obj/item/clothing/accessory/medal/M = contents[i] + var/mutable_appearance/medalicon = mutable_appearance(initial(icon), M.medaltype) + if(i > 1 && i <= 5) + medalicon.pixel_x += ((i-1)*3) + else if(i > 5) + medalicon.pixel_y -= 7 + medalicon.pixel_x -= 2 + medalicon.pixel_x += ((i-6)*3) + . += medalicon + +/obj/item/storage/lockbox/medal/sec + name = "security medal box" + desc = "A locked box used to store medals to be given to members of the security department." + req_access = list(ACCESS_HOS) + +/obj/item/storage/lockbox/medal/sec/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/silver/security(src) + +/obj/item/storage/lockbox/medal/cargo + name = "cargo award box" + desc = "A locked box used to store awards to be given to members of the cargo department." + req_access = list(ACCESS_QM) + +/obj/item/storage/lockbox/medal/cargo/PopulateContents() + new /obj/item/clothing/accessory/medal/ribbon/cargo(src) + +/obj/item/storage/lockbox/medal/service + name = "service award box" + desc = "A locked box used to store awards to be given to members of the service department." + req_access = list(ACCESS_HOP) + +/obj/item/storage/lockbox/medal/service/PopulateContents() + new /obj/item/clothing/accessory/medal/silver/excellence(src) + +/obj/item/storage/lockbox/medal/sci + name = "science medal box" + desc = "A locked box used to store medals to be given to members of the science department." + req_access = list(ACCESS_RD) + +/obj/item/storage/lockbox/medal/sci/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + +/obj/item/storage/lockbox/order + name = "order lockbox" + desc = "A box used to secure small cargo orders from being looted by those who didn't order it. Yeah, cargo tech, that means you." + icon = 'icons/obj/storage.dmi' + icon_state = "secure" + inhand_icon_state = "sec-case" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + w_class = WEIGHT_CLASS_HUGE + var/datum/bank_account/buyer_account + var/privacy_lock = TRUE + +/obj/item/storage/lockbox/order/Initialize(datum/bank_account/_buyer_account) + . = ..() + buyer_account = _buyer_account + +/obj/item/storage/lockbox/order/attackby(obj/item/W, mob/user, params) + if(!istype(W, /obj/item/card/id)) + return ..() + + var/obj/item/card/id/id_card = W + if(iscarbon(user)) + add_fingerprint(user) + + if(id_card.registered_account != buyer_account) + to_chat(user, "Bank account does not match with buyer![user] [privacy_lock ? "" : "un"]locks [src]'s privacy lock.", + "You [privacy_lock ? "" : "un"]lock [src]'s privacy lock.") + diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index 1f93412c556..4ce9266be3e 100644 --- a/code/game/objects/items/storage/secure.dm +++ b/code/game/objects/items/storage/secure.dm @@ -1,188 +1,188 @@ -/* - * Absorbs /obj/item/secstorage. - * Reimplements it only slightly to use existing storage functionality. - * - * Contains: - * Secure Briefcase - * Wall Safe - */ - -// ----------------------------- -// Generic Item -// ----------------------------- -/obj/item/storage/secure - name = "secstorage" - var/icon_locking = "secureb" - var/icon_sparking = "securespark" - var/icon_opened = "secure0" - var/code = "" - var/l_code = null - var/l_set = 0 - var/l_setshort = 0 - var/l_hacking = 0 - var/open = FALSE - w_class = WEIGHT_CLASS_NORMAL - desc = "This shouldn't exist. If it does, create an issue report." - -/obj/item/storage/secure/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_combined_w_class = 14 - -/obj/item/storage/secure/examine(mob/user) - . = ..() - . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]." - -/obj/item/storage/secure/attackby(obj/item/W, mob/user, params) - if(SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - if (W.tool_behaviour == TOOL_SCREWDRIVER) - if (W.use_tool(src, user, 20)) - open =! open - to_chat(user, "You [open ? "open" : "close"] the service panel.") - return - if (W.tool_behaviour == TOOL_WIRECUTTER) - to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") - if ((W.tool_behaviour == TOOL_MULTITOOL) && (!l_hacking)) - if(open == 1) - to_chat(user, "Now attempting to reset internal memory, please hold.") - l_hacking = 1 - if (W.use_tool(src, user, 400)) - to_chat(user, "Internal memory reset - lock has been disengaged.") - l_set = 0 - l_hacking = 0 - else - l_hacking = 0 - else - to_chat(user, "You must unscrew the service panel before you can pulse the wiring!") - return - //At this point you have exhausted all the special things to do when locked - // ... but it's still locked. - return - - // -> storage/attackby() what with handle insertion, etc - return ..() - -/obj/item/storage/secure/attack_self(mob/user) - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - user.set_machine(src) - var/dat = text("[]
                \n\nLock Status: []",src, (locked ? "LOCKED" : "UNLOCKED")) - var/message = "Code" - if ((l_set == 0) && (!l_setshort)) - dat += text("

                \n5-DIGIT PASSCODE NOT SET.
                ENTER NEW PASSCODE.
                ") - if (l_setshort) - dat += text("

                \nALERT: MEMORY SYSTEM ERROR - 6040 201") - message = text("[]", code) - if (!locked) - message = "*****" - dat += text("


                \n>[]
                \n1-2-3
                \n4-5-6
                \n7-8-9
                \nR-0-E
                \n
                ", message) - user << browse(dat, "window=caselock;size=300x280") - -/obj/item/storage/secure/Topic(href, href_list) - ..() - if ((usr.stat || usr.restrained()) || (get_dist(src, usr) > 1)) - return - if (href_list["type"]) - if (href_list["type"] == "E") - if ((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) - l_code = code - l_set = 1 - else if ((code == l_code) && (l_set == 1)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) - cut_overlays() - add_overlay(icon_opened) - code = null - else - code = "ERROR" - else - if ((href_list["type"] == "R") && (!l_setshort)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE) - cut_overlays() - code = null - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_FROM, usr) - else - code += text("[]", sanitize_text(href_list["type"])) - if (length(code) > 5) - code = "ERROR" - add_fingerprint(usr) - for(var/mob/M in viewers(1, loc)) - if ((M.client && M.machine == src)) - attack_self(M) - return - return - - -// ----------------------------- -// Secure Briefcase -// ----------------------------- -/obj/item/storage/secure/briefcase - name = "secure briefcase" - icon = 'icons/obj/storage.dmi' - icon_state = "secure" - inhand_icon_state = "sec-case" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - desc = "A large briefcase with a digital locking system." - force = 8 - hitsound = "swing_hit" - throw_speed = 2 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - -/obj/item/storage/secure/briefcase/PopulateContents() - new /obj/item/paper(src) - new /obj/item/pen(src) - -/obj/item/storage/secure/briefcase/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 21 - STR.max_w_class = WEIGHT_CLASS_NORMAL - -//Syndie variant of Secure Briefcase. Contains space cash, slightly more robust. -/obj/item/storage/secure/briefcase/syndie - force = 15 - -/obj/item/storage/secure/briefcase/syndie/PopulateContents() - ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - for(var/i = 0, i < STR.max_items - 2, i++) - new /obj/item/stack/spacecash/c1000(src) - - -// ----------------------------- -// Secure Safe -// ----------------------------- - -/obj/item/storage/secure/safe - name = "secure safe" - icon = 'icons/obj/storage.dmi' - icon_state = "safe" - icon_opened = "safe0" - icon_locking = "safeb" - icon_sparking = "safespark" - desc = "Excellent for securing things away from grubby hands." - force = 8 - w_class = WEIGHT_CLASS_GIGANTIC - anchored = TRUE - density = FALSE - -/obj/item/storage/secure/safe/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(null, list(/obj/item/storage/secure/briefcase)) - STR.max_w_class = 8 //?? - -/obj/item/storage/secure/safe/PopulateContents() - new /obj/item/paper(src) - new /obj/item/pen(src) - -/obj/item/storage/secure/safe/attack_hand(mob/user) - . = ..() - if(.) - return - return attack_self(user) - -/obj/item/storage/secure/safe/hos - name = "head of security's safe" +/* + * Absorbs /obj/item/secstorage. + * Reimplements it only slightly to use existing storage functionality. + * + * Contains: + * Secure Briefcase + * Wall Safe + */ + +// ----------------------------- +// Generic Item +// ----------------------------- +/obj/item/storage/secure + name = "secstorage" + var/icon_locking = "secureb" + var/icon_sparking = "securespark" + var/icon_opened = "secure0" + var/code = "" + var/l_code = null + var/l_set = 0 + var/l_setshort = 0 + var/l_hacking = 0 + var/open = FALSE + w_class = WEIGHT_CLASS_NORMAL + desc = "This shouldn't exist. If it does, create an issue report." + +/obj/item/storage/secure/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_combined_w_class = 14 + +/obj/item/storage/secure/examine(mob/user) + . = ..() + . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]." + +/obj/item/storage/secure/attackby(obj/item/W, mob/user, params) + if(SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + if (W.tool_behaviour == TOOL_SCREWDRIVER) + if (W.use_tool(src, user, 20)) + open =! open + to_chat(user, "You [open ? "open" : "close"] the service panel.") + return + if (W.tool_behaviour == TOOL_WIRECUTTER) + to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") + if ((W.tool_behaviour == TOOL_MULTITOOL) && (!l_hacking)) + if(open == 1) + to_chat(user, "Now attempting to reset internal memory, please hold.") + l_hacking = 1 + if (W.use_tool(src, user, 400)) + to_chat(user, "Internal memory reset - lock has been disengaged.") + l_set = 0 + l_hacking = 0 + else + l_hacking = 0 + else + to_chat(user, "You must unscrew the service panel before you can pulse the wiring!") + return + //At this point you have exhausted all the special things to do when locked + // ... but it's still locked. + return + + // -> storage/attackby() what with handle insertion, etc + return ..() + +/obj/item/storage/secure/attack_self(mob/user) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + user.set_machine(src) + var/dat = text("[]
                \n\nLock Status: []",src, (locked ? "LOCKED" : "UNLOCKED")) + var/message = "Code" + if ((l_set == 0) && (!l_setshort)) + dat += text("

                \n5-DIGIT PASSCODE NOT SET.
                ENTER NEW PASSCODE.
                ") + if (l_setshort) + dat += text("

                \nALERT: MEMORY SYSTEM ERROR - 6040 201") + message = text("[]", code) + if (!locked) + message = "*****" + dat += text("


                \n>[]
                \n1-2-3
                \n4-5-6
                \n7-8-9
                \nR-0-E
                \n
                ", message) + user << browse(dat, "window=caselock;size=300x280") + +/obj/item/storage/secure/Topic(href, href_list) + ..() + if ((usr.stat || usr.restrained()) || (get_dist(src, usr) > 1)) + return + if (href_list["type"]) + if (href_list["type"] == "E") + if ((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) + l_code = code + l_set = 1 + else if ((code == l_code) && (l_set == 1)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) + cut_overlays() + add_overlay(icon_opened) + code = null + else + code = "ERROR" + else + if ((href_list["type"] == "R") && (!l_setshort)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE) + cut_overlays() + code = null + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_FROM, usr) + else + code += text("[]", sanitize_text(href_list["type"])) + if (length(code) > 5) + code = "ERROR" + add_fingerprint(usr) + for(var/mob/M in viewers(1, loc)) + if ((M.client && M.machine == src)) + attack_self(M) + return + return + + +// ----------------------------- +// Secure Briefcase +// ----------------------------- +/obj/item/storage/secure/briefcase + name = "secure briefcase" + icon = 'icons/obj/storage.dmi' + icon_state = "secure" + inhand_icon_state = "sec-case" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + desc = "A large briefcase with a digital locking system." + force = 8 + hitsound = "swing_hit" + throw_speed = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + +/obj/item/storage/secure/briefcase/PopulateContents() + new /obj/item/paper(src) + new /obj/item/pen(src) + +/obj/item/storage/secure/briefcase/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 21 + STR.max_w_class = WEIGHT_CLASS_NORMAL + +//Syndie variant of Secure Briefcase. Contains space cash, slightly more robust. +/obj/item/storage/secure/briefcase/syndie + force = 15 + +/obj/item/storage/secure/briefcase/syndie/PopulateContents() + ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + for(var/i = 0, i < STR.max_items - 2, i++) + new /obj/item/stack/spacecash/c1000(src) + + +// ----------------------------- +// Secure Safe +// ----------------------------- + +/obj/item/storage/secure/safe + name = "secure safe" + icon = 'icons/obj/storage.dmi' + icon_state = "safe" + icon_opened = "safe0" + icon_locking = "safeb" + icon_sparking = "safespark" + desc = "Excellent for securing things away from grubby hands." + force = 8 + w_class = WEIGHT_CLASS_GIGANTIC + anchored = TRUE + density = FALSE + +/obj/item/storage/secure/safe/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(null, list(/obj/item/storage/secure/briefcase)) + STR.max_w_class = 8 //?? + +/obj/item/storage/secure/safe/PopulateContents() + new /obj/item/paper(src) + new /obj/item/pen(src) + +/obj/item/storage/secure/safe/attack_hand(mob/user) + . = ..() + if(.) + return + return attack_self(user) + +/obj/item/storage/secure/safe/hos + name = "head of security's safe" diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index ee3bc568bac..1e679adbc68 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -1,50 +1,50 @@ -/obj/item/storage - name = "storage" - icon = 'icons/obj/storage.dmi' - w_class = WEIGHT_CLASS_NORMAL - var/rummage_if_nodrop = TRUE - var/component_type = /datum/component/storage/concrete - -/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user) - return src - -/obj/item/storage/Initialize() - . = ..() - PopulateContents() - -/obj/item/storage/ComponentInitialize() - AddComponent(component_type) - -/obj/item/storage/AllowDrop() - return FALSE - -/obj/item/storage/contents_explosion(severity, target) - for(var/atom/A in contents) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += A - if(EXPLODE_HEAVY) - SSexplosions.medobj += A - if(EXPLODE_LIGHT) - SSexplosions.lowobj += A - -/obj/item/storage/canStrip(mob/who) - . = ..() - if(!. && rummage_if_nodrop) - return TRUE - -/obj/item/storage/doStrip(mob/who) - if(HAS_TRAIT(src, TRAIT_NODROP) && rummage_if_nodrop) - var/datum/component/storage/CP = GetComponent(/datum/component/storage) - CP.do_quick_empty() - return TRUE - return ..() - -/obj/item/storage/contents_explosion(severity, target) -//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" - -/obj/item/storage/proc/PopulateContents() - -/obj/item/storage/proc/emptyStorage() - var/datum/component/storage/ST = GetComponent(/datum/component/storage) - ST.do_quick_empty() +/obj/item/storage + name = "storage" + icon = 'icons/obj/storage.dmi' + w_class = WEIGHT_CLASS_NORMAL + var/rummage_if_nodrop = TRUE + var/component_type = /datum/component/storage/concrete + +/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user) + return src + +/obj/item/storage/Initialize() + . = ..() + PopulateContents() + +/obj/item/storage/ComponentInitialize() + AddComponent(component_type) + +/obj/item/storage/AllowDrop() + return FALSE + +/obj/item/storage/contents_explosion(severity, target) + for(var/atom/A in contents) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += A + if(EXPLODE_HEAVY) + SSexplosions.medobj += A + if(EXPLODE_LIGHT) + SSexplosions.lowobj += A + +/obj/item/storage/canStrip(mob/who) + . = ..() + if(!. && rummage_if_nodrop) + return TRUE + +/obj/item/storage/doStrip(mob/who) + if(HAS_TRAIT(src, TRAIT_NODROP) && rummage_if_nodrop) + var/datum/component/storage/CP = GetComponent(/datum/component/storage) + CP.do_quick_empty() + return TRUE + return ..() + +/obj/item/storage/contents_explosion(severity, target) +//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" + +/obj/item/storage/proc/PopulateContents() + +/obj/item/storage/proc/emptyStorage() + var/datum/component/storage/ST = GetComponent(/datum/component/storage) + ST.do_quick_empty() diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index a21c3a2e737..f3ab4039ce0 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -1,301 +1,301 @@ -/obj/item/storage/toolbox - name = "toolbox" - desc = "Danger. Very robust." - icon_state = "toolbox_default" - inhand_icon_state = "toolbox_default" - lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi' - flags_1 = CONDUCT_1 - force = 12 - throwforce = 12 - throw_speed = 2 - throw_range = 7 - w_class = WEIGHT_CLASS_BULKY - custom_materials = list(/datum/material/iron = 500) - attack_verb = list("robusted") - hitsound = 'sound/weapons/smash.ogg' - drop_sound = 'sound/items/handling/toolbox_drop.ogg' - pickup_sound = 'sound/items/handling/toolbox_pickup.ogg' - material_flags = MATERIAL_COLOR - var/latches = "single_latch" - var/has_latches = TRUE - wound_bonus = 5 - -/obj/item/storage/toolbox/Initialize() - . = ..() - if(has_latches) - if(prob(10)) - latches = "double_latch" - if(prob(1)) - latches = "triple_latch" - update_icon() - -/obj/item/storage/toolbox/update_overlays() - . = ..() - if(has_latches) - . += latches - - -/obj/item/storage/toolbox/suicide_act(mob/user) - user.visible_message("[user] robusts [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/storage/toolbox/emergency - name = "emergency toolbox" - icon_state = "red" - inhand_icon_state = "toolbox_red" - material_flags = NONE - -/obj/item/storage/toolbox/emergency/PopulateContents() - new /obj/item/crowbar/red(src) - new /obj/item/weldingtool/mini(src) - new /obj/item/extinguisher/mini(src) - switch(rand(1,3)) - if(1) - new /obj/item/flashlight(src) - if(2) - new /obj/item/flashlight/glowstick(src) - if(3) - new /obj/item/flashlight/flare(src) - new /obj/item/radio/off(src) - -/obj/item/storage/toolbox/emergency/old - name = "rusty red toolbox" - icon_state = "toolbox_red_old" - has_latches = FALSE - material_flags = NONE - -/obj/item/storage/toolbox/mechanical - name = "mechanical toolbox" - icon_state = "blue" - inhand_icon_state = "toolbox_blue" - material_flags = NONE - -/obj/item/storage/toolbox/mechanical/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/analyzer(src) - new /obj/item/wirecutters(src) - -/obj/item/storage/toolbox/mechanical/old - name = "rusty blue toolbox" - icon_state = "toolbox_blue_old" - has_latches = FALSE - material_flags = NONE - -/obj/item/storage/toolbox/mechanical/old/heirloom - name = "toolbox" //this will be named "X family toolbox" - desc = "It's seen better days." - force = 5 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() - return - -/obj/item/storage/toolbox/mechanical/old/clean - name = "toolbox" - desc = "An old, blue toolbox, it looks robust." - icon_state = "oldtoolboxclean" - inhand_icon_state = "toolbox_blue" - has_latches = FALSE - force = 19 - throwforce = 22 - -/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage() - var/power = 0 - for (var/obj/item/stack/telecrystal/TC in GetAllContents()) - power += TC.amount - force = 19 + power - throwforce = 22 + power - -/obj/item/storage/toolbox/mechanical/old/clean/attack(mob/target, mob/living/user) - calc_damage() - ..() - -/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - calc_damage() - ..() - -/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/clothing/gloves/color/yellow(src) - -/obj/item/storage/toolbox/electrical - name = "electrical toolbox" - icon_state = "yellow" - inhand_icon_state = "toolbox_yellow" - material_flags = NONE - -/obj/item/storage/toolbox/electrical/PopulateContents() - var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") - new /obj/item/screwdriver(src) - new /obj/item/wirecutters(src) - new /obj/item/t_scanner(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - else - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - -/obj/item/storage/toolbox/syndicate - name = "suspicious looking toolbox" - icon_state = "syndicate" - inhand_icon_state = "toolbox_syndi" - force = 15 - throwforce = 18 - material_flags = NONE - -/obj/item/storage/toolbox/syndicate/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.silent = TRUE - -/obj/item/storage/toolbox/syndicate/PopulateContents() - new /obj/item/screwdriver/nuke(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/crowbar/red(src) - new /obj/item/wirecutters(src, "red") - new /obj/item/multitool(src) - new /obj/item/clothing/gloves/combat(src) - -/obj/item/storage/toolbox/drone - name = "mechanical toolbox" - icon_state = "blue" - inhand_icon_state = "toolbox_blue" - material_flags = NONE - -/obj/item/storage/toolbox/drone/PopulateContents() - var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - -/obj/item/storage/toolbox/artistic - name = "artistic toolbox" - desc = "A toolbox painted bright green. Why anyone would store art supplies in a toolbox is beyond you, but it has plenty of extra space." - icon_state = "green" - inhand_icon_state = "artistic_toolbox" - w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox! - material_flags = NONE - -/obj/item/storage/toolbox/artistic/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 20 - STR.max_items = 10 - -/obj/item/storage/toolbox/artistic/PopulateContents() - new /obj/item/storage/crayons(src) - new /obj/item/crowbar(src) - new /obj/item/stack/pipe_cleaner_coil/red(src) - new /obj/item/stack/pipe_cleaner_coil/yellow(src) - new /obj/item/stack/pipe_cleaner_coil/blue(src) - new /obj/item/stack/pipe_cleaner_coil/green(src) - new /obj/item/stack/pipe_cleaner_coil/pink(src) - new /obj/item/stack/pipe_cleaner_coil/orange(src) - new /obj/item/stack/pipe_cleaner_coil/cyan(src) - new /obj/item/stack/pipe_cleaner_coil/white(src) - -/obj/item/storage/toolbox/ammo - name = "ammo box" - desc = "It contains a few clips." - icon_state = "ammobox" - inhand_icon_state = "ammobox" - drop_sound = 'sound/items/handling/ammobox_drop.ogg' - pickup_sound = 'sound/items/handling/ammobox_pickup.ogg' - -/obj/item/storage/toolbox/ammo/PopulateContents() - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - -/obj/item/storage/toolbox/infiltrator - name = "insidious case" - desc = "Bearing the emblem of the Syndicate, this case contains a full infiltrator stealth suit, and has enough room to fit weaponry if necessary." - icon_state = "infiltrator_case" - inhand_icon_state = "infiltrator_case" - force = 15 - throwforce = 18 - w_class = WEIGHT_CLASS_NORMAL - has_latches = FALSE - -/obj/item/storage/toolbox/infiltrator/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.set_holdable(list( - /obj/item/clothing/head/helmet/infiltrator, - /obj/item/clothing/suit/armor/vest/infiltrator, - /obj/item/clothing/under/syndicate/bloodred, - /obj/item/clothing/gloves/color/latex/nitrile/infiltrator, - /obj/item/clothing/mask/infiltrator, - /obj/item/clothing/shoes/combat/sneakboots, - /obj/item/gun/ballistic/automatic/pistol, - /obj/item/gun/ballistic/revolver, - /obj/item/ammo_box - )) - -/obj/item/storage/toolbox/infiltrator/PopulateContents() - new /obj/item/clothing/head/helmet/infiltrator(src) - new /obj/item/clothing/suit/armor/vest/infiltrator(src) - new /obj/item/clothing/under/syndicate/bloodred(src) - new /obj/item/clothing/gloves/color/latex/nitrile/infiltrator(src) - new /obj/item/clothing/mask/infiltrator(src) - new /obj/item/clothing/shoes/combat/sneakboots(src) - -//floorbot assembly -/obj/item/storage/toolbox/attackby(obj/item/stack/tile/plasteel/T, mob/user, params) - var/list/allowed_toolbox = list(/obj/item/storage/toolbox/emergency, //which toolboxes can be made into floorbots - /obj/item/storage/toolbox/electrical, - /obj/item/storage/toolbox/mechanical, - /obj/item/storage/toolbox/artistic, - /obj/item/storage/toolbox/syndicate) - - if(!istype(T, /obj/item/stack/tile/plasteel)) - ..() - return - if(!is_type_in_list(src, allowed_toolbox) && (type != /obj/item/storage/toolbox)) - return - if(contents.len >= 1) - to_chat(user, "They won't fit in, as there is already stuff inside!") - return - if(T.use(10)) - var/obj/item/bot_assembly/floorbot/B = new - B.toolbox = type - switch(B.toolbox) - if(/obj/item/storage/toolbox) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/emergency) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/electrical) - B.toolbox_color = "y" - if(/obj/item/storage/toolbox/artistic) - B.toolbox_color = "g" - if(/obj/item/storage/toolbox/syndicate) - B.toolbox_color = "s" - user.put_in_hands(B) - B.update_icon() - to_chat(user, "You add the tiles into the empty [name]. They protrude from the top.") - qdel(src) - else - to_chat(user, "You need 10 floor tiles to start building a floorbot!") - return +/obj/item/storage/toolbox + name = "toolbox" + desc = "Danger. Very robust." + icon_state = "toolbox_default" + inhand_icon_state = "toolbox_default" + lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi' + flags_1 = CONDUCT_1 + force = 12 + throwforce = 12 + throw_speed = 2 + throw_range = 7 + w_class = WEIGHT_CLASS_BULKY + custom_materials = list(/datum/material/iron = 500) + attack_verb = list("robusted") + hitsound = 'sound/weapons/smash.ogg' + drop_sound = 'sound/items/handling/toolbox_drop.ogg' + pickup_sound = 'sound/items/handling/toolbox_pickup.ogg' + material_flags = MATERIAL_COLOR + var/latches = "single_latch" + var/has_latches = TRUE + wound_bonus = 5 + +/obj/item/storage/toolbox/Initialize() + . = ..() + if(has_latches) + if(prob(10)) + latches = "double_latch" + if(prob(1)) + latches = "triple_latch" + update_icon() + +/obj/item/storage/toolbox/update_overlays() + . = ..() + if(has_latches) + . += latches + + +/obj/item/storage/toolbox/suicide_act(mob/user) + user.visible_message("[user] robusts [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/storage/toolbox/emergency + name = "emergency toolbox" + icon_state = "red" + inhand_icon_state = "toolbox_red" + material_flags = NONE + +/obj/item/storage/toolbox/emergency/PopulateContents() + new /obj/item/crowbar/red(src) + new /obj/item/weldingtool/mini(src) + new /obj/item/extinguisher/mini(src) + switch(rand(1,3)) + if(1) + new /obj/item/flashlight(src) + if(2) + new /obj/item/flashlight/glowstick(src) + if(3) + new /obj/item/flashlight/flare(src) + new /obj/item/radio/off(src) + +/obj/item/storage/toolbox/emergency/old + name = "rusty red toolbox" + icon_state = "toolbox_red_old" + has_latches = FALSE + material_flags = NONE + +/obj/item/storage/toolbox/mechanical + name = "mechanical toolbox" + icon_state = "blue" + inhand_icon_state = "toolbox_blue" + material_flags = NONE + +/obj/item/storage/toolbox/mechanical/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/analyzer(src) + new /obj/item/wirecutters(src) + +/obj/item/storage/toolbox/mechanical/old + name = "rusty blue toolbox" + icon_state = "toolbox_blue_old" + has_latches = FALSE + material_flags = NONE + +/obj/item/storage/toolbox/mechanical/old/heirloom + name = "toolbox" //this will be named "X family toolbox" + desc = "It's seen better days." + force = 5 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() + return + +/obj/item/storage/toolbox/mechanical/old/clean + name = "toolbox" + desc = "An old, blue toolbox, it looks robust." + icon_state = "oldtoolboxclean" + inhand_icon_state = "toolbox_blue" + has_latches = FALSE + force = 19 + throwforce = 22 + +/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage() + var/power = 0 + for (var/obj/item/stack/telecrystal/TC in GetAllContents()) + power += TC.amount + force = 19 + power + throwforce = 22 + power + +/obj/item/storage/toolbox/mechanical/old/clean/attack(mob/target, mob/living/user) + calc_damage() + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + calc_damage() + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/clothing/gloves/color/yellow(src) + +/obj/item/storage/toolbox/electrical + name = "electrical toolbox" + icon_state = "yellow" + inhand_icon_state = "toolbox_yellow" + material_flags = NONE + +/obj/item/storage/toolbox/electrical/PopulateContents() + var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") + new /obj/item/screwdriver(src) + new /obj/item/wirecutters(src) + new /obj/item/t_scanner(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + else + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + +/obj/item/storage/toolbox/syndicate + name = "suspicious looking toolbox" + icon_state = "syndicate" + inhand_icon_state = "toolbox_syndi" + force = 15 + throwforce = 18 + material_flags = NONE + +/obj/item/storage/toolbox/syndicate/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.silent = TRUE + +/obj/item/storage/toolbox/syndicate/PopulateContents() + new /obj/item/screwdriver/nuke(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/crowbar/red(src) + new /obj/item/wirecutters(src, "red") + new /obj/item/multitool(src) + new /obj/item/clothing/gloves/combat(src) + +/obj/item/storage/toolbox/drone + name = "mechanical toolbox" + icon_state = "blue" + inhand_icon_state = "toolbox_blue" + material_flags = NONE + +/obj/item/storage/toolbox/drone/PopulateContents() + var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + +/obj/item/storage/toolbox/artistic + name = "artistic toolbox" + desc = "A toolbox painted bright green. Why anyone would store art supplies in a toolbox is beyond you, but it has plenty of extra space." + icon_state = "green" + inhand_icon_state = "artistic_toolbox" + w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox! + material_flags = NONE + +/obj/item/storage/toolbox/artistic/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 20 + STR.max_items = 10 + +/obj/item/storage/toolbox/artistic/PopulateContents() + new /obj/item/storage/crayons(src) + new /obj/item/crowbar(src) + new /obj/item/stack/pipe_cleaner_coil/red(src) + new /obj/item/stack/pipe_cleaner_coil/yellow(src) + new /obj/item/stack/pipe_cleaner_coil/blue(src) + new /obj/item/stack/pipe_cleaner_coil/green(src) + new /obj/item/stack/pipe_cleaner_coil/pink(src) + new /obj/item/stack/pipe_cleaner_coil/orange(src) + new /obj/item/stack/pipe_cleaner_coil/cyan(src) + new /obj/item/stack/pipe_cleaner_coil/white(src) + +/obj/item/storage/toolbox/ammo + name = "ammo box" + desc = "It contains a few clips." + icon_state = "ammobox" + inhand_icon_state = "ammobox" + drop_sound = 'sound/items/handling/ammobox_drop.ogg' + pickup_sound = 'sound/items/handling/ammobox_pickup.ogg' + +/obj/item/storage/toolbox/ammo/PopulateContents() + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + +/obj/item/storage/toolbox/infiltrator + name = "insidious case" + desc = "Bearing the emblem of the Syndicate, this case contains a full infiltrator stealth suit, and has enough room to fit weaponry if necessary." + icon_state = "infiltrator_case" + inhand_icon_state = "infiltrator_case" + force = 15 + throwforce = 18 + w_class = WEIGHT_CLASS_NORMAL + has_latches = FALSE + +/obj/item/storage/toolbox/infiltrator/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.set_holdable(list( + /obj/item/clothing/head/helmet/infiltrator, + /obj/item/clothing/suit/armor/vest/infiltrator, + /obj/item/clothing/under/syndicate/bloodred, + /obj/item/clothing/gloves/color/latex/nitrile/infiltrator, + /obj/item/clothing/mask/infiltrator, + /obj/item/clothing/shoes/combat/sneakboots, + /obj/item/gun/ballistic/automatic/pistol, + /obj/item/gun/ballistic/revolver, + /obj/item/ammo_box + )) + +/obj/item/storage/toolbox/infiltrator/PopulateContents() + new /obj/item/clothing/head/helmet/infiltrator(src) + new /obj/item/clothing/suit/armor/vest/infiltrator(src) + new /obj/item/clothing/under/syndicate/bloodred(src) + new /obj/item/clothing/gloves/color/latex/nitrile/infiltrator(src) + new /obj/item/clothing/mask/infiltrator(src) + new /obj/item/clothing/shoes/combat/sneakboots(src) + +//floorbot assembly +/obj/item/storage/toolbox/attackby(obj/item/stack/tile/plasteel/T, mob/user, params) + var/list/allowed_toolbox = list(/obj/item/storage/toolbox/emergency, //which toolboxes can be made into floorbots + /obj/item/storage/toolbox/electrical, + /obj/item/storage/toolbox/mechanical, + /obj/item/storage/toolbox/artistic, + /obj/item/storage/toolbox/syndicate) + + if(!istype(T, /obj/item/stack/tile/plasteel)) + ..() + return + if(!is_type_in_list(src, allowed_toolbox) && (type != /obj/item/storage/toolbox)) + return + if(contents.len >= 1) + to_chat(user, "They won't fit in, as there is already stuff inside!") + return + if(T.use(10)) + var/obj/item/bot_assembly/floorbot/B = new + B.toolbox = type + switch(B.toolbox) + if(/obj/item/storage/toolbox) + B.toolbox_color = "r" + if(/obj/item/storage/toolbox/emergency) + B.toolbox_color = "r" + if(/obj/item/storage/toolbox/electrical) + B.toolbox_color = "y" + if(/obj/item/storage/toolbox/artistic) + B.toolbox_color = "g" + if(/obj/item/storage/toolbox/syndicate) + B.toolbox_color = "s" + user.put_in_hands(B) + B.update_icon() + to_chat(user, "You add the tiles into the empty [name]. They protrude from the top.") + qdel(src) + else + to_chat(user, "You need 10 floor tiles to start building a floorbot!") + return diff --git a/code/game/objects/items/storage/wallets.dm b/code/game/objects/items/storage/wallets.dm index 0a16d24ccb8..521e52ab4d7 100644 --- a/code/game/objects/items/storage/wallets.dm +++ b/code/game/objects/items/storage/wallets.dm @@ -1,125 +1,125 @@ -/obj/item/storage/wallet - name = "wallet" - desc = "It can hold a few small and personal things." - icon_state = "wallet" - w_class = WEIGHT_CLASS_SMALL - resistance_flags = FLAMMABLE - slot_flags = ITEM_SLOT_ID - component_type = /datum/component/storage/concrete/wallet - - var/obj/item/card/id/front_id = null - var/list/combined_access - var/cached_flat_icon - -/obj/item/storage/wallet/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage/concrete/wallet) - STR.max_items = 4 - STR.set_holdable(list( - /obj/item/stack/spacecash, - /obj/item/holochip, - /obj/item/card, - /obj/item/clothing/mask/cigarette, - /obj/item/flashlight/pen, - /obj/item/seeds, - /obj/item/stack/medical, - /obj/item/toy/crayon, - /obj/item/coin, - /obj/item/dice, - /obj/item/disk, - /obj/item/implanter, - /obj/item/lighter, - /obj/item/lipstick, - /obj/item/match, - /obj/item/paper, - /obj/item/pen, - /obj/item/photo, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/syringe, - /obj/item/screwdriver, - /obj/item/stamp), - list(/obj/item/screwdriver/power)) - -/obj/item/storage/wallet/Exited(atom/movable/AM) - . = ..() - refreshID() - -/obj/item/storage/wallet/proc/refreshID() - LAZYCLEARLIST(combined_access) - if(!(front_id in src)) - front_id = null - for(var/obj/item/card/id/I in contents) - if(!front_id) - front_id = I - LAZYINITLIST(combined_access) - combined_access |= I.access - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - update_icon() - update_label() - -/obj/item/storage/wallet/Entered(atom/movable/AM) - . = ..() - refreshID() - -/obj/item/storage/wallet/update_overlays() - . = ..() - cached_flat_icon = null - if(front_id) - . += mutable_appearance(front_id.icon, front_id.icon_state) - . += front_id.overlays - . += mutable_appearance(icon, "wallet_overlay") - -/obj/item/storage/wallet/proc/get_cached_flat_icon() - if(!cached_flat_icon) - cached_flat_icon = getFlatIcon(src) - return cached_flat_icon - -/obj/item/storage/wallet/get_examine_string(mob/user, thats = FALSE) - if(front_id) - return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat - return ..() - -/obj/item/storage/wallet/proc/update_label() - if(front_id) - name = "wallet displaying [front_id]" - else - name = "wallet" - -/obj/item/storage/wallet/examine() - . = ..() - if(front_id) - . += "Alt-click to remove the id." - -/obj/item/storage/wallet/GetID() - return front_id - -/obj/item/storage/wallet/RemoveID() - if(!front_id) - return - . = front_id - front_id.forceMove(get_turf(src)) - -/obj/item/storage/wallet/InsertID(obj/item/inserting_item) - var/obj/item/card/inserting_id = inserting_item.RemoveID() - if(!inserting_id) - return FALSE - attackby(inserting_id) - if(inserting_id in contents) - return TRUE - return FALSE - -/obj/item/storage/wallet/GetAccess() - if(LAZYLEN(combined_access)) - return combined_access - else - return ..() - -/obj/item/storage/wallet/random - icon_state = "random_wallet" - -/obj/item/storage/wallet/random/PopulateContents() - new /obj/item/holochip(src, rand(5,30)) - icon_state = "wallet" +/obj/item/storage/wallet + name = "wallet" + desc = "It can hold a few small and personal things." + icon_state = "wallet" + w_class = WEIGHT_CLASS_SMALL + resistance_flags = FLAMMABLE + slot_flags = ITEM_SLOT_ID + component_type = /datum/component/storage/concrete/wallet + + var/obj/item/card/id/front_id = null + var/list/combined_access + var/cached_flat_icon + +/obj/item/storage/wallet/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage/concrete/wallet) + STR.max_items = 4 + STR.set_holdable(list( + /obj/item/stack/spacecash, + /obj/item/holochip, + /obj/item/card, + /obj/item/clothing/mask/cigarette, + /obj/item/flashlight/pen, + /obj/item/seeds, + /obj/item/stack/medical, + /obj/item/toy/crayon, + /obj/item/coin, + /obj/item/dice, + /obj/item/disk, + /obj/item/implanter, + /obj/item/lighter, + /obj/item/lipstick, + /obj/item/match, + /obj/item/paper, + /obj/item/pen, + /obj/item/photo, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/syringe, + /obj/item/screwdriver, + /obj/item/stamp), + list(/obj/item/screwdriver/power)) + +/obj/item/storage/wallet/Exited(atom/movable/AM) + . = ..() + refreshID() + +/obj/item/storage/wallet/proc/refreshID() + LAZYCLEARLIST(combined_access) + if(!(front_id in src)) + front_id = null + for(var/obj/item/card/id/I in contents) + if(!front_id) + front_id = I + LAZYINITLIST(combined_access) + combined_access |= I.access + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() + update_icon() + update_label() + +/obj/item/storage/wallet/Entered(atom/movable/AM) + . = ..() + refreshID() + +/obj/item/storage/wallet/update_overlays() + . = ..() + cached_flat_icon = null + if(front_id) + . += mutable_appearance(front_id.icon, front_id.icon_state) + . += front_id.overlays + . += mutable_appearance(icon, "wallet_overlay") + +/obj/item/storage/wallet/proc/get_cached_flat_icon() + if(!cached_flat_icon) + cached_flat_icon = getFlatIcon(src) + return cached_flat_icon + +/obj/item/storage/wallet/get_examine_string(mob/user, thats = FALSE) + if(front_id) + return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat + return ..() + +/obj/item/storage/wallet/proc/update_label() + if(front_id) + name = "wallet displaying [front_id]" + else + name = "wallet" + +/obj/item/storage/wallet/examine() + . = ..() + if(front_id) + . += "Alt-click to remove the id." + +/obj/item/storage/wallet/GetID() + return front_id + +/obj/item/storage/wallet/RemoveID() + if(!front_id) + return + . = front_id + front_id.forceMove(get_turf(src)) + +/obj/item/storage/wallet/InsertID(obj/item/inserting_item) + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return FALSE + attackby(inserting_id) + if(inserting_id in contents) + return TRUE + return FALSE + +/obj/item/storage/wallet/GetAccess() + if(LAZYLEN(combined_access)) + return combined_access + else + return ..() + +/obj/item/storage/wallet/random + icon_state = "random_wallet" + +/obj/item/storage/wallet/random/PopulateContents() + new /obj/item/holochip(src, rand(5,30)) + icon_state = "wallet" diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 6120e408fe3..7ff12aaf767 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -1,353 +1,353 @@ -/obj/item/melee/baton - name = "stun baton" - desc = "A stun baton for incapacitating people with." - - icon_state = "stunbaton" - inhand_icon_state = "baton" - worn_icon_state = "baton" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - - force = 10 - attack_verb = list("beaten") - - w_class = WEIGHT_CLASS_NORMAL - slot_flags = ITEM_SLOT_BELT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - - throwforce = 7 - var/throw_stun_chance = 35 - - var/obj/item/stock_parts/cell/cell - var/preload_cell_type //if not empty the baton starts with this type of cell - var/cell_hit_cost = 1000 - var/can_remove_cell = TRUE - - var/turned_on = FALSE - var/activate_sound = "sparks" - - var/attack_cooldown_check = 0 SECONDS - var/attack_cooldown = 2.5 SECONDS - var/stun_sound = 'sound/weapons/egloves.ogg' - - var/confusion_amt = 10 - var/stamina_loss_amt = 60 - var/apply_stun_delay = 2 SECONDS - var/stun_time = 5 SECONDS - - var/convertible = TRUE //if it can be converted with a conversion kit - -/obj/item/melee/baton/get_cell() - return cell - -/obj/item/melee/baton/suicide_act(mob/user) - if(cell && cell.charge && turned_on) - user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") - . = (FIRELOSS) - attack(user,user) - else - user.visible_message("[user] is shoving the [name] down their throat! It looks like [user.p_theyre()] trying to commit suicide!") - . = (OXYLOSS) - -/obj/item/melee/baton/Initialize() - . = ..() - if(preload_cell_type) - if(!ispath(preload_cell_type,/obj/item/stock_parts/cell)) - log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") - else - cell = new preload_cell_type(src) - update_icon() - RegisterSignal(src, COMSIG_PARENT_ATTACKBY, .proc/convert) - - -/obj/item/melee/baton/Destroy() - if(cell) - QDEL_NULL(cell) - UnregisterSignal(src, COMSIG_PARENT_ATTACKBY) - return ..() - -/obj/item/melee/baton/proc/convert(datum/source, obj/item/I, mob/user) - if(istype(I,/obj/item/conversion_kit) && convertible) - var/turf/T = get_turf(src) - var/obj/item/melee/classic_baton/B = new /obj/item/melee/classic_baton (T) - B.alpha = 20 - playsound(T, 'sound/items/drill_use.ogg', 80, TRUE, -1) - animate(src, alpha = 0, time = 10) - animate(B, alpha = 255, time = 10) - qdel(I) - qdel(src) - -/obj/item/melee/baton/handle_atom_del(atom/A) - if(A == cell) - cell = null - turned_on = FALSE - update_icon() - return ..() - -/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - ..() - //Only mob/living types have stun handling - if(turned_on && prob(throw_stun_chance) && iscarbon(hit_atom)) - baton_effect(hit_atom) - -/obj/item/melee/baton/loaded //this one starts with a cell pre-installed. - preload_cell_type = /obj/item/stock_parts/cell/high - -/obj/item/melee/baton/proc/deductcharge(chrgdeductamt) - if(cell) - //Note this value returned is significant, as it will determine - //if a stun is applied or not - . = cell.use(chrgdeductamt) - if(turned_on && cell.charge < cell_hit_cost) - //we're below minimum, turn off - turned_on = FALSE - update_icon() - playsound(src, activate_sound, 75, TRUE, -1) - - -/obj/item/melee/baton/update_icon_state() - if(turned_on) - icon_state = "[initial(icon_state)]_active" - else if(!cell) - icon_state = "[initial(icon_state)]_nocell" - else - icon_state = "[initial(icon_state)]" - -/obj/item/melee/baton/examine(mob/user) - . = ..() - if(cell) - . += "\The [src] is [round(cell.percent())]% charged." - else - . += "\The [src] does not have a power source installed." - -/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell)) - var/obj/item/stock_parts/cell/C = W - if(cell) - to_chat(user, "[src] already has a cell!") - else - if(C.maxcharge < cell_hit_cost) - to_chat(user, "[src] requires a higher capacity cell.") - return - if(!user.transferItemToLoc(W, src)) - return - cell = W - to_chat(user, "You install a cell in [src].") - update_icon() - - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - tryremovecell(user) - else - return ..() - -/obj/item/melee/baton/proc/tryremovecell(mob/user) - if(cell && can_remove_cell) - cell.update_icon() - cell.forceMove(get_turf(src)) - cell = null - to_chat(user, "You remove the cell from [src].") - turned_on = FALSE - update_icon() - -/obj/item/melee/baton/attack_self(mob/user) - toggle_on(user) - -/obj/item/melee/baton/proc/toggle_on(mob/user) - if(cell && cell.charge > cell_hit_cost) - turned_on = !turned_on - to_chat(user, "[src] is now [turned_on ? "on" : "off"].") - playsound(src, activate_sound, 75, TRUE, -1) - else - turned_on = FALSE - if(!cell) - to_chat(user, "[src] does not have a power source!") - else - to_chat(user, "[src] is out of charge.") - update_icon() - add_fingerprint(user) - -/obj/item/melee/baton/proc/clumsy_check(mob/living/carbon/human/user) - if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - playsound(src, stun_sound, 75, TRUE, -1) - user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ - "You accidentally hit yourself with [src]!") - user.Knockdown(stun_time*3) //should really be an equivalent to attack(user,user) - deductcharge(cell_hit_cost) - return TRUE - return FALSE - -/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) - if(clumsy_check(user)) - return FALSE - - if(iscyborg(M)) - ..() - return - - - if(ishuman(M)) - var/mob/living/carbon/human/L = M - if(check_martial_counter(L, user)) - return - - if(user.a_intent != INTENT_HARM) - if(turned_on) - if(attack_cooldown_check <= world.time) - if(baton_effect(M, user)) - user.do_attack_animation(M) - return - else - to_chat(user, "The baton is still charging!") - else - M.visible_message("[user] prods [M] with [src]. Luckily it was off.", \ - "[user] prods you with [src]. Luckily it was off.") - else - if(turned_on) - if(attack_cooldown_check <= world.time) - baton_effect(M, user) - ..() - - -/obj/item/melee/baton/proc/baton_effect(mob/living/L, mob/user) - if(shields_blocked(L, user)) - return FALSE - if(HAS_TRAIT_FROM(L, TRAIT_IWASBATONED, user)) //no doublebaton abuse anon! - to_chat(user, "[L] manages to avoid the attack!") - return FALSE - if(iscyborg(loc)) - var/mob/living/silicon/robot/R = loc - if(!R || !R.cell || !R.cell.use(cell_hit_cost)) - return FALSE - else - if(!deductcharge(cell_hit_cost)) - return FALSE - /// After a target is hit, we do a chunk of stamina damage, along with other effects. - /// After a period of time, we then check to see what stun duration we give. - L.Jitter(20) - L.confused = max(confusion_amt, L.confused) - L.stuttering = max(8, L.stuttering) - L.apply_damage(stamina_loss_amt, STAMINA, BODY_ZONE_CHEST) - - SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) - addtimer(CALLBACK(src, .proc/apply_stun_effect_end, L), apply_stun_delay) - - if(user) - L.lastattacker = user.real_name - L.lastattackerckey = user.ckey - L.visible_message("[user] stuns [L] with [src]!", \ - "[user] stuns you with [src]!") - log_combat(user, L, "stunned") - - playsound(src, stun_sound, 50, TRUE, -1) - - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.forcesay(GLOB.hit_appends) - - attack_cooldown_check = world.time + attack_cooldown - - ADD_TRAIT(L, TRAIT_IWASBATONED, user) - addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_IWASBATONED, user), attack_cooldown) - - return 1 - -/// After the initial stun period, we check to see if the target needs to have the stun applied. -/obj/item/melee/baton/proc/apply_stun_effect_end(mob/living/target) - var/trait_check = HAS_TRAIT(target, TRAIT_STUNRESISTANCE) //var since we check it in out to_chat as well as determine stun duration - if(!target.IsKnockdown()) - to_chat(target, "Your muscles seize, making you collapse[trait_check ? ", but your body quickly recovers..." : "!"]") - - if(trait_check) - target.Knockdown(stun_time * 0.1) - else - target.Knockdown(stun_time) - -/obj/item/melee/baton/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_SELF)) - deductcharge(1000 / severity) - -/obj/item/melee/baton/proc/shields_blocked(mob/living/L, mob/user) - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that - playsound(H, 'sound/weapons/genhit.ogg', 50, TRUE) - return TRUE - return FALSE - -//Makeshift stun baton. Replacement for stun gloves. -/obj/item/melee/baton/cattleprod - name = "stunprod" - desc = "An improvised stun baton." - icon_state = "stunprod" - inhand_icon_state = "prod" - worn_icon_state = null - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - force = 3 - throwforce = 5 - stun_time = 5 SECONDS - cell_hit_cost = 2000 - throw_stun_chance = 10 - slot_flags = ITEM_SLOT_BACK - convertible = FALSE - var/obj/item/assembly/igniter/sparkler = 0 - -/obj/item/melee/baton/cattleprod/Initialize() - . = ..() - sparkler = new (src) - -/obj/item/melee/baton/cattleprod/baton_effect() - if(sparkler.activate()) - ..() - -/obj/item/melee/baton/cattleprod/Destroy() - if(sparkler) - QDEL_NULL(sparkler) - return ..() - -/obj/item/melee/baton/boomerang - name = "\improper OZtek Boomerang" - desc = "A device invented in 2486 for the great Space Emu War by the confederacy of Australicus, these high-tech boomerangs also work exceptionally well at stunning crewmembers. Just be careful to catch it when thrown!" - throw_speed = 1 - icon_state = "boomerang" - inhand_icon_state = "boomerang" - force = 5 - throwforce = 5 - throw_range = 5 - cell_hit_cost = 2000 - throw_stun_chance = 99 //Have you prayed today? - convertible = FALSE - custom_materials = list(/datum/material/iron = 10000, /datum/material/glass = 4000, /datum/material/silver = 10000, /datum/material/gold = 2000) - -/obj/item/melee/baton/boomerang/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - if(turned_on) - if(ishuman(thrower)) - var/mob/living/carbon/human/H = thrower - H.throw_mode_off() //so they can catch it on the return. - return ..() - -/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(turned_on) - var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum) - if(ishuman(hit_atom) && !caught && prob(throw_stun_chance))//if they are a carbon and they didn't catch it - baton_effect(hit_atom) - if(thrownby && !caught) - sleep(1) - if(!QDELETED(src)) - throw_at(thrownby, throw_range+2, throw_speed, null, TRUE) - else - return ..() - - -/obj/item/melee/baton/boomerang/update_icon_state() - if(turned_on) - icon_state = "[initial(icon_state)]_active" - else if(!cell) - icon_state = "[initial(icon_state)]_nocell" - else - icon_state = "[initial(icon_state)]" - -/obj/item/melee/baton/boomerang/loaded //Same as above, comes with a cell. - preload_cell_type = /obj/item/stock_parts/cell/high +/obj/item/melee/baton + name = "stun baton" + desc = "A stun baton for incapacitating people with." + + icon_state = "stunbaton" + inhand_icon_state = "baton" + worn_icon_state = "baton" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + + force = 10 + attack_verb = list("beaten") + + w_class = WEIGHT_CLASS_NORMAL + slot_flags = ITEM_SLOT_BELT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + + throwforce = 7 + var/throw_stun_chance = 35 + + var/obj/item/stock_parts/cell/cell + var/preload_cell_type //if not empty the baton starts with this type of cell + var/cell_hit_cost = 1000 + var/can_remove_cell = TRUE + + var/turned_on = FALSE + var/activate_sound = "sparks" + + var/attack_cooldown_check = 0 SECONDS + var/attack_cooldown = 2.5 SECONDS + var/stun_sound = 'sound/weapons/egloves.ogg' + + var/confusion_amt = 10 + var/stamina_loss_amt = 60 + var/apply_stun_delay = 2 SECONDS + var/stun_time = 5 SECONDS + + var/convertible = TRUE //if it can be converted with a conversion kit + +/obj/item/melee/baton/get_cell() + return cell + +/obj/item/melee/baton/suicide_act(mob/user) + if(cell && cell.charge && turned_on) + user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") + . = (FIRELOSS) + attack(user,user) + else + user.visible_message("[user] is shoving the [name] down their throat! It looks like [user.p_theyre()] trying to commit suicide!") + . = (OXYLOSS) + +/obj/item/melee/baton/Initialize() + . = ..() + if(preload_cell_type) + if(!ispath(preload_cell_type,/obj/item/stock_parts/cell)) + log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") + else + cell = new preload_cell_type(src) + update_icon() + RegisterSignal(src, COMSIG_PARENT_ATTACKBY, .proc/convert) + + +/obj/item/melee/baton/Destroy() + if(cell) + QDEL_NULL(cell) + UnregisterSignal(src, COMSIG_PARENT_ATTACKBY) + return ..() + +/obj/item/melee/baton/proc/convert(datum/source, obj/item/I, mob/user) + if(istype(I,/obj/item/conversion_kit) && convertible) + var/turf/T = get_turf(src) + var/obj/item/melee/classic_baton/B = new /obj/item/melee/classic_baton (T) + B.alpha = 20 + playsound(T, 'sound/items/drill_use.ogg', 80, TRUE, -1) + animate(src, alpha = 0, time = 10) + animate(B, alpha = 255, time = 10) + qdel(I) + qdel(src) + +/obj/item/melee/baton/handle_atom_del(atom/A) + if(A == cell) + cell = null + turned_on = FALSE + update_icon() + return ..() + +/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + ..() + //Only mob/living types have stun handling + if(turned_on && prob(throw_stun_chance) && iscarbon(hit_atom)) + baton_effect(hit_atom) + +/obj/item/melee/baton/loaded //this one starts with a cell pre-installed. + preload_cell_type = /obj/item/stock_parts/cell/high + +/obj/item/melee/baton/proc/deductcharge(chrgdeductamt) + if(cell) + //Note this value returned is significant, as it will determine + //if a stun is applied or not + . = cell.use(chrgdeductamt) + if(turned_on && cell.charge < cell_hit_cost) + //we're below minimum, turn off + turned_on = FALSE + update_icon() + playsound(src, activate_sound, 75, TRUE, -1) + + +/obj/item/melee/baton/update_icon_state() + if(turned_on) + icon_state = "[initial(icon_state)]_active" + else if(!cell) + icon_state = "[initial(icon_state)]_nocell" + else + icon_state = "[initial(icon_state)]" + +/obj/item/melee/baton/examine(mob/user) + . = ..() + if(cell) + . += "\The [src] is [round(cell.percent())]% charged." + else + . += "\The [src] does not have a power source installed." + +/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell)) + var/obj/item/stock_parts/cell/C = W + if(cell) + to_chat(user, "[src] already has a cell!") + else + if(C.maxcharge < cell_hit_cost) + to_chat(user, "[src] requires a higher capacity cell.") + return + if(!user.transferItemToLoc(W, src)) + return + cell = W + to_chat(user, "You install a cell in [src].") + update_icon() + + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + tryremovecell(user) + else + return ..() + +/obj/item/melee/baton/proc/tryremovecell(mob/user) + if(cell && can_remove_cell) + cell.update_icon() + cell.forceMove(get_turf(src)) + cell = null + to_chat(user, "You remove the cell from [src].") + turned_on = FALSE + update_icon() + +/obj/item/melee/baton/attack_self(mob/user) + toggle_on(user) + +/obj/item/melee/baton/proc/toggle_on(mob/user) + if(cell && cell.charge > cell_hit_cost) + turned_on = !turned_on + to_chat(user, "[src] is now [turned_on ? "on" : "off"].") + playsound(src, activate_sound, 75, TRUE, -1) + else + turned_on = FALSE + if(!cell) + to_chat(user, "[src] does not have a power source!") + else + to_chat(user, "[src] is out of charge.") + update_icon() + add_fingerprint(user) + +/obj/item/melee/baton/proc/clumsy_check(mob/living/carbon/human/user) + if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + playsound(src, stun_sound, 75, TRUE, -1) + user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ + "You accidentally hit yourself with [src]!") + user.Knockdown(stun_time*3) //should really be an equivalent to attack(user,user) + deductcharge(cell_hit_cost) + return TRUE + return FALSE + +/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) + if(clumsy_check(user)) + return FALSE + + if(iscyborg(M)) + ..() + return + + + if(ishuman(M)) + var/mob/living/carbon/human/L = M + if(check_martial_counter(L, user)) + return + + if(user.a_intent != INTENT_HARM) + if(turned_on) + if(attack_cooldown_check <= world.time) + if(baton_effect(M, user)) + user.do_attack_animation(M) + return + else + to_chat(user, "The baton is still charging!") + else + M.visible_message("[user] prods [M] with [src]. Luckily it was off.", \ + "[user] prods you with [src]. Luckily it was off.") + else + if(turned_on) + if(attack_cooldown_check <= world.time) + baton_effect(M, user) + ..() + + +/obj/item/melee/baton/proc/baton_effect(mob/living/L, mob/user) + if(shields_blocked(L, user)) + return FALSE + if(HAS_TRAIT_FROM(L, TRAIT_IWASBATONED, user)) //no doublebaton abuse anon! + to_chat(user, "[L] manages to avoid the attack!") + return FALSE + if(iscyborg(loc)) + var/mob/living/silicon/robot/R = loc + if(!R || !R.cell || !R.cell.use(cell_hit_cost)) + return FALSE + else + if(!deductcharge(cell_hit_cost)) + return FALSE + /// After a target is hit, we do a chunk of stamina damage, along with other effects. + /// After a period of time, we then check to see what stun duration we give. + L.Jitter(20) + L.confused = max(confusion_amt, L.confused) + L.stuttering = max(8, L.stuttering) + L.apply_damage(stamina_loss_amt, STAMINA, BODY_ZONE_CHEST) + + SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) + addtimer(CALLBACK(src, .proc/apply_stun_effect_end, L), apply_stun_delay) + + if(user) + L.lastattacker = user.real_name + L.lastattackerckey = user.ckey + L.visible_message("[user] stuns [L] with [src]!", \ + "[user] stuns you with [src]!") + log_combat(user, L, "stunned") + + playsound(src, stun_sound, 50, TRUE, -1) + + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.forcesay(GLOB.hit_appends) + + attack_cooldown_check = world.time + attack_cooldown + + ADD_TRAIT(L, TRAIT_IWASBATONED, user) + addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_IWASBATONED, user), attack_cooldown) + + return 1 + +/// After the initial stun period, we check to see if the target needs to have the stun applied. +/obj/item/melee/baton/proc/apply_stun_effect_end(mob/living/target) + var/trait_check = HAS_TRAIT(target, TRAIT_STUNRESISTANCE) //var since we check it in out to_chat as well as determine stun duration + if(!target.IsKnockdown()) + to_chat(target, "Your muscles seize, making you collapse[trait_check ? ", but your body quickly recovers..." : "!"]") + + if(trait_check) + target.Knockdown(stun_time * 0.1) + else + target.Knockdown(stun_time) + +/obj/item/melee/baton/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_SELF)) + deductcharge(1000 / severity) + +/obj/item/melee/baton/proc/shields_blocked(mob/living/L, mob/user) + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + playsound(H, 'sound/weapons/genhit.ogg', 50, TRUE) + return TRUE + return FALSE + +//Makeshift stun baton. Replacement for stun gloves. +/obj/item/melee/baton/cattleprod + name = "stunprod" + desc = "An improvised stun baton." + icon_state = "stunprod" + inhand_icon_state = "prod" + worn_icon_state = null + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + force = 3 + throwforce = 5 + stun_time = 5 SECONDS + cell_hit_cost = 2000 + throw_stun_chance = 10 + slot_flags = ITEM_SLOT_BACK + convertible = FALSE + var/obj/item/assembly/igniter/sparkler = 0 + +/obj/item/melee/baton/cattleprod/Initialize() + . = ..() + sparkler = new (src) + +/obj/item/melee/baton/cattleprod/baton_effect() + if(sparkler.activate()) + ..() + +/obj/item/melee/baton/cattleprod/Destroy() + if(sparkler) + QDEL_NULL(sparkler) + return ..() + +/obj/item/melee/baton/boomerang + name = "\improper OZtek Boomerang" + desc = "A device invented in 2486 for the great Space Emu War by the confederacy of Australicus, these high-tech boomerangs also work exceptionally well at stunning crewmembers. Just be careful to catch it when thrown!" + throw_speed = 1 + icon_state = "boomerang" + inhand_icon_state = "boomerang" + force = 5 + throwforce = 5 + throw_range = 5 + cell_hit_cost = 2000 + throw_stun_chance = 99 //Have you prayed today? + convertible = FALSE + custom_materials = list(/datum/material/iron = 10000, /datum/material/glass = 4000, /datum/material/silver = 10000, /datum/material/gold = 2000) + +/obj/item/melee/baton/boomerang/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + if(turned_on) + if(ishuman(thrower)) + var/mob/living/carbon/human/H = thrower + H.throw_mode_off() //so they can catch it on the return. + return ..() + +/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(turned_on) + var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum) + if(ishuman(hit_atom) && !caught && prob(throw_stun_chance))//if they are a carbon and they didn't catch it + baton_effect(hit_atom) + if(thrownby && !caught) + sleep(1) + if(!QDELETED(src)) + throw_at(thrownby, throw_range+2, throw_speed, null, TRUE) + else + return ..() + + +/obj/item/melee/baton/boomerang/update_icon_state() + if(turned_on) + icon_state = "[initial(icon_state)]_active" + else if(!cell) + icon_state = "[initial(icon_state)]_nocell" + else + icon_state = "[initial(icon_state)]" + +/obj/item/melee/baton/boomerang/loaded //Same as above, comes with a cell. + preload_cell_type = /obj/item/stock_parts/cell/high diff --git a/code/game/objects/items/tanks/tank_types.dm b/code/game/objects/items/tanks/tank_types.dm index 0e44d624ff3..a14d842b647 100644 --- a/code/game/objects/items/tanks/tank_types.dm +++ b/code/game/objects/items/tanks/tank_types.dm @@ -1,188 +1,188 @@ -/* Types of tanks! - * Contains: - * Oxygen - * Anesthetic - * Air - * Plasma - * Emergency Oxygen - * Generic - */ - -/* - * Oxygen - */ -/obj/item/tank/internals/oxygen - name = "oxygen tank" - desc = "A tank of oxygen, this one is blue." - icon_state = "oxygen" - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - force = 10 - dog_fashion = /datum/dog_fashion/back - - -/obj/item/tank/internals/oxygen/populate_gas() - air_contents.assert_gas(/datum/gas/oxygen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - - -/obj/item/tank/internals/oxygen/yellow - desc = "A tank of oxygen, this one is yellow." - icon_state = "oxygen_f" - dog_fashion = null - -/obj/item/tank/internals/oxygen/red - desc = "A tank of oxygen, this one is red." - icon_state = "oxygen_fr" - dog_fashion = null - -/obj/item/tank/internals/oxygen/empty/populate_gas() - return - -/* - * Anesthetic - */ -/obj/item/tank/internals/anesthetic - name = "anesthetic tank" - desc = "A tank with an N2O/O2 gas mix." - icon_state = "anesthetic" - inhand_icon_state = "an_tank" - force = 10 - -/obj/item/tank/internals/anesthetic/populate_gas() - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide) - air_contents.gases[/datum/gas/oxygen][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD - -/* - * Plasma - */ -/obj/item/tank/internals/plasma - name = "plasma tank" - desc = "Contains dangerous plasma. Do not inhale. Warning: extremely flammable." - icon_state = "plasma" - flags_1 = CONDUCT_1 - slot_flags = null //they have no straps! - force = 8 - - -/obj/item/tank/internals/plasma/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/internals/plasma/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/flamethrower)) - var/obj/item/flamethrower/F = W - if ((!F.status)||(F.ptank)) - return - if(!user.transferItemToLoc(src, F)) - return - src.master = F - F.ptank = src - F.update_icon() - else - return ..() - -/obj/item/tank/internals/plasma/full/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/internals/plasma/empty/populate_gas() - return - -/* - * Plasmaman Plasma Tank - */ - -/obj/item/tank/internals/plasmaman - name = "plasma internals tank" - desc = "A tank of plasma gas designed specifically for use as internals, particularly for plasma-based lifeforms. If you're not a Plasmaman, you probably shouldn't use this." - icon_state = "plasmaman_tank" - inhand_icon_state = "plasmaman_tank" - force = 10 - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - -/obj/item/tank/internals/plasmaman/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/internals/plasmaman/full/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - - -/obj/item/tank/internals/plasmaman/belt - icon_state = "plasmaman_tank_belt" - inhand_icon_state = "plasmaman_tank_belt" - worn_icon_state = "plasmaman_tank_belt" - worn_icon = null - slot_flags = ITEM_SLOT_BELT - force = 5 - volume = 6 - w_class = WEIGHT_CLASS_SMALL //thanks i forgot this - -/obj/item/tank/internals/plasmaman/belt/full/populate_gas() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/internals/plasmaman/belt/empty/populate_gas() - return - - - -/* - * Emergency Oxygen - */ -/obj/item/tank/internals/emergency_oxygen - name = "emergency oxygen tank" - desc = "Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it." - icon_state = "emergency" - worn_icon_state = "emergency" - worn_icon = null - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - force = 4 - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - volume = 1 //Tiny. Real life equivalents only have 21 breaths of oxygen in them. They're EMERGENCY tanks anyway -errorage (dangercon 2011) - - -/obj/item/tank/internals/emergency_oxygen/populate_gas() - air_contents.assert_gas(/datum/gas/oxygen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - - -/obj/item/tank/internals/emergency_oxygen/empty/populate_gas() - return - -/obj/item/tank/internals/emergency_oxygen/engi - name = "extended-capacity emergency oxygen tank" - icon_state = "emergency_engi" - worn_icon_state = "emergency_engi" - worn_icon = null - volume = 2 // should last a bit over 30 minutes if full - -/obj/item/tank/internals/emergency_oxygen/engi/empty/populate_gas() - return - -/obj/item/tank/internals/emergency_oxygen/double - name = "double emergency oxygen tank" - icon_state = "emergency_double" - volume = 8 - -/obj/item/tank/internals/emergency_oxygen/double/empty/populate_gas() - return - -// * -// * GENERIC -// * - -/obj/item/tank/internals/generic - name = "gas tank" - desc = "A generic tank used for storing and transporting gasses. Can be used for internals." - icon_state = "generic" - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - force = 10 - dog_fashion = /datum/dog_fashion/back - -/obj/item/tank/internals/generic/populate_gas() - return +/* Types of tanks! + * Contains: + * Oxygen + * Anesthetic + * Air + * Plasma + * Emergency Oxygen + * Generic + */ + +/* + * Oxygen + */ +/obj/item/tank/internals/oxygen + name = "oxygen tank" + desc = "A tank of oxygen, this one is blue." + icon_state = "oxygen" + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + force = 10 + dog_fashion = /datum/dog_fashion/back + + +/obj/item/tank/internals/oxygen/populate_gas() + air_contents.assert_gas(/datum/gas/oxygen) + air_contents.gases[/datum/gas/oxygen][MOLES] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + + +/obj/item/tank/internals/oxygen/yellow + desc = "A tank of oxygen, this one is yellow." + icon_state = "oxygen_f" + dog_fashion = null + +/obj/item/tank/internals/oxygen/red + desc = "A tank of oxygen, this one is red." + icon_state = "oxygen_fr" + dog_fashion = null + +/obj/item/tank/internals/oxygen/empty/populate_gas() + return + +/* + * Anesthetic + */ +/obj/item/tank/internals/anesthetic + name = "anesthetic tank" + desc = "A tank with an N2O/O2 gas mix." + icon_state = "anesthetic" + inhand_icon_state = "an_tank" + force = 10 + +/obj/item/tank/internals/anesthetic/populate_gas() + air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide) + air_contents.gases[/datum/gas/oxygen][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + +/* + * Plasma + */ +/obj/item/tank/internals/plasma + name = "plasma tank" + desc = "Contains dangerous plasma. Do not inhale. Warning: extremely flammable." + icon_state = "plasma" + flags_1 = CONDUCT_1 + slot_flags = null //they have no straps! + force = 8 + + +/obj/item/tank/internals/plasma/populate_gas() + air_contents.assert_gas(/datum/gas/plasma) + air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/internals/plasma/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/flamethrower)) + var/obj/item/flamethrower/F = W + if ((!F.status)||(F.ptank)) + return + if(!user.transferItemToLoc(src, F)) + return + src.master = F + F.ptank = src + F.update_icon() + else + return ..() + +/obj/item/tank/internals/plasma/full/populate_gas() + air_contents.assert_gas(/datum/gas/plasma) + air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/internals/plasma/empty/populate_gas() + return + +/* + * Plasmaman Plasma Tank + */ + +/obj/item/tank/internals/plasmaman + name = "plasma internals tank" + desc = "A tank of plasma gas designed specifically for use as internals, particularly for plasma-based lifeforms. If you're not a Plasmaman, you probably shouldn't use this." + icon_state = "plasmaman_tank" + inhand_icon_state = "plasmaman_tank" + force = 10 + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + +/obj/item/tank/internals/plasmaman/populate_gas() + air_contents.assert_gas(/datum/gas/plasma) + air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/internals/plasmaman/full/populate_gas() + air_contents.assert_gas(/datum/gas/plasma) + air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + + +/obj/item/tank/internals/plasmaman/belt + icon_state = "plasmaman_tank_belt" + inhand_icon_state = "plasmaman_tank_belt" + worn_icon_state = "plasmaman_tank_belt" + worn_icon = null + slot_flags = ITEM_SLOT_BELT + force = 5 + volume = 6 + w_class = WEIGHT_CLASS_SMALL //thanks i forgot this + +/obj/item/tank/internals/plasmaman/belt/full/populate_gas() + air_contents.assert_gas(/datum/gas/plasma) + air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/internals/plasmaman/belt/empty/populate_gas() + return + + + +/* + * Emergency Oxygen + */ +/obj/item/tank/internals/emergency_oxygen + name = "emergency oxygen tank" + desc = "Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it." + icon_state = "emergency" + worn_icon_state = "emergency" + worn_icon = null + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + force = 4 + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + volume = 1 //Tiny. Real life equivalents only have 21 breaths of oxygen in them. They're EMERGENCY tanks anyway -errorage (dangercon 2011) + + +/obj/item/tank/internals/emergency_oxygen/populate_gas() + air_contents.assert_gas(/datum/gas/oxygen) + air_contents.gases[/datum/gas/oxygen][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + + +/obj/item/tank/internals/emergency_oxygen/empty/populate_gas() + return + +/obj/item/tank/internals/emergency_oxygen/engi + name = "extended-capacity emergency oxygen tank" + icon_state = "emergency_engi" + worn_icon_state = "emergency_engi" + worn_icon = null + volume = 2 // should last a bit over 30 minutes if full + +/obj/item/tank/internals/emergency_oxygen/engi/empty/populate_gas() + return + +/obj/item/tank/internals/emergency_oxygen/double + name = "double emergency oxygen tank" + icon_state = "emergency_double" + volume = 8 + +/obj/item/tank/internals/emergency_oxygen/double/empty/populate_gas() + return + +// * +// * GENERIC +// * + +/obj/item/tank/internals/generic + name = "gas tank" + desc = "A generic tank used for storing and transporting gasses. Can be used for internals." + icon_state = "generic" + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + force = 10 + dog_fashion = /datum/dog_fashion/back + +/obj/item/tank/internals/generic/populate_gas() + return diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index 1014fa2b032..56f6f11e3d9 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -1,222 +1,222 @@ -#define SOURCE_PORTAL 1 -#define DESTINATION_PORTAL 2 - -/* Teleportation devices. - * Contains: - * Locator - * Hand-tele - */ - -/* - * Locator - */ -/obj/item/locator - name = "bluespace locator" - desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - var/temp = null - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_SMALL - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=400) - var/tracking_range = 20 - -/obj/item/locator/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "BluespaceLocator", name, 300, 300, master_ui, state) - ui.open() - -/obj/item/locator/ui_data(mob/user) - var/list/data = list() - - data["trackingrange"] = tracking_range; - - // Get our current turf location. - var/turf/sr = get_turf(src) - - if (sr) - // Check every teleport beacon. - var/list/tele_beacons = list() - for(var/obj/item/beacon/W in GLOB.teleportbeacons) - - // Get the tracking beacon's turf location. - var/turf/tr = get_turf(W) - - // Make sure it's on a turf and that its Z-level matches the tracker's Z-level - if (tr && tr.z == sr.z) - // Get the distance between the beacon's turf and our turf - var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - - // If the target is too far away, skip over this beacon. - if(distance > tracking_range) - continue - - var/beacon_name - - if(W.renamed) - beacon_name = W.name - else - var/area/A = get_area(W) - beacon_name = A.name - - var/D = dir2text(get_dir(sr, tr)) - tele_beacons += list(list(name = beacon_name, direction = D, distance = distance)) - - data["telebeacons"] = tele_beacons - - var/list/track_implants = list() - - for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) - if (!W.imp_in || !isliving(W.loc)) - continue - else - var/mob/living/M = W.loc - if (M.stat == DEAD) - if (M.timeofdeath + W.lifespan_postmortem < world.time) - continue - var/turf/tr = get_turf(W) - var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - - if(distance > tracking_range) - continue - - var/D = dir2text(get_dir(sr, tr)) - track_implants += list(list(name = W.imp_in.name, direction = D, distance = distance)) - data["trackimplants"] = track_implants - return data - -/obj/machinery/my_machine/ui_act(action, params) - if(..()) return - -/* - * Hand-tele - */ -/obj/item/hand_tele - name = "hand tele" - desc = "A portable item using blue-space technology." - icon = 'icons/obj/device.dmi' - icon_state = "hand_tele" - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=10000) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - var/list/active_portal_pairs - var/max_portal_pairs = 3 - var/atmos_link_override - -/obj/item/hand_tele/Initialize() - . = ..() - active_portal_pairs = list() - -/obj/item/hand_tele/pre_attack(atom/target, mob/user, params) - if(try_dispel_portal(target, user)) - return TRUE - return ..() - -/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user) - if(is_parent_of_portal(target)) - qdel(target) - to_chat(user, "You dispel [target] with \the [src]!") - return TRUE - return FALSE - -/obj/item/hand_tele/afterattack(atom/target, mob/user) - try_dispel_portal(target, user) - . = ..() - -/obj/item/hand_tele/attack_self(mob/user) - var/turf/current_location = get_turf(user)//What turf is the user on? - var/area/current_area = current_location.loc - if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf - to_chat(user, "\The [src] is malfunctioning.") - return - var/list/L = list( ) - for(var/obj/machinery/computer/teleporter/com in GLOB.machines) - if(com.target) - var/area/A = get_area(com.target) - if(!A || A.noteleport) - continue - if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) - L["[get_area(com.target)] (Active)"] = com.target - else - L["[get_area(com.target)] (Inactive)"] = com.target - var/list/turfs = list( ) - for(var/turf/T in urange(10, orange=1)) - if(T.x>world.maxx-8 || T.x<8) - continue //putting them at the edge is dumb - if(T.y>world.maxy-8 || T.y<8) - continue - var/area/A = T.loc - if(A.noteleport) - continue - turfs += T - if(turfs.len) - L["None (Dangerous)"] = pick(turfs) - var/t1 = input(user, "Please select a teleporter to lock in on.", "Hand Teleporter") as null|anything in L - if (!t1 || user.get_active_held_item() != src || user.incapacitated()) - return - if(active_portal_pairs.len >= max_portal_pairs) - user.show_message("\The [src] is recharging!") - return - var/atom/T = L[t1] - var/area/A = get_area(T) - if(A.noteleport) - to_chat(user, "\The [src] is malfunctioning.") - return - current_location = get_turf(user) //Recheck. - current_area = current_location.loc - if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf - to_chat(user, "\The [src] is malfunctioning.") - return - user.show_message("Locked In.", MSG_AUDIBLE) - var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), 300, 1, null, atmos_link_override) - if(!(LAZYLEN(created) == 2)) - return - RegisterSignal(created[1], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) //Gosh darn it kevinz. - RegisterSignal(created[2], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) - try_move_adjacent(created[1], user.dir) - active_portal_pairs[created[1]] = created[2] - var/obj/effect/portal/c1 = created[1] - var/obj/effect/portal/c2 = created[2] - investigate_log("was used by [key_name(user)] at [AREACOORD(user)] to create a portal pair with destinations [AREACOORD(c1)] and [AREACOORD(c2)].", INVESTIGATE_PORTAL) - add_fingerprint(user) - -/obj/item/hand_tele/proc/on_portal_destroy(obj/effect/portal/P) - active_portal_pairs -= P //If this portal pair is made by us it'll be erased along with the other portal by the portal. - -/obj/item/hand_tele/proc/is_parent_of_portal(obj/effect/portal/P) - if(!istype(P)) - return FALSE - if(active_portal_pairs[P]) - return SOURCE_PORTAL - for(var/i in active_portal_pairs) - if(active_portal_pairs[i] == P) - return DESTINATION_PORTAL - return FALSE - -/obj/item/hand_tele/suicide_act(mob/user) - if(iscarbon(user)) - user.visible_message("[user] is creating a weak portal and sticking [user.p_their()] head through! It looks like [user.p_theyre()] trying to commit suicide!") - var/mob/living/carbon/itemUser = user - var/obj/item/bodypart/head/head = itemUser.get_bodypart(BODY_ZONE_HEAD) - if(head) - head.drop_limb() - var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_SPACE_RUINS, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING)) - head.forceMove(locate(rand(1, world.maxx), rand(1, world.maxy), pick(safeLevels))) - itemUser.visible_message("The portal snaps closed taking [user]'s head with it!") - else - itemUser.visible_message("[user] looks even further depressed as they realize they do not have a head...and suddenly dies of shame!") - return (BRUTELOSS) +#define SOURCE_PORTAL 1 +#define DESTINATION_PORTAL 2 + +/* Teleportation devices. + * Contains: + * Locator + * Hand-tele + */ + +/* + * Locator + */ +/obj/item/locator + name = "bluespace locator" + desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + var/temp = null + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_SMALL + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=400) + var/tracking_range = 20 + +/obj/item/locator/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "BluespaceLocator", name, 300, 300, master_ui, state) + ui.open() + +/obj/item/locator/ui_data(mob/user) + var/list/data = list() + + data["trackingrange"] = tracking_range; + + // Get our current turf location. + var/turf/sr = get_turf(src) + + if (sr) + // Check every teleport beacon. + var/list/tele_beacons = list() + for(var/obj/item/beacon/W in GLOB.teleportbeacons) + + // Get the tracking beacon's turf location. + var/turf/tr = get_turf(W) + + // Make sure it's on a turf and that its Z-level matches the tracker's Z-level + if (tr && tr.z == sr.z) + // Get the distance between the beacon's turf and our turf + var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + + // If the target is too far away, skip over this beacon. + if(distance > tracking_range) + continue + + var/beacon_name + + if(W.renamed) + beacon_name = W.name + else + var/area/A = get_area(W) + beacon_name = A.name + + var/D = dir2text(get_dir(sr, tr)) + tele_beacons += list(list(name = beacon_name, direction = D, distance = distance)) + + data["telebeacons"] = tele_beacons + + var/list/track_implants = list() + + for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) + if (!W.imp_in || !isliving(W.loc)) + continue + else + var/mob/living/M = W.loc + if (M.stat == DEAD) + if (M.timeofdeath + W.lifespan_postmortem < world.time) + continue + var/turf/tr = get_turf(W) + var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + + if(distance > tracking_range) + continue + + var/D = dir2text(get_dir(sr, tr)) + track_implants += list(list(name = W.imp_in.name, direction = D, distance = distance)) + data["trackimplants"] = track_implants + return data + +/obj/machinery/my_machine/ui_act(action, params) + if(..()) return + +/* + * Hand-tele + */ +/obj/item/hand_tele + name = "hand tele" + desc = "A portable item using blue-space technology." + icon = 'icons/obj/device.dmi' + icon_state = "hand_tele" + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=10000) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + var/list/active_portal_pairs + var/max_portal_pairs = 3 + var/atmos_link_override + +/obj/item/hand_tele/Initialize() + . = ..() + active_portal_pairs = list() + +/obj/item/hand_tele/pre_attack(atom/target, mob/user, params) + if(try_dispel_portal(target, user)) + return TRUE + return ..() + +/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user) + if(is_parent_of_portal(target)) + qdel(target) + to_chat(user, "You dispel [target] with \the [src]!") + return TRUE + return FALSE + +/obj/item/hand_tele/afterattack(atom/target, mob/user) + try_dispel_portal(target, user) + . = ..() + +/obj/item/hand_tele/attack_self(mob/user) + var/turf/current_location = get_turf(user)//What turf is the user on? + var/area/current_area = current_location.loc + if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf + to_chat(user, "\The [src] is malfunctioning.") + return + var/list/L = list( ) + for(var/obj/machinery/computer/teleporter/com in GLOB.machines) + if(com.target) + var/area/A = get_area(com.target) + if(!A || A.noteleport) + continue + if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) + L["[get_area(com.target)] (Active)"] = com.target + else + L["[get_area(com.target)] (Inactive)"] = com.target + var/list/turfs = list( ) + for(var/turf/T in urange(10, orange=1)) + if(T.x>world.maxx-8 || T.x<8) + continue //putting them at the edge is dumb + if(T.y>world.maxy-8 || T.y<8) + continue + var/area/A = T.loc + if(A.noteleport) + continue + turfs += T + if(turfs.len) + L["None (Dangerous)"] = pick(turfs) + var/t1 = input(user, "Please select a teleporter to lock in on.", "Hand Teleporter") as null|anything in L + if (!t1 || user.get_active_held_item() != src || user.incapacitated()) + return + if(active_portal_pairs.len >= max_portal_pairs) + user.show_message("\The [src] is recharging!") + return + var/atom/T = L[t1] + var/area/A = get_area(T) + if(A.noteleport) + to_chat(user, "\The [src] is malfunctioning.") + return + current_location = get_turf(user) //Recheck. + current_area = current_location.loc + if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf + to_chat(user, "\The [src] is malfunctioning.") + return + user.show_message("Locked In.", MSG_AUDIBLE) + var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), 300, 1, null, atmos_link_override) + if(!(LAZYLEN(created) == 2)) + return + RegisterSignal(created[1], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) //Gosh darn it kevinz. + RegisterSignal(created[2], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) + try_move_adjacent(created[1], user.dir) + active_portal_pairs[created[1]] = created[2] + var/obj/effect/portal/c1 = created[1] + var/obj/effect/portal/c2 = created[2] + investigate_log("was used by [key_name(user)] at [AREACOORD(user)] to create a portal pair with destinations [AREACOORD(c1)] and [AREACOORD(c2)].", INVESTIGATE_PORTAL) + add_fingerprint(user) + +/obj/item/hand_tele/proc/on_portal_destroy(obj/effect/portal/P) + active_portal_pairs -= P //If this portal pair is made by us it'll be erased along with the other portal by the portal. + +/obj/item/hand_tele/proc/is_parent_of_portal(obj/effect/portal/P) + if(!istype(P)) + return FALSE + if(active_portal_pairs[P]) + return SOURCE_PORTAL + for(var/i in active_portal_pairs) + if(active_portal_pairs[i] == P) + return DESTINATION_PORTAL + return FALSE + +/obj/item/hand_tele/suicide_act(mob/user) + if(iscarbon(user)) + user.visible_message("[user] is creating a weak portal and sticking [user.p_their()] head through! It looks like [user.p_theyre()] trying to commit suicide!") + var/mob/living/carbon/itemUser = user + var/obj/item/bodypart/head/head = itemUser.get_bodypart(BODY_ZONE_HEAD) + if(head) + head.drop_limb() + var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_SPACE_RUINS, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING)) + head.forceMove(locate(rand(1, world.maxx), rand(1, world.maxy), pick(safeLevels))) + itemUser.visible_message("The portal snaps closed taking [user]'s head with it!") + else + itemUser.visible_message("[user] looks even further depressed as they realize they do not have a head...and suddenly dies of shame!") + return (BRUTELOSS) diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm index b47e2022845..29561eb15d2 100644 --- a/code/game/objects/items/tools/crowbar.dm +++ b/code/game/objects/items/tools/crowbar.dm @@ -1,135 +1,135 @@ -/obj/item/crowbar - name = "pocket crowbar" - desc = "A small crowbar. This handy tool is useful for lots of things, such as prying floor tiles or opening unpowered doors." - icon = 'icons/obj/tools.dmi' - icon_state = "crowbar" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - usesound = 'sound/items/crowbar.ogg' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=50) - drop_sound = 'sound/items/handling/crowbar_drop.ogg' - pickup_sound = 'sound/items/handling/crowbar_pickup.ogg' - - attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked") - tool_behaviour = TOOL_CROWBAR - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - var/force_opens = FALSE - -/obj/item/crowbar/suicide_act(mob/user) - user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/crowbar/red - icon_state = "crowbar_red" - force = 8 - -/obj/item/crowbar/abductor - name = "alien crowbar" - desc = "A hard-light crowbar. It appears to pry by itself, without any effort required." - icon = 'icons/obj/abductor.dmi' - usesound = 'sound/weapons/sonic_jackhammer.ogg' - icon_state = "crowbar" - toolspeed = 0.1 - - -/obj/item/crowbar/large - name = "crowbar" - desc = "It's a big crowbar. It doesn't fit in your pockets, because it's big." - force = 12 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 3 - throw_range = 3 - custom_materials = list(/datum/material/iron=70) - icon_state = "crowbar_large" - inhand_icon_state = "crowbar" - toolspeed = 0.7 - -/obj/item/crowbar/power - name = "jaws of life" - desc = "A set of jaws of life, compressed through the magic of science." - icon_state = "jaws_pry" - inhand_icon_state = "jawsoflife" - worn_icon_state = "jawsoflife" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) - usesound = 'sound/items/jaws_pry.ogg' - force = 15 - toolspeed = 0.7 - force_opens = TRUE - -/obj/item/crowbar/power/syndicate - name = "Syndicate jaws of life" - desc = "A rengineered copy of Nanotrasen's standard jaws of life. Can be used to force open airlocks in it's crowbar configuration." - icon_state = "jaws_pry_syndie" - toolspeed = 0.5 - force_opens = TRUE - -/obj/item/crowbar/power/examine() - . = ..() - . += " It's fitted with a [tool_behaviour == TOOL_CROWBAR ? "prying" : "cutting"] head." - -/obj/item/crowbar/power/suicide_act(mob/user) - if(tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user] is putting [user.p_their()] head in [src], it looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/items/jaws_pry.ogg', 50, TRUE, -1) - else - user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!") - playsound(loc, 'sound/items/jaws_cut.ogg', 50, TRUE, -1) - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) - if(BP) - BP.drop_limb() - playsound(loc, "desecration", 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/crowbar/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, TRUE) - if(tool_behaviour == TOOL_CROWBAR) - tool_behaviour = TOOL_WIRECUTTER - to_chat(user, "You attach the cutting jaws to [src].") - usesound = 'sound/items/jaws_cut.ogg' - update_icon() - - else - tool_behaviour = TOOL_CROWBAR - to_chat(user, "You attach the prying jaws to [src].") - usesound = 'sound/items/jaws_pry.ogg' - update_icon() - -/obj/item/crowbar/power/update_icon() - if(tool_behaviour == TOOL_WIRECUTTER) - icon_state = "jaws_cutter" - else - icon_state = "jaws_pry" - -/obj/item/crowbar/power/syndicate/update_icon() - if(tool_behaviour == TOOL_WIRECUTTER) - icon_state = "jaws_cutter_syndie" - else - icon_state = "jaws_pry_syndie" - -/obj/item/crowbar/power/attack(mob/living/carbon/C, mob/user) - if(istype(C) && C.handcuffed && tool_behaviour == TOOL_WIRECUTTER) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - qdel(C.handcuffed) - return - else - ..() - -/obj/item/crowbar/cyborg - name = "hydraulic crowbar" - desc = "A hydraulic prying tool, simple but powerful." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "crowbar_cyborg" - usesound = 'sound/items/jaws_pry.ogg' - force = 10 - toolspeed = 0.5 +/obj/item/crowbar + name = "pocket crowbar" + desc = "A small crowbar. This handy tool is useful for lots of things, such as prying floor tiles or opening unpowered doors." + icon = 'icons/obj/tools.dmi' + icon_state = "crowbar" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + usesound = 'sound/items/crowbar.ogg' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=50) + drop_sound = 'sound/items/handling/crowbar_drop.ogg' + pickup_sound = 'sound/items/handling/crowbar_pickup.ogg' + + attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked") + tool_behaviour = TOOL_CROWBAR + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + var/force_opens = FALSE + +/obj/item/crowbar/suicide_act(mob/user) + user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/crowbar/red + icon_state = "crowbar_red" + force = 8 + +/obj/item/crowbar/abductor + name = "alien crowbar" + desc = "A hard-light crowbar. It appears to pry by itself, without any effort required." + icon = 'icons/obj/abductor.dmi' + usesound = 'sound/weapons/sonic_jackhammer.ogg' + icon_state = "crowbar" + toolspeed = 0.1 + + +/obj/item/crowbar/large + name = "crowbar" + desc = "It's a big crowbar. It doesn't fit in your pockets, because it's big." + force = 12 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 3 + throw_range = 3 + custom_materials = list(/datum/material/iron=70) + icon_state = "crowbar_large" + inhand_icon_state = "crowbar" + toolspeed = 0.7 + +/obj/item/crowbar/power + name = "jaws of life" + desc = "A set of jaws of life, compressed through the magic of science." + icon_state = "jaws_pry" + inhand_icon_state = "jawsoflife" + worn_icon_state = "jawsoflife" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) + usesound = 'sound/items/jaws_pry.ogg' + force = 15 + toolspeed = 0.7 + force_opens = TRUE + +/obj/item/crowbar/power/syndicate + name = "Syndicate jaws of life" + desc = "A rengineered copy of Nanotrasen's standard jaws of life. Can be used to force open airlocks in it's crowbar configuration." + icon_state = "jaws_pry_syndie" + toolspeed = 0.5 + force_opens = TRUE + +/obj/item/crowbar/power/examine() + . = ..() + . += " It's fitted with a [tool_behaviour == TOOL_CROWBAR ? "prying" : "cutting"] head." + +/obj/item/crowbar/power/suicide_act(mob/user) + if(tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user] is putting [user.p_their()] head in [src], it looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/items/jaws_pry.ogg', 50, TRUE, -1) + else + user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!") + playsound(loc, 'sound/items/jaws_cut.ogg', 50, TRUE, -1) + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) + if(BP) + BP.drop_limb() + playsound(loc, "desecration", 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/crowbar/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, TRUE) + if(tool_behaviour == TOOL_CROWBAR) + tool_behaviour = TOOL_WIRECUTTER + to_chat(user, "You attach the cutting jaws to [src].") + usesound = 'sound/items/jaws_cut.ogg' + update_icon() + + else + tool_behaviour = TOOL_CROWBAR + to_chat(user, "You attach the prying jaws to [src].") + usesound = 'sound/items/jaws_pry.ogg' + update_icon() + +/obj/item/crowbar/power/update_icon() + if(tool_behaviour == TOOL_WIRECUTTER) + icon_state = "jaws_cutter" + else + icon_state = "jaws_pry" + +/obj/item/crowbar/power/syndicate/update_icon() + if(tool_behaviour == TOOL_WIRECUTTER) + icon_state = "jaws_cutter_syndie" + else + icon_state = "jaws_pry_syndie" + +/obj/item/crowbar/power/attack(mob/living/carbon/C, mob/user) + if(istype(C) && C.handcuffed && tool_behaviour == TOOL_WIRECUTTER) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + qdel(C.handcuffed) + return + else + ..() + +/obj/item/crowbar/cyborg + name = "hydraulic crowbar" + desc = "A hydraulic prying tool, simple but powerful." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "crowbar_cyborg" + usesound = 'sound/items/jaws_pry.ogg' + force = 10 + toolspeed = 0.5 diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm index fe3c0765252..59108d05c6e 100644 --- a/code/game/objects/items/tools/screwdriver.dm +++ b/code/game/objects/items/tools/screwdriver.dm @@ -1,141 +1,141 @@ -/obj/item/screwdriver - name = "screwdriver" - desc = "You can be totally screwy with this." - icon = 'icons/obj/tools.dmi' - icon_state = "screwdriver_map" - inhand_icon_state = "screwdriver" - worn_icon_state = "screwdriver" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 5 - w_class = WEIGHT_CLASS_TINY - throwforce = 5 - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=75) - attack_verb = list("stabbed") - hitsound = 'sound/weapons/bladeslice.ogg' - usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg') - tool_behaviour = TOOL_SCREWDRIVER - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - drop_sound = 'sound/items/handling/screwdriver_drop.ogg' - pickup_sound = 'sound/items/handling/screwdriver_pickup.ogg' - item_flags = EYE_STAB - var/random_color = TRUE //if the screwdriver uses random coloring - var/static/list/screwdriver_colors = list( - "blue" = rgb(24, 97, 213), - "red" = rgb(255, 0, 0), - "pink" = rgb(213, 24, 141), - "brown" = rgb(160, 82, 18), - "green" = rgb(14, 127, 27), - "cyan" = rgb(24, 162, 213), - "yellow" = rgb(255, 165, 0) - ) - -/obj/item/screwdriver/suicide_act(mob/user) - user.visible_message("[user] is stabbing [src] into [user.p_their()] [pick("temple", "heart")]! It looks like [user.p_theyre()] trying to commit suicide!") - return(BRUTELOSS) - -/obj/item/screwdriver/Initialize() - . = ..() - if(random_color) //random colors! - icon_state = "screwdriver" - var/our_color = pick(screwdriver_colors) - add_atom_colour(screwdriver_colors[our_color], FIXED_COLOUR_PRIORITY) - update_icon() - if(prob(75)) - pixel_y = rand(0, 16) - -/obj/item/screwdriver/update_overlays() - . = ..() - if(!random_color) //icon override - return - var/mutable_appearance/base_overlay = mutable_appearance(icon, "screwdriver_screwybits") - base_overlay.appearance_flags = RESET_COLOR - . += base_overlay - -/obj/item/screwdriver/worn_overlays(isinhands = FALSE, icon_file) - . = list() - if(isinhands && random_color) - var/mutable_appearance/M = mutable_appearance(icon_file, "screwdriver_head") - M.appearance_flags = RESET_COLOR - . += M - -/obj/item/screwdriver/get_belt_overlay() - if(random_color) - var/mutable_appearance/body = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver") - var/mutable_appearance/head = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_head") - body.color = color - head.add_overlay(body) - return head - else - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) - -/obj/item/screwdriver/abductor - name = "alien screwdriver" - desc = "An ultrasonic screwdriver." - icon = 'icons/obj/abductor.dmi' - icon_state = "screwdriver_a" - inhand_icon_state = "screwdriver_nuke" - usesound = 'sound/items/pshoom.ogg' - toolspeed = 0.1 - random_color = FALSE - -/obj/item/screwdriver/abductor/get_belt_overlay() - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_nuke") - -/obj/item/screwdriver/power - name = "hand drill" - desc = "A simple powered hand drill." - icon_state = "drill_screw" - inhand_icon_state = "drill" - worn_icon_state = "drill" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) //done for balance reasons, making them high value for research, but harder to get - force = 8 //might or might not be too high, subject to change - w_class = WEIGHT_CLASS_SMALL - throwforce = 8 - throw_speed = 2 - throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far - attack_verb = list("drilled", "screwed", "jabbed","whacked") - hitsound = 'sound/items/drill_hit.ogg' - usesound = 'sound/items/drill_use.ogg' - toolspeed = 0.7 - random_color = FALSE - -/obj/item/screwdriver/power/examine() - . = ..() - . += " It's fitted with a [tool_behaviour == TOOL_SCREWDRIVER ? "screw" : "bolt"] bit." - -/obj/item/screwdriver/power/suicide_act(mob/user) - if(tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") - else - user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/items/drill_use.ogg', 50, TRUE, -1) - return(BRUTELOSS) - -/obj/item/screwdriver/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, TRUE) - if(tool_behaviour == TOOL_SCREWDRIVER) - tool_behaviour = TOOL_WRENCH - to_chat(user, "You attach the bolt bit to [src].") - icon_state = "drill_bolt" - else - tool_behaviour = TOOL_SCREWDRIVER - to_chat(user, "You attach the screw bit to [src].") - icon_state = "drill_screw" - -/obj/item/screwdriver/cyborg - name = "automated screwdriver" - desc = "A powerful automated screwdriver, designed to be both precise and quick." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "screwdriver_cyborg" - hitsound = 'sound/items/drill_hit.ogg' - usesound = 'sound/items/drill_use.ogg' - toolspeed = 0.5 - random_color = FALSE +/obj/item/screwdriver + name = "screwdriver" + desc = "You can be totally screwy with this." + icon = 'icons/obj/tools.dmi' + icon_state = "screwdriver_map" + inhand_icon_state = "screwdriver" + worn_icon_state = "screwdriver" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 5 + w_class = WEIGHT_CLASS_TINY + throwforce = 5 + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=75) + attack_verb = list("stabbed") + hitsound = 'sound/weapons/bladeslice.ogg' + usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg') + tool_behaviour = TOOL_SCREWDRIVER + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + drop_sound = 'sound/items/handling/screwdriver_drop.ogg' + pickup_sound = 'sound/items/handling/screwdriver_pickup.ogg' + item_flags = EYE_STAB + var/random_color = TRUE //if the screwdriver uses random coloring + var/static/list/screwdriver_colors = list( + "blue" = rgb(24, 97, 213), + "red" = rgb(255, 0, 0), + "pink" = rgb(213, 24, 141), + "brown" = rgb(160, 82, 18), + "green" = rgb(14, 127, 27), + "cyan" = rgb(24, 162, 213), + "yellow" = rgb(255, 165, 0) + ) + +/obj/item/screwdriver/suicide_act(mob/user) + user.visible_message("[user] is stabbing [src] into [user.p_their()] [pick("temple", "heart")]! It looks like [user.p_theyre()] trying to commit suicide!") + return(BRUTELOSS) + +/obj/item/screwdriver/Initialize() + . = ..() + if(random_color) //random colors! + icon_state = "screwdriver" + var/our_color = pick(screwdriver_colors) + add_atom_colour(screwdriver_colors[our_color], FIXED_COLOUR_PRIORITY) + update_icon() + if(prob(75)) + pixel_y = rand(0, 16) + +/obj/item/screwdriver/update_overlays() + . = ..() + if(!random_color) //icon override + return + var/mutable_appearance/base_overlay = mutable_appearance(icon, "screwdriver_screwybits") + base_overlay.appearance_flags = RESET_COLOR + . += base_overlay + +/obj/item/screwdriver/worn_overlays(isinhands = FALSE, icon_file) + . = list() + if(isinhands && random_color) + var/mutable_appearance/M = mutable_appearance(icon_file, "screwdriver_head") + M.appearance_flags = RESET_COLOR + . += M + +/obj/item/screwdriver/get_belt_overlay() + if(random_color) + var/mutable_appearance/body = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver") + var/mutable_appearance/head = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_head") + body.color = color + head.add_overlay(body) + return head + else + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) + +/obj/item/screwdriver/abductor + name = "alien screwdriver" + desc = "An ultrasonic screwdriver." + icon = 'icons/obj/abductor.dmi' + icon_state = "screwdriver_a" + inhand_icon_state = "screwdriver_nuke" + usesound = 'sound/items/pshoom.ogg' + toolspeed = 0.1 + random_color = FALSE + +/obj/item/screwdriver/abductor/get_belt_overlay() + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_nuke") + +/obj/item/screwdriver/power + name = "hand drill" + desc = "A simple powered hand drill." + icon_state = "drill_screw" + inhand_icon_state = "drill" + worn_icon_state = "drill" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) //done for balance reasons, making them high value for research, but harder to get + force = 8 //might or might not be too high, subject to change + w_class = WEIGHT_CLASS_SMALL + throwforce = 8 + throw_speed = 2 + throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far + attack_verb = list("drilled", "screwed", "jabbed","whacked") + hitsound = 'sound/items/drill_hit.ogg' + usesound = 'sound/items/drill_use.ogg' + toolspeed = 0.7 + random_color = FALSE + +/obj/item/screwdriver/power/examine() + . = ..() + . += " It's fitted with a [tool_behaviour == TOOL_SCREWDRIVER ? "screw" : "bolt"] bit." + +/obj/item/screwdriver/power/suicide_act(mob/user) + if(tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") + else + user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/items/drill_use.ogg', 50, TRUE, -1) + return(BRUTELOSS) + +/obj/item/screwdriver/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, TRUE) + if(tool_behaviour == TOOL_SCREWDRIVER) + tool_behaviour = TOOL_WRENCH + to_chat(user, "You attach the bolt bit to [src].") + icon_state = "drill_bolt" + else + tool_behaviour = TOOL_SCREWDRIVER + to_chat(user, "You attach the screw bit to [src].") + icon_state = "drill_screw" + +/obj/item/screwdriver/cyborg + name = "automated screwdriver" + desc = "A powerful automated screwdriver, designed to be both precise and quick." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "screwdriver_cyborg" + hitsound = 'sound/items/drill_hit.ogg' + usesound = 'sound/items/drill_use.ogg' + toolspeed = 0.5 + random_color = FALSE diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm index fe6eeedd267..2a389aeafda 100644 --- a/code/game/objects/items/tools/wirecutters.dm +++ b/code/game/objects/items/tools/wirecutters.dm @@ -1,85 +1,85 @@ -/obj/item/wirecutters - name = "wirecutters" - desc = "This cuts wires." - icon = 'icons/obj/tools.dmi' - icon_state = "cutters_map" - inhand_icon_state = "cutters" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 6 - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=80) - attack_verb = list("pinched", "nipped") - hitsound = 'sound/items/wirecutter.ogg' - usesound = 'sound/items/wirecutter.ogg' - drop_sound = 'sound/items/handling/wirecutter_drop.ogg' - pickup_sound = 'sound/items/handling/wirecutter_pickup.ogg' - - tool_behaviour = TOOL_WIRECUTTER - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - var/random_color = TRUE - var/static/list/wirecutter_colors = list( - "blue" = "#1861d5", - "red" = "#951710", - "pink" = "#d5188d", - "brown" = "#a05212", - "green" = "#0e7f1b", - "cyan" = "#18a2d5", - "yellow" = "#d58c18" - ) - - -/obj/item/wirecutters/Initialize() - . = ..() - if(random_color) //random colors! - icon_state = "cutters" - var/our_color = pick(wirecutter_colors) - add_atom_colour(wirecutter_colors[our_color], FIXED_COLOUR_PRIORITY) - update_icon() - -/obj/item/wirecutters/update_overlays() - . = ..() - if(!random_color) //icon override - return - var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") - base_overlay.appearance_flags = RESET_COLOR - . += base_overlay - -/obj/item/wirecutters/attack(mob/living/carbon/C, mob/user) - if(istype(C) && C.handcuffed && istype(C.handcuffed, /obj/item/restraints/handcuffs/cable)) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - qdel(C.handcuffed) - return - else if(istype(C) && C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) - to_chat(C, "You attempt to remove the durathread strand from around your neck.") - if(do_after(user, 15, null, C)) - to_chat(C, "You succesfuly remove the durathread strand.") - C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) - else - ..() - -/obj/item/wirecutters/suicide_act(mob/user) - user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, usesound, 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/wirecutters/abductor - name = "alien wirecutters" - desc = "Extremely sharp wirecutters, made out of a silvery-green metal." - icon = 'icons/obj/abductor.dmi' - icon_state = "cutters" - toolspeed = 0.1 - random_color = FALSE - -/obj/item/wirecutters/cyborg - name = "powered wirecutters" - desc = "Cuts wires with the power of ELECTRICITY. Faster than normal wirecutters." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "wirecutters_cyborg" - toolspeed = 0.5 - random_color = FALSE +/obj/item/wirecutters + name = "wirecutters" + desc = "This cuts wires." + icon = 'icons/obj/tools.dmi' + icon_state = "cutters_map" + inhand_icon_state = "cutters" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 6 + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=80) + attack_verb = list("pinched", "nipped") + hitsound = 'sound/items/wirecutter.ogg' + usesound = 'sound/items/wirecutter.ogg' + drop_sound = 'sound/items/handling/wirecutter_drop.ogg' + pickup_sound = 'sound/items/handling/wirecutter_pickup.ogg' + + tool_behaviour = TOOL_WIRECUTTER + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + var/random_color = TRUE + var/static/list/wirecutter_colors = list( + "blue" = "#1861d5", + "red" = "#951710", + "pink" = "#d5188d", + "brown" = "#a05212", + "green" = "#0e7f1b", + "cyan" = "#18a2d5", + "yellow" = "#d58c18" + ) + + +/obj/item/wirecutters/Initialize() + . = ..() + if(random_color) //random colors! + icon_state = "cutters" + var/our_color = pick(wirecutter_colors) + add_atom_colour(wirecutter_colors[our_color], FIXED_COLOUR_PRIORITY) + update_icon() + +/obj/item/wirecutters/update_overlays() + . = ..() + if(!random_color) //icon override + return + var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") + base_overlay.appearance_flags = RESET_COLOR + . += base_overlay + +/obj/item/wirecutters/attack(mob/living/carbon/C, mob/user) + if(istype(C) && C.handcuffed && istype(C.handcuffed, /obj/item/restraints/handcuffs/cable)) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + qdel(C.handcuffed) + return + else if(istype(C) && C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) + to_chat(C, "You attempt to remove the durathread strand from around your neck.") + if(do_after(user, 15, null, C)) + to_chat(C, "You succesfuly remove the durathread strand.") + C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + else + ..() + +/obj/item/wirecutters/suicide_act(mob/user) + user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, usesound, 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/wirecutters/abductor + name = "alien wirecutters" + desc = "Extremely sharp wirecutters, made out of a silvery-green metal." + icon = 'icons/obj/abductor.dmi' + icon_state = "cutters" + toolspeed = 0.1 + random_color = FALSE + +/obj/item/wirecutters/cyborg + name = "powered wirecutters" + desc = "Cuts wires with the power of ELECTRICITY. Faster than normal wirecutters." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "wirecutters_cyborg" + toolspeed = 0.5 + random_color = FALSE diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm index b0fdb9ae002..e022c5dffe0 100644 --- a/code/game/objects/items/tools/wrench.dm +++ b/code/game/objects/items/tools/wrench.dm @@ -1,119 +1,119 @@ -/obj/item/wrench - name = "wrench" - desc = "A wrench with common uses. Can be found in your hand." - icon = 'icons/obj/tools.dmi' - icon_state = "wrench" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - usesound = 'sound/items/ratchet.ogg' - custom_materials = list(/datum/material/iron=150) - drop_sound = 'sound/items/handling/wrench_drop.ogg' - pickup_sound = 'sound/items/handling/wrench_pickup.ogg' - - attack_verb = list("bashed", "battered", "bludgeoned", "whacked") - tool_behaviour = TOOL_WRENCH - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - -/obj/item/wrench/suicide_act(mob/user) - user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/wrench/abductor - name = "alien wrench" - desc = "A polarized wrench. It causes anything placed between the jaws to turn." - icon = 'icons/obj/abductor.dmi' - icon_state = "wrench" - usesound = 'sound/effects/empulse.ogg' - toolspeed = 0.1 - - -/obj/item/wrench/medical - name = "medical wrench" - desc = "A medical wrench with common(medical?) uses. Can be found in your hand." - icon_state = "wrench_medical" - force = 2 //MEDICAL - throwforce = 4 - attack_verb = list("healed", "medicaled", "tapped", "poked", "analyzed") //"cobbyed" - ///var to hold the name of the person who suicided - var/suicider - -/obj/item/wrench/medical/examine(mob/user) - . = ..() - if(suicider) - . += "For some reason, it reminds you of [suicider]." - -/obj/item/wrench/medical/suicide_act(mob/living/user) - user.visible_message("[user] is praying to the medical wrench to take [user.p_their()] soul. It looks like [user.p_theyre()] trying to commit suicide!") - user.Stun(100, ignore_canstun = TRUE)// Stun stops them from wandering off - user.light_color = "#FAE48E" - user.set_light(2) - user.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER)) - playsound(loc, 'sound/effects/pray.ogg', 50, TRUE, -1) - - // Let the sound effect finish playing - add_fingerprint(user) - sleep(20) - if(!user) - return - for(var/obj/item/W in user) - user.dropItemToGround(W) - suicider = user.real_name - user.dust() - return OXYLOSS - -/obj/item/wrench/cyborg - name = "hydraulic wrench" - desc = "An advanced robotic wrench, powered by internal hydraulics. Twice as fast as the handheld version." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "wrench_cyborg" - toolspeed = 0.5 - -/obj/item/wrench/combat - name = "combat wrench" - desc = "It's like a normal wrench but edgier. Can be found on the battlefield." - icon_state = "wrench_combat" - inhand_icon_state = "wrench_combat" - attack_verb = list("devastated", "brutalized", "committed a war crime against", "obliterated", "humiliated") - tool_behaviour = null - toolspeed = null - var/on = FALSE - -/obj/item/wrench/combat/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/wrench/combat/attack_self(mob/living/user) - if(on) - on = FALSE - force = initial(force) - w_class = initial(w_class) - throwforce = initial(throwforce) - tool_behaviour = initial(tool_behaviour) - toolspeed = initial(toolspeed) - playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) - to_chat(user, "[src] can now be kept at bay.") - else - on = TRUE - force = 6 - w_class = WEIGHT_CLASS_NORMAL - throwforce = 8 - tool_behaviour = TOOL_WRENCH - toolspeed = 1 - playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) - to_chat(user, "[src] is now active. Woe onto your enemies!") - update_icon() - -/obj/item/wrench/combat/update_icon_state() - if(on) - icon_state = "[initial(icon_state)]_on" - inhand_icon_state = "[initial(inhand_icon_state)]1" - else - icon_state = "[initial(icon_state)]" - inhand_icon_state = "[initial(inhand_icon_state)]" +/obj/item/wrench + name = "wrench" + desc = "A wrench with common uses. Can be found in your hand." + icon = 'icons/obj/tools.dmi' + icon_state = "wrench" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + usesound = 'sound/items/ratchet.ogg' + custom_materials = list(/datum/material/iron=150) + drop_sound = 'sound/items/handling/wrench_drop.ogg' + pickup_sound = 'sound/items/handling/wrench_pickup.ogg' + + attack_verb = list("bashed", "battered", "bludgeoned", "whacked") + tool_behaviour = TOOL_WRENCH + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + +/obj/item/wrench/suicide_act(mob/user) + user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/wrench/abductor + name = "alien wrench" + desc = "A polarized wrench. It causes anything placed between the jaws to turn." + icon = 'icons/obj/abductor.dmi' + icon_state = "wrench" + usesound = 'sound/effects/empulse.ogg' + toolspeed = 0.1 + + +/obj/item/wrench/medical + name = "medical wrench" + desc = "A medical wrench with common(medical?) uses. Can be found in your hand." + icon_state = "wrench_medical" + force = 2 //MEDICAL + throwforce = 4 + attack_verb = list("healed", "medicaled", "tapped", "poked", "analyzed") //"cobbyed" + ///var to hold the name of the person who suicided + var/suicider + +/obj/item/wrench/medical/examine(mob/user) + . = ..() + if(suicider) + . += "For some reason, it reminds you of [suicider]." + +/obj/item/wrench/medical/suicide_act(mob/living/user) + user.visible_message("[user] is praying to the medical wrench to take [user.p_their()] soul. It looks like [user.p_theyre()] trying to commit suicide!") + user.Stun(100, ignore_canstun = TRUE)// Stun stops them from wandering off + user.light_color = "#FAE48E" + user.set_light(2) + user.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER)) + playsound(loc, 'sound/effects/pray.ogg', 50, TRUE, -1) + + // Let the sound effect finish playing + add_fingerprint(user) + sleep(20) + if(!user) + return + for(var/obj/item/W in user) + user.dropItemToGround(W) + suicider = user.real_name + user.dust() + return OXYLOSS + +/obj/item/wrench/cyborg + name = "hydraulic wrench" + desc = "An advanced robotic wrench, powered by internal hydraulics. Twice as fast as the handheld version." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "wrench_cyborg" + toolspeed = 0.5 + +/obj/item/wrench/combat + name = "combat wrench" + desc = "It's like a normal wrench but edgier. Can be found on the battlefield." + icon_state = "wrench_combat" + inhand_icon_state = "wrench_combat" + attack_verb = list("devastated", "brutalized", "committed a war crime against", "obliterated", "humiliated") + tool_behaviour = null + toolspeed = null + var/on = FALSE + +/obj/item/wrench/combat/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/wrench/combat/attack_self(mob/living/user) + if(on) + on = FALSE + force = initial(force) + w_class = initial(w_class) + throwforce = initial(throwforce) + tool_behaviour = initial(tool_behaviour) + toolspeed = initial(toolspeed) + playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) + to_chat(user, "[src] can now be kept at bay.") + else + on = TRUE + force = 6 + w_class = WEIGHT_CLASS_NORMAL + throwforce = 8 + tool_behaviour = TOOL_WRENCH + toolspeed = 1 + playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) + to_chat(user, "[src] is now active. Woe onto your enemies!") + update_icon() + +/obj/item/wrench/combat/update_icon_state() + if(on) + icon_state = "[initial(icon_state)]_on" + inhand_icon_state = "[initial(inhand_icon_state)]1" + else + icon_state = "[initial(icon_state)]" + inhand_icon_state = "[initial(inhand_icon_state)]" diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index c8c4be25d9f..e922244a7a2 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -1,110 +1,110 @@ -//Added by Jack Rost -/obj/item/trash - icon = 'icons/obj/janitor.dmi' - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - desc = "This is rubbish." - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - item_flags = NOBLUDGEON - -/obj/item/trash/Initialize(mapload) - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_created", 1, name) - return ..() - -/obj/item/trash/Destroy() - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) - return ..() - -/obj/item/trash/raisins - name = "\improper 4no raisins" - icon_state= "4no_raisins" - -/obj/item/trash/candy - name = "candy" - icon_state= "candy" - -/obj/item/trash/cheesie - name = "cheesie honkers" - icon_state = "cheesie_honkers" - -/obj/item/trash/chips - name = "chips" - icon_state = "chips" - -/obj/item/trash/boritos - name = "boritos bag" - icon_state = "boritos" - grind_results = list(/datum/reagent/aluminium = 1) //from the mylar bag - -/obj/item/trash/popcorn - name = "popcorn" - icon_state = "popcorn" - -/obj/item/trash/sosjerky - name = "\improper Scaredy's Private Reserve Beef Jerky" - icon_state = "sosjerky" - -/obj/item/trash/syndi_cakes - name = "syndi-cakes" - icon_state = "syndi_cakes" - -/obj/item/trash/energybar - name = "energybar wrapper" - icon_state = "energybar" - -/obj/item/trash/waffles - name = "waffles tray" - icon_state = "waffles" - -/obj/item/trash/plate - name = "plate" - icon_state = "plate" - resistance_flags = NONE - -/obj/item/trash/pistachios - name = "pistachios pack" - icon_state = "pistachios_pack" - -/obj/item/trash/semki - name = "semki pack" - icon_state = "semki_pack" - -/obj/item/trash/tray - name = "tray" - icon_state = "tray" - resistance_flags = NONE - -/obj/item/trash/candle - name = "candle" - icon = 'icons/obj/candle.dmi' - icon_state = "candle4" - -/obj/item/trash/can - name = "crushed can" - icon_state = "cola" - resistance_flags = NONE - grind_results = list(/datum/reagent/aluminium = 10) - -/obj/item/trash/can/food/peaches - name = "canned peaches" - icon = 'icons/obj/food/food.dmi' - icon_state = "peachcan_empty" - -/obj/item/trash/can/food/peaches/maint - name = "Maintenance Peaches" - icon_state = "peachcanmaint_empty" - -/obj/item/trash/can/food/beans - name = "tin of beans" - icon = 'icons/obj/food/food.dmi' - icon_state = "beans_empty" - -/obj/item/trash/can/Initialize() - . = ..() - pixel_x = rand(-4,4) - pixel_y = rand(-4,4) +//Added by Jack Rost +/obj/item/trash + icon = 'icons/obj/janitor.dmi' + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + desc = "This is rubbish." + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + item_flags = NOBLUDGEON + +/obj/item/trash/Initialize(mapload) + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_created", 1, name) + return ..() + +/obj/item/trash/Destroy() + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) + return ..() + +/obj/item/trash/raisins + name = "\improper 4no raisins" + icon_state= "4no_raisins" + +/obj/item/trash/candy + name = "candy" + icon_state= "candy" + +/obj/item/trash/cheesie + name = "cheesie honkers" + icon_state = "cheesie_honkers" + +/obj/item/trash/chips + name = "chips" + icon_state = "chips" + +/obj/item/trash/boritos + name = "boritos bag" + icon_state = "boritos" + grind_results = list(/datum/reagent/aluminium = 1) //from the mylar bag + +/obj/item/trash/popcorn + name = "popcorn" + icon_state = "popcorn" + +/obj/item/trash/sosjerky + name = "\improper Scaredy's Private Reserve Beef Jerky" + icon_state = "sosjerky" + +/obj/item/trash/syndi_cakes + name = "syndi-cakes" + icon_state = "syndi_cakes" + +/obj/item/trash/energybar + name = "energybar wrapper" + icon_state = "energybar" + +/obj/item/trash/waffles + name = "waffles tray" + icon_state = "waffles" + +/obj/item/trash/plate + name = "plate" + icon_state = "plate" + resistance_flags = NONE + +/obj/item/trash/pistachios + name = "pistachios pack" + icon_state = "pistachios_pack" + +/obj/item/trash/semki + name = "semki pack" + icon_state = "semki_pack" + +/obj/item/trash/tray + name = "tray" + icon_state = "tray" + resistance_flags = NONE + +/obj/item/trash/candle + name = "candle" + icon = 'icons/obj/candle.dmi' + icon_state = "candle4" + +/obj/item/trash/can + name = "crushed can" + icon_state = "cola" + resistance_flags = NONE + grind_results = list(/datum/reagent/aluminium = 10) + +/obj/item/trash/can/food/peaches + name = "canned peaches" + icon = 'icons/obj/food/food.dmi' + icon_state = "peachcan_empty" + +/obj/item/trash/can/food/peaches/maint + name = "Maintenance Peaches" + icon_state = "peachcanmaint_empty" + +/obj/item/trash/can/food/beans + name = "tin of beans" + icon = 'icons/obj/food/food.dmi' + icon_state = "beans_empty" + +/obj/item/trash/can/Initialize() + . = ..() + pixel_x = rand(-4,4) + pixel_y = rand(-4,4) diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm index b42fe120ffa..9a89e03a037 100644 --- a/code/game/objects/items/vending_items.dm +++ b/code/game/objects/items/vending_items.dm @@ -1,51 +1,51 @@ -/* - Vending machine refills can be found at /code/modules/vending/ within each vending machine's respective file -*/ -/obj/item/vending_refill - name = "resupply canister" - var/machine_name = "Generic" - - icon = 'icons/obj/vending_restock.dmi' - icon_state = "refill_snack" - inhand_icon_state = "restock_unit" - desc = "A vending machine restock cart." - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - force = 7 - throwforce = 10 - throw_speed = 1 - throw_range = 7 - w_class = WEIGHT_CLASS_BULKY - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) - - // Built automatically from the corresponding vending machine. - // If null, considered to be full. Otherwise, is list(/typepath = amount). - var/list/products - var/list/contraband - var/list/premium - -/obj/item/vending_refill/Initialize(mapload) - . = ..() - name = "\improper [machine_name] restocking unit" - -/obj/item/vending_refill/examine(mob/user) - . = ..() - var/num = get_part_rating() - if (num == INFINITY) - . += "It's sealed tight, completely full of supplies." - else if (num == 0) - . += "It's empty!" - else - . += "It can restock [num] item\s." - -/obj/item/vending_refill/get_part_rating() - if (!products || !contraband || !premium) - return INFINITY - . = 0 - for(var/key in products) - . += products[key] - for(var/key in contraband) - . += contraband[key] - for(var/key in premium) - . += premium[key] +/* + Vending machine refills can be found at /code/modules/vending/ within each vending machine's respective file +*/ +/obj/item/vending_refill + name = "resupply canister" + var/machine_name = "Generic" + + icon = 'icons/obj/vending_restock.dmi' + icon_state = "refill_snack" + inhand_icon_state = "restock_unit" + desc = "A vending machine restock cart." + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + force = 7 + throwforce = 10 + throw_speed = 1 + throw_range = 7 + w_class = WEIGHT_CLASS_BULKY + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) + + // Built automatically from the corresponding vending machine. + // If null, considered to be full. Otherwise, is list(/typepath = amount). + var/list/products + var/list/contraband + var/list/premium + +/obj/item/vending_refill/Initialize(mapload) + . = ..() + name = "\improper [machine_name] restocking unit" + +/obj/item/vending_refill/examine(mob/user) + . = ..() + var/num = get_part_rating() + if (num == INFINITY) + . += "It's sealed tight, completely full of supplies." + else if (num == 0) + . += "It's empty!" + else + . += "It can restock [num] item\s." + +/obj/item/vending_refill/get_part_rating() + if (!products || !contraband || !premium) + return INFINITY + . = 0 + for(var/key in products) + . += products[key] + for(var/key in contraband) + . += contraband[key] + for(var/key in premium) + . += premium[key] diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index e13f3ba1fc8..cfb86786baf 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -1,898 +1,898 @@ -/obj/item/banhammer - desc = "A banhammer." - name = "banhammer" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "toyhammer" - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - force = 1 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - attack_verb = list("banned") - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - -/obj/item/banhammer/suicide_act(mob/user) - user.visible_message("[user] is hitting [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.") - return (BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS) -/* -oranges says: This is a meme relating to the english translation of the ss13 russian wiki page on lurkmore. -mrdoombringer sez: and remember kids, if you try and PR a fix for this item's grammar, you are admitting that you are, indeed, a newfriend. -for further reading, please see: https://github.com/tgstation/tgstation/pull/30173 and https://translate.google.com/translate?sl=auto&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=%2F%2Flurkmore.to%2FSS13&edit-text=&act=url -*/ -/obj/item/banhammer/attack(mob/M, mob/user) - if(user.zone_selected == BODY_ZONE_HEAD) - M.visible_message("[user] are stroking the head of [M] with a bangammer.", "[user] are stroking your head with a bangammer.", "You hear a bangammer stroking a head.") // see above comment - else - M.visible_message("[M] has been banned FOR NO REISIN by [user]!", "You have been banned FOR NO REISIN by [user]!", "You hear a banhammer banning someone.") - playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much - if(user.a_intent != INTENT_HELP) - return ..(M, user) - -/obj/item/sord - name = "\improper SORD" - desc = "This thing is so unspeakably shitty you are having a hard time even holding it." - icon_state = "sord" - inhand_icon_state = "sord" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - slot_flags = ITEM_SLOT_BELT - force = 2 - throwforce = 1 - w_class = WEIGHT_CLASS_NORMAL - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") - -/obj/item/sord/suicide_act(mob/user) - user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \ - "You try to impale yourself with [src], but it's USELESS...") - return SHAME - -/obj/item/claymore - name = "claymore" - desc = "What are you standing around staring at this for? Get to killing!" - icon_state = "claymore" - inhand_icon_state = "claymore" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - hitsound = 'sound/weapons/bladeslice.ogg' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK - force = 40 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") - block_chance = 50 - sharpness = IS_SHARP - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - -/obj/item/claymore/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 40, 105) - -/obj/item/claymore/suicide_act(mob/user) - user.visible_message("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return(BRUTELOSS) - -/obj/item/claymore/highlander //ALL COMMENTS MADE REGARDING THIS SWORD MUST BE MADE IN ALL CAPS - desc = "THERE CAN BE ONLY ONE, AND IT WILL BE YOU!!!\nActivate it in your hand to point to the nearest victim." - flags_1 = CONDUCT_1 - item_flags = DROPDEL //WOW BRO YOU LOST AN ARM, GUESS WHAT YOU DONT GET YOUR SWORD ANYMORE //I CANT BELIEVE SPOOKYDONUT WOULD BREAK THE REQUIREMENTS - slot_flags = null - block_chance = 0 //RNG WON'T HELP YOU NOW, PANSY - light_range = 3 - attack_verb = list("brutalized", "eviscerated", "disemboweled", "hacked", "carved", "cleaved") //ONLY THE MOST VISCERAL ATTACK VERBS - var/notches = 0 //HOW MANY PEOPLE HAVE BEEN SLAIN WITH THIS BLADE - var/obj/item/disk/nuclear/nuke_disk //OUR STORED NUKE DISK - -/obj/item/claymore/highlander/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) - START_PROCESSING(SSobj, src) - -/obj/item/claymore/highlander/Destroy() - if(nuke_disk) - nuke_disk.forceMove(get_turf(src)) - nuke_disk.visible_message("The nuke disk is vulnerable!") - nuke_disk = null - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/claymore/highlander/process() - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - loc.layer = LARGE_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS) - H.bleedsuppress = TRUE //AND WE WON'T BLEED OUT LIKE COWARDS - else - if(!(flags_1 & ADMIN_SPAWNED_1)) - qdel(src) - - -/obj/item/claymore/highlander/pickup(mob/living/user) - . = ..() - to_chat(user, "The power of Scotland protects you! You are shielded from all stuns and knockdowns.") - user.add_stun_absorption("highlander", INFINITY, 1, " is protected by the power of Scotland!", "The power of Scotland absorbs the stun!", " is protected by the power of Scotland!") - user.ignore_slowdown(HIGHLANDER) - -/obj/item/claymore/highlander/dropped(mob/living/user) - . = ..() - user.unignore_slowdown(HIGHLANDER) - -/obj/item/claymore/highlander/examine(mob/user) - . = ..() - . += "It has [!notches ? "nothing" : "[notches] notches"] scratched into the blade." - if(nuke_disk) - . += "It's holding the nuke disk!" - -/obj/item/claymore/highlander/attack(mob/living/target, mob/living/user) - . = ..() - if(!QDELETED(target) && iscarbon(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander") - user.fully_heal(admin_revive = FALSE) //STEAL THE LIFE OF OUR FALLEN FOES - add_notch(user) - target.visible_message("[target] crumbles to dust beneath [user]'s blows!", "As you fall, your body crumbles to dust!") - target.dust() - -/obj/item/claymore/highlander/attack_self(mob/living/user) - var/closest_victim - var/closest_distance = 255 - for(var/mob/living/carbon/human/H in GLOB.player_list - user) - if(H.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance)) - closest_victim = H - if(!closest_victim) - to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.") - return - to_chat(user, "[src] thrums and points to the [dir2text(get_dir(user, closest_victim))].") - -/obj/item/claymore/highlander/IsReflect() - return 1 //YOU THINK YOUR PUNY LASERS CAN STOP ME? - -/obj/item/claymore/highlander/proc/add_notch(mob/living/user) //DYNAMIC CLAYMORE PROGRESSION SYSTEM - THIS IS THE FUTURE - notches++ - force++ - var/new_name = name - switch(notches) - if(1) - to_chat(user, "Your first kill - hopefully one of many. You scratch a notch into [src]'s blade.") - to_chat(user, "You feel your fallen foe's soul entering your blade, restoring your wounds!") - new_name = "notched claymore" - if(2) - to_chat(user, "Another falls before you. Another soul fuses with your own. Another notch in the blade.") - new_name = "double-notched claymore" - add_atom_colour(rgb(255, 235, 235), ADMIN_COLOUR_PRIORITY) - if(3) - to_chat(user, "You're beginning to relish the thrill of battle.") - new_name = "triple-notched claymore" - add_atom_colour(rgb(255, 215, 215), ADMIN_COLOUR_PRIORITY) - if(4) - to_chat(user, "You've lost count of how many you've killed.") - new_name = "many-notched claymore" - add_atom_colour(rgb(255, 195, 195), ADMIN_COLOUR_PRIORITY) - if(5) - to_chat(user, "Five voices now echo in your mind, cheering the slaughter.") - new_name = "battle-tested claymore" - add_atom_colour(rgb(255, 175, 175), ADMIN_COLOUR_PRIORITY) - if(6) - to_chat(user, "Is this what the vikings felt like? Visions of glory fill your head as you slay your sixth foe.") - new_name = "battle-scarred claymore" - add_atom_colour(rgb(255, 155, 155), ADMIN_COLOUR_PRIORITY) - if(7) - to_chat(user, "Kill. Butcher. Conquer.") - new_name = "vicious claymore" - add_atom_colour(rgb(255, 135, 135), ADMIN_COLOUR_PRIORITY) - if(8) - to_chat(user, "IT NEVER GETS OLD. THE SCREAMING. THE BLOOD AS IT SPRAYS ACROSS YOUR FACE.") - new_name = "bloodthirsty claymore" - add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) - if(9) - to_chat(user, "ANOTHER ONE FALLS TO YOUR BLOWS. ANOTHER WEAKLING UNFIT TO LIVE.") - new_name = "gore-stained claymore" - add_atom_colour(rgb(255, 95, 95), ADMIN_COLOUR_PRIORITY) - if(10) - user.visible_message("[user]'s eyes light up with a vengeful fire!", \ - "YOU FEEL THE POWER OF VALHALLA FLOWING THROUGH YOU! THERE CAN BE ONLY ONE!!!") - user.update_icons() - new_name = "GORE-DRENCHED CLAYMORE OF [pick("THE WHIMSICAL SLAUGHTER", "A THOUSAND SLAUGHTERED CATTLE", "GLORY AND VALHALLA", "ANNIHILATION", "OBLITERATION")]" - icon_state = "claymore_gold" - inhand_icon_state = "cultblade" - remove_atom_colour(ADMIN_COLOUR_PRIORITY) - - name = new_name - playsound(user, 'sound/items/screwdriver2.ogg', 50, TRUE) - -/obj/item/katana - name = "katana" - desc = "Woefully underpowered in D20." - icon_state = "katana" - inhand_icon_state = "katana" - worn_icon_state = "katana" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK - force = 40 - throwforce = 10 - w_class = WEIGHT_CLASS_HUGE - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") - block_chance = 50 - sharpness = IS_SHARP - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - -/obj/item/katana/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!") - return(BRUTELOSS) - -/obj/item/katana/cursed - slot_flags = null - item_flags = DROPDEL - -/obj/item/katana/cursed/equipped(mob/living/carbon/human/user) - . = ..() - if(!istype(user)) - return - user.gain_trauma(/datum/brain_trauma/magic/stalker, TRAUMA_RESILIENCE_MAGIC) - -/obj/item/katana/cursed/Initialize() - ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT) - -/obj/item/wirerod - name = "wired rod" - desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit." - icon_state = "wiredrod" - inhand_icon_state = "rods" - flags_1 = CONDUCT_1 - force = 9 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=1150, /datum/material/glass=75) - attack_verb = list("hit", "bludgeoned", "whacked", "bonked") - -/obj/item/wirerod/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/shard)) - var/obj/item/spear/S = new /obj/item/spear - - remove_item_from_storage(user) - if (!user.transferItemToLoc(I, S)) - return - S.CheckParts(list(I)) - qdel(src) - - user.put_in_hands(S) - to_chat(user, "You fasten the glass shard to the top of the rod with the cable.") - - else if(istype(I, /obj/item/assembly/igniter) && !(HAS_TRAIT(I, TRAIT_NODROP))) - var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod - - remove_item_from_storage(user) - - to_chat(user, "You fasten [I] to the top of the rod with the cable.") - - qdel(I) - qdel(src) - - user.put_in_hands(P) - else - return ..() - - -/obj/item/throwing_star - name = "throwing star" - desc = "An ancient weapon still used to this day, due to its ease of lodging itself into its victim's body parts." - icon_state = "throwingstar" - inhand_icon_state = "eshield0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - force = 2 - throwforce = 20 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding - throw_speed = 4 - embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0) - armour_penetration = 40 - - w_class = WEIGHT_CLASS_SMALL - sharpness = IS_SHARP - custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) - resistance_flags = FIRE_PROOF - -/obj/item/throwing_star/stamina - name = "shock throwing star" - desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm." - throwforce = 5 - embedding = list("pain_chance" = 5, "embed_chance" = 100, "fall_chance" = 0, "jostle_chance" = 10, "pain_stam_pct" = 0.8, "jostle_pain_mult" = 3) - -/obj/item/throwing_star/toy - name = "toy throwing star" - desc = "An aerodynamic disc strapped with adhesive for sticking to people, good for playing pranks and getting yourself killed by security." - sharpness = IS_BLUNT - force = 0 - throwforce = 0 - embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 100, "fall_chance" = 0) - -/obj/item/throwing_star/magspear - name = "magnetic spear" - desc = "A reusable spear that is typically loaded into kinetic spearguns." - icon = 'icons/obj/ammo.dmi' - icon_state = "magspear" - throwforce = 25 //kills regular carps in one hit - force = 10 - throw_range = 0 //throwing these invalidates the speargun - attack_verb = list("stabbed", "ripped", "gored", "impaled") - embedding = list("pain_mult" = 8, "embed_chance" = 100, "fall_chance" = 0, "impact_pain_mult" = 15) //55 damage+embed on hit - -/obj/item/switchblade - name = "switchblade" - icon_state = "switchblade" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A sharp, concealable, spring-loaded knife." - flags_1 = CONDUCT_1 - force = 3 - w_class = WEIGHT_CLASS_SMALL - throwforce = 5 - throw_speed = 3 - throw_range = 6 - custom_materials = list(/datum/material/iron=12000) - hitsound = 'sound/weapons/genhit.ogg' - attack_verb = list("stubbed", "poked") - resistance_flags = FIRE_PROOF - var/extended = 0 - -/obj/item/switchblade/attack_self(mob/user) - extended = !extended - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) - if(extended) - force = 20 - w_class = WEIGHT_CLASS_NORMAL - throwforce = 23 - icon_state = "switchblade_ext" - attack_verb = list("slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - else - force = 3 - w_class = WEIGHT_CLASS_SMALL - throwforce = 5 - icon_state = "switchblade" - attack_verb = list("stubbed", "poked") - hitsound = 'sound/weapons/genhit.ogg' - sharpness = IS_BLUNT - -/obj/item/switchblade/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] own throat with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/phone - name = "red phone" - desc = "Should anything ever go wrong..." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "red_phone" - force = 3 - throwforce = 2 - throw_speed = 3 - throw_range = 4 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("called", "rang") - hitsound = 'sound/weapons/ring.ogg' - -/obj/item/phone/suicide_act(mob/user) - if(locate(/obj/structure/chair/stool) in user.loc) - user.visible_message("[user] begins to tie a noose with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") - else - user.visible_message("[user] is strangling [user.p_them()]self with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") - return(OXYLOSS) - -/obj/item/cane - name = "cane" - desc = "A cane used by a true gentleman. Or a clown." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "cane" - inhand_icon_state = "stick" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 5 - throwforce = 5 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=50) - attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed") - -/obj/item/staff - name = "wizard staff" - desc = "Apparently a staff used by the wizard." - icon = 'icons/obj/wizard.dmi' - icon_state = "staff" - lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' - force = 3 - throwforce = 5 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - armour_penetration = 100 - attack_verb = list("bludgeoned", "whacked", "disciplined") - resistance_flags = FLAMMABLE - -/obj/item/staff/broom - name = "broom" - desc = "Used for sweeping, and flying into the night while cackling. Black cat not included." - icon = 'icons/obj/wizard.dmi' - icon_state = "broom" - resistance_flags = FLAMMABLE - -/obj/item/staff/stick - name = "stick" - desc = "A great tool to drag someone else's drinks across the bar." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "cane" - inhand_icon_state = "stick" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 3 - throwforce = 5 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - -/obj/item/ectoplasm - name = "ectoplasm" - desc = "Spooky." - gender = PLURAL - icon = 'icons/obj/wizard.dmi' - icon_state = "ectoplasm" - -/obj/item/ectoplasm/suicide_act(mob/user) - user.visible_message("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the astral plane!") - return (OXYLOSS) - -/obj/item/ectoplasm/angelic - icon = 'icons/obj/wizard.dmi' - icon_state = "angelplasm" - -/obj/item/mounted_chainsaw - name = "mounted chainsaw" - desc = "A chainsaw that has replaced your arm." - icon_state = "chainsaw_on" - inhand_icon_state = "mounted_chainsaw" - lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' - item_flags = ABSTRACT | DROPDEL - w_class = WEIGHT_CLASS_HUGE - force = 24 - throwforce = 0 - throw_range = 0 - throw_speed = 0 - sharpness = IS_SHARP - attack_verb = list("sawed", "tore", "lacerated", "cut", "chopped", "diced") - hitsound = 'sound/weapons/chainsawhit.ogg' - tool_behaviour = TOOL_SAW - toolspeed = 1 - -/obj/item/mounted_chainsaw/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) - -/obj/item/mounted_chainsaw/Destroy() - var/obj/item/bodypart/part - new /obj/item/chainsaw(get_turf(src)) - if(iscarbon(loc)) - var/mob/living/carbon/holder = loc - var/index = holder.get_held_index_of_item(src) - if(index) - part = holder.hand_bodyparts[index] - . = ..() - if(part) - part.drop_limb() - -/obj/item/statuebust - name = "bust" - desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it - icon = 'icons/obj/statue.dmi' - icon_state = "bust" - force = 15 - throwforce = 10 - throw_speed = 5 - throw_range = 2 - attack_verb = list("busted") - var/impressiveness = 45 - -/obj/item/statuebust/Initialize() - . = ..() - AddComponent(/datum/component/art, impressiveness) - addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 1000)), 0) - -/obj/item/statuebust/hippocratic - name = "hippocrates bust" - desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine." - icon_state = "hippocratic" - impressiveness = 50 - -/obj/item/tailclub - name = "tail club" - desc = "For the beating to death of lizards with their own tails." - icon_state = "tailclub" - force = 14 - throwforce = 1 // why are you throwing a club do you even weapon - throw_speed = 1 - throw_range = 1 - attack_verb = list("clubbed", "bludgeoned") - -/obj/item/melee/chainofcommand/tailwhip - name = "liz o' nine tails" - desc = "A whip fashioned from the severed tails of lizards." - icon_state = "tailwhip" - inhand_icon_state = "tailwhip" - item_flags = NONE - -/obj/item/melee/chainofcommand/tailwhip/kitty - name = "cat o' nine tails" - desc = "A whip fashioned from the severed tails of cats." - icon_state = "catwhip" - inhand_icon_state = "catwhip" - -/obj/item/melee/skateboard - name = "improvised skateboard" - desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon." - icon_state = "skateboard" - inhand_icon_state = "skateboard" - force = 12 - throwforce = 4 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("smacked", "whacked", "slammed", "smashed") - ///The vehicle counterpart for the board - var/board_item_type = /obj/vehicle/ridden/scooter/skateboard - -/obj/item/melee/skateboard/attack_self(mob/user) - var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasn't my fault - S.buckle_mob(user) - qdel(src) - -/obj/item/melee/skateboard/pro - name = "skateboard" - desc = "A RaDSTORMz brand professional skateboard. It looks sturdy and well made." - icon_state = "skateboard2" - inhand_icon_state = "skateboard2" - board_item_type = /obj/vehicle/ridden/scooter/skateboard/pro - custom_premium_price = 500 - -/obj/item/melee/skateboard/hoverboard - name = "hoverboard" - desc = "A blast from the past, so retro!" - icon_state = "hoverboard_red" - inhand_icon_state = "hoverboard_red" - board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard - custom_premium_price = 2015 - -/obj/item/melee/skateboard/hoverboard/admin - name = "\improper Board Of Directors" - desc = "The engineering complexity of a spaceship concentrated inside of a board. Just as expensive, too." - icon_state = "hoverboard_nt" - inhand_icon_state = "hoverboard_nt" - board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard/admin - -/obj/item/melee/baseball_bat - name = "baseball bat" - desc = "There ain't a skull in the league that can withstand a swatter." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "baseball_bat" - inhand_icon_state = "baseball_bat" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 10 - wound_bonus = -10 - throwforce = 12 - attack_verb = list("beat", "smacked") - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5) - w_class = WEIGHT_CLASS_HUGE - var/homerun_ready = 0 - var/homerun_able = 0 - -/obj/item/melee/baseball_bat/homerun - name = "home run bat" - desc = "This thing looks dangerous... Dangerously good at baseball, that is." - homerun_able = 1 - -/obj/item/melee/baseball_bat/attack_self(mob/user) - if(!homerun_able) - ..() - return - if(homerun_ready) - to_chat(user, "You're already ready to do a home run!") - ..() - return - to_chat(user, "You begin gathering strength...") - playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, TRUE) - if(do_after(user, 90, target = src)) - to_chat(user, "You gather power! Time for a home run!") - homerun_ready = 1 - ..() - -/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user) - . = ..() - var/atom/throw_target = get_edge_target_turf(target, user.dir) - if(homerun_ready) - user.visible_message("It's a home run!") - target.throw_at(throw_target, rand(8,10), 14, user) - SSexplosions.medturf += throw_target - playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, TRUE) - homerun_ready = 0 - return - else if(!target.anchored) - var/whack_speed = (prob(60) ? 1 : 4) - target.throw_at(throw_target, rand(1, 2), whack_speed, user) // sorry friends, 7 speed batting caused wounds to absolutely delete whoever you knocked your target into (and said target) - -/obj/item/melee/baseball_bat/ablative - name = "metal baseball bat" - desc = "This bat is made of highly reflective, highly armored material." - icon_state = "baseball_bat_metal" - inhand_icon_state = "baseball_bat_metal" - force = 12 - throwforce = 15 - -/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers - var/picksound = rand(1,2) - var/turf = get_turf(src) - if(picksound == 1) - playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, TRUE) - if(picksound == 2) - playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, TRUE) - return 1 - -/obj/item/melee/flyswatter - name = "flyswatter" - desc = "Useful for killing insects of all sizes." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "flyswatter" - inhand_icon_state = "flyswatter" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 1 - throwforce = 1 - attack_verb = list("swatted", "smacked") - hitsound = 'sound/effects/snap.ogg' - w_class = WEIGHT_CLASS_SMALL - //Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc. - var/list/strong_against - -/obj/item/melee/flyswatter/Initialize() - . = ..() - strong_against = typecacheof(list( - /mob/living/simple_animal/hostile/poison/bees/, - /mob/living/simple_animal/butterfly, - /mob/living/simple_animal/hostile/cockroach, - /obj/item/queen_bee - )) - - -/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag) - . = ..() - if(proximity_flag) - if(is_type_in_typecache(target, strong_against)) - new /obj/effect/decal/cleanable/insectguts(target.drop_location()) - to_chat(user, "You easily splat the [target].") - if(istype(target, /mob/living/)) - var/mob/living/bug = target - bug.death(1) - else - qdel(target) - -/obj/item/circlegame - name = "circled hand" - desc = "If somebody looks at this while it's below your waist, you get to bop them." - icon_state = "madeyoulook" - force = 0 - throwforce = 0 - item_flags = DROPDEL | ABSTRACT | HAND_ITEM - attack_verb = list("bopped") - -/obj/item/circlegame/Initialize() - . = ..() - var/mob/living/owner = loc - if(!istype(owner)) - return - RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined) - -/obj/item/circlegame/Destroy() - var/mob/owner = loc - if(istype(owner)) - UnregisterSignal(owner, COMSIG_PARENT_EXAMINE) - return ..() - -/obj/item/circlegame/dropped(mob/user) - UnregisterSignal(user, COMSIG_PARENT_EXAMINE) //loc will have changed by the time this is called, so Destroy() can't catch it - // this is a dropdel item. - return ..() - -/// Stage 1: The mistake is made -/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker) - if(!istype(sucker) || !in_range(owner, sucker)) - return - addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4) - -/// Stage 2: Fear sets in -/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker) - if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner)) - return - - if(owner == sucker) // big mood - to_chat(owner, "Wait a second... you just looked at your own [src.name]!") - addtimer(CALLBACK(src, .proc/selfGottem, owner), 10) - else - to_chat(sucker, "Wait a second... was that a-") - addtimer(CALLBACK(src, .proc/GOTTEM, owner, sucker), 6) - -/// Stage 3A: We face our own failures -/obj/item/circlegame/proc/selfGottem(mob/living/owner) - if(QDELETED(src) || QDELETED(owner)) - return - - playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1) - owner.visible_message("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].", "You shamefully bop yourself with your [src.name].", \ - "You hear a dull thud!") - log_combat(owner, owner, "bopped", src.name, "(self)") - owner.do_attack_animation(owner) - owner.apply_damage(100, STAMINA) - owner.Knockdown(10) - qdel(src) - -/// Stage 3B: We face our reckoning (unless we moved away or they're incapacitated) -/obj/item/circlegame/proc/GOTTEM(mob/living/owner, mob/living/sucker) - if(QDELETED(sucker)) - return - - if(QDELETED(src) || QDELETED(owner)) - to_chat(sucker, "Nevermind... must've been your imagination...") - return - - if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE)) - to_chat(sucker, "Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...") - return - - to_chat(owner, "[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!") - to_chat(sucker, "[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!") - - owner.face_atom(sucker) - if(owner.client) - owner.client.give_award(/datum/award/achievement/misc/gottem, owner) // then everybody clapped - - playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1) - owner.do_attack_animation(sucker) - - if(HAS_TRAIT(owner, TRAIT_HULK)) - owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!", \ - "You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!", "You hear a sickening sound of flesh hitting flesh!", ignored_mobs=list(sucker)) - to_chat(sucker, "[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!") - sucker.apply_damage(50, STAMINA) - sucker.Knockdown(50) - log_combat(owner, sucker, "bopped", src.name, "(setup- Hulk)") - var/atom/throw_target = get_edge_target_turf(sucker, owner.dir) - sucker.throw_at(throw_target, 6, 3, owner) - else - owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name]!", "You bop [sucker] with your [src.name]!", \ - "You hear a dull thud!", ignored_mobs=list(sucker)) - sucker.apply_damage(15, STAMINA) - log_combat(owner, sucker, "bopped", src.name, "(setup)") - to_chat(sucker, "[owner] bops you with [owner.p_their()] [src.name]!") - qdel(src) - -/obj/item/slapper - name = "slapper" - desc = "This is how real men fight." - icon_state = "latexballon" - inhand_icon_state = "nothing" - force = 0 - throwforce = 0 - item_flags = DROPDEL | ABSTRACT | HAND_ITEM - attack_verb = list("slapped") - hitsound = 'sound/effects/snap.ogg' - -/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user) - if(ishuman(M)) - var/mob/living/carbon/human/L = M - if(L && L.dna && L.dna.species) - L.dna.species.stop_wagging_tail(M) - user.do_attack_animation(M) - playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1) - user.visible_message("[user] slaps [M]!", - "You slap [M]!",\ - "You hear a slap.") - return -/obj/item/proc/can_trigger_gun(mob/living/user) - if(!user.can_use_guns(src)) - return FALSE - return TRUE - -/obj/item/extendohand - name = "extendo-hand" - desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "extendohand" - inhand_icon_state = "extendohand" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 0 - throwforce = 5 - reach = 2 - var/min_reach = 2 - -/obj/item/extendohand/acme - name = "\improper ACME Extendo-Hand" - desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners." - -/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user) - var/dist = get_dist(M, user) - if(dist < min_reach) - to_chat(user, "[M] is too close to use [src] on.") - return - M.attack_hand(user) - -/obj/item/gohei - name = "gohei" - desc = "A wooden stick with white streamers at the end. Originally used by shrine maidens to purify things. Now used by the station's valued weeaboos." - force = 5 - throwforce = 5 - hitsound = "swing_hit" - attack_verb = list("whacked", "thwacked", "walloped", "socked") - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "gohei" - inhand_icon_state = "gohei" - lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' - -//HF blade -/obj/item/vibro_weapon - icon_state = "hfrequency0" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - name = "vibro sword" - desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire." - armour_penetration = 100 - block_chance = 40 - force = 20 - throwforce = 20 - throw_speed = 4 - sharpness = IS_SHARP - attack_verb = list("cut", "sliced", "diced") - w_class = WEIGHT_CLASS_BULKY - slot_flags = ITEM_SLOT_BACK - hitsound = 'sound/weapons/bladeslice.ogg' - var/wielded = FALSE // track wielded status on item - -/obj/item/vibro_weapon/Initialize() - . = ..() - RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) - RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) - -/obj/item/vibro_weapon/ComponentInitialize() - . = ..() - AddComponent(/datum/component/butchering, 20, 105) - AddComponent(/datum/component/two_handed, force_multiplier=2, icon_wielded="hfrequency1") - -/// triggered on wield of two handed item -/obj/item/vibro_weapon/proc/on_wield(obj/item/source, mob/user) - wielded = TRUE - -/// triggered on unwield of two handed item -/obj/item/vibro_weapon/proc/on_unwield(obj/item/source, mob/user) - wielded = FALSE - -/obj/item/vibro_weapon/update_icon_state() - icon_state = "hfrequency0" - -/obj/item/vibro_weapon/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(wielded) - final_block_chance *= 2 - if(wielded || attack_type != PROJECTILE_ATTACK) - if(prob(final_block_chance)) - if(attack_type == PROJECTILE_ATTACK) - owner.visible_message("[owner] deflects [attack_text] with [src]!") - playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) - return 1 - else - owner.visible_message("[owner] parries [attack_text] with [src]!") - return 1 - return 0 +/obj/item/banhammer + desc = "A banhammer." + name = "banhammer" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "toyhammer" + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + force = 1 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + attack_verb = list("banned") + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + +/obj/item/banhammer/suicide_act(mob/user) + user.visible_message("[user] is hitting [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.") + return (BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS) +/* +oranges says: This is a meme relating to the english translation of the ss13 russian wiki page on lurkmore. +mrdoombringer sez: and remember kids, if you try and PR a fix for this item's grammar, you are admitting that you are, indeed, a newfriend. +for further reading, please see: https://github.com/tgstation/tgstation/pull/30173 and https://translate.google.com/translate?sl=auto&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=%2F%2Flurkmore.to%2FSS13&edit-text=&act=url +*/ +/obj/item/banhammer/attack(mob/M, mob/user) + if(user.zone_selected == BODY_ZONE_HEAD) + M.visible_message("[user] are stroking the head of [M] with a bangammer.", "[user] are stroking your head with a bangammer.", "You hear a bangammer stroking a head.") // see above comment + else + M.visible_message("[M] has been banned FOR NO REISIN by [user]!", "You have been banned FOR NO REISIN by [user]!", "You hear a banhammer banning someone.") + playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much + if(user.a_intent != INTENT_HELP) + return ..(M, user) + +/obj/item/sord + name = "\improper SORD" + desc = "This thing is so unspeakably shitty you are having a hard time even holding it." + icon_state = "sord" + inhand_icon_state = "sord" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + slot_flags = ITEM_SLOT_BELT + force = 2 + throwforce = 1 + w_class = WEIGHT_CLASS_NORMAL + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") + +/obj/item/sord/suicide_act(mob/user) + user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \ + "You try to impale yourself with [src], but it's USELESS...") + return SHAME + +/obj/item/claymore + name = "claymore" + desc = "What are you standing around staring at this for? Get to killing!" + icon_state = "claymore" + inhand_icon_state = "claymore" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + hitsound = 'sound/weapons/bladeslice.ogg' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + force = 40 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") + block_chance = 50 + sharpness = IS_SHARP + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + +/obj/item/claymore/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 40, 105) + +/obj/item/claymore/suicide_act(mob/user) + user.visible_message("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return(BRUTELOSS) + +/obj/item/claymore/highlander //ALL COMMENTS MADE REGARDING THIS SWORD MUST BE MADE IN ALL CAPS + desc = "THERE CAN BE ONLY ONE, AND IT WILL BE YOU!!!\nActivate it in your hand to point to the nearest victim." + flags_1 = CONDUCT_1 + item_flags = DROPDEL //WOW BRO YOU LOST AN ARM, GUESS WHAT YOU DONT GET YOUR SWORD ANYMORE //I CANT BELIEVE SPOOKYDONUT WOULD BREAK THE REQUIREMENTS + slot_flags = null + block_chance = 0 //RNG WON'T HELP YOU NOW, PANSY + light_range = 3 + attack_verb = list("brutalized", "eviscerated", "disemboweled", "hacked", "carved", "cleaved") //ONLY THE MOST VISCERAL ATTACK VERBS + var/notches = 0 //HOW MANY PEOPLE HAVE BEEN SLAIN WITH THIS BLADE + var/obj/item/disk/nuclear/nuke_disk //OUR STORED NUKE DISK + +/obj/item/claymore/highlander/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) + START_PROCESSING(SSobj, src) + +/obj/item/claymore/highlander/Destroy() + if(nuke_disk) + nuke_disk.forceMove(get_turf(src)) + nuke_disk.visible_message("The nuke disk is vulnerable!") + nuke_disk = null + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/claymore/highlander/process() + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + loc.layer = LARGE_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS) + H.bleedsuppress = TRUE //AND WE WON'T BLEED OUT LIKE COWARDS + else + if(!(flags_1 & ADMIN_SPAWNED_1)) + qdel(src) + + +/obj/item/claymore/highlander/pickup(mob/living/user) + . = ..() + to_chat(user, "The power of Scotland protects you! You are shielded from all stuns and knockdowns.") + user.add_stun_absorption("highlander", INFINITY, 1, " is protected by the power of Scotland!", "The power of Scotland absorbs the stun!", " is protected by the power of Scotland!") + user.ignore_slowdown(HIGHLANDER) + +/obj/item/claymore/highlander/dropped(mob/living/user) + . = ..() + user.unignore_slowdown(HIGHLANDER) + +/obj/item/claymore/highlander/examine(mob/user) + . = ..() + . += "It has [!notches ? "nothing" : "[notches] notches"] scratched into the blade." + if(nuke_disk) + . += "It's holding the nuke disk!" + +/obj/item/claymore/highlander/attack(mob/living/target, mob/living/user) + . = ..() + if(!QDELETED(target) && iscarbon(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander") + user.fully_heal(admin_revive = FALSE) //STEAL THE LIFE OF OUR FALLEN FOES + add_notch(user) + target.visible_message("[target] crumbles to dust beneath [user]'s blows!", "As you fall, your body crumbles to dust!") + target.dust() + +/obj/item/claymore/highlander/attack_self(mob/living/user) + var/closest_victim + var/closest_distance = 255 + for(var/mob/living/carbon/human/H in GLOB.player_list - user) + if(H.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance)) + closest_victim = H + if(!closest_victim) + to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.") + return + to_chat(user, "[src] thrums and points to the [dir2text(get_dir(user, closest_victim))].") + +/obj/item/claymore/highlander/IsReflect() + return 1 //YOU THINK YOUR PUNY LASERS CAN STOP ME? + +/obj/item/claymore/highlander/proc/add_notch(mob/living/user) //DYNAMIC CLAYMORE PROGRESSION SYSTEM - THIS IS THE FUTURE + notches++ + force++ + var/new_name = name + switch(notches) + if(1) + to_chat(user, "Your first kill - hopefully one of many. You scratch a notch into [src]'s blade.") + to_chat(user, "You feel your fallen foe's soul entering your blade, restoring your wounds!") + new_name = "notched claymore" + if(2) + to_chat(user, "Another falls before you. Another soul fuses with your own. Another notch in the blade.") + new_name = "double-notched claymore" + add_atom_colour(rgb(255, 235, 235), ADMIN_COLOUR_PRIORITY) + if(3) + to_chat(user, "You're beginning to relish the thrill of battle.") + new_name = "triple-notched claymore" + add_atom_colour(rgb(255, 215, 215), ADMIN_COLOUR_PRIORITY) + if(4) + to_chat(user, "You've lost count of how many you've killed.") + new_name = "many-notched claymore" + add_atom_colour(rgb(255, 195, 195), ADMIN_COLOUR_PRIORITY) + if(5) + to_chat(user, "Five voices now echo in your mind, cheering the slaughter.") + new_name = "battle-tested claymore" + add_atom_colour(rgb(255, 175, 175), ADMIN_COLOUR_PRIORITY) + if(6) + to_chat(user, "Is this what the vikings felt like? Visions of glory fill your head as you slay your sixth foe.") + new_name = "battle-scarred claymore" + add_atom_colour(rgb(255, 155, 155), ADMIN_COLOUR_PRIORITY) + if(7) + to_chat(user, "Kill. Butcher. Conquer.") + new_name = "vicious claymore" + add_atom_colour(rgb(255, 135, 135), ADMIN_COLOUR_PRIORITY) + if(8) + to_chat(user, "IT NEVER GETS OLD. THE SCREAMING. THE BLOOD AS IT SPRAYS ACROSS YOUR FACE.") + new_name = "bloodthirsty claymore" + add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) + if(9) + to_chat(user, "ANOTHER ONE FALLS TO YOUR BLOWS. ANOTHER WEAKLING UNFIT TO LIVE.") + new_name = "gore-stained claymore" + add_atom_colour(rgb(255, 95, 95), ADMIN_COLOUR_PRIORITY) + if(10) + user.visible_message("[user]'s eyes light up with a vengeful fire!", \ + "YOU FEEL THE POWER OF VALHALLA FLOWING THROUGH YOU! THERE CAN BE ONLY ONE!!!") + user.update_icons() + new_name = "GORE-DRENCHED CLAYMORE OF [pick("THE WHIMSICAL SLAUGHTER", "A THOUSAND SLAUGHTERED CATTLE", "GLORY AND VALHALLA", "ANNIHILATION", "OBLITERATION")]" + icon_state = "claymore_gold" + inhand_icon_state = "cultblade" + remove_atom_colour(ADMIN_COLOUR_PRIORITY) + + name = new_name + playsound(user, 'sound/items/screwdriver2.ogg', 50, TRUE) + +/obj/item/katana + name = "katana" + desc = "Woefully underpowered in D20." + icon_state = "katana" + inhand_icon_state = "katana" + worn_icon_state = "katana" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + force = 40 + throwforce = 10 + w_class = WEIGHT_CLASS_HUGE + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") + block_chance = 50 + sharpness = IS_SHARP + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + +/obj/item/katana/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!") + return(BRUTELOSS) + +/obj/item/katana/cursed + slot_flags = null + item_flags = DROPDEL + +/obj/item/katana/cursed/equipped(mob/living/carbon/human/user) + . = ..() + if(!istype(user)) + return + user.gain_trauma(/datum/brain_trauma/magic/stalker, TRAUMA_RESILIENCE_MAGIC) + +/obj/item/katana/cursed/Initialize() + ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT) + +/obj/item/wirerod + name = "wired rod" + desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit." + icon_state = "wiredrod" + inhand_icon_state = "rods" + flags_1 = CONDUCT_1 + force = 9 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=1150, /datum/material/glass=75) + attack_verb = list("hit", "bludgeoned", "whacked", "bonked") + +/obj/item/wirerod/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/shard)) + var/obj/item/spear/S = new /obj/item/spear + + remove_item_from_storage(user) + if (!user.transferItemToLoc(I, S)) + return + S.CheckParts(list(I)) + qdel(src) + + user.put_in_hands(S) + to_chat(user, "You fasten the glass shard to the top of the rod with the cable.") + + else if(istype(I, /obj/item/assembly/igniter) && !(HAS_TRAIT(I, TRAIT_NODROP))) + var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod + + remove_item_from_storage(user) + + to_chat(user, "You fasten [I] to the top of the rod with the cable.") + + qdel(I) + qdel(src) + + user.put_in_hands(P) + else + return ..() + + +/obj/item/throwing_star + name = "throwing star" + desc = "An ancient weapon still used to this day, due to its ease of lodging itself into its victim's body parts." + icon_state = "throwingstar" + inhand_icon_state = "eshield0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + force = 2 + throwforce = 20 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding + throw_speed = 4 + embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0) + armour_penetration = 40 + + w_class = WEIGHT_CLASS_SMALL + sharpness = IS_SHARP + custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) + resistance_flags = FIRE_PROOF + +/obj/item/throwing_star/stamina + name = "shock throwing star" + desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm." + throwforce = 5 + embedding = list("pain_chance" = 5, "embed_chance" = 100, "fall_chance" = 0, "jostle_chance" = 10, "pain_stam_pct" = 0.8, "jostle_pain_mult" = 3) + +/obj/item/throwing_star/toy + name = "toy throwing star" + desc = "An aerodynamic disc strapped with adhesive for sticking to people, good for playing pranks and getting yourself killed by security." + sharpness = IS_BLUNT + force = 0 + throwforce = 0 + embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 100, "fall_chance" = 0) + +/obj/item/throwing_star/magspear + name = "magnetic spear" + desc = "A reusable spear that is typically loaded into kinetic spearguns." + icon = 'icons/obj/ammo.dmi' + icon_state = "magspear" + throwforce = 25 //kills regular carps in one hit + force = 10 + throw_range = 0 //throwing these invalidates the speargun + attack_verb = list("stabbed", "ripped", "gored", "impaled") + embedding = list("pain_mult" = 8, "embed_chance" = 100, "fall_chance" = 0, "impact_pain_mult" = 15) //55 damage+embed on hit + +/obj/item/switchblade + name = "switchblade" + icon_state = "switchblade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "A sharp, concealable, spring-loaded knife." + flags_1 = CONDUCT_1 + force = 3 + w_class = WEIGHT_CLASS_SMALL + throwforce = 5 + throw_speed = 3 + throw_range = 6 + custom_materials = list(/datum/material/iron=12000) + hitsound = 'sound/weapons/genhit.ogg' + attack_verb = list("stubbed", "poked") + resistance_flags = FIRE_PROOF + var/extended = 0 + +/obj/item/switchblade/attack_self(mob/user) + extended = !extended + playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) + if(extended) + force = 20 + w_class = WEIGHT_CLASS_NORMAL + throwforce = 23 + icon_state = "switchblade_ext" + attack_verb = list("slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + else + force = 3 + w_class = WEIGHT_CLASS_SMALL + throwforce = 5 + icon_state = "switchblade" + attack_verb = list("stubbed", "poked") + hitsound = 'sound/weapons/genhit.ogg' + sharpness = IS_BLUNT + +/obj/item/switchblade/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] own throat with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/phone + name = "red phone" + desc = "Should anything ever go wrong..." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "red_phone" + force = 3 + throwforce = 2 + throw_speed = 3 + throw_range = 4 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("called", "rang") + hitsound = 'sound/weapons/ring.ogg' + +/obj/item/phone/suicide_act(mob/user) + if(locate(/obj/structure/chair/stool) in user.loc) + user.visible_message("[user] begins to tie a noose with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") + else + user.visible_message("[user] is strangling [user.p_them()]self with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") + return(OXYLOSS) + +/obj/item/cane + name = "cane" + desc = "A cane used by a true gentleman. Or a clown." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "cane" + inhand_icon_state = "stick" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 5 + throwforce = 5 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=50) + attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed") + +/obj/item/staff + name = "wizard staff" + desc = "Apparently a staff used by the wizard." + icon = 'icons/obj/wizard.dmi' + icon_state = "staff" + lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' + force = 3 + throwforce = 5 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + armour_penetration = 100 + attack_verb = list("bludgeoned", "whacked", "disciplined") + resistance_flags = FLAMMABLE + +/obj/item/staff/broom + name = "broom" + desc = "Used for sweeping, and flying into the night while cackling. Black cat not included." + icon = 'icons/obj/wizard.dmi' + icon_state = "broom" + resistance_flags = FLAMMABLE + +/obj/item/staff/stick + name = "stick" + desc = "A great tool to drag someone else's drinks across the bar." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "cane" + inhand_icon_state = "stick" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 3 + throwforce = 5 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + +/obj/item/ectoplasm + name = "ectoplasm" + desc = "Spooky." + gender = PLURAL + icon = 'icons/obj/wizard.dmi' + icon_state = "ectoplasm" + +/obj/item/ectoplasm/suicide_act(mob/user) + user.visible_message("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the astral plane!") + return (OXYLOSS) + +/obj/item/ectoplasm/angelic + icon = 'icons/obj/wizard.dmi' + icon_state = "angelplasm" + +/obj/item/mounted_chainsaw + name = "mounted chainsaw" + desc = "A chainsaw that has replaced your arm." + icon_state = "chainsaw_on" + inhand_icon_state = "mounted_chainsaw" + lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' + item_flags = ABSTRACT | DROPDEL + w_class = WEIGHT_CLASS_HUGE + force = 24 + throwforce = 0 + throw_range = 0 + throw_speed = 0 + sharpness = IS_SHARP + attack_verb = list("sawed", "tore", "lacerated", "cut", "chopped", "diced") + hitsound = 'sound/weapons/chainsawhit.ogg' + tool_behaviour = TOOL_SAW + toolspeed = 1 + +/obj/item/mounted_chainsaw/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + +/obj/item/mounted_chainsaw/Destroy() + var/obj/item/bodypart/part + new /obj/item/chainsaw(get_turf(src)) + if(iscarbon(loc)) + var/mob/living/carbon/holder = loc + var/index = holder.get_held_index_of_item(src) + if(index) + part = holder.hand_bodyparts[index] + . = ..() + if(part) + part.drop_limb() + +/obj/item/statuebust + name = "bust" + desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it + icon = 'icons/obj/statue.dmi' + icon_state = "bust" + force = 15 + throwforce = 10 + throw_speed = 5 + throw_range = 2 + attack_verb = list("busted") + var/impressiveness = 45 + +/obj/item/statuebust/Initialize() + . = ..() + AddComponent(/datum/component/art, impressiveness) + addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 1000)), 0) + +/obj/item/statuebust/hippocratic + name = "hippocrates bust" + desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine." + icon_state = "hippocratic" + impressiveness = 50 + +/obj/item/tailclub + name = "tail club" + desc = "For the beating to death of lizards with their own tails." + icon_state = "tailclub" + force = 14 + throwforce = 1 // why are you throwing a club do you even weapon + throw_speed = 1 + throw_range = 1 + attack_verb = list("clubbed", "bludgeoned") + +/obj/item/melee/chainofcommand/tailwhip + name = "liz o' nine tails" + desc = "A whip fashioned from the severed tails of lizards." + icon_state = "tailwhip" + inhand_icon_state = "tailwhip" + item_flags = NONE + +/obj/item/melee/chainofcommand/tailwhip/kitty + name = "cat o' nine tails" + desc = "A whip fashioned from the severed tails of cats." + icon_state = "catwhip" + inhand_icon_state = "catwhip" + +/obj/item/melee/skateboard + name = "improvised skateboard" + desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon." + icon_state = "skateboard" + inhand_icon_state = "skateboard" + force = 12 + throwforce = 4 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("smacked", "whacked", "slammed", "smashed") + ///The vehicle counterpart for the board + var/board_item_type = /obj/vehicle/ridden/scooter/skateboard + +/obj/item/melee/skateboard/attack_self(mob/user) + var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasn't my fault + S.buckle_mob(user) + qdel(src) + +/obj/item/melee/skateboard/pro + name = "skateboard" + desc = "A RaDSTORMz brand professional skateboard. It looks sturdy and well made." + icon_state = "skateboard2" + inhand_icon_state = "skateboard2" + board_item_type = /obj/vehicle/ridden/scooter/skateboard/pro + custom_premium_price = 500 + +/obj/item/melee/skateboard/hoverboard + name = "hoverboard" + desc = "A blast from the past, so retro!" + icon_state = "hoverboard_red" + inhand_icon_state = "hoverboard_red" + board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard + custom_premium_price = 2015 + +/obj/item/melee/skateboard/hoverboard/admin + name = "\improper Board Of Directors" + desc = "The engineering complexity of a spaceship concentrated inside of a board. Just as expensive, too." + icon_state = "hoverboard_nt" + inhand_icon_state = "hoverboard_nt" + board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard/admin + +/obj/item/melee/baseball_bat + name = "baseball bat" + desc = "There ain't a skull in the league that can withstand a swatter." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "baseball_bat" + inhand_icon_state = "baseball_bat" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 10 + wound_bonus = -10 + throwforce = 12 + attack_verb = list("beat", "smacked") + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5) + w_class = WEIGHT_CLASS_HUGE + var/homerun_ready = 0 + var/homerun_able = 0 + +/obj/item/melee/baseball_bat/homerun + name = "home run bat" + desc = "This thing looks dangerous... Dangerously good at baseball, that is." + homerun_able = 1 + +/obj/item/melee/baseball_bat/attack_self(mob/user) + if(!homerun_able) + ..() + return + if(homerun_ready) + to_chat(user, "You're already ready to do a home run!") + ..() + return + to_chat(user, "You begin gathering strength...") + playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, TRUE) + if(do_after(user, 90, target = src)) + to_chat(user, "You gather power! Time for a home run!") + homerun_ready = 1 + ..() + +/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user) + . = ..() + var/atom/throw_target = get_edge_target_turf(target, user.dir) + if(homerun_ready) + user.visible_message("It's a home run!") + target.throw_at(throw_target, rand(8,10), 14, user) + SSexplosions.medturf += throw_target + playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, TRUE) + homerun_ready = 0 + return + else if(!target.anchored) + var/whack_speed = (prob(60) ? 1 : 4) + target.throw_at(throw_target, rand(1, 2), whack_speed, user) // sorry friends, 7 speed batting caused wounds to absolutely delete whoever you knocked your target into (and said target) + +/obj/item/melee/baseball_bat/ablative + name = "metal baseball bat" + desc = "This bat is made of highly reflective, highly armored material." + icon_state = "baseball_bat_metal" + inhand_icon_state = "baseball_bat_metal" + force = 12 + throwforce = 15 + +/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers + var/picksound = rand(1,2) + var/turf = get_turf(src) + if(picksound == 1) + playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, TRUE) + if(picksound == 2) + playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, TRUE) + return 1 + +/obj/item/melee/flyswatter + name = "flyswatter" + desc = "Useful for killing insects of all sizes." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "flyswatter" + inhand_icon_state = "flyswatter" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 1 + throwforce = 1 + attack_verb = list("swatted", "smacked") + hitsound = 'sound/effects/snap.ogg' + w_class = WEIGHT_CLASS_SMALL + //Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc. + var/list/strong_against + +/obj/item/melee/flyswatter/Initialize() + . = ..() + strong_against = typecacheof(list( + /mob/living/simple_animal/hostile/poison/bees/, + /mob/living/simple_animal/butterfly, + /mob/living/simple_animal/hostile/cockroach, + /obj/item/queen_bee + )) + + +/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(proximity_flag) + if(is_type_in_typecache(target, strong_against)) + new /obj/effect/decal/cleanable/insectguts(target.drop_location()) + to_chat(user, "You easily splat the [target].") + if(istype(target, /mob/living/)) + var/mob/living/bug = target + bug.death(1) + else + qdel(target) + +/obj/item/circlegame + name = "circled hand" + desc = "If somebody looks at this while it's below your waist, you get to bop them." + icon_state = "madeyoulook" + force = 0 + throwforce = 0 + item_flags = DROPDEL | ABSTRACT | HAND_ITEM + attack_verb = list("bopped") + +/obj/item/circlegame/Initialize() + . = ..() + var/mob/living/owner = loc + if(!istype(owner)) + return + RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined) + +/obj/item/circlegame/Destroy() + var/mob/owner = loc + if(istype(owner)) + UnregisterSignal(owner, COMSIG_PARENT_EXAMINE) + return ..() + +/obj/item/circlegame/dropped(mob/user) + UnregisterSignal(user, COMSIG_PARENT_EXAMINE) //loc will have changed by the time this is called, so Destroy() can't catch it + // this is a dropdel item. + return ..() + +/// Stage 1: The mistake is made +/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker) + if(!istype(sucker) || !in_range(owner, sucker)) + return + addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4) + +/// Stage 2: Fear sets in +/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker) + if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner)) + return + + if(owner == sucker) // big mood + to_chat(owner, "Wait a second... you just looked at your own [src.name]!") + addtimer(CALLBACK(src, .proc/selfGottem, owner), 10) + else + to_chat(sucker, "Wait a second... was that a-") + addtimer(CALLBACK(src, .proc/GOTTEM, owner, sucker), 6) + +/// Stage 3A: We face our own failures +/obj/item/circlegame/proc/selfGottem(mob/living/owner) + if(QDELETED(src) || QDELETED(owner)) + return + + playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1) + owner.visible_message("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].", "You shamefully bop yourself with your [src.name].", \ + "You hear a dull thud!") + log_combat(owner, owner, "bopped", src.name, "(self)") + owner.do_attack_animation(owner) + owner.apply_damage(100, STAMINA) + owner.Knockdown(10) + qdel(src) + +/// Stage 3B: We face our reckoning (unless we moved away or they're incapacitated) +/obj/item/circlegame/proc/GOTTEM(mob/living/owner, mob/living/sucker) + if(QDELETED(sucker)) + return + + if(QDELETED(src) || QDELETED(owner)) + to_chat(sucker, "Nevermind... must've been your imagination...") + return + + if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE)) + to_chat(sucker, "Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...") + return + + to_chat(owner, "[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!") + to_chat(sucker, "[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!") + + owner.face_atom(sucker) + if(owner.client) + owner.client.give_award(/datum/award/achievement/misc/gottem, owner) // then everybody clapped + + playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1) + owner.do_attack_animation(sucker) + + if(HAS_TRAIT(owner, TRAIT_HULK)) + owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!", \ + "You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!", "You hear a sickening sound of flesh hitting flesh!", ignored_mobs=list(sucker)) + to_chat(sucker, "[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!") + sucker.apply_damage(50, STAMINA) + sucker.Knockdown(50) + log_combat(owner, sucker, "bopped", src.name, "(setup- Hulk)") + var/atom/throw_target = get_edge_target_turf(sucker, owner.dir) + sucker.throw_at(throw_target, 6, 3, owner) + else + owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name]!", "You bop [sucker] with your [src.name]!", \ + "You hear a dull thud!", ignored_mobs=list(sucker)) + sucker.apply_damage(15, STAMINA) + log_combat(owner, sucker, "bopped", src.name, "(setup)") + to_chat(sucker, "[owner] bops you with [owner.p_their()] [src.name]!") + qdel(src) + +/obj/item/slapper + name = "slapper" + desc = "This is how real men fight." + icon_state = "latexballon" + inhand_icon_state = "nothing" + force = 0 + throwforce = 0 + item_flags = DROPDEL | ABSTRACT | HAND_ITEM + attack_verb = list("slapped") + hitsound = 'sound/effects/snap.ogg' + +/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user) + if(ishuman(M)) + var/mob/living/carbon/human/L = M + if(L && L.dna && L.dna.species) + L.dna.species.stop_wagging_tail(M) + user.do_attack_animation(M) + playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1) + user.visible_message("[user] slaps [M]!", + "You slap [M]!",\ + "You hear a slap.") + return +/obj/item/proc/can_trigger_gun(mob/living/user) + if(!user.can_use_guns(src)) + return FALSE + return TRUE + +/obj/item/extendohand + name = "extendo-hand" + desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "extendohand" + inhand_icon_state = "extendohand" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 0 + throwforce = 5 + reach = 2 + var/min_reach = 2 + +/obj/item/extendohand/acme + name = "\improper ACME Extendo-Hand" + desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners." + +/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user) + var/dist = get_dist(M, user) + if(dist < min_reach) + to_chat(user, "[M] is too close to use [src] on.") + return + M.attack_hand(user) + +/obj/item/gohei + name = "gohei" + desc = "A wooden stick with white streamers at the end. Originally used by shrine maidens to purify things. Now used by the station's valued weeaboos." + force = 5 + throwforce = 5 + hitsound = "swing_hit" + attack_verb = list("whacked", "thwacked", "walloped", "socked") + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "gohei" + inhand_icon_state = "gohei" + lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' + +//HF blade +/obj/item/vibro_weapon + icon_state = "hfrequency0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + name = "vibro sword" + desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire." + armour_penetration = 100 + block_chance = 40 + force = 20 + throwforce = 20 + throw_speed = 4 + sharpness = IS_SHARP + attack_verb = list("cut", "sliced", "diced") + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK + hitsound = 'sound/weapons/bladeslice.ogg' + var/wielded = FALSE // track wielded status on item + +/obj/item/vibro_weapon/Initialize() + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + +/obj/item/vibro_weapon/ComponentInitialize() + . = ..() + AddComponent(/datum/component/butchering, 20, 105) + AddComponent(/datum/component/two_handed, force_multiplier=2, icon_wielded="hfrequency1") + +/// triggered on wield of two handed item +/obj/item/vibro_weapon/proc/on_wield(obj/item/source, mob/user) + wielded = TRUE + +/// triggered on unwield of two handed item +/obj/item/vibro_weapon/proc/on_unwield(obj/item/source, mob/user) + wielded = FALSE + +/obj/item/vibro_weapon/update_icon_state() + icon_state = "hfrequency0" + +/obj/item/vibro_weapon/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(wielded) + final_block_chance *= 2 + if(wielded || attack_type != PROJECTILE_ATTACK) + if(prob(final_block_chance)) + if(attack_type == PROJECTILE_ATTACK) + owner.visible_message("[owner] deflects [attack_text] with [src]!") + playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) + return 1 + else + owner.visible_message("[owner] parries [attack_text] with [src]!") + return 1 + return 0 diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 294adb944cc..afef5eb81c2 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -1,352 +1,352 @@ - -/obj - animate_movement = SLIDE_STEPS - speech_span = SPAN_ROBOT - var/obj_flags = CAN_BE_HIT - var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT. - - var/damtype = BRUTE - var/force = 0 - - /// How good a given object is at causing wounds on carbons. Higher values equal better shots at creating serious wounds. - var/wound_bonus = 0 - /// If this attacks a human with no wound armor on the affected body part, add this to the wound mod. Some attacks may be significantly worse at wounding if there's even a slight layer of armor to absorb some of it vs bare flesh - var/bare_wound_bonus = 0 - - var/datum/armor/armor - var/obj_integrity //defaults to max_integrity - var/max_integrity = 500 - var/integrity_failure = 0 //0 if we have no special broken behavior, otherwise is a percentage of at what point the obj breaks. 0.5 being 50% - ///Damage under this value will be completely ignored - var/damage_deflection = 0 - - var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF - - var/acid_level = 0 //how much acid is on that obj - - var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset. - var/current_skin //Has the item been reskinned? - var/list/unique_reskin //List of options to reskin. - - // Access levels, used in modules\jobs\access.dm - var/list/req_access - var/req_access_txt = "0" - var/list/req_one_access - var/req_one_access_txt = "0" - /// Custom fire overlay icon - var/custom_fire_overlay - - var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object - - var/drag_slowdown // Amont of multiplicative slowdown applied if pulled. >1 makes you slower, <1 makes you faster. - - vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of obj in openspace. - -/obj/vv_edit_var(vname, vval) - switch(vname) - if("anchored") - setAnchored(vval) - return TRUE - if(NAMEOF(src, obj_flags)) - if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) - return FALSE - return ..() - -/obj/Initialize() - if (islist(armor)) - armor = getArmor(arglist(armor)) - else if (!armor) - armor = getArmor() - else if (!istype(armor, /datum/armor)) - stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()") - if(obj_integrity == null) - obj_integrity = max_integrity - - . = ..() //Do this after, else mat datums is mad. - - if (set_obj_flags) - var/flagslist = splittext(set_obj_flags,";") - var/list/string_to_objflag = GLOB.bitfields["obj_flags"] - for (var/flag in flagslist) - if(flag[1] == "!") - flag = copytext(flag, length(flag[1]) + 1) // Get all but the initial ! - obj_flags &= ~string_to_objflag[flag] - else - obj_flags |= string_to_objflag[flag] - if((obj_flags & ON_BLUEPRINTS) && isturf(loc)) - var/turf/T = loc - T.add_blueprints_preround(src) - -/obj/Destroy(force=FALSE) - if(!ismachinery(src)) - STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists - SStgui.close_uis(src) - . = ..() - -/obj/proc/setAnchored(anchorvalue) - SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) - anchored = anchorvalue - -/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - ..() - if(obj_flags & FROZEN) - visible_message("[src] shatters into a million pieces!") - qdel(src) - - -/obj/assume_air(datum/gas_mixture/giver) - if(loc) - return loc.assume_air(giver) - else - return null - -/obj/remove_air(amount) - if(loc) - return loc.remove_air(amount) - else - return null - -/obj/return_air() - if(loc) - return loc.return_air() - else - return null - -/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) - //Return: (NONSTANDARD) - // null if object handles breathing logic for lifeform - // datum/air_group to tell lifeform to process using that breath return - //DEFAULT: Take air from turf to give to have mob process - - if(breath_request>0) - var/datum/gas_mixture/environment = return_air() - var/breath_percentage = BREATH_VOLUME / environment.return_volume() - return remove_air(environment.total_moles() * breath_percentage) - else - return null - -/obj/proc/updateUsrDialog() - if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) - var/is_in_use = FALSE - var/list/nearby = viewers(1, src) - for(var/mob/M in nearby) - if ((M.client && M.machine == src)) - is_in_use = TRUE - ui_interact(M) - if(issilicon(usr) || IsAdminGhost(usr)) - if (!(usr in nearby)) - if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. - is_in_use = TRUE - ui_interact(usr) - - // check for TK users - - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - if(!(usr in nearby)) - if(usr.client && usr.machine==src) - if(H.dna.check_mutation(TK)) - is_in_use = TRUE - ui_interact(usr) - if (is_in_use) - obj_flags |= IN_USE - else - obj_flags &= ~IN_USE - -/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) - // Check that people are actually using the machine. If not, don't update anymore. - if(obj_flags & IN_USE) - var/is_in_use = FALSE - if(update_viewers) - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - is_in_use = TRUE - src.interact(M) - var/ai_in_use = FALSE - if(update_ais) - ai_in_use = AutoUpdateAI(src) - - if(update_viewers && update_ais) //State change is sure only if we check both - if(!ai_in_use && !is_in_use) - obj_flags &= ~IN_USE - - -/obj/attack_ghost(mob/user) - . = ..() - if(.) - return - ui_interact(user) - -/obj/proc/container_resist(mob/living/user) - return - -/mob/proc/unset_machine() - if(machine) - machine.on_unset_machine(src) - machine = null - -//called when the user unsets the machine. -/atom/movable/proc/on_unset_machine(mob/user) - return - -/mob/proc/set_machine(obj/O) - if(src.machine) - unset_machine() - src.machine = O - if(istype(O)) - O.obj_flags |= IN_USE - -/obj/item/proc/updateSelfDialog() - var/mob/M = src.loc - if(istype(M) && M.client && M.machine == src) - src.attack_self(M) - -/obj/singularity_pull(S, current_size) - ..() - if(!anchored || current_size >= STAGE_FIVE) - step_towards(src,S) - -/obj/get_dumping_location(datum/component/storage/source,mob/user) - return get_turf(src) - -/obj/proc/CanAStarPass() - . = !density - -/obj/proc/check_uplink_validity() - return 1 - -/obj/vv_get_dropdown() - . = ..() - VV_DROPDOWN_OPTION("", "---") - VV_DROPDOWN_OPTION(VV_HK_MASS_DEL_TYPE, "Delete all of type") - VV_DROPDOWN_OPTION(VV_HK_OSAY, "Object Say") - VV_DROPDOWN_OPTION(VV_HK_ARMOR_MOD, "Modify armor values") - -/obj/vv_do_topic(list/href_list) - if(!(. = ..())) - return - if(href_list[VV_HK_OSAY]) - if(check_rights(R_FUN, FALSE)) - usr.client.object_say(src) - if(href_list[VV_HK_ARMOR_MOD]) - var/list/pickerlist = list() - var/list/armorlist = armor.getList() - - for (var/i in armorlist) - pickerlist += list(list("value" = armorlist[i], "name" = i)) - - var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [src]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist) - - if (islist(result)) - if (result["button"] != 2) // If the user pressed the cancel button - // text2num conveniently returns a null on invalid values - armor = armor.setRating(melee = text2num(result["values"]["melee"]),\ - bullet = text2num(result["values"]["bullet"]),\ - laser = text2num(result["values"]["laser"]),\ - energy = text2num(result["values"]["energy"]),\ - bomb = text2num(result["values"]["bomb"]),\ - bio = text2num(result["values"]["bio"]),\ - rad = text2num(result["values"]["rad"]),\ - fire = text2num(result["values"]["fire"]),\ - acid = text2num(result["values"]["acid"])) - log_admin("[key_name(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") - message_admins("[key_name_admin(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") - if(href_list[VV_HK_MASS_DEL_TYPE]) - if(check_rights(R_DEBUG|R_SERVER)) - var/action_type = alert("Strict type ([type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel") - if(action_type == "Cancel" || !action_type) - return - - if(alert("Are you really sure you want to delete all objects of type [type]?",,"Yes","No") != "Yes") - return - - if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") - return - - var/O_type = type - switch(action_type) - if("Strict type") - var/i = 0 - for(var/obj/Obj in world) - if(Obj.type == O_type) - i++ - qdel(Obj) - CHECK_TICK - if(!i) - to_chat(usr, "No objects of this type exist") - return - log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") - message_admins("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") - if("Type and subtypes") - var/i = 0 - for(var/obj/Obj in world) - if(istype(Obj,O_type)) - i++ - qdel(Obj) - CHECK_TICK - if(!i) - to_chat(usr, "No objects of this type exist") - return - log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") - message_admins("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") - -/obj/examine(mob/user) - . = ..() - if(obj_flags & UNIQUE_RENAME) - . += "Use a pen on it to rename it or change its description." - if(unique_reskin && !current_skin) - . += "Alt-click it to reskin it." - -/obj/AltClick(mob/user) - . = ..() - if(unique_reskin && !current_skin && user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY)) - reskin_obj(user) - -/obj/proc/reskin_obj(mob/M) - if(!LAZYLEN(unique_reskin)) - return - to_chat(M, "Reskin options for [name]:") - for(var/V in unique_reskin) - var/output = icon2html(src, M, unique_reskin[V]) - to_chat(M, "[V]: [output]") - - var/choice = input(M,"Warning, you can only reskin [src] once!","Reskin Object") as null|anything in sortList(unique_reskin) - if(!QDELETED(src) && choice && !current_skin && !M.incapacitated() && in_range(M,src)) - if(!unique_reskin[choice]) - return - current_skin = choice - icon_state = unique_reskin[choice] - to_chat(M, "[src] is now skinned as '[choice].'") - -/obj/analyzer_act(mob/living/user, obj/item/I) - if(atmosanalyzer_scan(user, src)) - return TRUE - return ..() - -/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) - return - -// Should move all contained objects to it's location. -/obj/proc/dump_contents() - CRASH("Unimplemented.") - -/obj/handle_ricochet(obj/projectile/P) - . = ..() - if(. && ricochet_damage_mod) - take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet - -/obj/update_overlays() - . = ..() - if(acid_level) - . += GLOB.acid_overlay - if(resistance_flags & ON_FIRE) - . += custom_fire_overlay ? custom_fire_overlay : GLOB.fire_overlay - -/// Handles exposing an object to reagents. -/obj/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) - if((. = ..()) & COMPONENT_NO_EXPOSE_REAGENTS) - return - - for(var/reagent in reagents) - var/datum/reagent/R = reagent - . |= R.expose_obj(src, reagents[R]) + +/obj + animate_movement = SLIDE_STEPS + speech_span = SPAN_ROBOT + var/obj_flags = CAN_BE_HIT + var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT. + + var/damtype = BRUTE + var/force = 0 + + /// How good a given object is at causing wounds on carbons. Higher values equal better shots at creating serious wounds. + var/wound_bonus = 0 + /// If this attacks a human with no wound armor on the affected body part, add this to the wound mod. Some attacks may be significantly worse at wounding if there's even a slight layer of armor to absorb some of it vs bare flesh + var/bare_wound_bonus = 0 + + var/datum/armor/armor + var/obj_integrity //defaults to max_integrity + var/max_integrity = 500 + var/integrity_failure = 0 //0 if we have no special broken behavior, otherwise is a percentage of at what point the obj breaks. 0.5 being 50% + ///Damage under this value will be completely ignored + var/damage_deflection = 0 + + var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF + + var/acid_level = 0 //how much acid is on that obj + + var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset. + var/current_skin //Has the item been reskinned? + var/list/unique_reskin //List of options to reskin. + + // Access levels, used in modules\jobs\access.dm + var/list/req_access + var/req_access_txt = "0" + var/list/req_one_access + var/req_one_access_txt = "0" + /// Custom fire overlay icon + var/custom_fire_overlay + + var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object + + var/drag_slowdown // Amont of multiplicative slowdown applied if pulled. >1 makes you slower, <1 makes you faster. + + vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of obj in openspace. + +/obj/vv_edit_var(vname, vval) + switch(vname) + if("anchored") + setAnchored(vval) + return TRUE + if(NAMEOF(src, obj_flags)) + if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) + return FALSE + return ..() + +/obj/Initialize() + if (islist(armor)) + armor = getArmor(arglist(armor)) + else if (!armor) + armor = getArmor() + else if (!istype(armor, /datum/armor)) + stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()") + if(obj_integrity == null) + obj_integrity = max_integrity + + . = ..() //Do this after, else mat datums is mad. + + if (set_obj_flags) + var/flagslist = splittext(set_obj_flags,";") + var/list/string_to_objflag = GLOB.bitfields["obj_flags"] + for (var/flag in flagslist) + if(flag[1] == "!") + flag = copytext(flag, length(flag[1]) + 1) // Get all but the initial ! + obj_flags &= ~string_to_objflag[flag] + else + obj_flags |= string_to_objflag[flag] + if((obj_flags & ON_BLUEPRINTS) && isturf(loc)) + var/turf/T = loc + T.add_blueprints_preround(src) + +/obj/Destroy(force=FALSE) + if(!ismachinery(src)) + STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists + SStgui.close_uis(src) + . = ..() + +/obj/proc/setAnchored(anchorvalue) + SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) + anchored = anchorvalue + +/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + ..() + if(obj_flags & FROZEN) + visible_message("[src] shatters into a million pieces!") + qdel(src) + + +/obj/assume_air(datum/gas_mixture/giver) + if(loc) + return loc.assume_air(giver) + else + return null + +/obj/remove_air(amount) + if(loc) + return loc.remove_air(amount) + else + return null + +/obj/return_air() + if(loc) + return loc.return_air() + else + return null + +/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) + //Return: (NONSTANDARD) + // null if object handles breathing logic for lifeform + // datum/air_group to tell lifeform to process using that breath return + //DEFAULT: Take air from turf to give to have mob process + + if(breath_request>0) + var/datum/gas_mixture/environment = return_air() + var/breath_percentage = BREATH_VOLUME / environment.return_volume() + return remove_air(environment.total_moles() * breath_percentage) + else + return null + +/obj/proc/updateUsrDialog() + if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) + var/is_in_use = FALSE + var/list/nearby = viewers(1, src) + for(var/mob/M in nearby) + if ((M.client && M.machine == src)) + is_in_use = TRUE + ui_interact(M) + if(issilicon(usr) || IsAdminGhost(usr)) + if (!(usr in nearby)) + if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. + is_in_use = TRUE + ui_interact(usr) + + // check for TK users + + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + if(!(usr in nearby)) + if(usr.client && usr.machine==src) + if(H.dna.check_mutation(TK)) + is_in_use = TRUE + ui_interact(usr) + if (is_in_use) + obj_flags |= IN_USE + else + obj_flags &= ~IN_USE + +/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) + // Check that people are actually using the machine. If not, don't update anymore. + if(obj_flags & IN_USE) + var/is_in_use = FALSE + if(update_viewers) + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + is_in_use = TRUE + src.interact(M) + var/ai_in_use = FALSE + if(update_ais) + ai_in_use = AutoUpdateAI(src) + + if(update_viewers && update_ais) //State change is sure only if we check both + if(!ai_in_use && !is_in_use) + obj_flags &= ~IN_USE + + +/obj/attack_ghost(mob/user) + . = ..() + if(.) + return + ui_interact(user) + +/obj/proc/container_resist(mob/living/user) + return + +/mob/proc/unset_machine() + if(machine) + machine.on_unset_machine(src) + machine = null + +//called when the user unsets the machine. +/atom/movable/proc/on_unset_machine(mob/user) + return + +/mob/proc/set_machine(obj/O) + if(src.machine) + unset_machine() + src.machine = O + if(istype(O)) + O.obj_flags |= IN_USE + +/obj/item/proc/updateSelfDialog() + var/mob/M = src.loc + if(istype(M) && M.client && M.machine == src) + src.attack_self(M) + +/obj/singularity_pull(S, current_size) + ..() + if(!anchored || current_size >= STAGE_FIVE) + step_towards(src,S) + +/obj/get_dumping_location(datum/component/storage/source,mob/user) + return get_turf(src) + +/obj/proc/CanAStarPass() + . = !density + +/obj/proc/check_uplink_validity() + return 1 + +/obj/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION("", "---") + VV_DROPDOWN_OPTION(VV_HK_MASS_DEL_TYPE, "Delete all of type") + VV_DROPDOWN_OPTION(VV_HK_OSAY, "Object Say") + VV_DROPDOWN_OPTION(VV_HK_ARMOR_MOD, "Modify armor values") + +/obj/vv_do_topic(list/href_list) + if(!(. = ..())) + return + if(href_list[VV_HK_OSAY]) + if(check_rights(R_FUN, FALSE)) + usr.client.object_say(src) + if(href_list[VV_HK_ARMOR_MOD]) + var/list/pickerlist = list() + var/list/armorlist = armor.getList() + + for (var/i in armorlist) + pickerlist += list(list("value" = armorlist[i], "name" = i)) + + var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [src]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist) + + if (islist(result)) + if (result["button"] != 2) // If the user pressed the cancel button + // text2num conveniently returns a null on invalid values + armor = armor.setRating(melee = text2num(result["values"]["melee"]),\ + bullet = text2num(result["values"]["bullet"]),\ + laser = text2num(result["values"]["laser"]),\ + energy = text2num(result["values"]["energy"]),\ + bomb = text2num(result["values"]["bomb"]),\ + bio = text2num(result["values"]["bio"]),\ + rad = text2num(result["values"]["rad"]),\ + fire = text2num(result["values"]["fire"]),\ + acid = text2num(result["values"]["acid"])) + log_admin("[key_name(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") + message_admins("[key_name_admin(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") + if(href_list[VV_HK_MASS_DEL_TYPE]) + if(check_rights(R_DEBUG|R_SERVER)) + var/action_type = alert("Strict type ([type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel") + if(action_type == "Cancel" || !action_type) + return + + if(alert("Are you really sure you want to delete all objects of type [type]?",,"Yes","No") != "Yes") + return + + if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") + return + + var/O_type = type + switch(action_type) + if("Strict type") + var/i = 0 + for(var/obj/Obj in world) + if(Obj.type == O_type) + i++ + qdel(Obj) + CHECK_TICK + if(!i) + to_chat(usr, "No objects of this type exist") + return + log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") + message_admins("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") + if("Type and subtypes") + var/i = 0 + for(var/obj/Obj in world) + if(istype(Obj,O_type)) + i++ + qdel(Obj) + CHECK_TICK + if(!i) + to_chat(usr, "No objects of this type exist") + return + log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") + message_admins("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") + +/obj/examine(mob/user) + . = ..() + if(obj_flags & UNIQUE_RENAME) + . += "Use a pen on it to rename it or change its description." + if(unique_reskin && !current_skin) + . += "Alt-click it to reskin it." + +/obj/AltClick(mob/user) + . = ..() + if(unique_reskin && !current_skin && user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY)) + reskin_obj(user) + +/obj/proc/reskin_obj(mob/M) + if(!LAZYLEN(unique_reskin)) + return + to_chat(M, "Reskin options for [name]:") + for(var/V in unique_reskin) + var/output = icon2html(src, M, unique_reskin[V]) + to_chat(M, "[V]: [output]") + + var/choice = input(M,"Warning, you can only reskin [src] once!","Reskin Object") as null|anything in sortList(unique_reskin) + if(!QDELETED(src) && choice && !current_skin && !M.incapacitated() && in_range(M,src)) + if(!unique_reskin[choice]) + return + current_skin = choice + icon_state = unique_reskin[choice] + to_chat(M, "[src] is now skinned as '[choice].'") + +/obj/analyzer_act(mob/living/user, obj/item/I) + if(atmosanalyzer_scan(user, src)) + return TRUE + return ..() + +/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) + return + +// Should move all contained objects to it's location. +/obj/proc/dump_contents() + CRASH("Unimplemented.") + +/obj/handle_ricochet(obj/projectile/P) + . = ..() + if(. && ricochet_damage_mod) + take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet + +/obj/update_overlays() + . = ..() + if(acid_level) + . += GLOB.acid_overlay + if(resistance_flags & ON_FIRE) + . += custom_fire_overlay ? custom_fire_overlay : GLOB.fire_overlay + +/// Handles exposing an object to reagents. +/obj/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) + if((. = ..()) & COMPONENT_NO_EXPOSE_REAGENTS) + return + + for(var/reagent in reagents) + var/datum/reagent/R = reagent + . |= R.expose_obj(src, reagents[R]) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 76919c2adaa..0c9c98f970e 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -1,134 +1,134 @@ -/obj/structure - icon = 'icons/obj/structures.dmi' - pressure_resistance = 8 - max_integrity = 300 - interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT - layer = BELOW_OBJ_LAYER - flags_ricochet = RICOCHET_HARD - ricochet_chance_mod = 0.5 - - var/climb_time = 20 - var/climb_stun = 20 - var/climbable = FALSE - var/mob/living/structureclimber - var/broken = 0 //similar to machinery's stat BROKEN - - -/obj/structure/Initialize() - if (!armor) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - . = ..() - if(smooth) - queue_smooth(src) - queue_smooth_neighbors(src) - icon_state = "" - GLOB.cameranet.updateVisibility(src) - -/obj/structure/Destroy() - GLOB.cameranet.updateVisibility(src) - if(smooth) - queue_smooth_neighbors(src) - return ..() - -/obj/structure/attack_hand(mob/user) - . = ..() - if(.) - return - if(structureclimber && structureclimber != user) - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src) - structureclimber.Paralyze(40) - structureclimber.visible_message("[structureclimber] is knocked off [src].", "You're knocked off [src]!", "You see [structureclimber] get knocked off [src].") - -/obj/structure/ui_act(action, params) - . = ..() - add_fingerprint(usr) - -/obj/structure/MouseDrop_T(atom/movable/O, mob/user) - . = ..() - if(!climbable) - return - if(user == O && isliving(O)) - var/mob/living/L = O - if(isanimal(L)) - var/mob/living/simple_animal/A = L - if (!A.dextrous) - return - if(L.mobility_flags & MOBILITY_MOVE) - climb_structure(user) - return - if(!istype(O, /obj/item) || user.get_active_held_item() != O) - return - if(iscyborg(user)) - return - if(!user.dropItemToGround(O)) - return - if (O.loc != src.loc) - step(O, get_dir(O, src)) - -/obj/structure/proc/do_climb(atom/movable/A) - if(climbable) - if(A.loc == src.loc) - var/where_to_climb = get_step(A,dir) - if(!(is_blocked_turf(where_to_climb))) - A.forceMove(where_to_climb) - return TRUE - density = FALSE - . = step(A,get_dir(A,src.loc)) - density = TRUE - -/obj/structure/proc/climb_structure(mob/living/user) - src.add_fingerprint(user) - user.visible_message("[user] starts climbing onto [src].", \ - "You start climbing onto [src]...") - var/adjusted_climb_time = climb_time - if(user.restrained()) //climbing takes twice as long when restrained. - adjusted_climb_time *= 2 - if(isalien(user)) - adjusted_climb_time *= 0.25 //aliens are terrifyingly fast - if(HAS_TRAIT(user, TRAIT_FREERUNNING)) //do you have any idea how fast I am??? - adjusted_climb_time *= 0.8 - structureclimber = user - if(do_mob(user, user, adjusted_climb_time)) - if(src.loc) //Checking if structure has been destroyed - if(do_climb(user)) - user.visible_message("[user] climbs onto [src].", \ - "You climb onto [src].") - log_combat(user, src, "climbed onto") - if(climb_stun) - user.Stun(climb_stun) - . = 1 - else - to_chat(user, "You fail to climb onto [src].") - structureclimber = null - -/obj/structure/examine(mob/user) - . = ..() - if(!(resistance_flags & INDESTRUCTIBLE)) - if(resistance_flags & ON_FIRE) - . += "It's on fire!" - if(broken) - . += "It appears to be broken." - var/examine_status = examine_status(user) - if(examine_status) - . += examine_status - -/obj/structure/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - - if(mover.pass_flags & PASSSTRUCTURE) - return TRUE - -/obj/structure/proc/examine_status(mob/user) //An overridable proc, mostly for falsewalls. - var/healthpercent = (obj_integrity/max_integrity) * 100 - switch(healthpercent) - if(50 to 99) - return "It looks slightly damaged." - if(25 to 50) - return "It appears heavily damaged." - if(0 to 25) - if(!broken) - return "It's falling apart!" - -/obj/structure/rust_heretic_act() - take_damage(500, BRUTE, "melee", 1) +/obj/structure + icon = 'icons/obj/structures.dmi' + pressure_resistance = 8 + max_integrity = 300 + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT + layer = BELOW_OBJ_LAYER + flags_ricochet = RICOCHET_HARD + ricochet_chance_mod = 0.5 + + var/climb_time = 20 + var/climb_stun = 20 + var/climbable = FALSE + var/mob/living/structureclimber + var/broken = 0 //similar to machinery's stat BROKEN + + +/obj/structure/Initialize() + if (!armor) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + . = ..() + if(smooth) + queue_smooth(src) + queue_smooth_neighbors(src) + icon_state = "" + GLOB.cameranet.updateVisibility(src) + +/obj/structure/Destroy() + GLOB.cameranet.updateVisibility(src) + if(smooth) + queue_smooth_neighbors(src) + return ..() + +/obj/structure/attack_hand(mob/user) + . = ..() + if(.) + return + if(structureclimber && structureclimber != user) + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src) + structureclimber.Paralyze(40) + structureclimber.visible_message("[structureclimber] is knocked off [src].", "You're knocked off [src]!", "You see [structureclimber] get knocked off [src].") + +/obj/structure/ui_act(action, params) + . = ..() + add_fingerprint(usr) + +/obj/structure/MouseDrop_T(atom/movable/O, mob/user) + . = ..() + if(!climbable) + return + if(user == O && isliving(O)) + var/mob/living/L = O + if(isanimal(L)) + var/mob/living/simple_animal/A = L + if (!A.dextrous) + return + if(L.mobility_flags & MOBILITY_MOVE) + climb_structure(user) + return + if(!istype(O, /obj/item) || user.get_active_held_item() != O) + return + if(iscyborg(user)) + return + if(!user.dropItemToGround(O)) + return + if (O.loc != src.loc) + step(O, get_dir(O, src)) + +/obj/structure/proc/do_climb(atom/movable/A) + if(climbable) + if(A.loc == src.loc) + var/where_to_climb = get_step(A,dir) + if(!(is_blocked_turf(where_to_climb))) + A.forceMove(where_to_climb) + return TRUE + density = FALSE + . = step(A,get_dir(A,src.loc)) + density = TRUE + +/obj/structure/proc/climb_structure(mob/living/user) + src.add_fingerprint(user) + user.visible_message("[user] starts climbing onto [src].", \ + "You start climbing onto [src]...") + var/adjusted_climb_time = climb_time + if(user.restrained()) //climbing takes twice as long when restrained. + adjusted_climb_time *= 2 + if(isalien(user)) + adjusted_climb_time *= 0.25 //aliens are terrifyingly fast + if(HAS_TRAIT(user, TRAIT_FREERUNNING)) //do you have any idea how fast I am??? + adjusted_climb_time *= 0.8 + structureclimber = user + if(do_mob(user, user, adjusted_climb_time)) + if(src.loc) //Checking if structure has been destroyed + if(do_climb(user)) + user.visible_message("[user] climbs onto [src].", \ + "You climb onto [src].") + log_combat(user, src, "climbed onto") + if(climb_stun) + user.Stun(climb_stun) + . = 1 + else + to_chat(user, "You fail to climb onto [src].") + structureclimber = null + +/obj/structure/examine(mob/user) + . = ..() + if(!(resistance_flags & INDESTRUCTIBLE)) + if(resistance_flags & ON_FIRE) + . += "It's on fire!" + if(broken) + . += "It appears to be broken." + var/examine_status = examine_status(user) + if(examine_status) + . += examine_status + +/obj/structure/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + + if(mover.pass_flags & PASSSTRUCTURE) + return TRUE + +/obj/structure/proc/examine_status(mob/user) //An overridable proc, mostly for falsewalls. + var/healthpercent = (obj_integrity/max_integrity) * 100 + switch(healthpercent) + if(50 to 99) + return "It looks slightly damaged." + if(25 to 50) + return "It appears heavily damaged." + if(0 to 25) + if(!broken) + return "It's falling apart!" + +/obj/structure/rust_heretic_act() + take_damage(500, BRUTE, "melee", 1) diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm index 276ddca1d79..3dc69b1a592 100644 --- a/code/game/objects/structures/ai_core.dm +++ b/code/game/objects/structures/ai_core.dm @@ -1,331 +1,331 @@ -/obj/structure/ai_core - density = TRUE - anchored = FALSE - name = "\improper AI core" - icon = 'icons/mob/ai.dmi' - icon_state = "0" - desc = "The framework for an artificial intelligence core." - max_integrity = 500 - var/state = EMPTY_CORE - var/datum/ai_laws/laws - var/obj/item/circuitboard/aicore/circuit - var/obj/item/mmi/brain - var/can_deconstruct = TRUE - -/obj/structure/ai_core/Initialize() - . = ..() - laws = new - laws.set_laws_config() - -/obj/structure/ai_core/handle_atom_del(atom/A) - if(A == circuit) - circuit = null - if((state != GLASS_CORE) && (state != AI_READY_CORE)) - state = EMPTY_CORE - update_icon() - if(A == brain) - brain = null - . = ..() - - -/obj/structure/ai_core/Destroy() - if(circuit) - qdel(circuit) - circuit = null - if(brain) - qdel(brain) - brain = null - return ..() - -/obj/structure/ai_core/latejoin_inactive - name = "networked AI core" - desc = "This AI core is connected by bluespace transmitters to NTNet, allowing for an AI personality to be downloaded to it on the fly mid-shift." - can_deconstruct = FALSE - icon_state = "ai-empty" - anchored = TRUE - state = AI_READY_CORE - var/available = TRUE - var/safety_checks = TRUE - var/active = TRUE - -/obj/structure/ai_core/latejoin_inactive/examine(mob/user) - . = ..() - . += "Its transmitter seems to be [active? "on" : "off"]." - . += "You could [active? "deactivate" : "activate"] it with a multitool." - -/obj/structure/ai_core/latejoin_inactive/proc/is_available() //If people still manage to use this feature to spawn-kill AI latejoins ahelp them. - if(!available) - return FALSE - if(!safety_checks) - return TRUE - if(!active) - return FALSE - var/turf/T = get_turf(src) - var/area/A = get_area(src) - if(!A.blob_allowed) - return FALSE - if(!A.power_equip) - return FALSE - if(!SSmapping.level_trait(T.z,ZTRAIT_STATION)) - return FALSE - if(!istype(T, /turf/open/floor)) - return FALSE - return TRUE - -/obj/structure/ai_core/latejoin_inactive/attackby(obj/item/P, mob/user, params) - if(P.tool_behaviour == TOOL_MULTITOOL) - active = !active - to_chat(user, "You [active? "activate" : "deactivate"] \the [src]'s transmitters.") - return - return ..() - -/obj/structure/ai_core/latejoin_inactive/Initialize() - . = ..() - GLOB.latejoin_ai_cores += src - -/obj/structure/ai_core/latejoin_inactive/Destroy() - GLOB.latejoin_ai_cores -= src - return ..() - -/obj/structure/ai_core/attackby(obj/item/P, mob/user, params) - if(P.tool_behaviour == TOOL_WRENCH) - return default_unfasten_wrench(user, P, 20) - if(!anchored) - if(P.tool_behaviour == TOOL_WELDER && can_deconstruct) - if(state != EMPTY_CORE) - to_chat(user, "The core must be empty to deconstruct it!") - return - - if(!P.tool_start_check(user, amount=0)) - return - - to_chat(user, "You start to deconstruct the frame...") - if(P.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) - to_chat(user, "You deconstruct the frame.") - deconstruct(TRUE) - return - else - switch(state) - if(EMPTY_CORE) - if(istype(P, /obj/item/circuitboard/aicore)) - if(!user.transferItemToLoc(P, src)) - return - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You place the circuit board inside the frame.") - update_icon() - state = CIRCUIT_CORE - circuit = P - return - if(CIRCUIT_CORE) - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You screw the circuit board into place.") - state = SCREWED_CORE - update_icon() - return - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the circuit board.") - state = EMPTY_CORE - update_icon() - circuit.forceMove(loc) - circuit = null - return - if(SCREWED_CORE) - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You unfasten the circuit board.") - state = CIRCUIT_CORE - update_icon() - return - if(istype(P, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = P - if(C.get_amount() >= 5) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You start to add cables to the frame...") - if(do_after(user, 20, target = src) && state == SCREWED_CORE && C.use(5)) - to_chat(user, "You add cables to the frame.") - state = CABLED_CORE - update_icon() - else - to_chat(user, "You need five lengths of cable to wire the AI core!") - return - if(CABLED_CORE) - if(P.tool_behaviour == TOOL_WIRECUTTER) - if(brain) - to_chat(user, "Get that [brain.name] out of there first!") - else - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = SCREWED_CORE - update_icon() - new /obj/item/stack/cable_coil(drop_location(), 5) - return - - if(istype(P, /obj/item/stack/sheet/rglass)) - var/obj/item/stack/sheet/rglass/G = P - if(G.get_amount() >= 2) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You start to put in the glass panel...") - if(do_after(user, 20, target = src) && state == CABLED_CORE && G.use(2)) - to_chat(user, "You put in the glass panel.") - state = GLASS_CORE - update_icon() - else - to_chat(user, "You need two sheets of reinforced glass to insert them into the AI core!") - return - - if(istype(P, /obj/item/ai_module)) - if(brain && brain.laws.id != DEFAULT_AI_LAWID) - to_chat(user, "The installed [brain.name] already has set laws!") - return - var/obj/item/ai_module/module = P - module.install(laws, user) - return - - if(istype(P, /obj/item/mmi) && !brain) - var/obj/item/mmi/M = P - if(!M.brain_check(user)) - return - - var/mob/living/brain/B = M.brainmob - if(!CONFIG_GET(flag/allow_ai) || (is_banned_from(B.ckey, "AI") && !QDELETED(src) && !QDELETED(user) && !QDELETED(M) && !QDELETED(user) && Adjacent(user))) - if(!QDELETED(M)) - to_chat(user, "This [M.name] does not seem to fit!") - return - if(!user.transferItemToLoc(M,src)) - return - - brain = M - to_chat(user, "You add [M.name] to the frame.") - update_icon() - return - - if(P.tool_behaviour == TOOL_CROWBAR && brain) - P.play_tool_sound(src) - to_chat(user, "You remove the brain.") - brain.forceMove(loc) - brain = null - update_icon() - return - - if(GLASS_CORE) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the glass panel.") - state = CABLED_CORE - update_icon() - new /obj/item/stack/sheet/rglass(loc, 2) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - if(brain) - var/mob/living/brain/B = brain.brainmob - SSticker.mode.remove_antag_for_borging(B.mind) - - var/mob/living/silicon/ai/A = null - - if (brain.overrides_aicore_laws) - A = new /mob/living/silicon/ai(loc, brain.laws, B) - else - A = new /mob/living/silicon/ai(loc, laws, B) - - if(brain.force_replace_ai_name) - A.fully_replace_character_name(A.name, brain.replacement_ai_name()) - SSblackbox.record_feedback("amount", "ais_created", 1) - deadchat_broadcast(" has been brought online at [get_area_name(A, TRUE)].", "[A]", follow_target=A, message_type=DEADCHAT_ANNOUNCEMENT) - qdel(src) - else - state = AI_READY_CORE - update_icon() - return - - if(AI_READY_CORE) - if(istype(P, /obj/item/aicard)) - P.transfer_ai("INACTIVE", "AICARD", src, user) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You disconnect the monitor.") - state = GLASS_CORE - update_icon() - return - return ..() - -/obj/structure/ai_core/update_icon_state() - switch(state) - if(EMPTY_CORE) - icon_state = "0" - if(CIRCUIT_CORE) - icon_state = "1" - if(SCREWED_CORE) - icon_state = "2" - if(CABLED_CORE) - if(brain) - icon_state = "3b" - else - icon_state = "3" - if(GLASS_CORE) - icon_state = "4" - if(AI_READY_CORE) - icon_state = "ai-empty" - -/obj/structure/ai_core/deconstruct(disassembled = TRUE) - if(state == GLASS_CORE) - new /obj/item/stack/sheet/rglass(loc, 2) - if(state >= CABLED_CORE) - new /obj/item/stack/cable_coil(loc, 5) - if(circuit) - circuit.forceMove(loc) - circuit = null - new /obj/item/stack/sheet/plasteel(loc, 4) - qdel(src) - -/obj/structure/ai_core/deactivated - name = "inactive AI" - icon_state = "ai-empty" - anchored = TRUE - state = AI_READY_CORE - -/obj/structure/ai_core/deactivated/Initialize() - . = ..() - circuit = new(src) - - -/* -This is a good place for AI-related object verbs so I'm sticking it here. -If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. -That prevents a few funky behaviors. -*/ -//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. - - -/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(istype(card)) - if(card.flush) - to_chat(user, "ERROR: AI flush is in progress, cannot execute transfer protocol.") - return FALSE - return TRUE - -/obj/structure/ai_core/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(state != AI_READY_CORE || !..()) - return - //Transferring a carded AI to a core. - if(interaction == AI_TRANS_FROM_CARD) - AI.control_disabled = FALSE - AI.radio_enabled = TRUE - AI.forceMove(loc) // to replace the terminal. - to_chat(AI, "You have been uploaded to a stationary terminal. Remote device connection restored.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - AI.battery = circuit.battery - qdel(src) - else //If for some reason you use an empty card on an empty AI terminal. - to_chat(user, "There is no AI loaded on this terminal.") - -/obj/item/circuitboard/aicore - name = "AI core (AI Core Board)" //Well, duh, but best to be consistent - var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core +/obj/structure/ai_core + density = TRUE + anchored = FALSE + name = "\improper AI core" + icon = 'icons/mob/ai.dmi' + icon_state = "0" + desc = "The framework for an artificial intelligence core." + max_integrity = 500 + var/state = EMPTY_CORE + var/datum/ai_laws/laws + var/obj/item/circuitboard/aicore/circuit + var/obj/item/mmi/brain + var/can_deconstruct = TRUE + +/obj/structure/ai_core/Initialize() + . = ..() + laws = new + laws.set_laws_config() + +/obj/structure/ai_core/handle_atom_del(atom/A) + if(A == circuit) + circuit = null + if((state != GLASS_CORE) && (state != AI_READY_CORE)) + state = EMPTY_CORE + update_icon() + if(A == brain) + brain = null + . = ..() + + +/obj/structure/ai_core/Destroy() + if(circuit) + qdel(circuit) + circuit = null + if(brain) + qdel(brain) + brain = null + return ..() + +/obj/structure/ai_core/latejoin_inactive + name = "networked AI core" + desc = "This AI core is connected by bluespace transmitters to NTNet, allowing for an AI personality to be downloaded to it on the fly mid-shift." + can_deconstruct = FALSE + icon_state = "ai-empty" + anchored = TRUE + state = AI_READY_CORE + var/available = TRUE + var/safety_checks = TRUE + var/active = TRUE + +/obj/structure/ai_core/latejoin_inactive/examine(mob/user) + . = ..() + . += "Its transmitter seems to be [active? "on" : "off"]." + . += "You could [active? "deactivate" : "activate"] it with a multitool." + +/obj/structure/ai_core/latejoin_inactive/proc/is_available() //If people still manage to use this feature to spawn-kill AI latejoins ahelp them. + if(!available) + return FALSE + if(!safety_checks) + return TRUE + if(!active) + return FALSE + var/turf/T = get_turf(src) + var/area/A = get_area(src) + if(!A.blob_allowed) + return FALSE + if(!A.power_equip) + return FALSE + if(!SSmapping.level_trait(T.z,ZTRAIT_STATION)) + return FALSE + if(!istype(T, /turf/open/floor)) + return FALSE + return TRUE + +/obj/structure/ai_core/latejoin_inactive/attackby(obj/item/P, mob/user, params) + if(P.tool_behaviour == TOOL_MULTITOOL) + active = !active + to_chat(user, "You [active? "activate" : "deactivate"] \the [src]'s transmitters.") + return + return ..() + +/obj/structure/ai_core/latejoin_inactive/Initialize() + . = ..() + GLOB.latejoin_ai_cores += src + +/obj/structure/ai_core/latejoin_inactive/Destroy() + GLOB.latejoin_ai_cores -= src + return ..() + +/obj/structure/ai_core/attackby(obj/item/P, mob/user, params) + if(P.tool_behaviour == TOOL_WRENCH) + return default_unfasten_wrench(user, P, 20) + if(!anchored) + if(P.tool_behaviour == TOOL_WELDER && can_deconstruct) + if(state != EMPTY_CORE) + to_chat(user, "The core must be empty to deconstruct it!") + return + + if(!P.tool_start_check(user, amount=0)) + return + + to_chat(user, "You start to deconstruct the frame...") + if(P.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) + to_chat(user, "You deconstruct the frame.") + deconstruct(TRUE) + return + else + switch(state) + if(EMPTY_CORE) + if(istype(P, /obj/item/circuitboard/aicore)) + if(!user.transferItemToLoc(P, src)) + return + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You place the circuit board inside the frame.") + update_icon() + state = CIRCUIT_CORE + circuit = P + return + if(CIRCUIT_CORE) + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You screw the circuit board into place.") + state = SCREWED_CORE + update_icon() + return + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the circuit board.") + state = EMPTY_CORE + update_icon() + circuit.forceMove(loc) + circuit = null + return + if(SCREWED_CORE) + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You unfasten the circuit board.") + state = CIRCUIT_CORE + update_icon() + return + if(istype(P, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = P + if(C.get_amount() >= 5) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You start to add cables to the frame...") + if(do_after(user, 20, target = src) && state == SCREWED_CORE && C.use(5)) + to_chat(user, "You add cables to the frame.") + state = CABLED_CORE + update_icon() + else + to_chat(user, "You need five lengths of cable to wire the AI core!") + return + if(CABLED_CORE) + if(P.tool_behaviour == TOOL_WIRECUTTER) + if(brain) + to_chat(user, "Get that [brain.name] out of there first!") + else + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = SCREWED_CORE + update_icon() + new /obj/item/stack/cable_coil(drop_location(), 5) + return + + if(istype(P, /obj/item/stack/sheet/rglass)) + var/obj/item/stack/sheet/rglass/G = P + if(G.get_amount() >= 2) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You start to put in the glass panel...") + if(do_after(user, 20, target = src) && state == CABLED_CORE && G.use(2)) + to_chat(user, "You put in the glass panel.") + state = GLASS_CORE + update_icon() + else + to_chat(user, "You need two sheets of reinforced glass to insert them into the AI core!") + return + + if(istype(P, /obj/item/ai_module)) + if(brain && brain.laws.id != DEFAULT_AI_LAWID) + to_chat(user, "The installed [brain.name] already has set laws!") + return + var/obj/item/ai_module/module = P + module.install(laws, user) + return + + if(istype(P, /obj/item/mmi) && !brain) + var/obj/item/mmi/M = P + if(!M.brain_check(user)) + return + + var/mob/living/brain/B = M.brainmob + if(!CONFIG_GET(flag/allow_ai) || (is_banned_from(B.ckey, "AI") && !QDELETED(src) && !QDELETED(user) && !QDELETED(M) && !QDELETED(user) && Adjacent(user))) + if(!QDELETED(M)) + to_chat(user, "This [M.name] does not seem to fit!") + return + if(!user.transferItemToLoc(M,src)) + return + + brain = M + to_chat(user, "You add [M.name] to the frame.") + update_icon() + return + + if(P.tool_behaviour == TOOL_CROWBAR && brain) + P.play_tool_sound(src) + to_chat(user, "You remove the brain.") + brain.forceMove(loc) + brain = null + update_icon() + return + + if(GLASS_CORE) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the glass panel.") + state = CABLED_CORE + update_icon() + new /obj/item/stack/sheet/rglass(loc, 2) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + if(brain) + var/mob/living/brain/B = brain.brainmob + SSticker.mode.remove_antag_for_borging(B.mind) + + var/mob/living/silicon/ai/A = null + + if (brain.overrides_aicore_laws) + A = new /mob/living/silicon/ai(loc, brain.laws, B) + else + A = new /mob/living/silicon/ai(loc, laws, B) + + if(brain.force_replace_ai_name) + A.fully_replace_character_name(A.name, brain.replacement_ai_name()) + SSblackbox.record_feedback("amount", "ais_created", 1) + deadchat_broadcast(" has been brought online at [get_area_name(A, TRUE)].", "[A]", follow_target=A, message_type=DEADCHAT_ANNOUNCEMENT) + qdel(src) + else + state = AI_READY_CORE + update_icon() + return + + if(AI_READY_CORE) + if(istype(P, /obj/item/aicard)) + P.transfer_ai("INACTIVE", "AICARD", src, user) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You disconnect the monitor.") + state = GLASS_CORE + update_icon() + return + return ..() + +/obj/structure/ai_core/update_icon_state() + switch(state) + if(EMPTY_CORE) + icon_state = "0" + if(CIRCUIT_CORE) + icon_state = "1" + if(SCREWED_CORE) + icon_state = "2" + if(CABLED_CORE) + if(brain) + icon_state = "3b" + else + icon_state = "3" + if(GLASS_CORE) + icon_state = "4" + if(AI_READY_CORE) + icon_state = "ai-empty" + +/obj/structure/ai_core/deconstruct(disassembled = TRUE) + if(state == GLASS_CORE) + new /obj/item/stack/sheet/rglass(loc, 2) + if(state >= CABLED_CORE) + new /obj/item/stack/cable_coil(loc, 5) + if(circuit) + circuit.forceMove(loc) + circuit = null + new /obj/item/stack/sheet/plasteel(loc, 4) + qdel(src) + +/obj/structure/ai_core/deactivated + name = "inactive AI" + icon_state = "ai-empty" + anchored = TRUE + state = AI_READY_CORE + +/obj/structure/ai_core/deactivated/Initialize() + . = ..() + circuit = new(src) + + +/* +This is a good place for AI-related object verbs so I'm sticking it here. +If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. +That prevents a few funky behaviors. +*/ +//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. + + +/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(istype(card)) + if(card.flush) + to_chat(user, "ERROR: AI flush is in progress, cannot execute transfer protocol.") + return FALSE + return TRUE + +/obj/structure/ai_core/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(state != AI_READY_CORE || !..()) + return + //Transferring a carded AI to a core. + if(interaction == AI_TRANS_FROM_CARD) + AI.control_disabled = FALSE + AI.radio_enabled = TRUE + AI.forceMove(loc) // to replace the terminal. + to_chat(AI, "You have been uploaded to a stationary terminal. Remote device connection restored.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + AI.battery = circuit.battery + qdel(src) + else //If for some reason you use an empty card on an empty AI terminal. + to_chat(user, "There is no AI loaded on this terminal.") + +/obj/item/circuitboard/aicore + name = "AI core (AI Core Board)" //Well, duh, but best to be consistent + var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index 79392098e88..886d9bea95a 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -1,417 +1,417 @@ -/* -CONTAINS: -BEDSHEETS -LINEN BINS -*/ - -/obj/item/bedsheet - name = "bedsheet" - desc = "A surprisingly soft linen bedsheet." - icon = 'icons/obj/bedsheets.dmi' - lefthand_file = 'icons/mob/inhands/misc/bedsheet_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/bedsheet_righthand.dmi' - icon_state = "sheetwhite" - inhand_icon_state = "sheetwhite" - slot_flags = ITEM_SLOT_NECK - layer = MOB_LAYER - throwforce = 0 - throw_speed = 1 - throw_range = 2 - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - dying_key = DYE_REGISTRY_BEDSHEET - - dog_fashion = /datum/dog_fashion/head/ghost - var/list/dream_messages = list("white") - -/obj/item/bedsheet/attack(mob/living/M, mob/user) - if(!attempt_initiate_surgery(src, M, user)) - ..() - -/obj/item/bedsheet/attack_self(mob/user) - if(!user.CanReach(src)) //No telekenetic grabbing. - return - if(!user.dropItemToGround(src)) - return - if(layer == initial(layer)) - layer = ABOVE_MOB_LAYER - to_chat(user, "You cover yourself with [src].") - pixel_x = 0 - pixel_y = 0 - else - layer = initial(layer) - to_chat(user, "You smooth [src] out beneath you.") - add_fingerprint(user) - return - -/obj/item/bedsheet/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) - var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) - transfer_fingerprints_to(C) - C.add_fingerprint(user) - qdel(src) - to_chat(user, "You tear [src] up.") - else - return ..() - -/obj/item/bedsheet/blue - icon_state = "sheetblue" - inhand_icon_state = "sheetblue" - dream_messages = list("blue") - -/obj/item/bedsheet/green - icon_state = "sheetgreen" - inhand_icon_state = "sheetgreen" - dream_messages = list("green") - -/obj/item/bedsheet/grey - icon_state = "sheetgrey" - inhand_icon_state = "sheetgrey" - dream_messages = list("grey") - -/obj/item/bedsheet/orange - icon_state = "sheetorange" - inhand_icon_state = "sheetorange" - dream_messages = list("orange") - -/obj/item/bedsheet/purple - icon_state = "sheetpurple" - inhand_icon_state = "sheetpurple" - dream_messages = list("purple") - -/obj/item/bedsheet/patriot - name = "patriotic bedsheet" - desc = "You've never felt more free than when sleeping on this." - icon_state = "sheetUSA" - inhand_icon_state = "sheetUSA" - dream_messages = list("America", "freedom", "fireworks", "bald eagles") - -/obj/item/bedsheet/rainbow - name = "rainbow bedsheet" - desc = "A multicolored blanket. It's actually several different sheets cut up and sewn together." - icon_state = "sheetrainbow" - inhand_icon_state = "sheetrainbow" - dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") - -/obj/item/bedsheet/red - icon_state = "sheetred" - inhand_icon_state = "sheetred" - dream_messages = list("red") - -/obj/item/bedsheet/yellow - icon_state = "sheetyellow" - inhand_icon_state = "sheetyellow" - dream_messages = list("yellow") - -/obj/item/bedsheet/mime - name = "mime's blanket" - desc = "A very soothing striped blanket. All the noise just seems to fade out when you're under the covers in this." - icon_state = "sheetmime" - inhand_icon_state = "sheetmime" - dream_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") - -/obj/item/bedsheet/clown - name = "clown's blanket" - desc = "A rainbow blanket with a clown mask woven in. It smells faintly of bananas." - icon_state = "sheetclown" - inhand_icon_state = "sheetrainbow" - dream_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") - -/obj/item/bedsheet/captain - name = "captain's bedsheet" - desc = "It has a Nanotrasen symbol on it, and was woven with a revolutionary new kind of thread guaranteed to have 0.01% permeability for most non-chemical substances, popular among most modern captains." - icon_state = "sheetcaptain" - inhand_icon_state = "sheetcaptain" - dream_messages = list("authority", "a golden ID", "sunglasses", "a green disc", "an antique gun", "the captain") - -/obj/item/bedsheet/rd - name = "research director's bedsheet" - desc = "It appears to have a beaker emblem, and is made out of fire-resistant material, although it probably won't protect you in the event of fires you're familiar with every day." - icon_state = "sheetrd" - inhand_icon_state = "sheetrd" - dream_messages = list("authority", "a silvery ID", "a bomb", "a mech", "a facehugger", "maniacal laughter", "the research director") - -// for Free Golems. -/obj/item/bedsheet/rd/royal_cape - name = "Royal Cape of the Liberator" - desc = "Majestic." - dream_messages = list("mining", "stone", "a golem", "freedom", "doing whatever") - -/obj/item/bedsheet/medical - name = "medical blanket" - desc = "It's a sterilized* blanket commonly used in the Medbay. *Sterilization is voided if a virologist is present onboard the station." - icon_state = "sheetmedical" - inhand_icon_state = "sheetmedical" - dream_messages = list("healing", "life", "surgery", "a doctor") - -/obj/item/bedsheet/cmo - name = "chief medical officer's bedsheet" - desc = "It's a sterilized blanket that has a cross emblem. There's some cat fur on it, likely from Runtime." - icon_state = "sheetcmo" - inhand_icon_state = "sheetcmo" - dream_messages = list("authority", "a silvery ID", "healing", "life", "surgery", "a cat", "the chief medical officer") - -/obj/item/bedsheet/hos - name = "head of security's bedsheet" - desc = "It is decorated with a shield emblem. While crime doesn't sleep, you do, but you are still THE LAW!" - icon_state = "sheethos" - inhand_icon_state = "sheethos" - dream_messages = list("authority", "a silvery ID", "handcuffs", "a baton", "a flashbang", "sunglasses", "the head of security") - -/obj/item/bedsheet/hop - name = "head of personnel's bedsheet" - desc = "It is decorated with a key emblem. For those rare moments when you can rest and cuddle with Ian without someone screaming for you over the radio." - icon_state = "sheethop" - inhand_icon_state = "sheethop" - dream_messages = list("authority", "a silvery ID", "obligation", "a computer", "an ID", "a corgi", "the head of personnel") - -/obj/item/bedsheet/ce - name = "chief engineer's bedsheet" - desc = "It is decorated with a wrench emblem. It's highly reflective and stain resistant, so you don't need to worry about ruining it with oil." - icon_state = "sheetce" - inhand_icon_state = "sheetce" - dream_messages = list("authority", "a silvery ID", "the engine", "power tools", "an APC", "a parrot", "the chief engineer") - -/obj/item/bedsheet/qm - name = "quartermaster's bedsheet" - desc = "It is decorated with a crate emblem in silver lining. It's rather tough, and just the thing to lie on after a hard day of pushing paper." - icon_state = "sheetqm" - inhand_icon_state = "sheetqm" - dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") - -/obj/item/bedsheet/chaplain - name = "chaplain's blanket" - desc = "A blanket woven with the hearts of gods themselves... Wait, that's just linen." - icon_state = "sheetchap" - inhand_icon_state = "sheetchap" - dream_messages = list("a grey ID", "the gods", "a fulfilled prayer", "a cult", "the chaplain") - -/obj/item/bedsheet/brown - icon_state = "sheetbrown" - inhand_icon_state = "sheetbrown" - dream_messages = list("brown") - -/obj/item/bedsheet/black - icon_state = "sheetblack" - inhand_icon_state = "sheetblack" - dream_messages = list("black") - -/obj/item/bedsheet/centcom - name = "\improper CentCom bedsheet" - desc = "Woven with advanced nanothread for warmth as well as being very decorated, essential for all officials." - icon_state = "sheetcentcom" - inhand_icon_state = "sheetcentcom" - dream_messages = list("a unique ID", "authority", "artillery", "an ending") - -/obj/item/bedsheet/syndie - name = "syndicate bedsheet" - desc = "It has a syndicate emblem and it has an aura of evil." - icon_state = "sheetsyndie" - inhand_icon_state = "sheetsyndie" - dream_messages = list("a green disc", "a red crystal", "a glowing blade", "a wire-covered ID") - -/obj/item/bedsheet/cult - name = "cultist's bedsheet" - desc = "You might dream of Nar'Sie if you sleep with this. It seems rather tattered and glows of an eldritch presence." - icon_state = "sheetcult" - inhand_icon_state = "sheetcult" - dream_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") - -/obj/item/bedsheet/wiz - name = "wizard's bedsheet" - desc = "A special fabric enchanted with magic so you can have an enchanted night. It even glows!" - icon_state = "sheetwiz" - inhand_icon_state = "sheetwiz" - dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") - -/obj/item/bedsheet/nanotrasen - name = "\improper Nanotrasen bedsheet" - desc = "It has the Nanotrasen logo on it and has an aura of duty." - icon_state = "sheetNT" - inhand_icon_state = "sheetNT" - dream_messages = list("authority", "an ending") - -/obj/item/bedsheet/ian - icon_state = "sheetian" - inhand_icon_state = "sheetian" - dream_messages = list("a dog", "a corgi", "woof", "bark", "arf") - -/obj/item/bedsheet/cosmos - name = "cosmic space bedsheet" - desc = "Made from the dreams of those who wonder at the stars." - icon_state = "sheetcosmos" - inhand_icon_state = "sheetcosmos" - dream_messages = list("the infinite cosmos", "Hans Zimmer music", "a flight through space", "the galaxy", "being fabulous", "shooting stars") - light_power = 2 - light_range = 1.4 - -/obj/item/bedsheet/random - icon_state = "random_bedsheet" - name = "random bedsheet" - desc = "If you're reading this description ingame, something has gone wrong! Honk!" - -/obj/item/bedsheet/random/Initialize() - ..() - var/type = pick(typesof(/obj/item/bedsheet) - /obj/item/bedsheet/random) - new type(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/bedsheet/dorms - icon_state = "random_bedsheet" - name = "random dorms bedsheet" - desc = "If you're reading this description ingame, something has gone wrong! Honk!" - -/obj/item/bedsheet/dorms/Initialize() - ..() - var/type = pickweight(list("Colors" = 80, "Special" = 20)) - switch(type) - if("Colors") - type = pick(list(/obj/item/bedsheet, - /obj/item/bedsheet/blue, - /obj/item/bedsheet/green, - /obj/item/bedsheet/grey, - /obj/item/bedsheet/orange, - /obj/item/bedsheet/purple, - /obj/item/bedsheet/red, - /obj/item/bedsheet/yellow, - /obj/item/bedsheet/brown, - /obj/item/bedsheet/black)) - if("Special") - type = pick(list(/obj/item/bedsheet/patriot, - /obj/item/bedsheet/rainbow, - /obj/item/bedsheet/ian, - /obj/item/bedsheet/cosmos, - /obj/item/bedsheet/nanotrasen)) - new type(loc) - return INITIALIZE_HINT_QDEL - -/obj/structure/bedsheetbin - name = "linen bin" - desc = "It looks rather cosy." - icon = 'icons/obj/structures.dmi' - icon_state = "linenbin-full" - anchored = TRUE - resistance_flags = FLAMMABLE - max_integrity = 70 - var/amount = 10 - var/list/sheets = list() - var/obj/item/hidden = null - -/obj/structure/bedsheetbin/empty - amount = 0 - icon_state = "linenbin-empty" - anchored = FALSE - - -/obj/structure/bedsheetbin/examine(mob/user) - . = ..() - if(amount < 1) - . += "There are no bed sheets in the bin." - else if(amount == 1) - . += "There is one bed sheet in the bin." - else - . += "There are [amount] bed sheets in the bin." - - -/obj/structure/bedsheetbin/update_icon_state() - switch(amount) - if(0) - icon_state = "linenbin-empty" - if(1 to 5) - icon_state = "linenbin-half" - else - icon_state = "linenbin-full" - -/obj/structure/bedsheetbin/fire_act(exposed_temperature, exposed_volume) - if(amount) - amount = 0 - update_icon() - ..() - -/obj/structure/bedsheetbin/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/bedsheet)) - if(!user.transferItemToLoc(I, src)) - return - sheets.Add(I) - amount++ - to_chat(user, "You put [I] in [src].") - update_icon() - - else if(default_unfasten_wrench(user, I, 5)) - return - - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(flags_1 & NODECONSTRUCT_1) - return - if(amount) - to_chat(user, "The [src] must be empty first!") - return - if(I.use_tool(src, user, 5, volume=50)) - to_chat(user, "You disassemble the [src].") - new /obj/item/stack/rods(loc, 2) - qdel(src) - - else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. - if(!user.transferItemToLoc(I, src)) - to_chat(user, "\The [I] is stuck to your hand, you cannot hide it among the sheets!") - return - hidden = I - to_chat(user, "You hide [I] among the sheets.") - - -/obj/structure/bedsheetbin/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/bedsheetbin/attack_hand(mob/user) - . = ..() - if(.) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.forceMove(drop_location()) - user.put_in_hands(B) - to_chat(user, "You take [B] out of [src].") - update_icon() - - if(hidden) - hidden.forceMove(drop_location()) - to_chat(user, "[hidden] falls out of [B]!") - hidden = null - - - add_fingerprint(user) -/obj/structure/bedsheetbin/attack_tk(mob/user) - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.forceMove(drop_location()) - to_chat(user, "You telekinetically remove [B] from [src].") - update_icon() - - if(hidden) - hidden.forceMove(drop_location()) - hidden = null - - - add_fingerprint(user) +/* +CONTAINS: +BEDSHEETS +LINEN BINS +*/ + +/obj/item/bedsheet + name = "bedsheet" + desc = "A surprisingly soft linen bedsheet." + icon = 'icons/obj/bedsheets.dmi' + lefthand_file = 'icons/mob/inhands/misc/bedsheet_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/bedsheet_righthand.dmi' + icon_state = "sheetwhite" + inhand_icon_state = "sheetwhite" + slot_flags = ITEM_SLOT_NECK + layer = MOB_LAYER + throwforce = 0 + throw_speed = 1 + throw_range = 2 + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + dying_key = DYE_REGISTRY_BEDSHEET + + dog_fashion = /datum/dog_fashion/head/ghost + var/list/dream_messages = list("white") + +/obj/item/bedsheet/attack(mob/living/M, mob/user) + if(!attempt_initiate_surgery(src, M, user)) + ..() + +/obj/item/bedsheet/attack_self(mob/user) + if(!user.CanReach(src)) //No telekenetic grabbing. + return + if(!user.dropItemToGround(src)) + return + if(layer == initial(layer)) + layer = ABOVE_MOB_LAYER + to_chat(user, "You cover yourself with [src].") + pixel_x = 0 + pixel_y = 0 + else + layer = initial(layer) + to_chat(user, "You smooth [src] out beneath you.") + add_fingerprint(user) + return + +/obj/item/bedsheet/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) + var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) + transfer_fingerprints_to(C) + C.add_fingerprint(user) + qdel(src) + to_chat(user, "You tear [src] up.") + else + return ..() + +/obj/item/bedsheet/blue + icon_state = "sheetblue" + inhand_icon_state = "sheetblue" + dream_messages = list("blue") + +/obj/item/bedsheet/green + icon_state = "sheetgreen" + inhand_icon_state = "sheetgreen" + dream_messages = list("green") + +/obj/item/bedsheet/grey + icon_state = "sheetgrey" + inhand_icon_state = "sheetgrey" + dream_messages = list("grey") + +/obj/item/bedsheet/orange + icon_state = "sheetorange" + inhand_icon_state = "sheetorange" + dream_messages = list("orange") + +/obj/item/bedsheet/purple + icon_state = "sheetpurple" + inhand_icon_state = "sheetpurple" + dream_messages = list("purple") + +/obj/item/bedsheet/patriot + name = "patriotic bedsheet" + desc = "You've never felt more free than when sleeping on this." + icon_state = "sheetUSA" + inhand_icon_state = "sheetUSA" + dream_messages = list("America", "freedom", "fireworks", "bald eagles") + +/obj/item/bedsheet/rainbow + name = "rainbow bedsheet" + desc = "A multicolored blanket. It's actually several different sheets cut up and sewn together." + icon_state = "sheetrainbow" + inhand_icon_state = "sheetrainbow" + dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") + +/obj/item/bedsheet/red + icon_state = "sheetred" + inhand_icon_state = "sheetred" + dream_messages = list("red") + +/obj/item/bedsheet/yellow + icon_state = "sheetyellow" + inhand_icon_state = "sheetyellow" + dream_messages = list("yellow") + +/obj/item/bedsheet/mime + name = "mime's blanket" + desc = "A very soothing striped blanket. All the noise just seems to fade out when you're under the covers in this." + icon_state = "sheetmime" + inhand_icon_state = "sheetmime" + dream_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") + +/obj/item/bedsheet/clown + name = "clown's blanket" + desc = "A rainbow blanket with a clown mask woven in. It smells faintly of bananas." + icon_state = "sheetclown" + inhand_icon_state = "sheetrainbow" + dream_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") + +/obj/item/bedsheet/captain + name = "captain's bedsheet" + desc = "It has a Nanotrasen symbol on it, and was woven with a revolutionary new kind of thread guaranteed to have 0.01% permeability for most non-chemical substances, popular among most modern captains." + icon_state = "sheetcaptain" + inhand_icon_state = "sheetcaptain" + dream_messages = list("authority", "a golden ID", "sunglasses", "a green disc", "an antique gun", "the captain") + +/obj/item/bedsheet/rd + name = "research director's bedsheet" + desc = "It appears to have a beaker emblem, and is made out of fire-resistant material, although it probably won't protect you in the event of fires you're familiar with every day." + icon_state = "sheetrd" + inhand_icon_state = "sheetrd" + dream_messages = list("authority", "a silvery ID", "a bomb", "a mech", "a facehugger", "maniacal laughter", "the research director") + +// for Free Golems. +/obj/item/bedsheet/rd/royal_cape + name = "Royal Cape of the Liberator" + desc = "Majestic." + dream_messages = list("mining", "stone", "a golem", "freedom", "doing whatever") + +/obj/item/bedsheet/medical + name = "medical blanket" + desc = "It's a sterilized* blanket commonly used in the Medbay. *Sterilization is voided if a virologist is present onboard the station." + icon_state = "sheetmedical" + inhand_icon_state = "sheetmedical" + dream_messages = list("healing", "life", "surgery", "a doctor") + +/obj/item/bedsheet/cmo + name = "chief medical officer's bedsheet" + desc = "It's a sterilized blanket that has a cross emblem. There's some cat fur on it, likely from Runtime." + icon_state = "sheetcmo" + inhand_icon_state = "sheetcmo" + dream_messages = list("authority", "a silvery ID", "healing", "life", "surgery", "a cat", "the chief medical officer") + +/obj/item/bedsheet/hos + name = "head of security's bedsheet" + desc = "It is decorated with a shield emblem. While crime doesn't sleep, you do, but you are still THE LAW!" + icon_state = "sheethos" + inhand_icon_state = "sheethos" + dream_messages = list("authority", "a silvery ID", "handcuffs", "a baton", "a flashbang", "sunglasses", "the head of security") + +/obj/item/bedsheet/hop + name = "head of personnel's bedsheet" + desc = "It is decorated with a key emblem. For those rare moments when you can rest and cuddle with Ian without someone screaming for you over the radio." + icon_state = "sheethop" + inhand_icon_state = "sheethop" + dream_messages = list("authority", "a silvery ID", "obligation", "a computer", "an ID", "a corgi", "the head of personnel") + +/obj/item/bedsheet/ce + name = "chief engineer's bedsheet" + desc = "It is decorated with a wrench emblem. It's highly reflective and stain resistant, so you don't need to worry about ruining it with oil." + icon_state = "sheetce" + inhand_icon_state = "sheetce" + dream_messages = list("authority", "a silvery ID", "the engine", "power tools", "an APC", "a parrot", "the chief engineer") + +/obj/item/bedsheet/qm + name = "quartermaster's bedsheet" + desc = "It is decorated with a crate emblem in silver lining. It's rather tough, and just the thing to lie on after a hard day of pushing paper." + icon_state = "sheetqm" + inhand_icon_state = "sheetqm" + dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") + +/obj/item/bedsheet/chaplain + name = "chaplain's blanket" + desc = "A blanket woven with the hearts of gods themselves... Wait, that's just linen." + icon_state = "sheetchap" + inhand_icon_state = "sheetchap" + dream_messages = list("a grey ID", "the gods", "a fulfilled prayer", "a cult", "the chaplain") + +/obj/item/bedsheet/brown + icon_state = "sheetbrown" + inhand_icon_state = "sheetbrown" + dream_messages = list("brown") + +/obj/item/bedsheet/black + icon_state = "sheetblack" + inhand_icon_state = "sheetblack" + dream_messages = list("black") + +/obj/item/bedsheet/centcom + name = "\improper CentCom bedsheet" + desc = "Woven with advanced nanothread for warmth as well as being very decorated, essential for all officials." + icon_state = "sheetcentcom" + inhand_icon_state = "sheetcentcom" + dream_messages = list("a unique ID", "authority", "artillery", "an ending") + +/obj/item/bedsheet/syndie + name = "syndicate bedsheet" + desc = "It has a syndicate emblem and it has an aura of evil." + icon_state = "sheetsyndie" + inhand_icon_state = "sheetsyndie" + dream_messages = list("a green disc", "a red crystal", "a glowing blade", "a wire-covered ID") + +/obj/item/bedsheet/cult + name = "cultist's bedsheet" + desc = "You might dream of Nar'Sie if you sleep with this. It seems rather tattered and glows of an eldritch presence." + icon_state = "sheetcult" + inhand_icon_state = "sheetcult" + dream_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") + +/obj/item/bedsheet/wiz + name = "wizard's bedsheet" + desc = "A special fabric enchanted with magic so you can have an enchanted night. It even glows!" + icon_state = "sheetwiz" + inhand_icon_state = "sheetwiz" + dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") + +/obj/item/bedsheet/nanotrasen + name = "\improper Nanotrasen bedsheet" + desc = "It has the Nanotrasen logo on it and has an aura of duty." + icon_state = "sheetNT" + inhand_icon_state = "sheetNT" + dream_messages = list("authority", "an ending") + +/obj/item/bedsheet/ian + icon_state = "sheetian" + inhand_icon_state = "sheetian" + dream_messages = list("a dog", "a corgi", "woof", "bark", "arf") + +/obj/item/bedsheet/cosmos + name = "cosmic space bedsheet" + desc = "Made from the dreams of those who wonder at the stars." + icon_state = "sheetcosmos" + inhand_icon_state = "sheetcosmos" + dream_messages = list("the infinite cosmos", "Hans Zimmer music", "a flight through space", "the galaxy", "being fabulous", "shooting stars") + light_power = 2 + light_range = 1.4 + +/obj/item/bedsheet/random + icon_state = "random_bedsheet" + name = "random bedsheet" + desc = "If you're reading this description ingame, something has gone wrong! Honk!" + +/obj/item/bedsheet/random/Initialize() + ..() + var/type = pick(typesof(/obj/item/bedsheet) - /obj/item/bedsheet/random) + new type(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/bedsheet/dorms + icon_state = "random_bedsheet" + name = "random dorms bedsheet" + desc = "If you're reading this description ingame, something has gone wrong! Honk!" + +/obj/item/bedsheet/dorms/Initialize() + ..() + var/type = pickweight(list("Colors" = 80, "Special" = 20)) + switch(type) + if("Colors") + type = pick(list(/obj/item/bedsheet, + /obj/item/bedsheet/blue, + /obj/item/bedsheet/green, + /obj/item/bedsheet/grey, + /obj/item/bedsheet/orange, + /obj/item/bedsheet/purple, + /obj/item/bedsheet/red, + /obj/item/bedsheet/yellow, + /obj/item/bedsheet/brown, + /obj/item/bedsheet/black)) + if("Special") + type = pick(list(/obj/item/bedsheet/patriot, + /obj/item/bedsheet/rainbow, + /obj/item/bedsheet/ian, + /obj/item/bedsheet/cosmos, + /obj/item/bedsheet/nanotrasen)) + new type(loc) + return INITIALIZE_HINT_QDEL + +/obj/structure/bedsheetbin + name = "linen bin" + desc = "It looks rather cosy." + icon = 'icons/obj/structures.dmi' + icon_state = "linenbin-full" + anchored = TRUE + resistance_flags = FLAMMABLE + max_integrity = 70 + var/amount = 10 + var/list/sheets = list() + var/obj/item/hidden = null + +/obj/structure/bedsheetbin/empty + amount = 0 + icon_state = "linenbin-empty" + anchored = FALSE + + +/obj/structure/bedsheetbin/examine(mob/user) + . = ..() + if(amount < 1) + . += "There are no bed sheets in the bin." + else if(amount == 1) + . += "There is one bed sheet in the bin." + else + . += "There are [amount] bed sheets in the bin." + + +/obj/structure/bedsheetbin/update_icon_state() + switch(amount) + if(0) + icon_state = "linenbin-empty" + if(1 to 5) + icon_state = "linenbin-half" + else + icon_state = "linenbin-full" + +/obj/structure/bedsheetbin/fire_act(exposed_temperature, exposed_volume) + if(amount) + amount = 0 + update_icon() + ..() + +/obj/structure/bedsheetbin/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/bedsheet)) + if(!user.transferItemToLoc(I, src)) + return + sheets.Add(I) + amount++ + to_chat(user, "You put [I] in [src].") + update_icon() + + else if(default_unfasten_wrench(user, I, 5)) + return + + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(flags_1 & NODECONSTRUCT_1) + return + if(amount) + to_chat(user, "The [src] must be empty first!") + return + if(I.use_tool(src, user, 5, volume=50)) + to_chat(user, "You disassemble the [src].") + new /obj/item/stack/rods(loc, 2) + qdel(src) + + else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. + if(!user.transferItemToLoc(I, src)) + to_chat(user, "\The [I] is stuck to your hand, you cannot hide it among the sheets!") + return + hidden = I + to_chat(user, "You hide [I] among the sheets.") + + +/obj/structure/bedsheetbin/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/bedsheetbin/attack_hand(mob/user) + . = ..() + if(.) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.forceMove(drop_location()) + user.put_in_hands(B) + to_chat(user, "You take [B] out of [src].") + update_icon() + + if(hidden) + hidden.forceMove(drop_location()) + to_chat(user, "[hidden] falls out of [B]!") + hidden = null + + + add_fingerprint(user) +/obj/structure/bedsheetbin/attack_tk(mob/user) + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.forceMove(drop_location()) + to_chat(user, "You telekinetically remove [B] from [src].") + update_icon() + + if(hidden) + hidden.forceMove(drop_location()) + hidden = null + + + add_fingerprint(user) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index a698dc2c0a7..5e5eab92743 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1,538 +1,538 @@ -/obj/structure/closet - name = "closet" - desc = "It's a basic storage unit." - icon = 'icons/obj/closet.dmi' - icon_state = "generic" - density = TRUE - drag_slowdown = 1.5 // Same as a prone mob - max_integrity = 200 - integrity_failure = 0.25 - armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) - - var/icon_door = null - var/icon_door_override = FALSE //override to have open overlay use icon different to its base's - var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights - var/opened = FALSE - var/welded = FALSE - var/locked = FALSE - var/large = TRUE - var/wall_mounted = 0 //never solid (You can always pass over it) - var/breakout_time = 1200 - var/message_cooldown - var/can_weld_shut = TRUE - var/horizontal = FALSE - var/allow_objects = FALSE - var/allow_dense = FALSE - var/dense_when_open = FALSE //if it's dense when open or not - var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container - var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet. - var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. - var/cutting_tool = /obj/item/weldingtool - var/open_sound = 'sound/machines/closet_open.ogg' - var/close_sound = 'sound/machines/closet_close.ogg' - var/open_sound_volume = 35 - var/close_sound_volume = 50 - var/material_drop = /obj/item/stack/sheet/metal - var/material_drop_amount = 2 - var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable. - var/anchorable = TRUE - var/icon_welded = "welded" - - -/obj/structure/closet/Initialize(mapload) - if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents - addtimer(CALLBACK(src, .proc/take_contents), 0) - . = ..() - update_icon() - PopulateContents() - -//USE THIS TO FILL IT, NOT INITIALIZE OR NEW -/obj/structure/closet/proc/PopulateContents() - return - -/obj/structure/closet/Destroy() - dump_contents() - return ..() - -/obj/structure/closet/update_icon() - . = ..() - if (istype(src, /obj/structure/closet/supplypod)) - return - if(!opened) - layer = OBJ_LAYER - else - layer = BELOW_OBJ_LAYER - -/obj/structure/closet/update_overlays() - . = ..() - closet_update_overlays(.) - -/obj/structure/closet/proc/closet_update_overlays(list/new_overlays) - . = new_overlays - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - luminosity = 0 - if(!opened) - if(icon_door) - . += "[icon_door]_door" - else - . += "[icon_state]_door" - if(welded) - . += icon_welded - if(secure && !broken) - //Overlay is similar enough for both that we can use the same mask for both - luminosity = 1 - SSvis_overlays.add_vis_overlay(src, icon, "locked", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - if(locked) - . += "locked" - else - . += "unlocked" - else - if(icon_door_override) - . += "[icon_door]_open" - else - . += "[icon_state]_open" - -/obj/structure/closet/examine(mob/user) - . = ..() - if(welded) - . += "It's welded shut." - if(anchored) - . += "It is bolted to the ground." - if(opened) - . += "The parts are welded together." - else if(secure && !opened) - . += "Alt-click to [locked ? "unlock" : "lock"]." - if(isliving(user)) - var/mob/living/L = user - if(HAS_TRAIT(L, TRAIT_SKITTISH)) - . += "Ctrl-Shift-click [src] to jump inside." - -/obj/structure/closet/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(wall_mounted) - return TRUE - -/obj/structure/closet/proc/can_open(mob/living/user, force = FALSE) - if(force) - return TRUE - if(welded || locked) - return FALSE - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) - if(user) - to_chat(user, "There's something large on top of [src], preventing it from opening." ) - return FALSE - return TRUE - -/obj/structure/closet/proc/can_close(mob/living/user) - var/turf/T = get_turf(src) - for(var/obj/structure/closet/closet in T) - if(closet != src && !closet.wall_mounted) - return FALSE - for(var/mob/living/L in T) - if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) - if(user) - to_chat(user, "There's something too large in [src], preventing it from closing.") - return FALSE - return TRUE - -/obj/structure/closet/dump_contents() - var/atom/L = drop_location() - for(var/atom/movable/AM in src) - AM.forceMove(L) - if(throwing) // you keep some momentum when getting out of a thrown closet - step(AM, dir) - if(throwing) - throwing.finalize(FALSE) - -/obj/structure/closet/proc/take_contents() - var/atom/L = drop_location() - for(var/atom/movable/AM in L) - if(AM != src && insert(AM) == -1) // limit reached - break - -/obj/structure/closet/proc/open(mob/living/user, force = FALSE) - if(!can_open(user, force)) - return - if(opened) - return - welded = FALSE - locked = FALSE - playsound(loc, open_sound, open_sound_volume, TRUE, -3) - opened = TRUE - if(!dense_when_open) - density = FALSE - climb_time *= 0.5 //it's faster to climb onto an open thing - dump_contents() - update_icon() - return TRUE - -/obj/structure/closet/proc/insert(atom/movable/AM) - if(contents.len >= storage_capacity) - return -1 - if(insertion_allowed(AM)) - AM.forceMove(src) - return TRUE - else - return FALSE - -/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) - if(ismob(AM)) - if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... - return FALSE - var/mob/living/L = AM - if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) - return FALSE - if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. - if(horizontal && L.density) - return FALSE - if(L.mob_size > max_mob_size) - return FALSE - var/mobs_stored = 0 - for(var/mob/living/M in contents) - if(++mobs_stored >= mob_storage_capacity) - return FALSE - L.stop_pulling() - - else if(istype(AM, /obj/structure/closet)) - return FALSE - else if(isobj(AM)) - if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) - return FALSE - else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) - return TRUE - else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) - return FALSE - else - return FALSE - - return TRUE - -/obj/structure/closet/proc/close(mob/living/user) - if(!opened || !can_close(user)) - return FALSE - take_contents() - playsound(loc, close_sound, close_sound_volume, TRUE, -3) - climb_time = initial(climb_time) - opened = FALSE - density = TRUE - update_icon() - return TRUE - -/obj/structure/closet/proc/toggle(mob/living/user) - if(opened) - return close(user) - else - return open(user) - -/obj/structure/closet/deconstruct(disassembled = TRUE) - if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) - new material_drop(loc, material_drop_amount) - qdel(src) - -/obj/structure/closet/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - bust_open() - -/obj/structure/closet/attackby(obj/item/W, mob/user, params) - if(user in src) - return - if(src.tool_interact(W,user)) - return 1 // No afterattack - else - return ..() - -/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldn't be continued (because tool was used/closet was of wrong type), FALSE if otherwise - . = TRUE - if(opened) - if(istype(W, cutting_tool)) - if(W.tool_behaviour == TOOL_WELDER) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin cutting \the [src] apart...") - if(W.use_tool(src, user, 40, volume=50)) - if(!opened) - return - user.visible_message("[user] slices apart \the [src].", - "You cut \the [src] apart with \the [W].", - "You hear welding.") - deconstruct(TRUE) - return - else // for example cardboard box is cut with wirecutters - user.visible_message("[user] cut apart \the [src].", \ - "You cut \the [src] apart with \the [W].") - deconstruct(TRUE) - return - if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too - return - else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") - if(W.use_tool(src, user, 40, volume=50)) - if(opened) - return - welded = !welded - after_weld(welded) - user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].", - "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", - "You hear welding.") - update_icon() - else if(W.tool_behaviour == TOOL_WRENCH && anchorable) - if(isinspace() && !anchored) - return - setAnchored(!anchored) - W.play_tool_sound(src, 75) - user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You hear a ratchet.") - else if(user.a_intent != INTENT_HARM) - var/item_is_id = W.GetID() - if(!item_is_id) - return FALSE - if(item_is_id || !toggle(user)) - togglelock(user) - else - return FALSE - -/obj/structure/closet/proc/after_weld(weld_state) - return - -/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) - if(!istype(O) || O.anchored || istype(O, /obj/screen)) - return - if(!istype(user) || user.incapacitated() || !(user.mobility_flags & MOBILITY_STAND)) - return - if(!Adjacent(user) || !user.Adjacent(O)) - return - if(user == O) //try to climb onto it - return ..() - if(!opened) - return - if(!isturf(O.loc)) - return - - var/actuallyismob = 0 - if(isliving(O)) - actuallyismob = 1 - else if(!isitem(O)) - return - var/turf/T = get_turf(src) - var/list/targets = list(O, src) - add_fingerprint(user) - user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ - "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ - "You hear clanging.") - if(actuallyismob) - if(do_after_mob(user, targets, 40)) - user.visible_message("[user] stuffs [O] into [src].", \ - "You stuff [O] into [src].", \ - "You hear a loud metal bang.") - var/mob/living/L = O - if(!issilicon(L)) - L.Paralyze(40) - if(istype(src, /obj/structure/closet/supplypod/extractionpod)) - O.forceMove(src) - else - O.forceMove(T) - close() - else - O.forceMove(T) - return 1 - -/obj/structure/closet/relaymove(mob/user) - if(user.stat || !isturf(loc) || !isliving(user)) - return - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - container_resist(user) - -/obj/structure/closet/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!(user.mobility_flags & MOBILITY_STAND) && get_dist(src, user) > 0) - return - - if(!toggle(user)) - togglelock(user) - -/obj/structure/closet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/closet/attack_robot(mob/user) - if(user.Adjacent(src)) - return attack_hand(user) - -// tk grab then use on self -/obj/structure/closet/attack_self_tk(mob/user) - return attack_hand(user) - -/obj/structure/closet/verb/verb_toggleopen() - set src in view(1) - set category = "Object" - set name = "Toggle Open" - - if(!usr.canUseTopic(src, BE_CLOSE) || !isturf(loc)) - return - - if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) - return toggle(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -// Objects that try to exit a locker by stepping were doing so successfully, -// and due to an oversight in turf/Enter() were going through walls. That -// should be independently resolved, but this is also an interesting twist. -/obj/structure/closet/Exit(atom/movable/AM) - open() - if(AM.loc == src) - return 0 - return 1 - -/obj/structure/closet/container_resist(mob/living/user) - if(opened) - return - if(ismovable(loc)) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - var/atom/movable/AM = loc - AM.relay_container_resist(user, src) - return - if(!welded && !locked) - open() - return - - //okay, so the closet is either welded or locked... resist!!! - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("[src] begins to shake violently!", \ - "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear banging from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) - return - //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting - user.visible_message("[user] successfully broke out of [src]!", - "You successfully break out of [src]!") - bust_open() - else - if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. - to_chat(user, "You fail to break out of [src]!") - -/obj/structure/closet/proc/bust_open() - welded = FALSE //applies to all lockers - locked = FALSE //applies to critter crates and secure lockers only - broken = TRUE //applies to secure lockers only - open() - -/obj/structure/closet/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc)) - return - if(opened || !secure) - return - else - togglelock(user) - -/obj/structure/closet/CtrlShiftClick(mob/living/user) - if(!HAS_TRAIT(user, TRAIT_SKITTISH)) - return ..() - if(!user.canUseTopic(src, BE_CLOSE) || !isturf(user.loc)) - return - dive_into(user) - -/obj/structure/closet/proc/togglelock(mob/living/user, silent) - if(secure && !broken) - if(allowed(user)) - if(iscarbon(user)) - add_fingerprint(user) - locked = !locked - user.visible_message("[user] [locked ? null : "un"]locks [src].", - "You [locked ? null : "un"]lock [src].") - update_icon() - else if(!silent) - to_chat(user, "Access Denied.") - else if(secure && broken) - to_chat(user, "\The [src] is broken!") - -/obj/structure/closet/emag_act(mob/user) - if(secure && !broken) - if(user) - user.visible_message("Sparks fly from [src]!", - "You scramble [src]'s lock, breaking it open!", - "You hear a faint electrical spark.") - playsound(src, "sparks", 50, TRUE) - broken = TRUE - locked = FALSE - update_icon() - -/obj/structure/closet/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/structure/closet/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/obj/O in src) - O.emp_act(severity) - if(secure && !broken && !(. & EMP_PROTECT_SELF)) - if(prob(50 / severity)) - locked = !locked - update_icon() - if(prob(20 / severity) && !opened) - if(!locked) - open() - else - req_access = list() - req_access += pick(get_all_accesses()) - -/obj/structure/closet/contents_explosion(severity, target) - for(var/atom/A in contents) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += A - if(EXPLODE_HEAVY) - SSexplosions.medobj += A - if(EXPLODE_LIGHT) - SSexplosions.lowobj += A - -/obj/structure/closet/singularity_act() - dump_contents() - ..() - -/obj/structure/closet/AllowDrop() - return TRUE - - -/obj/structure/closet/return_temperature() - return - -/obj/structure/closet/proc/dive_into(mob/living/user) - var/turf/T1 = get_turf(user) - var/turf/T2 = get_turf(src) - if(!opened) - if(locked) - togglelock(user, TRUE) - if(!open(user)) - to_chat(user, "It won't budge!") - return - step_towards(user, T2) - T1 = get_turf(user) - if(T1 == T2) - user.resting = TRUE //so people can jump into crates without slamming the lid on their head - if(!close(user)) - to_chat(user, "You can't get [src] to close!") - user.resting = FALSE - return - user.resting = FALSE - togglelock(user) - T1.visible_message("[user] dives into [src]!") +/obj/structure/closet + name = "closet" + desc = "It's a basic storage unit." + icon = 'icons/obj/closet.dmi' + icon_state = "generic" + density = TRUE + drag_slowdown = 1.5 // Same as a prone mob + max_integrity = 200 + integrity_failure = 0.25 + armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + + var/icon_door = null + var/icon_door_override = FALSE //override to have open overlay use icon different to its base's + var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights + var/opened = FALSE + var/welded = FALSE + var/locked = FALSE + var/large = TRUE + var/wall_mounted = 0 //never solid (You can always pass over it) + var/breakout_time = 1200 + var/message_cooldown + var/can_weld_shut = TRUE + var/horizontal = FALSE + var/allow_objects = FALSE + var/allow_dense = FALSE + var/dense_when_open = FALSE //if it's dense when open or not + var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container + var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet. + var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. + var/cutting_tool = /obj/item/weldingtool + var/open_sound = 'sound/machines/closet_open.ogg' + var/close_sound = 'sound/machines/closet_close.ogg' + var/open_sound_volume = 35 + var/close_sound_volume = 50 + var/material_drop = /obj/item/stack/sheet/metal + var/material_drop_amount = 2 + var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable. + var/anchorable = TRUE + var/icon_welded = "welded" + + +/obj/structure/closet/Initialize(mapload) + if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents + addtimer(CALLBACK(src, .proc/take_contents), 0) + . = ..() + update_icon() + PopulateContents() + +//USE THIS TO FILL IT, NOT INITIALIZE OR NEW +/obj/structure/closet/proc/PopulateContents() + return + +/obj/structure/closet/Destroy() + dump_contents() + return ..() + +/obj/structure/closet/update_icon() + . = ..() + if (istype(src, /obj/structure/closet/supplypod)) + return + if(!opened) + layer = OBJ_LAYER + else + layer = BELOW_OBJ_LAYER + +/obj/structure/closet/update_overlays() + . = ..() + closet_update_overlays(.) + +/obj/structure/closet/proc/closet_update_overlays(list/new_overlays) + . = new_overlays + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + luminosity = 0 + if(!opened) + if(icon_door) + . += "[icon_door]_door" + else + . += "[icon_state]_door" + if(welded) + . += icon_welded + if(secure && !broken) + //Overlay is similar enough for both that we can use the same mask for both + luminosity = 1 + SSvis_overlays.add_vis_overlay(src, icon, "locked", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + if(locked) + . += "locked" + else + . += "unlocked" + else + if(icon_door_override) + . += "[icon_door]_open" + else + . += "[icon_state]_open" + +/obj/structure/closet/examine(mob/user) + . = ..() + if(welded) + . += "It's welded shut." + if(anchored) + . += "It is bolted to the ground." + if(opened) + . += "The parts are welded together." + else if(secure && !opened) + . += "Alt-click to [locked ? "unlock" : "lock"]." + if(isliving(user)) + var/mob/living/L = user + if(HAS_TRAIT(L, TRAIT_SKITTISH)) + . += "Ctrl-Shift-click [src] to jump inside." + +/obj/structure/closet/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(wall_mounted) + return TRUE + +/obj/structure/closet/proc/can_open(mob/living/user, force = FALSE) + if(force) + return TRUE + if(welded || locked) + return FALSE + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) + if(user) + to_chat(user, "There's something large on top of [src], preventing it from opening." ) + return FALSE + return TRUE + +/obj/structure/closet/proc/can_close(mob/living/user) + var/turf/T = get_turf(src) + for(var/obj/structure/closet/closet in T) + if(closet != src && !closet.wall_mounted) + return FALSE + for(var/mob/living/L in T) + if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) + if(user) + to_chat(user, "There's something too large in [src], preventing it from closing.") + return FALSE + return TRUE + +/obj/structure/closet/dump_contents() + var/atom/L = drop_location() + for(var/atom/movable/AM in src) + AM.forceMove(L) + if(throwing) // you keep some momentum when getting out of a thrown closet + step(AM, dir) + if(throwing) + throwing.finalize(FALSE) + +/obj/structure/closet/proc/take_contents() + var/atom/L = drop_location() + for(var/atom/movable/AM in L) + if(AM != src && insert(AM) == -1) // limit reached + break + +/obj/structure/closet/proc/open(mob/living/user, force = FALSE) + if(!can_open(user, force)) + return + if(opened) + return + welded = FALSE + locked = FALSE + playsound(loc, open_sound, open_sound_volume, TRUE, -3) + opened = TRUE + if(!dense_when_open) + density = FALSE + climb_time *= 0.5 //it's faster to climb onto an open thing + dump_contents() + update_icon() + return TRUE + +/obj/structure/closet/proc/insert(atom/movable/AM) + if(contents.len >= storage_capacity) + return -1 + if(insertion_allowed(AM)) + AM.forceMove(src) + return TRUE + else + return FALSE + +/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) + if(ismob(AM)) + if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... + return FALSE + var/mob/living/L = AM + if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) + return FALSE + if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. + if(horizontal && L.density) + return FALSE + if(L.mob_size > max_mob_size) + return FALSE + var/mobs_stored = 0 + for(var/mob/living/M in contents) + if(++mobs_stored >= mob_storage_capacity) + return FALSE + L.stop_pulling() + + else if(istype(AM, /obj/structure/closet)) + return FALSE + else if(isobj(AM)) + if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) + return FALSE + else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) + return TRUE + else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) + return FALSE + else + return FALSE + + return TRUE + +/obj/structure/closet/proc/close(mob/living/user) + if(!opened || !can_close(user)) + return FALSE + take_contents() + playsound(loc, close_sound, close_sound_volume, TRUE, -3) + climb_time = initial(climb_time) + opened = FALSE + density = TRUE + update_icon() + return TRUE + +/obj/structure/closet/proc/toggle(mob/living/user) + if(opened) + return close(user) + else + return open(user) + +/obj/structure/closet/deconstruct(disassembled = TRUE) + if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) + new material_drop(loc, material_drop_amount) + qdel(src) + +/obj/structure/closet/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + bust_open() + +/obj/structure/closet/attackby(obj/item/W, mob/user, params) + if(user in src) + return + if(src.tool_interact(W,user)) + return 1 // No afterattack + else + return ..() + +/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldn't be continued (because tool was used/closet was of wrong type), FALSE if otherwise + . = TRUE + if(opened) + if(istype(W, cutting_tool)) + if(W.tool_behaviour == TOOL_WELDER) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin cutting \the [src] apart...") + if(W.use_tool(src, user, 40, volume=50)) + if(!opened) + return + user.visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [W].", + "You hear welding.") + deconstruct(TRUE) + return + else // for example cardboard box is cut with wirecutters + user.visible_message("[user] cut apart \the [src].", \ + "You cut \the [src] apart with \the [W].") + deconstruct(TRUE) + return + if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too + return + else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") + if(W.use_tool(src, user, 40, volume=50)) + if(opened) + return + welded = !welded + after_weld(welded) + user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].", + "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", + "You hear welding.") + update_icon() + else if(W.tool_behaviour == TOOL_WRENCH && anchorable) + if(isinspace() && !anchored) + return + setAnchored(!anchored) + W.play_tool_sound(src, 75) + user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You hear a ratchet.") + else if(user.a_intent != INTENT_HARM) + var/item_is_id = W.GetID() + if(!item_is_id) + return FALSE + if(item_is_id || !toggle(user)) + togglelock(user) + else + return FALSE + +/obj/structure/closet/proc/after_weld(weld_state) + return + +/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) + if(!istype(O) || O.anchored || istype(O, /obj/screen)) + return + if(!istype(user) || user.incapacitated() || !(user.mobility_flags & MOBILITY_STAND)) + return + if(!Adjacent(user) || !user.Adjacent(O)) + return + if(user == O) //try to climb onto it + return ..() + if(!opened) + return + if(!isturf(O.loc)) + return + + var/actuallyismob = 0 + if(isliving(O)) + actuallyismob = 1 + else if(!isitem(O)) + return + var/turf/T = get_turf(src) + var/list/targets = list(O, src) + add_fingerprint(user) + user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ + "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ + "You hear clanging.") + if(actuallyismob) + if(do_after_mob(user, targets, 40)) + user.visible_message("[user] stuffs [O] into [src].", \ + "You stuff [O] into [src].", \ + "You hear a loud metal bang.") + var/mob/living/L = O + if(!issilicon(L)) + L.Paralyze(40) + if(istype(src, /obj/structure/closet/supplypod/extractionpod)) + O.forceMove(src) + else + O.forceMove(T) + close() + else + O.forceMove(T) + return 1 + +/obj/structure/closet/relaymove(mob/user) + if(user.stat || !isturf(loc) || !isliving(user)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + container_resist(user) + +/obj/structure/closet/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!(user.mobility_flags & MOBILITY_STAND) && get_dist(src, user) > 0) + return + + if(!toggle(user)) + togglelock(user) + +/obj/structure/closet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/closet/attack_robot(mob/user) + if(user.Adjacent(src)) + return attack_hand(user) + +// tk grab then use on self +/obj/structure/closet/attack_self_tk(mob/user) + return attack_hand(user) + +/obj/structure/closet/verb/verb_toggleopen() + set src in view(1) + set category = "Object" + set name = "Toggle Open" + + if(!usr.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + + if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) + return toggle(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +// Objects that try to exit a locker by stepping were doing so successfully, +// and due to an oversight in turf/Enter() were going through walls. That +// should be independently resolved, but this is also an interesting twist. +/obj/structure/closet/Exit(atom/movable/AM) + open() + if(AM.loc == src) + return 0 + return 1 + +/obj/structure/closet/container_resist(mob/living/user) + if(opened) + return + if(ismovable(loc)) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + var/atom/movable/AM = loc + AM.relay_container_resist(user, src) + return + if(!welded && !locked) + open() + return + + //okay, so the closet is either welded or locked... resist!!! + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("[src] begins to shake violently!", \ + "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear banging from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) + return + //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting + user.visible_message("[user] successfully broke out of [src]!", + "You successfully break out of [src]!") + bust_open() + else + if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. + to_chat(user, "You fail to break out of [src]!") + +/obj/structure/closet/proc/bust_open() + welded = FALSE //applies to all lockers + locked = FALSE //applies to critter crates and secure lockers only + broken = TRUE //applies to secure lockers only + open() + +/obj/structure/closet/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + if(opened || !secure) + return + else + togglelock(user) + +/obj/structure/closet/CtrlShiftClick(mob/living/user) + if(!HAS_TRAIT(user, TRAIT_SKITTISH)) + return ..() + if(!user.canUseTopic(src, BE_CLOSE) || !isturf(user.loc)) + return + dive_into(user) + +/obj/structure/closet/proc/togglelock(mob/living/user, silent) + if(secure && !broken) + if(allowed(user)) + if(iscarbon(user)) + add_fingerprint(user) + locked = !locked + user.visible_message("[user] [locked ? null : "un"]locks [src].", + "You [locked ? null : "un"]lock [src].") + update_icon() + else if(!silent) + to_chat(user, "Access Denied.") + else if(secure && broken) + to_chat(user, "\The [src] is broken!") + +/obj/structure/closet/emag_act(mob/user) + if(secure && !broken) + if(user) + user.visible_message("Sparks fly from [src]!", + "You scramble [src]'s lock, breaking it open!", + "You hear a faint electrical spark.") + playsound(src, "sparks", 50, TRUE) + broken = TRUE + locked = FALSE + update_icon() + +/obj/structure/closet/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/structure/closet/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/obj/O in src) + O.emp_act(severity) + if(secure && !broken && !(. & EMP_PROTECT_SELF)) + if(prob(50 / severity)) + locked = !locked + update_icon() + if(prob(20 / severity) && !opened) + if(!locked) + open() + else + req_access = list() + req_access += pick(get_all_accesses()) + +/obj/structure/closet/contents_explosion(severity, target) + for(var/atom/A in contents) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += A + if(EXPLODE_HEAVY) + SSexplosions.medobj += A + if(EXPLODE_LIGHT) + SSexplosions.lowobj += A + +/obj/structure/closet/singularity_act() + dump_contents() + ..() + +/obj/structure/closet/AllowDrop() + return TRUE + + +/obj/structure/closet/return_temperature() + return + +/obj/structure/closet/proc/dive_into(mob/living/user) + var/turf/T1 = get_turf(user) + var/turf/T2 = get_turf(src) + if(!opened) + if(locked) + togglelock(user, TRUE) + if(!open(user)) + to_chat(user, "It won't budge!") + return + step_towards(user, T2) + T1 = get_turf(user) + if(T1 == T2) + user.resting = TRUE //so people can jump into crates without slamming the lid on their head + if(!close(user)) + to_chat(user, "You can't get [src] to close!") + user.resting = FALSE + return + user.resting = FALSE + togglelock(user) + T1.visible_message("[user] dives into [src]!") diff --git a/code/game/objects/structures/crates_lockers/closets/fitness.dm b/code/game/objects/structures/crates_lockers/closets/fitness.dm index 7f22a9a4998..b0dd6db262a 100644 --- a/code/game/objects/structures/crates_lockers/closets/fitness.dm +++ b/code/game/objects/structures/crates_lockers/closets/fitness.dm @@ -1,65 +1,65 @@ -/obj/structure/closet/athletic_mixed - name = "athletic wardrobe" - desc = "It's a storage unit for athletic wear." - icon_door = "mixed" - -/obj/structure/closet/athletic_mixed/PopulateContents() - ..() - new /obj/item/clothing/under/shorts/purple(src) - new /obj/item/clothing/under/shorts/grey(src) - new /obj/item/clothing/under/shorts/black(src) - new /obj/item/clothing/under/shorts/red(src) - new /obj/item/clothing/under/shorts/blue(src) - new /obj/item/clothing/under/shorts/green(src) - new /obj/item/clothing/under/costume/jabroni(src) - - -/obj/structure/closet/boxinggloves - name = "boxing gloves" - desc = "It's a storage unit for gloves for use in the boxing ring." - -/obj/structure/closet/boxinggloves/PopulateContents() - ..() - new /obj/item/clothing/gloves/boxing/blue(src) - new /obj/item/clothing/gloves/boxing/green(src) - new /obj/item/clothing/gloves/boxing/yellow(src) - new /obj/item/clothing/gloves/boxing(src) - - -/obj/structure/closet/masks - name = "mask closet" - desc = "IT'S A STORAGE UNIT FOR FIGHTER MASKS OLE!" - -/obj/structure/closet/masks/PopulateContents() - ..() - new /obj/item/clothing/mask/luchador(src) - new /obj/item/clothing/mask/luchador/rudos(src) - new /obj/item/clothing/mask/luchador/tecnicos(src) - - -/obj/structure/closet/lasertag/red - name = "red laser tag equipment" - desc = "It's a storage unit for laser tag equipment." - icon_door = "red" - -/obj/structure/closet/lasertag/red/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser/redtag(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/redtag(src) - new /obj/item/clothing/head/helmet/redtaghelm(src) - - -/obj/structure/closet/lasertag/blue - name = "blue laser tag equipment" - desc = "It's a storage unit for laser tag equipment." - icon_door = "blue" - -/obj/structure/closet/lasertag/blue/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser/bluetag(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/bluetag(src) - new /obj/item/clothing/head/helmet/bluetaghelm(src) +/obj/structure/closet/athletic_mixed + name = "athletic wardrobe" + desc = "It's a storage unit for athletic wear." + icon_door = "mixed" + +/obj/structure/closet/athletic_mixed/PopulateContents() + ..() + new /obj/item/clothing/under/shorts/purple(src) + new /obj/item/clothing/under/shorts/grey(src) + new /obj/item/clothing/under/shorts/black(src) + new /obj/item/clothing/under/shorts/red(src) + new /obj/item/clothing/under/shorts/blue(src) + new /obj/item/clothing/under/shorts/green(src) + new /obj/item/clothing/under/costume/jabroni(src) + + +/obj/structure/closet/boxinggloves + name = "boxing gloves" + desc = "It's a storage unit for gloves for use in the boxing ring." + +/obj/structure/closet/boxinggloves/PopulateContents() + ..() + new /obj/item/clothing/gloves/boxing/blue(src) + new /obj/item/clothing/gloves/boxing/green(src) + new /obj/item/clothing/gloves/boxing/yellow(src) + new /obj/item/clothing/gloves/boxing(src) + + +/obj/structure/closet/masks + name = "mask closet" + desc = "IT'S A STORAGE UNIT FOR FIGHTER MASKS OLE!" + +/obj/structure/closet/masks/PopulateContents() + ..() + new /obj/item/clothing/mask/luchador(src) + new /obj/item/clothing/mask/luchador/rudos(src) + new /obj/item/clothing/mask/luchador/tecnicos(src) + + +/obj/structure/closet/lasertag/red + name = "red laser tag equipment" + desc = "It's a storage unit for laser tag equipment." + icon_door = "red" + +/obj/structure/closet/lasertag/red/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser/redtag(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/redtag(src) + new /obj/item/clothing/head/helmet/redtaghelm(src) + + +/obj/structure/closet/lasertag/blue + name = "blue laser tag equipment" + desc = "It's a storage unit for laser tag equipment." + icon_door = "blue" + +/obj/structure/closet/lasertag/blue/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser/bluetag(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/bluetag(src) + new /obj/item/clothing/head/helmet/bluetaghelm(src) diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index 2320c39acd2..6c389bc160c 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -1,111 +1,111 @@ -/obj/structure/closet/cabinet - name = "cabinet" - desc = "Old will forever be in fashion." - icon_state = "cabinet" - resistance_flags = FLAMMABLE - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - max_integrity = 70 - -/obj/structure/closet/acloset - name = "strange closet" - desc = "It looks alien!" - icon_state = "alien" - - -/obj/structure/closet/gimmick - name = "administrative supply closet" - desc = "It's a storage unit for things that have no right being here." - icon_state = "syndicate" - -/obj/structure/closet/gimmick/russian - name = "\improper Russian surplus closet" - desc = "It's a storage unit for Russian standard-issue surplus." - -/obj/structure/closet/gimmick/russian/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/clothing/head/ushanka(src) - for(var/i in 1 to 5) - new /obj/item/clothing/under/costume/soviet(src) - -/obj/structure/closet/gimmick/tacticool - name = "tacticool gear closet" - desc = "It's a storage unit for Tacticool gear." - -/obj/structure/closet/gimmick/tacticool/PopulateContents() - ..() - new /obj/item/clothing/glasses/eyepatch(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/gloves/tackler/combat(src) - new /obj/item/clothing/gloves/tackler/combat(src) - new /obj/item/clothing/head/helmet/swat(src) - new /obj/item/clothing/head/helmet/swat(src) - new /obj/item/clothing/mask/gas/sechailer/swat(src) - new /obj/item/clothing/mask/gas/sechailer/swat(src) - new /obj/item/clothing/shoes/combat/swat(src) - new /obj/item/clothing/shoes/combat/swat(src) - new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) - new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) - new /obj/item/clothing/under/syndicate/tacticool(src) - new /obj/item/clothing/under/syndicate/tacticool(src) - - -/obj/structure/closet/thunderdome - name = "\improper Thunderdome closet" - desc = "Everything you need!" - anchored = TRUE - -/obj/structure/closet/thunderdome/tdred - name = "red-team Thunderdome closet" - icon_door = "red" - -/obj/structure/closet/thunderdome/tdred/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/suit/armor/tdome/red(src) - for(var/i in 1 to 3) - new /obj/item/melee/transforming/energy/sword/saber(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) - for(var/i in 1 to 3) - new /obj/item/melee/baton/loaded(src) - for(var/i in 1 to 3) - new /obj/item/storage/box/flashbangs(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/helmet/thunderdome(src) - -/obj/structure/closet/thunderdome/tdgreen - name = "green-team Thunderdome closet" - icon_door = "green" - -/obj/structure/closet/thunderdome/tdgreen/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/suit/armor/tdome/green(src) - for(var/i in 1 to 3) - new /obj/item/melee/transforming/energy/sword/saber(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) - for(var/i in 1 to 3) - new /obj/item/melee/baton/loaded(src) - for(var/i in 1 to 3) - new /obj/item/storage/box/flashbangs(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/helmet/thunderdome(src) - -/obj/structure/closet/malf/suits - desc = "It's a storage unit for operational gear." - icon_state = "syndicate" - -/obj/structure/closet/malf/suits/PopulateContents() - ..() - new /obj/item/tank/jetpack/void(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/clothing/head/helmet/space/nasavoid(src) - new /obj/item/clothing/suit/space/nasavoid(src) - new /obj/item/crowbar(src) - new /obj/item/stock_parts/cell(src) - new /obj/item/multitool(src) +/obj/structure/closet/cabinet + name = "cabinet" + desc = "Old will forever be in fashion." + icon_state = "cabinet" + resistance_flags = FLAMMABLE + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + max_integrity = 70 + +/obj/structure/closet/acloset + name = "strange closet" + desc = "It looks alien!" + icon_state = "alien" + + +/obj/structure/closet/gimmick + name = "administrative supply closet" + desc = "It's a storage unit for things that have no right being here." + icon_state = "syndicate" + +/obj/structure/closet/gimmick/russian + name = "\improper Russian surplus closet" + desc = "It's a storage unit for Russian standard-issue surplus." + +/obj/structure/closet/gimmick/russian/PopulateContents() + ..() + for(var/i in 1 to 5) + new /obj/item/clothing/head/ushanka(src) + for(var/i in 1 to 5) + new /obj/item/clothing/under/costume/soviet(src) + +/obj/structure/closet/gimmick/tacticool + name = "tacticool gear closet" + desc = "It's a storage unit for Tacticool gear." + +/obj/structure/closet/gimmick/tacticool/PopulateContents() + ..() + new /obj/item/clothing/glasses/eyepatch(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/gloves/tackler/combat(src) + new /obj/item/clothing/gloves/tackler/combat(src) + new /obj/item/clothing/head/helmet/swat(src) + new /obj/item/clothing/head/helmet/swat(src) + new /obj/item/clothing/mask/gas/sechailer/swat(src) + new /obj/item/clothing/mask/gas/sechailer/swat(src) + new /obj/item/clothing/shoes/combat/swat(src) + new /obj/item/clothing/shoes/combat/swat(src) + new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) + new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) + new /obj/item/clothing/under/syndicate/tacticool(src) + new /obj/item/clothing/under/syndicate/tacticool(src) + + +/obj/structure/closet/thunderdome + name = "\improper Thunderdome closet" + desc = "Everything you need!" + anchored = TRUE + +/obj/structure/closet/thunderdome/tdred + name = "red-team Thunderdome closet" + icon_door = "red" + +/obj/structure/closet/thunderdome/tdred/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/suit/armor/tdome/red(src) + for(var/i in 1 to 3) + new /obj/item/melee/transforming/energy/sword/saber(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + for(var/i in 1 to 3) + new /obj/item/melee/baton/loaded(src) + for(var/i in 1 to 3) + new /obj/item/storage/box/flashbangs(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/helmet/thunderdome(src) + +/obj/structure/closet/thunderdome/tdgreen + name = "green-team Thunderdome closet" + icon_door = "green" + +/obj/structure/closet/thunderdome/tdgreen/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/suit/armor/tdome/green(src) + for(var/i in 1 to 3) + new /obj/item/melee/transforming/energy/sword/saber(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + for(var/i in 1 to 3) + new /obj/item/melee/baton/loaded(src) + for(var/i in 1 to 3) + new /obj/item/storage/box/flashbangs(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/helmet/thunderdome(src) + +/obj/structure/closet/malf/suits + desc = "It's a storage unit for operational gear." + icon_state = "syndicate" + +/obj/structure/closet/malf/suits/PopulateContents() + ..() + new /obj/item/tank/jetpack/void(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/clothing/head/helmet/space/nasavoid(src) + new /obj/item/clothing/suit/space/nasavoid(src) + new /obj/item/crowbar(src) + new /obj/item/stock_parts/cell(src) + new /obj/item/multitool(src) diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm index f7558a46c69..bbc172ba843 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -1,313 +1,313 @@ -// Closets for specific jobs - -/obj/structure/closet/gmcloset - name = "formal closet" - desc = "It's a storage unit for formal clothing." - icon_door = "black" - -/obj/structure/closet/gmcloset/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/head/that = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/under/suit/sl = 2, - /obj/item/clothing/under/rank/civilian/bartender = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/head/soft/black = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/reagent_containers/glass/rag = 2, - /obj/item/storage/box/beanbag = 1, - /obj/item/clothing/suit/armor/vest/alt = 1, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/glasses/sunglasses/reagent = 1, - /obj/item/clothing/neck/petcollar = 1, - /obj/item/storage/belt/bandolier = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/chefcloset - name = "\proper chef's closet" - desc = "It's a storage unit for foodservice garments and mouse traps." - icon_door = "black" - -/obj/structure/closet/chefcloset/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/under/suit/waiter = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/suit/apron/chef = 3, - /obj/item/clothing/head/soft/mime = 2, - /obj/item/storage/box/mousetraps = 2, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/suit/toggle/chef = 1, - /obj/item/clothing/under/rank/civilian/chef = 1, - /obj/item/clothing/head/chefhat = 1, - /obj/item/reagent_containers/glass/rag = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/jcloset - name = "custodial closet" - desc = "It's a storage unit for janitorial clothes and gear." - icon_door = "mixed" - -/obj/structure/closet/jcloset/PopulateContents() - ..() - new /obj/item/clothing/under/rank/civilian/janitor(src) - new /obj/item/cartridge/janitor(src) - new /obj/item/clothing/gloves/color/black(src) - new /obj/item/clothing/head/soft/purple(src) - new /obj/item/paint/paint_remover(src) - new /obj/item/melee/flyswatter(src) - new /obj/item/flashlight(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/caution(src) - new /obj/item/holosign_creator(src) - new /obj/item/lightreplacer(src) - new /obj/item/soap(src) - new /obj/item/storage/bag/trash(src) - new /obj/item/clothing/shoes/galoshes(src) - new /obj/item/watertank/janitor(src) - new /obj/item/storage/belt/janitor(src) - - -/obj/structure/closet/lawcloset - name = "legal closet" - desc = "It's a storage unit for courtroom apparel and items." - icon_door = "blue" - -/obj/structure/closet/lawcloset/PopulateContents() - ..() - new /obj/item/clothing/under/suit/blacktwopiece(src) - new /obj/item/clothing/under/rank/civilian/lawyer/female(src) - new /obj/item/clothing/under/rank/civilian/lawyer/black(src) - new /obj/item/clothing/under/rank/civilian/lawyer/red(src) - new /obj/item/clothing/under/rank/civilian/lawyer/bluesuit(src) - new /obj/item/clothing/suit/toggle/lawyer(src) - new /obj/item/clothing/under/rank/civilian/lawyer/purpsuit(src) - new /obj/item/clothing/suit/toggle/lawyer/purple(src) - new /obj/item/clothing/under/suit/black(src) - new /obj/item/clothing/suit/toggle/lawyer/black(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/clothing/accessory/lawyers_badge(src) - new /obj/item/clothing/accessory/lawyers_badge(src) - -/obj/structure/closet/wardrobe/chaplain_black - name = "chapel wardrobe" - desc = "It's a storage unit for Nanotrasen-approved religious attire." - icon_door = "black" - -/obj/structure/closet/wardrobe/chaplain_black/PopulateContents() - new /obj/item/choice_beacon/holy(src) - new /obj/item/clothing/accessory/pocketprotector/cosmetology(src) - new /obj/item/clothing/under/rank/civilian/chaplain(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/suit/chaplainsuit/nun(src) - new /obj/item/clothing/head/nun_hood(src) - new /obj/item/clothing/suit/hooded/chaplainsuit/monkhabit(src) - new /obj/item/clothing/suit/chaplainsuit/holidaypriest(src) - new /obj/item/storage/backpack/cultpack(src) - new /obj/item/storage/fancy/candle_box(src) - new /obj/item/storage/fancy/candle_box(src) - return - -/obj/structure/closet/wardrobe/red - name = "security wardrobe" - icon_door = "red" - -/obj/structure/closet/wardrobe/red/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/suit/hooded/wintercoat/security = 1, - /obj/item/storage/backpack/security = 1, - /obj/item/storage/backpack/satchel/sec = 1, - /obj/item/storage/backpack/duffelbag/sec = 2, - /obj/item/clothing/under/rank/security/officer = 3, - /obj/item/clothing/under/rank/security/officer/skirt = 2, - /obj/item/clothing/shoes/jackboots = 3, - /obj/item/clothing/head/beret/sec = 3, - /obj/item/clothing/head/soft/sec = 3, - /obj/item/clothing/mask/bandana/red = 2) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/cargotech - name = "cargo wardrobe" - icon_door = "orange" - -/obj/structure/closet/wardrobe/cargotech/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/suit/hooded/wintercoat/cargo = 1, - /obj/item/clothing/under/rank/cargo/tech = 3, - /obj/item/clothing/shoes/sneakers/black = 3, - /obj/item/clothing/gloves/fingerless = 3, - /obj/item/clothing/head/soft = 3, - /obj/item/radio/headset/headset_cargo = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/wardrobe/atmospherics_yellow - name = "atmospherics wardrobe" - icon_door = "atmos_wardrobe" - -/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/engineering = 1, - /obj/item/storage/backpack/satchel/eng = 1, - /obj/item/storage/backpack/industrial = 1, - /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, - /obj/item/clothing/under/rank/engineering/atmospheric_technician = 3, - /obj/item/clothing/shoes/sneakers/black = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/engineering_yellow - name = "engineering wardrobe" - icon_door = "yellow" - -/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/engineering = 1, - /obj/item/storage/backpack/industrial = 1, - /obj/item/storage/backpack/satchel/eng = 1, - /obj/item/clothing/suit/hooded/wintercoat/engineering = 1, - /obj/item/clothing/under/rank/engineering/engineer = 3, - /obj/item/clothing/suit/hazardvest = 3, - /obj/item/clothing/shoes/workboots = 3, - /obj/item/clothing/head/hardhat = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/white/medical - name = "medical doctor's wardrobe" - -/obj/structure/closet/wardrobe/white/medical/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/med = 1, - /obj/item/storage/backpack/medic = 1, - /obj/item/storage/backpack/satchel/med = 1, - /obj/item/clothing/suit/hooded/wintercoat/medical = 1, - /obj/item/clothing/under/rank/medical/doctor/nurse = 1, - /obj/item/clothing/head/nursehat = 1, - /obj/item/clothing/under/rank/medical/doctor/blue = 1, - /obj/item/clothing/under/rank/medical/doctor/green = 1, - /obj/item/clothing/under/rank/medical/doctor/purple = 1, - /obj/item/clothing/under/rank/medical/doctor = 3, - /obj/item/clothing/suit/toggle/labcoat = 3, - /obj/item/clothing/suit/toggle/labcoat/paramedic = 3, - /obj/item/clothing/shoes/sneakers/white = 3, - /obj/item/clothing/head/soft/paramedic = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/robotics_black - name = "robotics wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/robotics_black/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/glasses/hud/diagnostic = 2, - /obj/item/clothing/under/rank/rnd/roboticist = 2, - /obj/item/clothing/suit/toggle/labcoat = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/clothing/gloves/fingerless = 2, - /obj/item/clothing/head/soft/black = 2) - generate_items_inside(items_inside,src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - return - - -/obj/structure/closet/wardrobe/chemistry_white - name = "chemistry wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/chemistry_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/medical/chemist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/chemist = 2, - /obj/item/storage/backpack/chemistry = 2, - /obj/item/storage/backpack/satchel/chem = 2, - /obj/item/storage/bag/chemistry = 2) - generate_items_inside(items_inside,src) - return - - -/obj/structure/closet/wardrobe/genetics_white - name = "genetics wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/genetics_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/rnd/geneticist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/genetics = 2, - /obj/item/storage/backpack/genetics = 2, - /obj/item/storage/backpack/satchel/gen = 2) - generate_items_inside(items_inside,src) - return - - -/obj/structure/closet/wardrobe/virology_white - name = "virology wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/virology_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/medical/virologist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/virologist = 2, - /obj/item/clothing/mask/surgical = 2, - /obj/item/storage/backpack/virology = 2, - /obj/item/storage/backpack/satchel/vir = 2) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/science_white - name = "science wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/science_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/science = 2, - /obj/item/storage/backpack/satchel/tox = 2, - /obj/item/clothing/suit/hooded/wintercoat/science = 1, - /obj/item/clothing/under/rank/rnd/scientist = 3, - /obj/item/clothing/suit/toggle/labcoat/science = 3, - /obj/item/clothing/shoes/sneakers/white = 3, - /obj/item/radio/headset/headset_sci = 2, - /obj/item/clothing/mask/gas = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/botanist - name = "botanist wardrobe" - icon_door = "green" - -/obj/structure/closet/wardrobe/botanist/PopulateContents() - var/static/items_inside = list( - /obj/item/storage/backpack/botany = 2, - /obj/item/storage/backpack/satchel/hyd = 2, - /obj/item/clothing/suit/hooded/wintercoat/hydro = 1, - /obj/item/clothing/suit/apron = 2, - /obj/item/clothing/suit/apron/overalls = 2, - /obj/item/clothing/under/rank/civilian/hydroponics = 3, - /obj/item/clothing/mask/bandana = 3) - generate_items_inside(items_inside,src) - -/obj/structure/closet/wardrobe/curator - name = "treasure hunting wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/curator/PopulateContents() - new /obj/item/clothing/head/fedora/curator(src) - new /obj/item/clothing/suit/curator(src) - new /obj/item/clothing/under/rank/civilian/curator/treasure_hunter(src) - new /obj/item/clothing/shoes/workboots/mining(src) - new /obj/item/storage/backpack/satchel/explorer(src) - +// Closets for specific jobs + +/obj/structure/closet/gmcloset + name = "formal closet" + desc = "It's a storage unit for formal clothing." + icon_door = "black" + +/obj/structure/closet/gmcloset/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/head/that = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/under/suit/sl = 2, + /obj/item/clothing/under/rank/civilian/bartender = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/head/soft/black = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/reagent_containers/glass/rag = 2, + /obj/item/storage/box/beanbag = 1, + /obj/item/clothing/suit/armor/vest/alt = 1, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/glasses/sunglasses/reagent = 1, + /obj/item/clothing/neck/petcollar = 1, + /obj/item/storage/belt/bandolier = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/chefcloset + name = "\proper chef's closet" + desc = "It's a storage unit for foodservice garments and mouse traps." + icon_door = "black" + +/obj/structure/closet/chefcloset/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/under/suit/waiter = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/suit/apron/chef = 3, + /obj/item/clothing/head/soft/mime = 2, + /obj/item/storage/box/mousetraps = 2, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/suit/toggle/chef = 1, + /obj/item/clothing/under/rank/civilian/chef = 1, + /obj/item/clothing/head/chefhat = 1, + /obj/item/reagent_containers/glass/rag = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/jcloset + name = "custodial closet" + desc = "It's a storage unit for janitorial clothes and gear." + icon_door = "mixed" + +/obj/structure/closet/jcloset/PopulateContents() + ..() + new /obj/item/clothing/under/rank/civilian/janitor(src) + new /obj/item/cartridge/janitor(src) + new /obj/item/clothing/gloves/color/black(src) + new /obj/item/clothing/head/soft/purple(src) + new /obj/item/paint/paint_remover(src) + new /obj/item/melee/flyswatter(src) + new /obj/item/flashlight(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/caution(src) + new /obj/item/holosign_creator(src) + new /obj/item/lightreplacer(src) + new /obj/item/soap(src) + new /obj/item/storage/bag/trash(src) + new /obj/item/clothing/shoes/galoshes(src) + new /obj/item/watertank/janitor(src) + new /obj/item/storage/belt/janitor(src) + + +/obj/structure/closet/lawcloset + name = "legal closet" + desc = "It's a storage unit for courtroom apparel and items." + icon_door = "blue" + +/obj/structure/closet/lawcloset/PopulateContents() + ..() + new /obj/item/clothing/under/suit/blacktwopiece(src) + new /obj/item/clothing/under/rank/civilian/lawyer/female(src) + new /obj/item/clothing/under/rank/civilian/lawyer/black(src) + new /obj/item/clothing/under/rank/civilian/lawyer/red(src) + new /obj/item/clothing/under/rank/civilian/lawyer/bluesuit(src) + new /obj/item/clothing/suit/toggle/lawyer(src) + new /obj/item/clothing/under/rank/civilian/lawyer/purpsuit(src) + new /obj/item/clothing/suit/toggle/lawyer/purple(src) + new /obj/item/clothing/under/suit/black(src) + new /obj/item/clothing/suit/toggle/lawyer/black(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/clothing/accessory/lawyers_badge(src) + new /obj/item/clothing/accessory/lawyers_badge(src) + +/obj/structure/closet/wardrobe/chaplain_black + name = "chapel wardrobe" + desc = "It's a storage unit for Nanotrasen-approved religious attire." + icon_door = "black" + +/obj/structure/closet/wardrobe/chaplain_black/PopulateContents() + new /obj/item/choice_beacon/holy(src) + new /obj/item/clothing/accessory/pocketprotector/cosmetology(src) + new /obj/item/clothing/under/rank/civilian/chaplain(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/suit/chaplainsuit/nun(src) + new /obj/item/clothing/head/nun_hood(src) + new /obj/item/clothing/suit/hooded/chaplainsuit/monkhabit(src) + new /obj/item/clothing/suit/chaplainsuit/holidaypriest(src) + new /obj/item/storage/backpack/cultpack(src) + new /obj/item/storage/fancy/candle_box(src) + new /obj/item/storage/fancy/candle_box(src) + return + +/obj/structure/closet/wardrobe/red + name = "security wardrobe" + icon_door = "red" + +/obj/structure/closet/wardrobe/red/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/suit/hooded/wintercoat/security = 1, + /obj/item/storage/backpack/security = 1, + /obj/item/storage/backpack/satchel/sec = 1, + /obj/item/storage/backpack/duffelbag/sec = 2, + /obj/item/clothing/under/rank/security/officer = 3, + /obj/item/clothing/under/rank/security/officer/skirt = 2, + /obj/item/clothing/shoes/jackboots = 3, + /obj/item/clothing/head/beret/sec = 3, + /obj/item/clothing/head/soft/sec = 3, + /obj/item/clothing/mask/bandana/red = 2) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/cargotech + name = "cargo wardrobe" + icon_door = "orange" + +/obj/structure/closet/wardrobe/cargotech/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/suit/hooded/wintercoat/cargo = 1, + /obj/item/clothing/under/rank/cargo/tech = 3, + /obj/item/clothing/shoes/sneakers/black = 3, + /obj/item/clothing/gloves/fingerless = 3, + /obj/item/clothing/head/soft = 3, + /obj/item/radio/headset/headset_cargo = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/wardrobe/atmospherics_yellow + name = "atmospherics wardrobe" + icon_door = "atmos_wardrobe" + +/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/engineering = 1, + /obj/item/storage/backpack/satchel/eng = 1, + /obj/item/storage/backpack/industrial = 1, + /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, + /obj/item/clothing/under/rank/engineering/atmospheric_technician = 3, + /obj/item/clothing/shoes/sneakers/black = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/engineering_yellow + name = "engineering wardrobe" + icon_door = "yellow" + +/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/engineering = 1, + /obj/item/storage/backpack/industrial = 1, + /obj/item/storage/backpack/satchel/eng = 1, + /obj/item/clothing/suit/hooded/wintercoat/engineering = 1, + /obj/item/clothing/under/rank/engineering/engineer = 3, + /obj/item/clothing/suit/hazardvest = 3, + /obj/item/clothing/shoes/workboots = 3, + /obj/item/clothing/head/hardhat = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/white/medical + name = "medical doctor's wardrobe" + +/obj/structure/closet/wardrobe/white/medical/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/med = 1, + /obj/item/storage/backpack/medic = 1, + /obj/item/storage/backpack/satchel/med = 1, + /obj/item/clothing/suit/hooded/wintercoat/medical = 1, + /obj/item/clothing/under/rank/medical/doctor/nurse = 1, + /obj/item/clothing/head/nursehat = 1, + /obj/item/clothing/under/rank/medical/doctor/blue = 1, + /obj/item/clothing/under/rank/medical/doctor/green = 1, + /obj/item/clothing/under/rank/medical/doctor/purple = 1, + /obj/item/clothing/under/rank/medical/doctor = 3, + /obj/item/clothing/suit/toggle/labcoat = 3, + /obj/item/clothing/suit/toggle/labcoat/paramedic = 3, + /obj/item/clothing/shoes/sneakers/white = 3, + /obj/item/clothing/head/soft/paramedic = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/robotics_black + name = "robotics wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/robotics_black/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/glasses/hud/diagnostic = 2, + /obj/item/clothing/under/rank/rnd/roboticist = 2, + /obj/item/clothing/suit/toggle/labcoat = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/clothing/gloves/fingerless = 2, + /obj/item/clothing/head/soft/black = 2) + generate_items_inside(items_inside,src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + return + + +/obj/structure/closet/wardrobe/chemistry_white + name = "chemistry wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/chemistry_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/medical/chemist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/chemist = 2, + /obj/item/storage/backpack/chemistry = 2, + /obj/item/storage/backpack/satchel/chem = 2, + /obj/item/storage/bag/chemistry = 2) + generate_items_inside(items_inside,src) + return + + +/obj/structure/closet/wardrobe/genetics_white + name = "genetics wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/genetics_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/rnd/geneticist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/genetics = 2, + /obj/item/storage/backpack/genetics = 2, + /obj/item/storage/backpack/satchel/gen = 2) + generate_items_inside(items_inside,src) + return + + +/obj/structure/closet/wardrobe/virology_white + name = "virology wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/virology_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/medical/virologist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/virologist = 2, + /obj/item/clothing/mask/surgical = 2, + /obj/item/storage/backpack/virology = 2, + /obj/item/storage/backpack/satchel/vir = 2) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/science_white + name = "science wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/science_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/science = 2, + /obj/item/storage/backpack/satchel/tox = 2, + /obj/item/clothing/suit/hooded/wintercoat/science = 1, + /obj/item/clothing/under/rank/rnd/scientist = 3, + /obj/item/clothing/suit/toggle/labcoat/science = 3, + /obj/item/clothing/shoes/sneakers/white = 3, + /obj/item/radio/headset/headset_sci = 2, + /obj/item/clothing/mask/gas = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/botanist + name = "botanist wardrobe" + icon_door = "green" + +/obj/structure/closet/wardrobe/botanist/PopulateContents() + var/static/items_inside = list( + /obj/item/storage/backpack/botany = 2, + /obj/item/storage/backpack/satchel/hyd = 2, + /obj/item/clothing/suit/hooded/wintercoat/hydro = 1, + /obj/item/clothing/suit/apron = 2, + /obj/item/clothing/suit/apron/overalls = 2, + /obj/item/clothing/under/rank/civilian/hydroponics = 3, + /obj/item/clothing/mask/bandana = 3) + generate_items_inside(items_inside,src) + +/obj/structure/closet/wardrobe/curator + name = "treasure hunting wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/curator/PopulateContents() + new /obj/item/clothing/head/fedora/curator(src) + new /obj/item/clothing/suit/curator(src) + new /obj/item/clothing/under/rank/civilian/curator/treasure_hunter(src) + new /obj/item/clothing/shoes/workboots/mining(src) + new /obj/item/storage/backpack/satchel/explorer(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/l3closet.dm b/code/game/objects/structures/crates_lockers/closets/l3closet.dm index f53398e6785..22d996c5ef7 100644 --- a/code/game/objects/structures/crates_lockers/closets/l3closet.dm +++ b/code/game/objects/structures/crates_lockers/closets/l3closet.dm @@ -1,54 +1,54 @@ -/obj/structure/closet/l3closet - name = "level 3 biohazard gear closet" - desc = "It's a storage unit for level 3 biohazard gear." - icon_state = "bio" - -/obj/structure/closet/l3closet/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/general(src) - new /obj/item/clothing/head/bio_hood/general(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/virology - icon_state = "bio_viro" - -/obj/structure/closet/l3closet/virology/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/virology(src) - new /obj/item/clothing/head/bio_hood/virology(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/security - icon_state = "bio_sec" - -/obj/structure/closet/l3closet/security/PopulateContents() - new /obj/item/clothing/suit/bio_suit/security(src) - new /obj/item/clothing/head/bio_hood/security(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/janitor - icon_state = "bio_jan" - -/obj/structure/closet/l3closet/janitor/PopulateContents() - new /obj/item/clothing/suit/bio_suit/janitor(src) - new /obj/item/clothing/head/bio_hood/janitor(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/scientist - icon_state = "bio_viro" - -/obj/structure/closet/l3closet/scientist/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/scientist(src) - new /obj/item/clothing/head/bio_hood/scientist(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - +/obj/structure/closet/l3closet + name = "level 3 biohazard gear closet" + desc = "It's a storage unit for level 3 biohazard gear." + icon_state = "bio" + +/obj/structure/closet/l3closet/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/general(src) + new /obj/item/clothing/head/bio_hood/general(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/virology + icon_state = "bio_viro" + +/obj/structure/closet/l3closet/virology/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/virology(src) + new /obj/item/clothing/head/bio_hood/virology(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/security + icon_state = "bio_sec" + +/obj/structure/closet/l3closet/security/PopulateContents() + new /obj/item/clothing/suit/bio_suit/security(src) + new /obj/item/clothing/head/bio_hood/security(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/janitor + icon_state = "bio_jan" + +/obj/structure/closet/l3closet/janitor/PopulateContents() + new /obj/item/clothing/suit/bio_suit/janitor(src) + new /obj/item/clothing/head/bio_hood/janitor(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/scientist + icon_state = "bio_viro" + +/obj/structure/closet/l3closet/scientist/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/scientist(src) + new /obj/item/clothing/head/bio_hood/scientist(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm index 465d488973d..4bbee2f969a 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm @@ -1,17 +1,17 @@ -/obj/structure/closet/secure_closet/bar - name = "booze storage" - req_access = list(ACCESS_BAR) - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/secure_closet/bar/PopulateContents() - ..() - for(var/i in 1 to 10) - new /obj/item/reagent_containers/food/drinks/beer( src ) - new /obj/item/etherealballdeployer(src) - new /obj/item/roulette_wheel_beacon(src) +/obj/structure/closet/secure_closet/bar + name = "booze storage" + req_access = list(ACCESS_BAR) + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/secure_closet/bar/PopulateContents() + ..() + for(var/i in 1 to 10) + new /obj/item/reagent_containers/food/drinks/beer( src ) + new /obj/item/etherealballdeployer(src) + new /obj/item/roulette_wheel_beacon(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm index 31a953f6cca..1e487d7a8d9 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm @@ -1,25 +1,25 @@ -/obj/structure/closet/secure_closet/quartermaster - name = "\proper quartermaster's locker" - req_access = list(ACCESS_QM) - icon_state = "qm" - -/obj/structure/closet/secure_closet/quartermaster/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/qm(src) - new /obj/item/storage/lockbox/medal/cargo(src) - new /obj/item/clothing/under/rank/cargo/qm(src) - new /obj/item/clothing/under/rank/cargo/qm/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/radio/headset/headset_cargo(src) - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/gloves/fingerless(src) - new /obj/item/megaphone/cargo(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/head/soft(src) - new /obj/item/export_scanner(src) - new /obj/item/door_remote/quartermaster(src) - new /obj/item/circuitboard/machine/techfab/department/cargo(src) - new /obj/item/storage/photo_album/qm(src) - new /obj/item/circuitboard/machine/ore_silo(src) - new /obj/item/card/id/departmental_budget/car(src) +/obj/structure/closet/secure_closet/quartermaster + name = "\proper quartermaster's locker" + req_access = list(ACCESS_QM) + icon_state = "qm" + +/obj/structure/closet/secure_closet/quartermaster/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/qm(src) + new /obj/item/storage/lockbox/medal/cargo(src) + new /obj/item/clothing/under/rank/cargo/qm(src) + new /obj/item/clothing/under/rank/cargo/qm/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/radio/headset/headset_cargo(src) + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/gloves/fingerless(src) + new /obj/item/megaphone/cargo(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/head/soft(src) + new /obj/item/export_scanner(src) + new /obj/item/door_remote/quartermaster(src) + new /obj/item/circuitboard/machine/techfab/department/cargo(src) + new /obj/item/storage/photo_album/qm(src) + new /obj/item/circuitboard/machine/ore_silo(src) + new /obj/item/card/id/departmental_budget/car(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm index 43795f1b1dc..59cafe6c5e8 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -1,92 +1,92 @@ -/obj/structure/closet/secure_closet/engineering_chief - name = "\proper chief engineer's locker" - req_access = list(ACCESS_CE) - icon_state = "ce" - -/obj/structure/closet/secure_closet/engineering_chief/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/ce(src) - new /obj/item/clothing/under/rank/engineering/chief_engineer(src) - new /obj/item/clothing/under/rank/engineering/chief_engineer/skirt(src) - new /obj/item/clothing/head/hardhat/white(src) - new /obj/item/clothing/head/hardhat/weldhat/white(src) - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/tank/jetpack/suit(src) - new /obj/item/cartridge/ce(src) - new /obj/item/radio/headset/heads/ce(src) - new /obj/item/megaphone/command(src) - new /obj/item/areaeditor/blueprints(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/clothing/glasses/meson/engine(src) - new /obj/item/door_remote/chief_engineer(src) - new /obj/item/pipe_dispenser(src) - new /obj/item/circuitboard/machine/techfab/department/engineering(src) - new /obj/item/extinguisher/advanced(src) - new /obj/item/storage/photo_album/ce(src) - -/obj/structure/closet/secure_closet/engineering_electrical - name = "electrical supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng" - icon_door = "eng_elec" - -/obj/structure/closet/secure_closet/engineering_electrical/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/gloves/color/yellow = 2, - /obj/item/inducer = 2, - /obj/item/storage/toolbox/electrical = 3, - /obj/item/electronics/apc = 3, - /obj/item/multitool = 3) - generate_items_inside(items_inside,src) - -/obj/structure/closet/secure_closet/engineering_welding - name = "welding supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng" - icon_door = "eng_weld" - -/obj/structure/closet/secure_closet/engineering_welding/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/head/welding(src) - for(var/i in 1 to 3) - new /obj/item/weldingtool(src) - -/obj/structure/closet/secure_closet/engineering_personal - name = "engineer's locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng_secure" - -/obj/structure/closet/secure_closet/engineering_personal/PopulateContents() - ..() - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/glasses/meson/engine(src) - new /obj/item/storage/box/emptysandbags(src) - new /obj/item/storage/bag/construction(src) - - -/obj/structure/closet/secure_closet/atmospherics - name = "\proper atmospheric technician's locker" - req_access = list(ACCESS_ATMOSPHERICS) - icon_state = "atmos" - -/obj/structure/closet/secure_closet/atmospherics/PopulateContents() - ..() - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/pipe_dispenser(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - new /obj/item/holosign_creator/atmos(src) - new /obj/item/watertank/atmos(src) - new /obj/item/clothing/suit/fire/atmos(src) - new /obj/item/clothing/mask/gas/atmos(src) - new /obj/item/clothing/head/hardhat/atmos(src) - new /obj/item/clothing/glasses/meson/engine/tray(src) - new /obj/item/extinguisher/advanced(src) +/obj/structure/closet/secure_closet/engineering_chief + name = "\proper chief engineer's locker" + req_access = list(ACCESS_CE) + icon_state = "ce" + +/obj/structure/closet/secure_closet/engineering_chief/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/ce(src) + new /obj/item/clothing/under/rank/engineering/chief_engineer(src) + new /obj/item/clothing/under/rank/engineering/chief_engineer/skirt(src) + new /obj/item/clothing/head/hardhat/white(src) + new /obj/item/clothing/head/hardhat/weldhat/white(src) + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/tank/jetpack/suit(src) + new /obj/item/cartridge/ce(src) + new /obj/item/radio/headset/heads/ce(src) + new /obj/item/megaphone/command(src) + new /obj/item/areaeditor/blueprints(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/clothing/glasses/meson/engine(src) + new /obj/item/door_remote/chief_engineer(src) + new /obj/item/pipe_dispenser(src) + new /obj/item/circuitboard/machine/techfab/department/engineering(src) + new /obj/item/extinguisher/advanced(src) + new /obj/item/storage/photo_album/ce(src) + +/obj/structure/closet/secure_closet/engineering_electrical + name = "electrical supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng" + icon_door = "eng_elec" + +/obj/structure/closet/secure_closet/engineering_electrical/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/gloves/color/yellow = 2, + /obj/item/inducer = 2, + /obj/item/storage/toolbox/electrical = 3, + /obj/item/electronics/apc = 3, + /obj/item/multitool = 3) + generate_items_inside(items_inside,src) + +/obj/structure/closet/secure_closet/engineering_welding + name = "welding supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng" + icon_door = "eng_weld" + +/obj/structure/closet/secure_closet/engineering_welding/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/head/welding(src) + for(var/i in 1 to 3) + new /obj/item/weldingtool(src) + +/obj/structure/closet/secure_closet/engineering_personal + name = "engineer's locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng_secure" + +/obj/structure/closet/secure_closet/engineering_personal/PopulateContents() + ..() + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/glasses/meson/engine(src) + new /obj/item/storage/box/emptysandbags(src) + new /obj/item/storage/bag/construction(src) + + +/obj/structure/closet/secure_closet/atmospherics + name = "\proper atmospheric technician's locker" + req_access = list(ACCESS_ATMOSPHERICS) + icon_state = "atmos" + +/obj/structure/closet/secure_closet/atmospherics/PopulateContents() + ..() + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/pipe_dispenser(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + new /obj/item/holosign_creator/atmos(src) + new /obj/item/watertank/atmos(src) + new /obj/item/clothing/suit/fire/atmos(src) + new /obj/item/clothing/mask/gas/atmos(src) + new /obj/item/clothing/head/hardhat/atmos(src) + new /obj/item/clothing/glasses/meson/engine/tray(src) + new /obj/item/extinguisher/advanced(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm index 6507e5efea1..1cb8b878c6b 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -1,116 +1,116 @@ -/obj/structure/closet/secure_closet/freezer - icon_state = "freezer" - var/jones = FALSE - -/obj/structure/closet/secure_closet/freezer/Destroy() - recursive_organ_check(src) - ..() - -/obj/structure/closet/secure_closet/freezer/Initialize() - . = ..() - recursive_organ_check(src) - -/obj/structure/closet/secure_closet/freezer/open(mob/living/user, force = TRUE) - if(opened || !can_open(user, force)) //dupe check just so we don't let the organs decay when someone fails to open the locker - return FALSE - recursive_organ_check(src) - return ..() - -/obj/structure/closet/secure_closet/freezer/close(mob/living/user) - if(..()) //if we actually closed the locker - recursive_organ_check(src) - -/obj/structure/closet/secure_closet/freezer/ex_act() - if(!jones) - jones = TRUE - else - ..() - -/obj/structure/closet/secure_closet/freezer/kitchen - name = "kitchen cabinet" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/kitchen/PopulateContents() - ..() - for(var/i = 0, i < 3, i++) - new /obj/item/reagent_containers/food/condiment/flour(src) - new /obj/item/reagent_containers/food/condiment/rice(src) - new /obj/item/reagent_containers/food/condiment/sugar(src) - -/obj/structure/closet/secure_closet/freezer/kitchen/maintenance - name = "maintenance refrigerator" - desc = "This refrigerator looks quite dusty, is there anything edible still inside?" - req_access = list() - -/obj/structure/closet/secure_closet/freezer/kitchen/maintenance/PopulateContents() - ..() - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/milk(src) - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/soymilk(src) - for(var/i = 0, i < 2, i++) - new /obj/item/storage/fancy/egg_box(src) - -/obj/structure/closet/secure_closet/freezer/kitchen/mining - req_access = list() - -/obj/structure/closet/secure_closet/freezer/meat - name = "meat fridge" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/meat/PopulateContents() - ..() - for(var/i = 0, i < 4, i++) - new /obj/item/reagent_containers/food/snacks/meat/slab/monkey(src) - -/obj/structure/closet/secure_closet/freezer/meat/open - req_access = null - locked = FALSE - -/obj/structure/closet/secure_closet/freezer/gulag_fridge - name = "refrigerator" - -/obj/structure/closet/secure_closet/freezer/gulag_fridge/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/drinks/beer/light(src) - -/obj/structure/closet/secure_closet/freezer/fridge - name = "refrigerator" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/fridge/PopulateContents() - ..() - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/milk(src) - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/soymilk(src) - for(var/i = 0, i < 2, i++) - new /obj/item/storage/fancy/egg_box(src) - -/obj/structure/closet/secure_closet/freezer/fridge/open - req_access = null - locked = FALSE - -/obj/structure/closet/secure_closet/freezer/money - name = "freezer" - desc = "This contains cold hard cash." - req_access = list(ACCESS_VAULT) - -/obj/structure/closet/secure_closet/freezer/money/PopulateContents() - ..() - for(var/i = 0, i < 3, i++) - new /obj/item/stack/spacecash/c1000(src) - for(var/i = 0, i < 5, i++) - new /obj/item/stack/spacecash/c500(src) - for(var/i = 0, i < 6, i++) - new /obj/item/stack/spacecash/c200(src) - -/obj/structure/closet/secure_closet/freezer/cream_pie - name = "cream pie closet" - desc = "Contains pies filled with cream and/or custard, you sickos." - req_access = list(ACCESS_THEATRE) - -/obj/structure/closet/secure_closet/freezer/cream_pie/PopulateContents() - ..() - new /obj/item/reagent_containers/food/snacks/pie/cream(src) +/obj/structure/closet/secure_closet/freezer + icon_state = "freezer" + var/jones = FALSE + +/obj/structure/closet/secure_closet/freezer/Destroy() + recursive_organ_check(src) + ..() + +/obj/structure/closet/secure_closet/freezer/Initialize() + . = ..() + recursive_organ_check(src) + +/obj/structure/closet/secure_closet/freezer/open(mob/living/user, force = TRUE) + if(opened || !can_open(user, force)) //dupe check just so we don't let the organs decay when someone fails to open the locker + return FALSE + recursive_organ_check(src) + return ..() + +/obj/structure/closet/secure_closet/freezer/close(mob/living/user) + if(..()) //if we actually closed the locker + recursive_organ_check(src) + +/obj/structure/closet/secure_closet/freezer/ex_act() + if(!jones) + jones = TRUE + else + ..() + +/obj/structure/closet/secure_closet/freezer/kitchen + name = "kitchen cabinet" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/kitchen/PopulateContents() + ..() + for(var/i = 0, i < 3, i++) + new /obj/item/reagent_containers/food/condiment/flour(src) + new /obj/item/reagent_containers/food/condiment/rice(src) + new /obj/item/reagent_containers/food/condiment/sugar(src) + +/obj/structure/closet/secure_closet/freezer/kitchen/maintenance + name = "maintenance refrigerator" + desc = "This refrigerator looks quite dusty, is there anything edible still inside?" + req_access = list() + +/obj/structure/closet/secure_closet/freezer/kitchen/maintenance/PopulateContents() + ..() + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/milk(src) + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/soymilk(src) + for(var/i = 0, i < 2, i++) + new /obj/item/storage/fancy/egg_box(src) + +/obj/structure/closet/secure_closet/freezer/kitchen/mining + req_access = list() + +/obj/structure/closet/secure_closet/freezer/meat + name = "meat fridge" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/meat/PopulateContents() + ..() + for(var/i = 0, i < 4, i++) + new /obj/item/reagent_containers/food/snacks/meat/slab/monkey(src) + +/obj/structure/closet/secure_closet/freezer/meat/open + req_access = null + locked = FALSE + +/obj/structure/closet/secure_closet/freezer/gulag_fridge + name = "refrigerator" + +/obj/structure/closet/secure_closet/freezer/gulag_fridge/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/drinks/beer/light(src) + +/obj/structure/closet/secure_closet/freezer/fridge + name = "refrigerator" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/fridge/PopulateContents() + ..() + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/milk(src) + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/soymilk(src) + for(var/i = 0, i < 2, i++) + new /obj/item/storage/fancy/egg_box(src) + +/obj/structure/closet/secure_closet/freezer/fridge/open + req_access = null + locked = FALSE + +/obj/structure/closet/secure_closet/freezer/money + name = "freezer" + desc = "This contains cold hard cash." + req_access = list(ACCESS_VAULT) + +/obj/structure/closet/secure_closet/freezer/money/PopulateContents() + ..() + for(var/i = 0, i < 3, i++) + new /obj/item/stack/spacecash/c1000(src) + for(var/i = 0, i < 5, i++) + new /obj/item/stack/spacecash/c500(src) + for(var/i = 0, i < 6, i++) + new /obj/item/stack/spacecash/c200(src) + +/obj/structure/closet/secure_closet/freezer/cream_pie + name = "cream pie closet" + desc = "Contains pies filled with cream and/or custard, you sickos." + req_access = list(ACCESS_THEATRE) + +/obj/structure/closet/secure_closet/freezer/cream_pie/PopulateContents() + ..() + new /obj/item/reagent_containers/food/snacks/pie/cream(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm index d60a622c970..f27a04209c5 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm @@ -1,13 +1,13 @@ -/obj/structure/closet/secure_closet/hydroponics - name = "botanist's locker" - req_access = list(ACCESS_HYDROPONICS) - icon_state = "hydro" - -/obj/structure/closet/secure_closet/hydroponics/PopulateContents() - ..() - new /obj/item/storage/bag/plants/portaseeder(src) - new /obj/item/plant_analyzer(src) - new /obj/item/radio/headset/headset_srv(src) - new /obj/item/cultivator(src) - new /obj/item/hatchet(src) - new /obj/item/secateurs(src) +/obj/structure/closet/secure_closet/hydroponics + name = "botanist's locker" + req_access = list(ACCESS_HYDROPONICS) + icon_state = "hydro" + +/obj/structure/closet/secure_closet/hydroponics/PopulateContents() + ..() + new /obj/item/storage/bag/plants/portaseeder(src) + new /obj/item/plant_analyzer(src) + new /obj/item/radio/headset/headset_srv(src) + new /obj/item/cultivator(src) + new /obj/item/hatchet(src) + new /obj/item/secateurs(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index a5971588a7f..90a335e819a 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -1,131 +1,131 @@ -/obj/structure/closet/secure_closet/medical1 - name = "medicine closet" - desc = "Filled to the brim with medical junk." - icon_state = "med" - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/medical1/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/reagent_containers/glass/beaker = 2, - /obj/item/reagent_containers/dropper = 2, - /obj/item/storage/belt/medical = 1, - /obj/item/storage/box/syringes = 1, - /obj/item/reagent_containers/glass/bottle/toxin = 1, - /obj/item/reagent_containers/glass/bottle/morphine = 2, - /obj/item/reagent_containers/glass/bottle/epinephrine= 3, - /obj/item/reagent_containers/glass/bottle/multiver = 3, - /obj/item/storage/box/rxglasses = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/secure_closet/medical2 - name = "anesthetic closet" - desc = "Used to knock people out." - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/medical2/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/tank/internals/anesthetic(src) - for(var/i in 1 to 3) - new /obj/item/clothing/mask/breath/medical(src) - -/obj/structure/closet/secure_closet/medical3 - name = "medical doctor's locker" - req_access = list(ACCESS_SURGERY) - icon_state = "med_secure" - -/obj/structure/closet/secure_closet/medical3/PopulateContents() - ..() - new /obj/item/radio/headset/headset_med(src) - new /obj/item/defibrillator/loaded(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/clothing/glasses/hud/health(src) - return - -/obj/structure/closet/secure_closet/psychology - name = "psychology locker" - req_access = list(ACCESS_PSYCHOLOGY) - icon_state = "cabinet" - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/secure_closet/psychology/PopulateContents() - ..() - new /obj/item/clothing/under/suit/black(src) - new /obj/item/clothing/under/suit/black/skirt(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/storage/backpack/medic(src) - new /obj/item/radio/headset/headset_srvmed(src) - new /obj/item/clipboard(src) - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/clothing/ears/earmuffs(src) - new /obj/item/clothing/mask/muzzle(src) - new /obj/item/clothing/glasses/blindfold(src) - -/obj/structure/closet/secure_closet/chief_medical - name = "\proper chief medical officer's locker" - req_access = list(ACCESS_CMO) - icon_state = "cmo" - -/obj/structure/closet/secure_closet/chief_medical/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/cmo(src) - new /obj/item/clothing/suit/bio_suit/cmo(src) - new /obj/item/clothing/head/bio_hood/cmo(src) - new /obj/item/clothing/suit/toggle/labcoat/cmo(src) - new /obj/item/clothing/under/rank/medical/chief_medical_officer(src) - new /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown (src) - new /obj/item/cartridge/cmo(src) - new /obj/item/radio/headset/heads/cmo(src) - new /obj/item/megaphone/command(src) - new /obj/item/defibrillator/compact/loaded(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/healthanalyzer/advanced(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/reagent_containers/hypospray/cmo(src) - new /obj/item/autosurgeon/cmo(src) - new /obj/item/door_remote/chief_medical_officer(src) - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/pet_carrier(src) - new /obj/item/wallframe/defib_mount(src) - new /obj/item/circuitboard/machine/techfab/department/medical(src) - new /obj/item/storage/photo_album/cmo(src) - -/obj/structure/closet/secure_closet/animal - name = "animal control" - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/animal/PopulateContents() - ..() - new /obj/item/assembly/signaler(src) - for(var/i in 1 to 3) - new /obj/item/electropack(src) - -/obj/structure/closet/secure_closet/chemical - name = "chemical closet" - desc = "Store dangerous chemicals in here." - req_access = list(ACCESS_CHEMISTRY) - icon_door = "chemical" - -/obj/structure/closet/secure_closet/chemical/PopulateContents() - ..() - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/medigels(src) - new /obj/item/storage/box/medigels(src) - -/obj/structure/closet/secure_closet/chemical/heisenberg //contains one of each beaker, syringe etc. - name = "advanced chemical closet" - -/obj/structure/closet/secure_closet/chemical/heisenberg/PopulateContents() - ..() - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/storage/box/syringes/variety(src) - new /obj/item/storage/box/beakers/variety(src) - new /obj/item/clothing/glasses/science(src) +/obj/structure/closet/secure_closet/medical1 + name = "medicine closet" + desc = "Filled to the brim with medical junk." + icon_state = "med" + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/medical1/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/reagent_containers/glass/beaker = 2, + /obj/item/reagent_containers/dropper = 2, + /obj/item/storage/belt/medical = 1, + /obj/item/storage/box/syringes = 1, + /obj/item/reagent_containers/glass/bottle/toxin = 1, + /obj/item/reagent_containers/glass/bottle/morphine = 2, + /obj/item/reagent_containers/glass/bottle/epinephrine= 3, + /obj/item/reagent_containers/glass/bottle/multiver = 3, + /obj/item/storage/box/rxglasses = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/secure_closet/medical2 + name = "anesthetic closet" + desc = "Used to knock people out." + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/medical2/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/tank/internals/anesthetic(src) + for(var/i in 1 to 3) + new /obj/item/clothing/mask/breath/medical(src) + +/obj/structure/closet/secure_closet/medical3 + name = "medical doctor's locker" + req_access = list(ACCESS_SURGERY) + icon_state = "med_secure" + +/obj/structure/closet/secure_closet/medical3/PopulateContents() + ..() + new /obj/item/radio/headset/headset_med(src) + new /obj/item/defibrillator/loaded(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/clothing/glasses/hud/health(src) + return + +/obj/structure/closet/secure_closet/psychology + name = "psychology locker" + req_access = list(ACCESS_PSYCHOLOGY) + icon_state = "cabinet" + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/secure_closet/psychology/PopulateContents() + ..() + new /obj/item/clothing/under/suit/black(src) + new /obj/item/clothing/under/suit/black/skirt(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/storage/backpack/medic(src) + new /obj/item/radio/headset/headset_srvmed(src) + new /obj/item/clipboard(src) + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/clothing/ears/earmuffs(src) + new /obj/item/clothing/mask/muzzle(src) + new /obj/item/clothing/glasses/blindfold(src) + +/obj/structure/closet/secure_closet/chief_medical + name = "\proper chief medical officer's locker" + req_access = list(ACCESS_CMO) + icon_state = "cmo" + +/obj/structure/closet/secure_closet/chief_medical/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/cmo(src) + new /obj/item/clothing/suit/bio_suit/cmo(src) + new /obj/item/clothing/head/bio_hood/cmo(src) + new /obj/item/clothing/suit/toggle/labcoat/cmo(src) + new /obj/item/clothing/under/rank/medical/chief_medical_officer(src) + new /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown (src) + new /obj/item/cartridge/cmo(src) + new /obj/item/radio/headset/heads/cmo(src) + new /obj/item/megaphone/command(src) + new /obj/item/defibrillator/compact/loaded(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/healthanalyzer/advanced(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/reagent_containers/hypospray/cmo(src) + new /obj/item/autosurgeon/cmo(src) + new /obj/item/door_remote/chief_medical_officer(src) + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/pet_carrier(src) + new /obj/item/wallframe/defib_mount(src) + new /obj/item/circuitboard/machine/techfab/department/medical(src) + new /obj/item/storage/photo_album/cmo(src) + +/obj/structure/closet/secure_closet/animal + name = "animal control" + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/animal/PopulateContents() + ..() + new /obj/item/assembly/signaler(src) + for(var/i in 1 to 3) + new /obj/item/electropack(src) + +/obj/structure/closet/secure_closet/chemical + name = "chemical closet" + desc = "Store dangerous chemicals in here." + req_access = list(ACCESS_CHEMISTRY) + icon_door = "chemical" + +/obj/structure/closet/secure_closet/chemical/PopulateContents() + ..() + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/medigels(src) + new /obj/item/storage/box/medigels(src) + +/obj/structure/closet/secure_closet/chemical/heisenberg //contains one of each beaker, syringe etc. + name = "advanced chemical closet" + +/obj/structure/closet/secure_closet/chemical/heisenberg/PopulateContents() + ..() + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/storage/box/syringes/variety(src) + new /obj/item/storage/box/beakers/variety(src) + new /obj/item/clothing/glasses/science(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm index 0ebde8b2796..eeb3c1ef9f1 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm @@ -1,57 +1,57 @@ -/obj/structure/closet/secure_closet/personal - desc = "It's a secure locker for personnel. The first card swiped gains control." - name = "personal closet" - req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) - var/registered_name = null - -/obj/structure/closet/secure_closet/personal/PopulateContents() - ..() - if(prob(50)) - new /obj/item/storage/backpack/duffelbag(src) - if(prob(50)) - new /obj/item/storage/backpack(src) - else - new /obj/item/storage/backpack/satchel(src) - new /obj/item/radio/headset( src ) - -/obj/structure/closet/secure_closet/personal/patient - name = "patient's closet" - -/obj/structure/closet/secure_closet/personal/patient/PopulateContents() - new /obj/item/clothing/under/color/white( src ) - new /obj/item/clothing/shoes/sneakers/white( src ) - -/obj/structure/closet/secure_closet/personal/cabinet - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/secure_closet/personal/cabinet/PopulateContents() - new /obj/item/storage/backpack/satchel/leather/withwallet( src ) - new /obj/item/instrument/piano_synth(src) - new /obj/item/radio/headset( src ) - -/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) - var/obj/item/card/id/I = W.GetID() - if(istype(I)) - if(broken) - to_chat(user, "It appears to be broken.") - return - if(!I || !I.registered_name) - return - if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name))) - //they can open all lockers, or nobody owns this, or they own this locker - locked = !locked - update_icon() - - if(!registered_name) - registered_name = I.registered_name - desc = "Owned by [I.registered_name]." - else - to_chat(user, "Access Denied.") - else - return ..() +/obj/structure/closet/secure_closet/personal + desc = "It's a secure locker for personnel. The first card swiped gains control." + name = "personal closet" + req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) + var/registered_name = null + +/obj/structure/closet/secure_closet/personal/PopulateContents() + ..() + if(prob(50)) + new /obj/item/storage/backpack/duffelbag(src) + if(prob(50)) + new /obj/item/storage/backpack(src) + else + new /obj/item/storage/backpack/satchel(src) + new /obj/item/radio/headset( src ) + +/obj/structure/closet/secure_closet/personal/patient + name = "patient's closet" + +/obj/structure/closet/secure_closet/personal/patient/PopulateContents() + new /obj/item/clothing/under/color/white( src ) + new /obj/item/clothing/shoes/sneakers/white( src ) + +/obj/structure/closet/secure_closet/personal/cabinet + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/secure_closet/personal/cabinet/PopulateContents() + new /obj/item/storage/backpack/satchel/leather/withwallet( src ) + new /obj/item/instrument/piano_synth(src) + new /obj/item/radio/headset( src ) + +/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) + var/obj/item/card/id/I = W.GetID() + if(istype(I)) + if(broken) + to_chat(user, "It appears to be broken.") + return + if(!I || !I.registered_name) + return + if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name))) + //they can open all lockers, or nobody owns this, or they own this locker + locked = !locked + update_icon() + + if(!registered_name) + registered_name = I.registered_name + desc = "Owned by [I.registered_name]." + else + to_chat(user, "Access Denied.") + else + return ..() diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm index 3d2048f3cba..34be5461a53 100755 --- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm @@ -1,28 +1,28 @@ -/obj/structure/closet/secure_closet/research_director - name = "\proper research director's locker" - req_access = list(ACCESS_RD) - icon_state = "rd" - -/obj/structure/closet/secure_closet/research_director/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/rd(src) - new /obj/item/clothing/suit/bio_suit/scientist(src) - new /obj/item/clothing/head/bio_hood/scientist(src) - new /obj/item/clothing/suit/toggle/labcoat(src) - new /obj/item/clothing/under/rank/rnd/research_director(src) - new /obj/item/clothing/under/rank/rnd/research_director/skirt(src) - new /obj/item/clothing/under/rank/rnd/research_director/alt(src) - new /obj/item/clothing/under/rank/rnd/research_director/alt/skirt(src) - new /obj/item/clothing/under/rank/rnd/research_director/turtleneck(src) - new /obj/item/clothing/under/rank/rnd/research_director/turtleneck/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/cartridge/rd(src) - new /obj/item/radio/headset/heads/rd(src) - new /obj/item/megaphone/command(src) - new /obj/item/storage/lockbox/medal/sci(src) - new /obj/item/clothing/suit/armor/reactive/teleport(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/laser_pointer(src) - new /obj/item/door_remote/research_director(src) - new /obj/item/circuitboard/machine/techfab/department/science(src) - new /obj/item/storage/photo_album/rd(src) +/obj/structure/closet/secure_closet/research_director + name = "\proper research director's locker" + req_access = list(ACCESS_RD) + icon_state = "rd" + +/obj/structure/closet/secure_closet/research_director/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/rd(src) + new /obj/item/clothing/suit/bio_suit/scientist(src) + new /obj/item/clothing/head/bio_hood/scientist(src) + new /obj/item/clothing/suit/toggle/labcoat(src) + new /obj/item/clothing/under/rank/rnd/research_director(src) + new /obj/item/clothing/under/rank/rnd/research_director/skirt(src) + new /obj/item/clothing/under/rank/rnd/research_director/alt(src) + new /obj/item/clothing/under/rank/rnd/research_director/alt/skirt(src) + new /obj/item/clothing/under/rank/rnd/research_director/turtleneck(src) + new /obj/item/clothing/under/rank/rnd/research_director/turtleneck/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/cartridge/rd(src) + new /obj/item/radio/headset/heads/rd(src) + new /obj/item/megaphone/command(src) + new /obj/item/storage/lockbox/medal/sci(src) + new /obj/item/clothing/suit/armor/reactive/teleport(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/laser_pointer(src) + new /obj/item/door_remote/research_director(src) + new /obj/item/circuitboard/machine/techfab/department/science(src) + new /obj/item/storage/photo_album/rd(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm index c555c598512..2a12cfa870a 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm @@ -1,9 +1,9 @@ -/obj/structure/closet/secure_closet - name = "secure locker" - desc = "It's a card-locked storage unit." - locked = TRUE - icon_state = "secure" - max_integrity = 250 - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - secure = TRUE - damage_deflection = 20 +/obj/structure/closet/secure_closet + name = "secure locker" + desc = "It's a card-locked storage unit." + locked = TRUE + icon_state = "secure" + max_integrity = 250 + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + secure = TRUE + damage_deflection = 20 diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index 204ea4a00b4..0fa45680a68 100755 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -1,322 +1,322 @@ -/obj/structure/closet/secure_closet/captains - name = "\proper captain's locker" - req_access = list(ACCESS_CAPTAIN) - icon_state = "cap" - -/obj/structure/closet/secure_closet/captains/PopulateContents() - ..() - new /obj/item/clothing/suit/hooded/wintercoat/captain(src) - new /obj/item/storage/backpack/captain(src) - new /obj/item/storage/backpack/satchel/cap(src) - new /obj/item/storage/backpack/duffelbag/captain(src) - new /obj/item/clothing/neck/cloak/cap(src) - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/pet_carrier(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/clothing/under/rank/captain(src) - new /obj/item/clothing/under/rank/captain/skirt(src) - new /obj/item/clothing/suit/armor/vest/capcarapace(src) - new /obj/item/clothing/head/caphat(src) - new /obj/item/clothing/under/rank/captain/parade(src) - new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) - new /obj/item/clothing/head/caphat/parade(src) - new /obj/item/clothing/suit/captunic(src) - new /obj/item/clothing/head/crown/fancy(src) - new /obj/item/cartridge/captain(src) - new /obj/item/storage/box/silver_ids(src) - new /obj/item/radio/headset/heads/captain/alt(src) - new /obj/item/radio/headset/heads/captain(src) - new /obj/item/clothing/glasses/sunglasses/gar/supergar(src) - new /obj/item/clothing/gloves/color/captain(src) - new /obj/item/storage/belt/sabre(src) - new /obj/item/gun/energy/e_gun(src) - new /obj/item/door_remote/captain(src) - new /obj/item/card/id/captains_spare(src) - new /obj/item/storage/photo_album/captain(src) - -/obj/structure/closet/secure_closet/hop - name = "\proper head of personnel's locker" - req_access = list(ACCESS_HOP) - icon_state = "hop" - -/obj/structure/closet/secure_closet/hop/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/hop(src) - new /obj/item/storage/lockbox/medal/service(src) - new /obj/item/clothing/under/rank/civilian/head_of_personnel(src) - new /obj/item/clothing/under/rank/civilian/head_of_personnel/skirt(src) - new /obj/item/clothing/head/hopcap(src) - new /obj/item/cartridge/hop(src) - new /obj/item/radio/headset/heads/hop(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/storage/box/ids(src) - new /obj/item/storage/box/ids(src) - new /obj/item/megaphone/command(src) - new /obj/item/clothing/suit/armor/vest/alt(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/gun/energy/e_gun(src) - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/pet_carrier(src) - new /obj/item/door_remote/civilian(src) - new /obj/item/circuitboard/machine/techfab/department/service(src) - new /obj/item/storage/photo_album/hop(src) - -/obj/structure/closet/secure_closet/hos - name = "\proper head of security's locker" - req_access = list(ACCESS_HOS) - icon_state = "hos" - -/obj/structure/closet/secure_closet/hos/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/hos(src) - new /obj/item/cartridge/hos(src) - new /obj/item/radio/headset/heads/hos(src) - new /obj/item/clothing/under/rank/security/head_of_security/parade/female(src) - new /obj/item/clothing/under/rank/security/head_of_security/parade(src) - new /obj/item/clothing/suit/armor/vest/leather(src) - new /obj/item/clothing/suit/armor/hos(src) - new /obj/item/clothing/under/rank/security/head_of_security/skirt(src) - new /obj/item/clothing/under/rank/security/head_of_security/alt(src) - new /obj/item/clothing/under/rank/security/head_of_security/alt/skirt(src) - new /obj/item/clothing/head/hos(src) - new /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch(src) - new /obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars(src) - new /obj/item/clothing/under/rank/security/head_of_security/grey(src) - new /obj/item/storage/lockbox/medal/sec(src) - new /obj/item/megaphone/sec(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/storage/lockbox/loyalty(src) - new /obj/item/clothing/mask/gas/sechailer/swat(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/shield/riot/tele(src) - new /obj/item/storage/belt/security/full(src) - new /obj/item/gun/energy/e_gun/hos(src) - new /obj/item/pinpointer/nuke(src) - new /obj/item/circuitboard/machine/techfab/department/security(src) - new /obj/item/storage/photo_album/hos(src) - -/obj/structure/closet/secure_closet/warden - name = "\proper warden's locker" - req_access = list(ACCESS_ARMORY) - icon_state = "warden" - -/obj/structure/closet/secure_closet/warden/PopulateContents() - ..() - new /obj/item/radio/headset/headset_sec(src) - new /obj/item/clothing/suit/armor/vest/warden(src) - new /obj/item/clothing/head/warden(src) - new /obj/item/clothing/head/warden/drill(src) - new /obj/item/clothing/head/beret/sec/navywarden(src) - new /obj/item/clothing/suit/armor/vest/warden/alt(src) - new /obj/item/clothing/under/rank/security/warden/formal(src) - new /obj/item/clothing/under/rank/security/warden/skirt(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/storage/box/zipties(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/belt/security/full(src) - new /obj/item/flashlight/seclite(src) - new /obj/item/clothing/gloves/krav_maga/sec(src) - new /obj/item/door_remote/head_of_security(src) - -/obj/structure/closet/secure_closet/security - name = "security officer's locker" - req_access = list(ACCESS_SECURITY) - icon_state = "sec" - -/obj/structure/closet/secure_closet/security/PopulateContents() - ..() - new /obj/item/clothing/suit/armor/vest(src) - new /obj/item/clothing/head/helmet/sec(src) - new /obj/item/radio/headset/headset_sec(src) - new /obj/item/radio/headset/headset_sec/alt(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/flashlight/seclite(src) - -/obj/structure/closet/secure_closet/security/sec - -/obj/structure/closet/secure_closet/security/sec/PopulateContents() - ..() - new /obj/item/storage/belt/security/full(src) - -/obj/structure/closet/secure_closet/security/cargo - -/obj/structure/closet/secure_closet/security/cargo/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/cargo(src) - new /obj/item/encryptionkey/headset_cargo(src) - -/obj/structure/closet/secure_closet/security/engine - -/obj/structure/closet/secure_closet/security/engine/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/engine(src) - new /obj/item/encryptionkey/headset_eng(src) - -/obj/structure/closet/secure_closet/security/science - -/obj/structure/closet/secure_closet/security/science/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/science(src) - new /obj/item/encryptionkey/headset_sci(src) - -/obj/structure/closet/secure_closet/security/med - -/obj/structure/closet/secure_closet/security/med/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/medblue(src) - new /obj/item/encryptionkey/headset_med(src) - -/obj/structure/closet/secure_closet/detective - name = "\improper detective's cabinet" - req_access = list(ACCESS_FORENSICS_LOCKERS) - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - -/obj/structure/closet/secure_closet/detective/PopulateContents() - ..() - new /obj/item/storage/box/evidence(src) - new /obj/item/radio/headset/headset_sec(src) - new /obj/item/detective_scanner(src) - new /obj/item/flashlight/seclite(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/clothing/suit/armor/vest/det_suit(src) - new /obj/item/storage/belt/holster/detective/full(src) - new /obj/item/pinpointer/crew(src) - new /obj/item/binoculars(src) - new /obj/item/storage/box/rxglasses/spyglasskit(src) - -/obj/structure/closet/secure_closet/injection - name = "lethal injections" - req_access = list(ACCESS_HOS) - -/obj/structure/closet/secure_closet/injection/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/syringe/lethal/execution(src) - -/obj/structure/closet/secure_closet/brig - name = "brig locker" - req_access = list(ACCESS_BRIG) - anchored = TRUE - var/id = null - -/obj/structure/closet/secure_closet/evidence - anchored = TRUE - name = "Secure Evidence Closet" - req_access_txt = "0" - req_one_access_txt = list(ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS) - -/obj/structure/closet/secure_closet/brig/PopulateContents() - ..() - new /obj/item/clothing/under/rank/prisoner( src ) - new /obj/item/clothing/under/rank/prisoner/skirt( src ) - new /obj/item/clothing/shoes/sneakers/orange( src ) - -/obj/structure/closet/secure_closet/courtroom - name = "courtroom locker" - req_access = list(ACCESS_COURT) - -/obj/structure/closet/secure_closet/courtroom/PopulateContents() - ..() - new /obj/item/clothing/shoes/sneakers/brown(src) - for(var/i in 1 to 3) - new /obj/item/paper/fluff/jobs/security/court_judgement (src) - new /obj/item/pen (src) - new /obj/item/clothing/suit/judgerobe (src) - new /obj/item/clothing/head/powdered_wig (src) - new /obj/item/storage/briefcase(src) - -/obj/structure/closet/secure_closet/contraband/armory - anchored = TRUE - name = "Contraband Locker" - req_access = list(ACCESS_ARMORY) - -/obj/structure/closet/secure_closet/contraband/heads - anchored = TRUE - name = "Contraband Locker" - req_access = list(ACCESS_HEADS) - -/obj/structure/closet/secure_closet/armory1 - name = "armory armor locker" - req_access = list(ACCESS_ARMORY) - icon_state = "armory" - -/obj/structure/closet/secure_closet/armory1/PopulateContents() - ..() - new /obj/item/clothing/suit/hooded/ablative(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/armor/riot(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/helmet/riot(src) - for(var/i in 1 to 3) - new /obj/item/shield/riot(src) - -/obj/structure/closet/secure_closet/armory2 - name = "armory ballistics locker" - req_access = list(ACCESS_ARMORY) - icon_state = "armory" - -/obj/structure/closet/secure_closet/armory2/PopulateContents() - ..() - new /obj/item/storage/box/firingpins(src) - for(var/i in 1 to 3) - new /obj/item/storage/box/rubbershot(src) - for(var/i in 1 to 3) - new /obj/item/gun/ballistic/shotgun/riot(src) - -/obj/structure/closet/secure_closet/armory3 - name = "armory energy gun locker" - req_access = list(ACCESS_ARMORY) - icon_state = "armory" - -/obj/structure/closet/secure_closet/armory3/PopulateContents() - ..() - new /obj/item/storage/box/firingpins(src) - new /obj/item/gun/energy/ionrifle(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/e_gun(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) - -/obj/structure/closet/secure_closet/tac - name = "armory tac locker" - req_access = list(ACCESS_ARMORY) - icon_state = "tac" - -/obj/structure/closet/secure_closet/tac/PopulateContents() - ..() - new /obj/item/gun/ballistic/automatic/wt550(src) - new /obj/item/clothing/head/helmet/alt(src) - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/clothing/suit/armor/bulletproof(src) - -/obj/structure/closet/secure_closet/lethalshots - name = "shotgun lethal rounds" - req_access = list(ACCESS_ARMORY) - icon_state = "tac" - -/obj/structure/closet/secure_closet/lethalshots/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/storage/box/lethalshot(src) - -/obj/structure/closet/secure_closet/labor_camp_security - name = "labor camp security locker" - req_access = list(ACCESS_SECURITY) - icon_state = "sec" - -/obj/structure/closet/secure_closet/labor_camp_security/PopulateContents() - ..() - new /obj/item/clothing/suit/armor/vest(src) - new /obj/item/clothing/head/helmet/sec(src) - new /obj/item/clothing/under/rank/security/officer(src) - new /obj/item/clothing/under/rank/security/officer/skirt(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/flashlight/seclite(src) +/obj/structure/closet/secure_closet/captains + name = "\proper captain's locker" + req_access = list(ACCESS_CAPTAIN) + icon_state = "cap" + +/obj/structure/closet/secure_closet/captains/PopulateContents() + ..() + new /obj/item/clothing/suit/hooded/wintercoat/captain(src) + new /obj/item/storage/backpack/captain(src) + new /obj/item/storage/backpack/satchel/cap(src) + new /obj/item/storage/backpack/duffelbag/captain(src) + new /obj/item/clothing/neck/cloak/cap(src) + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/pet_carrier(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/clothing/under/rank/captain(src) + new /obj/item/clothing/under/rank/captain/skirt(src) + new /obj/item/clothing/suit/armor/vest/capcarapace(src) + new /obj/item/clothing/head/caphat(src) + new /obj/item/clothing/under/rank/captain/parade(src) + new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) + new /obj/item/clothing/head/caphat/parade(src) + new /obj/item/clothing/suit/captunic(src) + new /obj/item/clothing/head/crown/fancy(src) + new /obj/item/cartridge/captain(src) + new /obj/item/storage/box/silver_ids(src) + new /obj/item/radio/headset/heads/captain/alt(src) + new /obj/item/radio/headset/heads/captain(src) + new /obj/item/clothing/glasses/sunglasses/gar/supergar(src) + new /obj/item/clothing/gloves/color/captain(src) + new /obj/item/storage/belt/sabre(src) + new /obj/item/gun/energy/e_gun(src) + new /obj/item/door_remote/captain(src) + new /obj/item/card/id/captains_spare(src) + new /obj/item/storage/photo_album/captain(src) + +/obj/structure/closet/secure_closet/hop + name = "\proper head of personnel's locker" + req_access = list(ACCESS_HOP) + icon_state = "hop" + +/obj/structure/closet/secure_closet/hop/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/hop(src) + new /obj/item/storage/lockbox/medal/service(src) + new /obj/item/clothing/under/rank/civilian/head_of_personnel(src) + new /obj/item/clothing/under/rank/civilian/head_of_personnel/skirt(src) + new /obj/item/clothing/head/hopcap(src) + new /obj/item/cartridge/hop(src) + new /obj/item/radio/headset/heads/hop(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/storage/box/ids(src) + new /obj/item/storage/box/ids(src) + new /obj/item/megaphone/command(src) + new /obj/item/clothing/suit/armor/vest/alt(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/gun/energy/e_gun(src) + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/pet_carrier(src) + new /obj/item/door_remote/civilian(src) + new /obj/item/circuitboard/machine/techfab/department/service(src) + new /obj/item/storage/photo_album/hop(src) + +/obj/structure/closet/secure_closet/hos + name = "\proper head of security's locker" + req_access = list(ACCESS_HOS) + icon_state = "hos" + +/obj/structure/closet/secure_closet/hos/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/hos(src) + new /obj/item/cartridge/hos(src) + new /obj/item/radio/headset/heads/hos(src) + new /obj/item/clothing/under/rank/security/head_of_security/parade/female(src) + new /obj/item/clothing/under/rank/security/head_of_security/parade(src) + new /obj/item/clothing/suit/armor/vest/leather(src) + new /obj/item/clothing/suit/armor/hos(src) + new /obj/item/clothing/under/rank/security/head_of_security/skirt(src) + new /obj/item/clothing/under/rank/security/head_of_security/alt(src) + new /obj/item/clothing/under/rank/security/head_of_security/alt/skirt(src) + new /obj/item/clothing/head/hos(src) + new /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch(src) + new /obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars(src) + new /obj/item/clothing/under/rank/security/head_of_security/grey(src) + new /obj/item/storage/lockbox/medal/sec(src) + new /obj/item/megaphone/sec(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/storage/lockbox/loyalty(src) + new /obj/item/clothing/mask/gas/sechailer/swat(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/shield/riot/tele(src) + new /obj/item/storage/belt/security/full(src) + new /obj/item/gun/energy/e_gun/hos(src) + new /obj/item/pinpointer/nuke(src) + new /obj/item/circuitboard/machine/techfab/department/security(src) + new /obj/item/storage/photo_album/hos(src) + +/obj/structure/closet/secure_closet/warden + name = "\proper warden's locker" + req_access = list(ACCESS_ARMORY) + icon_state = "warden" + +/obj/structure/closet/secure_closet/warden/PopulateContents() + ..() + new /obj/item/radio/headset/headset_sec(src) + new /obj/item/clothing/suit/armor/vest/warden(src) + new /obj/item/clothing/head/warden(src) + new /obj/item/clothing/head/warden/drill(src) + new /obj/item/clothing/head/beret/sec/navywarden(src) + new /obj/item/clothing/suit/armor/vest/warden/alt(src) + new /obj/item/clothing/under/rank/security/warden/formal(src) + new /obj/item/clothing/under/rank/security/warden/skirt(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/storage/box/zipties(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/belt/security/full(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/clothing/gloves/krav_maga/sec(src) + new /obj/item/door_remote/head_of_security(src) + +/obj/structure/closet/secure_closet/security + name = "security officer's locker" + req_access = list(ACCESS_SECURITY) + icon_state = "sec" + +/obj/structure/closet/secure_closet/security/PopulateContents() + ..() + new /obj/item/clothing/suit/armor/vest(src) + new /obj/item/clothing/head/helmet/sec(src) + new /obj/item/radio/headset/headset_sec(src) + new /obj/item/radio/headset/headset_sec/alt(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/flashlight/seclite(src) + +/obj/structure/closet/secure_closet/security/sec + +/obj/structure/closet/secure_closet/security/sec/PopulateContents() + ..() + new /obj/item/storage/belt/security/full(src) + +/obj/structure/closet/secure_closet/security/cargo + +/obj/structure/closet/secure_closet/security/cargo/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/cargo(src) + new /obj/item/encryptionkey/headset_cargo(src) + +/obj/structure/closet/secure_closet/security/engine + +/obj/structure/closet/secure_closet/security/engine/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/engine(src) + new /obj/item/encryptionkey/headset_eng(src) + +/obj/structure/closet/secure_closet/security/science + +/obj/structure/closet/secure_closet/security/science/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/science(src) + new /obj/item/encryptionkey/headset_sci(src) + +/obj/structure/closet/secure_closet/security/med + +/obj/structure/closet/secure_closet/security/med/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/medblue(src) + new /obj/item/encryptionkey/headset_med(src) + +/obj/structure/closet/secure_closet/detective + name = "\improper detective's cabinet" + req_access = list(ACCESS_FORENSICS_LOCKERS) + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + +/obj/structure/closet/secure_closet/detective/PopulateContents() + ..() + new /obj/item/storage/box/evidence(src) + new /obj/item/radio/headset/headset_sec(src) + new /obj/item/detective_scanner(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/clothing/suit/armor/vest/det_suit(src) + new /obj/item/storage/belt/holster/detective/full(src) + new /obj/item/pinpointer/crew(src) + new /obj/item/binoculars(src) + new /obj/item/storage/box/rxglasses/spyglasskit(src) + +/obj/structure/closet/secure_closet/injection + name = "lethal injections" + req_access = list(ACCESS_HOS) + +/obj/structure/closet/secure_closet/injection/PopulateContents() + ..() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/syringe/lethal/execution(src) + +/obj/structure/closet/secure_closet/brig + name = "brig locker" + req_access = list(ACCESS_BRIG) + anchored = TRUE + var/id = null + +/obj/structure/closet/secure_closet/evidence + anchored = TRUE + name = "Secure Evidence Closet" + req_access_txt = "0" + req_one_access_txt = list(ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS) + +/obj/structure/closet/secure_closet/brig/PopulateContents() + ..() + new /obj/item/clothing/under/rank/prisoner( src ) + new /obj/item/clothing/under/rank/prisoner/skirt( src ) + new /obj/item/clothing/shoes/sneakers/orange( src ) + +/obj/structure/closet/secure_closet/courtroom + name = "courtroom locker" + req_access = list(ACCESS_COURT) + +/obj/structure/closet/secure_closet/courtroom/PopulateContents() + ..() + new /obj/item/clothing/shoes/sneakers/brown(src) + for(var/i in 1 to 3) + new /obj/item/paper/fluff/jobs/security/court_judgement (src) + new /obj/item/pen (src) + new /obj/item/clothing/suit/judgerobe (src) + new /obj/item/clothing/head/powdered_wig (src) + new /obj/item/storage/briefcase(src) + +/obj/structure/closet/secure_closet/contraband/armory + anchored = TRUE + name = "Contraband Locker" + req_access = list(ACCESS_ARMORY) + +/obj/structure/closet/secure_closet/contraband/heads + anchored = TRUE + name = "Contraband Locker" + req_access = list(ACCESS_HEADS) + +/obj/structure/closet/secure_closet/armory1 + name = "armory armor locker" + req_access = list(ACCESS_ARMORY) + icon_state = "armory" + +/obj/structure/closet/secure_closet/armory1/PopulateContents() + ..() + new /obj/item/clothing/suit/hooded/ablative(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/armor/riot(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/helmet/riot(src) + for(var/i in 1 to 3) + new /obj/item/shield/riot(src) + +/obj/structure/closet/secure_closet/armory2 + name = "armory ballistics locker" + req_access = list(ACCESS_ARMORY) + icon_state = "armory" + +/obj/structure/closet/secure_closet/armory2/PopulateContents() + ..() + new /obj/item/storage/box/firingpins(src) + for(var/i in 1 to 3) + new /obj/item/storage/box/rubbershot(src) + for(var/i in 1 to 3) + new /obj/item/gun/ballistic/shotgun/riot(src) + +/obj/structure/closet/secure_closet/armory3 + name = "armory energy gun locker" + req_access = list(ACCESS_ARMORY) + icon_state = "armory" + +/obj/structure/closet/secure_closet/armory3/PopulateContents() + ..() + new /obj/item/storage/box/firingpins(src) + new /obj/item/gun/energy/ionrifle(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/e_gun(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + +/obj/structure/closet/secure_closet/tac + name = "armory tac locker" + req_access = list(ACCESS_ARMORY) + icon_state = "tac" + +/obj/structure/closet/secure_closet/tac/PopulateContents() + ..() + new /obj/item/gun/ballistic/automatic/wt550(src) + new /obj/item/clothing/head/helmet/alt(src) + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/clothing/suit/armor/bulletproof(src) + +/obj/structure/closet/secure_closet/lethalshots + name = "shotgun lethal rounds" + req_access = list(ACCESS_ARMORY) + icon_state = "tac" + +/obj/structure/closet/secure_closet/lethalshots/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/storage/box/lethalshot(src) + +/obj/structure/closet/secure_closet/labor_camp_security + name = "labor camp security locker" + req_access = list(ACCESS_SECURITY) + icon_state = "sec" + +/obj/structure/closet/secure_closet/labor_camp_security/PopulateContents() + ..() + new /obj/item/clothing/suit/armor/vest(src) + new /obj/item/clothing/head/helmet/sec(src) + new /obj/item/clothing/under/rank/security/officer(src) + new /obj/item/clothing/under/rank/security/officer/skirt(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/flashlight/seclite(src) diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm index 0fbd850503e..4ee1d106bf2 100644 --- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm +++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm @@ -1,122 +1,122 @@ -/obj/structure/closet/syndicate - name = "armory closet" - desc = "Why is this here?" - icon_state = "syndicate" - -/obj/structure/closet/syndicate/personal - desc = "It's a personal storage unit for operative gear." - -/obj/structure/closet/syndicate/personal/PopulateContents() - ..() - new /obj/item/clothing/under/syndicate(src) - new /obj/item/clothing/under/syndicate/skirt(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/radio/headset/syndicate(src) - new /obj/item/ammo_box/magazine/m9mm(src) - new /obj/item/storage/belt/military(src) - new /obj/item/crowbar/red(src) - new /obj/item/clothing/glasses/night(src) - new /obj/item/storage/belt/holster/nukie(src) - new /obj/item/pickaxe/drill/diamonddrill(src) - -/obj/structure/closet/syndicate/nuclear - desc = "It's a storage unit for a Syndicate boarding party." - -/obj/structure/closet/syndicate/nuclear/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/ammo_box/magazine/m9mm(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/box/teargas(src) - new /obj/item/storage/backpack/duffelbag/syndie/med(src) - new /obj/item/pda/syndicate(src) - -/obj/structure/closet/syndicate/resources - desc = "An old, dusty locker." - -/obj/structure/closet/syndicate/resources/PopulateContents() - ..() - var/common_min = 30 //Minimum amount of minerals in the stack for common minerals - var/common_max = 50 //Maximum amount of HONK in the stack for HONK common minerals - var/rare_min = 5 //Minimum HONK of HONK in the stack HONK HONK rare minerals - var/rare_max = 20 //Maximum HONK HONK HONK in the HONK for HONK rare HONK - - - var/pickednum = rand(1, 50) - - //Sad trombone - if(pickednum == 1) - var/obj/item/paper/P = new /obj/item/paper(src) - P.name = "\improper IOU" - P.info = "Sorry man, we needed the money so we sold your stash. It's ok, we'll double our money for sure this time!" - - //Metal (common ore) - if(pickednum >= 2) - new /obj/item/stack/sheet/metal(src, rand(common_min, common_max)) - - //Glass (common ore) - if(pickednum >= 5) - new /obj/item/stack/sheet/glass(src, rand(common_min, common_max)) - - //Plasteel (common ore) Because it has a million more uses then plasma - if(pickednum >= 10) - new /obj/item/stack/sheet/plasteel(src, rand(common_min, common_max)) - - //Plasma (rare ore) - if(pickednum >= 15) - new /obj/item/stack/sheet/mineral/plasma(src, rand(rare_min, rare_max)) - - //Silver (rare ore) - if(pickednum >= 20) - new /obj/item/stack/sheet/mineral/silver(src, rand(rare_min, rare_max)) - - //Gold (rare ore) - if(pickednum >= 30) - new /obj/item/stack/sheet/mineral/gold(src, rand(rare_min, rare_max)) - - //Uranium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/uranium(src, rand(rare_min, rare_max)) - - //Titanium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/titanium(src, rand(rare_min, rare_max)) - - //Plastitanium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/plastitanium(src, rand(rare_min, rare_max)) - - //Diamond (rare HONK) - if(pickednum >= 45) - new /obj/item/stack/sheet/mineral/diamond(src, rand(rare_min, rare_max)) - - //Jetpack (You hit the jackpot!) - if(pickednum == 50) - new /obj/item/tank/jetpack/carbondioxide(src) - -/obj/structure/closet/syndicate/resources/everything - desc = "It's an emergency storage closet for repairs." - -/obj/structure/closet/syndicate/resources/everything/PopulateContents() - var/list/resources = list( - /obj/item/stack/sheet/metal, - /obj/item/stack/sheet/glass, - /obj/item/stack/sheet/mineral/gold, - /obj/item/stack/sheet/mineral/silver, - /obj/item/stack/sheet/mineral/plasma, - /obj/item/stack/sheet/mineral/uranium, - /obj/item/stack/sheet/mineral/diamond, - /obj/item/stack/sheet/mineral/bananium, - /obj/item/stack/sheet/plasteel, - /obj/item/stack/sheet/mineral/titanium, - /obj/item/stack/sheet/mineral/plastitanium, - /obj/item/stack/rods, - /obj/item/stack/sheet/bluespace_crystal, - /obj/item/stack/sheet/mineral/abductor, - /obj/item/stack/sheet/plastic, - /obj/item/stack/sheet/mineral/wood - ) - - for(var/i = 0, i<2, i++) - for(var/res in resources) - var/obj/item/stack/R = res - new res(src, initial(R.max_amount)) +/obj/structure/closet/syndicate + name = "armory closet" + desc = "Why is this here?" + icon_state = "syndicate" + +/obj/structure/closet/syndicate/personal + desc = "It's a personal storage unit for operative gear." + +/obj/structure/closet/syndicate/personal/PopulateContents() + ..() + new /obj/item/clothing/under/syndicate(src) + new /obj/item/clothing/under/syndicate/skirt(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/radio/headset/syndicate(src) + new /obj/item/ammo_box/magazine/m9mm(src) + new /obj/item/storage/belt/military(src) + new /obj/item/crowbar/red(src) + new /obj/item/clothing/glasses/night(src) + new /obj/item/storage/belt/holster/nukie(src) + new /obj/item/pickaxe/drill/diamonddrill(src) + +/obj/structure/closet/syndicate/nuclear + desc = "It's a storage unit for a Syndicate boarding party." + +/obj/structure/closet/syndicate/nuclear/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/ammo_box/magazine/m9mm(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/box/teargas(src) + new /obj/item/storage/backpack/duffelbag/syndie/med(src) + new /obj/item/pda/syndicate(src) + +/obj/structure/closet/syndicate/resources + desc = "An old, dusty locker." + +/obj/structure/closet/syndicate/resources/PopulateContents() + ..() + var/common_min = 30 //Minimum amount of minerals in the stack for common minerals + var/common_max = 50 //Maximum amount of HONK in the stack for HONK common minerals + var/rare_min = 5 //Minimum HONK of HONK in the stack HONK HONK rare minerals + var/rare_max = 20 //Maximum HONK HONK HONK in the HONK for HONK rare HONK + + + var/pickednum = rand(1, 50) + + //Sad trombone + if(pickednum == 1) + var/obj/item/paper/P = new /obj/item/paper(src) + P.name = "\improper IOU" + P.info = "Sorry man, we needed the money so we sold your stash. It's ok, we'll double our money for sure this time!" + + //Metal (common ore) + if(pickednum >= 2) + new /obj/item/stack/sheet/metal(src, rand(common_min, common_max)) + + //Glass (common ore) + if(pickednum >= 5) + new /obj/item/stack/sheet/glass(src, rand(common_min, common_max)) + + //Plasteel (common ore) Because it has a million more uses then plasma + if(pickednum >= 10) + new /obj/item/stack/sheet/plasteel(src, rand(common_min, common_max)) + + //Plasma (rare ore) + if(pickednum >= 15) + new /obj/item/stack/sheet/mineral/plasma(src, rand(rare_min, rare_max)) + + //Silver (rare ore) + if(pickednum >= 20) + new /obj/item/stack/sheet/mineral/silver(src, rand(rare_min, rare_max)) + + //Gold (rare ore) + if(pickednum >= 30) + new /obj/item/stack/sheet/mineral/gold(src, rand(rare_min, rare_max)) + + //Uranium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/uranium(src, rand(rare_min, rare_max)) + + //Titanium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/titanium(src, rand(rare_min, rare_max)) + + //Plastitanium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/plastitanium(src, rand(rare_min, rare_max)) + + //Diamond (rare HONK) + if(pickednum >= 45) + new /obj/item/stack/sheet/mineral/diamond(src, rand(rare_min, rare_max)) + + //Jetpack (You hit the jackpot!) + if(pickednum == 50) + new /obj/item/tank/jetpack/carbondioxide(src) + +/obj/structure/closet/syndicate/resources/everything + desc = "It's an emergency storage closet for repairs." + +/obj/structure/closet/syndicate/resources/everything/PopulateContents() + var/list/resources = list( + /obj/item/stack/sheet/metal, + /obj/item/stack/sheet/glass, + /obj/item/stack/sheet/mineral/gold, + /obj/item/stack/sheet/mineral/silver, + /obj/item/stack/sheet/mineral/plasma, + /obj/item/stack/sheet/mineral/uranium, + /obj/item/stack/sheet/mineral/diamond, + /obj/item/stack/sheet/mineral/bananium, + /obj/item/stack/sheet/plasteel, + /obj/item/stack/sheet/mineral/titanium, + /obj/item/stack/sheet/mineral/plastitanium, + /obj/item/stack/rods, + /obj/item/stack/sheet/bluespace_crystal, + /obj/item/stack/sheet/mineral/abductor, + /obj/item/stack/sheet/plastic, + /obj/item/stack/sheet/mineral/wood + ) + + for(var/i = 0, i<2, i++) + for(var/res in resources) + var/obj/item/stack/R = res + new res(src, initial(R.max_amount)) diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm index 16a69905947..82fc32e1322 100644 --- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm @@ -1,175 +1,175 @@ -/* Utility Closets - * Contains: - * Emergency Closet - * Fire Closet - * Tool Closet - * Radiation Closet - * Bombsuit Closet - * Hydrant - * First Aid - */ - -/* - * Emergency Closet - */ -/obj/structure/closet/emcloset - name = "emergency closet" - desc = "It's a storage unit for emergency breath masks and O2 tanks." - icon_state = "emergency" - -/obj/structure/closet/emcloset/anchored - anchored = TRUE - -/obj/structure/closet/emcloset/PopulateContents() - ..() - - if (prob(40)) - new /obj/item/storage/toolbox/emergency(src) - - switch (pickweight(list("small" = 35, "aid" = 30, "tank" = 20, "both" = 10, "nothing" = 4, "delete" = 1))) - if ("small") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/clothing/mask/breath(src) - - if ("aid") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/storage/firstaid/emergency(src) - new /obj/item/clothing/mask/breath(src) - - if ("tank") - new /obj/item/tank/internals/oxygen(src) - new /obj/item/clothing/mask/breath(src) - - if ("both") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/breath(src) - - if ("nothing") - // doot - - // teehee - if ("delete") - qdel(src) - -/* - * Fire Closet - */ -/obj/structure/closet/firecloset - name = "fire-safety closet" - desc = "It's a storage unit for fire-fighting supplies." - icon_state = "fire" - -/obj/structure/closet/firecloset/PopulateContents() - ..() - - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/tank/internals/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/obj/structure/closet/firecloset/full/PopulateContents() - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/flashlight(src) - new /obj/item/tank/internals/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/* - * Tool Closet - */ -/obj/structure/closet/toolcloset - name = "tool closet" - desc = "It's a storage unit for tools." - icon_state = "eng" - icon_door = "eng_tool" - -/obj/structure/closet/toolcloset/PopulateContents() - ..() - if(prob(40)) - new /obj/item/clothing/suit/hazardvest(src) - if(prob(70)) - new /obj/item/flashlight(src) - if(prob(70)) - new /obj/item/screwdriver(src) - if(prob(70)) - new /obj/item/wrench(src) - if(prob(70)) - new /obj/item/weldingtool(src) - if(prob(70)) - new /obj/item/crowbar(src) - if(prob(70)) - new /obj/item/wirecutters(src) - if(prob(70)) - new /obj/item/t_scanner(src) - if(prob(20)) - new /obj/item/storage/belt/utility(src) - if(prob(30)) - new /obj/item/stack/cable_coil(src) - if(prob(30)) - new /obj/item/stack/cable_coil(src) - if(prob(30)) - new /obj/item/stack/cable_coil(src) - if(prob(20)) - new /obj/item/multitool(src) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - if(prob(40)) - new /obj/item/clothing/head/hardhat(src) - - -/* - * Radiation Closet - */ -/obj/structure/closet/radiation - name = "radiation suit closet" - desc = "It's a storage unit for rad-protective suits." - icon_state = "eng" - icon_door = "eng_rad" - -/obj/structure/closet/radiation/PopulateContents() - ..() - new /obj/item/geiger_counter(src) - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - -/* - * Bombsuit closet - */ -/obj/structure/closet/bombcloset - name = "\improper EOD closet" - desc = "It's a storage unit for explosion-protective suits." - icon_state = "bomb" - -/obj/structure/closet/bombcloset/PopulateContents() - ..() - new /obj/item/clothing/suit/bomb_suit(src) - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/head/bomb_hood(src) - -/obj/structure/closet/bombcloset/security/PopulateContents() - new /obj/item/clothing/suit/bomb_suit/security(src) - new /obj/item/clothing/under/rank/security/officer(src) - new /obj/item/clothing/shoes/jackboots(src) - new /obj/item/clothing/head/bomb_hood/security(src) - -/obj/structure/closet/bombcloset/white/PopulateContents() - new /obj/item/clothing/suit/bomb_suit/white(src) - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/head/bomb_hood/white(src) - -/* - * Ammunition - */ -/obj/structure/closet/ammunitionlocker - name = "ammunition locker" - -/obj/structure/closet/ammunitionlocker/PopulateContents() - ..() - for(var/i in 1 to 8) - new /obj/item/ammo_casing/shotgun/beanbag(src) +/* Utility Closets + * Contains: + * Emergency Closet + * Fire Closet + * Tool Closet + * Radiation Closet + * Bombsuit Closet + * Hydrant + * First Aid + */ + +/* + * Emergency Closet + */ +/obj/structure/closet/emcloset + name = "emergency closet" + desc = "It's a storage unit for emergency breath masks and O2 tanks." + icon_state = "emergency" + +/obj/structure/closet/emcloset/anchored + anchored = TRUE + +/obj/structure/closet/emcloset/PopulateContents() + ..() + + if (prob(40)) + new /obj/item/storage/toolbox/emergency(src) + + switch (pickweight(list("small" = 35, "aid" = 30, "tank" = 20, "both" = 10, "nothing" = 4, "delete" = 1))) + if ("small") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/clothing/mask/breath(src) + + if ("aid") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/storage/firstaid/emergency(src) + new /obj/item/clothing/mask/breath(src) + + if ("tank") + new /obj/item/tank/internals/oxygen(src) + new /obj/item/clothing/mask/breath(src) + + if ("both") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/breath(src) + + if ("nothing") + // doot + + // teehee + if ("delete") + qdel(src) + +/* + * Fire Closet + */ +/obj/structure/closet/firecloset + name = "fire-safety closet" + desc = "It's a storage unit for fire-fighting supplies." + icon_state = "fire" + +/obj/structure/closet/firecloset/PopulateContents() + ..() + + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/tank/internals/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/obj/structure/closet/firecloset/full/PopulateContents() + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/flashlight(src) + new /obj/item/tank/internals/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/* + * Tool Closet + */ +/obj/structure/closet/toolcloset + name = "tool closet" + desc = "It's a storage unit for tools." + icon_state = "eng" + icon_door = "eng_tool" + +/obj/structure/closet/toolcloset/PopulateContents() + ..() + if(prob(40)) + new /obj/item/clothing/suit/hazardvest(src) + if(prob(70)) + new /obj/item/flashlight(src) + if(prob(70)) + new /obj/item/screwdriver(src) + if(prob(70)) + new /obj/item/wrench(src) + if(prob(70)) + new /obj/item/weldingtool(src) + if(prob(70)) + new /obj/item/crowbar(src) + if(prob(70)) + new /obj/item/wirecutters(src) + if(prob(70)) + new /obj/item/t_scanner(src) + if(prob(20)) + new /obj/item/storage/belt/utility(src) + if(prob(30)) + new /obj/item/stack/cable_coil(src) + if(prob(30)) + new /obj/item/stack/cable_coil(src) + if(prob(30)) + new /obj/item/stack/cable_coil(src) + if(prob(20)) + new /obj/item/multitool(src) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + if(prob(40)) + new /obj/item/clothing/head/hardhat(src) + + +/* + * Radiation Closet + */ +/obj/structure/closet/radiation + name = "radiation suit closet" + desc = "It's a storage unit for rad-protective suits." + icon_state = "eng" + icon_door = "eng_rad" + +/obj/structure/closet/radiation/PopulateContents() + ..() + new /obj/item/geiger_counter(src) + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + +/* + * Bombsuit closet + */ +/obj/structure/closet/bombcloset + name = "\improper EOD closet" + desc = "It's a storage unit for explosion-protective suits." + icon_state = "bomb" + +/obj/structure/closet/bombcloset/PopulateContents() + ..() + new /obj/item/clothing/suit/bomb_suit(src) + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/head/bomb_hood(src) + +/obj/structure/closet/bombcloset/security/PopulateContents() + new /obj/item/clothing/suit/bomb_suit/security(src) + new /obj/item/clothing/under/rank/security/officer(src) + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/clothing/head/bomb_hood/security(src) + +/obj/structure/closet/bombcloset/white/PopulateContents() + new /obj/item/clothing/suit/bomb_suit/white(src) + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/head/bomb_hood/white(src) + +/* + * Ammunition + */ +/obj/structure/closet/ammunitionlocker + name = "ammunition locker" + +/obj/structure/closet/ammunitionlocker/PopulateContents() + ..() + for(var/i in 1 to 8) + new /obj/item/ammo_casing/shotgun/beanbag(src) diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm index bf68ae95f2e..bca315340a4 100644 --- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm +++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm @@ -1,204 +1,204 @@ -/obj/structure/closet/wardrobe - name = "wardrobe" - desc = "It's a storage unit for standard-issue Nanotrasen attire." - icon_door = "blue" - -/obj/structure/closet/wardrobe/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/blue(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/blue(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/brown(src) - return - -/obj/structure/closet/wardrobe/pink - name = "pink wardrobe" - icon_door = "pink" - -/obj/structure/closet/wardrobe/pink/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/pink(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/pink(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/brown(src) - return - -/obj/structure/closet/wardrobe/black - name = "black wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/black/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/black(src) - if(prob(25)) - new /obj/item/clothing/suit/jacket/leather(src) - if(prob(20)) - new /obj/item/clothing/suit/jacket/leather/overcoat(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/that(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/mask/bandana/black(src) - new /obj/item/clothing/mask/bandana/black(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - return - - -/obj/structure/closet/wardrobe/green - name = "green wardrobe" - icon_door = "green" - -/obj/structure/closet/wardrobe/green/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/green(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/green(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/mask/bandana/green(src) - new /obj/item/clothing/mask/bandana/green(src) - return - - -/obj/structure/closet/wardrobe/orange - name = "prison wardrobe" - desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." - icon_door = "orange" - -/obj/structure/closet/wardrobe/orange/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/prisoner(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/prisoner/skirt(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/orange(src) - return - - -/obj/structure/closet/wardrobe/yellow - name = "yellow wardrobe" - icon_door = "yellow" - -/obj/structure/closet/wardrobe/yellow/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/yellow(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/yellow(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/orange(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/mask/bandana/gold(src) - return - - -/obj/structure/closet/wardrobe/white - name = "white wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/white/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/mime(src) - return - -/obj/structure/closet/wardrobe/pjs - name = "pajama wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/pjs/PopulateContents() - new /obj/item/clothing/under/misc/pj/red(src) - new /obj/item/clothing/under/misc/pj/red(src) - new /obj/item/clothing/under/misc/pj/blue(src) - new /obj/item/clothing/under/misc/pj/blue(src) - for(var/i in 1 to 4) - new /obj/item/clothing/shoes/sneakers/white(src) - return - - -/obj/structure/closet/wardrobe/grey - name = "grey wardrobe" - icon_door = "grey" - -/obj/structure/closet/wardrobe/grey/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/grey(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/grey(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/grey(src) - if(prob(50)) - new /obj/item/storage/backpack/duffelbag(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/black(src) - new /obj/item/clothing/mask/bandana/black(src) - if(prob(40)) - new /obj/item/clothing/under/misc/assistantformal(src) - if(prob(40)) - new /obj/item/clothing/under/misc/assistantformal(src) - if(prob(30)) - new /obj/item/clothing/suit/hooded/wintercoat(src) - new /obj/item/clothing/shoes/winterboots(src) - if(prob(30)) - new /obj/item/clothing/accessory/pocketprotector(src) - return - - -/obj/structure/closet/wardrobe/mixed - name = "mixed wardrobe" - icon_door = "mixed" - -/obj/structure/closet/wardrobe/mixed/PopulateContents() - if(prob(40)) - new /obj/item/clothing/suit/jacket(src) - if(prob(40)) - new /obj/item/clothing/suit/jacket(src) - new /obj/item/clothing/under/color/white(src) - new /obj/item/clothing/under/color/jumpskirt/white(src) - new /obj/item/clothing/under/color/blue(src) - new /obj/item/clothing/under/color/jumpskirt/blue(src) - new /obj/item/clothing/under/color/yellow(src) - new /obj/item/clothing/under/color/jumpskirt/yellow(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/color/jumpskirt/green(src) - new /obj/item/clothing/under/color/orange(src) - new /obj/item/clothing/under/color/jumpskirt/orange(src) - new /obj/item/clothing/under/color/pink(src) - new /obj/item/clothing/under/color/jumpskirt/pink(src) - new /obj/item/clothing/under/color/red(src) - new /obj/item/clothing/under/color/jumpskirt/red(src) - new /obj/item/clothing/under/color/darkblue(src) - new /obj/item/clothing/under/color/jumpskirt/darkblue(src) - new /obj/item/clothing/under/color/teal(src) - new /obj/item/clothing/under/color/jumpskirt/teal(src) - new /obj/item/clothing/under/color/lightpurple(src) - new /obj/item/clothing/under/color/jumpskirt/lightpurple(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/color/jumpskirt/green(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/clothing/shoes/sneakers/white(src) - if(prob(30)) - new /obj/item/clothing/suit/hooded/wintercoat(src) - new /obj/item/clothing/shoes/winterboots(src) - return +/obj/structure/closet/wardrobe + name = "wardrobe" + desc = "It's a storage unit for standard-issue Nanotrasen attire." + icon_door = "blue" + +/obj/structure/closet/wardrobe/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/blue(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/blue(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/brown(src) + return + +/obj/structure/closet/wardrobe/pink + name = "pink wardrobe" + icon_door = "pink" + +/obj/structure/closet/wardrobe/pink/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/pink(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/pink(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/brown(src) + return + +/obj/structure/closet/wardrobe/black + name = "black wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/black/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/black(src) + if(prob(25)) + new /obj/item/clothing/suit/jacket/leather(src) + if(prob(20)) + new /obj/item/clothing/suit/jacket/leather/overcoat(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/that(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/mask/bandana/black(src) + new /obj/item/clothing/mask/bandana/black(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + return + + +/obj/structure/closet/wardrobe/green + name = "green wardrobe" + icon_door = "green" + +/obj/structure/closet/wardrobe/green/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/green(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/green(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/mask/bandana/green(src) + new /obj/item/clothing/mask/bandana/green(src) + return + + +/obj/structure/closet/wardrobe/orange + name = "prison wardrobe" + desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." + icon_door = "orange" + +/obj/structure/closet/wardrobe/orange/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/prisoner(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/prisoner/skirt(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/orange(src) + return + + +/obj/structure/closet/wardrobe/yellow + name = "yellow wardrobe" + icon_door = "yellow" + +/obj/structure/closet/wardrobe/yellow/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/yellow(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/yellow(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/orange(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/mask/bandana/gold(src) + return + + +/obj/structure/closet/wardrobe/white + name = "white wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/white/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/mime(src) + return + +/obj/structure/closet/wardrobe/pjs + name = "pajama wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/pjs/PopulateContents() + new /obj/item/clothing/under/misc/pj/red(src) + new /obj/item/clothing/under/misc/pj/red(src) + new /obj/item/clothing/under/misc/pj/blue(src) + new /obj/item/clothing/under/misc/pj/blue(src) + for(var/i in 1 to 4) + new /obj/item/clothing/shoes/sneakers/white(src) + return + + +/obj/structure/closet/wardrobe/grey + name = "grey wardrobe" + icon_door = "grey" + +/obj/structure/closet/wardrobe/grey/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/grey(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/grey(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/grey(src) + if(prob(50)) + new /obj/item/storage/backpack/duffelbag(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/black(src) + new /obj/item/clothing/mask/bandana/black(src) + if(prob(40)) + new /obj/item/clothing/under/misc/assistantformal(src) + if(prob(40)) + new /obj/item/clothing/under/misc/assistantformal(src) + if(prob(30)) + new /obj/item/clothing/suit/hooded/wintercoat(src) + new /obj/item/clothing/shoes/winterboots(src) + if(prob(30)) + new /obj/item/clothing/accessory/pocketprotector(src) + return + + +/obj/structure/closet/wardrobe/mixed + name = "mixed wardrobe" + icon_door = "mixed" + +/obj/structure/closet/wardrobe/mixed/PopulateContents() + if(prob(40)) + new /obj/item/clothing/suit/jacket(src) + if(prob(40)) + new /obj/item/clothing/suit/jacket(src) + new /obj/item/clothing/under/color/white(src) + new /obj/item/clothing/under/color/jumpskirt/white(src) + new /obj/item/clothing/under/color/blue(src) + new /obj/item/clothing/under/color/jumpskirt/blue(src) + new /obj/item/clothing/under/color/yellow(src) + new /obj/item/clothing/under/color/jumpskirt/yellow(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/color/jumpskirt/green(src) + new /obj/item/clothing/under/color/orange(src) + new /obj/item/clothing/under/color/jumpskirt/orange(src) + new /obj/item/clothing/under/color/pink(src) + new /obj/item/clothing/under/color/jumpskirt/pink(src) + new /obj/item/clothing/under/color/red(src) + new /obj/item/clothing/under/color/jumpskirt/red(src) + new /obj/item/clothing/under/color/darkblue(src) + new /obj/item/clothing/under/color/jumpskirt/darkblue(src) + new /obj/item/clothing/under/color/teal(src) + new /obj/item/clothing/under/color/jumpskirt/teal(src) + new /obj/item/clothing/under/color/lightpurple(src) + new /obj/item/clothing/under/color/jumpskirt/lightpurple(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/color/jumpskirt/green(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/clothing/shoes/sneakers/white(src) + if(prob(30)) + new /obj/item/clothing/suit/hooded/wintercoat(src) + new /obj/item/clothing/shoes/winterboots(src) + return diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index 8fea5c986db..119a8084a58 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -1,230 +1,230 @@ -/obj/structure/closet/crate - name = "crate" - desc = "A rectangular steel crate." - icon = 'icons/obj/crates.dmi' - icon_state = "crate" - req_access = null - can_weld_shut = FALSE - horizontal = TRUE - allow_objects = TRUE - allow_dense = TRUE - dense_when_open = TRUE - climbable = TRUE - climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy - climb_stun = 0 //climbing onto crates isn't hard, guys - delivery_icon = "deliverycrate" - open_sound = 'sound/machines/crate_open.ogg' - close_sound = 'sound/machines/crate_close.ogg' - open_sound_volume = 35 - close_sound_volume = 50 - drag_slowdown = 0 - var/obj/item/paper/fluff/jobs/cargo/manifest/manifest - -/obj/structure/closet/crate/Initialize() - . = ..() - if(icon_state == "[initial(icon_state)]open") - opened = TRUE - update_icon() - -/obj/structure/closet/crate/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(!istype(mover, /obj/structure/closet)) - var/obj/structure/closet/crate/locatedcrate = locate(/obj/structure/closet/crate) in get_turf(mover) - if(locatedcrate) //you can walk on it like tables, if you're not in an open crate trying to move to a closed crate - if(opened) //if we're open, allow entering regardless of located crate openness - return TRUE - if(!locatedcrate.opened) //otherwise, if the located crate is closed, allow entering - return TRUE - -/obj/structure/closet/crate/update_icon_state() - icon_state = "[initial(icon_state)][opened ? "open" : ""]" - -/obj/structure/closet/crate/closet_update_overlays(list/new_overlays) - . = new_overlays - if(manifest) - . += "manifest" - -/obj/structure/closet/crate/attack_hand(mob/user) - . = ..() - if(.) - return - if(manifest) - tear_manifest(user) - -/obj/structure/closet/crate/open(mob/living/user, force = FALSE) - . = ..() - if(. && manifest) - to_chat(user, "The manifest is torn off [src].") - playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) - manifest.forceMove(get_turf(src)) - manifest = null - update_icon() - -/obj/structure/closet/crate/proc/tear_manifest(mob/user) - to_chat(user, "You tear the manifest off of [src].") - playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) - - manifest.forceMove(loc) - if(ishuman(user)) - user.put_in_hands(manifest) - manifest = null - update_icon() - -/obj/structure/closet/crate/coffin - name = "coffin" - desc = "It's a burial receptacle for the dearly departed." - icon_state = "coffin" - resistance_flags = FLAMMABLE - max_integrity = 70 - material_drop = /obj/item/stack/sheet/mineral/wood - material_drop_amount = 5 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/crate/internals - desc = "An internals crate." - name = "internals crate" - icon_state = "o2crate" - -/obj/structure/closet/crate/trashcart //please make this a generic cart path later after things calm down a little - desc = "A heavy, metal trashcart with wheels." - name = "trash cart" - icon_state = "trashcart" - -/obj/structure/closet/crate/trashcart/Moved() - . = ..() - if(has_gravity()) - playsound(src, 'sound/effects/roll.ogg', 100, TRUE) - -/obj/structure/closet/crate/trashcart/laundry - name = "laundry cart" - desc = "A large cart for hauling around large amounts of laundry." - icon_state = "laundry" - -/obj/structure/closet/crate/medical - desc = "A medical crate." - name = "medical crate" - icon_state = "medicalcrate" - -/obj/structure/closet/crate/freezer - desc = "A freezer." - name = "freezer" - icon_state = "freezer" - -//Snowflake organ freezer code -//Order is important, since we check source, we need to do the check whenever we have all the organs in the crate - -/obj/structure/closet/crate/freezer/open(mob/living/user, force = FALSE) - recursive_organ_check(src) - ..() - -/obj/structure/closet/crate/freezer/close() - ..() - recursive_organ_check(src) - -/obj/structure/closet/crate/freezer/Destroy() - recursive_organ_check(src) - ..() - -/obj/structure/closet/crate/freezer/Initialize() - . = ..() - recursive_organ_check(src) - - - -/obj/structure/closet/crate/freezer/blood - name = "blood freezer" - desc = "A freezer containing packs of blood." - -/obj/structure/closet/crate/freezer/blood/PopulateContents() - . = ..() - new /obj/item/reagent_containers/blood(src) - new /obj/item/reagent_containers/blood(src) - new /obj/item/reagent_containers/blood/a_minus(src) - new /obj/item/reagent_containers/blood/b_minus(src) - new /obj/item/reagent_containers/blood/b_plus(src) - new /obj/item/reagent_containers/blood/o_minus(src) - new /obj/item/reagent_containers/blood/o_plus(src) - new /obj/item/reagent_containers/blood/lizard(src) - new /obj/item/reagent_containers/blood/ethereal(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/blood/random(src) - -/obj/structure/closet/crate/freezer/surplus_limbs - name = "surplus prosthetic limbs" - desc = "A crate containing an assortment of cheap prosthetic limbs." - -/obj/structure/closet/crate/freezer/surplus_limbs/PopulateContents() - . = ..() - new /obj/item/bodypart/l_arm/robot/surplus(src) - new /obj/item/bodypart/l_arm/robot/surplus(src) - new /obj/item/bodypart/r_arm/robot/surplus(src) - new /obj/item/bodypart/r_arm/robot/surplus(src) - new /obj/item/bodypart/l_leg/robot/surplus(src) - new /obj/item/bodypart/l_leg/robot/surplus(src) - new /obj/item/bodypart/r_leg/robot/surplus(src) - new /obj/item/bodypart/r_leg/robot/surplus(src) - -/obj/structure/closet/crate/radiation - desc = "A crate with a radiation sign on it." - name = "radiation crate" - icon_state = "radiation" - -/obj/structure/closet/crate/hydroponics - name = "hydroponics crate" - desc = "All you need to destroy those pesky weeds and pests." - icon_state = "hydrocrate" - -/obj/structure/closet/crate/engineering - name = "engineering crate" - icon_state = "engi_crate" - -/obj/structure/closet/crate/engineering/electrical - icon_state = "engi_e_crate" - -/obj/structure/closet/crate/rcd - desc = "A crate for the storage of an RCD." - name = "\improper RCD crate" - icon_state = "engi_crate" - -/obj/structure/closet/crate/rcd/PopulateContents() - ..() - for(var/i in 1 to 4) - new /obj/item/rcd_ammo(src) - new /obj/item/construction/rcd(src) - -/obj/structure/closet/crate/science - name = "science crate" - desc = "A science crate." - icon_state = "scicrate" - -/obj/structure/closet/crate/solarpanel_small - name = "budget solar panel crate" - icon_state = "engi_e_crate" - -/obj/structure/closet/crate/solarpanel_small/PopulateContents() - ..() - for(var/i in 1 to 13) - new /obj/item/solar_assembly(src) - new /obj/item/circuitboard/computer/solar_control(src) - new /obj/item/paper/guides/jobs/engi/solars(src) - new /obj/item/electronics/tracker(src) - -/obj/structure/closet/crate/goldcrate - name = "gold crate" - -/obj/structure/closet/crate/goldcrate/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/stack/sheet/mineral/gold(src, 1, FALSE) - new /obj/item/storage/belt/champion(src) - -/obj/structure/closet/crate/silvercrate - name = "silver crate" - -/obj/structure/closet/crate/silvercrate/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/coin/silver(src) +/obj/structure/closet/crate + name = "crate" + desc = "A rectangular steel crate." + icon = 'icons/obj/crates.dmi' + icon_state = "crate" + req_access = null + can_weld_shut = FALSE + horizontal = TRUE + allow_objects = TRUE + allow_dense = TRUE + dense_when_open = TRUE + climbable = TRUE + climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy + climb_stun = 0 //climbing onto crates isn't hard, guys + delivery_icon = "deliverycrate" + open_sound = 'sound/machines/crate_open.ogg' + close_sound = 'sound/machines/crate_close.ogg' + open_sound_volume = 35 + close_sound_volume = 50 + drag_slowdown = 0 + var/obj/item/paper/fluff/jobs/cargo/manifest/manifest + +/obj/structure/closet/crate/Initialize() + . = ..() + if(icon_state == "[initial(icon_state)]open") + opened = TRUE + update_icon() + +/obj/structure/closet/crate/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(!istype(mover, /obj/structure/closet)) + var/obj/structure/closet/crate/locatedcrate = locate(/obj/structure/closet/crate) in get_turf(mover) + if(locatedcrate) //you can walk on it like tables, if you're not in an open crate trying to move to a closed crate + if(opened) //if we're open, allow entering regardless of located crate openness + return TRUE + if(!locatedcrate.opened) //otherwise, if the located crate is closed, allow entering + return TRUE + +/obj/structure/closet/crate/update_icon_state() + icon_state = "[initial(icon_state)][opened ? "open" : ""]" + +/obj/structure/closet/crate/closet_update_overlays(list/new_overlays) + . = new_overlays + if(manifest) + . += "manifest" + +/obj/structure/closet/crate/attack_hand(mob/user) + . = ..() + if(.) + return + if(manifest) + tear_manifest(user) + +/obj/structure/closet/crate/open(mob/living/user, force = FALSE) + . = ..() + if(. && manifest) + to_chat(user, "The manifest is torn off [src].") + playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) + manifest.forceMove(get_turf(src)) + manifest = null + update_icon() + +/obj/structure/closet/crate/proc/tear_manifest(mob/user) + to_chat(user, "You tear the manifest off of [src].") + playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) + + manifest.forceMove(loc) + if(ishuman(user)) + user.put_in_hands(manifest) + manifest = null + update_icon() + +/obj/structure/closet/crate/coffin + name = "coffin" + desc = "It's a burial receptacle for the dearly departed." + icon_state = "coffin" + resistance_flags = FLAMMABLE + max_integrity = 70 + material_drop = /obj/item/stack/sheet/mineral/wood + material_drop_amount = 5 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/crate/internals + desc = "An internals crate." + name = "internals crate" + icon_state = "o2crate" + +/obj/structure/closet/crate/trashcart //please make this a generic cart path later after things calm down a little + desc = "A heavy, metal trashcart with wheels." + name = "trash cart" + icon_state = "trashcart" + +/obj/structure/closet/crate/trashcart/Moved() + . = ..() + if(has_gravity()) + playsound(src, 'sound/effects/roll.ogg', 100, TRUE) + +/obj/structure/closet/crate/trashcart/laundry + name = "laundry cart" + desc = "A large cart for hauling around large amounts of laundry." + icon_state = "laundry" + +/obj/structure/closet/crate/medical + desc = "A medical crate." + name = "medical crate" + icon_state = "medicalcrate" + +/obj/structure/closet/crate/freezer + desc = "A freezer." + name = "freezer" + icon_state = "freezer" + +//Snowflake organ freezer code +//Order is important, since we check source, we need to do the check whenever we have all the organs in the crate + +/obj/structure/closet/crate/freezer/open(mob/living/user, force = FALSE) + recursive_organ_check(src) + ..() + +/obj/structure/closet/crate/freezer/close() + ..() + recursive_organ_check(src) + +/obj/structure/closet/crate/freezer/Destroy() + recursive_organ_check(src) + ..() + +/obj/structure/closet/crate/freezer/Initialize() + . = ..() + recursive_organ_check(src) + + + +/obj/structure/closet/crate/freezer/blood + name = "blood freezer" + desc = "A freezer containing packs of blood." + +/obj/structure/closet/crate/freezer/blood/PopulateContents() + . = ..() + new /obj/item/reagent_containers/blood(src) + new /obj/item/reagent_containers/blood(src) + new /obj/item/reagent_containers/blood/a_minus(src) + new /obj/item/reagent_containers/blood/b_minus(src) + new /obj/item/reagent_containers/blood/b_plus(src) + new /obj/item/reagent_containers/blood/o_minus(src) + new /obj/item/reagent_containers/blood/o_plus(src) + new /obj/item/reagent_containers/blood/lizard(src) + new /obj/item/reagent_containers/blood/ethereal(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/blood/random(src) + +/obj/structure/closet/crate/freezer/surplus_limbs + name = "surplus prosthetic limbs" + desc = "A crate containing an assortment of cheap prosthetic limbs." + +/obj/structure/closet/crate/freezer/surplus_limbs/PopulateContents() + . = ..() + new /obj/item/bodypart/l_arm/robot/surplus(src) + new /obj/item/bodypart/l_arm/robot/surplus(src) + new /obj/item/bodypart/r_arm/robot/surplus(src) + new /obj/item/bodypart/r_arm/robot/surplus(src) + new /obj/item/bodypart/l_leg/robot/surplus(src) + new /obj/item/bodypart/l_leg/robot/surplus(src) + new /obj/item/bodypart/r_leg/robot/surplus(src) + new /obj/item/bodypart/r_leg/robot/surplus(src) + +/obj/structure/closet/crate/radiation + desc = "A crate with a radiation sign on it." + name = "radiation crate" + icon_state = "radiation" + +/obj/structure/closet/crate/hydroponics + name = "hydroponics crate" + desc = "All you need to destroy those pesky weeds and pests." + icon_state = "hydrocrate" + +/obj/structure/closet/crate/engineering + name = "engineering crate" + icon_state = "engi_crate" + +/obj/structure/closet/crate/engineering/electrical + icon_state = "engi_e_crate" + +/obj/structure/closet/crate/rcd + desc = "A crate for the storage of an RCD." + name = "\improper RCD crate" + icon_state = "engi_crate" + +/obj/structure/closet/crate/rcd/PopulateContents() + ..() + for(var/i in 1 to 4) + new /obj/item/rcd_ammo(src) + new /obj/item/construction/rcd(src) + +/obj/structure/closet/crate/science + name = "science crate" + desc = "A science crate." + icon_state = "scicrate" + +/obj/structure/closet/crate/solarpanel_small + name = "budget solar panel crate" + icon_state = "engi_e_crate" + +/obj/structure/closet/crate/solarpanel_small/PopulateContents() + ..() + for(var/i in 1 to 13) + new /obj/item/solar_assembly(src) + new /obj/item/circuitboard/computer/solar_control(src) + new /obj/item/paper/guides/jobs/engi/solars(src) + new /obj/item/electronics/tracker(src) + +/obj/structure/closet/crate/goldcrate + name = "gold crate" + +/obj/structure/closet/crate/goldcrate/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/stack/sheet/mineral/gold(src, 1, FALSE) + new /obj/item/storage/belt/champion(src) + +/obj/structure/closet/crate/silvercrate + name = "silver crate" + +/obj/structure/closet/crate/silvercrate/PopulateContents() + ..() + for(var/i in 1 to 5) + new /obj/item/coin/silver(src) diff --git a/code/game/objects/structures/crates_lockers/crates/bins.dm b/code/game/objects/structures/crates_lockers/crates/bins.dm index efc7fb3a097..6b8e3ac6586 100644 --- a/code/game/objects/structures/crates_lockers/crates/bins.dm +++ b/code/game/objects/structures/crates_lockers/crates/bins.dm @@ -1,43 +1,43 @@ -/obj/structure/closet/crate/bin - desc = "A trash bin, place your trash here for the janitor to collect." - name = "trash bin" - icon_state = "largebins" - open_sound = 'sound/effects/bin_open.ogg' - close_sound = 'sound/effects/bin_close.ogg' - anchored = TRUE - horizontal = FALSE - delivery_icon = null - -/obj/structure/closet/crate/bin/Initialize() - . = ..() - update_icon() - -/obj/structure/closet/crate/bin/update_overlays() - . = ..() - if(contents.len == 0) - . += "largebing" - else if(contents.len >= storage_capacity) - . += "largebinr" - else - . += "largebino" - -/obj/structure/closet/crate/bin/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/storage/bag/trash)) - var/obj/item/storage/bag/trash/T = W - to_chat(user, "You fill the bag.") - for(var/obj/item/O in src) - SEND_SIGNAL(T, COMSIG_TRY_STORAGE_INSERT, O, user, TRUE) - T.update_icon() - do_animate() - return TRUE - else - return ..() - -/obj/structure/closet/crate/bin/proc/do_animate() - playsound(loc, open_sound, 15, TRUE, -3) - flick("animate_largebins", src) - addtimer(CALLBACK(src, .proc/do_close), 13) - -/obj/structure/closet/crate/bin/proc/do_close() - playsound(loc, close_sound, 15, TRUE, -3) - update_icon() +/obj/structure/closet/crate/bin + desc = "A trash bin, place your trash here for the janitor to collect." + name = "trash bin" + icon_state = "largebins" + open_sound = 'sound/effects/bin_open.ogg' + close_sound = 'sound/effects/bin_close.ogg' + anchored = TRUE + horizontal = FALSE + delivery_icon = null + +/obj/structure/closet/crate/bin/Initialize() + . = ..() + update_icon() + +/obj/structure/closet/crate/bin/update_overlays() + . = ..() + if(contents.len == 0) + . += "largebing" + else if(contents.len >= storage_capacity) + . += "largebinr" + else + . += "largebino" + +/obj/structure/closet/crate/bin/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/storage/bag/trash)) + var/obj/item/storage/bag/trash/T = W + to_chat(user, "You fill the bag.") + for(var/obj/item/O in src) + SEND_SIGNAL(T, COMSIG_TRY_STORAGE_INSERT, O, user, TRUE) + T.update_icon() + do_animate() + return TRUE + else + return ..() + +/obj/structure/closet/crate/bin/proc/do_animate() + playsound(loc, open_sound, 15, TRUE, -3) + flick("animate_largebins", src) + addtimer(CALLBACK(src, .proc/do_close), 13) + +/obj/structure/closet/crate/bin/proc/do_close() + playsound(loc, close_sound, 15, TRUE, -3) + update_icon() diff --git a/code/game/objects/structures/crates_lockers/crates/critter.dm b/code/game/objects/structures/crates_lockers/crates/critter.dm index 952bb1a5d37..78bb1b06234 100644 --- a/code/game/objects/structures/crates_lockers/crates/critter.dm +++ b/code/game/objects/structures/crates_lockers/crates/critter.dm @@ -1,51 +1,51 @@ -/obj/structure/closet/crate/critter - name = "critter crate" - desc = "A crate designed for safe transport of animals. It has an oxygen tank for safe transport in space." - icon_state = "crittercrate" - horizontal = FALSE - allow_objects = FALSE - breakout_time = 600 - material_drop = /obj/item/stack/sheet/mineral/wood - material_drop_amount = 4 - delivery_icon = "deliverybox" - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - var/obj/item/tank/internals/emergency_oxygen/tank - -/obj/structure/closet/crate/critter/Initialize() - . = ..() - tank = new - -/obj/structure/closet/crate/critter/Destroy() - var/turf/T = get_turf(src) - if(tank) - tank.forceMove(T) - tank = null - - return ..() - -/obj/structure/closet/crate/critter/update_icon_state() - return - -/obj/structure/closet/crate/critter/update_overlays() - . = ..() - if(opened) - . += "crittercrate_door_open" - else - . += "crittercrate_door" - if(manifest) - . += "manifest" - -/obj/structure/closet/crate/critter/return_air() - if(tank) - return tank.air_contents - else - return loc.return_air() - -/obj/structure/closet/crate/critter/return_analyzable_air() - if(tank) - return tank.return_analyzable_air() - else - return null +/obj/structure/closet/crate/critter + name = "critter crate" + desc = "A crate designed for safe transport of animals. It has an oxygen tank for safe transport in space." + icon_state = "crittercrate" + horizontal = FALSE + allow_objects = FALSE + breakout_time = 600 + material_drop = /obj/item/stack/sheet/mineral/wood + material_drop_amount = 4 + delivery_icon = "deliverybox" + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + var/obj/item/tank/internals/emergency_oxygen/tank + +/obj/structure/closet/crate/critter/Initialize() + . = ..() + tank = new + +/obj/structure/closet/crate/critter/Destroy() + var/turf/T = get_turf(src) + if(tank) + tank.forceMove(T) + tank = null + + return ..() + +/obj/structure/closet/crate/critter/update_icon_state() + return + +/obj/structure/closet/crate/critter/update_overlays() + . = ..() + if(opened) + . += "crittercrate_door_open" + else + . += "crittercrate_door" + if(manifest) + . += "manifest" + +/obj/structure/closet/crate/critter/return_air() + if(tank) + return tank.air_contents + else + return loc.return_air() + +/obj/structure/closet/crate/critter/return_analyzable_air() + if(tank) + return tank.return_analyzable_air() + else + return null diff --git a/code/game/objects/structures/crates_lockers/crates/large.dm b/code/game/objects/structures/crates_lockers/crates/large.dm index 04a6aa53dc8..bd21cc5f0bb 100644 --- a/code/game/objects/structures/crates_lockers/crates/large.dm +++ b/code/game/objects/structures/crates_lockers/crates/large.dm @@ -1,47 +1,47 @@ -/obj/structure/closet/crate/large - name = "large crate" - desc = "A hefty wooden crate. You'll need a crowbar to get it open." - icon_state = "largecrate" - density = TRUE - material_drop = /obj/item/stack/sheet/mineral/wood - material_drop_amount = 4 - delivery_icon = "deliverybox" - integrity_failure = 0 //Makes the crate break when integrity reaches 0, instead of opening and becoming an invisible sprite. - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/crate/large/attack_hand(mob/user) - add_fingerprint(user) - if(manifest) - tear_manifest(user) - else - to_chat(user, "You need a crowbar to pry this open!") - -/obj/structure/closet/crate/large/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_CROWBAR) - if(manifest) - tear_manifest(user) - - user.visible_message("[user] pries \the [src] open.", \ - "You pry open \the [src].", \ - "You hear splitting wood.") - playsound(src.loc, 'sound/weapons/slashmiss.ogg', 75, TRUE) - - var/turf/T = get_turf(src) - for(var/i in 1 to material_drop_amount) - new material_drop(src) - for(var/atom/movable/AM in contents) - AM.forceMove(T) - - qdel(src) - - else - if(user.a_intent == INTENT_HARM) //Only return ..() if intent is harm, otherwise return 0 or just end it. - return ..() //Stops it from opening and turning invisible when items are used on it. - - else - to_chat(user, "You need a crowbar to pry this open!") - return FALSE //Just stop. Do nothing. Don't turn into an invisible sprite. Don't open like a locker. - //The large crate has no non-attack interactions other than the crowbar, anyway. +/obj/structure/closet/crate/large + name = "large crate" + desc = "A hefty wooden crate. You'll need a crowbar to get it open." + icon_state = "largecrate" + density = TRUE + material_drop = /obj/item/stack/sheet/mineral/wood + material_drop_amount = 4 + delivery_icon = "deliverybox" + integrity_failure = 0 //Makes the crate break when integrity reaches 0, instead of opening and becoming an invisible sprite. + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/crate/large/attack_hand(mob/user) + add_fingerprint(user) + if(manifest) + tear_manifest(user) + else + to_chat(user, "You need a crowbar to pry this open!") + +/obj/structure/closet/crate/large/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_CROWBAR) + if(manifest) + tear_manifest(user) + + user.visible_message("[user] pries \the [src] open.", \ + "You pry open \the [src].", \ + "You hear splitting wood.") + playsound(src.loc, 'sound/weapons/slashmiss.ogg', 75, TRUE) + + var/turf/T = get_turf(src) + for(var/i in 1 to material_drop_amount) + new material_drop(src) + for(var/atom/movable/AM in contents) + AM.forceMove(T) + + qdel(src) + + else + if(user.a_intent == INTENT_HARM) //Only return ..() if intent is harm, otherwise return 0 or just end it. + return ..() //Stops it from opening and turning invisible when items are used on it. + + else + to_chat(user, "You need a crowbar to pry this open!") + return FALSE //Just stop. Do nothing. Don't turn into an invisible sprite. Don't open like a locker. + //The large crate has no non-attack interactions other than the crowbar, anyway. diff --git a/code/game/objects/structures/crates_lockers/crates/secure.dm b/code/game/objects/structures/crates_lockers/crates/secure.dm index baf60779997..f83d99672d1 100644 --- a/code/game/objects/structures/crates_lockers/crates/secure.dm +++ b/code/game/objects/structures/crates_lockers/crates/secure.dm @@ -1,104 +1,104 @@ -/obj/structure/closet/crate/secure - desc = "A secure crate." - name = "secure crate" - icon_state = "securecrate" - secure = TRUE - locked = TRUE - max_integrity = 500 - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - var/tamperproof = 0 - damage_deflection = 25 - -/obj/structure/closet/crate/secure/update_overlays() - . = ..() - if(broken) - . += "securecrateemag" - else if(locked) - . += "securecrater" - else - . += "securecrateg" - -/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION) - boom() - else - return ..() - - -/obj/structure/closet/crate/secure/proc/boom(mob/user) - if(user) - to_chat(user, "The crate's anti-tamper system activates!") - log_bomber(user, "has detonated a", src) - for(var/atom/movable/AM in src) - qdel(AM) - explosion(get_turf(src), 0, 1, 5, 5) - qdel(src) - -/obj/structure/closet/crate/secure/weapon - desc = "A secure weapons crate." - name = "weapons crate" - icon_state = "weaponcrate" - -/obj/structure/closet/crate/secure/plasma - desc = "A secure plasma crate." - name = "plasma crate" - icon_state = "plasmacrate" - -/obj/structure/closet/crate/secure/gear - desc = "A secure gear crate." - name = "gear crate" - icon_state = "secgearcrate" - -/obj/structure/closet/crate/secure/hydroponics - desc = "A crate with a lock on it, painted in the scheme of the station's botanists." - name = "secure hydroponics crate" - icon_state = "hydrosecurecrate" - -/obj/structure/closet/crate/secure/engineering - desc = "A crate with a lock on it, painted in the scheme of the station's engineers." - name = "secure engineering crate" - icon_state = "engi_secure_crate" - -/obj/structure/closet/crate/secure/science - name = "secure science crate" - desc = "A crate with a lock on it, painted in the scheme of the station's scientists." - icon_state = "scisecurecrate" - -/obj/structure/closet/crate/secure/owned - name = "private crate" - desc = "A crate cover designed to only open for who purchased its contents." - icon_state = "privatecrate" - var/datum/bank_account/buyer_account - var/privacy_lock = TRUE - -/obj/structure/closet/crate/secure/owned/examine(mob/user) - . = ..() - . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID." - -/obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account) - . = ..() - buyer_account = _buyer_account - -/obj/structure/closet/crate/secure/owned/togglelock(mob/living/user, silent) - if(privacy_lock) - if(!broken) - var/obj/item/card/id/id_card = user.get_idcard(TRUE) - if(id_card) - if(id_card.registered_account) - if(id_card.registered_account == buyer_account) - if(iscarbon(user)) - add_fingerprint(user) - locked = !locked - user.visible_message("[user] unlocks [src]'s privacy lock.", - "You unlock [src]'s privacy lock.") - privacy_lock = FALSE - update_icon() - else if(!silent) - to_chat(user, "Bank account does not match with buyer!") - else if(!silent) - to_chat(user, "No linked bank account detected!") - else if(!silent) - to_chat(user, "No ID detected!") - else if(!silent) - to_chat(user, "[src] is broken!") - else ..() +/obj/structure/closet/crate/secure + desc = "A secure crate." + name = "secure crate" + icon_state = "securecrate" + secure = TRUE + locked = TRUE + max_integrity = 500 + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + var/tamperproof = 0 + damage_deflection = 25 + +/obj/structure/closet/crate/secure/update_overlays() + . = ..() + if(broken) + . += "securecrateemag" + else if(locked) + . += "securecrater" + else + . += "securecrateg" + +/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION) + boom() + else + return ..() + + +/obj/structure/closet/crate/secure/proc/boom(mob/user) + if(user) + to_chat(user, "The crate's anti-tamper system activates!") + log_bomber(user, "has detonated a", src) + for(var/atom/movable/AM in src) + qdel(AM) + explosion(get_turf(src), 0, 1, 5, 5) + qdel(src) + +/obj/structure/closet/crate/secure/weapon + desc = "A secure weapons crate." + name = "weapons crate" + icon_state = "weaponcrate" + +/obj/structure/closet/crate/secure/plasma + desc = "A secure plasma crate." + name = "plasma crate" + icon_state = "plasmacrate" + +/obj/structure/closet/crate/secure/gear + desc = "A secure gear crate." + name = "gear crate" + icon_state = "secgearcrate" + +/obj/structure/closet/crate/secure/hydroponics + desc = "A crate with a lock on it, painted in the scheme of the station's botanists." + name = "secure hydroponics crate" + icon_state = "hydrosecurecrate" + +/obj/structure/closet/crate/secure/engineering + desc = "A crate with a lock on it, painted in the scheme of the station's engineers." + name = "secure engineering crate" + icon_state = "engi_secure_crate" + +/obj/structure/closet/crate/secure/science + name = "secure science crate" + desc = "A crate with a lock on it, painted in the scheme of the station's scientists." + icon_state = "scisecurecrate" + +/obj/structure/closet/crate/secure/owned + name = "private crate" + desc = "A crate cover designed to only open for who purchased its contents." + icon_state = "privatecrate" + var/datum/bank_account/buyer_account + var/privacy_lock = TRUE + +/obj/structure/closet/crate/secure/owned/examine(mob/user) + . = ..() + . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID." + +/obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account) + . = ..() + buyer_account = _buyer_account + +/obj/structure/closet/crate/secure/owned/togglelock(mob/living/user, silent) + if(privacy_lock) + if(!broken) + var/obj/item/card/id/id_card = user.get_idcard(TRUE) + if(id_card) + if(id_card.registered_account) + if(id_card.registered_account == buyer_account) + if(iscarbon(user)) + add_fingerprint(user) + locked = !locked + user.visible_message("[user] unlocks [src]'s privacy lock.", + "You unlock [src]'s privacy lock.") + privacy_lock = FALSE + update_icon() + else if(!silent) + to_chat(user, "Bank account does not match with buyer!") + else if(!silent) + to_chat(user, "No linked bank account detected!") + else if(!silent) + to_chat(user, "No ID detected!") + else if(!silent) + to_chat(user, "[src] is broken!") + else ..() diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index fd8a42329d4..53319850cf8 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -1,573 +1,573 @@ -/obj/structure/displaycase - name = "display case" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "glassbox0" - desc = "A display case for prized possessions." - density = TRUE - anchored = TRUE - resistance_flags = ACID_PROOF - armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) - max_integrity = 200 - integrity_failure = 0.25 - var/obj/item/showpiece = null - var/obj/item/showpiece_type = null //This allows for showpieces that can only hold items if they're the same istype as this. - var/alert = TRUE - var/open = FALSE - var/openable = TRUE - var/obj/item/electronics/airlock/electronics - var/start_showpiece_type = null //add type for items on display - var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk") - var/trophy_message = "" - var/glass_fix = TRUE - -/obj/structure/displaycase/Initialize() - . = ..() - if(start_showpieces.len && !start_showpiece_type) - var/list/showpiece_entry = pick(start_showpieces) - if (showpiece_entry && showpiece_entry["type"]) - start_showpiece_type = showpiece_entry["type"] - if (showpiece_entry["trophy_message"]) - trophy_message = showpiece_entry["trophy_message"] - if(start_showpiece_type) - showpiece = new start_showpiece_type (src) - update_icon() - -/obj/structure/displaycase/Destroy() - if(electronics) - QDEL_NULL(electronics) - if(showpiece) - QDEL_NULL(showpiece) - return ..() - -/obj/structure/displaycase/examine(mob/user) - . = ..() - if(alert) - . += "Hooked up with an anti-theft system." - if(showpiece) - . += "There's [showpiece] inside." - if(trophy_message) - . += "The plaque reads:\n [trophy_message]" - - -/obj/structure/displaycase/proc/dump() - if (showpiece) - showpiece.forceMove(loc) - showpiece = null - -/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/structure/displaycase/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - dump() - if(!disassembled) - new /obj/item/shard( src.loc ) - trigger_alarm() - qdel(src) - -/obj/structure/displaycase/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - density = FALSE - broken = 1 - new /obj/item/shard( src.loc ) - playsound(src, "shatter", 70, TRUE) - update_icon() - trigger_alarm() - -/obj/structure/displaycase/proc/trigger_alarm() - //Activate Anti-theft - if(alert) - var/area/alarmed = get_area(src) - alarmed.burglaralert(src) - playsound(src, 'sound/effects/alert.ogg', 50, TRUE) - -/obj/structure/displaycase/update_icon() - var/icon/I - if(open) - I = icon('icons/obj/stationobjs.dmi',"glassbox_open") - else - I = icon('icons/obj/stationobjs.dmi',"glassbox0") - if(broken) - I = icon('icons/obj/stationobjs.dmi',"glassboxb0") - if(showpiece) - var/icon/S = getFlatIcon(showpiece) - S.Scale(17,17) - I.Blend(S,ICON_UNDERLAY,8,8) - src.icon = I - return - -/obj/structure/displaycase/attackby(obj/item/W, mob/user, params) - if(W.GetID() && !broken && openable) - if(allowed(user)) - to_chat(user, "You [open ? "close":"open"] [src].") - toggle_lock(user) - else - to_chat(user, "Access denied.") - else if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken) - if(obj_integrity < max_integrity) - if(!W.tool_start_check(user, amount=5)) - return - - to_chat(user, "You begin repairing [src]...") - if(W.use_tool(src, user, 40, amount=5, volume=50)) - obj_integrity = max_integrity - update_icon() - to_chat(user, "You repair [src].") - else - to_chat(user, "[src] is already in good condition!") - return - else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases - if(broken) - if(showpiece) - to_chat(user, "Remove the displayed object first!") - else - to_chat(user, "You remove the destroyed case.") - qdel(src) - else - to_chat(user, "You start to [open ? "close":"open"] [src]...") - if(W.use_tool(src, user, 20)) - to_chat(user, "You [open ? "close":"open"] [src].") - toggle_lock(user) - else if(open && !showpiece) - if(showpiece_type && !istype(W, showpiece_type)) - to_chat(user, "This doesn't belong in this kind of display.") - return TRUE - if(user.transferItemToLoc(W, src)) - showpiece = W - to_chat(user, "You put [W] on display.") - update_icon() - else if(glass_fix && broken && istype(W, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = W - if(G.get_amount() < 2) - to_chat(user, "You need two glass sheets to fix the case!") - return - to_chat(user, "You start fixing [src]...") - if(do_after(user, 20, target = src)) - G.use(2) - broken = 0 - obj_integrity = max_integrity - update_icon() - else - return ..() - -/obj/structure/displaycase/proc/toggle_lock(mob/user) - open = !open - update_icon() - -/obj/structure/displaycase/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/displaycase/attack_hand(mob/user) - . = ..() - if(.) - return - user.changeNext_move(CLICK_CD_MELEE) - if (showpiece && (broken || open)) - to_chat(user, "You deactivate the hover field built into the case.") - log_combat(user, src, "deactivates the hover field of") - dump() - src.add_fingerprint(user) - update_icon() - return - else - //prevents remote "kicks" with TK - if (!Adjacent(user)) - return - if (user.a_intent == INTENT_HELP) - user.examinate(src) - return - user.visible_message("[user] kicks the display case.", null, null, COMBAT_MESSAGE_RANGE) - log_combat(user, src, "kicks") - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - take_damage(2) - -/obj/structure/displaycase_chassis - anchored = TRUE - density = FALSE - name = "display case chassis" - desc = "The wooden base of a display case." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "glassbox_chassis" - var/obj/item/electronics/airlock/electronics - - -/obj/structure/displaycase_chassis/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) //The player can only deconstruct the wooden frame - to_chat(user, "You start disassembling [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 30)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - new /obj/item/stack/sheet/mineral/wood(get_turf(src), 5) - qdel(src) - - else if(istype(I, /obj/item/electronics/airlock)) - to_chat(user, "You start installing the electronics into [src]...") - I.play_tool_sound(src) - if(do_after(user, 30, target = src) && user.transferItemToLoc(I,src)) - electronics = I - to_chat(user, "You install the airlock electronics.") - - else if(istype(I, /obj/item/stock_parts/card_reader)) - var/obj/item/stock_parts/card_reader/C = I - to_chat(user, "You start adding [C] to [src]...") - if(do_after(user, 20, target = src)) - var/obj/structure/displaycase/forsale/sale = new(src.loc) - if(electronics) - electronics.forceMove(sale) - sale.electronics = electronics - if(electronics.one_access) - sale.req_one_access = electronics.accesses - else - sale.req_access = electronics.accesses - qdel(src) - qdel(C) - - else if(istype(I, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = I - if(G.get_amount() < 10) - to_chat(user, "You need ten glass sheets to do this!") - return - to_chat(user, "You start adding [G] to [src]...") - if(do_after(user, 20, target = src)) - G.use(10) - var/obj/structure/displaycase/noalert/display = new(src.loc) - if(electronics) - electronics.forceMove(display) - display.electronics = electronics - if(electronics.one_access) - display.req_one_access = electronics.accesses - else - display.req_access = electronics.accesses - qdel(src) - else - return ..() - -//The lab cage and captain's display case do not spawn with electronics, which is why req_access is needed. -/obj/structure/displaycase/captain - start_showpiece_type = /obj/item/gun/energy/laser/captain - req_access = list(ACCESS_CENT_SPECOPS) //this was intentional, presumably to make it slightly harder for caps to grab their gun roundstart - -/obj/structure/displaycase/labcage - name = "lab cage" - desc = "A glass lab container for storing interesting creatures." - start_showpiece_type = /obj/item/clothing/mask/facehugger/lamarr - req_access = list(ACCESS_RD) - -/obj/structure/displaycase/noalert - alert = FALSE - -/obj/structure/displaycase/trophy - name = "trophy display case" - desc = "Store your trophies of accomplishment in here, and they will stay forever." - var/placer_key = "" - var/added_roundstart = TRUE - var/is_locked = TRUE - integrity_failure = 0 - openable = FALSE - -/obj/structure/displaycase/trophy/Initialize() - . = ..() - GLOB.trophy_cases += src - -/obj/structure/displaycase/trophy/Destroy() - GLOB.trophy_cases -= src - return ..() - -/obj/structure/displaycase/trophy/attackby(obj/item/W, mob/user, params) - - if(!user.Adjacent(src)) //no TK museology - return - if(user.a_intent == INTENT_HARM) - return ..() - - if(user.is_holding_item_of_type(/obj/item/key/displaycase)) - if(added_roundstart) - is_locked = !is_locked - to_chat(user, "You [!is_locked ? "un" : ""]lock the case.") - else - to_chat(user, "The lock is stuck shut!") - return - - if(is_locked) - to_chat(user, "The case is shut tight with an old-fashioned physical lock. Maybe you should ask the curator for the key?") - return - - if(!added_roundstart) - to_chat(user, "You've already put something new in this case!") - return - - if(is_type_in_typecache(W, GLOB.blacklisted_cargo_types)) - to_chat(user, "The case rejects the [W]!") - return - - for(var/a in W.GetAllContents()) - if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types)) - to_chat(user, "The case rejects the [W]!") - return - - if(user.transferItemToLoc(W, src)) - - if(showpiece) - to_chat(user, "You press a button, and [showpiece] descends into the floor of the case.") - QDEL_NULL(showpiece) - - to_chat(user, "You insert [W] into the case.") - showpiece = W - added_roundstart = FALSE - update_icon() - - placer_key = user.ckey - - trophy_message = W.desc //default value - - var/chosen_plaque = stripped_input(user, "What would you like the plaque to say? Default value is item's description.", "Trophy Plaque") - if(chosen_plaque) - if(user.Adjacent(src)) - trophy_message = chosen_plaque - to_chat(user, "You set the plaque's text.") - else - to_chat(user, "You are too far to set the plaque's text!") - - SSpersistence.SaveTrophy(src) - return TRUE - - else - to_chat(user, "\The [W] is stuck to your hand, you can't put it in the [src.name]!") - - return - -/obj/structure/displaycase/trophy/dump() - if (showpiece) - if(added_roundstart) - visible_message("The [showpiece] crumbles to dust!") - new /obj/effect/decal/cleanable/ash(loc) - QDEL_NULL(showpiece) - else - ..() - -/obj/item/key/displaycase - name = "display case key" - desc = "The key to the curator's display cases." - -/obj/item/showpiece_dummy - name = "Cheap replica" - -/obj/item/showpiece_dummy/Initialize(mapload, path) - . = ..() - var/obj/item/I = path - name = initial(I.name) - icon = initial(I.icon) - icon_state = initial(I.icon_state) - -/obj/structure/displaycase/forsale - name = "vend-a-tray" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "laserbox0" - desc = "A display case with an ID-card swiper. Use your ID to purchase the contents." - density = FALSE - max_integrity = 100 - req_access = null - showpiece_type = /obj/item/reagent_containers/food - alert = FALSE //No, we're not calling the fire department because someone stole your cookie. - glass_fix = FALSE //Fixable with tools instead. - ///The price of the item being sold. Altered by grab intent ID use. - var/sale_price = 20 - ///The Account which will receive payment for purchases. Set by the first ID to swipe the tray. - var/datum/bank_account/payments_acc = null - ///We're using the same trick as paper does in order to cache the image, and only load the UI when messed with. - var/list/viewing_ui = list() - -/obj/structure/displaycase/forsale/update_icon() //remind me to fix my shitcode later - var/icon/I - if(open) - I = icon('icons/obj/stationobjs.dmi',"laserboxb0") - else - I = icon('icons/obj/stationobjs.dmi',"laserbox0") - if(!showpiece && !open) - I = icon('icons/obj/stationobjs.dmi',"laserbox_open") - if(broken) - I = icon('icons/obj/stationobjs.dmi',"laserbox_broken") - if(showpiece) - var/icon/S = getFlatIcon(showpiece) - S.Scale(17,17) - I.Blend(S,ICON_UNDERLAY,8,12) - src.icon = I - return - -/obj/structure/displaycase/forsale/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state) - . = ..() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Vendatray", name, 300, 270, master_ui, state) - ui.set_autoupdate(FALSE) - viewing_ui[user] = ui - ui.open() - -/obj/structure/displaycase/forsale/ui_data(mob/user) - var/list/data = list() - var/register = FALSE - if(payments_acc) - register = TRUE - data["owner_name"] = payments_acc.account_holder - if(showpiece) - data["product_name"] = capitalize(showpiece.name) - var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state)) - data["product_icon"] = base64 - data["registered"] = register - data["product_cost"] = sale_price - data["tray_open"] = open - return data - -/obj/structure/displaycase/forsale/ui_act(action, params) - if(..()) - return - var/obj/item/card/id/potential_acc = usr.get_idcard(hand_first = TRUE) - switch(action) - if("Buy") - if(!showpiece) - to_chat(usr, "There's nothing for sale.") - return TRUE - if(broken) - to_chat(usr, "[src] appears to be broken.") - return TRUE - if(!payments_acc) - to_chat(usr, "[src] hasn't been registered yet.") - return TRUE - if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return TRUE - if(!potential_acc) - to_chat(usr, "No ID card detected.") - return - var/datum/bank_account/account = potential_acc.registered_account - if(!account) - to_chat(usr, "[potential_acc] has no account registered!") - return - if(!account.has_money(sale_price)) - to_chat(usr, "You do not possess the funds to purchase this.") - return TRUE - else - account.adjust_money(-sale_price) - if(payments_acc) - payments_acc.adjust_money(sale_price) - usr.put_in_hands(showpiece) - to_chat(usr, "You purchase [showpiece] for [sale_price] credits.") - playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE) - icon = 'icons/obj/stationobjs.dmi' - flick("laserbox_vend", src) - showpiece = null - update_icon() - SStgui.update_uis(src) - return TRUE - if("Open") - if(!payments_acc) - to_chat(usr, "[src] hasn't been registered yet.") - return TRUE - if(!potential_acc || !potential_acc.registered_account) - return - if(!check_access(potential_acc)) - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) - return - toggle_lock() - SStgui.update_uis(src) - if("Register") - if(payments_acc) - return - if(!potential_acc || !potential_acc.registered_account) - return - if(!check_access(potential_acc)) - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) - return - payments_acc = potential_acc.registered_account - playsound(src, 'sound/machines/click.ogg', 20, TRUE) - if("Adjust") - if(!check_access(potential_acc) || potential_acc.registered_account != payments_acc) - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) - return - - var/new_price_input = input(usr,"Set the sale price for this vend-a-tray.","new price",0) as num|null - if(isnull(new_price_input) || (payments_acc != potential_acc.registered_account)) - to_chat(usr, "[src] rejects your new price.") - return - if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) ) - to_chat(usr, "You need to get closer!") - return - new_price_input = clamp(round(new_price_input, 1), 10, 1000) - sale_price = new_price_input - to_chat(usr, "The cost is now set to [sale_price].") - SStgui.update_uis(src) - return TRUE - . = TRUE -/obj/structure/displaycase/forsale/attackby(obj/item/I, mob/living/user, params) - if(isidcard(I)) - //Card Registration - var/obj/item/card/id/potential_acc = I - if(!potential_acc.registered_account) - to_chat(user, "This ID card has no account registered!") - return - if(payments_acc == potential_acc.registered_account) - playsound(src, 'sound/machines/click.ogg', 20, TRUE) - toggle_lock() - return - if(istype(I, /obj/item/pda)) - return TRUE - SStgui.update_uis(src) - . = ..() - - -/obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I) - . = ..() - if(obj_integrity <= (integrity_failure * max_integrity)) - to_chat(user, "You start recalibrating [src]'s hover field...") - if(do_after(user, 20, target = src)) - broken = 0 - obj_integrity = max_integrity - update_icon() - return TRUE - -/obj/structure/displaycase/forsale/wrench_act(mob/living/user, obj/item/I) - . = ..() - if(open && user.a_intent == INTENT_HELP ) - if(anchored) - to_chat(user, "You start unsecuring [src]...") - else - to_chat(user, "You start securing [src]...") - if(I.use_tool(src, user, 16, volume=50)) - if(QDELETED(I)) - return - if(anchored) - to_chat(user, "You unsecure [src].") - else - to_chat(user, "You secure [src].") - anchored = !anchored - return - else if(!open && user.a_intent == INTENT_HELP) - to_chat(user, "[src] must be open to move it.") - return - -/obj/structure/displaycase/forsale/emag_act(mob/user) - . = ..() - payments_acc = null - req_access = list() - to_chat(user, "[src]'s card reader fizzles and smokes, and the account owner is reset.") - -/obj/structure/displaycase/forsale/examine(mob/user) - . = ..() - if(showpiece && !open) - . += "[showpiece] is for sale for [sale_price] credits." - if(broken) - . += "[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it." - -/obj/structure/displaycase/forsale/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - broken = TRUE - playsound(src, "shatter", 70, TRUE) - update_icon() - trigger_alarm() //In case it's given an alarm anyway. - -/obj/structure/displaycase/forsale/kitchen - desc = "A display case with an ID-card swiper. Use your ID to purchase the contents. Meant for the bartender and chef." - req_one_access = list(ACCESS_KITCHEN, ACCESS_BAR) +/obj/structure/displaycase + name = "display case" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "glassbox0" + desc = "A display case for prized possessions." + density = TRUE + anchored = TRUE + resistance_flags = ACID_PROOF + armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) + max_integrity = 200 + integrity_failure = 0.25 + var/obj/item/showpiece = null + var/obj/item/showpiece_type = null //This allows for showpieces that can only hold items if they're the same istype as this. + var/alert = TRUE + var/open = FALSE + var/openable = TRUE + var/obj/item/electronics/airlock/electronics + var/start_showpiece_type = null //add type for items on display + var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk") + var/trophy_message = "" + var/glass_fix = TRUE + +/obj/structure/displaycase/Initialize() + . = ..() + if(start_showpieces.len && !start_showpiece_type) + var/list/showpiece_entry = pick(start_showpieces) + if (showpiece_entry && showpiece_entry["type"]) + start_showpiece_type = showpiece_entry["type"] + if (showpiece_entry["trophy_message"]) + trophy_message = showpiece_entry["trophy_message"] + if(start_showpiece_type) + showpiece = new start_showpiece_type (src) + update_icon() + +/obj/structure/displaycase/Destroy() + if(electronics) + QDEL_NULL(electronics) + if(showpiece) + QDEL_NULL(showpiece) + return ..() + +/obj/structure/displaycase/examine(mob/user) + . = ..() + if(alert) + . += "Hooked up with an anti-theft system." + if(showpiece) + . += "There's [showpiece] inside." + if(trophy_message) + . += "The plaque reads:\n [trophy_message]" + + +/obj/structure/displaycase/proc/dump() + if (showpiece) + showpiece.forceMove(loc) + showpiece = null + +/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/displaycase/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + dump() + if(!disassembled) + new /obj/item/shard( src.loc ) + trigger_alarm() + qdel(src) + +/obj/structure/displaycase/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + density = FALSE + broken = 1 + new /obj/item/shard( src.loc ) + playsound(src, "shatter", 70, TRUE) + update_icon() + trigger_alarm() + +/obj/structure/displaycase/proc/trigger_alarm() + //Activate Anti-theft + if(alert) + var/area/alarmed = get_area(src) + alarmed.burglaralert(src) + playsound(src, 'sound/effects/alert.ogg', 50, TRUE) + +/obj/structure/displaycase/update_icon() + var/icon/I + if(open) + I = icon('icons/obj/stationobjs.dmi',"glassbox_open") + else + I = icon('icons/obj/stationobjs.dmi',"glassbox0") + if(broken) + I = icon('icons/obj/stationobjs.dmi',"glassboxb0") + if(showpiece) + var/icon/S = getFlatIcon(showpiece) + S.Scale(17,17) + I.Blend(S,ICON_UNDERLAY,8,8) + src.icon = I + return + +/obj/structure/displaycase/attackby(obj/item/W, mob/user, params) + if(W.GetID() && !broken && openable) + if(allowed(user)) + to_chat(user, "You [open ? "close":"open"] [src].") + toggle_lock(user) + else + to_chat(user, "Access denied.") + else if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken) + if(obj_integrity < max_integrity) + if(!W.tool_start_check(user, amount=5)) + return + + to_chat(user, "You begin repairing [src]...") + if(W.use_tool(src, user, 40, amount=5, volume=50)) + obj_integrity = max_integrity + update_icon() + to_chat(user, "You repair [src].") + else + to_chat(user, "[src] is already in good condition!") + return + else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases + if(broken) + if(showpiece) + to_chat(user, "Remove the displayed object first!") + else + to_chat(user, "You remove the destroyed case.") + qdel(src) + else + to_chat(user, "You start to [open ? "close":"open"] [src]...") + if(W.use_tool(src, user, 20)) + to_chat(user, "You [open ? "close":"open"] [src].") + toggle_lock(user) + else if(open && !showpiece) + if(showpiece_type && !istype(W, showpiece_type)) + to_chat(user, "This doesn't belong in this kind of display.") + return TRUE + if(user.transferItemToLoc(W, src)) + showpiece = W + to_chat(user, "You put [W] on display.") + update_icon() + else if(glass_fix && broken && istype(W, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = W + if(G.get_amount() < 2) + to_chat(user, "You need two glass sheets to fix the case!") + return + to_chat(user, "You start fixing [src]...") + if(do_after(user, 20, target = src)) + G.use(2) + broken = 0 + obj_integrity = max_integrity + update_icon() + else + return ..() + +/obj/structure/displaycase/proc/toggle_lock(mob/user) + open = !open + update_icon() + +/obj/structure/displaycase/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/displaycase/attack_hand(mob/user) + . = ..() + if(.) + return + user.changeNext_move(CLICK_CD_MELEE) + if (showpiece && (broken || open)) + to_chat(user, "You deactivate the hover field built into the case.") + log_combat(user, src, "deactivates the hover field of") + dump() + src.add_fingerprint(user) + update_icon() + return + else + //prevents remote "kicks" with TK + if (!Adjacent(user)) + return + if (user.a_intent == INTENT_HELP) + user.examinate(src) + return + user.visible_message("[user] kicks the display case.", null, null, COMBAT_MESSAGE_RANGE) + log_combat(user, src, "kicks") + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + take_damage(2) + +/obj/structure/displaycase_chassis + anchored = TRUE + density = FALSE + name = "display case chassis" + desc = "The wooden base of a display case." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "glassbox_chassis" + var/obj/item/electronics/airlock/electronics + + +/obj/structure/displaycase_chassis/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) //The player can only deconstruct the wooden frame + to_chat(user, "You start disassembling [src]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 30)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + new /obj/item/stack/sheet/mineral/wood(get_turf(src), 5) + qdel(src) + + else if(istype(I, /obj/item/electronics/airlock)) + to_chat(user, "You start installing the electronics into [src]...") + I.play_tool_sound(src) + if(do_after(user, 30, target = src) && user.transferItemToLoc(I,src)) + electronics = I + to_chat(user, "You install the airlock electronics.") + + else if(istype(I, /obj/item/stock_parts/card_reader)) + var/obj/item/stock_parts/card_reader/C = I + to_chat(user, "You start adding [C] to [src]...") + if(do_after(user, 20, target = src)) + var/obj/structure/displaycase/forsale/sale = new(src.loc) + if(electronics) + electronics.forceMove(sale) + sale.electronics = electronics + if(electronics.one_access) + sale.req_one_access = electronics.accesses + else + sale.req_access = electronics.accesses + qdel(src) + qdel(C) + + else if(istype(I, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = I + if(G.get_amount() < 10) + to_chat(user, "You need ten glass sheets to do this!") + return + to_chat(user, "You start adding [G] to [src]...") + if(do_after(user, 20, target = src)) + G.use(10) + var/obj/structure/displaycase/noalert/display = new(src.loc) + if(electronics) + electronics.forceMove(display) + display.electronics = electronics + if(electronics.one_access) + display.req_one_access = electronics.accesses + else + display.req_access = electronics.accesses + qdel(src) + else + return ..() + +//The lab cage and captain's display case do not spawn with electronics, which is why req_access is needed. +/obj/structure/displaycase/captain + start_showpiece_type = /obj/item/gun/energy/laser/captain + req_access = list(ACCESS_CENT_SPECOPS) //this was intentional, presumably to make it slightly harder for caps to grab their gun roundstart + +/obj/structure/displaycase/labcage + name = "lab cage" + desc = "A glass lab container for storing interesting creatures." + start_showpiece_type = /obj/item/clothing/mask/facehugger/lamarr + req_access = list(ACCESS_RD) + +/obj/structure/displaycase/noalert + alert = FALSE + +/obj/structure/displaycase/trophy + name = "trophy display case" + desc = "Store your trophies of accomplishment in here, and they will stay forever." + var/placer_key = "" + var/added_roundstart = TRUE + var/is_locked = TRUE + integrity_failure = 0 + openable = FALSE + +/obj/structure/displaycase/trophy/Initialize() + . = ..() + GLOB.trophy_cases += src + +/obj/structure/displaycase/trophy/Destroy() + GLOB.trophy_cases -= src + return ..() + +/obj/structure/displaycase/trophy/attackby(obj/item/W, mob/user, params) + + if(!user.Adjacent(src)) //no TK museology + return + if(user.a_intent == INTENT_HARM) + return ..() + + if(user.is_holding_item_of_type(/obj/item/key/displaycase)) + if(added_roundstart) + is_locked = !is_locked + to_chat(user, "You [!is_locked ? "un" : ""]lock the case.") + else + to_chat(user, "The lock is stuck shut!") + return + + if(is_locked) + to_chat(user, "The case is shut tight with an old-fashioned physical lock. Maybe you should ask the curator for the key?") + return + + if(!added_roundstart) + to_chat(user, "You've already put something new in this case!") + return + + if(is_type_in_typecache(W, GLOB.blacklisted_cargo_types)) + to_chat(user, "The case rejects the [W]!") + return + + for(var/a in W.GetAllContents()) + if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types)) + to_chat(user, "The case rejects the [W]!") + return + + if(user.transferItemToLoc(W, src)) + + if(showpiece) + to_chat(user, "You press a button, and [showpiece] descends into the floor of the case.") + QDEL_NULL(showpiece) + + to_chat(user, "You insert [W] into the case.") + showpiece = W + added_roundstart = FALSE + update_icon() + + placer_key = user.ckey + + trophy_message = W.desc //default value + + var/chosen_plaque = stripped_input(user, "What would you like the plaque to say? Default value is item's description.", "Trophy Plaque") + if(chosen_plaque) + if(user.Adjacent(src)) + trophy_message = chosen_plaque + to_chat(user, "You set the plaque's text.") + else + to_chat(user, "You are too far to set the plaque's text!") + + SSpersistence.SaveTrophy(src) + return TRUE + + else + to_chat(user, "\The [W] is stuck to your hand, you can't put it in the [src.name]!") + + return + +/obj/structure/displaycase/trophy/dump() + if (showpiece) + if(added_roundstart) + visible_message("The [showpiece] crumbles to dust!") + new /obj/effect/decal/cleanable/ash(loc) + QDEL_NULL(showpiece) + else + ..() + +/obj/item/key/displaycase + name = "display case key" + desc = "The key to the curator's display cases." + +/obj/item/showpiece_dummy + name = "Cheap replica" + +/obj/item/showpiece_dummy/Initialize(mapload, path) + . = ..() + var/obj/item/I = path + name = initial(I.name) + icon = initial(I.icon) + icon_state = initial(I.icon_state) + +/obj/structure/displaycase/forsale + name = "vend-a-tray" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "laserbox0" + desc = "A display case with an ID-card swiper. Use your ID to purchase the contents." + density = FALSE + max_integrity = 100 + req_access = null + showpiece_type = /obj/item/reagent_containers/food + alert = FALSE //No, we're not calling the fire department because someone stole your cookie. + glass_fix = FALSE //Fixable with tools instead. + ///The price of the item being sold. Altered by grab intent ID use. + var/sale_price = 20 + ///The Account which will receive payment for purchases. Set by the first ID to swipe the tray. + var/datum/bank_account/payments_acc = null + ///We're using the same trick as paper does in order to cache the image, and only load the UI when messed with. + var/list/viewing_ui = list() + +/obj/structure/displaycase/forsale/update_icon() //remind me to fix my shitcode later + var/icon/I + if(open) + I = icon('icons/obj/stationobjs.dmi',"laserboxb0") + else + I = icon('icons/obj/stationobjs.dmi',"laserbox0") + if(!showpiece && !open) + I = icon('icons/obj/stationobjs.dmi',"laserbox_open") + if(broken) + I = icon('icons/obj/stationobjs.dmi',"laserbox_broken") + if(showpiece) + var/icon/S = getFlatIcon(showpiece) + S.Scale(17,17) + I.Blend(S,ICON_UNDERLAY,8,12) + src.icon = I + return + +/obj/structure/displaycase/forsale/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state) + . = ..() + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Vendatray", name, 300, 270, master_ui, state) + ui.set_autoupdate(FALSE) + viewing_ui[user] = ui + ui.open() + +/obj/structure/displaycase/forsale/ui_data(mob/user) + var/list/data = list() + var/register = FALSE + if(payments_acc) + register = TRUE + data["owner_name"] = payments_acc.account_holder + if(showpiece) + data["product_name"] = capitalize(showpiece.name) + var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state)) + data["product_icon"] = base64 + data["registered"] = register + data["product_cost"] = sale_price + data["tray_open"] = open + return data + +/obj/structure/displaycase/forsale/ui_act(action, params) + if(..()) + return + var/obj/item/card/id/potential_acc = usr.get_idcard(hand_first = TRUE) + switch(action) + if("Buy") + if(!showpiece) + to_chat(usr, "There's nothing for sale.") + return TRUE + if(broken) + to_chat(usr, "[src] appears to be broken.") + return TRUE + if(!payments_acc) + to_chat(usr, "[src] hasn't been registered yet.") + return TRUE + if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return TRUE + if(!potential_acc) + to_chat(usr, "No ID card detected.") + return + var/datum/bank_account/account = potential_acc.registered_account + if(!account) + to_chat(usr, "[potential_acc] has no account registered!") + return + if(!account.has_money(sale_price)) + to_chat(usr, "You do not possess the funds to purchase this.") + return TRUE + else + account.adjust_money(-sale_price) + if(payments_acc) + payments_acc.adjust_money(sale_price) + usr.put_in_hands(showpiece) + to_chat(usr, "You purchase [showpiece] for [sale_price] credits.") + playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE) + icon = 'icons/obj/stationobjs.dmi' + flick("laserbox_vend", src) + showpiece = null + update_icon() + SStgui.update_uis(src) + return TRUE + if("Open") + if(!payments_acc) + to_chat(usr, "[src] hasn't been registered yet.") + return TRUE + if(!potential_acc || !potential_acc.registered_account) + return + if(!check_access(potential_acc)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + toggle_lock() + SStgui.update_uis(src) + if("Register") + if(payments_acc) + return + if(!potential_acc || !potential_acc.registered_account) + return + if(!check_access(potential_acc)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + payments_acc = potential_acc.registered_account + playsound(src, 'sound/machines/click.ogg', 20, TRUE) + if("Adjust") + if(!check_access(potential_acc) || potential_acc.registered_account != payments_acc) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + + var/new_price_input = input(usr,"Set the sale price for this vend-a-tray.","new price",0) as num|null + if(isnull(new_price_input) || (payments_acc != potential_acc.registered_account)) + to_chat(usr, "[src] rejects your new price.") + return + if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) ) + to_chat(usr, "You need to get closer!") + return + new_price_input = clamp(round(new_price_input, 1), 10, 1000) + sale_price = new_price_input + to_chat(usr, "The cost is now set to [sale_price].") + SStgui.update_uis(src) + return TRUE + . = TRUE +/obj/structure/displaycase/forsale/attackby(obj/item/I, mob/living/user, params) + if(isidcard(I)) + //Card Registration + var/obj/item/card/id/potential_acc = I + if(!potential_acc.registered_account) + to_chat(user, "This ID card has no account registered!") + return + if(payments_acc == potential_acc.registered_account) + playsound(src, 'sound/machines/click.ogg', 20, TRUE) + toggle_lock() + return + if(istype(I, /obj/item/pda)) + return TRUE + SStgui.update_uis(src) + . = ..() + + +/obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I) + . = ..() + if(obj_integrity <= (integrity_failure * max_integrity)) + to_chat(user, "You start recalibrating [src]'s hover field...") + if(do_after(user, 20, target = src)) + broken = 0 + obj_integrity = max_integrity + update_icon() + return TRUE + +/obj/structure/displaycase/forsale/wrench_act(mob/living/user, obj/item/I) + . = ..() + if(open && user.a_intent == INTENT_HELP ) + if(anchored) + to_chat(user, "You start unsecuring [src]...") + else + to_chat(user, "You start securing [src]...") + if(I.use_tool(src, user, 16, volume=50)) + if(QDELETED(I)) + return + if(anchored) + to_chat(user, "You unsecure [src].") + else + to_chat(user, "You secure [src].") + anchored = !anchored + return + else if(!open && user.a_intent == INTENT_HELP) + to_chat(user, "[src] must be open to move it.") + return + +/obj/structure/displaycase/forsale/emag_act(mob/user) + . = ..() + payments_acc = null + req_access = list() + to_chat(user, "[src]'s card reader fizzles and smokes, and the account owner is reset.") + +/obj/structure/displaycase/forsale/examine(mob/user) + . = ..() + if(showpiece && !open) + . += "[showpiece] is for sale for [sale_price] credits." + if(broken) + . += "[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it." + +/obj/structure/displaycase/forsale/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + broken = TRUE + playsound(src, "shatter", 70, TRUE) + update_icon() + trigger_alarm() //In case it's given an alarm anyway. + +/obj/structure/displaycase/forsale/kitchen + desc = "A display case with an ID-card swiper. Use your ID to purchase the contents. Meant for the bartender and chef." + req_one_access = list(ACCESS_KITCHEN, ACCESS_BAR) diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm index b7621500e3a..f7eb4c0a29d 100644 --- a/code/game/objects/structures/dresser.dm +++ b/code/game/objects/structures/dresser.dm @@ -1,58 +1,58 @@ -/obj/structure/dresser - name = "dresser" - desc = "A nicely-crafted wooden dresser. It's filled with lots of undies." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "dresser" - density = TRUE - anchored = TRUE - -/obj/structure/dresser/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") - setAnchored(!anchored) - else - return ..() - -/obj/structure/dresser/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) - qdel(src) - -/obj/structure/dresser/attack_hand(mob/user) - . = ..() - if(.) - return - if(!Adjacent(user))//no tele-grooming - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - - if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) - to_chat(user, "You are not capable of wearing underwear.") - return - - var/choice = input(user, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in list("Underwear","Underwear Color","Undershirt","Socks") - - if(!Adjacent(user)) - return - switch(choice) - if("Underwear") - var/new_undies = input(user, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list - if(new_undies) - H.underwear = new_undies - if("Underwear Color") - var/new_underwear_color = input(H, "Choose your underwear color", "Underwear Color","#"+H.underwear_color) as color|null - if(new_underwear_color) - H.underwear_color = sanitize_hexcolor(new_underwear_color) - if("Undershirt") - var/new_undershirt = input(user, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list - if(new_undershirt) - H.undershirt = new_undershirt - if("Socks") - var/new_socks = input(user, "Select your socks", "Changing") as null|anything in GLOB.socks_list - if(new_socks) - H.socks= new_socks - - add_fingerprint(H) - H.update_body() +/obj/structure/dresser + name = "dresser" + desc = "A nicely-crafted wooden dresser. It's filled with lots of undies." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "dresser" + density = TRUE + anchored = TRUE + +/obj/structure/dresser/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") + setAnchored(!anchored) + else + return ..() + +/obj/structure/dresser/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) + qdel(src) + +/obj/structure/dresser/attack_hand(mob/user) + . = ..() + if(.) + return + if(!Adjacent(user))//no tele-grooming + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + + if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) + to_chat(user, "You are not capable of wearing underwear.") + return + + var/choice = input(user, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in list("Underwear","Underwear Color","Undershirt","Socks") + + if(!Adjacent(user)) + return + switch(choice) + if("Underwear") + var/new_undies = input(user, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list + if(new_undies) + H.underwear = new_undies + if("Underwear Color") + var/new_underwear_color = input(H, "Choose your underwear color", "Underwear Color","#"+H.underwear_color) as color|null + if(new_underwear_color) + H.underwear_color = sanitize_hexcolor(new_underwear_color) + if("Undershirt") + var/new_undershirt = input(user, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list + if(new_undershirt) + H.undershirt = new_undershirt + if("Socks") + var/new_socks = input(user, "Select your socks", "Changing") as null|anything in GLOB.socks_list + if(new_socks) + H.socks= new_socks + + add_fingerprint(H) + H.update_body() diff --git a/code/game/objects/structures/electricchair.dm b/code/game/objects/structures/electricchair.dm index 8004d5733fe..a5235f461a4 100644 --- a/code/game/objects/structures/electricchair.dm +++ b/code/game/objects/structures/electricchair.dm @@ -1,52 +1,52 @@ -/obj/structure/chair/e_chair - name = "electric chair" - desc = "Looks absolutely SHOCKING!" - icon_state = "echair0" - var/obj/item/assembly/shock_kit/part = null - var/last_time = 1 - item_chair = null - -/obj/structure/chair/e_chair/Initialize() - . = ..() - add_overlay(mutable_appearance('icons/obj/chairs.dmi', "echair_over", MOB_LAYER + 1)) - -/obj/structure/chair/e_chair/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WRENCH) - var/obj/structure/chair/C = new /obj/structure/chair(loc) - W.play_tool_sound(src) - C.setDir(dir) - part.forceMove(loc) - part.master = null - part = null - qdel(src) - -/obj/structure/chair/e_chair/proc/shock() - if(last_time + 50 > world.time) - return - last_time = world.time - - // special power handling - var/area/A = get_area(src) - if(!isarea(A)) - return - if(!A.powered(AREA_USAGE_EQUIP)) - return - A.use_power(AREA_USAGE_EQUIP, 5000) - - flick("echair_shock", src) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(12, 1, src) - s.start() - if(has_buckled_mobs()) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - buckled_mob.electrocute_act(85, src, 1) - to_chat(buckled_mob, "You feel a deep shock course through your body!") - addtimer(CALLBACK(buckled_mob, /mob/living.proc/electrocute_act, 85, src, 1), 1) - visible_message("The electric chair went off!", "You hear a deep sharp shock!") - -/obj/structure/chair/e_chair/post_buckle_mob(mob/living/L) - SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "dying", /datum/mood_event/deaths_door) - -/obj/structure/chair/e_chair/post_unbuckle_mob(mob/living/L) - SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "dying") +/obj/structure/chair/e_chair + name = "electric chair" + desc = "Looks absolutely SHOCKING!" + icon_state = "echair0" + var/obj/item/assembly/shock_kit/part = null + var/last_time = 1 + item_chair = null + +/obj/structure/chair/e_chair/Initialize() + . = ..() + add_overlay(mutable_appearance('icons/obj/chairs.dmi', "echair_over", MOB_LAYER + 1)) + +/obj/structure/chair/e_chair/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WRENCH) + var/obj/structure/chair/C = new /obj/structure/chair(loc) + W.play_tool_sound(src) + C.setDir(dir) + part.forceMove(loc) + part.master = null + part = null + qdel(src) + +/obj/structure/chair/e_chair/proc/shock() + if(last_time + 50 > world.time) + return + last_time = world.time + + // special power handling + var/area/A = get_area(src) + if(!isarea(A)) + return + if(!A.powered(AREA_USAGE_EQUIP)) + return + A.use_power(AREA_USAGE_EQUIP, 5000) + + flick("echair_shock", src) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(12, 1, src) + s.start() + if(has_buckled_mobs()) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + buckled_mob.electrocute_act(85, src, 1) + to_chat(buckled_mob, "You feel a deep shock course through your body!") + addtimer(CALLBACK(buckled_mob, /mob/living.proc/electrocute_act, 85, src, 1), 1) + visible_message("The electric chair went off!", "You hear a deep sharp shock!") + +/obj/structure/chair/e_chair/post_buckle_mob(mob/living/L) + SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "dying", /datum/mood_event/deaths_door) + +/obj/structure/chair/e_chair/post_unbuckle_mob(mob/living/L) + SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "dying") diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm index c1f094f4a9e..620653c5913 100644 --- a/code/game/objects/structures/extinguisher.dm +++ b/code/game/objects/structures/extinguisher.dm @@ -1,159 +1,159 @@ -/obj/structure/extinguisher_cabinet - name = "extinguisher cabinet" - desc = "A small wall mounted cabinet designed to hold a fire extinguisher." - icon = 'icons/obj/wallmounts.dmi' - icon_state = "extinguisher_closed" - anchored = TRUE - density = FALSE - max_integrity = 200 - integrity_failure = 0.25 - var/obj/item/extinguisher/stored_extinguisher - var/opened = FALSE - -/obj/structure/extinguisher_cabinet/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -27 : 27) - pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 - opened = TRUE - icon_state = "extinguisher_empty" - else - stored_extinguisher = new /obj/item/extinguisher(src) - -/obj/structure/extinguisher_cabinet/examine(mob/user) - . = ..() - . += "Alt-click to [opened ? "close":"open"] it." - -/obj/structure/extinguisher_cabinet/Destroy() - if(stored_extinguisher) - qdel(stored_extinguisher) - stored_extinguisher = null - return ..() - -/obj/structure/extinguisher_cabinet/contents_explosion(severity, target) - if(stored_extinguisher) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += stored_extinguisher - if(EXPLODE_HEAVY) - SSexplosions.medobj += stored_extinguisher - if(EXPLODE_LIGHT) - SSexplosions.lowobj += stored_extinguisher - -/obj/structure/extinguisher_cabinet/handle_atom_del(atom/A) - if(A == stored_extinguisher) - stored_extinguisher = null - update_icon() - -/obj/structure/extinguisher_cabinet/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH && !stored_extinguisher) - to_chat(user, "You start unsecuring [name]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 60)) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You unsecure [name].") - deconstruct(TRUE) - return - - if(iscyborg(user) || isalien(user)) - return - if(istype(I, /obj/item/extinguisher)) - if(!stored_extinguisher && opened) - if(!user.transferItemToLoc(I, src)) - return - stored_extinguisher = I - to_chat(user, "You place [I] in [src].") - update_icon() - return TRUE - else - toggle_cabinet(user) - else if(user.a_intent != INTENT_HARM) - toggle_cabinet(user) - else - return ..() - - -/obj/structure/extinguisher_cabinet/attack_hand(mob/user) - . = ..() - if(.) - return - if(iscyborg(user) || isalien(user)) - return - if(stored_extinguisher) - user.put_in_hands(stored_extinguisher) - to_chat(user, "You take [stored_extinguisher] from [src].") - stored_extinguisher = null - if(!opened) - opened = 1 - playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) - update_icon() - else - toggle_cabinet(user) - - -/obj/structure/extinguisher_cabinet/attack_tk(mob/user) - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - to_chat(user, "You telekinetically remove [stored_extinguisher] from [src].") - stored_extinguisher = null - opened = 1 - playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) - update_icon() - else - toggle_cabinet(user) - - -/obj/structure/extinguisher_cabinet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/extinguisher_cabinet/AltClick(mob/living/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - toggle_cabinet(user) - -/obj/structure/extinguisher_cabinet/proc/toggle_cabinet(mob/user) - if(opened && broken) - to_chat(user, "[src] is broken open.") - else - playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) - opened = !opened - update_icon() - -/obj/structure/extinguisher_cabinet/update_icon_state() - if(!opened) - icon_state = "extinguisher_closed" - else if(stored_extinguisher) - if(istype(stored_extinguisher, /obj/item/extinguisher/mini)) - icon_state = "extinguisher_mini" - else - icon_state = "extinguisher_full" - else - icon_state = "extinguisher_empty" - -/obj/structure/extinguisher_cabinet/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - broken = 1 - opened = 1 - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - stored_extinguisher = null - update_icon() - - -/obj/structure/extinguisher_cabinet/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - new /obj/item/wallframe/extinguisher_cabinet(loc) - else - new /obj/item/stack/sheet/metal (loc, 2) - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - stored_extinguisher = null - qdel(src) - -/obj/item/wallframe/extinguisher_cabinet - name = "extinguisher cabinet frame" - desc = "Used for building wall-mounted extinguisher cabinets." - icon_state = "extinguisher" - result_path = /obj/structure/extinguisher_cabinet +/obj/structure/extinguisher_cabinet + name = "extinguisher cabinet" + desc = "A small wall mounted cabinet designed to hold a fire extinguisher." + icon = 'icons/obj/wallmounts.dmi' + icon_state = "extinguisher_closed" + anchored = TRUE + density = FALSE + max_integrity = 200 + integrity_failure = 0.25 + var/obj/item/extinguisher/stored_extinguisher + var/opened = FALSE + +/obj/structure/extinguisher_cabinet/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -27 : 27) + pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + opened = TRUE + icon_state = "extinguisher_empty" + else + stored_extinguisher = new /obj/item/extinguisher(src) + +/obj/structure/extinguisher_cabinet/examine(mob/user) + . = ..() + . += "Alt-click to [opened ? "close":"open"] it." + +/obj/structure/extinguisher_cabinet/Destroy() + if(stored_extinguisher) + qdel(stored_extinguisher) + stored_extinguisher = null + return ..() + +/obj/structure/extinguisher_cabinet/contents_explosion(severity, target) + if(stored_extinguisher) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += stored_extinguisher + if(EXPLODE_HEAVY) + SSexplosions.medobj += stored_extinguisher + if(EXPLODE_LIGHT) + SSexplosions.lowobj += stored_extinguisher + +/obj/structure/extinguisher_cabinet/handle_atom_del(atom/A) + if(A == stored_extinguisher) + stored_extinguisher = null + update_icon() + +/obj/structure/extinguisher_cabinet/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH && !stored_extinguisher) + to_chat(user, "You start unsecuring [name]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 60)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You unsecure [name].") + deconstruct(TRUE) + return + + if(iscyborg(user) || isalien(user)) + return + if(istype(I, /obj/item/extinguisher)) + if(!stored_extinguisher && opened) + if(!user.transferItemToLoc(I, src)) + return + stored_extinguisher = I + to_chat(user, "You place [I] in [src].") + update_icon() + return TRUE + else + toggle_cabinet(user) + else if(user.a_intent != INTENT_HARM) + toggle_cabinet(user) + else + return ..() + + +/obj/structure/extinguisher_cabinet/attack_hand(mob/user) + . = ..() + if(.) + return + if(iscyborg(user) || isalien(user)) + return + if(stored_extinguisher) + user.put_in_hands(stored_extinguisher) + to_chat(user, "You take [stored_extinguisher] from [src].") + stored_extinguisher = null + if(!opened) + opened = 1 + playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) + update_icon() + else + toggle_cabinet(user) + + +/obj/structure/extinguisher_cabinet/attack_tk(mob/user) + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + to_chat(user, "You telekinetically remove [stored_extinguisher] from [src].") + stored_extinguisher = null + opened = 1 + playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) + update_icon() + else + toggle_cabinet(user) + + +/obj/structure/extinguisher_cabinet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/extinguisher_cabinet/AltClick(mob/living/user) + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + toggle_cabinet(user) + +/obj/structure/extinguisher_cabinet/proc/toggle_cabinet(mob/user) + if(opened && broken) + to_chat(user, "[src] is broken open.") + else + playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) + opened = !opened + update_icon() + +/obj/structure/extinguisher_cabinet/update_icon_state() + if(!opened) + icon_state = "extinguisher_closed" + else if(stored_extinguisher) + if(istype(stored_extinguisher, /obj/item/extinguisher/mini)) + icon_state = "extinguisher_mini" + else + icon_state = "extinguisher_full" + else + icon_state = "extinguisher_empty" + +/obj/structure/extinguisher_cabinet/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + broken = 1 + opened = 1 + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + stored_extinguisher = null + update_icon() + + +/obj/structure/extinguisher_cabinet/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(disassembled) + new /obj/item/wallframe/extinguisher_cabinet(loc) + else + new /obj/item/stack/sheet/metal (loc, 2) + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + stored_extinguisher = null + qdel(src) + +/obj/item/wallframe/extinguisher_cabinet + name = "extinguisher cabinet frame" + desc = "Used for building wall-mounted extinguisher cabinets." + icon_state = "extinguisher" + result_path = /obj/structure/extinguisher_cabinet diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm index a5dfcddfc4f..cb8364f6e83 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -1,475 +1,475 @@ -/obj/structure/flora - resistance_flags = FLAMMABLE - max_integrity = 150 - anchored = TRUE - -//trees -/obj/structure/flora/tree - name = "tree" - desc = "A large tree." - density = TRUE - pixel_x = -16 - layer = FLY_LAYER - var/log_amount = 10 - -/obj/structure/flora/tree/attackby(obj/item/W, mob/user, params) - if(log_amount && (!(flags_1 & NODECONSTRUCT_1))) - if(W.get_sharpness() && W.force > 0) - if(W.hitsound) - playsound(get_turf(src), W.hitsound, 100, FALSE, FALSE) - user.visible_message("[user] begins to cut down [src] with [W].","You begin to cut down [src] with [W].", "You hear the sound of sawing.") - if(do_after(user, 1000/W.force, target = src)) //5 seconds with 20 force, 8 seconds with a hatchet, 20 seconds with a shard. - user.visible_message("[user] fells [src] with the [W].","You fell [src] with the [W].", "You hear the sound of a tree falling.") - playsound(get_turf(src), 'sound/effects/meteorimpact.ogg', 100 , FALSE, FALSE) - user.log_message("cut down [src] at [AREACOORD(src)]", LOG_ATTACK) - for(var/i=1 to log_amount) - new /obj/item/grown/log/tree(get_turf(src)) - var/obj/structure/flora/stump/S = new(loc) - S.name = "[name] stump" - qdel(src) - else - return ..() - -/obj/structure/flora/stump - name = "stump" - desc = "This represents our promise to the crew, and the station itself, to cut down as many trees as possible." //running naked through the trees - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "tree_stump" - density = FALSE - pixel_x = -16 - -/obj/structure/flora/tree/pine - name = "pine tree" - desc = "A coniferous pine tree." - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "pine_1" - var/list/icon_states = list("pine_1", "pine_2", "pine_3") - -/obj/structure/flora/tree/pine/Initialize() - . = ..() - - if(islist(icon_states && icon_states.len)) - icon_state = pick(icon_states) - -/obj/structure/flora/tree/pine/xmas - name = "xmas tree" - desc = "A wondrous decorated Christmas tree." - icon_state = "pine_c" - icon_states = null - flags_1 = NODECONSTRUCT_1 //protected by the christmas spirit - -/obj/structure/flora/tree/pine/xmas/presents - icon_state = "pinepresents" - desc = "A wondrous decorated Christmas tree. It has presents!" - var/gift_type = /obj/item/a_gift/anything - var/unlimited = FALSE - var/static/list/took_presents //shared between all xmas trees - -/obj/structure/flora/tree/pine/xmas/presents/Initialize() - . = ..() - if(!took_presents) - took_presents = list() - -/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!user.ckey) - return - - if(took_presents[user.ckey] && !unlimited) - to_chat(user, "There are no presents with your name on.") - return - to_chat(user, "After a bit of rummaging, you locate a gift with your name on it!") - - if(!unlimited) - took_presents[user.ckey] = TRUE - - var/obj/item/G = new gift_type(src) - user.put_in_hands(G) - -/obj/structure/flora/tree/pine/xmas/presents/unlimited - desc = "A wonderous decorated Christmas tree. It has a seemly endless supply of presents!" - unlimited = TRUE - -/obj/structure/flora/tree/dead - icon = 'icons/obj/flora/deadtrees.dmi' - desc = "A dead tree. How it died, you know not." - icon_state = "tree_1" - -/obj/structure/flora/tree/palm - icon = 'icons/misc/beach2.dmi' - desc = "A tree straight from the tropics." - icon_state = "palm1" - -/obj/structure/flora/tree/palm/Initialize() - . = ..() - icon_state = pick("palm1","palm2") - pixel_x = 0 - -/obj/structure/festivus - name = "festivus pole" - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "festivus_pole" - desc = "During last year's Feats of Strength the Research Director was able to suplex this passing immobile rod into a planter." - -/obj/structure/festivus/anchored - name = "suplexed rod" - desc = "A true feat of strength, almost as good as last year." - icon_state = "anchored_rod" - anchored = TRUE - -/obj/structure/flora/tree/dead/Initialize() - icon_state = "tree_[rand(1, 6)]" - . = ..() - -/obj/structure/flora/tree/jungle - name = "tree" - icon_state = "tree" - desc = "It's seriously hampering your view of the jungle." - icon = 'icons/obj/flora/jungletrees.dmi' - pixel_x = -48 - pixel_y = -20 - -/obj/structure/flora/tree/jungle/Initialize() - icon_state = "[icon_state][rand(1, 6)]" - . = ..() - -/obj/structure/flora/tree/jungle/small - pixel_y = 0 - pixel_x = -32 - icon = 'icons/obj/flora/jungletreesmall.dmi' - -//grass -/obj/structure/flora/grass - name = "grass" - desc = "A patch of overgrown grass." - icon = 'icons/obj/flora/snowflora.dmi' - gender = PLURAL //"this is grass" not "this is a grass" - -/obj/structure/flora/grass/brown - icon_state = "snowgrass1bb" - -/obj/structure/flora/grass/brown/Initialize() - icon_state = "snowgrass[rand(1, 3)]bb" - . = ..() - - -/obj/structure/flora/grass/green - icon_state = "snowgrass1gb" - -/obj/structure/flora/grass/green/Initialize() - icon_state = "snowgrass[rand(1, 3)]gb" - . = ..() - -/obj/structure/flora/grass/both - icon_state = "snowgrassall1" - -/obj/structure/flora/grass/both/Initialize() - icon_state = "snowgrassall[rand(1, 3)]" - . = ..() - - -//bushes -/obj/structure/flora/bush - name = "bush" - desc = "Some type of shrub." - icon = 'icons/obj/flora/snowflora.dmi' - icon_state = "snowbush1" - anchored = TRUE - -/obj/structure/flora/bush/Initialize() - icon_state = "snowbush[rand(1, 6)]" - . = ..() - -//newbushes - -/obj/structure/flora/ausbushes - name = "bush" - desc = "Some kind of plant." - icon = 'icons/obj/flora/ausflora.dmi' - icon_state = "firstbush_1" - -/obj/structure/flora/ausbushes/Initialize() - if(icon_state == "firstbush_1") - icon_state = "firstbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/reedbush - icon_state = "reedbush_1" - -/obj/structure/flora/ausbushes/reedbush/Initialize() - icon_state = "reedbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/leafybush - icon_state = "leafybush_1" - -/obj/structure/flora/ausbushes/leafybush/Initialize() - icon_state = "leafybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/palebush - icon_state = "palebush_1" - -/obj/structure/flora/ausbushes/palebush/Initialize() - icon_state = "palebush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/stalkybush - icon_state = "stalkybush_1" - -/obj/structure/flora/ausbushes/stalkybush/Initialize() - icon_state = "stalkybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/grassybush - icon_state = "grassybush_1" - -/obj/structure/flora/ausbushes/grassybush/Initialize() - icon_state = "grassybush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/fernybush - icon_state = "fernybush_1" - -/obj/structure/flora/ausbushes/fernybush/Initialize() - icon_state = "fernybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/sunnybush - icon_state = "sunnybush_1" - -/obj/structure/flora/ausbushes/sunnybush/Initialize() - icon_state = "sunnybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/genericbush - icon_state = "genericbush_1" - -/obj/structure/flora/ausbushes/genericbush/Initialize() - icon_state = "genericbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/pointybush - icon_state = "pointybush_1" - -/obj/structure/flora/ausbushes/pointybush/Initialize() - icon_state = "pointybush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/lavendergrass - icon_state = "lavendergrass_1" - -/obj/structure/flora/ausbushes/lavendergrass/Initialize() - icon_state = "lavendergrass_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/ywflowers - icon_state = "ywflowers_1" - -/obj/structure/flora/ausbushes/ywflowers/Initialize() - icon_state = "ywflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/brflowers - icon_state = "brflowers_1" - -/obj/structure/flora/ausbushes/brflowers/Initialize() - icon_state = "brflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/ppflowers - icon_state = "ppflowers_1" - -/obj/structure/flora/ausbushes/ppflowers/Initialize() - icon_state = "ppflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/sparsegrass - icon_state = "sparsegrass_1" - -/obj/structure/flora/ausbushes/sparsegrass/Initialize() - icon_state = "sparsegrass_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/fullgrass - icon_state = "fullgrass_1" - -/obj/structure/flora/ausbushes/fullgrass/Initialize() - icon_state = "fullgrass_[rand(1, 3)]" - . = ..() - -/obj/item/kirbyplants - name = "potted plant" - icon = 'icons/obj/flora/plants.dmi' - icon_state = "plant-01" - desc = "A little bit of nature contained in a pot." - layer = ABOVE_MOB_LAYER - w_class = WEIGHT_CLASS_HUGE - force = 10 - throwforce = 13 - throw_speed = 2 - throw_range = 4 - -/obj/item/kirbyplants/ComponentInitialize() - . = ..() - AddComponent(/datum/component/tactical) - addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 500)), 0) - AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_unwielded=10, force_wielded=10) - -/obj/item/kirbyplants/random - icon = 'icons/obj/flora/_flora.dmi' - icon_state = "random_plant" - var/list/static/states - -/obj/item/kirbyplants/random/Initialize() - . = ..() - icon = 'icons/obj/flora/plants.dmi' - if(!states) - generate_states() - icon_state = pick(states) - -/obj/item/kirbyplants/random/proc/generate_states() - states = list() - for(var/i in 1 to 25) - var/number - if(i < 10) - number = "0[i]" - else - number = "[i]" - states += "plant-[number]" - states += "applebush" - - -/obj/item/kirbyplants/dead - name = "RD's potted plant" - desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..." - icon_state = "plant-25" - -/obj/item/kirbyplants/photosynthetic - name = "photosynthetic potted plant" - desc = "A bioluminescent plant." - icon_state = "plant-09" - light_color = "#2cb2e8" - light_range = 3 - -/obj/item/kirbyplants/fullysynthetic - name = "plastic potted plant" - desc = "A fake, cheap looking, plastic tree. Perfect for people who kill every plant they touch." - icon_state = "plant-26" - custom_materials = (list(/datum/material/plastic = 8000)) - -/obj/item/kirbyplants/fullysynthetic/Initialize() - . = ..() - icon_state = "plant-[rand(26, 29)]" - -/obj/item/kirbyplants/potty - name = "Potty the Potted Plant" - desc = "A secret agent staffed in the station's bar to protect the mystical cakehat." - icon_state = "potty" - -//a rock is flora according to where the icon file is -//and now these defines - -/obj/structure/flora/rock - icon_state = "basalt" - desc = "A volcanic rock. Pioneers used to ride these babies for miles." - icon = 'icons/obj/flora/rocks.dmi' - resistance_flags = FIRE_PROOF - density = TRUE - /// Itemstack that is dropped when a rock is mined with a pickaxe - var/obj/item/stack/mineResult = /obj/item/stack/ore/glass/basalt - /// Amount of the itemstack to drop - var/mineAmount = 20 - -/obj/structure/flora/rock/Initialize() - . = ..() - icon_state = "[icon_state][rand(1,3)]" - -/obj/structure/flora/rock/Destroy() - if(mineResult && mineAmount) - new mineResult(loc, mineAmount) - . = ..() - -/obj/structure/flora/rock/attackby(obj/item/W, mob/user, params) - if(!mineResult || W.tool_behaviour != TOOL_MINING) - return ..() - if(flags_1 & NODECONSTRUCT_1) - return ..() - to_chat(user, "You start mining...") - if(W.use_tool(src, user, 40, volume=50)) - to_chat(user, "You finish mining the rock.") - SSblackbox.record_feedback("tally", "pick_used_mining", 1, W.type) - qdel(src) - -/obj/structure/flora/rock/pile - icon_state = "lavarocks" - desc = "A pile of rocks." - -//Jungle grass - -/obj/structure/flora/grass/jungle - name = "jungle grass" - desc = "Thick alien flora." - icon = 'icons/obj/flora/jungleflora.dmi' - icon_state = "grassa" - - -/obj/structure/flora/grass/jungle/Initialize() - icon_state = "[icon_state][rand(1, 5)]" - . = ..() - -/obj/structure/flora/grass/jungle/b - icon_state = "grassb" - -//Jungle rocks - -/obj/structure/flora/rock/jungle - icon_state = "rock" - desc = "A pile of rocks." - icon = 'icons/obj/flora/jungleflora.dmi' - density = FALSE - -/obj/structure/flora/rock/jungle/Initialize() - . = ..() - icon_state = "[initial(icon_state)][rand(1,5)]" - - -//Jungle bushes - -/obj/structure/flora/junglebush - name = "bush" - desc = "A wild plant that is found in jungles." - icon = 'icons/obj/flora/jungleflora.dmi' - icon_state = "busha" - -/obj/structure/flora/junglebush/Initialize() - icon_state = "[icon_state][rand(1, 3)]" - . = ..() - -/obj/structure/flora/junglebush/b - icon_state = "bushb" - -/obj/structure/flora/junglebush/c - icon_state = "bushc" - -/obj/structure/flora/junglebush/large - icon_state = "bush" - icon = 'icons/obj/flora/largejungleflora.dmi' - pixel_x = -16 - pixel_y = -12 - layer = ABOVE_ALL_MOB_LAYER - -/obj/structure/flora/rock/pile/largejungle - name = "rocks" - icon_state = "rocks" - icon = 'icons/obj/flora/largejungleflora.dmi' - density = FALSE - pixel_x = -16 - pixel_y = -16 - -/obj/structure/flora/rock/pile/largejungle/Initialize() - . = ..() - icon_state = "[initial(icon_state)][rand(1,3)]" - +/obj/structure/flora + resistance_flags = FLAMMABLE + max_integrity = 150 + anchored = TRUE + +//trees +/obj/structure/flora/tree + name = "tree" + desc = "A large tree." + density = TRUE + pixel_x = -16 + layer = FLY_LAYER + var/log_amount = 10 + +/obj/structure/flora/tree/attackby(obj/item/W, mob/user, params) + if(log_amount && (!(flags_1 & NODECONSTRUCT_1))) + if(W.get_sharpness() && W.force > 0) + if(W.hitsound) + playsound(get_turf(src), W.hitsound, 100, FALSE, FALSE) + user.visible_message("[user] begins to cut down [src] with [W].","You begin to cut down [src] with [W].", "You hear the sound of sawing.") + if(do_after(user, 1000/W.force, target = src)) //5 seconds with 20 force, 8 seconds with a hatchet, 20 seconds with a shard. + user.visible_message("[user] fells [src] with the [W].","You fell [src] with the [W].", "You hear the sound of a tree falling.") + playsound(get_turf(src), 'sound/effects/meteorimpact.ogg', 100 , FALSE, FALSE) + user.log_message("cut down [src] at [AREACOORD(src)]", LOG_ATTACK) + for(var/i=1 to log_amount) + new /obj/item/grown/log/tree(get_turf(src)) + var/obj/structure/flora/stump/S = new(loc) + S.name = "[name] stump" + qdel(src) + else + return ..() + +/obj/structure/flora/stump + name = "stump" + desc = "This represents our promise to the crew, and the station itself, to cut down as many trees as possible." //running naked through the trees + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "tree_stump" + density = FALSE + pixel_x = -16 + +/obj/structure/flora/tree/pine + name = "pine tree" + desc = "A coniferous pine tree." + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_1" + var/list/icon_states = list("pine_1", "pine_2", "pine_3") + +/obj/structure/flora/tree/pine/Initialize() + . = ..() + + if(islist(icon_states && icon_states.len)) + icon_state = pick(icon_states) + +/obj/structure/flora/tree/pine/xmas + name = "xmas tree" + desc = "A wondrous decorated Christmas tree." + icon_state = "pine_c" + icon_states = null + flags_1 = NODECONSTRUCT_1 //protected by the christmas spirit + +/obj/structure/flora/tree/pine/xmas/presents + icon_state = "pinepresents" + desc = "A wondrous decorated Christmas tree. It has presents!" + var/gift_type = /obj/item/a_gift/anything + var/unlimited = FALSE + var/static/list/took_presents //shared between all xmas trees + +/obj/structure/flora/tree/pine/xmas/presents/Initialize() + . = ..() + if(!took_presents) + took_presents = list() + +/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!user.ckey) + return + + if(took_presents[user.ckey] && !unlimited) + to_chat(user, "There are no presents with your name on.") + return + to_chat(user, "After a bit of rummaging, you locate a gift with your name on it!") + + if(!unlimited) + took_presents[user.ckey] = TRUE + + var/obj/item/G = new gift_type(src) + user.put_in_hands(G) + +/obj/structure/flora/tree/pine/xmas/presents/unlimited + desc = "A wonderous decorated Christmas tree. It has a seemly endless supply of presents!" + unlimited = TRUE + +/obj/structure/flora/tree/dead + icon = 'icons/obj/flora/deadtrees.dmi' + desc = "A dead tree. How it died, you know not." + icon_state = "tree_1" + +/obj/structure/flora/tree/palm + icon = 'icons/misc/beach2.dmi' + desc = "A tree straight from the tropics." + icon_state = "palm1" + +/obj/structure/flora/tree/palm/Initialize() + . = ..() + icon_state = pick("palm1","palm2") + pixel_x = 0 + +/obj/structure/festivus + name = "festivus pole" + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "festivus_pole" + desc = "During last year's Feats of Strength the Research Director was able to suplex this passing immobile rod into a planter." + +/obj/structure/festivus/anchored + name = "suplexed rod" + desc = "A true feat of strength, almost as good as last year." + icon_state = "anchored_rod" + anchored = TRUE + +/obj/structure/flora/tree/dead/Initialize() + icon_state = "tree_[rand(1, 6)]" + . = ..() + +/obj/structure/flora/tree/jungle + name = "tree" + icon_state = "tree" + desc = "It's seriously hampering your view of the jungle." + icon = 'icons/obj/flora/jungletrees.dmi' + pixel_x = -48 + pixel_y = -20 + +/obj/structure/flora/tree/jungle/Initialize() + icon_state = "[icon_state][rand(1, 6)]" + . = ..() + +/obj/structure/flora/tree/jungle/small + pixel_y = 0 + pixel_x = -32 + icon = 'icons/obj/flora/jungletreesmall.dmi' + +//grass +/obj/structure/flora/grass + name = "grass" + desc = "A patch of overgrown grass." + icon = 'icons/obj/flora/snowflora.dmi' + gender = PLURAL //"this is grass" not "this is a grass" + +/obj/structure/flora/grass/brown + icon_state = "snowgrass1bb" + +/obj/structure/flora/grass/brown/Initialize() + icon_state = "snowgrass[rand(1, 3)]bb" + . = ..() + + +/obj/structure/flora/grass/green + icon_state = "snowgrass1gb" + +/obj/structure/flora/grass/green/Initialize() + icon_state = "snowgrass[rand(1, 3)]gb" + . = ..() + +/obj/structure/flora/grass/both + icon_state = "snowgrassall1" + +/obj/structure/flora/grass/both/Initialize() + icon_state = "snowgrassall[rand(1, 3)]" + . = ..() + + +//bushes +/obj/structure/flora/bush + name = "bush" + desc = "Some type of shrub." + icon = 'icons/obj/flora/snowflora.dmi' + icon_state = "snowbush1" + anchored = TRUE + +/obj/structure/flora/bush/Initialize() + icon_state = "snowbush[rand(1, 6)]" + . = ..() + +//newbushes + +/obj/structure/flora/ausbushes + name = "bush" + desc = "Some kind of plant." + icon = 'icons/obj/flora/ausflora.dmi' + icon_state = "firstbush_1" + +/obj/structure/flora/ausbushes/Initialize() + if(icon_state == "firstbush_1") + icon_state = "firstbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/reedbush + icon_state = "reedbush_1" + +/obj/structure/flora/ausbushes/reedbush/Initialize() + icon_state = "reedbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/leafybush + icon_state = "leafybush_1" + +/obj/structure/flora/ausbushes/leafybush/Initialize() + icon_state = "leafybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/palebush + icon_state = "palebush_1" + +/obj/structure/flora/ausbushes/palebush/Initialize() + icon_state = "palebush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/stalkybush + icon_state = "stalkybush_1" + +/obj/structure/flora/ausbushes/stalkybush/Initialize() + icon_state = "stalkybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/grassybush + icon_state = "grassybush_1" + +/obj/structure/flora/ausbushes/grassybush/Initialize() + icon_state = "grassybush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/fernybush + icon_state = "fernybush_1" + +/obj/structure/flora/ausbushes/fernybush/Initialize() + icon_state = "fernybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/sunnybush + icon_state = "sunnybush_1" + +/obj/structure/flora/ausbushes/sunnybush/Initialize() + icon_state = "sunnybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/genericbush + icon_state = "genericbush_1" + +/obj/structure/flora/ausbushes/genericbush/Initialize() + icon_state = "genericbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/pointybush + icon_state = "pointybush_1" + +/obj/structure/flora/ausbushes/pointybush/Initialize() + icon_state = "pointybush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/lavendergrass + icon_state = "lavendergrass_1" + +/obj/structure/flora/ausbushes/lavendergrass/Initialize() + icon_state = "lavendergrass_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/ywflowers + icon_state = "ywflowers_1" + +/obj/structure/flora/ausbushes/ywflowers/Initialize() + icon_state = "ywflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/brflowers + icon_state = "brflowers_1" + +/obj/structure/flora/ausbushes/brflowers/Initialize() + icon_state = "brflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/ppflowers + icon_state = "ppflowers_1" + +/obj/structure/flora/ausbushes/ppflowers/Initialize() + icon_state = "ppflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/sparsegrass + icon_state = "sparsegrass_1" + +/obj/structure/flora/ausbushes/sparsegrass/Initialize() + icon_state = "sparsegrass_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/fullgrass + icon_state = "fullgrass_1" + +/obj/structure/flora/ausbushes/fullgrass/Initialize() + icon_state = "fullgrass_[rand(1, 3)]" + . = ..() + +/obj/item/kirbyplants + name = "potted plant" + icon = 'icons/obj/flora/plants.dmi' + icon_state = "plant-01" + desc = "A little bit of nature contained in a pot." + layer = ABOVE_MOB_LAYER + w_class = WEIGHT_CLASS_HUGE + force = 10 + throwforce = 13 + throw_speed = 2 + throw_range = 4 + +/obj/item/kirbyplants/ComponentInitialize() + . = ..() + AddComponent(/datum/component/tactical) + addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 500)), 0) + AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_unwielded=10, force_wielded=10) + +/obj/item/kirbyplants/random + icon = 'icons/obj/flora/_flora.dmi' + icon_state = "random_plant" + var/list/static/states + +/obj/item/kirbyplants/random/Initialize() + . = ..() + icon = 'icons/obj/flora/plants.dmi' + if(!states) + generate_states() + icon_state = pick(states) + +/obj/item/kirbyplants/random/proc/generate_states() + states = list() + for(var/i in 1 to 25) + var/number + if(i < 10) + number = "0[i]" + else + number = "[i]" + states += "plant-[number]" + states += "applebush" + + +/obj/item/kirbyplants/dead + name = "RD's potted plant" + desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..." + icon_state = "plant-25" + +/obj/item/kirbyplants/photosynthetic + name = "photosynthetic potted plant" + desc = "A bioluminescent plant." + icon_state = "plant-09" + light_color = "#2cb2e8" + light_range = 3 + +/obj/item/kirbyplants/fullysynthetic + name = "plastic potted plant" + desc = "A fake, cheap looking, plastic tree. Perfect for people who kill every plant they touch." + icon_state = "plant-26" + custom_materials = (list(/datum/material/plastic = 8000)) + +/obj/item/kirbyplants/fullysynthetic/Initialize() + . = ..() + icon_state = "plant-[rand(26, 29)]" + +/obj/item/kirbyplants/potty + name = "Potty the Potted Plant" + desc = "A secret agent staffed in the station's bar to protect the mystical cakehat." + icon_state = "potty" + +//a rock is flora according to where the icon file is +//and now these defines + +/obj/structure/flora/rock + icon_state = "basalt" + desc = "A volcanic rock. Pioneers used to ride these babies for miles." + icon = 'icons/obj/flora/rocks.dmi' + resistance_flags = FIRE_PROOF + density = TRUE + /// Itemstack that is dropped when a rock is mined with a pickaxe + var/obj/item/stack/mineResult = /obj/item/stack/ore/glass/basalt + /// Amount of the itemstack to drop + var/mineAmount = 20 + +/obj/structure/flora/rock/Initialize() + . = ..() + icon_state = "[icon_state][rand(1,3)]" + +/obj/structure/flora/rock/Destroy() + if(mineResult && mineAmount) + new mineResult(loc, mineAmount) + . = ..() + +/obj/structure/flora/rock/attackby(obj/item/W, mob/user, params) + if(!mineResult || W.tool_behaviour != TOOL_MINING) + return ..() + if(flags_1 & NODECONSTRUCT_1) + return ..() + to_chat(user, "You start mining...") + if(W.use_tool(src, user, 40, volume=50)) + to_chat(user, "You finish mining the rock.") + SSblackbox.record_feedback("tally", "pick_used_mining", 1, W.type) + qdel(src) + +/obj/structure/flora/rock/pile + icon_state = "lavarocks" + desc = "A pile of rocks." + +//Jungle grass + +/obj/structure/flora/grass/jungle + name = "jungle grass" + desc = "Thick alien flora." + icon = 'icons/obj/flora/jungleflora.dmi' + icon_state = "grassa" + + +/obj/structure/flora/grass/jungle/Initialize() + icon_state = "[icon_state][rand(1, 5)]" + . = ..() + +/obj/structure/flora/grass/jungle/b + icon_state = "grassb" + +//Jungle rocks + +/obj/structure/flora/rock/jungle + icon_state = "rock" + desc = "A pile of rocks." + icon = 'icons/obj/flora/jungleflora.dmi' + density = FALSE + +/obj/structure/flora/rock/jungle/Initialize() + . = ..() + icon_state = "[initial(icon_state)][rand(1,5)]" + + +//Jungle bushes + +/obj/structure/flora/junglebush + name = "bush" + desc = "A wild plant that is found in jungles." + icon = 'icons/obj/flora/jungleflora.dmi' + icon_state = "busha" + +/obj/structure/flora/junglebush/Initialize() + icon_state = "[icon_state][rand(1, 3)]" + . = ..() + +/obj/structure/flora/junglebush/b + icon_state = "bushb" + +/obj/structure/flora/junglebush/c + icon_state = "bushc" + +/obj/structure/flora/junglebush/large + icon_state = "bush" + icon = 'icons/obj/flora/largejungleflora.dmi' + pixel_x = -16 + pixel_y = -12 + layer = ABOVE_ALL_MOB_LAYER + +/obj/structure/flora/rock/pile/largejungle + name = "rocks" + icon_state = "rocks" + icon = 'icons/obj/flora/largejungleflora.dmi' + density = FALSE + pixel_x = -16 + pixel_y = -16 + +/obj/structure/flora/rock/pile/largejungle/Initialize() + . = ..() + icon_state = "[initial(icon_state)][rand(1,3)]" + diff --git a/code/game/objects/structures/hivebot.dm b/code/game/objects/structures/hivebot.dm index 6813b6f3848..51107d7411e 100644 --- a/code/game/objects/structures/hivebot.dm +++ b/code/game/objects/structures/hivebot.dm @@ -1,36 +1,36 @@ -/obj/structure/hivebot_beacon - name = "beacon" - desc = "Some odd beacon thing." - icon = 'icons/mob/hivebot.dmi' - icon_state = "def_radar-off" - anchored = TRUE - density = TRUE - var/bot_type = "norm" - var/bot_amt = 10 - -/obj/structure/hivebot_beacon/Initialize() - . = ..() - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, loc) - smoke.start() - visible_message("[src] warps in!") - playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) - addtimer(CALLBACK(src, .proc/warpbots), rand(10, 600)) - -/obj/structure/hivebot_beacon/proc/warpbots() - icon_state = "def_radar" - visible_message("[src] turns on!") - while(bot_amt > 0) - bot_amt-- - switch(bot_type) - if("norm") - new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) - if("range") - new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) - if("rapid") - new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) - sleep(100) - visible_message("[src] warps out!") - playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) - qdel(src) - return +/obj/structure/hivebot_beacon + name = "beacon" + desc = "Some odd beacon thing." + icon = 'icons/mob/hivebot.dmi' + icon_state = "def_radar-off" + anchored = TRUE + density = TRUE + var/bot_type = "norm" + var/bot_amt = 10 + +/obj/structure/hivebot_beacon/Initialize() + . = ..() + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(2, loc) + smoke.start() + visible_message("[src] warps in!") + playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) + addtimer(CALLBACK(src, .proc/warpbots), rand(10, 600)) + +/obj/structure/hivebot_beacon/proc/warpbots() + icon_state = "def_radar" + visible_message("[src] turns on!") + while(bot_amt > 0) + bot_amt-- + switch(bot_type) + if("norm") + new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) + if("range") + new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) + if("rapid") + new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) + sleep(100) + visible_message("[src] warps out!") + playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) + qdel(src) + return diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm index 888ef613457..9cde0769013 100644 --- a/code/game/objects/structures/kitchen_spike.dm +++ b/code/game/objects/structures/kitchen_spike.dm @@ -1,149 +1,149 @@ -//////Kitchen Spike -#define VIABLE_MOB_CHECK(X) (isliving(X) && !issilicon(X) && !isbot(X)) - -/obj/structure/kitchenspike_frame - name = "meatspike frame" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spikeframe" - desc = "The frame of a meat spike." - density = TRUE - anchored = FALSE - max_integrity = 200 - -/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) - add_fingerprint(user) - if(default_unfasten_wrench(user, I)) - return - else if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if(R.get_amount() >= 4) - R.use(4) - to_chat(user, "You add spikes to the frame.") - var/obj/F = new /obj/structure/kitchenspike(src.loc) - transfer_fingerprints_to(F) - qdel(src) - else if(I.tool_behaviour == TOOL_WELDER) - if(!I.tool_start_check(user, amount=0)) - return - to_chat(user, "You begin cutting \the [src] apart...") - if(I.use_tool(src, user, 50, volume=50)) - visible_message("[user] slices apart \the [src].", - "You cut \the [src] apart with \the [I].", - "You hear welding.") - new /obj/item/stack/sheet/metal(src.loc, 4) - qdel(src) - return - else - return ..() - -/obj/structure/kitchenspike - name = "meat spike" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spike" - desc = "A spike for collecting meat from animals." - density = TRUE - anchored = TRUE - buckle_lying = 0 - can_buckle = 1 - max_integrity = 250 - -/obj/structure/kitchenspike/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/kitchenspike/crowbar_act(mob/living/user, obj/item/I) - if(has_buckled_mobs()) - to_chat(user, "You can't do that while something's on the spike!") - return TRUE - - if(I.use_tool(src, user, 20, volume=100)) - to_chat(user, "You pry the spikes out of the frame.") - deconstruct(TRUE) - return TRUE - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/kitchenspike/attack_hand(mob/user) - if(VIABLE_MOB_CHECK(user.pulling) && user.a_intent == INTENT_GRAB && !has_buckled_mobs()) - var/mob/living/L = user.pulling - if(do_mob(user, src, 120)) - if(has_buckled_mobs()) //to prevent spam/queing up attacks - return - if(L.buckled) - return - if(user.pulling != L) - return - playsound(src.loc, 'sound/effects/splat.ogg', 25, TRUE) - L.visible_message("[user] slams [L] onto the meat spike!", "[user] slams you onto the meat spike!", "You hear a squishy wet noise.") - L.forceMove(drop_location()) - L.emote("scream") - L.add_splatter_floor() - L.adjustBruteLoss(30) - L.setDir(2) - buckle_mob(L, force=1) - var/matrix/m180 = matrix(L.transform) - m180.Turn(180) - animate(L, transform = m180, time = 3) - L.pixel_y = L.get_standard_pixel_y_offset(180) - else if (has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - user_unbuckle_mob(L, user) - else - ..() - - - -/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking - return - -/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) - if(buckled_mob) - var/mob/living/M = buckled_mob - if(M != user) - M.visible_message("[user] tries to pull [M] free of [src]!",\ - "[user] is trying to pull you off [src], opening up fresh wounds!",\ - "You hear a squishy wet noise.") - if(!do_after(user, 300, target = src)) - if(M && M.buckled) - M.visible_message("[user] fails to free [M]!",\ - "[user] fails to pull you off of [src].") - return - - else - M.visible_message("[M] struggles to break free from [src]!",\ - "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ - "You hear a wet squishing noise..") - M.adjustBruteLoss(30) - if(!do_after(M, 1200, target = src)) - if(M && M.buckled) - to_chat(M, "You fail to free yourself!") - return - if(!M.buckled) - return - release_mob(M) - -/obj/structure/kitchenspike/proc/release_mob(mob/living/M) - var/matrix/m180 = matrix(M.transform) - m180.Turn(180) - animate(M, transform = m180, time = 3) - M.pixel_y = M.get_standard_pixel_y_offset(180) - M.adjustBruteLoss(30) - src.visible_message(text("[M] falls free of [src]!")) - unbuckle_mob(M,force=1) - M.emote("scream") - M.AdjustParalyzed(20) - -/obj/structure/kitchenspike/Destroy() - if(has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - release_mob(L) - return ..() - -/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) - if(disassembled) - var/obj/F = new /obj/structure/kitchenspike_frame(src.loc) - transfer_fingerprints_to(F) - else - new /obj/item/stack/sheet/metal(src.loc, 4) - new /obj/item/stack/rods(loc, 4) - qdel(src) - -#undef VIABLE_MOB_CHECK +//////Kitchen Spike +#define VIABLE_MOB_CHECK(X) (isliving(X) && !issilicon(X) && !isbot(X)) + +/obj/structure/kitchenspike_frame + name = "meatspike frame" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spikeframe" + desc = "The frame of a meat spike." + density = TRUE + anchored = FALSE + max_integrity = 200 + +/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) + add_fingerprint(user) + if(default_unfasten_wrench(user, I)) + return + else if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if(R.get_amount() >= 4) + R.use(4) + to_chat(user, "You add spikes to the frame.") + var/obj/F = new /obj/structure/kitchenspike(src.loc) + transfer_fingerprints_to(F) + qdel(src) + else if(I.tool_behaviour == TOOL_WELDER) + if(!I.tool_start_check(user, amount=0)) + return + to_chat(user, "You begin cutting \the [src] apart...") + if(I.use_tool(src, user, 50, volume=50)) + visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [I].", + "You hear welding.") + new /obj/item/stack/sheet/metal(src.loc, 4) + qdel(src) + return + else + return ..() + +/obj/structure/kitchenspike + name = "meat spike" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spike" + desc = "A spike for collecting meat from animals." + density = TRUE + anchored = TRUE + buckle_lying = 0 + can_buckle = 1 + max_integrity = 250 + +/obj/structure/kitchenspike/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/kitchenspike/crowbar_act(mob/living/user, obj/item/I) + if(has_buckled_mobs()) + to_chat(user, "You can't do that while something's on the spike!") + return TRUE + + if(I.use_tool(src, user, 20, volume=100)) + to_chat(user, "You pry the spikes out of the frame.") + deconstruct(TRUE) + return TRUE + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/structure/kitchenspike/attack_hand(mob/user) + if(VIABLE_MOB_CHECK(user.pulling) && user.a_intent == INTENT_GRAB && !has_buckled_mobs()) + var/mob/living/L = user.pulling + if(do_mob(user, src, 120)) + if(has_buckled_mobs()) //to prevent spam/queing up attacks + return + if(L.buckled) + return + if(user.pulling != L) + return + playsound(src.loc, 'sound/effects/splat.ogg', 25, TRUE) + L.visible_message("[user] slams [L] onto the meat spike!", "[user] slams you onto the meat spike!", "You hear a squishy wet noise.") + L.forceMove(drop_location()) + L.emote("scream") + L.add_splatter_floor() + L.adjustBruteLoss(30) + L.setDir(2) + buckle_mob(L, force=1) + var/matrix/m180 = matrix(L.transform) + m180.Turn(180) + animate(L, transform = m180, time = 3) + L.pixel_y = L.get_standard_pixel_y_offset(180) + else if (has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + user_unbuckle_mob(L, user) + else + ..() + + + +/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking + return + +/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) + if(buckled_mob) + var/mob/living/M = buckled_mob + if(M != user) + M.visible_message("[user] tries to pull [M] free of [src]!",\ + "[user] is trying to pull you off [src], opening up fresh wounds!",\ + "You hear a squishy wet noise.") + if(!do_after(user, 300, target = src)) + if(M && M.buckled) + M.visible_message("[user] fails to free [M]!",\ + "[user] fails to pull you off of [src].") + return + + else + M.visible_message("[M] struggles to break free from [src]!",\ + "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ + "You hear a wet squishing noise..") + M.adjustBruteLoss(30) + if(!do_after(M, 1200, target = src)) + if(M && M.buckled) + to_chat(M, "You fail to free yourself!") + return + if(!M.buckled) + return + release_mob(M) + +/obj/structure/kitchenspike/proc/release_mob(mob/living/M) + var/matrix/m180 = matrix(M.transform) + m180.Turn(180) + animate(M, transform = m180, time = 3) + M.pixel_y = M.get_standard_pixel_y_offset(180) + M.adjustBruteLoss(30) + src.visible_message(text("[M] falls free of [src]!")) + unbuckle_mob(M,force=1) + M.emote("scream") + M.AdjustParalyzed(20) + +/obj/structure/kitchenspike/Destroy() + if(has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + release_mob(L) + return ..() + +/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) + if(disassembled) + var/obj/F = new /obj/structure/kitchenspike_frame(src.loc) + transfer_fingerprints_to(F) + else + new /obj/item/stack/sheet/metal(src.loc, 4) + new /obj/item/stack/rods(loc, 4) + qdel(src) + +#undef VIABLE_MOB_CHECK diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index a1bdf9fa198..3fa4d4f3b18 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -1,191 +1,191 @@ -// Basic ladder. By default links to the z-level above/below. -/obj/structure/ladder - name = "ladder" - desc = "A sturdy metal ladder." - icon = 'icons/obj/structures.dmi' - icon_state = "ladder11" - anchored = TRUE - var/obj/structure/ladder/down //the ladder below this one - var/obj/structure/ladder/up //the ladder above this one - obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN - -/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down) - ..() - if (up) - src.up = up - up.down = src - up.update_icon() - if (down) - src.down = down - down.up = src - down.update_icon() - return INITIALIZE_HINT_LATELOAD - -/obj/structure/ladder/Destroy(force) - if ((resistance_flags & INDESTRUCTIBLE) && !force) - return QDEL_HINT_LETMELIVE - disconnect() - return ..() - -/obj/structure/ladder/LateInitialize() - // By default, discover ladders above and below us vertically - var/turf/T = get_turf(src) - var/obj/structure/ladder/L - - if (!down) - L = locate() in SSmapping.get_turf_below(T) - if (L) - down = L - L.up = src // Don't waste effort looping the other way - L.update_icon() - if (!up) - L = locate() in SSmapping.get_turf_above(T) - if (L) - up = L - L.down = src // Don't waste effort looping the other way - L.update_icon() - - update_icon() - -/obj/structure/ladder/proc/disconnect() - if(up && up.down == src) - up.down = null - up.update_icon() - if(down && down.up == src) - down.up = null - down.update_icon() - up = down = null - -/obj/structure/ladder/update_icon_state() - if(up && down) - icon_state = "ladder11" - else if(up) - icon_state = "ladder10" - else if(down) - icon_state = "ladder01" - else //wtf make your ladders properly assholes - icon_state = "ladder00" - -/obj/structure/ladder/singularity_pull() - if (!(resistance_flags & INDESTRUCTIBLE)) - visible_message("[src] is torn to pieces by the gravitational pull!") - qdel(src) - -/obj/structure/ladder/proc/travel(going_up, mob/user, is_ghost, obj/structure/ladder/ladder) - if(!is_ghost) - show_fluff_message(going_up, user) - ladder.add_fingerprint(user) - - var/turf/T = get_turf(ladder) - var/atom/movable/AM - if(user.pulling) - AM = user.pulling - AM.forceMove(T) - user.forceMove(T) - if(AM) - user.start_pulling(AM) - -/obj/structure/ladder/proc/use(mob/user, is_ghost=FALSE) - if (!is_ghost && !in_range(src, user)) - return - - var/list/tool_list = list( - "Up" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), - "Down" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH) - ) - - if (up && down) - var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE) - if (!is_ghost && !in_range(src, user)) - return // nice try - switch(result) - if("Up") - travel(TRUE, user, is_ghost, up) - if("Down") - travel(FALSE, user, is_ghost, down) - if("Cancel") - return - else if(up) - travel(TRUE, user, is_ghost, up) - else if(down) - travel(FALSE, user, is_ghost, down) - else - to_chat(user, "[src] doesn't seem to lead anywhere!") - - if(!is_ghost) - add_fingerprint(user) - -/obj/structure/ladder/proc/check_menu(mob/user) - if(user.incapacitated() || !user.Adjacent(src)) - return FALSE - return TRUE - -/obj/structure/ladder/attack_hand(mob/user) - . = ..() - if(.) - return - use(user) - -/obj/structure/ladder/attack_paw(mob/user) - return use(user) - -/obj/structure/ladder/attackby(obj/item/W, mob/user, params) - return use(user) - -/obj/structure/ladder/attack_robot(mob/living/silicon/robot/R) - if(R.Adjacent(src)) - return use(R) - -//ATTACK GHOST IGNORING PARENT RETURN VALUE -/obj/structure/ladder/attack_ghost(mob/dead/observer/user) - use(user, TRUE) - return ..() - -/obj/structure/ladder/proc/show_fluff_message(going_up, mob/user) - if(going_up) - user.visible_message("[user] climbs up [src].", "You climb up [src].") - else - user.visible_message("[user] climbs down [src].", "You climb down [src].") - - -// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z. -/obj/structure/ladder/unbreakable - name = "sturdy ladder" - desc = "An extremely sturdy metal ladder." - resistance_flags = INDESTRUCTIBLE - var/id - var/height = 0 // higher numbers are considered physically higher - -/obj/structure/ladder/unbreakable/Initialize() - GLOB.ladders += src - return ..() - -/obj/structure/ladder/unbreakable/Destroy() - . = ..() - if (. != QDEL_HINT_LETMELIVE) - GLOB.ladders -= src - -/obj/structure/ladder/unbreakable/LateInitialize() - // Override the parent to find ladders based on being height-linked - if (!id || (up && down)) - update_icon() - return - - for (var/O in GLOB.ladders) - var/obj/structure/ladder/unbreakable/L = O - if (L.id != id) - continue // not one of our pals - if (!down && L.height == height - 1) - down = L - L.up = src - L.update_icon() - if (up) - break // break if both our connections are filled - else if (!up && L.height == height + 1) - up = L - L.down = src - L.update_icon() - if (down) - break // break if both our connections are filled - - update_icon() +// Basic ladder. By default links to the z-level above/below. +/obj/structure/ladder + name = "ladder" + desc = "A sturdy metal ladder." + icon = 'icons/obj/structures.dmi' + icon_state = "ladder11" + anchored = TRUE + var/obj/structure/ladder/down //the ladder below this one + var/obj/structure/ladder/up //the ladder above this one + obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN + +/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down) + ..() + if (up) + src.up = up + up.down = src + up.update_icon() + if (down) + src.down = down + down.up = src + down.update_icon() + return INITIALIZE_HINT_LATELOAD + +/obj/structure/ladder/Destroy(force) + if ((resistance_flags & INDESTRUCTIBLE) && !force) + return QDEL_HINT_LETMELIVE + disconnect() + return ..() + +/obj/structure/ladder/LateInitialize() + // By default, discover ladders above and below us vertically + var/turf/T = get_turf(src) + var/obj/structure/ladder/L + + if (!down) + L = locate() in SSmapping.get_turf_below(T) + if (L) + down = L + L.up = src // Don't waste effort looping the other way + L.update_icon() + if (!up) + L = locate() in SSmapping.get_turf_above(T) + if (L) + up = L + L.down = src // Don't waste effort looping the other way + L.update_icon() + + update_icon() + +/obj/structure/ladder/proc/disconnect() + if(up && up.down == src) + up.down = null + up.update_icon() + if(down && down.up == src) + down.up = null + down.update_icon() + up = down = null + +/obj/structure/ladder/update_icon_state() + if(up && down) + icon_state = "ladder11" + else if(up) + icon_state = "ladder10" + else if(down) + icon_state = "ladder01" + else //wtf make your ladders properly assholes + icon_state = "ladder00" + +/obj/structure/ladder/singularity_pull() + if (!(resistance_flags & INDESTRUCTIBLE)) + visible_message("[src] is torn to pieces by the gravitational pull!") + qdel(src) + +/obj/structure/ladder/proc/travel(going_up, mob/user, is_ghost, obj/structure/ladder/ladder) + if(!is_ghost) + show_fluff_message(going_up, user) + ladder.add_fingerprint(user) + + var/turf/T = get_turf(ladder) + var/atom/movable/AM + if(user.pulling) + AM = user.pulling + AM.forceMove(T) + user.forceMove(T) + if(AM) + user.start_pulling(AM) + +/obj/structure/ladder/proc/use(mob/user, is_ghost=FALSE) + if (!is_ghost && !in_range(src, user)) + return + + var/list/tool_list = list( + "Up" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), + "Down" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH) + ) + + if (up && down) + var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE) + if (!is_ghost && !in_range(src, user)) + return // nice try + switch(result) + if("Up") + travel(TRUE, user, is_ghost, up) + if("Down") + travel(FALSE, user, is_ghost, down) + if("Cancel") + return + else if(up) + travel(TRUE, user, is_ghost, up) + else if(down) + travel(FALSE, user, is_ghost, down) + else + to_chat(user, "[src] doesn't seem to lead anywhere!") + + if(!is_ghost) + add_fingerprint(user) + +/obj/structure/ladder/proc/check_menu(mob/user) + if(user.incapacitated() || !user.Adjacent(src)) + return FALSE + return TRUE + +/obj/structure/ladder/attack_hand(mob/user) + . = ..() + if(.) + return + use(user) + +/obj/structure/ladder/attack_paw(mob/user) + return use(user) + +/obj/structure/ladder/attackby(obj/item/W, mob/user, params) + return use(user) + +/obj/structure/ladder/attack_robot(mob/living/silicon/robot/R) + if(R.Adjacent(src)) + return use(R) + +//ATTACK GHOST IGNORING PARENT RETURN VALUE +/obj/structure/ladder/attack_ghost(mob/dead/observer/user) + use(user, TRUE) + return ..() + +/obj/structure/ladder/proc/show_fluff_message(going_up, mob/user) + if(going_up) + user.visible_message("[user] climbs up [src].", "You climb up [src].") + else + user.visible_message("[user] climbs down [src].", "You climb down [src].") + + +// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z. +/obj/structure/ladder/unbreakable + name = "sturdy ladder" + desc = "An extremely sturdy metal ladder." + resistance_flags = INDESTRUCTIBLE + var/id + var/height = 0 // higher numbers are considered physically higher + +/obj/structure/ladder/unbreakable/Initialize() + GLOB.ladders += src + return ..() + +/obj/structure/ladder/unbreakable/Destroy() + . = ..() + if (. != QDEL_HINT_LETMELIVE) + GLOB.ladders -= src + +/obj/structure/ladder/unbreakable/LateInitialize() + // Override the parent to find ladders based on being height-linked + if (!id || (up && down)) + update_icon() + return + + for (var/O in GLOB.ladders) + var/obj/structure/ladder/unbreakable/L = O + if (L.id != id) + continue // not one of our pals + if (!down && L.height == height - 1) + down = L + L.up = src + L.update_icon() + if (up) + break // break if both our connections are filled + else if (!up && L.height == height + 1) + up = L + L.down = src + L.update_icon() + if (down) + break // break if both our connections are filled + + update_icon() diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm index 4cce22f8232..4ca79754e22 100644 --- a/code/game/objects/structures/manned_turret.dm +++ b/code/game/objects/structures/manned_turret.dm @@ -1,216 +1,216 @@ -/////// MANNED TURRET //////// - -/obj/machinery/manned_turret - name = "machine gun turret" - desc = "While the trigger is held down, this gun will redistribute recoil to allow its user to easily shift targets." - icon = 'icons/obj/turrets.dmi' - icon_state = "machinegun" - can_buckle = TRUE - anchored = FALSE - density = TRUE - max_integrity = 100 - buckle_lying = FALSE - layer = ABOVE_MOB_LAYER - var/view_range = 2.5 - var/cooldown = 0 - var/projectile_type = /obj/projectile/bullet/manned_turret - var/rate_of_fire = 1 - var/number_of_shots = 40 - var/cooldown_duration = 90 - var/atom/target - var/turf/target_turf - var/warned = FALSE - var/list/calculated_projectile_vars - -/obj/machinery/manned_turret/Destroy() - target = null - target_turf = null - ..() - -//BUCKLE HOOKS - -/obj/machinery/manned_turret/unbuckle_mob(mob/living/buckled_mob,force = FALSE) - playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) - for(var/obj/item/I in buckled_mob.held_items) - if(istype(I, /obj/item/gun_control)) - qdel(I) - if(istype(buckled_mob)) - buckled_mob.pixel_x = 0 - buckled_mob.pixel_y = 0 - if(buckled_mob.client) - buckled_mob.client.view_size.resetToDefault() - anchored = FALSE - . = ..() - STOP_PROCESSING(SSfastprocess, src) - -/obj/machinery/manned_turret/user_buckle_mob(mob/living/M, mob/living/carbon/user) - if(user.incapacitated() || !istype(user)) - return - M.forceMove(get_turf(src)) - . = ..() - if(!.) - return - for(var/V in M.held_items) - var/obj/item/I = V - if(istype(I)) - if(M.dropItemToGround(I)) - var/obj/item/gun_control/TC = new(src) - M.put_in_hands(TC) - else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand - var/obj/item/gun_control/TC = new(src) - M.put_in_hands(TC) - M.pixel_y = 14 - layer = ABOVE_MOB_LAYER - setDir(SOUTH) - playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) - anchored = TRUE - if(M.client) - M.client.view_size.setTo(view_range) - START_PROCESSING(SSfastprocess, src) - -/obj/machinery/manned_turret/process() - if (!update_positioning()) - return PROCESS_KILL - -/obj/machinery/manned_turret/proc/update_positioning() - if (!LAZYLEN(buckled_mobs)) - return FALSE - var/mob/living/controller = buckled_mobs[1] - if(!istype(controller)) - return FALSE - var/client/C = controller.client - if(C) - var/atom/A = C.mouseObject - var/turf/T = get_turf(A) - if(istype(T)) //They're hovering over something in the map. - direction_track(controller, T) - calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, C.mouseParams) - -/obj/machinery/manned_turret/proc/direction_track(mob/user, atom/targeted) - if(user.incapacitated()) - return - setDir(get_dir(src,targeted)) - user.setDir(dir) - switch(dir) - if(NORTH) - layer = BELOW_MOB_LAYER - user.pixel_x = 0 - user.pixel_y = -14 - if(NORTHEAST) - layer = BELOW_MOB_LAYER - user.pixel_x = -8 - user.pixel_y = -4 - if(EAST) - layer = ABOVE_MOB_LAYER - user.pixel_x = -14 - user.pixel_y = 0 - if(SOUTHEAST) - layer = BELOW_MOB_LAYER - user.pixel_x = -8 - user.pixel_y = 4 - if(SOUTH) - layer = ABOVE_MOB_LAYER - user.pixel_x = 0 - user.pixel_y = 14 - if(SOUTHWEST) - layer = BELOW_MOB_LAYER - user.pixel_x = 8 - user.pixel_y = 4 - if(WEST) - layer = ABOVE_MOB_LAYER - user.pixel_x = 14 - user.pixel_y = 0 - if(NORTHWEST) - layer = BELOW_MOB_LAYER - user.pixel_x = 8 - user.pixel_y = -4 - -/obj/machinery/manned_turret/proc/checkfire(atom/targeted_atom, mob/user) - target = targeted_atom - if(target == user || user.incapacitated() || target == get_turf(src)) - return - if(world.time < cooldown) - if(!warned && world.time > (cooldown - cooldown_duration + rate_of_fire*number_of_shots)) // To capture the window where one is done firing - warned = TRUE - playsound(src, 'sound/weapons/sear.ogg', 100, TRUE) - return - else - cooldown = world.time + cooldown_duration - warned = FALSE - volley(user) - -/obj/machinery/manned_turret/proc/volley(mob/user) - target_turf = get_turf(target) - for(var/i in 1 to number_of_shots) - addtimer(CALLBACK(src, /obj/machinery/manned_turret/.proc/fire_helper, user), i*rate_of_fire) - -/obj/machinery/manned_turret/proc/fire_helper(mob/user) - if(user.incapacitated() || !(user in buckled_mobs)) - return - update_positioning() //REFRESH MOUSE TRACKING!! - var/turf/targets_from = get_turf(src) - if(QDELETED(target)) - target = target_turf - var/obj/projectile/P = new projectile_type(targets_from) - P.starting = targets_from - P.firer = user - P.original = target - playsound(src, 'sound/weapons/gun/smg/shot.ogg', 75, TRUE) - P.xo = target.x - targets_from.x - P.yo = target.y - targets_from.y - P.Angle = calculated_projectile_vars[1] + rand(-9, 9) - P.p_x = calculated_projectile_vars[2] - P.p_y = calculated_projectile_vars[3] - P.fire() - -/obj/machinery/manned_turret/ultimate // Admin-only proof of concept for autoclicker automatics - name = "Infinity Gun" - view_range = 12 - projectile_type = /obj/projectile/bullet/manned_turret - -/obj/machinery/manned_turret/ultimate/checkfire(atom/targeted_atom, mob/user) - target = targeted_atom - if(target == user || target == get_turf(src)) - return - target_turf = get_turf(target) - fire_helper(user) - -/obj/item/gun_control - name = "turret controls" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "offhand" - w_class = WEIGHT_CLASS_HUGE - item_flags = ABSTRACT | NOBLUDGEON | DROPDEL - resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/obj/machinery/manned_turret/turret - -/obj/item/gun_control/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - turret = loc - if(!istype(turret)) - return INITIALIZE_HINT_QDEL - -/obj/item/gun_control/Destroy() - turret = null - ..() - -/obj/item/gun_control/CanItemAutoclick() - return TRUE - -/obj/item/gun_control/attack_obj(obj/O, mob/living/user) - user.changeNext_move(CLICK_CD_MELEE) - O.attacked_by(src, user) - -/obj/item/gun_control/attack(mob/living/M, mob/living/user) - M.lastattacker = user.real_name - M.lastattackerckey = user.ckey - M.attacked_by(src, user) - add_fingerprint(user) - -/obj/item/gun_control/afterattack(atom/targeted_atom, mob/user, flag, params) - . = ..() - var/obj/machinery/manned_turret/E = user.buckled - E.calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(user, params) - E.direction_track(user, targeted_atom) - E.checkfire(targeted_atom, user) +/////// MANNED TURRET //////// + +/obj/machinery/manned_turret + name = "machine gun turret" + desc = "While the trigger is held down, this gun will redistribute recoil to allow its user to easily shift targets." + icon = 'icons/obj/turrets.dmi' + icon_state = "machinegun" + can_buckle = TRUE + anchored = FALSE + density = TRUE + max_integrity = 100 + buckle_lying = FALSE + layer = ABOVE_MOB_LAYER + var/view_range = 2.5 + var/cooldown = 0 + var/projectile_type = /obj/projectile/bullet/manned_turret + var/rate_of_fire = 1 + var/number_of_shots = 40 + var/cooldown_duration = 90 + var/atom/target + var/turf/target_turf + var/warned = FALSE + var/list/calculated_projectile_vars + +/obj/machinery/manned_turret/Destroy() + target = null + target_turf = null + ..() + +//BUCKLE HOOKS + +/obj/machinery/manned_turret/unbuckle_mob(mob/living/buckled_mob,force = FALSE) + playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) + for(var/obj/item/I in buckled_mob.held_items) + if(istype(I, /obj/item/gun_control)) + qdel(I) + if(istype(buckled_mob)) + buckled_mob.pixel_x = 0 + buckled_mob.pixel_y = 0 + if(buckled_mob.client) + buckled_mob.client.view_size.resetToDefault() + anchored = FALSE + . = ..() + STOP_PROCESSING(SSfastprocess, src) + +/obj/machinery/manned_turret/user_buckle_mob(mob/living/M, mob/living/carbon/user) + if(user.incapacitated() || !istype(user)) + return + M.forceMove(get_turf(src)) + . = ..() + if(!.) + return + for(var/V in M.held_items) + var/obj/item/I = V + if(istype(I)) + if(M.dropItemToGround(I)) + var/obj/item/gun_control/TC = new(src) + M.put_in_hands(TC) + else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand + var/obj/item/gun_control/TC = new(src) + M.put_in_hands(TC) + M.pixel_y = 14 + layer = ABOVE_MOB_LAYER + setDir(SOUTH) + playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) + anchored = TRUE + if(M.client) + M.client.view_size.setTo(view_range) + START_PROCESSING(SSfastprocess, src) + +/obj/machinery/manned_turret/process() + if (!update_positioning()) + return PROCESS_KILL + +/obj/machinery/manned_turret/proc/update_positioning() + if (!LAZYLEN(buckled_mobs)) + return FALSE + var/mob/living/controller = buckled_mobs[1] + if(!istype(controller)) + return FALSE + var/client/C = controller.client + if(C) + var/atom/A = C.mouseObject + var/turf/T = get_turf(A) + if(istype(T)) //They're hovering over something in the map. + direction_track(controller, T) + calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, C.mouseParams) + +/obj/machinery/manned_turret/proc/direction_track(mob/user, atom/targeted) + if(user.incapacitated()) + return + setDir(get_dir(src,targeted)) + user.setDir(dir) + switch(dir) + if(NORTH) + layer = BELOW_MOB_LAYER + user.pixel_x = 0 + user.pixel_y = -14 + if(NORTHEAST) + layer = BELOW_MOB_LAYER + user.pixel_x = -8 + user.pixel_y = -4 + if(EAST) + layer = ABOVE_MOB_LAYER + user.pixel_x = -14 + user.pixel_y = 0 + if(SOUTHEAST) + layer = BELOW_MOB_LAYER + user.pixel_x = -8 + user.pixel_y = 4 + if(SOUTH) + layer = ABOVE_MOB_LAYER + user.pixel_x = 0 + user.pixel_y = 14 + if(SOUTHWEST) + layer = BELOW_MOB_LAYER + user.pixel_x = 8 + user.pixel_y = 4 + if(WEST) + layer = ABOVE_MOB_LAYER + user.pixel_x = 14 + user.pixel_y = 0 + if(NORTHWEST) + layer = BELOW_MOB_LAYER + user.pixel_x = 8 + user.pixel_y = -4 + +/obj/machinery/manned_turret/proc/checkfire(atom/targeted_atom, mob/user) + target = targeted_atom + if(target == user || user.incapacitated() || target == get_turf(src)) + return + if(world.time < cooldown) + if(!warned && world.time > (cooldown - cooldown_duration + rate_of_fire*number_of_shots)) // To capture the window where one is done firing + warned = TRUE + playsound(src, 'sound/weapons/sear.ogg', 100, TRUE) + return + else + cooldown = world.time + cooldown_duration + warned = FALSE + volley(user) + +/obj/machinery/manned_turret/proc/volley(mob/user) + target_turf = get_turf(target) + for(var/i in 1 to number_of_shots) + addtimer(CALLBACK(src, /obj/machinery/manned_turret/.proc/fire_helper, user), i*rate_of_fire) + +/obj/machinery/manned_turret/proc/fire_helper(mob/user) + if(user.incapacitated() || !(user in buckled_mobs)) + return + update_positioning() //REFRESH MOUSE TRACKING!! + var/turf/targets_from = get_turf(src) + if(QDELETED(target)) + target = target_turf + var/obj/projectile/P = new projectile_type(targets_from) + P.starting = targets_from + P.firer = user + P.original = target + playsound(src, 'sound/weapons/gun/smg/shot.ogg', 75, TRUE) + P.xo = target.x - targets_from.x + P.yo = target.y - targets_from.y + P.Angle = calculated_projectile_vars[1] + rand(-9, 9) + P.p_x = calculated_projectile_vars[2] + P.p_y = calculated_projectile_vars[3] + P.fire() + +/obj/machinery/manned_turret/ultimate // Admin-only proof of concept for autoclicker automatics + name = "Infinity Gun" + view_range = 12 + projectile_type = /obj/projectile/bullet/manned_turret + +/obj/machinery/manned_turret/ultimate/checkfire(atom/targeted_atom, mob/user) + target = targeted_atom + if(target == user || target == get_turf(src)) + return + target_turf = get_turf(target) + fire_helper(user) + +/obj/item/gun_control + name = "turret controls" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "offhand" + w_class = WEIGHT_CLASS_HUGE + item_flags = ABSTRACT | NOBLUDGEON | DROPDEL + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/obj/machinery/manned_turret/turret + +/obj/item/gun_control/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + turret = loc + if(!istype(turret)) + return INITIALIZE_HINT_QDEL + +/obj/item/gun_control/Destroy() + turret = null + ..() + +/obj/item/gun_control/CanItemAutoclick() + return TRUE + +/obj/item/gun_control/attack_obj(obj/O, mob/living/user) + user.changeNext_move(CLICK_CD_MELEE) + O.attacked_by(src, user) + +/obj/item/gun_control/attack(mob/living/M, mob/living/user) + M.lastattacker = user.real_name + M.lastattackerckey = user.ckey + M.attacked_by(src, user) + add_fingerprint(user) + +/obj/item/gun_control/afterattack(atom/targeted_atom, mob/user, flag, params) + . = ..() + var/obj/machinery/manned_turret/E = user.buckled + E.calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(user, params) + E.direction_track(user, targeted_atom) + E.checkfire(targeted_atom, user) diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm index 1098f8cedd7..3bb560eedf9 100644 --- a/code/game/objects/structures/mineral_doors.dm +++ b/code/game/objects/structures/mineral_doors.dm @@ -1,350 +1,350 @@ -//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its -//machineryness - -/obj/structure/mineral_door - name = "metal door" - density = TRUE - anchored = TRUE - opacity = TRUE - layer = CLOSED_DOOR_LAYER - - icon = 'icons/obj/doors/mineral_doors.dmi' - icon_state = "metal" - max_integrity = 200 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) - CanAtmosPass = ATMOS_PASS_DENSITY - flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1 - rad_insulation = RAD_MEDIUM_INSULATION - - var/door_opened = FALSE //if it's open or not. - var/isSwitchingStates = FALSE //don't try to change stats if we're already opening - - var/close_delay = -1 //-1 if does not auto close. - var/openSound = 'sound/effects/stonedoor_openclose.ogg' - var/closeSound = 'sound/effects/stonedoor_openclose.ogg' - - var/sheetType = /obj/item/stack/sheet/metal //what we're made of - var/sheetAmount = 7 //how much we drop when deconstructed - -/obj/structure/mineral_door/Initialize() - . = ..() - - air_update_turf(TRUE) - -/obj/structure/mineral_door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/structure/mineral_door/Bumped(atom/movable/AM) - ..() - if(!door_opened) - return TryToSwitchState(AM) - -/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral - if(isAI(user)) //so the AI can't open it - return - else if(iscyborg(user)) //but cyborgs can - if(get_dist(user,src) <= 1) //not remotely though - return TryToSwitchState(user) - -/obj/structure/mineral_door/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/mineral_door/attack_hand(mob/user) - . = ..() - if(.) - return - return TryToSwitchState(user) - -/obj/structure/mineral_door/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover, /obj/effect/beam)) - return !opacity - -/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) - if(isSwitchingStates || !anchored) - return - if(isliving(user)) - var/mob/living/M = user - if(world.time - M.last_bumped <= 60) - return //NOTE do we really need that? - if(M.client) - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.handcuffed) - SwitchState() - else - SwitchState() - else if(ismecha(user)) - SwitchState() - -/obj/structure/mineral_door/proc/SwitchState() - if(door_opened) - Close() - else - Open() - -/obj/structure/mineral_door/proc/Open() - isSwitchingStates = TRUE - playsound(src, openSound, 100, TRUE) - set_opacity(FALSE) - flick("[initial(icon_state)]opening",src) - sleep(10) - density = FALSE - door_opened = TRUE - layer = OPEN_DOOR_LAYER - air_update_turf(1) - update_icon() - isSwitchingStates = FALSE - - if(close_delay != -1) - addtimer(CALLBACK(src, .proc/Close), close_delay) - -/obj/structure/mineral_door/proc/Close() - if(isSwitchingStates || !door_opened) - return - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - return - isSwitchingStates = TRUE - playsound(src, closeSound, 100, TRUE) - flick("[initial(icon_state)]closing",src) - sleep(10) - density = TRUE - set_opacity(TRUE) - door_opened = FALSE - layer = initial(layer) - air_update_turf(1) - update_icon() - isSwitchingStates = FALSE - -/obj/structure/mineral_door/update_icon_state() - icon_state = "[initial(icon_state)][door_opened ? "open":""]" - -/obj/structure/mineral_door/attackby(obj/item/I, mob/user) - if(pickaxe_door(user, I)) - return - else if(user.a_intent != INTENT_HARM) - return attack_hand(user) - else - return ..() - -/obj/structure/mineral_door/setAnchored(anchorvalue) //called in default_unfasten_wrench() chain - . = ..() - set_opacity(anchored ? !door_opened : FALSE) - air_update_turf(TRUE) - -/obj/structure/mineral_door/wrench_act(mob/living/user, obj/item/I) - ..() - default_unfasten_wrench(user, I, 40) - return TRUE - - -/////////////////////// TOOL OVERRIDES /////////////////////// - - -/obj/structure/mineral_door/proc/pickaxe_door(mob/living/user, obj/item/I) //override if the door isn't supposed to be a minable mineral. - if(!istype(user)) - return - if(I.tool_behaviour != TOOL_MINING) - return - . = TRUE - to_chat(user, "You start digging [src]...") - if(I.use_tool(src, user, 40, volume=50)) - to_chat(user, "You finish digging.") - deconstruct(TRUE) - -/obj/structure/mineral_door/welder_act(mob/living/user, obj/item/I) //override if the door is supposed to be flammable. - ..() - . = TRUE - if(anchored) - to_chat(user, "[src] is still firmly secured to the ground!") - return - - user.visible_message("[user] starts to weld apart [src]!", "You start welding apart [src].") - if(!I.use_tool(src, user, 60, 5, 50)) - to_chat(user, "You failed to weld apart [src]!") - return - - user.visible_message("[user] welded [src] into pieces!", "You welded apart [src]!") - deconstruct(TRUE) - -/obj/structure/mineral_door/proc/crowbar_door(mob/living/user, obj/item/I) //if the door is flammable, call this in crowbar_act() so we can still decon it - . = TRUE - if(anchored) - to_chat(user, "[src] is still firmly secured to the ground!") - return - - user.visible_message("[user] starts to pry apart [src]!", "You start prying apart [src].") - if(!I.use_tool(src, user, 60, volume = 50)) - to_chat(user, "You failed to pry apart [src]!") - return - - user.visible_message("[user] pried [src] into pieces!", "You pried apart [src]!") - deconstruct(TRUE) - - -/////////////////////// END TOOL OVERRIDES /////////////////////// - - -/obj/structure/mineral_door/deconstruct(disassembled = TRUE) - var/turf/T = get_turf(src) - if(disassembled) - new sheetType(T, sheetAmount) - else - new sheetType(T, max(sheetAmount - 2, 1)) - qdel(src) - - -/obj/structure/mineral_door/iron - name = "iron door" - max_integrity = 300 - -/obj/structure/mineral_door/silver - name = "silver door" - icon_state = "silver" - sheetType = /obj/item/stack/sheet/mineral/silver - max_integrity = 300 - rad_insulation = RAD_HEAVY_INSULATION - -/obj/structure/mineral_door/gold - name = "gold door" - icon_state = "gold" - sheetType = /obj/item/stack/sheet/mineral/gold - rad_insulation = RAD_HEAVY_INSULATION - -/obj/structure/mineral_door/uranium - name = "uranium door" - icon_state = "uranium" - sheetType = /obj/item/stack/sheet/mineral/uranium - max_integrity = 300 - light_range = 2 - -/obj/structure/mineral_door/uranium/ComponentInitialize() - return - -/obj/structure/mineral_door/sandstone - name = "sandstone door" - icon_state = "sandstone" - sheetType = /obj/item/stack/sheet/mineral/sandstone - max_integrity = 100 - -/obj/structure/mineral_door/transparent - opacity = FALSE - rad_insulation = RAD_VERY_LIGHT_INSULATION - -/obj/structure/mineral_door/transparent/Close() - ..() - set_opacity(FALSE) - -/obj/structure/mineral_door/transparent/plasma - name = "plasma door" - icon_state = "plasma" - sheetType = /obj/item/stack/sheet/mineral/plasma - -/obj/structure/mineral_door/transparent/plasma/ComponentInitialize() - return - -/obj/structure/mineral_door/transparent/plasma/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user, params) - if(W.get_temperature()) - var/turf/T = get_turf(src) - message_admins("Plasma mineral door ignited by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") - log_game("Plasma mineral door ignited by [key_name(user)] in [AREACOORD(T)]") - TemperatureAct() - else - return ..() - -/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > 300) - TemperatureAct() - -/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct() - atmos_spawn_air("plasma=500;TEMP=1000") - deconstruct(FALSE) - -/obj/structure/mineral_door/transparent/diamond - name = "diamond door" - icon_state = "diamond" - sheetType = /obj/item/stack/sheet/mineral/diamond - max_integrity = 1000 - rad_insulation = RAD_EXTREME_INSULATION - -/obj/structure/mineral_door/wood - name = "wood door" - icon_state = "wood" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/mineral/wood - resistance_flags = FLAMMABLE - max_integrity = 200 - rad_insulation = RAD_VERY_LIGHT_INSULATION - -/obj/structure/mineral_door/wood/pickaxe_door(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/wood/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/wood/crowbar_act(mob/living/user, obj/item/I) - return crowbar_door(user, I) - -/obj/structure/mineral_door/wood/attackby(obj/item/I, mob/living/user) - if(I.get_temperature()) - fire_act(I.get_temperature()) - return - - return ..() - -/obj/structure/mineral_door/paperframe - name = "paper frame door" - icon_state = "paperframe" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/paperframes - sheetAmount = 3 - resistance_flags = FLAMMABLE - max_integrity = 20 - -/obj/structure/mineral_door/paperframe/Initialize() - . = ..() - queue_smooth_neighbors(src) - -/obj/structure/mineral_door/paperframe/examine(mob/user) - . = ..() - if(obj_integrity < max_integrity) - . += "It looks a bit damaged, you may be able to fix it with some paper." - -/obj/structure/mineral_door/paperframe/pickaxe_door(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/paperframe/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/paperframe/crowbar_act(mob/living/user, obj/item/I) - return crowbar_door(user, I) - -/obj/structure/mineral_door/paperframe/attackby(obj/item/I, mob/living/user) - if(I.get_temperature()) //BURN IT ALL DOWN JIM - fire_act(I.get_temperature()) - return - - if((user.a_intent != INTENT_HARM) && istype(I, /obj/item/paper) && (obj_integrity < max_integrity)) - user.visible_message("[user] starts to patch the holes in [src].", "You start patching some of the holes in [src]!") - if(do_after(user, 20, TRUE, src)) - obj_integrity = min(obj_integrity+4,max_integrity) - qdel(I) - user.visible_message("[user] patches some of the holes in [src].", "You patch some of the holes in [src]!") - return TRUE - - return ..() - -/obj/structure/mineral_door/paperframe/ComponentInitialize() - return - -/obj/structure/mineral_door/paperframe/Destroy() - queue_smooth_neighbors(src) - return ..() +//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its +//machineryness + +/obj/structure/mineral_door + name = "metal door" + density = TRUE + anchored = TRUE + opacity = TRUE + layer = CLOSED_DOOR_LAYER + + icon = 'icons/obj/doors/mineral_doors.dmi' + icon_state = "metal" + max_integrity = 200 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) + CanAtmosPass = ATMOS_PASS_DENSITY + flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1 + rad_insulation = RAD_MEDIUM_INSULATION + + var/door_opened = FALSE //if it's open or not. + var/isSwitchingStates = FALSE //don't try to change stats if we're already opening + + var/close_delay = -1 //-1 if does not auto close. + var/openSound = 'sound/effects/stonedoor_openclose.ogg' + var/closeSound = 'sound/effects/stonedoor_openclose.ogg' + + var/sheetType = /obj/item/stack/sheet/metal //what we're made of + var/sheetAmount = 7 //how much we drop when deconstructed + +/obj/structure/mineral_door/Initialize() + . = ..() + + air_update_turf(TRUE) + +/obj/structure/mineral_door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/structure/mineral_door/Bumped(atom/movable/AM) + ..() + if(!door_opened) + return TryToSwitchState(AM) + +/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral + if(isAI(user)) //so the AI can't open it + return + else if(iscyborg(user)) //but cyborgs can + if(get_dist(user,src) <= 1) //not remotely though + return TryToSwitchState(user) + +/obj/structure/mineral_door/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/mineral_door/attack_hand(mob/user) + . = ..() + if(.) + return + return TryToSwitchState(user) + +/obj/structure/mineral_door/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover, /obj/effect/beam)) + return !opacity + +/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) + if(isSwitchingStates || !anchored) + return + if(isliving(user)) + var/mob/living/M = user + if(world.time - M.last_bumped <= 60) + return //NOTE do we really need that? + if(M.client) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(!C.handcuffed) + SwitchState() + else + SwitchState() + else if(ismecha(user)) + SwitchState() + +/obj/structure/mineral_door/proc/SwitchState() + if(door_opened) + Close() + else + Open() + +/obj/structure/mineral_door/proc/Open() + isSwitchingStates = TRUE + playsound(src, openSound, 100, TRUE) + set_opacity(FALSE) + flick("[initial(icon_state)]opening",src) + sleep(10) + density = FALSE + door_opened = TRUE + layer = OPEN_DOOR_LAYER + air_update_turf(1) + update_icon() + isSwitchingStates = FALSE + + if(close_delay != -1) + addtimer(CALLBACK(src, .proc/Close), close_delay) + +/obj/structure/mineral_door/proc/Close() + if(isSwitchingStates || !door_opened) + return + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + return + isSwitchingStates = TRUE + playsound(src, closeSound, 100, TRUE) + flick("[initial(icon_state)]closing",src) + sleep(10) + density = TRUE + set_opacity(TRUE) + door_opened = FALSE + layer = initial(layer) + air_update_turf(1) + update_icon() + isSwitchingStates = FALSE + +/obj/structure/mineral_door/update_icon_state() + icon_state = "[initial(icon_state)][door_opened ? "open":""]" + +/obj/structure/mineral_door/attackby(obj/item/I, mob/user) + if(pickaxe_door(user, I)) + return + else if(user.a_intent != INTENT_HARM) + return attack_hand(user) + else + return ..() + +/obj/structure/mineral_door/setAnchored(anchorvalue) //called in default_unfasten_wrench() chain + . = ..() + set_opacity(anchored ? !door_opened : FALSE) + air_update_turf(TRUE) + +/obj/structure/mineral_door/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I, 40) + return TRUE + + +/////////////////////// TOOL OVERRIDES /////////////////////// + + +/obj/structure/mineral_door/proc/pickaxe_door(mob/living/user, obj/item/I) //override if the door isn't supposed to be a minable mineral. + if(!istype(user)) + return + if(I.tool_behaviour != TOOL_MINING) + return + . = TRUE + to_chat(user, "You start digging [src]...") + if(I.use_tool(src, user, 40, volume=50)) + to_chat(user, "You finish digging.") + deconstruct(TRUE) + +/obj/structure/mineral_door/welder_act(mob/living/user, obj/item/I) //override if the door is supposed to be flammable. + ..() + . = TRUE + if(anchored) + to_chat(user, "[src] is still firmly secured to the ground!") + return + + user.visible_message("[user] starts to weld apart [src]!", "You start welding apart [src].") + if(!I.use_tool(src, user, 60, 5, 50)) + to_chat(user, "You failed to weld apart [src]!") + return + + user.visible_message("[user] welded [src] into pieces!", "You welded apart [src]!") + deconstruct(TRUE) + +/obj/structure/mineral_door/proc/crowbar_door(mob/living/user, obj/item/I) //if the door is flammable, call this in crowbar_act() so we can still decon it + . = TRUE + if(anchored) + to_chat(user, "[src] is still firmly secured to the ground!") + return + + user.visible_message("[user] starts to pry apart [src]!", "You start prying apart [src].") + if(!I.use_tool(src, user, 60, volume = 50)) + to_chat(user, "You failed to pry apart [src]!") + return + + user.visible_message("[user] pried [src] into pieces!", "You pried apart [src]!") + deconstruct(TRUE) + + +/////////////////////// END TOOL OVERRIDES /////////////////////// + + +/obj/structure/mineral_door/deconstruct(disassembled = TRUE) + var/turf/T = get_turf(src) + if(disassembled) + new sheetType(T, sheetAmount) + else + new sheetType(T, max(sheetAmount - 2, 1)) + qdel(src) + + +/obj/structure/mineral_door/iron + name = "iron door" + max_integrity = 300 + +/obj/structure/mineral_door/silver + name = "silver door" + icon_state = "silver" + sheetType = /obj/item/stack/sheet/mineral/silver + max_integrity = 300 + rad_insulation = RAD_HEAVY_INSULATION + +/obj/structure/mineral_door/gold + name = "gold door" + icon_state = "gold" + sheetType = /obj/item/stack/sheet/mineral/gold + rad_insulation = RAD_HEAVY_INSULATION + +/obj/structure/mineral_door/uranium + name = "uranium door" + icon_state = "uranium" + sheetType = /obj/item/stack/sheet/mineral/uranium + max_integrity = 300 + light_range = 2 + +/obj/structure/mineral_door/uranium/ComponentInitialize() + return + +/obj/structure/mineral_door/sandstone + name = "sandstone door" + icon_state = "sandstone" + sheetType = /obj/item/stack/sheet/mineral/sandstone + max_integrity = 100 + +/obj/structure/mineral_door/transparent + opacity = FALSE + rad_insulation = RAD_VERY_LIGHT_INSULATION + +/obj/structure/mineral_door/transparent/Close() + ..() + set_opacity(FALSE) + +/obj/structure/mineral_door/transparent/plasma + name = "plasma door" + icon_state = "plasma" + sheetType = /obj/item/stack/sheet/mineral/plasma + +/obj/structure/mineral_door/transparent/plasma/ComponentInitialize() + return + +/obj/structure/mineral_door/transparent/plasma/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user, params) + if(W.get_temperature()) + var/turf/T = get_turf(src) + message_admins("Plasma mineral door ignited by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") + log_game("Plasma mineral door ignited by [key_name(user)] in [AREACOORD(T)]") + TemperatureAct() + else + return ..() + +/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > 300) + TemperatureAct() + +/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct() + atmos_spawn_air("plasma=500;TEMP=1000") + deconstruct(FALSE) + +/obj/structure/mineral_door/transparent/diamond + name = "diamond door" + icon_state = "diamond" + sheetType = /obj/item/stack/sheet/mineral/diamond + max_integrity = 1000 + rad_insulation = RAD_EXTREME_INSULATION + +/obj/structure/mineral_door/wood + name = "wood door" + icon_state = "wood" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/mineral/wood + resistance_flags = FLAMMABLE + max_integrity = 200 + rad_insulation = RAD_VERY_LIGHT_INSULATION + +/obj/structure/mineral_door/wood/pickaxe_door(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/wood/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/wood/crowbar_act(mob/living/user, obj/item/I) + return crowbar_door(user, I) + +/obj/structure/mineral_door/wood/attackby(obj/item/I, mob/living/user) + if(I.get_temperature()) + fire_act(I.get_temperature()) + return + + return ..() + +/obj/structure/mineral_door/paperframe + name = "paper frame door" + icon_state = "paperframe" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/paperframes + sheetAmount = 3 + resistance_flags = FLAMMABLE + max_integrity = 20 + +/obj/structure/mineral_door/paperframe/Initialize() + . = ..() + queue_smooth_neighbors(src) + +/obj/structure/mineral_door/paperframe/examine(mob/user) + . = ..() + if(obj_integrity < max_integrity) + . += "It looks a bit damaged, you may be able to fix it with some paper." + +/obj/structure/mineral_door/paperframe/pickaxe_door(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/paperframe/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/paperframe/crowbar_act(mob/living/user, obj/item/I) + return crowbar_door(user, I) + +/obj/structure/mineral_door/paperframe/attackby(obj/item/I, mob/living/user) + if(I.get_temperature()) //BURN IT ALL DOWN JIM + fire_act(I.get_temperature()) + return + + if((user.a_intent != INTENT_HARM) && istype(I, /obj/item/paper) && (obj_integrity < max_integrity)) + user.visible_message("[user] starts to patch the holes in [src].", "You start patching some of the holes in [src]!") + if(do_after(user, 20, TRUE, src)) + obj_integrity = min(obj_integrity+4,max_integrity) + qdel(I) + user.visible_message("[user] patches some of the holes in [src].", "You patch some of the holes in [src]!") + return TRUE + + return ..() + +/obj/structure/mineral_door/paperframe/ComponentInitialize() + return + +/obj/structure/mineral_door/paperframe/Destroy() + queue_smooth_neighbors(src) + return ..() diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 6d2a07cdef7..44d9c28ec14 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -1,249 +1,249 @@ -//wip wip wup -/obj/structure/mirror - name = "mirror" - desc = "Mirror mirror on the wall, who's the most robust of them all?" - icon = 'icons/obj/watercloset.dmi' - icon_state = "mirror" - density = FALSE - anchored = TRUE - max_integrity = 200 - integrity_failure = 0.5 - -/obj/structure/mirror/Initialize(mapload) - . = ..() - if(icon_state == "mirror_broke" && !broken) - obj_break(null, mapload) - -/obj/structure/mirror/attack_hand(mob/user) - . = ..() - if(.) - return - if(broken || !Adjacent(user)) - return - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - - //see code/modules/mob/dead/new_player/preferences.dm at approx line 545 for comments! - //this is largely copypasted from there. - - //handle facial hair (if necessary) - if(H.gender != FEMALE) - var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return //no tele-grooming - if(new_style) - H.facial_hairstyle = new_style - else - H.facial_hairstyle = "Shaved" - - //handle normal hair - var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return //no tele-grooming - if(HAS_TRAIT(H, TRAIT_BALD)) - to_chat(H, "If only growing back hair were that easy for you...") - if(new_style) - H.hairstyle = new_style - - H.update_hair() - -/obj/structure/mirror/examine_status(mob/user) - if(broken) - return list()// no message spam - return ..() - -/obj/structure/mirror/obj_break(damage_flag, mapload) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - icon_state = "mirror_broke" - if(!mapload) - playsound(src, "shatter", 70, TRUE) - if(desc == initial(desc)) - desc = "Oh no, seven years of bad luck!" - broken = TRUE - -/obj/structure/mirror/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(!disassembled) - new /obj/item/shard( src.loc ) - qdel(src) - -/obj/structure/mirror/welder_act(mob/living/user, obj/item/I) - ..() - if(user.a_intent == INTENT_HARM) - return FALSE - - if(!broken) - return TRUE - - if(!I.tool_start_check(user, amount=0)) - return TRUE - - to_chat(user, "You begin repairing [src]...") - if(I.use_tool(src, user, 10, volume=50)) - to_chat(user, "You repair [src].") - broken = 0 - icon_state = initial(icon_state) - desc = initial(desc) - - return TRUE - -/obj/structure/mirror/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) - if(BURN) - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) - - -/obj/structure/mirror/magic - name = "magic mirror" - desc = "Turn and face the strange... face." - icon_state = "magic_mirror" - var/list/choosable_races = list() - -/obj/structure/mirror/magic/New() - if(!choosable_races.len) - for(var/speciestype in subtypesof(/datum/species)) - var/datum/species/S = speciestype - if(initial(S.changesource_flags) & MIRROR_MAGIC) - choosable_races += initial(S.id) - choosable_races = sortList(choosable_races) - ..() - -/obj/structure/mirror/magic/lesser/New() - choosable_races = GLOB.roundstart_races.Copy() - ..() - -/obj/structure/mirror/magic/badmin/New() - for(var/speciestype in subtypesof(/datum/species)) - var/datum/species/S = speciestype - if(initial(S.changesource_flags) & MIRROR_BADMIN) - choosable_races += initial(S.id) - ..() - -/obj/structure/mirror/magic/attack_hand(mob/user) - . = ..() - if(.) - return - if(!ishuman(user)) - return - - var/mob/living/carbon/human/H = user - - var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("name", "race", "gender", "hair", "eyes") - - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - switch(choice) - if("name") - var/newname = sanitize_name(stripped_input(H, "Who are we again?", "Name change", H.name, MAX_NAME_LEN), allow_numbers = TRUE) //It's magic so whatever. - if(!newname) - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.real_name = newname - H.name = newname - if(H.dna) - H.dna.real_name = newname - if(H.mind) - H.mind.name = newname - - if("race") - var/newrace - var/racechoice = input(H, "What are we again?", "Race change") as null|anything in choosable_races - newrace = GLOB.species_list[racechoice] - - if(!newrace) - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.set_species(newrace, icon_update=0) - - if(H.dna.species.use_skintones) - var/new_s_tone = input(user, "Choose your skin tone:", "Race change") as null|anything in GLOB.skin_tones - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - if(new_s_tone) - H.skin_tone = new_s_tone - H.dna.update_ui_block(DNA_SKIN_TONE_BLOCK) - - if(MUTCOLORS in H.dna.species.species_traits) - var/new_mutantcolor = input(user, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_mutantcolor) - var/temp_hsv = RGBtoHSV(new_mutantcolor) - - if(ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright - H.dna.features["mcolor"] = sanitize_hexcolor(new_mutantcolor) - - else - to_chat(H, "Invalid color. Your color is not bright enough.") - - H.update_body() - H.update_hair() - H.update_body_parts() - H.update_mutations_overlay() // no hulk lizard - - if("gender") - if(!(H.gender in list("male", "female"))) //blame the patriarchy - return - if(H.gender == "male") - if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.gender = FEMALE - H.body_type = FEMALE - to_chat(H, "Man, you feel like a woman!") - else - return - - else - if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.gender = MALE - H.body_type = MALE - to_chat(H, "Whoa man, you feel like a man!") - else - return - H.dna.update_ui_block(DNA_GENDER_BLOCK) - H.update_body() - H.update_mutations_overlay() //(hulk male/female) - - if("hair") - var/hairchoice = alert(H, "Hairstyle or hair color?", "Change Hair", "Style", "Color") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(hairchoice == "Style") //So you just want to use a mirror then? - ..() - else - var/new_hair_color = input(H, "Choose your hair color", "Hair Color","#"+H.hair_color) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_hair_color) - H.hair_color = sanitize_hexcolor(new_hair_color) - H.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK) - if(H.gender == "male") - var/new_face_color = input(H, "Choose your facial hair color", "Hair Color","#"+H.facial_hair_color) as color|null - if(new_face_color) - H.facial_hair_color = sanitize_hexcolor(new_face_color) - H.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK) - H.update_hair() - - if(BODY_ZONE_PRECISE_EYES) - var/new_eye_color = input(H, "Choose your eye color", "Eye Color","#"+H.eye_color) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_eye_color) - H.eye_color = sanitize_hexcolor(new_eye_color) - H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) - H.update_body() - if(choice) - curse(user) - -/obj/structure/mirror/magic/proc/curse(mob/living/user) - return +//wip wip wup +/obj/structure/mirror + name = "mirror" + desc = "Mirror mirror on the wall, who's the most robust of them all?" + icon = 'icons/obj/watercloset.dmi' + icon_state = "mirror" + density = FALSE + anchored = TRUE + max_integrity = 200 + integrity_failure = 0.5 + +/obj/structure/mirror/Initialize(mapload) + . = ..() + if(icon_state == "mirror_broke" && !broken) + obj_break(null, mapload) + +/obj/structure/mirror/attack_hand(mob/user) + . = ..() + if(.) + return + if(broken || !Adjacent(user)) + return + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + + //see code/modules/mob/dead/new_player/preferences.dm at approx line 545 for comments! + //this is largely copypasted from there. + + //handle facial hair (if necessary) + if(H.gender != FEMALE) + var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return //no tele-grooming + if(new_style) + H.facial_hairstyle = new_style + else + H.facial_hairstyle = "Shaved" + + //handle normal hair + var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return //no tele-grooming + if(HAS_TRAIT(H, TRAIT_BALD)) + to_chat(H, "If only growing back hair were that easy for you...") + if(new_style) + H.hairstyle = new_style + + H.update_hair() + +/obj/structure/mirror/examine_status(mob/user) + if(broken) + return list()// no message spam + return ..() + +/obj/structure/mirror/obj_break(damage_flag, mapload) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + icon_state = "mirror_broke" + if(!mapload) + playsound(src, "shatter", 70, TRUE) + if(desc == initial(desc)) + desc = "Oh no, seven years of bad luck!" + broken = TRUE + +/obj/structure/mirror/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(!disassembled) + new /obj/item/shard( src.loc ) + qdel(src) + +/obj/structure/mirror/welder_act(mob/living/user, obj/item/I) + ..() + if(user.a_intent == INTENT_HARM) + return FALSE + + if(!broken) + return TRUE + + if(!I.tool_start_check(user, amount=0)) + return TRUE + + to_chat(user, "You begin repairing [src]...") + if(I.use_tool(src, user, 10, volume=50)) + to_chat(user, "You repair [src].") + broken = 0 + icon_state = initial(icon_state) + desc = initial(desc) + + return TRUE + +/obj/structure/mirror/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) + if(BURN) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) + + +/obj/structure/mirror/magic + name = "magic mirror" + desc = "Turn and face the strange... face." + icon_state = "magic_mirror" + var/list/choosable_races = list() + +/obj/structure/mirror/magic/New() + if(!choosable_races.len) + for(var/speciestype in subtypesof(/datum/species)) + var/datum/species/S = speciestype + if(initial(S.changesource_flags) & MIRROR_MAGIC) + choosable_races += initial(S.id) + choosable_races = sortList(choosable_races) + ..() + +/obj/structure/mirror/magic/lesser/New() + choosable_races = GLOB.roundstart_races.Copy() + ..() + +/obj/structure/mirror/magic/badmin/New() + for(var/speciestype in subtypesof(/datum/species)) + var/datum/species/S = speciestype + if(initial(S.changesource_flags) & MIRROR_BADMIN) + choosable_races += initial(S.id) + ..() + +/obj/structure/mirror/magic/attack_hand(mob/user) + . = ..() + if(.) + return + if(!ishuman(user)) + return + + var/mob/living/carbon/human/H = user + + var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("name", "race", "gender", "hair", "eyes") + + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + switch(choice) + if("name") + var/newname = sanitize_name(stripped_input(H, "Who are we again?", "Name change", H.name, MAX_NAME_LEN), allow_numbers = TRUE) //It's magic so whatever. + if(!newname) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.real_name = newname + H.name = newname + if(H.dna) + H.dna.real_name = newname + if(H.mind) + H.mind.name = newname + + if("race") + var/newrace + var/racechoice = input(H, "What are we again?", "Race change") as null|anything in choosable_races + newrace = GLOB.species_list[racechoice] + + if(!newrace) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.set_species(newrace, icon_update=0) + + if(H.dna.species.use_skintones) + var/new_s_tone = input(user, "Choose your skin tone:", "Race change") as null|anything in GLOB.skin_tones + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + if(new_s_tone) + H.skin_tone = new_s_tone + H.dna.update_ui_block(DNA_SKIN_TONE_BLOCK) + + if(MUTCOLORS in H.dna.species.species_traits) + var/new_mutantcolor = input(user, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_mutantcolor) + var/temp_hsv = RGBtoHSV(new_mutantcolor) + + if(ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright + H.dna.features["mcolor"] = sanitize_hexcolor(new_mutantcolor) + + else + to_chat(H, "Invalid color. Your color is not bright enough.") + + H.update_body() + H.update_hair() + H.update_body_parts() + H.update_mutations_overlay() // no hulk lizard + + if("gender") + if(!(H.gender in list("male", "female"))) //blame the patriarchy + return + if(H.gender == "male") + if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.gender = FEMALE + H.body_type = FEMALE + to_chat(H, "Man, you feel like a woman!") + else + return + + else + if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.gender = MALE + H.body_type = MALE + to_chat(H, "Whoa man, you feel like a man!") + else + return + H.dna.update_ui_block(DNA_GENDER_BLOCK) + H.update_body() + H.update_mutations_overlay() //(hulk male/female) + + if("hair") + var/hairchoice = alert(H, "Hairstyle or hair color?", "Change Hair", "Style", "Color") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(hairchoice == "Style") //So you just want to use a mirror then? + ..() + else + var/new_hair_color = input(H, "Choose your hair color", "Hair Color","#"+H.hair_color) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_hair_color) + H.hair_color = sanitize_hexcolor(new_hair_color) + H.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK) + if(H.gender == "male") + var/new_face_color = input(H, "Choose your facial hair color", "Hair Color","#"+H.facial_hair_color) as color|null + if(new_face_color) + H.facial_hair_color = sanitize_hexcolor(new_face_color) + H.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK) + H.update_hair() + + if(BODY_ZONE_PRECISE_EYES) + var/new_eye_color = input(H, "Choose your eye color", "Eye Color","#"+H.eye_color) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_eye_color) + H.eye_color = sanitize_hexcolor(new_eye_color) + H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) + H.update_body() + if(choice) + curse(user) + +/obj/structure/mirror/magic/proc/curse(mob/living/user) + return diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm index a5f84b9bc14..457bd0faf4d 100644 --- a/code/game/objects/structures/mop_bucket.dm +++ b/code/game/objects/structures/mop_bucket.dm @@ -1,30 +1,30 @@ -/obj/structure/mopbucket - name = "mop bucket" - desc = "Fill it with water, but don't forget a mop!" - icon = 'icons/obj/janitor.dmi' - icon_state = "mopbucket" - density = TRUE - var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite - - -/obj/structure/mopbucket/Initialize() - . = ..() - create_reagents(100, OPENCONTAINER) - -/obj/structure/mopbucket/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/mop)) - if(reagents.total_volume < 1) - to_chat(user, "[src] is out of water!") - else - reagents.trans_to(I, 5, transfered_by = user) - to_chat(user, "You wet [I] in [src].") - playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) - update_icon() - else - . = ..() - update_icon() - -/obj/structure/mopbucket/update_overlays() - . = ..() - if(reagents.total_volume > 0) - . += "mopbucket_water" +/obj/structure/mopbucket + name = "mop bucket" + desc = "Fill it with water, but don't forget a mop!" + icon = 'icons/obj/janitor.dmi' + icon_state = "mopbucket" + density = TRUE + var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite + + +/obj/structure/mopbucket/Initialize() + . = ..() + create_reagents(100, OPENCONTAINER) + +/obj/structure/mopbucket/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/mop)) + if(reagents.total_volume < 1) + to_chat(user, "[src] is out of water!") + else + reagents.trans_to(I, 5, transfered_by = user) + to_chat(user, "You wet [I] in [src].") + playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) + update_icon() + else + . = ..() + update_icon() + +/obj/structure/mopbucket/update_overlays() + . = ..() + if(reagents.total_volume > 0) + . += "mopbucket_water" diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index 0a0d4ca151d..262cb9fa564 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -1,403 +1,403 @@ -/* Morgue stuff - * Contains: - * Morgue - * Morgue tray - * Crematorium - * Creamatorium - * Crematorium tray - * Crematorium button - */ - -/* - * Bodycontainer - * Parent class for morgue and crematorium - * For overriding only - */ -GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants and other ghosties. - -/obj/structure/bodycontainer - icon = 'icons/obj/stationobjs.dmi' - icon_state = "morgue1" - density = TRUE - anchored = TRUE - max_integrity = 400 - - var/obj/structure/tray/connected = null - var/locked = FALSE - dir = SOUTH - var/message_cooldown - var/breakout_time = 600 - -/obj/structure/bodycontainer/Initialize() - . = ..() - GLOB.bodycontainers += src - recursive_organ_check(src) - -/obj/structure/bodycontainer/Destroy() - GLOB.bodycontainers -= src - open() - if(connected) - qdel(connected) - connected = null - return ..() - -/obj/structure/bodycontainer/on_log(login) - ..() - update_icon() - -/obj/structure/bodycontainer/update_icon() - return - -/obj/structure/bodycontainer/relaymove(mob/user) - if(user.stat || !isturf(loc)) - return - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - open() - -/obj/structure/bodycontainer/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/bodycontainer/attack_hand(mob/user) - . = ..() - if(.) - return - if(locked) - to_chat(user, "It's locked.") - return - if(!connected) - to_chat(user, "That doesn't appear to have a tray.") - return - if(connected.loc == src) - open() - else - close() - add_fingerprint(user) - -/obj/structure/bodycontainer/attack_robot(mob/user) - if(!user.Adjacent(src)) - return - return attack_hand(user) - -/obj/structure/bodycontainer/attackby(obj/P, mob/user, params) - add_fingerprint(user) - if(istype(P, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/t = stripped_input(user, "What would you like the label to be?", text("[]", name), null) - if (user.get_active_held_item() != P) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (t) - name = text("[]- '[]'", initial(name), t) - else - name = initial(name) - else - return ..() - -/obj/structure/bodycontainer/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 5) - recursive_organ_check(src) - qdel(src) - -/obj/structure/bodycontainer/container_resist(mob/living/user) - if(!locked) - open() - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message(null, \ - "You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a metallic creaking from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open() - -/obj/structure/bodycontainer/proc/open() - recursive_organ_check(src) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - playsound(src, 'sound/effects/roll.ogg', 5, TRUE) - var/turf/T = get_step(src, dir) - connected.setDir(dir) - for(var/atom/movable/AM in src) - AM.forceMove(T) - update_icon() - -/obj/structure/bodycontainer/proc/close() - playsound(src, 'sound/effects/roll.ogg', 5, TRUE) - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - for(var/atom/movable/AM in connected.loc) - if(!AM.anchored || AM == connected) - if(ismob(AM) && !isliving(AM)) - continue - AM.forceMove(src) - recursive_organ_check(src) - update_icon() - -/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) -/* - * Morgue - */ -/obj/structure/bodycontainer/morgue - name = "morgue" - desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." - icon_state = "morgue1" - dir = EAST - var/beeper = TRUE - var/beep_cooldown = 50 - var/next_beep = 0 - -/obj/structure/bodycontainer/morgue/Initialize() - . = ..() - connected = new/obj/structure/tray/m_tray(src) - connected.connected = src - -/obj/structure/bodycontainer/morgue/examine(mob/user) - . = ..() - . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." - -/obj/structure/bodycontainer/morgue/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user))) - return - beeper = !beeper - to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") - -/obj/structure/bodycontainer/morgue/update_icon() - if (!connected || connected.loc != src) // Open or tray is gone. - icon_state = "morgue0" - else - if(contents.len == 1) // Empty - icon_state = "morgue1" - else - icon_state = "morgue2" // Dead, brainded mob. - var/list/compiled = GetAllContents(/mob/living) // Search for mobs in all contents. - if(!length(compiled)) // No mobs? - icon_state = "morgue3" - return - - for(var/mob/living/M in compiled) - var/mob/living/mob_occupant = get_mob_or_brainmob(M) - if(mob_occupant.client && !mob_occupant.suiciding && !(HAS_TRAIT(mob_occupant, TRAIT_BADDNA)) && !mob_occupant.hellbound) - icon_state = "morgue4" // Revivable - if(mob_occupant.stat == DEAD && beeper) - if(world.time > next_beep) - playsound(src, 'sound/weapons/gun/general/empty_alarm.ogg', 50, FALSE) //Revive them you blind fucks - next_beep = world.time + beep_cooldown - break - - -/obj/item/paper/guides/jobs/medical/morgue - name = "morgue memo" - info = "Since this station's medbay never seems to fail to be staffed by the mindless monkeys meant for genetics experiments, I'm leaving a reminder here for anyone handling the pile of cadavers the quacks are sure to leave.

                Red lights mean there's a plain ol' dead body inside.

                Yellow lights mean there's non-body objects inside.
                Probably stuff pried off a corpse someone grabbed, or if you're lucky it's stashed booze.

                Green lights mean the morgue system detects the body may be able to be brought back to life.

                I don't know how that works, but keep it away from the kitchen and go yell at the geneticists.

                - CentCom medical inspector" - -/* - * Crematorium - */ -GLOBAL_LIST_EMPTY(crematoriums) -/obj/structure/bodycontainer/crematorium - name = "crematorium" - desc = "A human incinerator. Works well on barbecue nights." - icon_state = "crema1" - dir = SOUTH - var/id = 1 - -/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help - to_chat(user, "[src] is locked against you.") - return - -/obj/structure/bodycontainer/crematorium/Destroy() - GLOB.crematoriums.Remove(src) - return ..() - -/obj/structure/bodycontainer/crematorium/New() - GLOB.crematoriums.Add(src) - ..() - -/obj/structure/bodycontainer/crematorium/Initialize() - . = ..() - connected = new /obj/structure/tray/c_tray(src) - connected.connected = src - -/obj/structure/bodycontainer/crematorium/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -/obj/structure/bodycontainer/crematorium/update_icon() - if(!connected || connected.loc != src) - icon_state = "crema0" - else - - if(src.contents.len > 1) - src.icon_state = "crema2" - else - src.icon_state = "crema1" - - if(locked) - src.icon_state = "crema_active" - - return - -/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user) - if(locked) - return //don't let you cremate something twice or w/e - // Make sure we don't delete the actual morgue and its tray - var/list/conts = GetAllContents() - src - connected - - if(!conts.len) - audible_message("You hear a hollow crackle.") - return - - else - audible_message("You hear a roar as the crematorium activates.") - - locked = TRUE - update_icon() - - for(var/mob/living/M in conts) - if (M.stat != DEAD) - M.emote("scream") - if(user) - log_combat(user, M, "cremated") - else - M.log_message("was cremated", LOG_ATTACK) - - M.death(1) - if(M) //some animals get automatically deleted on death. - M.ghostize() - qdel(M) - - for(var/obj/O in conts) //conts defined above, ignores crematorium and tray - qdel(O) - - if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up - new/obj/effect/decal/cleanable/ash/crematorium(src) - - sleep(30) - - if(!QDELETED(src)) - locked = FALSE - update_icon() - playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) //you horrible people - -/obj/structure/bodycontainer/crematorium/creamatorium - name = "creamatorium" - desc = "A human incinerator. Works well during ice cream socials." - -/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user) - var/list/icecreams = new() - for(var/i_scream in GetAllContents(/mob/living)) - var/obj/item/reagent_containers/food/snacks/icecream/IC = new() - IC.set_cone_type("waffle") - IC.add_mob_flavor(i_scream) - icecreams += IC - . = ..() - for(var/obj/IC in icecreams) - IC.forceMove(src) - -/* - * Generic Tray - * Parent class for morguetray and crematoriumtray - * For overriding only - */ -/obj/structure/tray - icon = 'icons/obj/stationobjs.dmi' - density = TRUE - var/obj/structure/bodycontainer/connected = null - anchored = TRUE - pass_flags = LETPASSTHROW - max_integrity = 350 - -/obj/structure/tray/Destroy() - if(connected) - connected.connected = null - connected.update_icon() - connected = null - return ..() - -/obj/structure/tray/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/structure/tray/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/tray/attack_hand(mob/user) - . = ..() - if(.) - return - if (src.connected) - connected.close() - add_fingerprint(user) - else - to_chat(user, "That's not connected to anything!") - -/obj/structure/tray/attackby(obj/P, mob/user, params) - if(!istype(P, /obj/item/riding_offhand)) - return ..() - - var/obj/item/riding_offhand/riding_item = P - var/mob/living/carried_mob = riding_item.rider - if(carried_mob == user) //Piggyback user. - return - user.unbuckle_mob(carried_mob) - MouseDrop_T(carried_mob, user) - -/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user) - if(!ismovable(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user) - return - if(!ismob(O)) - if(!istype(O, /obj/structure/closet/body_bag)) - return - else - var/mob/M = O - if(M.buckled) - return - if(!ismob(user) || user.incapacitated()) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - O.forceMove(src.loc) - if (user != O) - visible_message("[user] stuffs [O] into [src].") - return - -/* - * Crematorium tray - */ -/obj/structure/tray/c_tray - name = "crematorium tray" - desc = "Apply body before burning." - icon_state = "cremat" - -/* - * Morgue tray - */ -/obj/structure/tray/m_tray - name = "morgue tray" - desc = "Apply corpse before closing." - icon_state = "morguet" - -/obj/structure/tray/m_tray/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return TRUE - if(locate(/obj/structure/table) in get_turf(mover)) - return TRUE - -/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) - . = !density - if(ismovable(caller)) - var/atom/movable/mover = caller - . = . || (mover.pass_flags & PASSTABLE) +/* Morgue stuff + * Contains: + * Morgue + * Morgue tray + * Crematorium + * Creamatorium + * Crematorium tray + * Crematorium button + */ + +/* + * Bodycontainer + * Parent class for morgue and crematorium + * For overriding only + */ +GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants and other ghosties. + +/obj/structure/bodycontainer + icon = 'icons/obj/stationobjs.dmi' + icon_state = "morgue1" + density = TRUE + anchored = TRUE + max_integrity = 400 + + var/obj/structure/tray/connected = null + var/locked = FALSE + dir = SOUTH + var/message_cooldown + var/breakout_time = 600 + +/obj/structure/bodycontainer/Initialize() + . = ..() + GLOB.bodycontainers += src + recursive_organ_check(src) + +/obj/structure/bodycontainer/Destroy() + GLOB.bodycontainers -= src + open() + if(connected) + qdel(connected) + connected = null + return ..() + +/obj/structure/bodycontainer/on_log(login) + ..() + update_icon() + +/obj/structure/bodycontainer/update_icon() + return + +/obj/structure/bodycontainer/relaymove(mob/user) + if(user.stat || !isturf(loc)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + open() + +/obj/structure/bodycontainer/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/bodycontainer/attack_hand(mob/user) + . = ..() + if(.) + return + if(locked) + to_chat(user, "It's locked.") + return + if(!connected) + to_chat(user, "That doesn't appear to have a tray.") + return + if(connected.loc == src) + open() + else + close() + add_fingerprint(user) + +/obj/structure/bodycontainer/attack_robot(mob/user) + if(!user.Adjacent(src)) + return + return attack_hand(user) + +/obj/structure/bodycontainer/attackby(obj/P, mob/user, params) + add_fingerprint(user) + if(istype(P, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/t = stripped_input(user, "What would you like the label to be?", text("[]", name), null) + if (user.get_active_held_item() != P) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (t) + name = text("[]- '[]'", initial(name), t) + else + name = initial(name) + else + return ..() + +/obj/structure/bodycontainer/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 5) + recursive_organ_check(src) + qdel(src) + +/obj/structure/bodycontainer/container_resist(mob/living/user) + if(!locked) + open() + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message(null, \ + "You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a metallic creaking from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open() + +/obj/structure/bodycontainer/proc/open() + recursive_organ_check(src) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + playsound(src, 'sound/effects/roll.ogg', 5, TRUE) + var/turf/T = get_step(src, dir) + connected.setDir(dir) + for(var/atom/movable/AM in src) + AM.forceMove(T) + update_icon() + +/obj/structure/bodycontainer/proc/close() + playsound(src, 'sound/effects/roll.ogg', 5, TRUE) + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + for(var/atom/movable/AM in connected.loc) + if(!AM.anchored || AM == connected) + if(ismob(AM) && !isliving(AM)) + continue + AM.forceMove(src) + recursive_organ_check(src) + update_icon() + +/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) +/* + * Morgue + */ +/obj/structure/bodycontainer/morgue + name = "morgue" + desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." + icon_state = "morgue1" + dir = EAST + var/beeper = TRUE + var/beep_cooldown = 50 + var/next_beep = 0 + +/obj/structure/bodycontainer/morgue/Initialize() + . = ..() + connected = new/obj/structure/tray/m_tray(src) + connected.connected = src + +/obj/structure/bodycontainer/morgue/examine(mob/user) + . = ..() + . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." + +/obj/structure/bodycontainer/morgue/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, !issilicon(user))) + return + beeper = !beeper + to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") + +/obj/structure/bodycontainer/morgue/update_icon() + if (!connected || connected.loc != src) // Open or tray is gone. + icon_state = "morgue0" + else + if(contents.len == 1) // Empty + icon_state = "morgue1" + else + icon_state = "morgue2" // Dead, brainded mob. + var/list/compiled = GetAllContents(/mob/living) // Search for mobs in all contents. + if(!length(compiled)) // No mobs? + icon_state = "morgue3" + return + + for(var/mob/living/M in compiled) + var/mob/living/mob_occupant = get_mob_or_brainmob(M) + if(mob_occupant.client && !mob_occupant.suiciding && !(HAS_TRAIT(mob_occupant, TRAIT_BADDNA)) && !mob_occupant.hellbound) + icon_state = "morgue4" // Revivable + if(mob_occupant.stat == DEAD && beeper) + if(world.time > next_beep) + playsound(src, 'sound/weapons/gun/general/empty_alarm.ogg', 50, FALSE) //Revive them you blind fucks + next_beep = world.time + beep_cooldown + break + + +/obj/item/paper/guides/jobs/medical/morgue + name = "morgue memo" + info = "Since this station's medbay never seems to fail to be staffed by the mindless monkeys meant for genetics experiments, I'm leaving a reminder here for anyone handling the pile of cadavers the quacks are sure to leave.

                Red lights mean there's a plain ol' dead body inside.

                Yellow lights mean there's non-body objects inside.
                Probably stuff pried off a corpse someone grabbed, or if you're lucky it's stashed booze.

                Green lights mean the morgue system detects the body may be able to be brought back to life.

                I don't know how that works, but keep it away from the kitchen and go yell at the geneticists.

                - CentCom medical inspector" + +/* + * Crematorium + */ +GLOBAL_LIST_EMPTY(crematoriums) +/obj/structure/bodycontainer/crematorium + name = "crematorium" + desc = "A human incinerator. Works well on barbecue nights." + icon_state = "crema1" + dir = SOUTH + var/id = 1 + +/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help + to_chat(user, "[src] is locked against you.") + return + +/obj/structure/bodycontainer/crematorium/Destroy() + GLOB.crematoriums.Remove(src) + return ..() + +/obj/structure/bodycontainer/crematorium/New() + GLOB.crematoriums.Add(src) + ..() + +/obj/structure/bodycontainer/crematorium/Initialize() + . = ..() + connected = new /obj/structure/tray/c_tray(src) + connected.connected = src + +/obj/structure/bodycontainer/crematorium/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +/obj/structure/bodycontainer/crematorium/update_icon() + if(!connected || connected.loc != src) + icon_state = "crema0" + else + + if(src.contents.len > 1) + src.icon_state = "crema2" + else + src.icon_state = "crema1" + + if(locked) + src.icon_state = "crema_active" + + return + +/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user) + if(locked) + return //don't let you cremate something twice or w/e + // Make sure we don't delete the actual morgue and its tray + var/list/conts = GetAllContents() - src - connected + + if(!conts.len) + audible_message("You hear a hollow crackle.") + return + + else + audible_message("You hear a roar as the crematorium activates.") + + locked = TRUE + update_icon() + + for(var/mob/living/M in conts) + if (M.stat != DEAD) + M.emote("scream") + if(user) + log_combat(user, M, "cremated") + else + M.log_message("was cremated", LOG_ATTACK) + + M.death(1) + if(M) //some animals get automatically deleted on death. + M.ghostize() + qdel(M) + + for(var/obj/O in conts) //conts defined above, ignores crematorium and tray + qdel(O) + + if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up + new/obj/effect/decal/cleanable/ash/crematorium(src) + + sleep(30) + + if(!QDELETED(src)) + locked = FALSE + update_icon() + playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) //you horrible people + +/obj/structure/bodycontainer/crematorium/creamatorium + name = "creamatorium" + desc = "A human incinerator. Works well during ice cream socials." + +/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user) + var/list/icecreams = new() + for(var/i_scream in GetAllContents(/mob/living)) + var/obj/item/reagent_containers/food/snacks/icecream/IC = new() + IC.set_cone_type("waffle") + IC.add_mob_flavor(i_scream) + icecreams += IC + . = ..() + for(var/obj/IC in icecreams) + IC.forceMove(src) + +/* + * Generic Tray + * Parent class for morguetray and crematoriumtray + * For overriding only + */ +/obj/structure/tray + icon = 'icons/obj/stationobjs.dmi' + density = TRUE + var/obj/structure/bodycontainer/connected = null + anchored = TRUE + pass_flags = LETPASSTHROW + max_integrity = 350 + +/obj/structure/tray/Destroy() + if(connected) + connected.connected = null + connected.update_icon() + connected = null + return ..() + +/obj/structure/tray/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/structure/tray/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/tray/attack_hand(mob/user) + . = ..() + if(.) + return + if (src.connected) + connected.close() + add_fingerprint(user) + else + to_chat(user, "That's not connected to anything!") + +/obj/structure/tray/attackby(obj/P, mob/user, params) + if(!istype(P, /obj/item/riding_offhand)) + return ..() + + var/obj/item/riding_offhand/riding_item = P + var/mob/living/carried_mob = riding_item.rider + if(carried_mob == user) //Piggyback user. + return + user.unbuckle_mob(carried_mob) + MouseDrop_T(carried_mob, user) + +/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user) + if(!ismovable(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user) + return + if(!ismob(O)) + if(!istype(O, /obj/structure/closet/body_bag)) + return + else + var/mob/M = O + if(M.buckled) + return + if(!ismob(user) || user.incapacitated()) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + O.forceMove(src.loc) + if (user != O) + visible_message("[user] stuffs [O] into [src].") + return + +/* + * Crematorium tray + */ +/obj/structure/tray/c_tray + name = "crematorium tray" + desc = "Apply body before burning." + icon_state = "cremat" + +/* + * Morgue tray + */ +/obj/structure/tray/m_tray + name = "morgue tray" + desc = "Apply corpse before closing." + icon_state = "morguet" + +/obj/structure/tray/m_tray/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return TRUE + if(locate(/obj/structure/table) in get_turf(mover)) + return TRUE + +/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) + . = !density + if(ismovable(caller)) + var/atom/movable/mover = caller + . = . || (mover.pass_flags & PASSTABLE) diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm index 01f832aaa7f..24e95c834a9 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -1,393 +1,393 @@ - -#define MUSICIAN_HEARCHECK_MINDELAY 4 -#define MUSIC_MAXLINES 300 -#define MUSIC_MAXLINECHARS 50 - -/datum/song - var/name = "Untitled" - var/list/lines = new() - var/tempo = 5 // delay between notes - - var/playing = 0 // if we're playing - var/help = 0 // if help is open - var/edit = 1 // if we're in editing mode - var/repeat = 0 // number of times remaining to repeat - var/max_repeats = 10 // maximum times we can repeat - - var/instrumentDir = "piano" // the folder with the sounds - var/instrumentExt = "ogg" // the file extension - var/instrumentRange = 15 // how far the sound can be heard - var/obj/instrumentObj = null // the associated obj playing the sound - var/last_hearcheck = 0 - var/list/hearing_mobs - -/datum/song/New(dir, obj, ext = "ogg", range) - tempo = sanitize_tempo(tempo) - instrumentDir = dir - instrumentObj = obj - instrumentExt = ext - instrumentRange = range - -/datum/song/Destroy() - instrumentObj = null - return ..() - -// note is a number from 1-7 for A-G -// acc is either "b", "n", or "#" -// oct is 1-8 (or 9 for C) -/datum/song/proc/playnote(mob/user, note, acc as text, oct) - // handle accidental -> B<>C of E<>F - if(acc == "b" && (note == 3 || note == 6)) // C or F - if(note == 3) - oct-- - note-- - acc = "n" - else if(acc == "#" && (note == 2 || note == 5)) // B or E - if(note == 2) - oct++ - note++ - acc = "n" - else if(acc == "#" && (note == 7)) //G# - note = 1 - acc = "b" - else if(acc == "#") // mass convert all sharps to flats, octave jump already handled - acc = "b" - note++ - - // check octave, C is allowed to go to 9 - if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) - return - - // now generate name - var/soundfile = "sound/runtime/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]" - soundfile = file(soundfile) - // make sure the note exists - if(!fexists(soundfile)) - return - // and play - var/turf/source = get_turf(instrumentObj) - if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) - LAZYCLEARLIST(hearing_mobs) - for(var/mob/M in get_hearers_in_view(instrumentRange, source)) - LAZYADD(hearing_mobs, M) - last_hearcheck = world.time - - var/sound/music_played = sound(soundfile) - for(var/i in hearing_mobs) - var/mob/M = i - if(HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M)) - var/mob/living/L = M - L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC) - if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) - continue - M.playsound_local(source, null, 100, falloff = 5, S = music_played) - -/datum/song/proc/updateDialog(mob/user) - instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise - -/datum/song/proc/shouldStopPlaying(mob/user) - if(instrumentObj) - if(!user.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return TRUE - return !instrumentObj.anchored // add special cases to stop in subclasses - else - return TRUE - -/datum/song/proc/playsong(mob/user) - while(repeat >= 0) - var/cur_oct[7] - var/cur_acc[7] - for(var/i = 1 to 7) - cur_oct[i] = 3 - cur_acc[i] = "n" - - for(var/line in lines) - for(var/beat in splittext(lowertext(line), ",")) - var/list/notes = splittext(beat, "/") - for(var/note in splittext(notes[1], "-")) - if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case - toggle_playing(user, FALSE) - return - if(!length(note)) - continue - var/cur_note = text2ascii(note) - 96 - if(cur_note < 1 || cur_note > 7) - continue - var/notelen = length(note) - var/ni = "" - for(var/i = length(note[1]) + 1, i <= notelen, i += length(ni)) - ni = note[i] - if(!text2num(ni)) - if(ni == "#" || ni == "b" || ni == "n") - cur_acc[cur_note] = ni - else if(ni == "s") - cur_acc[cur_note] = "#" // so shift is never required - else - cur_oct[cur_note] = text2num(ni) - if(user.dizziness > 0 && prob(user.dizziness / 2)) - cur_note = clamp(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7) - if(user.dizziness > 0 && prob(user.dizziness / 5)) - if(prob(30)) - cur_acc[cur_note] = "#" - else if(prob(42)) - cur_acc[cur_note] = "b" - else if(prob(75)) - cur_acc[cur_note] = "n" - playnote(user, cur_note, cur_acc[cur_note], cur_oct[cur_note]) - if(notes.len >= 2 && text2num(notes[2])) - sleep(sanitize_tempo(tempo / text2num(notes[2]))) - else - sleep(tempo) - repeat-- - toggle_playing(user, FALSE) - repeat = 0 - updateDialog(user) - -/datum/song/proc/interact(mob/user) - var/dat = "" - - if(lines.len > 0) - dat += "

                Playback

                " - if(!playing) - dat += "Play Stop

                " - dat += "Repeat Song: " - dat += repeat > 0 ? "--" : "--" - dat += " [repeat] times " - dat += repeat < max_repeats ? "++" : "++" - dat += "
                " - else - dat += "Play Stop
                " - dat += "Repeats left: [repeat]
                " - if(!edit) - dat += "
                Show Editor
                " - else - dat += "

                Editing

                " - dat += "Hide Editor" - dat += " Start a New Song" - dat += " Import a Song

                " - var/bpm = round(600 / tempo) - dat += "Tempo: - [bpm] BPM +

                " - var/linecount = 0 - for(var/line in lines) - linecount += 1 - dat += "Line [linecount]: Edit X [line]
                " - dat += "Add Line

                " - if(help) - dat += "Hide Help
                " - dat += {" - Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
                - Every note in a chord will play together, with chord timed by the tempo.
                -
                - Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
                - By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
                - Example: C,D,E,F,G,A,B will play a C major scale.
                - After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
                - Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
                - A pause may be denoted by an empty chord: C,E,,C,G
                - To make a chord be a different time, end it with /x, where the chord length will be length
                - defined by tempo / x: C,G/2,E/4
                - Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 -
                - Lines may be up to [MUSIC_MAXLINECHARS] characters.
                - A song may only contain up to [MUSIC_MAXLINES] lines.
                - "} - else - dat += "Show Help
                " - - var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state)) - popup.open() - -/datum/song/proc/ParseSong(text) - set waitfor = FALSE - //split into lines - lines = splittext(text, "\n") - if(lines.len) - var/bpm_string = "BPM: " - if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1)) - tempo = sanitize_tempo(600 / text2num(copytext(lines[1], length(bpm_string) + 1))) - lines.Cut(1, 2) - else - tempo = sanitize_tempo(5) // default 120 BPM - if(lines.len > MUSIC_MAXLINES) - to_chat(usr, "Too many lines!") - lines.Cut(MUSIC_MAXLINES + 1) - var/linenum = 1 - for(var/l in lines) - if(length_char(l) > MUSIC_MAXLINECHARS) - to_chat(usr, "Line [linenum] too long!") - lines.Remove(l) - else - linenum++ - updateDialog(usr) // make sure updates when complete - -/datum/song/Topic(href, href_list) - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=instrument") - usr.unset_machine() - return - - instrumentObj.add_fingerprint(usr) - - if(href_list["newsong"]) - lines = new() - tempo = sanitize_tempo(5) // default 120 BPM - name = "" - - else if(href_list["import"]) - var/t = "" - do - t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - - if(length(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(cont == "no") - break - while(length(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - ParseSong(t) - - else if(href_list["help"]) - help = text2num(href_list["help"]) - 1 - - else if(href_list["edit"]) - edit = text2num(href_list["edit"]) - 1 - - if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. - if(playing) - return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. - repeat += round(text2num(href_list["repeat"])) - if(repeat < 0) - repeat = 0 - if(repeat > max_repeats) - repeat = max_repeats - - else if(href_list["tempo"]) - tempo = sanitize_tempo(tempo + text2num(href_list["tempo"])) - - else if(href_list["play"]) - toggle_playing(usr, TRUE) - - else if(href_list["newline"]) - var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) - if(!newline || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(lines.len > MUSIC_MAXLINES) - return - if(length_char(newline) > MUSIC_MAXLINECHARS) - newline = copytext_char(newline, 1, MUSIC_MAXLINECHARS) - lines.Add(newline) - - else if(href_list["deleteline"]) - var/num = round(text2num(href_list["deleteline"])) - if(num > lines.len || num < 1) - return - lines.Cut(num, num+1) - - else if(href_list["modifyline"]) - var/num = round(text2num(href_list["modifyline"]),1) - var/content = stripped_input(usr, "Enter your line: ", instrumentObj.name, lines[num], MUSIC_MAXLINECHARS) - if(!content || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(num > lines.len || num < 1) - return - lines[num] = content - - else if(href_list["stop"]) - toggle_playing(usr, FALSE) - - updateDialog(usr) - return - -/datum/song/proc/sanitize_tempo(new_tempo) - new_tempo = abs(new_tempo) - return max(round(new_tempo, world.tick_lag), world.tick_lag) - -/datum/song/proc/toggle_playing(user, new_play_state) - playing = new_play_state - if(playing) - INVOKE_ASYNC(src, .proc/playsong, user) - SEND_SIGNAL(instrumentObj, COMSIG_SONG_START) - else - hearing_mobs = null - SEND_SIGNAL(instrumentObj, COMSIG_SONG_END) - -// subclass for handheld instruments, like violin -/datum/song/handheld - -/datum/song/handheld/updateDialog(mob/user) - instrumentObj.interact(user) - -/datum/song/handheld/shouldStopPlaying() - if(instrumentObj) - return !isliving(instrumentObj.loc) - else - return TRUE - -////////////////////////////////////////////////////////////////////////// - - -/obj/structure/piano - name = "space minimoog" - icon = 'icons/obj/musician.dmi' - icon_state = "minimoog" - anchored = TRUE - density = TRUE - var/datum/song/song - -/obj/structure/piano/unanchored - anchored = FALSE - -/obj/structure/piano/Initialize() - . = ..() - song = new("piano", src) - - if(prob(50) && icon_state == initial(icon_state)) - name = "space minimoog" - desc = "This is a minimoog, like a space piano, but more spacey!" - icon_state = "minimoog" - else - name = "space piano" - desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." - icon_state = "piano" - -/obj/structure/piano/Destroy() - qdel(song) - song = null - return ..() - -/obj/structure/piano/Initialize(mapload) - . = ..() - if(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - -/obj/structure/piano/attack_hand(mob/user) - . = ..() - if(.) - return - interact(user) - -/obj/structure/piano/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/piano/interact(mob/user) - ui_interact(user) - -/obj/structure/piano/ui_interact(mob/user) - if(!user || !anchored) - return - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - user.set_machine(src) - song.interact(user) - -/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) - ..() - default_unfasten_wrench(user, I, 40) - return TRUE + +#define MUSICIAN_HEARCHECK_MINDELAY 4 +#define MUSIC_MAXLINES 300 +#define MUSIC_MAXLINECHARS 50 + +/datum/song + var/name = "Untitled" + var/list/lines = new() + var/tempo = 5 // delay between notes + + var/playing = 0 // if we're playing + var/help = 0 // if help is open + var/edit = 1 // if we're in editing mode + var/repeat = 0 // number of times remaining to repeat + var/max_repeats = 10 // maximum times we can repeat + + var/instrumentDir = "piano" // the folder with the sounds + var/instrumentExt = "ogg" // the file extension + var/instrumentRange = 15 // how far the sound can be heard + var/obj/instrumentObj = null // the associated obj playing the sound + var/last_hearcheck = 0 + var/list/hearing_mobs + +/datum/song/New(dir, obj, ext = "ogg", range) + tempo = sanitize_tempo(tempo) + instrumentDir = dir + instrumentObj = obj + instrumentExt = ext + instrumentRange = range + +/datum/song/Destroy() + instrumentObj = null + return ..() + +// note is a number from 1-7 for A-G +// acc is either "b", "n", or "#" +// oct is 1-8 (or 9 for C) +/datum/song/proc/playnote(mob/user, note, acc as text, oct) + // handle accidental -> B<>C of E<>F + if(acc == "b" && (note == 3 || note == 6)) // C or F + if(note == 3) + oct-- + note-- + acc = "n" + else if(acc == "#" && (note == 2 || note == 5)) // B or E + if(note == 2) + oct++ + note++ + acc = "n" + else if(acc == "#" && (note == 7)) //G# + note = 1 + acc = "b" + else if(acc == "#") // mass convert all sharps to flats, octave jump already handled + acc = "b" + note++ + + // check octave, C is allowed to go to 9 + if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) + return + + // now generate name + var/soundfile = "sound/runtime/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]" + soundfile = file(soundfile) + // make sure the note exists + if(!fexists(soundfile)) + return + // and play + var/turf/source = get_turf(instrumentObj) + if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) + LAZYCLEARLIST(hearing_mobs) + for(var/mob/M in get_hearers_in_view(instrumentRange, source)) + LAZYADD(hearing_mobs, M) + last_hearcheck = world.time + + var/sound/music_played = sound(soundfile) + for(var/i in hearing_mobs) + var/mob/M = i + if(HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M)) + var/mob/living/L = M + L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC) + if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) + continue + M.playsound_local(source, null, 100, falloff = 5, S = music_played) + +/datum/song/proc/updateDialog(mob/user) + instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise + +/datum/song/proc/shouldStopPlaying(mob/user) + if(instrumentObj) + if(!user.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return TRUE + return !instrumentObj.anchored // add special cases to stop in subclasses + else + return TRUE + +/datum/song/proc/playsong(mob/user) + while(repeat >= 0) + var/cur_oct[7] + var/cur_acc[7] + for(var/i = 1 to 7) + cur_oct[i] = 3 + cur_acc[i] = "n" + + for(var/line in lines) + for(var/beat in splittext(lowertext(line), ",")) + var/list/notes = splittext(beat, "/") + for(var/note in splittext(notes[1], "-")) + if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case + toggle_playing(user, FALSE) + return + if(!length(note)) + continue + var/cur_note = text2ascii(note) - 96 + if(cur_note < 1 || cur_note > 7) + continue + var/notelen = length(note) + var/ni = "" + for(var/i = length(note[1]) + 1, i <= notelen, i += length(ni)) + ni = note[i] + if(!text2num(ni)) + if(ni == "#" || ni == "b" || ni == "n") + cur_acc[cur_note] = ni + else if(ni == "s") + cur_acc[cur_note] = "#" // so shift is never required + else + cur_oct[cur_note] = text2num(ni) + if(user.dizziness > 0 && prob(user.dizziness / 2)) + cur_note = clamp(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7) + if(user.dizziness > 0 && prob(user.dizziness / 5)) + if(prob(30)) + cur_acc[cur_note] = "#" + else if(prob(42)) + cur_acc[cur_note] = "b" + else if(prob(75)) + cur_acc[cur_note] = "n" + playnote(user, cur_note, cur_acc[cur_note], cur_oct[cur_note]) + if(notes.len >= 2 && text2num(notes[2])) + sleep(sanitize_tempo(tempo / text2num(notes[2]))) + else + sleep(tempo) + repeat-- + toggle_playing(user, FALSE) + repeat = 0 + updateDialog(user) + +/datum/song/proc/interact(mob/user) + var/dat = "" + + if(lines.len > 0) + dat += "

                Playback

                " + if(!playing) + dat += "Play Stop

                " + dat += "Repeat Song: " + dat += repeat > 0 ? "--" : "--" + dat += " [repeat] times " + dat += repeat < max_repeats ? "++" : "++" + dat += "
                " + else + dat += "Play Stop
                " + dat += "Repeats left: [repeat]
                " + if(!edit) + dat += "
                Show Editor
                " + else + dat += "

                Editing

                " + dat += "Hide Editor" + dat += " Start a New Song" + dat += " Import a Song

                " + var/bpm = round(600 / tempo) + dat += "Tempo: - [bpm] BPM +

                " + var/linecount = 0 + for(var/line in lines) + linecount += 1 + dat += "Line [linecount]: Edit X [line]
                " + dat += "Add Line

                " + if(help) + dat += "Hide Help
                " + dat += {" + Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
                + Every note in a chord will play together, with chord timed by the tempo.
                +
                + Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
                + By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
                + Example: C,D,E,F,G,A,B will play a C major scale.
                + After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
                + Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
                + A pause may be denoted by an empty chord: C,E,,C,G
                + To make a chord be a different time, end it with /x, where the chord length will be length
                + defined by tempo / x: C,G/2,E/4
                + Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 +
                + Lines may be up to [MUSIC_MAXLINECHARS] characters.
                + A song may only contain up to [MUSIC_MAXLINES] lines.
                + "} + else + dat += "Show Help
                " + + var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state)) + popup.open() + +/datum/song/proc/ParseSong(text) + set waitfor = FALSE + //split into lines + lines = splittext(text, "\n") + if(lines.len) + var/bpm_string = "BPM: " + if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1)) + tempo = sanitize_tempo(600 / text2num(copytext(lines[1], length(bpm_string) + 1))) + lines.Cut(1, 2) + else + tempo = sanitize_tempo(5) // default 120 BPM + if(lines.len > MUSIC_MAXLINES) + to_chat(usr, "Too many lines!") + lines.Cut(MUSIC_MAXLINES + 1) + var/linenum = 1 + for(var/l in lines) + if(length_char(l) > MUSIC_MAXLINECHARS) + to_chat(usr, "Line [linenum] too long!") + lines.Remove(l) + else + linenum++ + updateDialog(usr) // make sure updates when complete + +/datum/song/Topic(href, href_list) + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + usr << browse(null, "window=instrument") + usr.unset_machine() + return + + instrumentObj.add_fingerprint(usr) + + if(href_list["newsong"]) + lines = new() + tempo = sanitize_tempo(5) // default 120 BPM + name = "" + + else if(href_list["import"]) + var/t = "" + do + t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + + if(length(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(cont == "no") + break + while(length(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + ParseSong(t) + + else if(href_list["help"]) + help = text2num(href_list["help"]) - 1 + + else if(href_list["edit"]) + edit = text2num(href_list["edit"]) - 1 + + if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. + if(playing) + return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. + repeat += round(text2num(href_list["repeat"])) + if(repeat < 0) + repeat = 0 + if(repeat > max_repeats) + repeat = max_repeats + + else if(href_list["tempo"]) + tempo = sanitize_tempo(tempo + text2num(href_list["tempo"])) + + else if(href_list["play"]) + toggle_playing(usr, TRUE) + + else if(href_list["newline"]) + var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) + if(!newline || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(lines.len > MUSIC_MAXLINES) + return + if(length_char(newline) > MUSIC_MAXLINECHARS) + newline = copytext_char(newline, 1, MUSIC_MAXLINECHARS) + lines.Add(newline) + + else if(href_list["deleteline"]) + var/num = round(text2num(href_list["deleteline"])) + if(num > lines.len || num < 1) + return + lines.Cut(num, num+1) + + else if(href_list["modifyline"]) + var/num = round(text2num(href_list["modifyline"]),1) + var/content = stripped_input(usr, "Enter your line: ", instrumentObj.name, lines[num], MUSIC_MAXLINECHARS) + if(!content || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(num > lines.len || num < 1) + return + lines[num] = content + + else if(href_list["stop"]) + toggle_playing(usr, FALSE) + + updateDialog(usr) + return + +/datum/song/proc/sanitize_tempo(new_tempo) + new_tempo = abs(new_tempo) + return max(round(new_tempo, world.tick_lag), world.tick_lag) + +/datum/song/proc/toggle_playing(user, new_play_state) + playing = new_play_state + if(playing) + INVOKE_ASYNC(src, .proc/playsong, user) + SEND_SIGNAL(instrumentObj, COMSIG_SONG_START) + else + hearing_mobs = null + SEND_SIGNAL(instrumentObj, COMSIG_SONG_END) + +// subclass for handheld instruments, like violin +/datum/song/handheld + +/datum/song/handheld/updateDialog(mob/user) + instrumentObj.interact(user) + +/datum/song/handheld/shouldStopPlaying() + if(instrumentObj) + return !isliving(instrumentObj.loc) + else + return TRUE + +////////////////////////////////////////////////////////////////////////// + + +/obj/structure/piano + name = "space minimoog" + icon = 'icons/obj/musician.dmi' + icon_state = "minimoog" + anchored = TRUE + density = TRUE + var/datum/song/song + +/obj/structure/piano/unanchored + anchored = FALSE + +/obj/structure/piano/Initialize() + . = ..() + song = new("piano", src) + + if(prob(50) && icon_state == initial(icon_state)) + name = "space minimoog" + desc = "This is a minimoog, like a space piano, but more spacey!" + icon_state = "minimoog" + else + name = "space piano" + desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." + icon_state = "piano" + +/obj/structure/piano/Destroy() + qdel(song) + song = null + return ..() + +/obj/structure/piano/Initialize(mapload) + . = ..() + if(mapload) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + +/obj/structure/piano/attack_hand(mob/user) + . = ..() + if(.) + return + interact(user) + +/obj/structure/piano/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/piano/interact(mob/user) + ui_interact(user) + +/obj/structure/piano/ui_interact(mob/user) + if(!user || !anchored) + return + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + user.set_machine(src) + song.interact(user) + +/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I, 40) + return TRUE diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm index f6c0916a300..071d1d0392e 100644 --- a/code/game/objects/structures/noticeboard.dm +++ b/code/game/objects/structures/noticeboard.dm @@ -1,132 +1,132 @@ -/obj/structure/noticeboard - name = "notice board" - desc = "A board for pinning important notices upon." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "nboard00" - density = FALSE - anchored = TRUE - max_integrity = 150 - var/notices = 0 - -/obj/structure/noticeboard/Initialize(mapload) - . = ..() - - if(!mapload) - return - - for(var/obj/item/I in loc) - if(notices > 4) - break - if(istype(I, /obj/item/paper)) - I.forceMove(src) - notices++ - icon_state = "nboard0[notices]" - -//attaching papers!! -/obj/structure/noticeboard/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/paper) || istype(O, /obj/item/photo)) - if(!allowed(user)) - to_chat(user, "You are not authorized to add notices!") - return - if(notices < 5) - if(!user.transferItemToLoc(O, src)) - return - notices++ - icon_state = "nboard0[notices]" - to_chat(user, "You pin the [O] to the noticeboard.") - else - to_chat(user, "The notice board is full!") - else - return ..() - -/obj/structure/noticeboard/interact(mob/user) - ui_interact(user) - -/obj/structure/noticeboard/ui_interact(mob/user) - . = ..() - var/auth = allowed(user) - var/dat = "[name]
                " - for(var/obj/item/P in src) - if(istype(P, /obj/item/paper)) - dat += "[P.name] [auth ? "Write Remove" : ""]
                " - else - dat += "[P.name] [auth ? "Remove" : ""]
                " - user << browse("Notices[dat]","window=noticeboard") - onclose(user, "noticeboard") - -/obj/structure/noticeboard/Topic(href, href_list) - ..() - usr.set_machine(src) - if(href_list["remove"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/I = locate(href_list["remove"]) in contents - if(istype(I) && I.loc == src) - I.forceMove(usr.loc) - usr.put_in_hands(I) - notices-- - icon_state = "nboard0[notices]" - - if(href_list["write"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/P = locate(href_list["write"]) in contents - if(istype(P) && P.loc == src) - var/obj/item/I = usr.is_holding_item_of_type(/obj/item/pen) - if(I) - add_fingerprint(usr) - P.attackby(I, usr) - else - to_chat(usr, "You'll need something to write with!") - - if(href_list["read"]) - var/obj/item/I = locate(href_list["read"]) in contents - if(istype(I) && I.loc == src) - usr.examinate(I) - -/obj/structure/noticeboard/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal (loc, 1) - qdel(src) - -// Notice boards for the heads of staff (plus the qm) - -/obj/structure/noticeboard/captain - name = "Captain's Notice Board" - desc = "Important notices from the Captain." - req_access = list(ACCESS_CAPTAIN) - -/obj/structure/noticeboard/hop - name = "Head of Personnel's Notice Board" - desc = "Important notices from the Head of Personnel." - req_access = list(ACCESS_HOP) - -/obj/structure/noticeboard/ce - name = "Chief Engineer's Notice Board" - desc = "Important notices from the Chief Engineer." - req_access = list(ACCESS_CE) - -/obj/structure/noticeboard/hos - name = "Head of Security's Notice Board" - desc = "Important notices from the Head of Security." - req_access = list(ACCESS_HOS) - -/obj/structure/noticeboard/cmo - name = "Chief Medical Officer's Notice Board" - desc = "Important notices from the Chief Medical Officer." - req_access = list(ACCESS_CMO) - -/obj/structure/noticeboard/rd - name = "Research Director's Notice Board" - desc = "Important notices from the Research Director." - req_access = list(ACCESS_RD) - -/obj/structure/noticeboard/qm - name = "Quartermaster's Notice Board" - desc = "Important notices from the Quartermaster." - req_access = list(ACCESS_QM) - -/obj/structure/noticeboard/staff - name = "Staff Notice Board" - desc = "Important notices from the heads of staff." - req_access = list(ACCESS_HEADS) +/obj/structure/noticeboard + name = "notice board" + desc = "A board for pinning important notices upon." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "nboard00" + density = FALSE + anchored = TRUE + max_integrity = 150 + var/notices = 0 + +/obj/structure/noticeboard/Initialize(mapload) + . = ..() + + if(!mapload) + return + + for(var/obj/item/I in loc) + if(notices > 4) + break + if(istype(I, /obj/item/paper)) + I.forceMove(src) + notices++ + icon_state = "nboard0[notices]" + +//attaching papers!! +/obj/structure/noticeboard/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/paper) || istype(O, /obj/item/photo)) + if(!allowed(user)) + to_chat(user, "You are not authorized to add notices!") + return + if(notices < 5) + if(!user.transferItemToLoc(O, src)) + return + notices++ + icon_state = "nboard0[notices]" + to_chat(user, "You pin the [O] to the noticeboard.") + else + to_chat(user, "The notice board is full!") + else + return ..() + +/obj/structure/noticeboard/interact(mob/user) + ui_interact(user) + +/obj/structure/noticeboard/ui_interact(mob/user) + . = ..() + var/auth = allowed(user) + var/dat = "[name]
                " + for(var/obj/item/P in src) + if(istype(P, /obj/item/paper)) + dat += "[P.name] [auth ? "Write Remove" : ""]
                " + else + dat += "[P.name] [auth ? "Remove" : ""]
                " + user << browse("Notices[dat]","window=noticeboard") + onclose(user, "noticeboard") + +/obj/structure/noticeboard/Topic(href, href_list) + ..() + usr.set_machine(src) + if(href_list["remove"]) + if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open + return + var/obj/item/I = locate(href_list["remove"]) in contents + if(istype(I) && I.loc == src) + I.forceMove(usr.loc) + usr.put_in_hands(I) + notices-- + icon_state = "nboard0[notices]" + + if(href_list["write"]) + if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open + return + var/obj/item/P = locate(href_list["write"]) in contents + if(istype(P) && P.loc == src) + var/obj/item/I = usr.is_holding_item_of_type(/obj/item/pen) + if(I) + add_fingerprint(usr) + P.attackby(I, usr) + else + to_chat(usr, "You'll need something to write with!") + + if(href_list["read"]) + var/obj/item/I = locate(href_list["read"]) in contents + if(istype(I) && I.loc == src) + usr.examinate(I) + +/obj/structure/noticeboard/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal (loc, 1) + qdel(src) + +// Notice boards for the heads of staff (plus the qm) + +/obj/structure/noticeboard/captain + name = "Captain's Notice Board" + desc = "Important notices from the Captain." + req_access = list(ACCESS_CAPTAIN) + +/obj/structure/noticeboard/hop + name = "Head of Personnel's Notice Board" + desc = "Important notices from the Head of Personnel." + req_access = list(ACCESS_HOP) + +/obj/structure/noticeboard/ce + name = "Chief Engineer's Notice Board" + desc = "Important notices from the Chief Engineer." + req_access = list(ACCESS_CE) + +/obj/structure/noticeboard/hos + name = "Head of Security's Notice Board" + desc = "Important notices from the Head of Security." + req_access = list(ACCESS_HOS) + +/obj/structure/noticeboard/cmo + name = "Chief Medical Officer's Notice Board" + desc = "Important notices from the Chief Medical Officer." + req_access = list(ACCESS_CMO) + +/obj/structure/noticeboard/rd + name = "Research Director's Notice Board" + desc = "Important notices from the Research Director." + req_access = list(ACCESS_RD) + +/obj/structure/noticeboard/qm + name = "Quartermaster's Notice Board" + desc = "Important notices from the Quartermaster." + req_access = list(ACCESS_QM) + +/obj/structure/noticeboard/staff + name = "Staff Notice Board" + desc = "Important notices from the heads of staff." + req_access = list(ACCESS_HEADS) diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm index 0496aa49ed3..73323d92048 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -1,205 +1,205 @@ -/* -CONTAINS: -SAFES -FLOOR SAFES -*/ - -//SAFES -/obj/structure/safe - name = "safe" - desc = "A huge chunk of metal with a dial embedded in it. Fine print on the dial reads \"Scarborough Arms - 2 tumbler safe, guaranteed thermite resistant, explosion resistant, and assistant resistant.\"" - icon = 'icons/obj/structures.dmi' - icon_state = "safe" - anchored = TRUE - density = TRUE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT - var/open = FALSE //is the safe open? - var/tumbler_1_pos //the tumbler position- from 0 to 72 - var/tumbler_1_open //the tumbler position to open at- 0 to 72 - var/tumbler_2_pos - var/tumbler_2_open - var/dial = 0 //where is the dial pointing? - var/space = 0 //the combined w_class of everything in the safe - var/maxspace = 24 //the maximum combined w_class of stuff in the safe - var/explosion_count = 0 //Tough, but breakable - -/obj/structure/safe/Initialize() - . = ..() - tumbler_1_pos = rand(0, 71) - tumbler_1_open = rand(0, 71) - - tumbler_2_pos = rand(0, 71) - tumbler_2_open = rand(0, 71) - - -/obj/structure/safe/Initialize(mapload) - . = ..() - - if(!mapload) - return - - for(var/obj/item/I in loc) - if(space >= maxspace) - return - if(I.w_class + space <= maxspace) - space += I.w_class - I.forceMove(src) - - -/obj/structure/safe/proc/check_unlocked(mob/user, canhear) - if(explosion_count > 2) - return 1 - if(user && canhear) - if(tumbler_1_pos == tumbler_1_open) - to_chat(user, "You hear a [pick("tonk", "krunk", "plunk")] from [src].") - if(tumbler_2_pos == tumbler_2_open) - to_chat(user, "You hear a [pick("tink", "krink", "plink")] from [src].") - if(tumbler_1_pos == tumbler_1_open && tumbler_2_pos == tumbler_2_open) - if(user) - visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") - return TRUE - return FALSE - -/obj/structure/safe/proc/decrement(num) - num -= 1 - if(num < 0) - num = 71 - return num - -/obj/structure/safe/proc/increment(num) - num += 1 - if(num > 71) - num = 0 - return num - -/obj/structure/safe/update_icon_state() - if(open) - icon_state = "[initial(icon_state)]-open" - else - icon_state = initial(icon_state) - -/obj/structure/safe/ui_interact(mob/user) - user.set_machine(src) - var/dat = "
                " - dat += "[open ? "Close" : "Open"] [src] | - [dial] +" - if(open) - dat += "" - for(var/i = contents.len, i>=1, i--) - var/obj/item/P = contents[i] - dat += "" - dat += "
                [P.name]
                " - user << browse("[name][dat]", "window=safe;size=350x300") - -/obj/structure/safe/Topic(href, href_list) - if(!ishuman(usr)) - return - var/mob/living/carbon/human/user = usr - - if(!user.canUseTopic(src, BE_CLOSE)) - return - - var/canhear = FALSE - if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope)) - canhear = TRUE - - if(href_list["open"]) - if(check_unlocked()) - to_chat(user, "You [open ? "close" : "open"] [src].") - open = !open - update_icon() - updateUsrDialog() - return - else - to_chat(user, "You can't [open ? "close" : "open"] [src], the lock is engaged!") - return - - if(href_list["decrement"]) - dial = decrement(dial) - if(dial == tumbler_1_pos + 1 || dial == tumbler_1_pos - 71) - tumbler_1_pos = decrement(tumbler_1_pos) - if(canhear) - to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") - if(tumbler_1_pos == tumbler_2_pos + 37 || tumbler_1_pos == tumbler_2_pos - 35) - tumbler_2_pos = decrement(tumbler_2_pos) - if(canhear) - to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") - check_unlocked(user, canhear) - updateUsrDialog() - return - - if(href_list["increment"]) - dial = increment(dial) - if(dial == tumbler_1_pos - 1 || dial == tumbler_1_pos + 71) - tumbler_1_pos = increment(tumbler_1_pos) - if(canhear) - to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") - if(tumbler_1_pos == tumbler_2_pos - 37 || tumbler_1_pos == tumbler_2_pos + 35) - tumbler_2_pos = increment(tumbler_2_pos) - if(canhear) - to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") - check_unlocked(user, canhear) - updateUsrDialog() - return - - if(href_list["retrieve"]) - user << browse("", "window=safe") // Close the menu - - var/obj/item/P = locate(href_list["retrieve"]) in src - if(open) - if(P && in_range(src, user)) - user.put_in_hands(P) - space -= P.w_class - updateUsrDialog() - - -/obj/structure/safe/attackby(obj/item/I, mob/user, params) - if(open) - . = 1 //no afterattack - if(I.w_class + space <= maxspace) - space += I.w_class - if(!user.transferItemToLoc(I, src)) - to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!") - return - to_chat(user, "You put [I] in [src].") - updateUsrDialog() - return - else - to_chat(user, "[I] won't fit in [src].") - return - else if(istype(I, /obj/item/clothing/neck/stethoscope)) - to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!") - else - return ..() - - -/obj/structure/safe/handle_atom_del(atom/A) - updateUsrDialog() - -/obj/structure/safe/blob_act(obj/structure/blob/B) - return - -/obj/structure/safe/ex_act(severity, target) - if(((severity == 2 && target == src) || severity == 1) && explosion_count < 3) - explosion_count++ - switch(explosion_count) - if(1) - desc = initial(desc) + "\nIt looks a little banged up." - if(2) - desc = initial(desc) + "\nIt's pretty heavily damaged." - if(3) - desc = initial(desc) + "\nThe lock seems to be broken." - - -//FLOOR SAFES -/obj/structure/safe/floor - name = "floor safe" - icon_state = "floorsafe" - density = FALSE - layer = LOW_OBJ_LAYER - - -/obj/structure/safe/floor/Initialize(mapload) - . = ..() - - AddElement(/datum/element/undertile) +/* +CONTAINS: +SAFES +FLOOR SAFES +*/ + +//SAFES +/obj/structure/safe + name = "safe" + desc = "A huge chunk of metal with a dial embedded in it. Fine print on the dial reads \"Scarborough Arms - 2 tumbler safe, guaranteed thermite resistant, explosion resistant, and assistant resistant.\"" + icon = 'icons/obj/structures.dmi' + icon_state = "safe" + anchored = TRUE + density = TRUE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT + var/open = FALSE //is the safe open? + var/tumbler_1_pos //the tumbler position- from 0 to 72 + var/tumbler_1_open //the tumbler position to open at- 0 to 72 + var/tumbler_2_pos + var/tumbler_2_open + var/dial = 0 //where is the dial pointing? + var/space = 0 //the combined w_class of everything in the safe + var/maxspace = 24 //the maximum combined w_class of stuff in the safe + var/explosion_count = 0 //Tough, but breakable + +/obj/structure/safe/Initialize() + . = ..() + tumbler_1_pos = rand(0, 71) + tumbler_1_open = rand(0, 71) + + tumbler_2_pos = rand(0, 71) + tumbler_2_open = rand(0, 71) + + +/obj/structure/safe/Initialize(mapload) + . = ..() + + if(!mapload) + return + + for(var/obj/item/I in loc) + if(space >= maxspace) + return + if(I.w_class + space <= maxspace) + space += I.w_class + I.forceMove(src) + + +/obj/structure/safe/proc/check_unlocked(mob/user, canhear) + if(explosion_count > 2) + return 1 + if(user && canhear) + if(tumbler_1_pos == tumbler_1_open) + to_chat(user, "You hear a [pick("tonk", "krunk", "plunk")] from [src].") + if(tumbler_2_pos == tumbler_2_open) + to_chat(user, "You hear a [pick("tink", "krink", "plink")] from [src].") + if(tumbler_1_pos == tumbler_1_open && tumbler_2_pos == tumbler_2_open) + if(user) + visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") + return TRUE + return FALSE + +/obj/structure/safe/proc/decrement(num) + num -= 1 + if(num < 0) + num = 71 + return num + +/obj/structure/safe/proc/increment(num) + num += 1 + if(num > 71) + num = 0 + return num + +/obj/structure/safe/update_icon_state() + if(open) + icon_state = "[initial(icon_state)]-open" + else + icon_state = initial(icon_state) + +/obj/structure/safe/ui_interact(mob/user) + user.set_machine(src) + var/dat = "
                " + dat += "[open ? "Close" : "Open"] [src] | - [dial] +" + if(open) + dat += "" + for(var/i = contents.len, i>=1, i--) + var/obj/item/P = contents[i] + dat += "" + dat += "
                [P.name]
                " + user << browse("[name][dat]", "window=safe;size=350x300") + +/obj/structure/safe/Topic(href, href_list) + if(!ishuman(usr)) + return + var/mob/living/carbon/human/user = usr + + if(!user.canUseTopic(src, BE_CLOSE)) + return + + var/canhear = FALSE + if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope)) + canhear = TRUE + + if(href_list["open"]) + if(check_unlocked()) + to_chat(user, "You [open ? "close" : "open"] [src].") + open = !open + update_icon() + updateUsrDialog() + return + else + to_chat(user, "You can't [open ? "close" : "open"] [src], the lock is engaged!") + return + + if(href_list["decrement"]) + dial = decrement(dial) + if(dial == tumbler_1_pos + 1 || dial == tumbler_1_pos - 71) + tumbler_1_pos = decrement(tumbler_1_pos) + if(canhear) + to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") + if(tumbler_1_pos == tumbler_2_pos + 37 || tumbler_1_pos == tumbler_2_pos - 35) + tumbler_2_pos = decrement(tumbler_2_pos) + if(canhear) + to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") + check_unlocked(user, canhear) + updateUsrDialog() + return + + if(href_list["increment"]) + dial = increment(dial) + if(dial == tumbler_1_pos - 1 || dial == tumbler_1_pos + 71) + tumbler_1_pos = increment(tumbler_1_pos) + if(canhear) + to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") + if(tumbler_1_pos == tumbler_2_pos - 37 || tumbler_1_pos == tumbler_2_pos + 35) + tumbler_2_pos = increment(tumbler_2_pos) + if(canhear) + to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") + check_unlocked(user, canhear) + updateUsrDialog() + return + + if(href_list["retrieve"]) + user << browse("", "window=safe") // Close the menu + + var/obj/item/P = locate(href_list["retrieve"]) in src + if(open) + if(P && in_range(src, user)) + user.put_in_hands(P) + space -= P.w_class + updateUsrDialog() + + +/obj/structure/safe/attackby(obj/item/I, mob/user, params) + if(open) + . = 1 //no afterattack + if(I.w_class + space <= maxspace) + space += I.w_class + if(!user.transferItemToLoc(I, src)) + to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!") + return + to_chat(user, "You put [I] in [src].") + updateUsrDialog() + return + else + to_chat(user, "[I] won't fit in [src].") + return + else if(istype(I, /obj/item/clothing/neck/stethoscope)) + to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!") + else + return ..() + + +/obj/structure/safe/handle_atom_del(atom/A) + updateUsrDialog() + +/obj/structure/safe/blob_act(obj/structure/blob/B) + return + +/obj/structure/safe/ex_act(severity, target) + if(((severity == 2 && target == src) || severity == 1) && explosion_count < 3) + explosion_count++ + switch(explosion_count) + if(1) + desc = initial(desc) + "\nIt looks a little banged up." + if(2) + desc = initial(desc) + "\nIt's pretty heavily damaged." + if(3) + desc = initial(desc) + "\nThe lock seems to be broken." + + +//FLOOR SAFES +/obj/structure/safe/floor + name = "floor safe" + icon_state = "floorsafe" + density = FALSE + layer = LOW_OBJ_LAYER + + +/obj/structure/safe/floor/Initialize(mapload) + . = ..() + + AddElement(/datum/element/undertile) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 65247f2ce57..147687e7094 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -1,685 +1,685 @@ -/* Tables and Racks - * Contains: - * Tables - * Glass Tables - * Wooden Tables - * Reinforced Tables - * Racks - * Rack Parts - */ - -/* - * Tables - */ - -/obj/structure/table - name = "table" - desc = "A square piece of metal standing on four metal legs. It can not move." - icon = 'icons/obj/smooth_structures/table.dmi' - icon_state = "table" - density = TRUE - anchored = TRUE - layer = TABLE_LAYER - climbable = TRUE - pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.") - var/frame = /obj/structure/table_frame - var/framestack = /obj/item/stack/rods - var/buildstack = /obj/item/stack/sheet/metal - var/busy = FALSE - var/buildstackamount = 1 - var/framestackamount = 2 - var/deconstruction_ready = 1 - custom_materials = list(/datum/material/iron = 2000) - max_integrity = 100 - integrity_failure = 0.33 - smooth = SMOOTH_TRUE - canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced, /obj/structure/table/greyscale) - -/obj/structure/table/examine(mob/user) - . = ..() - . += deconstruction_hints(user) - -/obj/structure/table/proc/deconstruction_hints(mob/user) - return "The top is screwed on, but the main bolts are also visible." - -/obj/structure/table/update_icon() - if(smooth) - queue_smooth(src) - queue_smooth_neighbors(src) - -/obj/structure/table/narsie_act() - var/atom/A = loc - qdel(src) - new /obj/structure/table/wood(A) - -/obj/structure/table/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/table/attack_hand(mob/living/user) - if(Adjacent(user) && user.pulling) - if(isliving(user.pulling)) - var/mob/living/pushed_mob = user.pulling - if(pushed_mob.buckled) - to_chat(user, "[pushed_mob] is buckled to [pushed_mob.buckled]!") - return - if(user.a_intent == INTENT_GRAB) - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, "You need a better grip to do that!") - return - if(user.grab_state >= GRAB_NECK) - tablelimbsmash(user, pushed_mob) - else - tablepush(user, pushed_mob) - if(user.a_intent == INTENT_HELP) - pushed_mob.visible_message("[user] begins to place [pushed_mob] onto [src]...", \ - "[user] begins to place [pushed_mob] onto [src]...") - if(do_after(user, 35, target = pushed_mob)) - tableplace(user, pushed_mob) - else - return - user.stop_pulling() - else if(user.pulling.pass_flags & PASSTABLE) - user.Move_Pulled(src) - if (user.pulling.loc == loc) - user.visible_message("[user] places [user.pulling] onto [src].", - "You place [user.pulling] onto [src].") - user.stop_pulling() - return ..() - -/obj/structure/table/attack_tk() - return FALSE - -/obj/structure/table/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return TRUE - if(mover.throwing) - return TRUE - if(locate(/obj/structure/table) in get_turf(mover)) - return TRUE - -/obj/structure/table/CanAStarPass(ID, dir, caller) - . = !density - if(ismovable(caller)) - var/atom/movable/mover = caller - . = . || (mover.pass_flags & PASSTABLE) - -/obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob) - pushed_mob.forceMove(loc) - pushed_mob.set_resting(TRUE, TRUE) - pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ - "[user] places [pushed_mob] onto [src].") - log_combat(user, pushed_mob, "places", null, "onto [src]") - -/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "Throwing [pushed_mob] onto the table might hurt them!") - return - var/added_passtable = FALSE - if(!pushed_mob.pass_flags & PASSTABLE) - added_passtable = TRUE - pushed_mob.pass_flags |= PASSTABLE - pushed_mob.Move(src.loc) - if(added_passtable) - pushed_mob.pass_flags &= ~PASSTABLE - if(pushed_mob.loc != loc) //Something prevented the tabling - return - pushed_mob.Knockdown(30) - pushed_mob.apply_damage(10, BRUTE) - pushed_mob.apply_damage(40, STAMINA) - if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) - deconstruct(FALSE) - playsound(pushed_mob, 'sound/effects/tableslam.ogg', 90, TRUE) - pushed_mob.visible_message("[user] slams [pushed_mob] onto \the [src]!", \ - "[user] slams you onto \the [src]!") - log_combat(user, pushed_mob, "tabled", null, "onto [src]") - SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table) - -/obj/structure/table/proc/tablelimbsmash(mob/living/user, mob/living/pushed_mob) - pushed_mob.Knockdown(30) - var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD) - var/extra_wound = 0 - if(HAS_TRAIT(user, TRAIT_HULK)) - extra_wound = 20 - banged_limb.receive_damage(30, wound_bonus = extra_wound) - pushed_mob.apply_damage(60, STAMINA) - take_damage(50) - if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) - deconstruct(FALSE) - playsound(pushed_mob, 'sound/effects/bang.ogg', 90, TRUE) - pushed_mob.visible_message("[user] smashes [pushed_mob]'s [banged_limb.name] against \the [src]!", - "[user] smashes your [banged_limb.name] against \the [src]") - log_combat(user, pushed_mob, "head slammed", null, "against [src]") - SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table_limbsmash, banged_limb) - -/obj/structure/table/attackby(obj/item/I, mob/user, params) - if(!(flags_1 & NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) - if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready) - to_chat(user, "You start disassembling [src]...") - if(I.use_tool(src, user, 20, volume=50)) - deconstruct(TRUE) - return - - if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready) - to_chat(user, "You start deconstructing [src]...") - if(I.use_tool(src, user, 40, volume=50)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - deconstruct(TRUE, 1) - return - - if(istype(I, /obj/item/storage/bag/tray)) - var/obj/item/storage/bag/tray/T = I - if(T.contents.len > 0) // If the tray isn't empty - for(var/x in T.contents) - var/obj/item/item = x - AfterPutItemOnTable(item, user) - SEND_SIGNAL(I, COMSIG_TRY_STORAGE_QUICK_EMPTY, drop_location()) - user.visible_message("[user] empties [I] on [src].") - return - // If the tray IS empty, continue on (tray will be placed on the table like other items) - - if(istype(I, /obj/item/riding_offhand)) - var/obj/item/riding_offhand/riding_item = I - var/mob/living/carried_mob = riding_item.rider - if(carried_mob == user) //Piggyback user. - return - switch(user.a_intent) - if(INTENT_HARM) - user.unbuckle_mob(carried_mob) - tablelimbsmash(user, carried_mob) - if(INTENT_HELP) - var/tableplace_delay = 3.5 SECONDS - var/skills_space = "" - if(HAS_TRAIT(user, TRAIT_QUICKER_CARRY)) - tableplace_delay = 2 SECONDS - skills_space = " expertly" - else if(HAS_TRAIT(user, TRAIT_QUICK_CARRY)) - tableplace_delay = 2.75 SECONDS - skills_space = " quickly" - carried_mob.visible_message("[user] begins to[skills_space] place [carried_mob] onto [src]...", - "[user] begins to[skills_space] place [carried_mob] onto [src]...") - if(do_after(user, tableplace_delay, target = carried_mob)) - user.unbuckle_mob(carried_mob) - tableplace(user, carried_mob) - else - user.unbuckle_mob(carried_mob) - tablepush(user, carried_mob) - return TRUE - - if(user.a_intent != INTENT_HARM && !(I.item_flags & ABSTRACT)) - if(user.transferItemToLoc(I, drop_location(), silent = FALSE)) - var/list/click_params = params2list(params) - //Center the icon where the user clicked. - if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) - return - //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) - I.pixel_x = clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2) - I.pixel_y = clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2) - AfterPutItemOnTable(I, user) - return TRUE - else - return ..() - -/obj/structure/table/proc/AfterPutItemOnTable(obj/item/I, mob/living/user) - return - -/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags_1 & NODECONSTRUCT_1)) - var/turf/T = get_turf(src) - if(buildstack) - new buildstack(T, buildstackamount) - else - for(var/i in custom_materials) - var/datum/material/M = i - new M.sheet_type(T, FLOOR(custom_materials[M] / MINERAL_MATERIAL_AMOUNT, 1)) - if(!wrench_disassembly) - new frame(T) - else - new framestack(T, framestackamount) - qdel(src) - - -/obj/structure/table/greyscale - icon = 'icons/obj/smooth_structures/table_greyscale.dmi' - icon_state = "table" - material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS - buildstack = null //No buildstack, so generate from mat datums - -///Table on wheels -/obj/structure/table/rolling - name = "Rolling table" - desc = "An NT brand \"Rolly poly\" rolling table. It can and will move." - anchored = FALSE - smooth = SMOOTH_FALSE - canSmoothWith = list() - icon = 'icons/obj/smooth_structures/rollingtable.dmi' - icon_state = "rollingtable" - var/list/attached_items = list() - -/obj/structure/table/rolling/AfterPutItemOnTable(obj/item/I, mob/living/user) - . = ..() - attached_items += I - RegisterSignal(I, COMSIG_MOVABLE_MOVED, .proc/RemoveItemFromTable) //Listen for the pickup event, unregister on pick-up so we aren't moved - -/obj/structure/table/rolling/proc/RemoveItemFromTable(datum/source, newloc, dir) - if(newloc != loc) //Did we not move with the table? because that shit's ok - return FALSE - attached_items -= source - UnregisterSignal(source, COMSIG_MOVABLE_MOVED) - -/obj/structure/table/rolling/Moved(atom/OldLoc, Dir) - . = ..() - for(var/mob/M in OldLoc.contents)//Kidnap everyone on top - M.forceMove(loc) - for(var/x in attached_items) - var/atom/movable/AM = x - if(!AM.Move(loc)) - RemoveItemFromTable(AM, AM.loc) - -/* - * Glass tables - */ -/obj/structure/table/glass - name = "glass table" - desc = "What did I say about leaning on the glass tables? Now you need surgery." - icon = 'icons/obj/smooth_structures/glass_table.dmi' - icon_state = "glass_table" - buildstack = /obj/item/stack/sheet/glass - canSmoothWith = null - max_integrity = 70 - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - var/list/debris = list() - -/obj/structure/table/glass/Initialize() - . = ..() - debris += new frame - debris += new /obj/item/shard - -/obj/structure/table/glass/Destroy() - QDEL_LIST(debris) - . = ..() - -/obj/structure/table/glass/Crossed(atom/movable/AM) - . = ..() - if(flags_1 & NODECONSTRUCT_1) - return - if(!isliving(AM)) - return - // Don't break if they're just flying past - if(AM.throwing) - addtimer(CALLBACK(src, .proc/throw_check, AM), 5) - else - check_break(AM) - -/obj/structure/table/glass/proc/throw_check(mob/living/M) - if(M.loc == get_turf(src)) - check_break(M) - -/obj/structure/table/glass/proc/check_break(mob/living/M) - if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & FLYING)) - table_shatter(M) - -/obj/structure/table/glass/proc/table_shatter(mob/living/L) - visible_message("[src] breaks!", - "You hear breaking glass.") - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, TRUE) - for(var/I in debris) - var/atom/movable/AM = I - AM.forceMove(T) - debris -= AM - if(istype(AM, /obj/item/shard)) - AM.throw_impact(L) - L.Paralyze(100) - qdel(src) - -/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - ..() - return - else - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, TRUE) - for(var/X in debris) - var/atom/movable/AM = X - AM.forceMove(T) - debris -= AM - qdel(src) - -/obj/structure/table/glass/narsie_act() - color = NARSIE_WINDOW_COLOUR - for(var/obj/item/shard/S in debris) - S.color = NARSIE_WINDOW_COLOUR - -/* - * Wooden tables - */ - -/obj/structure/table/wood - name = "wooden table" - desc = "Do not apply fire to this. Rumour says it burns easily." - icon = 'icons/obj/smooth_structures/wood_table.dmi' - icon_state = "wood_table" - frame = /obj/structure/table_frame/wood - framestack = /obj/item/stack/sheet/mineral/wood - buildstack = /obj/item/stack/sheet/mineral/wood - resistance_flags = FLAMMABLE - max_integrity = 70 - canSmoothWith = list(/obj/structure/table/wood, - /obj/structure/table/wood/poker, - /obj/structure/table/wood/bar) - -/obj/structure/table/wood/narsie_act(total_override = TRUE) - if(!total_override) - ..() - -/obj/structure/table/wood/poker //No specialties, Just a mapping object. - name = "gambling table" - desc = "A seedy table for seedy dealings in seedy places." - icon = 'icons/obj/smooth_structures/poker_table.dmi' - icon_state = "poker_table" - buildstack = /obj/item/stack/tile/carpet - -/obj/structure/table/wood/poker/narsie_act() - ..(FALSE) - -/obj/structure/table/wood/fancy - name = "fancy table" - desc = "A standard metal table frame covered with an amazingly fancy, patterned cloth." - icon = 'icons/obj/structures.dmi' - icon_state = "fancy_table" - frame = /obj/structure/table_frame - framestack = /obj/item/stack/rods - buildstack = /obj/item/stack/tile/carpet - canSmoothWith = list(/obj/structure/table/wood/fancy, - /obj/structure/table/wood/fancy/black, - /obj/structure/table/wood/fancy/blue, - /obj/structure/table/wood/fancy/cyan, - /obj/structure/table/wood/fancy/green, - /obj/structure/table/wood/fancy/orange, - /obj/structure/table/wood/fancy/purple, - /obj/structure/table/wood/fancy/red, - /obj/structure/table/wood/fancy/royalblack, - /obj/structure/table/wood/fancy/royalblue) - var/smooth_icon = 'icons/obj/smooth_structures/fancy_table.dmi' // see Initialize() - -/obj/structure/table/wood/fancy/Initialize() - . = ..() - // Needs to be set dynamically because table smooth sprites are 32x34, - // which the editor treats as a two-tile-tall object. The sprites are that - // size so that the north/south corners look nice - examine the detail on - // the sprites in the editor to see why. - icon = smooth_icon - -/obj/structure/table/wood/fancy/black - icon_state = "fancy_table_black" - buildstack = /obj/item/stack/tile/carpet/black - smooth_icon = 'icons/obj/smooth_structures/fancy_table_black.dmi' - -/obj/structure/table/wood/fancy/blue - icon_state = "fancy_table_blue" - buildstack = /obj/item/stack/tile/carpet/blue - smooth_icon = 'icons/obj/smooth_structures/fancy_table_blue.dmi' - -/obj/structure/table/wood/fancy/cyan - icon_state = "fancy_table_cyan" - buildstack = /obj/item/stack/tile/carpet/cyan - smooth_icon = 'icons/obj/smooth_structures/fancy_table_cyan.dmi' - -/obj/structure/table/wood/fancy/green - icon_state = "fancy_table_green" - buildstack = /obj/item/stack/tile/carpet/green - smooth_icon = 'icons/obj/smooth_structures/fancy_table_green.dmi' - -/obj/structure/table/wood/fancy/orange - icon_state = "fancy_table_orange" - buildstack = /obj/item/stack/tile/carpet/orange - smooth_icon = 'icons/obj/smooth_structures/fancy_table_orange.dmi' - -/obj/structure/table/wood/fancy/purple - icon_state = "fancy_table_purple" - buildstack = /obj/item/stack/tile/carpet/purple - smooth_icon = 'icons/obj/smooth_structures/fancy_table_purple.dmi' - -/obj/structure/table/wood/fancy/red - icon_state = "fancy_table_red" - buildstack = /obj/item/stack/tile/carpet/red - smooth_icon = 'icons/obj/smooth_structures/fancy_table_red.dmi' - -/obj/structure/table/wood/fancy/royalblack - icon_state = "fancy_table_royalblack" - buildstack = /obj/item/stack/tile/carpet/royalblack - smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblack.dmi' - -/obj/structure/table/wood/fancy/royalblue - icon_state = "fancy_table_royalblue" - buildstack = /obj/item/stack/tile/carpet/royalblue - smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblue.dmi' - -/* - * Reinforced tables - */ -/obj/structure/table/reinforced - name = "reinforced table" - desc = "A reinforced version of the four legged table." - icon = 'icons/obj/smooth_structures/reinforced_table.dmi' - icon_state = "r_table" - deconstruction_ready = 0 - buildstack = /obj/item/stack/sheet/plasteel - canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table, /obj/structure/table/reinforced/ctf) - max_integrity = 200 - integrity_failure = 0.25 - armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - -/obj/structure/table/reinforced/deconstruction_hints(mob/user) - if(deconstruction_ready) - return "The top cover has been welded loose and the main frame's bolts are exposed." - else - return "The top cover is firmly welded on." - -/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HELP) - if(!W.tool_start_check(user, amount=0)) - return - - if(deconstruction_ready) - to_chat(user, "You start strengthening the reinforced table...") - if (W.use_tool(src, user, 50, volume=50)) - to_chat(user, "You strengthen the table.") - deconstruction_ready = 0 - else - to_chat(user, "You start weakening the reinforced table...") - if (W.use_tool(src, user, 50, volume=50)) - to_chat(user, "You weaken the table.") - deconstruction_ready = 1 - else - . = ..() - -/obj/structure/table/bronze - name = "bronze table" - desc = "A solid table made out of bronze." - icon = 'icons/obj/smooth_structures/brass_table.dmi' - icon_state = "brass_table" - resistance_flags = FIRE_PROOF | ACID_PROOF - buildstack = /obj/item/stack/tile/bronze - canSmoothWith = list(/obj/structure/table/bronze) - -/obj/structure/table/bronze/tablepush(mob/living/user, mob/living/pushed_mob) - ..() - playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE) - -/* - * Surgery Tables - */ - -/obj/structure/table/optable - name = "operating table" - desc = "Used for advanced medical procedures." - icon = 'icons/obj/surgery.dmi' - icon_state = "optable" - buildstack = /obj/item/stack/sheet/mineral/silver - smooth = SMOOTH_FALSE - can_buckle = 1 - buckle_lying = -1 - buckle_requires_restraints = 1 - var/mob/living/carbon/human/patient = null - var/obj/machinery/computer/operating/computer = null - -/obj/structure/table/optable/Initialize() - . = ..() - for(var/direction in GLOB.alldirs) - computer = locate(/obj/machinery/computer/operating) in get_step(src, direction) - if(computer) - computer.table = src - break - -/obj/structure/table/optable/Destroy() - . = ..() - if(computer && computer.table == src) - computer.table = null - -/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob) - pushed_mob.forceMove(loc) - pushed_mob.set_resting(TRUE, TRUE) - visible_message("[user] lays [pushed_mob] on [src].") - get_patient() - -/obj/structure/table/optable/proc/get_patient() - var/mob/living/carbon/M = locate(/mob/living/carbon) in loc - if(M) - if(M.resting) - patient = M - else - patient = null - -/obj/structure/table/optable/proc/check_eligible_patient() - get_patient() - if(!patient) - return FALSE - if(ishuman(patient) || ismonkey(patient)) - return TRUE - return FALSE - -/* - * Racks - */ -/obj/structure/rack - name = "rack" - desc = "Different from the Middle Ages version." - icon = 'icons/obj/objects.dmi' - icon_state = "rack" - layer = TABLE_LAYER - density = TRUE - anchored = TRUE - pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density. - max_integrity = 20 - -/obj/structure/rack/examine(mob/user) - . = ..() - . += "It's held together by a couple of bolts." - -/obj/structure/rack/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(.) - return - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return TRUE - -/obj/structure/rack/CanAStarPass(ID, dir, caller) - . = !density - if(ismovable(caller)) - var/atom/movable/mover = caller - . = . || (mover.pass_flags & PASSTABLE) - -/obj/structure/rack/MouseDrop_T(obj/O, mob/user) - . = ..() - if ((!( istype(O, /obj/item) ) || user.get_active_held_item() != O)) - return - if(!user.dropItemToGround(O)) - return - if(O.loc != src.loc) - step(O, get_dir(O, src)) - -/obj/structure/rack/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) - W.play_tool_sound(src) - deconstruct(TRUE) - return - if(user.a_intent == INTENT_HARM) - return ..() - if(user.transferItemToLoc(W, drop_location())) - return 1 - -/obj/structure/rack/attack_paw(mob/living/user) - attack_hand(user) - -/obj/structure/rack/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!(user.mobility_flags & MOBILITY_STAND) || user.get_num_legs() < 2) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - user.visible_message("[user] kicks [src].", null, null, COMBAT_MESSAGE_RANGE) - take_damage(rand(4,8), BRUTE, "melee", 1) - -/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(loc, 'sound/items/dodgeball.ogg', 80, TRUE) - else - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(loc, 'sound/items/welder.ogg', 40, TRUE) - -/* - * Rack destruction - */ - -/obj/structure/rack/deconstruct(disassembled = TRUE) - if(!(flags_1&NODECONSTRUCT_1)) - density = FALSE - var/obj/item/rack_parts/newparts = new(loc) - transfer_fingerprints_to(newparts) - qdel(src) - - -/* - * Rack Parts - */ - -/obj/item/rack_parts - name = "rack parts" - desc = "Parts of a rack." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "rack_parts" - flags_1 = CONDUCT_1 - custom_materials = list(/datum/material/iron=2000) - var/building = FALSE - -/obj/item/rack_parts/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_WRENCH) - new /obj/item/stack/sheet/metal(user.loc) - qdel(src) - else - . = ..() - -/obj/item/rack_parts/attack_self(mob/user) - if(building) - return - building = TRUE - to_chat(user, "You start constructing a rack...") - if(do_after(user, 50, target = user, progress=TRUE)) - if(!user.temporarilyRemoveItemFromInventory(src)) - return - var/obj/structure/rack/R = new /obj/structure/rack(user.loc) - user.visible_message("[user] assembles \a [R].\ - ", "You assemble \a [R].") - R.add_fingerprint(user) - qdel(src) - building = FALSE +/* Tables and Racks + * Contains: + * Tables + * Glass Tables + * Wooden Tables + * Reinforced Tables + * Racks + * Rack Parts + */ + +/* + * Tables + */ + +/obj/structure/table + name = "table" + desc = "A square piece of metal standing on four metal legs. It can not move." + icon = 'icons/obj/smooth_structures/table.dmi' + icon_state = "table" + density = TRUE + anchored = TRUE + layer = TABLE_LAYER + climbable = TRUE + pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.") + var/frame = /obj/structure/table_frame + var/framestack = /obj/item/stack/rods + var/buildstack = /obj/item/stack/sheet/metal + var/busy = FALSE + var/buildstackamount = 1 + var/framestackamount = 2 + var/deconstruction_ready = 1 + custom_materials = list(/datum/material/iron = 2000) + max_integrity = 100 + integrity_failure = 0.33 + smooth = SMOOTH_TRUE + canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced, /obj/structure/table/greyscale) + +/obj/structure/table/examine(mob/user) + . = ..() + . += deconstruction_hints(user) + +/obj/structure/table/proc/deconstruction_hints(mob/user) + return "The top is screwed on, but the main bolts are also visible." + +/obj/structure/table/update_icon() + if(smooth) + queue_smooth(src) + queue_smooth_neighbors(src) + +/obj/structure/table/narsie_act() + var/atom/A = loc + qdel(src) + new /obj/structure/table/wood(A) + +/obj/structure/table/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/table/attack_hand(mob/living/user) + if(Adjacent(user) && user.pulling) + if(isliving(user.pulling)) + var/mob/living/pushed_mob = user.pulling + if(pushed_mob.buckled) + to_chat(user, "[pushed_mob] is buckled to [pushed_mob.buckled]!") + return + if(user.a_intent == INTENT_GRAB) + if(user.grab_state < GRAB_AGGRESSIVE) + to_chat(user, "You need a better grip to do that!") + return + if(user.grab_state >= GRAB_NECK) + tablelimbsmash(user, pushed_mob) + else + tablepush(user, pushed_mob) + if(user.a_intent == INTENT_HELP) + pushed_mob.visible_message("[user] begins to place [pushed_mob] onto [src]...", \ + "[user] begins to place [pushed_mob] onto [src]...") + if(do_after(user, 35, target = pushed_mob)) + tableplace(user, pushed_mob) + else + return + user.stop_pulling() + else if(user.pulling.pass_flags & PASSTABLE) + user.Move_Pulled(src) + if (user.pulling.loc == loc) + user.visible_message("[user] places [user.pulling] onto [src].", + "You place [user.pulling] onto [src].") + user.stop_pulling() + return ..() + +/obj/structure/table/attack_tk() + return FALSE + +/obj/structure/table/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return TRUE + if(mover.throwing) + return TRUE + if(locate(/obj/structure/table) in get_turf(mover)) + return TRUE + +/obj/structure/table/CanAStarPass(ID, dir, caller) + . = !density + if(ismovable(caller)) + var/atom/movable/mover = caller + . = . || (mover.pass_flags & PASSTABLE) + +/obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob) + pushed_mob.forceMove(loc) + pushed_mob.set_resting(TRUE, TRUE) + pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ + "[user] places [pushed_mob] onto [src].") + log_combat(user, pushed_mob, "places", null, "onto [src]") + +/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "Throwing [pushed_mob] onto the table might hurt them!") + return + var/added_passtable = FALSE + if(!pushed_mob.pass_flags & PASSTABLE) + added_passtable = TRUE + pushed_mob.pass_flags |= PASSTABLE + pushed_mob.Move(src.loc) + if(added_passtable) + pushed_mob.pass_flags &= ~PASSTABLE + if(pushed_mob.loc != loc) //Something prevented the tabling + return + pushed_mob.Knockdown(30) + pushed_mob.apply_damage(10, BRUTE) + pushed_mob.apply_damage(40, STAMINA) + if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) + deconstruct(FALSE) + playsound(pushed_mob, 'sound/effects/tableslam.ogg', 90, TRUE) + pushed_mob.visible_message("[user] slams [pushed_mob] onto \the [src]!", \ + "[user] slams you onto \the [src]!") + log_combat(user, pushed_mob, "tabled", null, "onto [src]") + SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table) + +/obj/structure/table/proc/tablelimbsmash(mob/living/user, mob/living/pushed_mob) + pushed_mob.Knockdown(30) + var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD) + var/extra_wound = 0 + if(HAS_TRAIT(user, TRAIT_HULK)) + extra_wound = 20 + banged_limb.receive_damage(30, wound_bonus = extra_wound) + pushed_mob.apply_damage(60, STAMINA) + take_damage(50) + if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) + deconstruct(FALSE) + playsound(pushed_mob, 'sound/effects/bang.ogg', 90, TRUE) + pushed_mob.visible_message("[user] smashes [pushed_mob]'s [banged_limb.name] against \the [src]!", + "[user] smashes your [banged_limb.name] against \the [src]") + log_combat(user, pushed_mob, "head slammed", null, "against [src]") + SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table_limbsmash, banged_limb) + +/obj/structure/table/attackby(obj/item/I, mob/user, params) + if(!(flags_1 & NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) + if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready) + to_chat(user, "You start disassembling [src]...") + if(I.use_tool(src, user, 20, volume=50)) + deconstruct(TRUE) + return + + if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready) + to_chat(user, "You start deconstructing [src]...") + if(I.use_tool(src, user, 40, volume=50)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + deconstruct(TRUE, 1) + return + + if(istype(I, /obj/item/storage/bag/tray)) + var/obj/item/storage/bag/tray/T = I + if(T.contents.len > 0) // If the tray isn't empty + for(var/x in T.contents) + var/obj/item/item = x + AfterPutItemOnTable(item, user) + SEND_SIGNAL(I, COMSIG_TRY_STORAGE_QUICK_EMPTY, drop_location()) + user.visible_message("[user] empties [I] on [src].") + return + // If the tray IS empty, continue on (tray will be placed on the table like other items) + + if(istype(I, /obj/item/riding_offhand)) + var/obj/item/riding_offhand/riding_item = I + var/mob/living/carried_mob = riding_item.rider + if(carried_mob == user) //Piggyback user. + return + switch(user.a_intent) + if(INTENT_HARM) + user.unbuckle_mob(carried_mob) + tablelimbsmash(user, carried_mob) + if(INTENT_HELP) + var/tableplace_delay = 3.5 SECONDS + var/skills_space = "" + if(HAS_TRAIT(user, TRAIT_QUICKER_CARRY)) + tableplace_delay = 2 SECONDS + skills_space = " expertly" + else if(HAS_TRAIT(user, TRAIT_QUICK_CARRY)) + tableplace_delay = 2.75 SECONDS + skills_space = " quickly" + carried_mob.visible_message("[user] begins to[skills_space] place [carried_mob] onto [src]...", + "[user] begins to[skills_space] place [carried_mob] onto [src]...") + if(do_after(user, tableplace_delay, target = carried_mob)) + user.unbuckle_mob(carried_mob) + tableplace(user, carried_mob) + else + user.unbuckle_mob(carried_mob) + tablepush(user, carried_mob) + return TRUE + + if(user.a_intent != INTENT_HARM && !(I.item_flags & ABSTRACT)) + if(user.transferItemToLoc(I, drop_location(), silent = FALSE)) + var/list/click_params = params2list(params) + //Center the icon where the user clicked. + if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) + return + //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) + I.pixel_x = clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2) + I.pixel_y = clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2) + AfterPutItemOnTable(I, user) + return TRUE + else + return ..() + +/obj/structure/table/proc/AfterPutItemOnTable(obj/item/I, mob/living/user) + return + +/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags_1 & NODECONSTRUCT_1)) + var/turf/T = get_turf(src) + if(buildstack) + new buildstack(T, buildstackamount) + else + for(var/i in custom_materials) + var/datum/material/M = i + new M.sheet_type(T, FLOOR(custom_materials[M] / MINERAL_MATERIAL_AMOUNT, 1)) + if(!wrench_disassembly) + new frame(T) + else + new framestack(T, framestackamount) + qdel(src) + + +/obj/structure/table/greyscale + icon = 'icons/obj/smooth_structures/table_greyscale.dmi' + icon_state = "table" + material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + buildstack = null //No buildstack, so generate from mat datums + +///Table on wheels +/obj/structure/table/rolling + name = "Rolling table" + desc = "An NT brand \"Rolly poly\" rolling table. It can and will move." + anchored = FALSE + smooth = SMOOTH_FALSE + canSmoothWith = list() + icon = 'icons/obj/smooth_structures/rollingtable.dmi' + icon_state = "rollingtable" + var/list/attached_items = list() + +/obj/structure/table/rolling/AfterPutItemOnTable(obj/item/I, mob/living/user) + . = ..() + attached_items += I + RegisterSignal(I, COMSIG_MOVABLE_MOVED, .proc/RemoveItemFromTable) //Listen for the pickup event, unregister on pick-up so we aren't moved + +/obj/structure/table/rolling/proc/RemoveItemFromTable(datum/source, newloc, dir) + if(newloc != loc) //Did we not move with the table? because that shit's ok + return FALSE + attached_items -= source + UnregisterSignal(source, COMSIG_MOVABLE_MOVED) + +/obj/structure/table/rolling/Moved(atom/OldLoc, Dir) + . = ..() + for(var/mob/M in OldLoc.contents)//Kidnap everyone on top + M.forceMove(loc) + for(var/x in attached_items) + var/atom/movable/AM = x + if(!AM.Move(loc)) + RemoveItemFromTable(AM, AM.loc) + +/* + * Glass tables + */ +/obj/structure/table/glass + name = "glass table" + desc = "What did I say about leaning on the glass tables? Now you need surgery." + icon = 'icons/obj/smooth_structures/glass_table.dmi' + icon_state = "glass_table" + buildstack = /obj/item/stack/sheet/glass + canSmoothWith = null + max_integrity = 70 + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + var/list/debris = list() + +/obj/structure/table/glass/Initialize() + . = ..() + debris += new frame + debris += new /obj/item/shard + +/obj/structure/table/glass/Destroy() + QDEL_LIST(debris) + . = ..() + +/obj/structure/table/glass/Crossed(atom/movable/AM) + . = ..() + if(flags_1 & NODECONSTRUCT_1) + return + if(!isliving(AM)) + return + // Don't break if they're just flying past + if(AM.throwing) + addtimer(CALLBACK(src, .proc/throw_check, AM), 5) + else + check_break(AM) + +/obj/structure/table/glass/proc/throw_check(mob/living/M) + if(M.loc == get_turf(src)) + check_break(M) + +/obj/structure/table/glass/proc/check_break(mob/living/M) + if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & FLYING)) + table_shatter(M) + +/obj/structure/table/glass/proc/table_shatter(mob/living/L) + visible_message("[src] breaks!", + "You hear breaking glass.") + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, TRUE) + for(var/I in debris) + var/atom/movable/AM = I + AM.forceMove(T) + debris -= AM + if(istype(AM, /obj/item/shard)) + AM.throw_impact(L) + L.Paralyze(100) + qdel(src) + +/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags_1 & NODECONSTRUCT_1)) + if(disassembled) + ..() + return + else + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, TRUE) + for(var/X in debris) + var/atom/movable/AM = X + AM.forceMove(T) + debris -= AM + qdel(src) + +/obj/structure/table/glass/narsie_act() + color = NARSIE_WINDOW_COLOUR + for(var/obj/item/shard/S in debris) + S.color = NARSIE_WINDOW_COLOUR + +/* + * Wooden tables + */ + +/obj/structure/table/wood + name = "wooden table" + desc = "Do not apply fire to this. Rumour says it burns easily." + icon = 'icons/obj/smooth_structures/wood_table.dmi' + icon_state = "wood_table" + frame = /obj/structure/table_frame/wood + framestack = /obj/item/stack/sheet/mineral/wood + buildstack = /obj/item/stack/sheet/mineral/wood + resistance_flags = FLAMMABLE + max_integrity = 70 + canSmoothWith = list(/obj/structure/table/wood, + /obj/structure/table/wood/poker, + /obj/structure/table/wood/bar) + +/obj/structure/table/wood/narsie_act(total_override = TRUE) + if(!total_override) + ..() + +/obj/structure/table/wood/poker //No specialties, Just a mapping object. + name = "gambling table" + desc = "A seedy table for seedy dealings in seedy places." + icon = 'icons/obj/smooth_structures/poker_table.dmi' + icon_state = "poker_table" + buildstack = /obj/item/stack/tile/carpet + +/obj/structure/table/wood/poker/narsie_act() + ..(FALSE) + +/obj/structure/table/wood/fancy + name = "fancy table" + desc = "A standard metal table frame covered with an amazingly fancy, patterned cloth." + icon = 'icons/obj/structures.dmi' + icon_state = "fancy_table" + frame = /obj/structure/table_frame + framestack = /obj/item/stack/rods + buildstack = /obj/item/stack/tile/carpet + canSmoothWith = list(/obj/structure/table/wood/fancy, + /obj/structure/table/wood/fancy/black, + /obj/structure/table/wood/fancy/blue, + /obj/structure/table/wood/fancy/cyan, + /obj/structure/table/wood/fancy/green, + /obj/structure/table/wood/fancy/orange, + /obj/structure/table/wood/fancy/purple, + /obj/structure/table/wood/fancy/red, + /obj/structure/table/wood/fancy/royalblack, + /obj/structure/table/wood/fancy/royalblue) + var/smooth_icon = 'icons/obj/smooth_structures/fancy_table.dmi' // see Initialize() + +/obj/structure/table/wood/fancy/Initialize() + . = ..() + // Needs to be set dynamically because table smooth sprites are 32x34, + // which the editor treats as a two-tile-tall object. The sprites are that + // size so that the north/south corners look nice - examine the detail on + // the sprites in the editor to see why. + icon = smooth_icon + +/obj/structure/table/wood/fancy/black + icon_state = "fancy_table_black" + buildstack = /obj/item/stack/tile/carpet/black + smooth_icon = 'icons/obj/smooth_structures/fancy_table_black.dmi' + +/obj/structure/table/wood/fancy/blue + icon_state = "fancy_table_blue" + buildstack = /obj/item/stack/tile/carpet/blue + smooth_icon = 'icons/obj/smooth_structures/fancy_table_blue.dmi' + +/obj/structure/table/wood/fancy/cyan + icon_state = "fancy_table_cyan" + buildstack = /obj/item/stack/tile/carpet/cyan + smooth_icon = 'icons/obj/smooth_structures/fancy_table_cyan.dmi' + +/obj/structure/table/wood/fancy/green + icon_state = "fancy_table_green" + buildstack = /obj/item/stack/tile/carpet/green + smooth_icon = 'icons/obj/smooth_structures/fancy_table_green.dmi' + +/obj/structure/table/wood/fancy/orange + icon_state = "fancy_table_orange" + buildstack = /obj/item/stack/tile/carpet/orange + smooth_icon = 'icons/obj/smooth_structures/fancy_table_orange.dmi' + +/obj/structure/table/wood/fancy/purple + icon_state = "fancy_table_purple" + buildstack = /obj/item/stack/tile/carpet/purple + smooth_icon = 'icons/obj/smooth_structures/fancy_table_purple.dmi' + +/obj/structure/table/wood/fancy/red + icon_state = "fancy_table_red" + buildstack = /obj/item/stack/tile/carpet/red + smooth_icon = 'icons/obj/smooth_structures/fancy_table_red.dmi' + +/obj/structure/table/wood/fancy/royalblack + icon_state = "fancy_table_royalblack" + buildstack = /obj/item/stack/tile/carpet/royalblack + smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblack.dmi' + +/obj/structure/table/wood/fancy/royalblue + icon_state = "fancy_table_royalblue" + buildstack = /obj/item/stack/tile/carpet/royalblue + smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblue.dmi' + +/* + * Reinforced tables + */ +/obj/structure/table/reinforced + name = "reinforced table" + desc = "A reinforced version of the four legged table." + icon = 'icons/obj/smooth_structures/reinforced_table.dmi' + icon_state = "r_table" + deconstruction_ready = 0 + buildstack = /obj/item/stack/sheet/plasteel + canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table, /obj/structure/table/reinforced/ctf) + max_integrity = 200 + integrity_failure = 0.25 + armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + +/obj/structure/table/reinforced/deconstruction_hints(mob/user) + if(deconstruction_ready) + return "The top cover has been welded loose and the main frame's bolts are exposed." + else + return "The top cover is firmly welded on." + +/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HELP) + if(!W.tool_start_check(user, amount=0)) + return + + if(deconstruction_ready) + to_chat(user, "You start strengthening the reinforced table...") + if (W.use_tool(src, user, 50, volume=50)) + to_chat(user, "You strengthen the table.") + deconstruction_ready = 0 + else + to_chat(user, "You start weakening the reinforced table...") + if (W.use_tool(src, user, 50, volume=50)) + to_chat(user, "You weaken the table.") + deconstruction_ready = 1 + else + . = ..() + +/obj/structure/table/bronze + name = "bronze table" + desc = "A solid table made out of bronze." + icon = 'icons/obj/smooth_structures/brass_table.dmi' + icon_state = "brass_table" + resistance_flags = FIRE_PROOF | ACID_PROOF + buildstack = /obj/item/stack/tile/bronze + canSmoothWith = list(/obj/structure/table/bronze) + +/obj/structure/table/bronze/tablepush(mob/living/user, mob/living/pushed_mob) + ..() + playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE) + +/* + * Surgery Tables + */ + +/obj/structure/table/optable + name = "operating table" + desc = "Used for advanced medical procedures." + icon = 'icons/obj/surgery.dmi' + icon_state = "optable" + buildstack = /obj/item/stack/sheet/mineral/silver + smooth = SMOOTH_FALSE + can_buckle = 1 + buckle_lying = -1 + buckle_requires_restraints = 1 + var/mob/living/carbon/human/patient = null + var/obj/machinery/computer/operating/computer = null + +/obj/structure/table/optable/Initialize() + . = ..() + for(var/direction in GLOB.alldirs) + computer = locate(/obj/machinery/computer/operating) in get_step(src, direction) + if(computer) + computer.table = src + break + +/obj/structure/table/optable/Destroy() + . = ..() + if(computer && computer.table == src) + computer.table = null + +/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob) + pushed_mob.forceMove(loc) + pushed_mob.set_resting(TRUE, TRUE) + visible_message("[user] lays [pushed_mob] on [src].") + get_patient() + +/obj/structure/table/optable/proc/get_patient() + var/mob/living/carbon/M = locate(/mob/living/carbon) in loc + if(M) + if(M.resting) + patient = M + else + patient = null + +/obj/structure/table/optable/proc/check_eligible_patient() + get_patient() + if(!patient) + return FALSE + if(ishuman(patient) || ismonkey(patient)) + return TRUE + return FALSE + +/* + * Racks + */ +/obj/structure/rack + name = "rack" + desc = "Different from the Middle Ages version." + icon = 'icons/obj/objects.dmi' + icon_state = "rack" + layer = TABLE_LAYER + density = TRUE + anchored = TRUE + pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density. + max_integrity = 20 + +/obj/structure/rack/examine(mob/user) + . = ..() + . += "It's held together by a couple of bolts." + +/obj/structure/rack/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(.) + return + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return TRUE + +/obj/structure/rack/CanAStarPass(ID, dir, caller) + . = !density + if(ismovable(caller)) + var/atom/movable/mover = caller + . = . || (mover.pass_flags & PASSTABLE) + +/obj/structure/rack/MouseDrop_T(obj/O, mob/user) + . = ..() + if ((!( istype(O, /obj/item) ) || user.get_active_held_item() != O)) + return + if(!user.dropItemToGround(O)) + return + if(O.loc != src.loc) + step(O, get_dir(O, src)) + +/obj/structure/rack/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) + W.play_tool_sound(src) + deconstruct(TRUE) + return + if(user.a_intent == INTENT_HARM) + return ..() + if(user.transferItemToLoc(W, drop_location())) + return 1 + +/obj/structure/rack/attack_paw(mob/living/user) + attack_hand(user) + +/obj/structure/rack/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!(user.mobility_flags & MOBILITY_STAND) || user.get_num_legs() < 2) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + user.visible_message("[user] kicks [src].", null, null, COMBAT_MESSAGE_RANGE) + take_damage(rand(4,8), BRUTE, "melee", 1) + +/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/items/dodgeball.ogg', 80, TRUE) + else + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(loc, 'sound/items/welder.ogg', 40, TRUE) + +/* + * Rack destruction + */ + +/obj/structure/rack/deconstruct(disassembled = TRUE) + if(!(flags_1&NODECONSTRUCT_1)) + density = FALSE + var/obj/item/rack_parts/newparts = new(loc) + transfer_fingerprints_to(newparts) + qdel(src) + + +/* + * Rack Parts + */ + +/obj/item/rack_parts + name = "rack parts" + desc = "Parts of a rack." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "rack_parts" + flags_1 = CONDUCT_1 + custom_materials = list(/datum/material/iron=2000) + var/building = FALSE + +/obj/item/rack_parts/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_WRENCH) + new /obj/item/stack/sheet/metal(user.loc) + qdel(src) + else + . = ..() + +/obj/item/rack_parts/attack_self(mob/user) + if(building) + return + building = TRUE + to_chat(user, "You start constructing a rack...") + if(do_after(user, 50, target = user, progress=TRUE)) + if(!user.temporarilyRemoveItemFromInventory(src)) + return + var/obj/structure/rack/R = new /obj/structure/rack(user.loc) + user.visible_message("[user] assembles \a [R].\ + ", "You assemble \a [R].") + R.add_fingerprint(user) + qdel(src) + building = FALSE diff --git a/code/game/objects/structures/tank_dispenser.dm b/code/game/objects/structures/tank_dispenser.dm index 1988510b7b8..1245b0cbc2e 100644 --- a/code/game/objects/structures/tank_dispenser.dm +++ b/code/game/objects/structures/tank_dispenser.dm @@ -1,111 +1,111 @@ -#define TANK_DISPENSER_CAPACITY 10 - -/obj/structure/tank_dispenser - name = "tank dispenser" - desc = "A simple yet bulky storage device for gas tanks. Holds up to 10 oxygen tanks and 10 plasma tanks." - icon = 'icons/obj/objects.dmi' - icon_state = "dispenser" - density = TRUE - anchored = TRUE - max_integrity = 300 - var/oxygentanks = TANK_DISPENSER_CAPACITY - var/plasmatanks = TANK_DISPENSER_CAPACITY - -/obj/structure/tank_dispenser/oxygen - plasmatanks = 0 - -/obj/structure/tank_dispenser/plasma - oxygentanks = 0 - -/obj/structure/tank_dispenser/Initialize() - . = ..() - for(var/i in 1 to oxygentanks) - new /obj/item/tank/internals/oxygen(src) - for(var/i in 1 to plasmatanks) - new /obj/item/tank/internals/plasma(src) - update_icon() - -/obj/structure/tank_dispenser/update_overlays() - . = ..() - switch(oxygentanks) - if(1 to 3) - . += "oxygen-[oxygentanks]" - if(4 to TANK_DISPENSER_CAPACITY) - . += "oxygen-4" - switch(plasmatanks) - if(1 to 4) - . += "plasma-[plasmatanks]" - if(5 to TANK_DISPENSER_CAPACITY) - . += "plasma-5" - -/obj/structure/tank_dispenser/attackby(obj/item/I, mob/user, params) - var/full - if(istype(I, /obj/item/tank/internals/plasma)) - if(plasmatanks < TANK_DISPENSER_CAPACITY) - plasmatanks++ - else - full = TRUE - else if(istype(I, /obj/item/tank/internals/oxygen)) - if(oxygentanks < TANK_DISPENSER_CAPACITY) - oxygentanks++ - else - full = TRUE - else if(I.tool_behaviour == TOOL_WRENCH) - default_unfasten_wrench(user, I, time = 20) - return - else if(user.a_intent != INTENT_HARM) - to_chat(user, "[I] does not fit into [src].") - return - else - return ..() - if(full) - to_chat(user, "[src] can't hold any more of [I].") - return - - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You put [I] in [src].") - update_icon() - -/obj/structure/tank_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "TankDispenser", name, 275, 103, master_ui, state) - ui.open() - -/obj/structure/tank_dispenser/ui_data(mob/user) - var/list/data = list() - data["oxygen"] = oxygentanks - data["plasma"] = plasmatanks - - return data - -/obj/structure/tank_dispenser/ui_act(action, params) - if(..()) - return - switch(action) - if("plasma") - var/obj/item/tank/internals/plasma/tank = locate() in src - if(tank && Adjacent(usr)) - usr.put_in_hands(tank) - plasmatanks-- - . = TRUE - if("oxygen") - var/obj/item/tank/internals/oxygen/tank = locate() in src - if(tank && Adjacent(usr)) - usr.put_in_hands(tank) - oxygentanks-- - . = TRUE - update_icon() - - -/obj/structure/tank_dispenser/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - for(var/X in src) - var/obj/item/I = X - I.forceMove(loc) - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -#undef TANK_DISPENSER_CAPACITY +#define TANK_DISPENSER_CAPACITY 10 + +/obj/structure/tank_dispenser + name = "tank dispenser" + desc = "A simple yet bulky storage device for gas tanks. Holds up to 10 oxygen tanks and 10 plasma tanks." + icon = 'icons/obj/objects.dmi' + icon_state = "dispenser" + density = TRUE + anchored = TRUE + max_integrity = 300 + var/oxygentanks = TANK_DISPENSER_CAPACITY + var/plasmatanks = TANK_DISPENSER_CAPACITY + +/obj/structure/tank_dispenser/oxygen + plasmatanks = 0 + +/obj/structure/tank_dispenser/plasma + oxygentanks = 0 + +/obj/structure/tank_dispenser/Initialize() + . = ..() + for(var/i in 1 to oxygentanks) + new /obj/item/tank/internals/oxygen(src) + for(var/i in 1 to plasmatanks) + new /obj/item/tank/internals/plasma(src) + update_icon() + +/obj/structure/tank_dispenser/update_overlays() + . = ..() + switch(oxygentanks) + if(1 to 3) + . += "oxygen-[oxygentanks]" + if(4 to TANK_DISPENSER_CAPACITY) + . += "oxygen-4" + switch(plasmatanks) + if(1 to 4) + . += "plasma-[plasmatanks]" + if(5 to TANK_DISPENSER_CAPACITY) + . += "plasma-5" + +/obj/structure/tank_dispenser/attackby(obj/item/I, mob/user, params) + var/full + if(istype(I, /obj/item/tank/internals/plasma)) + if(plasmatanks < TANK_DISPENSER_CAPACITY) + plasmatanks++ + else + full = TRUE + else if(istype(I, /obj/item/tank/internals/oxygen)) + if(oxygentanks < TANK_DISPENSER_CAPACITY) + oxygentanks++ + else + full = TRUE + else if(I.tool_behaviour == TOOL_WRENCH) + default_unfasten_wrench(user, I, time = 20) + return + else if(user.a_intent != INTENT_HARM) + to_chat(user, "[I] does not fit into [src].") + return + else + return ..() + if(full) + to_chat(user, "[src] can't hold any more of [I].") + return + + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You put [I] in [src].") + update_icon() + +/obj/structure/tank_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "TankDispenser", name, 275, 103, master_ui, state) + ui.open() + +/obj/structure/tank_dispenser/ui_data(mob/user) + var/list/data = list() + data["oxygen"] = oxygentanks + data["plasma"] = plasmatanks + + return data + +/obj/structure/tank_dispenser/ui_act(action, params) + if(..()) + return + switch(action) + if("plasma") + var/obj/item/tank/internals/plasma/tank = locate() in src + if(tank && Adjacent(usr)) + usr.put_in_hands(tank) + plasmatanks-- + . = TRUE + if("oxygen") + var/obj/item/tank/internals/oxygen/tank = locate() in src + if(tank && Adjacent(usr)) + usr.put_in_hands(tank) + oxygentanks-- + . = TRUE + update_icon() + + +/obj/structure/tank_dispenser/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + for(var/X in src) + var/obj/item/I = X + I.forceMove(loc) + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +#undef TANK_DISPENSER_CAPACITY diff --git a/code/game/objects/structures/target_stake.dm b/code/game/objects/structures/target_stake.dm index 342d4f0c49d..6f5d5463cfb 100644 --- a/code/game/objects/structures/target_stake.dm +++ b/code/game/objects/structures/target_stake.dm @@ -1,76 +1,76 @@ -/obj/structure/target_stake - name = "target stake" - desc = "A thin platform with negatively-magnetized wheels." - icon = 'icons/obj/objects.dmi' - icon_state = "target_stake" - density = FALSE - flags_1 = CONDUCT_1 - can_buckle = TRUE - max_buckled_mobs = 1 - buckle_lying = FALSE - var/obj/item/target/pinned_target - -/obj/structure/target_stake/Destroy() - if(pinned_target) - pinned_target.nullPinnedLoc() - return ..() - -/obj/structure/target_stake/proc/handle_density() - if(length(buckled_mobs) || pinned_target) - density = TRUE - else - density = FALSE - -/obj/structure/target_stake/post_buckle_mob() - handle_density() - return ..() - -/obj/structure/target_stake/post_unbuckle_mob() - handle_density() - return ..() - -/obj/structure/target_stake/proc/nullPinnedTarget() - pinned_target = null - -/obj/structure/target_stake/Move() - . = ..() - if(pinned_target) - pinned_target.forceMove(loc) - -/obj/structure/target_stake/attackby(obj/item/target/T, mob/user) - if(pinned_target) - return - if(istype(T) && user.transferItemToLoc(T, drop_location())) - pinned_target = T - T.pinnedLoc = src - T.density = TRUE - T.layer = OBJ_LAYER + 0.01 - handle_density() - to_chat(user, "You slide the target into the stake.") - -/obj/structure/target_stake/attack_hand(mob/user) - . = ..() - if(.) - return - if(pinned_target) - removeTarget(user) - -/obj/structure/target_stake/proc/removeTarget(mob/user) - pinned_target.layer = OBJ_LAYER - pinned_target.forceMove(user.loc) - pinned_target.nullPinnedLoc() - nullPinnedTarget() - handle_density() - if(ishuman(user)) - if(!user.get_active_held_item()) - user.put_in_hands(pinned_target) - to_chat(user, "You take the target out of the stake.") - else - pinned_target.forceMove(user.drop_location()) - to_chat(user, "You take the target out of the stake.") - -/obj/structure/target_stake/bullet_act(obj/projectile/P) - if(pinned_target) - pinned_target.bullet_act(P) - else - . = ..() +/obj/structure/target_stake + name = "target stake" + desc = "A thin platform with negatively-magnetized wheels." + icon = 'icons/obj/objects.dmi' + icon_state = "target_stake" + density = FALSE + flags_1 = CONDUCT_1 + can_buckle = TRUE + max_buckled_mobs = 1 + buckle_lying = FALSE + var/obj/item/target/pinned_target + +/obj/structure/target_stake/Destroy() + if(pinned_target) + pinned_target.nullPinnedLoc() + return ..() + +/obj/structure/target_stake/proc/handle_density() + if(length(buckled_mobs) || pinned_target) + density = TRUE + else + density = FALSE + +/obj/structure/target_stake/post_buckle_mob() + handle_density() + return ..() + +/obj/structure/target_stake/post_unbuckle_mob() + handle_density() + return ..() + +/obj/structure/target_stake/proc/nullPinnedTarget() + pinned_target = null + +/obj/structure/target_stake/Move() + . = ..() + if(pinned_target) + pinned_target.forceMove(loc) + +/obj/structure/target_stake/attackby(obj/item/target/T, mob/user) + if(pinned_target) + return + if(istype(T) && user.transferItemToLoc(T, drop_location())) + pinned_target = T + T.pinnedLoc = src + T.density = TRUE + T.layer = OBJ_LAYER + 0.01 + handle_density() + to_chat(user, "You slide the target into the stake.") + +/obj/structure/target_stake/attack_hand(mob/user) + . = ..() + if(.) + return + if(pinned_target) + removeTarget(user) + +/obj/structure/target_stake/proc/removeTarget(mob/user) + pinned_target.layer = OBJ_LAYER + pinned_target.forceMove(user.loc) + pinned_target.nullPinnedLoc() + nullPinnedTarget() + handle_density() + if(ishuman(user)) + if(!user.get_active_held_item()) + user.put_in_hands(pinned_target) + to_chat(user, "You take the target out of the stake.") + else + pinned_target.forceMove(user.drop_location()) + to_chat(user, "You take the target out of the stake.") + +/obj/structure/target_stake/bullet_act(obj/projectile/P) + if(pinned_target) + pinned_target.bullet_act(P) + else + . = ..() diff --git a/code/game/objects/structures/transit_tubes/transit_tube_construction.dm b/code/game/objects/structures/transit_tubes/transit_tube_construction.dm index c8b3c433557..354a9f98cdd 100644 --- a/code/game/objects/structures/transit_tubes/transit_tube_construction.dm +++ b/code/game/objects/structures/transit_tubes/transit_tube_construction.dm @@ -1,164 +1,164 @@ -// transit tube construction - -// normal transit tubes -/obj/structure/c_transit_tube - name = "unattached transit tube" - icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' - icon_state = "straight" - desc = "An unattached segment of transit tube." - density = FALSE - layer = LOW_ITEM_LAYER //same as the built tube - anchored = FALSE - var/flipped = 0 - var/build_type = /obj/structure/transit_tube - var/flipped_build_type - var/base_icon - -/obj/structure/c_transit_tube/proc/can_wrench_in_loc(mob/user) - var/turf/source_turf = get_turf(loc) - var/existing_tubes = 0 - for(var/obj/structure/transit_tube/tube in source_turf) - existing_tubes +=1 - if(existing_tubes >= 2) - to_chat(user, "You cannot wrench any more transit tubes! ") - return FALSE - return TRUE - -/obj/structure/c_transit_tube/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_FLIP | ROTATION_VERBS,null,null,CALLBACK(src,.proc/after_rot)) - -/obj/structure/c_transit_tube/proc/after_rot(mob/user,rotation_type) - if(flipped_build_type && rotation_type == ROTATION_FLIP) - setDir(turn(dir,-180)) //Turn back we don't actually flip - flipped = !flipped - var/cur_flip = initial(flipped) ? !flipped : flipped - if(cur_flip) - build_type = flipped_build_type - else - build_type = initial(build_type) - icon_state = "[base_icon][flipped]" - -/obj/structure/c_transit_tube/wrench_act(mob/living/user, obj/item/I) - ..() - if(!can_wrench_in_loc(user)) - return - to_chat(user, "You start attaching the [name]...") - add_fingerprint(user) - if(I.use_tool(src, user, 2 SECONDS, volume=50, extra_checks=CALLBACK(src, .proc/can_wrench_in_loc, user))) - to_chat(user, "You attach the [name].") - var/obj/structure/transit_tube/R = new build_type(loc, dir) - transfer_fingerprints_to(R) - qdel(src) - return TRUE - -// transit tube station -/obj/structure/c_transit_tube/station - name = "unattached through station" - icon_state = "closed_station0" - build_type = /obj/structure/transit_tube/station - flipped_build_type = /obj/structure/transit_tube/station/flipped - base_icon = "closed_station" - -/obj/structure/c_transit_tube/station/flipped - icon_state = "closed_station1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/flipped - flipped_build_type = /obj/structure/transit_tube/station - - -// reverser station, used for the terminus -/obj/structure/c_transit_tube/station/reverse - name = "unattached terminus station" - icon_state = "closed_terminus0" - build_type = /obj/structure/transit_tube/station/reverse - flipped_build_type = /obj/structure/transit_tube/station/reverse/flipped - base_icon = "closed_terminus" - -/obj/structure/c_transit_tube/station/reverse/flipped - icon_state = "closed_terminus1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/reverse/flipped - flipped_build_type = /obj/structure/transit_tube/station/reverse - -//all the dispenser stations - -/obj/structure/c_transit_tube/station/dispenser - icon_state = "closed_dispenser0" - name = "unattached dispenser station" - build_type = /obj/structure/transit_tube/station/dispenser - flipped_build_type = /obj/structure/transit_tube/station/dispenser/flipped - -/obj/structure/c_transit_tube/station/dispenser/flipped - icon_state = "closed_station1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/dispenser/flipped - flipped_build_type = /obj/structure/transit_tube/station/dispenser - -//and the ones that reverse - -/obj/structure/c_transit_tube/station/dispenser/reverse - name = "unattached terminus dispenser station" - icon_state = "closed_terminus0" - build_type = /obj/structure/transit_tube/station/dispenser/reverse - flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped - base_icon = "closed_terminus" - -/obj/structure/c_transit_tube/station/dispenser/reverse/flipped - icon_state = "closed_terminus1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped - flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse - -//onto some special tube types - -/obj/structure/c_transit_tube/crossing - icon_state = "crossing" - build_type = /obj/structure/transit_tube/crossing - - -/obj/structure/c_transit_tube/diagonal - icon_state = "diagonal" - build_type = /obj/structure/transit_tube/diagonal - -/obj/structure/c_transit_tube/diagonal/crossing - icon_state = "diagonal_crossing" - build_type = /obj/structure/transit_tube/diagonal/crossing - - -/obj/structure/c_transit_tube/curved - icon_state = "curved0" - build_type = /obj/structure/transit_tube/curved - flipped_build_type = /obj/structure/transit_tube/curved/flipped - base_icon = "curved" - -/obj/structure/c_transit_tube/curved/flipped - icon_state = "curved1" - build_type = /obj/structure/transit_tube/curved/flipped - flipped_build_type = /obj/structure/transit_tube/curved - flipped = 1 - - -/obj/structure/c_transit_tube/junction - icon_state = "junction0" - build_type = /obj/structure/transit_tube/junction - flipped_build_type = /obj/structure/transit_tube/junction/flipped - base_icon = "junction" - - -/obj/structure/c_transit_tube/junction/flipped - icon_state = "junction1" - flipped = 1 - build_type = /obj/structure/transit_tube/junction/flipped - flipped_build_type = /obj/structure/transit_tube/junction - - -//transit tube pod -//see station.dm for the logic -/obj/structure/c_transit_tube_pod - name = "unattached transit tube pod" - icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' - icon_state = "pod" - desc = "Could probably be dragged into an open Transit Tube." - anchored = FALSE - density = FALSE +// transit tube construction + +// normal transit tubes +/obj/structure/c_transit_tube + name = "unattached transit tube" + icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' + icon_state = "straight" + desc = "An unattached segment of transit tube." + density = FALSE + layer = LOW_ITEM_LAYER //same as the built tube + anchored = FALSE + var/flipped = 0 + var/build_type = /obj/structure/transit_tube + var/flipped_build_type + var/base_icon + +/obj/structure/c_transit_tube/proc/can_wrench_in_loc(mob/user) + var/turf/source_turf = get_turf(loc) + var/existing_tubes = 0 + for(var/obj/structure/transit_tube/tube in source_turf) + existing_tubes +=1 + if(existing_tubes >= 2) + to_chat(user, "You cannot wrench any more transit tubes! ") + return FALSE + return TRUE + +/obj/structure/c_transit_tube/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_FLIP | ROTATION_VERBS,null,null,CALLBACK(src,.proc/after_rot)) + +/obj/structure/c_transit_tube/proc/after_rot(mob/user,rotation_type) + if(flipped_build_type && rotation_type == ROTATION_FLIP) + setDir(turn(dir,-180)) //Turn back we don't actually flip + flipped = !flipped + var/cur_flip = initial(flipped) ? !flipped : flipped + if(cur_flip) + build_type = flipped_build_type + else + build_type = initial(build_type) + icon_state = "[base_icon][flipped]" + +/obj/structure/c_transit_tube/wrench_act(mob/living/user, obj/item/I) + ..() + if(!can_wrench_in_loc(user)) + return + to_chat(user, "You start attaching the [name]...") + add_fingerprint(user) + if(I.use_tool(src, user, 2 SECONDS, volume=50, extra_checks=CALLBACK(src, .proc/can_wrench_in_loc, user))) + to_chat(user, "You attach the [name].") + var/obj/structure/transit_tube/R = new build_type(loc, dir) + transfer_fingerprints_to(R) + qdel(src) + return TRUE + +// transit tube station +/obj/structure/c_transit_tube/station + name = "unattached through station" + icon_state = "closed_station0" + build_type = /obj/structure/transit_tube/station + flipped_build_type = /obj/structure/transit_tube/station/flipped + base_icon = "closed_station" + +/obj/structure/c_transit_tube/station/flipped + icon_state = "closed_station1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/flipped + flipped_build_type = /obj/structure/transit_tube/station + + +// reverser station, used for the terminus +/obj/structure/c_transit_tube/station/reverse + name = "unattached terminus station" + icon_state = "closed_terminus0" + build_type = /obj/structure/transit_tube/station/reverse + flipped_build_type = /obj/structure/transit_tube/station/reverse/flipped + base_icon = "closed_terminus" + +/obj/structure/c_transit_tube/station/reverse/flipped + icon_state = "closed_terminus1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/reverse/flipped + flipped_build_type = /obj/structure/transit_tube/station/reverse + +//all the dispenser stations + +/obj/structure/c_transit_tube/station/dispenser + icon_state = "closed_dispenser0" + name = "unattached dispenser station" + build_type = /obj/structure/transit_tube/station/dispenser + flipped_build_type = /obj/structure/transit_tube/station/dispenser/flipped + +/obj/structure/c_transit_tube/station/dispenser/flipped + icon_state = "closed_station1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/dispenser/flipped + flipped_build_type = /obj/structure/transit_tube/station/dispenser + +//and the ones that reverse + +/obj/structure/c_transit_tube/station/dispenser/reverse + name = "unattached terminus dispenser station" + icon_state = "closed_terminus0" + build_type = /obj/structure/transit_tube/station/dispenser/reverse + flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped + base_icon = "closed_terminus" + +/obj/structure/c_transit_tube/station/dispenser/reverse/flipped + icon_state = "closed_terminus1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped + flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse + +//onto some special tube types + +/obj/structure/c_transit_tube/crossing + icon_state = "crossing" + build_type = /obj/structure/transit_tube/crossing + + +/obj/structure/c_transit_tube/diagonal + icon_state = "diagonal" + build_type = /obj/structure/transit_tube/diagonal + +/obj/structure/c_transit_tube/diagonal/crossing + icon_state = "diagonal_crossing" + build_type = /obj/structure/transit_tube/diagonal/crossing + + +/obj/structure/c_transit_tube/curved + icon_state = "curved0" + build_type = /obj/structure/transit_tube/curved + flipped_build_type = /obj/structure/transit_tube/curved/flipped + base_icon = "curved" + +/obj/structure/c_transit_tube/curved/flipped + icon_state = "curved1" + build_type = /obj/structure/transit_tube/curved/flipped + flipped_build_type = /obj/structure/transit_tube/curved + flipped = 1 + + +/obj/structure/c_transit_tube/junction + icon_state = "junction0" + build_type = /obj/structure/transit_tube/junction + flipped_build_type = /obj/structure/transit_tube/junction/flipped + base_icon = "junction" + + +/obj/structure/c_transit_tube/junction/flipped + icon_state = "junction1" + flipped = 1 + build_type = /obj/structure/transit_tube/junction/flipped + flipped_build_type = /obj/structure/transit_tube/junction + + +//transit tube pod +//see station.dm for the logic +/obj/structure/c_transit_tube_pod + name = "unattached transit tube pod" + icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' + icon_state = "pod" + desc = "Could probably be dragged into an open Transit Tube." + anchored = FALSE + density = FALSE diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm index 666ea864260..3739cb7aaf5 100644 --- a/code/game/objects/structures/windoor_assembly.dm +++ b/code/game/objects/structures/windoor_assembly.dm @@ -1,357 +1,357 @@ -/* Windoor (window door) assembly -Nodrak - * Step 1: Create a windoor out of rglass - * Step 2: Add r-glass to the assembly to make a secure windoor (Optional) - * Step 3: Rotate or Flip the assembly to face and open the way you want - * Step 4: Wrench the assembly in place - * Step 5: Add cables to the assembly - * Step 6: Set access for the door. - * Step 7: Screwdriver the door to complete - */ - - -/obj/structure/windoor_assembly - icon = 'icons/obj/doors/windoor.dmi' - - name = "windoor Assembly" - icon_state = "l_windoor_assembly01" - desc = "A small glass and wire assembly for windoors." - anchored = FALSE - density = FALSE - dir = NORTH - - var/ini_dir - var/obj/item/electronics/airlock/electronics = null - var/created_name = null - - //Vars to help with the icon's name - var/facing = "l" //Does the windoor open to the left or right? - var/secure = FALSE //Whether or not this creates a secure windoor - var/state = "01" //How far the door assembly has progressed - CanAtmosPass = ATMOS_PASS_PROC - -/obj/structure/windoor_assembly/New(loc, set_dir) - ..() - if(set_dir) - setDir(set_dir) - ini_dir = dir - air_update_turf(1) - -/obj/structure/windoor_assembly/Destroy() - density = FALSE - air_update_turf(1) - return ..() - -/obj/structure/windoor_assembly/Move() - var/turf/T = loc - . = ..() - setDir(ini_dir) - move_update_air(T) - -/obj/structure/windoor_assembly/update_icon_state() - icon_state = "[facing]_[secure ? "secure_" : ""]windoor_assembly[state]" - -/obj/structure/windoor_assembly/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) - return FALSE - -/obj/structure/windoor_assembly/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return 1 - -/obj/structure/windoor_assembly/CheckExit(atom/movable/mover as mob|obj, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) - return !density - else - return 1 - - -/obj/structure/windoor_assembly/attackby(obj/item/W, mob/user, params) - //I really should have spread this out across more states but thin little windoors are hard to sprite. - add_fingerprint(user) - switch(state) - if("01") - if(W.tool_behaviour == TOOL_WELDER && !anchored) - if(!W.tool_start_check(user, amount=0)) - return - - user.visible_message("[user] disassembles the windoor assembly.", - "You start to disassemble the windoor assembly...") - - if(W.use_tool(src, user, 40, volume=50)) - to_chat(user, "You disassemble the windoor assembly.") - var/obj/item/stack/sheet/rglass/RG = new (get_turf(src), 5) - RG.add_fingerprint(user) - if(secure) - var/obj/item/stack/rods/R = new (get_turf(src), 4) - R.add_fingerprint(user) - qdel(src) - return - - //Wrenching an unsecure assembly anchors it in place. Step 4 complete - if(W.tool_behaviour == TOOL_WRENCH && !anchored) - for(var/obj/machinery/door/window/WD in loc) - if(WD.dir == dir) - to_chat(user, "There is already a windoor in that location!") - return - user.visible_message("[user] secures the windoor assembly to the floor.", - "You start to secure the windoor assembly to the floor...") - - if(W.use_tool(src, user, 40, volume=100)) - if(anchored) - return - for(var/obj/machinery/door/window/WD in loc) - if(WD.dir == dir) - to_chat(user, "There is already a windoor in that location!") - return - to_chat(user, "You secure the windoor assembly.") - setAnchored(TRUE) - if(secure) - name = "secure anchored windoor assembly" - else - name = "anchored windoor assembly" - - //Unwrenching an unsecure assembly un-anchors it. Step 4 undone - else if(W.tool_behaviour == TOOL_WRENCH && anchored) - user.visible_message("[user] unsecures the windoor assembly to the floor.", - "You start to unsecure the windoor assembly to the floor...") - - if(W.use_tool(src, user, 40, volume=100)) - if(!anchored) - return - to_chat(user, "You unsecure the windoor assembly.") - setAnchored(FALSE) - if(secure) - name = "secure windoor assembly" - else - name = "windoor assembly" - - //Adding plasteel makes the assembly a secure windoor assembly. Step 2 (optional) complete. - else if(istype(W, /obj/item/stack/sheet/plasteel) && !secure) - var/obj/item/stack/sheet/plasteel/P = W - if(P.get_amount() < 2) - to_chat(user, "You need more plasteel to do this!") - return - to_chat(user, "You start to reinforce the windoor with plasteel...") - - if(do_after(user,40, target = src)) - if(!src || secure || P.get_amount() < 2) - return - - P.use(2) - to_chat(user, "You reinforce the windoor.") - secure = TRUE - if(anchored) - name = "secure anchored windoor assembly" - else - name = "secure windoor assembly" - - //Adding cable to the assembly. Step 5 complete. - else if(istype(W, /obj/item/stack/cable_coil) && anchored) - user.visible_message("[user] wires the windoor assembly.", "You start to wire the windoor assembly...") - - if(do_after(user, 40, target = src)) - if(!src || !anchored || src.state != "01") - return - var/obj/item/stack/cable_coil/CC = W - if(!CC.use(1)) - to_chat(user, "You need more cable to do this!") - return - to_chat(user, "You wire the windoor.") - state = "02" - if(secure) - name = "secure wired windoor assembly" - else - name = "wired windoor assembly" - else - return ..() - - if("02") - - //Removing wire from the assembly. Step 5 undone. - if(W.tool_behaviour == TOOL_WIRECUTTER) - user.visible_message("[user] cuts the wires from the airlock assembly.", "You start to cut the wires from airlock assembly...") - - if(W.use_tool(src, user, 40, volume=100)) - if(state != "02") - return - - to_chat(user, "You cut the windoor wires.") - new/obj/item/stack/cable_coil(get_turf(user), 1) - state = "01" - if(secure) - name = "secure anchored windoor assembly" - else - name = "anchored windoor assembly" - - //Adding airlock electronics for access. Step 6 complete. - else if(istype(W, /obj/item/electronics/airlock)) - if(!user.transferItemToLoc(W, src)) - return - W.play_tool_sound(src, 100) - user.visible_message("[user] installs the electronics into the airlock assembly.", - "You start to install electronics into the airlock assembly...") - - if(do_after(user, 40, target = src)) - if(!src || electronics) - W.forceMove(drop_location()) - return - to_chat(user, "You install the airlock electronics.") - name = "near finished windoor assembly" - electronics = W - else - W.forceMove(drop_location()) - - //Screwdriver to remove airlock electronics. Step 6 undone. - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(!electronics) - return - - user.visible_message("[user] removes the electronics from the airlock assembly.", - "You start to uninstall electronics from the airlock assembly...") - - if(W.use_tool(src, user, 40, volume=100) && electronics) - to_chat(user, "You remove the airlock electronics.") - name = "wired windoor assembly" - var/obj/item/electronics/airlock/ae - ae = electronics - electronics = null - ae.forceMove(drop_location()) - - else if(istype(W, /obj/item/pen)) - var/t = stripped_input(user, "Enter the name for the door.", name, created_name,MAX_NAME_LEN) - if(!t) - return - if(!in_range(src, usr) && loc != usr) - return - created_name = t - return - - - - //Crowbar to complete the assembly, Step 7 complete. - else if(W.tool_behaviour == TOOL_CROWBAR) - if(!electronics) - to_chat(usr, "The assembly is missing electronics!") - return - user << browse(null, "window=windoor_access") - user.visible_message("[user] pries the windoor into the frame.", - "You start prying the windoor into the frame...") - - if(W.use_tool(src, user, 40, volume=100) && electronics) - - density = TRUE //Shouldn't matter but just incase - to_chat(user, "You finish the windoor.") - - if(secure) - var/obj/machinery/door/window/brigdoor/windoor = new /obj/machinery/door/window/brigdoor(loc) - if(facing == "l") - windoor.icon_state = "leftsecureopen" - windoor.base_state = "leftsecure" - else - windoor.icon_state = "rightsecureopen" - windoor.base_state = "rightsecure" - windoor.setDir(dir) - windoor.density = FALSE - - if(electronics.one_access) - windoor.req_one_access = electronics.accesses - else - windoor.req_access = electronics.accesses - windoor.electronics = electronics - electronics.forceMove(windoor) - if(created_name) - windoor.name = created_name - qdel(src) - windoor.close() - - - else - var/obj/machinery/door/window/windoor = new /obj/machinery/door/window(loc) - if(facing == "l") - windoor.icon_state = "leftopen" - windoor.base_state = "left" - else - windoor.icon_state = "rightopen" - windoor.base_state = "right" - windoor.setDir(dir) - windoor.density = FALSE - - if(electronics.one_access) - windoor.req_one_access = electronics.accesses - else - windoor.req_access = electronics.accesses - windoor.electronics = electronics - electronics.loc = windoor - if(created_name) - windoor.name = created_name - qdel(src) - windoor.close() - - - else - return ..() - - //Update to reflect changes(if applicable) - update_icon() - - - -/obj/structure/windoor_assembly/ComponentInitialize() - . = ..() - var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS - AddComponent(/datum/component/simple_rotation, rotation_flags, can_be_rotated=CALLBACK(src, .proc/can_be_rotated), after_rotation=CALLBACK(src,.proc/after_rotation)) - -/obj/structure/windoor_assembly/proc/can_be_rotated(mob/user,rotation_type) - if(anchored) - to_chat(user, "[src] cannot be rotated while it is fastened to the floor!") - return FALSE - var/target_dir = turn(dir, rotation_type == ROTATION_CLOCKWISE ? -90 : 90) - - if(!valid_window_location(loc, target_dir)) - to_chat(user, "[src] cannot be rotated in that direction!") - return FALSE - return TRUE - -/obj/structure/windoor_assembly/proc/after_rotation(mob/user) - ini_dir = dir - update_icon() - -//Flips the windoor assembly, determines whather the door opens to the left or the right -/obj/structure/windoor_assembly/verb/flip() - set name = "Flip Windoor Assembly" - set category = "Object" - set src in oview(1) - if(usr.stat || usr.restrained()) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_USE)) - return - - if(facing == "l") - to_chat(usr, "The windoor will now slide to the right.") - facing = "r" - else - facing = "l" - to_chat(usr, "The windoor will now slide to the left.") - - update_icon() - return +/* Windoor (window door) assembly -Nodrak + * Step 1: Create a windoor out of rglass + * Step 2: Add r-glass to the assembly to make a secure windoor (Optional) + * Step 3: Rotate or Flip the assembly to face and open the way you want + * Step 4: Wrench the assembly in place + * Step 5: Add cables to the assembly + * Step 6: Set access for the door. + * Step 7: Screwdriver the door to complete + */ + + +/obj/structure/windoor_assembly + icon = 'icons/obj/doors/windoor.dmi' + + name = "windoor Assembly" + icon_state = "l_windoor_assembly01" + desc = "A small glass and wire assembly for windoors." + anchored = FALSE + density = FALSE + dir = NORTH + + var/ini_dir + var/obj/item/electronics/airlock/electronics = null + var/created_name = null + + //Vars to help with the icon's name + var/facing = "l" //Does the windoor open to the left or right? + var/secure = FALSE //Whether or not this creates a secure windoor + var/state = "01" //How far the door assembly has progressed + CanAtmosPass = ATMOS_PASS_PROC + +/obj/structure/windoor_assembly/New(loc, set_dir) + ..() + if(set_dir) + setDir(set_dir) + ini_dir = dir + air_update_turf(1) + +/obj/structure/windoor_assembly/Destroy() + density = FALSE + air_update_turf(1) + return ..() + +/obj/structure/windoor_assembly/Move() + var/turf/T = loc + . = ..() + setDir(ini_dir) + move_update_air(T) + +/obj/structure/windoor_assembly/update_icon_state() + icon_state = "[facing]_[secure ? "secure_" : ""]windoor_assembly[state]" + +/obj/structure/windoor_assembly/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + return FALSE + +/obj/structure/windoor_assembly/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return 1 + +/obj/structure/windoor_assembly/CheckExit(atom/movable/mover as mob|obj, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) + return !density + else + return 1 + + +/obj/structure/windoor_assembly/attackby(obj/item/W, mob/user, params) + //I really should have spread this out across more states but thin little windoors are hard to sprite. + add_fingerprint(user) + switch(state) + if("01") + if(W.tool_behaviour == TOOL_WELDER && !anchored) + if(!W.tool_start_check(user, amount=0)) + return + + user.visible_message("[user] disassembles the windoor assembly.", + "You start to disassemble the windoor assembly...") + + if(W.use_tool(src, user, 40, volume=50)) + to_chat(user, "You disassemble the windoor assembly.") + var/obj/item/stack/sheet/rglass/RG = new (get_turf(src), 5) + RG.add_fingerprint(user) + if(secure) + var/obj/item/stack/rods/R = new (get_turf(src), 4) + R.add_fingerprint(user) + qdel(src) + return + + //Wrenching an unsecure assembly anchors it in place. Step 4 complete + if(W.tool_behaviour == TOOL_WRENCH && !anchored) + for(var/obj/machinery/door/window/WD in loc) + if(WD.dir == dir) + to_chat(user, "There is already a windoor in that location!") + return + user.visible_message("[user] secures the windoor assembly to the floor.", + "You start to secure the windoor assembly to the floor...") + + if(W.use_tool(src, user, 40, volume=100)) + if(anchored) + return + for(var/obj/machinery/door/window/WD in loc) + if(WD.dir == dir) + to_chat(user, "There is already a windoor in that location!") + return + to_chat(user, "You secure the windoor assembly.") + setAnchored(TRUE) + if(secure) + name = "secure anchored windoor assembly" + else + name = "anchored windoor assembly" + + //Unwrenching an unsecure assembly un-anchors it. Step 4 undone + else if(W.tool_behaviour == TOOL_WRENCH && anchored) + user.visible_message("[user] unsecures the windoor assembly to the floor.", + "You start to unsecure the windoor assembly to the floor...") + + if(W.use_tool(src, user, 40, volume=100)) + if(!anchored) + return + to_chat(user, "You unsecure the windoor assembly.") + setAnchored(FALSE) + if(secure) + name = "secure windoor assembly" + else + name = "windoor assembly" + + //Adding plasteel makes the assembly a secure windoor assembly. Step 2 (optional) complete. + else if(istype(W, /obj/item/stack/sheet/plasteel) && !secure) + var/obj/item/stack/sheet/plasteel/P = W + if(P.get_amount() < 2) + to_chat(user, "You need more plasteel to do this!") + return + to_chat(user, "You start to reinforce the windoor with plasteel...") + + if(do_after(user,40, target = src)) + if(!src || secure || P.get_amount() < 2) + return + + P.use(2) + to_chat(user, "You reinforce the windoor.") + secure = TRUE + if(anchored) + name = "secure anchored windoor assembly" + else + name = "secure windoor assembly" + + //Adding cable to the assembly. Step 5 complete. + else if(istype(W, /obj/item/stack/cable_coil) && anchored) + user.visible_message("[user] wires the windoor assembly.", "You start to wire the windoor assembly...") + + if(do_after(user, 40, target = src)) + if(!src || !anchored || src.state != "01") + return + var/obj/item/stack/cable_coil/CC = W + if(!CC.use(1)) + to_chat(user, "You need more cable to do this!") + return + to_chat(user, "You wire the windoor.") + state = "02" + if(secure) + name = "secure wired windoor assembly" + else + name = "wired windoor assembly" + else + return ..() + + if("02") + + //Removing wire from the assembly. Step 5 undone. + if(W.tool_behaviour == TOOL_WIRECUTTER) + user.visible_message("[user] cuts the wires from the airlock assembly.", "You start to cut the wires from airlock assembly...") + + if(W.use_tool(src, user, 40, volume=100)) + if(state != "02") + return + + to_chat(user, "You cut the windoor wires.") + new/obj/item/stack/cable_coil(get_turf(user), 1) + state = "01" + if(secure) + name = "secure anchored windoor assembly" + else + name = "anchored windoor assembly" + + //Adding airlock electronics for access. Step 6 complete. + else if(istype(W, /obj/item/electronics/airlock)) + if(!user.transferItemToLoc(W, src)) + return + W.play_tool_sound(src, 100) + user.visible_message("[user] installs the electronics into the airlock assembly.", + "You start to install electronics into the airlock assembly...") + + if(do_after(user, 40, target = src)) + if(!src || electronics) + W.forceMove(drop_location()) + return + to_chat(user, "You install the airlock electronics.") + name = "near finished windoor assembly" + electronics = W + else + W.forceMove(drop_location()) + + //Screwdriver to remove airlock electronics. Step 6 undone. + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(!electronics) + return + + user.visible_message("[user] removes the electronics from the airlock assembly.", + "You start to uninstall electronics from the airlock assembly...") + + if(W.use_tool(src, user, 40, volume=100) && electronics) + to_chat(user, "You remove the airlock electronics.") + name = "wired windoor assembly" + var/obj/item/electronics/airlock/ae + ae = electronics + electronics = null + ae.forceMove(drop_location()) + + else if(istype(W, /obj/item/pen)) + var/t = stripped_input(user, "Enter the name for the door.", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + return + + + + //Crowbar to complete the assembly, Step 7 complete. + else if(W.tool_behaviour == TOOL_CROWBAR) + if(!electronics) + to_chat(usr, "The assembly is missing electronics!") + return + user << browse(null, "window=windoor_access") + user.visible_message("[user] pries the windoor into the frame.", + "You start prying the windoor into the frame...") + + if(W.use_tool(src, user, 40, volume=100) && electronics) + + density = TRUE //Shouldn't matter but just incase + to_chat(user, "You finish the windoor.") + + if(secure) + var/obj/machinery/door/window/brigdoor/windoor = new /obj/machinery/door/window/brigdoor(loc) + if(facing == "l") + windoor.icon_state = "leftsecureopen" + windoor.base_state = "leftsecure" + else + windoor.icon_state = "rightsecureopen" + windoor.base_state = "rightsecure" + windoor.setDir(dir) + windoor.density = FALSE + + if(electronics.one_access) + windoor.req_one_access = electronics.accesses + else + windoor.req_access = electronics.accesses + windoor.electronics = electronics + electronics.forceMove(windoor) + if(created_name) + windoor.name = created_name + qdel(src) + windoor.close() + + + else + var/obj/machinery/door/window/windoor = new /obj/machinery/door/window(loc) + if(facing == "l") + windoor.icon_state = "leftopen" + windoor.base_state = "left" + else + windoor.icon_state = "rightopen" + windoor.base_state = "right" + windoor.setDir(dir) + windoor.density = FALSE + + if(electronics.one_access) + windoor.req_one_access = electronics.accesses + else + windoor.req_access = electronics.accesses + windoor.electronics = electronics + electronics.loc = windoor + if(created_name) + windoor.name = created_name + qdel(src) + windoor.close() + + + else + return ..() + + //Update to reflect changes(if applicable) + update_icon() + + + +/obj/structure/windoor_assembly/ComponentInitialize() + . = ..() + var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS + AddComponent(/datum/component/simple_rotation, rotation_flags, can_be_rotated=CALLBACK(src, .proc/can_be_rotated), after_rotation=CALLBACK(src,.proc/after_rotation)) + +/obj/structure/windoor_assembly/proc/can_be_rotated(mob/user,rotation_type) + if(anchored) + to_chat(user, "[src] cannot be rotated while it is fastened to the floor!") + return FALSE + var/target_dir = turn(dir, rotation_type == ROTATION_CLOCKWISE ? -90 : 90) + + if(!valid_window_location(loc, target_dir)) + to_chat(user, "[src] cannot be rotated in that direction!") + return FALSE + return TRUE + +/obj/structure/windoor_assembly/proc/after_rotation(mob/user) + ini_dir = dir + update_icon() + +//Flips the windoor assembly, determines whather the door opens to the left or the right +/obj/structure/windoor_assembly/verb/flip() + set name = "Flip Windoor Assembly" + set category = "Object" + set src in oview(1) + if(usr.stat || usr.restrained()) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_USE)) + return + + if(facing == "l") + to_chat(usr, "The windoor will now slide to the right.") + facing = "r" + else + facing = "l" + to_chat(usr, "The windoor will now slide to the left.") + + update_icon() + return diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm index 4967a675bd3..78cabdab2aa 100644 --- a/code/game/shuttle_engines.dm +++ b/code/game/shuttle_engines.dm @@ -1,158 +1,158 @@ -#define ENGINE_UNWRENCHED 0 -#define ENGINE_WRENCHED 1 -#define ENGINE_WELDED 2 -#define ENGINE_WELDTIME 200 - -/obj/structure/shuttle - name = "shuttle" - icon = 'icons/turf/shuttle.dmi' - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - max_integrity = 500 - armor = list("melee" = 100, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) //default + ignores melee - -/obj/structure/shuttle/engine - name = "engine" - desc = "A bluespace engine used to make shuttles move." - density = TRUE - anchored = TRUE - var/engine_power = 1 - var/state = ENGINE_WELDED //welding shmelding - -//Ugh this is a lot of copypasta from emitters, welding need some boilerplate reduction -/obj/structure/shuttle/engine/can_be_unfasten_wrench(mob/user, silent) - if(state == ENGINE_WELDED) - if(!silent) - to_chat(user, "[src] is welded to the floor!") - return FAILED_UNFASTEN - return ..() - -/obj/structure/shuttle/engine/default_unfasten_wrench(mob/user, obj/item/I, time = 20) - . = ..() - if(. == SUCCESSFUL_UNFASTEN) - if(anchored) - state = ENGINE_WRENCHED - else - state = ENGINE_UNWRENCHED - -/obj/structure/shuttle/engine/wrench_act(mob/living/user, obj/item/I) - ..() - default_unfasten_wrench(user, I) - return TRUE - -/obj/structure/shuttle/engine/welder_act(mob/living/user, obj/item/I) - . = ..() - switch(state) - if(ENGINE_UNWRENCHED) - to_chat(user, "The [src.name] needs to be wrenched to the floor!") - if(ENGINE_WRENCHED) - if(!I.tool_start_check(user, amount=0)) - return TRUE - - user.visible_message("[user.name] starts to weld the [name] to the floor.", \ - "You start to weld \the [src] to the floor...", \ - "You hear welding.") - - if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) - state = ENGINE_WELDED - to_chat(user, "You weld \the [src] to the floor.") - alter_engine_power(engine_power) - - if(ENGINE_WELDED) - if(!I.tool_start_check(user, amount=0)) - return TRUE - - user.visible_message("[user.name] starts to cut the [name] free from the floor.", \ - "You start to cut \the [src] free from the floor...", \ - "You hear welding.") - - if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) - state = ENGINE_WRENCHED - to_chat(user, "You cut \the [src] free from the floor.") - alter_engine_power(-engine_power) - return TRUE - -/obj/structure/shuttle/engine/Destroy() - if(state == ENGINE_WELDED) - alter_engine_power(-engine_power) - . = ..() - -//Propagates the change to the shuttle. -/obj/structure/shuttle/engine/proc/alter_engine_power(mod) - if(mod == 0) - return - if(SSshuttle.is_in_shuttle_bounds(src)) - var/obj/docking_port/mobile/M = SSshuttle.get_containing_shuttle(src) - if(M) - M.alter_engines(mod) - -/obj/structure/shuttle/engine/heater - name = "engine heater" - icon_state = "heater" - desc = "Directs energy into compressed particles in order to power engines." - engine_power = 0 // todo make these into 2x1 parts - -/obj/structure/shuttle/engine/platform - name = "engine platform" - icon_state = "platform" - desc = "A platform for engine components." - engine_power = 0 - -/obj/structure/shuttle/engine/propulsion - name = "propulsion engine" - icon_state = "propulsion" - desc = "A standard reliable bluespace engine used by many forms of shuttles." - opacity = 1 - -/obj/structure/shuttle/engine/propulsion/left - name = "left propulsion engine" - icon_state = "propulsion_l" - -/obj/structure/shuttle/engine/propulsion/right - name = "right propulsion engine" - icon_state = "propulsion_r" - -/obj/structure/shuttle/engine/propulsion/burst - name = "burst engine" - desc = "An engine that releases a large bluespace burst to propel it." - -/obj/structure/shuttle/engine/propulsion/burst/cargo - state = ENGINE_UNWRENCHED - anchored = FALSE - -/obj/structure/shuttle/engine/propulsion/burst/left - name = "left burst engine" - icon_state = "burst_l" - -/obj/structure/shuttle/engine/propulsion/burst/right - name = "right burst engine" - icon_state = "burst_r" - -/obj/structure/shuttle/engine/router - name = "engine router" - icon_state = "router" - desc = "Redirects around energized particles in engine structures." - -/obj/structure/shuttle/engine/large - name = "engine" - opacity = 1 - icon = 'icons/obj/2x2.dmi' - icon_state = "large_engine" - desc = "A very large bluespace engine used to propel very large ships." - bound_width = 64 - bound_height = 64 - appearance_flags = 0 - -/obj/structure/shuttle/engine/huge - name = "engine" - opacity = 1 - icon = 'icons/obj/3x3.dmi' - icon_state = "huge_engine" - desc = "An extremely large bluespace engine used to propel extremely large ships." - bound_width = 96 - bound_height = 96 - appearance_flags = 0 - -#undef ENGINE_UNWRENCHED -#undef ENGINE_WRENCHED -#undef ENGINE_WELDED -#undef ENGINE_WELDTIME +#define ENGINE_UNWRENCHED 0 +#define ENGINE_WRENCHED 1 +#define ENGINE_WELDED 2 +#define ENGINE_WELDTIME 200 + +/obj/structure/shuttle + name = "shuttle" + icon = 'icons/turf/shuttle.dmi' + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + max_integrity = 500 + armor = list("melee" = 100, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) //default + ignores melee + +/obj/structure/shuttle/engine + name = "engine" + desc = "A bluespace engine used to make shuttles move." + density = TRUE + anchored = TRUE + var/engine_power = 1 + var/state = ENGINE_WELDED //welding shmelding + +//Ugh this is a lot of copypasta from emitters, welding need some boilerplate reduction +/obj/structure/shuttle/engine/can_be_unfasten_wrench(mob/user, silent) + if(state == ENGINE_WELDED) + if(!silent) + to_chat(user, "[src] is welded to the floor!") + return FAILED_UNFASTEN + return ..() + +/obj/structure/shuttle/engine/default_unfasten_wrench(mob/user, obj/item/I, time = 20) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + if(anchored) + state = ENGINE_WRENCHED + else + state = ENGINE_UNWRENCHED + +/obj/structure/shuttle/engine/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I) + return TRUE + +/obj/structure/shuttle/engine/welder_act(mob/living/user, obj/item/I) + . = ..() + switch(state) + if(ENGINE_UNWRENCHED) + to_chat(user, "The [src.name] needs to be wrenched to the floor!") + if(ENGINE_WRENCHED) + if(!I.tool_start_check(user, amount=0)) + return TRUE + + user.visible_message("[user.name] starts to weld the [name] to the floor.", \ + "You start to weld \the [src] to the floor...", \ + "You hear welding.") + + if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) + state = ENGINE_WELDED + to_chat(user, "You weld \the [src] to the floor.") + alter_engine_power(engine_power) + + if(ENGINE_WELDED) + if(!I.tool_start_check(user, amount=0)) + return TRUE + + user.visible_message("[user.name] starts to cut the [name] free from the floor.", \ + "You start to cut \the [src] free from the floor...", \ + "You hear welding.") + + if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) + state = ENGINE_WRENCHED + to_chat(user, "You cut \the [src] free from the floor.") + alter_engine_power(-engine_power) + return TRUE + +/obj/structure/shuttle/engine/Destroy() + if(state == ENGINE_WELDED) + alter_engine_power(-engine_power) + . = ..() + +//Propagates the change to the shuttle. +/obj/structure/shuttle/engine/proc/alter_engine_power(mod) + if(mod == 0) + return + if(SSshuttle.is_in_shuttle_bounds(src)) + var/obj/docking_port/mobile/M = SSshuttle.get_containing_shuttle(src) + if(M) + M.alter_engines(mod) + +/obj/structure/shuttle/engine/heater + name = "engine heater" + icon_state = "heater" + desc = "Directs energy into compressed particles in order to power engines." + engine_power = 0 // todo make these into 2x1 parts + +/obj/structure/shuttle/engine/platform + name = "engine platform" + icon_state = "platform" + desc = "A platform for engine components." + engine_power = 0 + +/obj/structure/shuttle/engine/propulsion + name = "propulsion engine" + icon_state = "propulsion" + desc = "A standard reliable bluespace engine used by many forms of shuttles." + opacity = 1 + +/obj/structure/shuttle/engine/propulsion/left + name = "left propulsion engine" + icon_state = "propulsion_l" + +/obj/structure/shuttle/engine/propulsion/right + name = "right propulsion engine" + icon_state = "propulsion_r" + +/obj/structure/shuttle/engine/propulsion/burst + name = "burst engine" + desc = "An engine that releases a large bluespace burst to propel it." + +/obj/structure/shuttle/engine/propulsion/burst/cargo + state = ENGINE_UNWRENCHED + anchored = FALSE + +/obj/structure/shuttle/engine/propulsion/burst/left + name = "left burst engine" + icon_state = "burst_l" + +/obj/structure/shuttle/engine/propulsion/burst/right + name = "right burst engine" + icon_state = "burst_r" + +/obj/structure/shuttle/engine/router + name = "engine router" + icon_state = "router" + desc = "Redirects around energized particles in engine structures." + +/obj/structure/shuttle/engine/large + name = "engine" + opacity = 1 + icon = 'icons/obj/2x2.dmi' + icon_state = "large_engine" + desc = "A very large bluespace engine used to propel very large ships." + bound_width = 64 + bound_height = 64 + appearance_flags = 0 + +/obj/structure/shuttle/engine/huge + name = "engine" + opacity = 1 + icon = 'icons/obj/3x3.dmi' + icon_state = "huge_engine" + desc = "An extremely large bluespace engine used to propel extremely large ships." + bound_width = 96 + bound_height = 96 + appearance_flags = 0 + +#undef ENGINE_UNWRENCHED +#undef ENGINE_WRENCHED +#undef ENGINE_WELDED +#undef ENGINE_WELDTIME diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index 9c4ed110e4c..8e0d1ff6deb 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -1,168 +1,168 @@ -GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdrop, new) - -/atom/movable/openspace_backdrop - name = "openspace_backdrop" - - anchored = TRUE - - icon = 'icons/turf/floors.dmi' - icon_state = "grey" - plane = OPENSPACE_BACKDROP_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - layer = SPLASHSCREEN_LAYER - //I don't know why the others are aligned but I shall do the same. - vis_flags = VIS_INHERIT_ID - -/turf/open/transparent/openspace - name = "open space" - desc = "Watch your step!" - icon_state = "transparent" - baseturfs = /turf/open/transparent/openspace - CanAtmosPassVertical = ATMOS_PASS_YES - //mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/can_cover_up = TRUE - var/can_build_on = TRUE - -/turf/open/transparent/openspace/airless - initial_gas_mix = AIRLESS_ATMOS - -/turf/open/transparent/openspace/debug/update_multiz() - ..() - return TRUE - -///No bottom level for openspace. -/turf/open/transparent/openspace/show_bottom_level() - return FALSE - -/turf/open/transparent/openspace/Initialize() // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker - . = ..() - - vis_contents += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it. - -/turf/open/transparent/openspace/can_have_cabling() - if(locate(/obj/structure/lattice/catwalk, src)) - return TRUE - return FALSE - -/turf/open/transparent/openspace/zAirIn() - return TRUE - -/turf/open/transparent/openspace/zAirOut() - return TRUE - -/turf/open/transparent/openspace/zPassIn(atom/movable/A, direction, turf/source) - if(direction == DOWN) - for(var/obj/O in contents) - if(O.obj_flags & BLOCK_Z_IN_DOWN) - return FALSE - return TRUE - if(direction == UP) - for(var/obj/O in contents) - if(O.obj_flags & BLOCK_Z_IN_UP) - return FALSE - return TRUE - return FALSE - -/turf/open/transparent/openspace/zPassOut(atom/movable/A, direction, turf/destination) - if(A.anchored) - return FALSE - if(direction == DOWN) - for(var/obj/O in contents) - if(O.obj_flags & BLOCK_Z_OUT_DOWN) - return FALSE - return TRUE - if(direction == UP) - for(var/obj/O in contents) - if(O.obj_flags & BLOCK_Z_OUT_UP) - return FALSE - return TRUE - return FALSE - -/turf/open/transparent/openspace/proc/CanCoverUp() - return can_cover_up - -/turf/open/transparent/openspace/proc/CanBuildHere() - return can_build_on - -/turf/open/transparent/openspace/attackby(obj/item/C, mob/user, params) - ..() - if(!CanBuildHere()) - return - if(istype(C, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = C - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) - if(W) - to_chat(user, "There is already a catwalk here!") - return - if(L) - if(R.use(1)) - qdel(L) - to_chat(user, "You construct a catwalk.") - playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) - new/obj/structure/lattice/catwalk(src) - else - to_chat(user, "You need two rods to build a catwalk!") - return - if(R.use(1)) - to_chat(user, "You construct a lattice.") - playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) - ReplaceWithLattice() - else - to_chat(user, "You need one rod to build a lattice.") - return - if(istype(C, /obj/item/stack/tile/plasteel)) - if(!CanCoverUp()) - return - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - var/obj/item/stack/tile/plasteel/S = C - if(S.use(1)) - qdel(L) - playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) - to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - else - to_chat(user, "You need one floor tile to build a floor!") - else - to_chat(user, "The plating is going to need some support! Place metal rods first.") - -/turf/open/transparent/openspace/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(!CanBuildHere()) - return FALSE - - switch(the_rcd.mode) - if(RCD_FLOORWALL) - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) - else - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) - return FALSE - -/turf/open/transparent/openspace/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) - to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - return TRUE - return FALSE - -/turf/open/transparent/openspace/icemoon - name = "ice chasm" - baseturfs = /turf/open/transparent/openspace/icemoon - initial_gas_mix = ICEMOON_DEFAULT_ATMOS - var/replacement_turf = /turf/open/floor/plating/asteroid/snow/icemoon - -/turf/open/transparent/openspace/icemoon/Initialize() - . = ..() - var/turf/T = below() - if(T.flags_1 & NO_RUINS_1) - ChangeTurf(replacement_turf, null, CHANGETURF_IGNORE_AIR) - return - if(!ismineralturf(T)) - return - var/turf/closed/mineral/M = T - M.mineralAmt = 0 - M.gets_drilled() - +GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdrop, new) + +/atom/movable/openspace_backdrop + name = "openspace_backdrop" + + anchored = TRUE + + icon = 'icons/turf/floors.dmi' + icon_state = "grey" + plane = OPENSPACE_BACKDROP_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = SPLASHSCREEN_LAYER + //I don't know why the others are aligned but I shall do the same. + vis_flags = VIS_INHERIT_ID + +/turf/open/transparent/openspace + name = "open space" + desc = "Watch your step!" + icon_state = "transparent" + baseturfs = /turf/open/transparent/openspace + CanAtmosPassVertical = ATMOS_PASS_YES + //mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/can_cover_up = TRUE + var/can_build_on = TRUE + +/turf/open/transparent/openspace/airless + initial_gas_mix = AIRLESS_ATMOS + +/turf/open/transparent/openspace/debug/update_multiz() + ..() + return TRUE + +///No bottom level for openspace. +/turf/open/transparent/openspace/show_bottom_level() + return FALSE + +/turf/open/transparent/openspace/Initialize() // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker + . = ..() + + vis_contents += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it. + +/turf/open/transparent/openspace/can_have_cabling() + if(locate(/obj/structure/lattice/catwalk, src)) + return TRUE + return FALSE + +/turf/open/transparent/openspace/zAirIn() + return TRUE + +/turf/open/transparent/openspace/zAirOut() + return TRUE + +/turf/open/transparent/openspace/zPassIn(atom/movable/A, direction, turf/source) + if(direction == DOWN) + for(var/obj/O in contents) + if(O.obj_flags & BLOCK_Z_IN_DOWN) + return FALSE + return TRUE + if(direction == UP) + for(var/obj/O in contents) + if(O.obj_flags & BLOCK_Z_IN_UP) + return FALSE + return TRUE + return FALSE + +/turf/open/transparent/openspace/zPassOut(atom/movable/A, direction, turf/destination) + if(A.anchored) + return FALSE + if(direction == DOWN) + for(var/obj/O in contents) + if(O.obj_flags & BLOCK_Z_OUT_DOWN) + return FALSE + return TRUE + if(direction == UP) + for(var/obj/O in contents) + if(O.obj_flags & BLOCK_Z_OUT_UP) + return FALSE + return TRUE + return FALSE + +/turf/open/transparent/openspace/proc/CanCoverUp() + return can_cover_up + +/turf/open/transparent/openspace/proc/CanBuildHere() + return can_build_on + +/turf/open/transparent/openspace/attackby(obj/item/C, mob/user, params) + ..() + if(!CanBuildHere()) + return + if(istype(C, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = C + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) + if(W) + to_chat(user, "There is already a catwalk here!") + return + if(L) + if(R.use(1)) + qdel(L) + to_chat(user, "You construct a catwalk.") + playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) + new/obj/structure/lattice/catwalk(src) + else + to_chat(user, "You need two rods to build a catwalk!") + return + if(R.use(1)) + to_chat(user, "You construct a lattice.") + playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) + ReplaceWithLattice() + else + to_chat(user, "You need one rod to build a lattice.") + return + if(istype(C, /obj/item/stack/tile/plasteel)) + if(!CanCoverUp()) + return + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + if(L) + var/obj/item/stack/tile/plasteel/S = C + if(S.use(1)) + qdel(L) + playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) + to_chat(user, "You build a floor.") + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + else + to_chat(user, "You need one floor tile to build a floor!") + else + to_chat(user, "The plating is going to need some support! Place metal rods first.") + +/turf/open/transparent/openspace/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + if(!CanBuildHere()) + return FALSE + + switch(the_rcd.mode) + if(RCD_FLOORWALL) + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + if(L) + return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) + else + return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) + return FALSE + +/turf/open/transparent/openspace/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + switch(passed_mode) + if(RCD_FLOORWALL) + to_chat(user, "You build a floor.") + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + return TRUE + return FALSE + +/turf/open/transparent/openspace/icemoon + name = "ice chasm" + baseturfs = /turf/open/transparent/openspace/icemoon + initial_gas_mix = ICEMOON_DEFAULT_ATMOS + var/replacement_turf = /turf/open/floor/plating/asteroid/snow/icemoon + +/turf/open/transparent/openspace/icemoon/Initialize() + . = ..() + var/turf/T = below() + if(T.flags_1 & NO_RUINS_1) + ChangeTurf(replacement_turf, null, CHANGETURF_IGNORE_AIR) + return + if(!ismineralturf(T)) + return + var/turf/closed/mineral/M = T + M.mineralAmt = 0 + M.gets_drilled() + diff --git a/code/game/world.dm b/code/game/world.dm index af835bbc92c..dc31e2a9318 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,373 +1,373 @@ -#define RESTART_COUNTER_PATH "data/round_counter.txt" - -GLOBAL_VAR(restart_counter) - -/** - * World creation - * - * Here is where a round itself is actually begun and setup, lots of important config changes happen here - * * db connection setup - * * config loaded from files - * * loads admins - * * Sets up the dynamic menu system - * * and most importantly, calls initialize on the master subsystem, starting the game loop that causes the rest of the game to begin processing and setting up - * - * Note this happens after the Master subsystem is created (as that is a global datum), this means all the subsystems exist, - * but they have not been Initialized at this point, only their New proc has run - * - * Nothing happens until something moves. ~Albert Einstein - * - */ -/world/New() - var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so") - if (fexists(extools)) - call(extools, "maptick_initialize")() - enable_debugger() -#ifdef REFERENCE_TRACKING - enable_reference_tracking() -#endif - - //Early profile for auto-profiler - will be stopped on profiler init if necessary. -#if DM_BUILD >= 1506 - world.Profile(PROFILE_START) -#endif - - log_world("World loaded at [time_stamp()]!") - - SetupExternalRSC() - - make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) - - GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = GLOB.world_econ_log = GLOB.world_shuttle_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl - - GLOB.revdata = new - - InitTgs() - - config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - - load_admins() - - //SetupLogs depends on the RoundID, so lets check - //DB schema and set RoundID if we can - SSdbcore.CheckSchemaVersion() - SSdbcore.SetRoundID() - SetupLogs() - load_poll_data() - -#ifndef USE_CUSTOM_ERROR_HANDLER - world.log = file("[GLOB.log_directory]/dd.log") -#else - if (TgsAvailable()) - world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. -#endif - - LoadVerbs(/datum/verbs/menu) - if(CONFIG_GET(flag/usewhitelist)) - load_whitelist() - - GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 - - if(fexists(RESTART_COUNTER_PATH)) - GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) - fdel(RESTART_COUNTER_PATH) - - if(NO_INIT_PARAMETER in params) - return - - Master.Initialize(10, FALSE, TRUE) - - if(TEST_RUN_PARAMETER in params) - HandleTestRun() - -/world/proc/InitTgs() - TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED) - GLOB.revdata.load_tgs_info() - -/world/proc/HandleTestRun() - //trigger things to run the whole process - Master.sleep_offline_after_initializations = FALSE - SSticker.start_immediately = TRUE - CONFIG_SET(number/round_end_countdown, 0) - var/datum/callback/cb -#ifdef UNIT_TESTS - cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) -#else - cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) -#endif - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) - -/world/proc/SetupExternalRSC() -#if (PRELOAD_RSC == 0) - GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") - var/i=1 - while(i<=GLOB.external_rsc_urls.len) - if(GLOB.external_rsc_urls[i]) - i++ - else - GLOB.external_rsc_urls.Cut(i,i+1) -#endif - -/world/proc/SetupLogs() - var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] - if(!override_dir) - var/realtime = world.realtime - var/texttime = time2text(realtime, "YYYY/MM/DD") - GLOB.log_directory = "data/logs/[texttime]/round-" - GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" - GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" - if(GLOB.round_id) - GLOB.log_directory += "[GLOB.round_id]" - GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" - GLOB.picture_log_directory += "[GLOB.round_id]" - else - var/timestamp = replacetext(time_stamp(), ":", ".") - GLOB.log_directory += "[timestamp]" - GLOB.picture_log_directory += "[timestamp]" - GLOB.picture_logging_prefix += "T_[timestamp]_" - else - GLOB.log_directory = "data/logs/[override_dir]" - GLOB.picture_logging_prefix = "O_[override_dir]_" - GLOB.picture_log_directory = "data/picture_logs/[override_dir]" - - GLOB.world_game_log = "[GLOB.log_directory]/game.log" - GLOB.world_mecha_log = "[GLOB.log_directory]/mecha.log" - GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" - GLOB.world_cloning_log = "[GLOB.log_directory]/cloning.log" - GLOB.world_asset_log = "[GLOB.log_directory]/asset.log" - GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" - GLOB.world_econ_log = "[GLOB.log_directory]/econ.log" - GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" - GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" - GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" - GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" - GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" - GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" - GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" - GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" - GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" - GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" - GLOB.world_paper_log = "[GLOB.log_directory]/paper.log" - GLOB.tgui_log = "[GLOB.log_directory]/tgui.log" - GLOB.world_shuttle_log = "[GLOB.log_directory]/shuttle.log" - GLOB.discord_api_log = "[GLOB.log_directory]/discord_api_log.log" - - GLOB.demo_log = "[GLOB.log_directory]/demo.log" - -#ifdef UNIT_TESTS - GLOB.test_log = "[GLOB.log_directory]/tests.log" - start_log(GLOB.test_log) -#endif - start_log(GLOB.world_game_log) - start_log(GLOB.world_attack_log) - start_log(GLOB.world_econ_log) - start_log(GLOB.world_pda_log) - start_log(GLOB.world_telecomms_log) - start_log(GLOB.world_manifest_log) - start_log(GLOB.world_href_log) - start_log(GLOB.world_qdel_log) - start_log(GLOB.world_runtime_log) - start_log(GLOB.world_job_debug_log) - start_log(GLOB.tgui_log) - start_log(GLOB.world_shuttle_log) - start_log(GLOB.discord_api_log) - - GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently - if(fexists(GLOB.config_error_log)) - fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") - fdel(GLOB.config_error_log) - - if(GLOB.round_id) - log_game("Round ID: [GLOB.round_id]") - - // This was printed early in startup to the world log and config_error.log, - // but those are both private, so let's put the commit info in the runtime - // log which is ultimately public. - log_runtime(GLOB.revdata.get_log_message()) - -/world/Topic(T, addr, master, key) - TGS_TOPIC //redirect to server tools if necessary - - var/static/list/topic_handlers = TopicHandlers() - - var/list/input = params2list(T) - var/datum/world_topic/handler - for(var/I in topic_handlers) - if(I in input) - handler = topic_handlers[I] - break - - if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) - log_topic("\"[T]\", from:[addr], master:[master], key:[key]") - - if(!handler) - return - - handler = new handler() - return handler.TryRun(input) - -/world/proc/AnnouncePR(announcement, list/payload) - var/static/list/PRcounts = list() //PR id -> number of times announced this round - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [announcement]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/world/proc/FinishTestRun() - set waitfor = FALSE - var/list/fail_reasons - if(GLOB) - if(GLOB.total_runtimes != 0) - fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") -#ifdef UNIT_TESTS - if(GLOB.failed_any_test) - LAZYADD(fail_reasons, "Unit Tests failed!") -#endif - if(!GLOB.log_directory) - LAZYADD(fail_reasons, "Missing GLOB.log_directory!") - else - fail_reasons = list("Missing GLOB!") - if(!fail_reasons) - text2file("Success!", "[GLOB.log_directory]/clean_run.lk") - else - log_world("Test run failed!\n[fail_reasons.Join("\n")]") - sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes - qdel(src) //shut it down - -/world/Reboot(reason = 0, fast_track = FALSE) - if (reason || fast_track) //special reboot, do none of the normal stuff - if (usr) - log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") - message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") - to_chat(world, "Rebooting World immediately due to host request.") - else - to_chat(world, "Rebooting world...") - Master.Shutdown() //run SS shutdowns - - TgsReboot() - - if(TEST_RUN_PARAMETER in params) - FinishTestRun() - return - - if(TgsAvailable()) - var/do_hard_reboot - // check the hard reboot counter - var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) - switch(ruhr) - if(-1) - do_hard_reboot = FALSE - if(0) - do_hard_reboot = TRUE - else - if(GLOB.restart_counter >= ruhr) - do_hard_reboot = TRUE - else - text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) - do_hard_reboot = FALSE - - if(do_hard_reboot) - log_world("World hard rebooted at [time_stamp()]") - shutdown_logging() // See comment below. - TgsEndProcess() - - log_world("World rebooted at [time_stamp()]") - shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. - ..() - -/world/proc/update_status() - - var/list/features = list() - - if(GLOB.master_mode) - features += GLOB.master_mode - - if (!GLOB.enter_allowed) - features += "closed" - - var/s = "" - var/hostedby - if(config) - var/server_name = CONFIG_GET(string/servername) - if (server_name) - s += "[server_name] — " - features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" - if(CONFIG_GET(flag/allow_vote_mode)) - features += "vote" - if(CONFIG_GET(flag/allow_ai)) - features += "AI allowed" - hostedby = CONFIG_GET(string/hostedby) - - s += "[station_name()]"; - s += " (" - s += "" //Change this to wherever you want the hub to link to. - s += "Default" //Replace this with something else. Or ever better, delete it and uncomment the game version. - s += "" - s += ")" - - var/players = GLOB.clients.len - - var/popcaptext = "" - var/popcap = max(CONFIG_GET(number/extreme_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/soft_popcap)) - if (popcap) - popcaptext = "/[popcap]" - - if (players > 1) - features += "[players][popcaptext] players" - else if (players > 0) - features += "[players][popcaptext] player" - - game_state = (CONFIG_GET(number/extreme_popcap) && players >= CONFIG_GET(number/extreme_popcap)) //tells the hub if we are full - - if (!host && hostedby) - features += "hosted by [hostedby]" - - if (features) - s += ": [jointext(features, ", ")]" - - status = s - -/world/proc/update_hub_visibility(new_visibility) - if(new_visibility == GLOB.hub_visibility) - return - GLOB.hub_visibility = new_visibility - if(GLOB.hub_visibility) - hub_password = "kMZy3U5jJHSiBQjr" - else - hub_password = "SORRYNOPASSWORD" - -/world/proc/incrementMaxZ() - maxz++ - SSmobs.MaxZChanged() - SSidlenpcpool.MaxZChanged() - - -/world/proc/change_fps(new_value = 20) - if(new_value <= 0) - CRASH("change_fps() called with [new_value] new_value.") - if(fps == new_value) - return //No change required. - - fps = new_value - on_tickrate_change() - - -/world/proc/change_tick_lag(new_value = 0.5) - if(new_value <= 0) - CRASH("change_tick_lag() called with [new_value] new_value.") - if(tick_lag == new_value) - return //No change required. - - tick_lag = new_value - on_tickrate_change() - - -/world/proc/on_tickrate_change() - SStimer?.reset_buckets() +#define RESTART_COUNTER_PATH "data/round_counter.txt" + +GLOBAL_VAR(restart_counter) + +/** + * World creation + * + * Here is where a round itself is actually begun and setup, lots of important config changes happen here + * * db connection setup + * * config loaded from files + * * loads admins + * * Sets up the dynamic menu system + * * and most importantly, calls initialize on the master subsystem, starting the game loop that causes the rest of the game to begin processing and setting up + * + * Note this happens after the Master subsystem is created (as that is a global datum), this means all the subsystems exist, + * but they have not been Initialized at this point, only their New proc has run + * + * Nothing happens until something moves. ~Albert Einstein + * + */ +/world/New() + var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so") + if (fexists(extools)) + call(extools, "maptick_initialize")() + enable_debugger() +#ifdef REFERENCE_TRACKING + enable_reference_tracking() +#endif + + //Early profile for auto-profiler - will be stopped on profiler init if necessary. +#if DM_BUILD >= 1506 + world.Profile(PROFILE_START) +#endif + + log_world("World loaded at [time_stamp()]!") + + SetupExternalRSC() + + make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) + + GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = GLOB.world_econ_log = GLOB.world_shuttle_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl + + GLOB.revdata = new + + InitTgs() + + config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + + load_admins() + + //SetupLogs depends on the RoundID, so lets check + //DB schema and set RoundID if we can + SSdbcore.CheckSchemaVersion() + SSdbcore.SetRoundID() + SetupLogs() + load_poll_data() + +#ifndef USE_CUSTOM_ERROR_HANDLER + world.log = file("[GLOB.log_directory]/dd.log") +#else + if (TgsAvailable()) + world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. +#endif + + LoadVerbs(/datum/verbs/menu) + if(CONFIG_GET(flag/usewhitelist)) + load_whitelist() + + GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 + + if(fexists(RESTART_COUNTER_PATH)) + GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) + fdel(RESTART_COUNTER_PATH) + + if(NO_INIT_PARAMETER in params) + return + + Master.Initialize(10, FALSE, TRUE) + + if(TEST_RUN_PARAMETER in params) + HandleTestRun() + +/world/proc/InitTgs() + TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED) + GLOB.revdata.load_tgs_info() + +/world/proc/HandleTestRun() + //trigger things to run the whole process + Master.sleep_offline_after_initializations = FALSE + SSticker.start_immediately = TRUE + CONFIG_SET(number/round_end_countdown, 0) + var/datum/callback/cb +#ifdef UNIT_TESTS + cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) +#else + cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) +#endif + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) + +/world/proc/SetupExternalRSC() +#if (PRELOAD_RSC == 0) + GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") + var/i=1 + while(i<=GLOB.external_rsc_urls.len) + if(GLOB.external_rsc_urls[i]) + i++ + else + GLOB.external_rsc_urls.Cut(i,i+1) +#endif + +/world/proc/SetupLogs() + var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] + if(!override_dir) + var/realtime = world.realtime + var/texttime = time2text(realtime, "YYYY/MM/DD") + GLOB.log_directory = "data/logs/[texttime]/round-" + GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" + GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" + if(GLOB.round_id) + GLOB.log_directory += "[GLOB.round_id]" + GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" + GLOB.picture_log_directory += "[GLOB.round_id]" + else + var/timestamp = replacetext(time_stamp(), ":", ".") + GLOB.log_directory += "[timestamp]" + GLOB.picture_log_directory += "[timestamp]" + GLOB.picture_logging_prefix += "T_[timestamp]_" + else + GLOB.log_directory = "data/logs/[override_dir]" + GLOB.picture_logging_prefix = "O_[override_dir]_" + GLOB.picture_log_directory = "data/picture_logs/[override_dir]" + + GLOB.world_game_log = "[GLOB.log_directory]/game.log" + GLOB.world_mecha_log = "[GLOB.log_directory]/mecha.log" + GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" + GLOB.world_cloning_log = "[GLOB.log_directory]/cloning.log" + GLOB.world_asset_log = "[GLOB.log_directory]/asset.log" + GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" + GLOB.world_econ_log = "[GLOB.log_directory]/econ.log" + GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" + GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" + GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" + GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" + GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" + GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" + GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" + GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" + GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" + GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" + GLOB.world_paper_log = "[GLOB.log_directory]/paper.log" + GLOB.tgui_log = "[GLOB.log_directory]/tgui.log" + GLOB.world_shuttle_log = "[GLOB.log_directory]/shuttle.log" + GLOB.discord_api_log = "[GLOB.log_directory]/discord_api_log.log" + + GLOB.demo_log = "[GLOB.log_directory]/demo.log" + +#ifdef UNIT_TESTS + GLOB.test_log = "[GLOB.log_directory]/tests.log" + start_log(GLOB.test_log) +#endif + start_log(GLOB.world_game_log) + start_log(GLOB.world_attack_log) + start_log(GLOB.world_econ_log) + start_log(GLOB.world_pda_log) + start_log(GLOB.world_telecomms_log) + start_log(GLOB.world_manifest_log) + start_log(GLOB.world_href_log) + start_log(GLOB.world_qdel_log) + start_log(GLOB.world_runtime_log) + start_log(GLOB.world_job_debug_log) + start_log(GLOB.tgui_log) + start_log(GLOB.world_shuttle_log) + start_log(GLOB.discord_api_log) + + GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently + if(fexists(GLOB.config_error_log)) + fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") + fdel(GLOB.config_error_log) + + if(GLOB.round_id) + log_game("Round ID: [GLOB.round_id]") + + // This was printed early in startup to the world log and config_error.log, + // but those are both private, so let's put the commit info in the runtime + // log which is ultimately public. + log_runtime(GLOB.revdata.get_log_message()) + +/world/Topic(T, addr, master, key) + TGS_TOPIC //redirect to server tools if necessary + + var/static/list/topic_handlers = TopicHandlers() + + var/list/input = params2list(T) + var/datum/world_topic/handler + for(var/I in topic_handlers) + if(I in input) + handler = topic_handlers[I] + break + + if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) + log_topic("\"[T]\", from:[addr], master:[master], key:[key]") + + if(!handler) + return + + handler = new handler() + return handler.TryRun(input) + +/world/proc/AnnouncePR(announcement, list/payload) + var/static/list/PRcounts = list() //PR id -> number of times announced this round + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [announcement]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/world/proc/FinishTestRun() + set waitfor = FALSE + var/list/fail_reasons + if(GLOB) + if(GLOB.total_runtimes != 0) + fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") +#ifdef UNIT_TESTS + if(GLOB.failed_any_test) + LAZYADD(fail_reasons, "Unit Tests failed!") +#endif + if(!GLOB.log_directory) + LAZYADD(fail_reasons, "Missing GLOB.log_directory!") + else + fail_reasons = list("Missing GLOB!") + if(!fail_reasons) + text2file("Success!", "[GLOB.log_directory]/clean_run.lk") + else + log_world("Test run failed!\n[fail_reasons.Join("\n")]") + sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes + qdel(src) //shut it down + +/world/Reboot(reason = 0, fast_track = FALSE) + if (reason || fast_track) //special reboot, do none of the normal stuff + if (usr) + log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") + message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") + to_chat(world, "Rebooting World immediately due to host request.") + else + to_chat(world, "Rebooting world...") + Master.Shutdown() //run SS shutdowns + + TgsReboot() + + if(TEST_RUN_PARAMETER in params) + FinishTestRun() + return + + if(TgsAvailable()) + var/do_hard_reboot + // check the hard reboot counter + var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) + switch(ruhr) + if(-1) + do_hard_reboot = FALSE + if(0) + do_hard_reboot = TRUE + else + if(GLOB.restart_counter >= ruhr) + do_hard_reboot = TRUE + else + text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) + do_hard_reboot = FALSE + + if(do_hard_reboot) + log_world("World hard rebooted at [time_stamp()]") + shutdown_logging() // See comment below. + TgsEndProcess() + + log_world("World rebooted at [time_stamp()]") + shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. + ..() + +/world/proc/update_status() + + var/list/features = list() + + if(GLOB.master_mode) + features += GLOB.master_mode + + if (!GLOB.enter_allowed) + features += "closed" + + var/s = "" + var/hostedby + if(config) + var/server_name = CONFIG_GET(string/servername) + if (server_name) + s += "[server_name] — " + features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" + if(CONFIG_GET(flag/allow_vote_mode)) + features += "vote" + if(CONFIG_GET(flag/allow_ai)) + features += "AI allowed" + hostedby = CONFIG_GET(string/hostedby) + + s += "[station_name()]"; + s += " (" + s += "" //Change this to wherever you want the hub to link to. + s += "Default" //Replace this with something else. Or ever better, delete it and uncomment the game version. + s += "" + s += ")" + + var/players = GLOB.clients.len + + var/popcaptext = "" + var/popcap = max(CONFIG_GET(number/extreme_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/soft_popcap)) + if (popcap) + popcaptext = "/[popcap]" + + if (players > 1) + features += "[players][popcaptext] players" + else if (players > 0) + features += "[players][popcaptext] player" + + game_state = (CONFIG_GET(number/extreme_popcap) && players >= CONFIG_GET(number/extreme_popcap)) //tells the hub if we are full + + if (!host && hostedby) + features += "hosted by [hostedby]" + + if (features) + s += ": [jointext(features, ", ")]" + + status = s + +/world/proc/update_hub_visibility(new_visibility) + if(new_visibility == GLOB.hub_visibility) + return + GLOB.hub_visibility = new_visibility + if(GLOB.hub_visibility) + hub_password = "kMZy3U5jJHSiBQjr" + else + hub_password = "SORRYNOPASSWORD" + +/world/proc/incrementMaxZ() + maxz++ + SSmobs.MaxZChanged() + SSidlenpcpool.MaxZChanged() + + +/world/proc/change_fps(new_value = 20) + if(new_value <= 0) + CRASH("change_fps() called with [new_value] new_value.") + if(fps == new_value) + return //No change required. + + fps = new_value + on_tickrate_change() + + +/world/proc/change_tick_lag(new_value = 0.5) + if(new_value <= 0) + CRASH("change_tick_lag() called with [new_value] new_value.") + if(tick_lag == new_value) + return //No change required. + + tick_lag = new_value + on_tickrate_change() + + +/world/proc/on_tickrate_change() + SStimer?.reset_buckets() diff --git a/code/modules/NTNet/services/_service.dm b/code/modules/NTNet/services/_service.dm index 3622dc38810..75059d9992b 100644 --- a/code/modules/NTNet/services/_service.dm +++ b/code/modules/NTNet/services/_service.dm @@ -1,38 +1,38 @@ -/datum/ntnet_service - var/name = "Unidentified Network Service" - var/id - var/list/networks_by_id = list() //Yes we support multinetwork services! - -/datum/ntnet_service/New() - var/datum/component/ntnet_interface/N = AddComponent(/datum/component/ntnet_interface, id, name, FALSE) - id = N.hardware_id - -/datum/ntnet_service/Destroy() - for(var/i in networks_by_id) - var/datum/ntnet/N = i - disconnect(N, TRUE) - networks_by_id = null - return ..() - -/datum/ntnet_service/proc/connect(datum/ntnet/net) - if(!istype(net)) - return FALSE - var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) - if(!interface.register_connection(net)) - return FALSE - if(!net.register_service(src)) - interface.unregister_connection(net) - return FALSE - networks_by_id[net.network_id] = net - return TRUE - -/datum/ntnet_service/proc/disconnect(datum/ntnet/net, force = FALSE) - if(!istype(net) || (!net.unregister_service(src) && !force)) - return FALSE - var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) - interface.unregister_connection(net) - networks_by_id -= net.network_id - return TRUE - -/datum/ntnet_service/proc/ntnet_intercept(datum/netdata/data, datum/ntnet/net, datum/component/ntnet_interface/sender) - return +/datum/ntnet_service + var/name = "Unidentified Network Service" + var/id + var/list/networks_by_id = list() //Yes we support multinetwork services! + +/datum/ntnet_service/New() + var/datum/component/ntnet_interface/N = AddComponent(/datum/component/ntnet_interface, id, name, FALSE) + id = N.hardware_id + +/datum/ntnet_service/Destroy() + for(var/i in networks_by_id) + var/datum/ntnet/N = i + disconnect(N, TRUE) + networks_by_id = null + return ..() + +/datum/ntnet_service/proc/connect(datum/ntnet/net) + if(!istype(net)) + return FALSE + var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) + if(!interface.register_connection(net)) + return FALSE + if(!net.register_service(src)) + interface.unregister_connection(net) + return FALSE + networks_by_id[net.network_id] = net + return TRUE + +/datum/ntnet_service/proc/disconnect(datum/ntnet/net, force = FALSE) + if(!istype(net) || (!net.unregister_service(src) && !force)) + return FALSE + var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) + interface.unregister_connection(net) + networks_by_id -= net.network_id + return TRUE + +/datum/ntnet_service/proc/ntnet_intercept(datum/netdata/data, datum/ntnet/net, datum/component/ntnet_interface/sender) + return diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 4e32a5a6747..b6d8dd219b5 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -1,243 +1,243 @@ -//Blocks an attempt to connect before even creating our client datum thing. - -//How many new ckey matches before we revert the stickyban to it's roundstart state -//These are exclusive, so once it goes over one of these numbers, it reverts the ban -#define STICKYBAN_MAX_MATCHES 15 -#define STICKYBAN_MAX_EXISTING_USER_MATCHES 3 //ie, users who were connected before the ban triggered -#define STICKYBAN_MAX_ADMIN_MATCHES 1 - -/world/IsBanned(key, address, computer_id, type, real_bans_only=FALSE) - debug_world_log("isbanned(): '[args.Join("', '")]'") - if (!key || (!real_bans_only && (!address || !computer_id))) - if(real_bans_only) - return FALSE - log_access("Failed Login (invalid data): [key] [address]-[computer_id]") - return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)") - - if (type == "world") - return ..() //shunt world topic banchecks to purely to byond's internal ban system - - var/admin = FALSE - var/ckey = ckey(key) - - var/client/C = GLOB.directory[ckey] - if (C && ckey == C.ckey && computer_id == C.computer_id && address == C.address) - return //don't recheck connected clients. - - //IsBanned can get re-called on a user in certain situations, this prevents that leading to repeated messages to admins. - var/static/list/checkedckeys = list() - //magic voodo to check for a key in a list while also adding that key to the list without having to do two associated lookups - var/message = !checkedckeys[ckey]++ - - if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]) - admin = TRUE - - - //Whitelist - if(!real_bans_only && !C && CONFIG_GET(flag/usewhitelist)) - if(!check_whitelist(ckey)) - if (admin) - log_admin("The admin [key] has been allowed to bypass the whitelist") - if (message) - message_admins("The admin [key] has been allowed to bypass the whitelist") - addclientmessage(ckey,"You have been allowed to bypass the whitelist") - else - log_access("Failed Login: [key] - Not on whitelist") - return list("reason"="whitelist", "desc" = "\nReason: You are not on the white list for this server") - - //Guest Checking - if(!real_bans_only && !C && IsGuestKey(key)) - if (CONFIG_GET(flag/guest_ban)) - log_access("Failed Login: [key] - Guests not allowed") - return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.") - if (CONFIG_GET(flag/panic_bunker) && SSdbcore.Connect()) - log_access("Failed Login: [key] - Guests not allowed during panic bunker") - return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.") - - //Population Cap Checking - var/extreme_popcap = CONFIG_GET(number/extreme_popcap) - if(!real_bans_only && !C && extreme_popcap && !admin) - var/popcap_value = GLOB.clients.len - if(popcap_value >= extreme_popcap && !GLOB.joined_player_list.Find(ckey)) - if(!CONFIG_GET(flag/byond_member_bypass_popcap) || !world.IsSubscribed(ckey, "BYOND")) - log_access("Failed Login: [key] - Population cap reached") - return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]") - - if(CONFIG_GET(flag/sql_enabled)) - if(!SSdbcore.Connect()) - var/msg = "Ban database connection failure. Key [ckey] not checked" - log_world(msg) - if (message) - message_admins(msg) - else - var/list/ban_details = is_banned_from_with_details(ckey, address, computer_id, "Server") - for(var/i in ban_details) - if(admin) - if(text2num(i["applies_to_admins"])) - var/msg = "Admin [key] is admin banned, and has been disallowed access." - log_admin(msg) - if (message) - message_admins(msg) - else - var/msg = "Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]]." - log_admin(msg) - if (message) - message_admins(msg) - addclientmessage(ckey,"Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]].") - continue - var/expires = "This is a permanent ban." - if(i["expiration_time"]) - expires = " The ban is for [DisplayTimeText(text2num(i["duration"]) MINUTES)] and expires on [i["expiration_time"]] (server time)." - var/desc = {"You, or another user of this computer or connection ([i["key"]]) is banned from playing here. - The ban reason is: [i["reason"]] - This ban (BanID #[i["id"]]) was applied by [i["admin_key"]] on [i["bantime"]] during round ID [i["round_id"]]. - [expires]"} - log_access("Failed Login: [key] [computer_id] [address] - Banned (#[i["id"]])") - return list("reason"="Banned","desc"="[desc]") - if (admin) - if (GLOB.directory[ckey]) - return - - //oh boy, so basically, because of a bug in byond, sometimes stickyban matches don't trigger here, so we can't exempt admins. - // Whitelisting the ckey with the byond whitelist field doesn't work. - // So we instead have to remove every stickyban than later re-add them. - if (!length(GLOB.stickybanadminexemptions)) - for (var/banned_ckey in world.GetConfig("ban")) - GLOB.stickybanadmintexts[banned_ckey] = world.GetConfig("ban", banned_ckey) - world.SetConfig("ban", banned_ckey, null) - if (!SSstickyban.initialized) - return - GLOB.stickybanadminexemptions[ckey] = world.time - stoplag() // sleep a byond tick - GLOB.stickbanadminexemptiontimerid = addtimer(CALLBACK(GLOBAL_PROC, /proc/restore_stickybans), 5 SECONDS, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_OVERRIDE) - return - var/list/ban = ..() //default pager ban stuff - - if (ban) - if (!admin) - . = ban - if (real_bans_only) - return - var/bannedckey = "ERROR" - if (ban["ckey"]) - bannedckey = ban["ckey"] - - var/newmatch = FALSE - var/list/cachedban = SSstickyban.cache[bannedckey] - //rogue ban in the process of being reverted. - if (cachedban && (cachedban["reverting"] || cachedban["timeout"])) - world.SetConfig("ban", bannedckey, null) - return null - - if (cachedban && ckey != bannedckey) - newmatch = TRUE - if (cachedban["keys"]) - if (cachedban["keys"][ckey]) - newmatch = FALSE - if (cachedban["matches_this_round"][ckey]) - newmatch = FALSE - - if (newmatch && cachedban) - var/list/newmatches = cachedban["matches_this_round"] - var/list/pendingmatches = cachedban["matches_this_round"] - var/list/newmatches_connected = cachedban["existing_user_matches_this_round"] - var/list/newmatches_admin = cachedban["admin_matches_this_round"] - - if (C) - newmatches_connected[ckey] = ckey - newmatches_connected = cachedban["existing_user_matches_this_round"] - pendingmatches[ckey] = ckey - sleep(STICKYBAN_ROGUE_CHECK_TIME) - pendingmatches -= ckey - if (admin) - newmatches_admin[ckey] = ckey - - if (cachedban["reverting"] || cachedban["timeout"]) - return null - - newmatches[ckey] = ckey - - - if (\ - newmatches.len+pendingmatches.len > STICKYBAN_MAX_MATCHES || \ - newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \ - newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \ - ) - - var/action - if (ban["fromdb"]) - cachedban["timeout"] = TRUE - action = "putting it on timeout for the remainder of the round" - else - cachedban["reverting"] = TRUE - action = "reverting to its roundstart state" - - world.SetConfig("ban", bannedckey, null) - - //we always report this - log_game("Stickyban on [bannedckey] detected as rogue, [action]") - message_admins("Stickyban on [bannedckey] detected as rogue, [action]") - //do not convert to timer. - spawn (5) - world.SetConfig("ban", bannedckey, null) - sleep(1) - world.SetConfig("ban", bannedckey, null) - if (!ban["fromdb"]) - cachedban = cachedban.Copy() //so old references to the list still see the ban as reverting - cachedban["matches_this_round"] = list() - cachedban["existing_user_matches_this_round"] = list() - cachedban["admin_matches_this_round"] = list() - cachedban -= "reverting" - SSstickyban.cache[bannedckey] = cachedban - world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) - return null - - if (ban["fromdb"]) - if(SSdbcore.Connect()) - INVOKE_ASYNC(SSdbcore, /datum/controller/subsystem/dbcore/proc.QuerySelect, list( - SSdbcore.NewQuery( - "INSERT INTO [format_table_name("stickyban_matched_ckey")] (matched_ckey, stickyban) VALUES (:ckey, :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", - list("ckey" = ckey, "bannedckey" = bannedckey) - ), - SSdbcore.NewQuery( - "INSERT INTO [format_table_name("stickyban_matched_ip")] (matched_ip, stickyban) VALUES (INET_ATON(:address), :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", - list("address" = address, "bannedckey" = bannedckey) - ), - SSdbcore.NewQuery( - "INSERT INTO [format_table_name("stickyban_matched_cid")] (matched_cid, stickyban) VALUES (:computer_id, :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", - list("computer_id" = computer_id, "bannedckey" = bannedckey) - ) - ), FALSE, TRUE) - - - //byond will not trigger isbanned() for "global" host bans, - //ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked) - //So it's safe to let admins walk thru host/sticky bans here - if (admin) - log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") - if (message) - message_admins("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") - addclientmessage(ckey,"You have been allowed to bypass a matching host/sticky ban on [bannedckey]") - return null - - if (C) //user is already connected!. - to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was automatically reversed.", confidential = TRUE) - - var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n" - . = list("reason" = "Stickyban", "desc" = desc) - log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]") - - return . - -/proc/restore_stickybans() - for (var/banned_ckey in GLOB.stickybanadmintexts) - world.SetConfig("ban", banned_ckey, GLOB.stickybanadmintexts[banned_ckey]) - GLOB.stickybanadminexemptions = list() - GLOB.stickybanadmintexts = list() - if (GLOB.stickbanadminexemptiontimerid) - deltimer(GLOB.stickbanadminexemptiontimerid) - GLOB.stickbanadminexemptiontimerid = null - -#undef STICKYBAN_MAX_MATCHES -#undef STICKYBAN_MAX_EXISTING_USER_MATCHES -#undef STICKYBAN_MAX_ADMIN_MATCHES +//Blocks an attempt to connect before even creating our client datum thing. + +//How many new ckey matches before we revert the stickyban to it's roundstart state +//These are exclusive, so once it goes over one of these numbers, it reverts the ban +#define STICKYBAN_MAX_MATCHES 15 +#define STICKYBAN_MAX_EXISTING_USER_MATCHES 3 //ie, users who were connected before the ban triggered +#define STICKYBAN_MAX_ADMIN_MATCHES 1 + +/world/IsBanned(key, address, computer_id, type, real_bans_only=FALSE) + debug_world_log("isbanned(): '[args.Join("', '")]'") + if (!key || (!real_bans_only && (!address || !computer_id))) + if(real_bans_only) + return FALSE + log_access("Failed Login (invalid data): [key] [address]-[computer_id]") + return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)") + + if (type == "world") + return ..() //shunt world topic banchecks to purely to byond's internal ban system + + var/admin = FALSE + var/ckey = ckey(key) + + var/client/C = GLOB.directory[ckey] + if (C && ckey == C.ckey && computer_id == C.computer_id && address == C.address) + return //don't recheck connected clients. + + //IsBanned can get re-called on a user in certain situations, this prevents that leading to repeated messages to admins. + var/static/list/checkedckeys = list() + //magic voodo to check for a key in a list while also adding that key to the list without having to do two associated lookups + var/message = !checkedckeys[ckey]++ + + if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]) + admin = TRUE + + + //Whitelist + if(!real_bans_only && !C && CONFIG_GET(flag/usewhitelist)) + if(!check_whitelist(ckey)) + if (admin) + log_admin("The admin [key] has been allowed to bypass the whitelist") + if (message) + message_admins("The admin [key] has been allowed to bypass the whitelist") + addclientmessage(ckey,"You have been allowed to bypass the whitelist") + else + log_access("Failed Login: [key] - Not on whitelist") + return list("reason"="whitelist", "desc" = "\nReason: You are not on the white list for this server") + + //Guest Checking + if(!real_bans_only && !C && IsGuestKey(key)) + if (CONFIG_GET(flag/guest_ban)) + log_access("Failed Login: [key] - Guests not allowed") + return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.") + if (CONFIG_GET(flag/panic_bunker) && SSdbcore.Connect()) + log_access("Failed Login: [key] - Guests not allowed during panic bunker") + return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.") + + //Population Cap Checking + var/extreme_popcap = CONFIG_GET(number/extreme_popcap) + if(!real_bans_only && !C && extreme_popcap && !admin) + var/popcap_value = GLOB.clients.len + if(popcap_value >= extreme_popcap && !GLOB.joined_player_list.Find(ckey)) + if(!CONFIG_GET(flag/byond_member_bypass_popcap) || !world.IsSubscribed(ckey, "BYOND")) + log_access("Failed Login: [key] - Population cap reached") + return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]") + + if(CONFIG_GET(flag/sql_enabled)) + if(!SSdbcore.Connect()) + var/msg = "Ban database connection failure. Key [ckey] not checked" + log_world(msg) + if (message) + message_admins(msg) + else + var/list/ban_details = is_banned_from_with_details(ckey, address, computer_id, "Server") + for(var/i in ban_details) + if(admin) + if(text2num(i["applies_to_admins"])) + var/msg = "Admin [key] is admin banned, and has been disallowed access." + log_admin(msg) + if (message) + message_admins(msg) + else + var/msg = "Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]]." + log_admin(msg) + if (message) + message_admins(msg) + addclientmessage(ckey,"Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]].") + continue + var/expires = "This is a permanent ban." + if(i["expiration_time"]) + expires = " The ban is for [DisplayTimeText(text2num(i["duration"]) MINUTES)] and expires on [i["expiration_time"]] (server time)." + var/desc = {"You, or another user of this computer or connection ([i["key"]]) is banned from playing here. + The ban reason is: [i["reason"]] + This ban (BanID #[i["id"]]) was applied by [i["admin_key"]] on [i["bantime"]] during round ID [i["round_id"]]. + [expires]"} + log_access("Failed Login: [key] [computer_id] [address] - Banned (#[i["id"]])") + return list("reason"="Banned","desc"="[desc]") + if (admin) + if (GLOB.directory[ckey]) + return + + //oh boy, so basically, because of a bug in byond, sometimes stickyban matches don't trigger here, so we can't exempt admins. + // Whitelisting the ckey with the byond whitelist field doesn't work. + // So we instead have to remove every stickyban than later re-add them. + if (!length(GLOB.stickybanadminexemptions)) + for (var/banned_ckey in world.GetConfig("ban")) + GLOB.stickybanadmintexts[banned_ckey] = world.GetConfig("ban", banned_ckey) + world.SetConfig("ban", banned_ckey, null) + if (!SSstickyban.initialized) + return + GLOB.stickybanadminexemptions[ckey] = world.time + stoplag() // sleep a byond tick + GLOB.stickbanadminexemptiontimerid = addtimer(CALLBACK(GLOBAL_PROC, /proc/restore_stickybans), 5 SECONDS, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_OVERRIDE) + return + var/list/ban = ..() //default pager ban stuff + + if (ban) + if (!admin) + . = ban + if (real_bans_only) + return + var/bannedckey = "ERROR" + if (ban["ckey"]) + bannedckey = ban["ckey"] + + var/newmatch = FALSE + var/list/cachedban = SSstickyban.cache[bannedckey] + //rogue ban in the process of being reverted. + if (cachedban && (cachedban["reverting"] || cachedban["timeout"])) + world.SetConfig("ban", bannedckey, null) + return null + + if (cachedban && ckey != bannedckey) + newmatch = TRUE + if (cachedban["keys"]) + if (cachedban["keys"][ckey]) + newmatch = FALSE + if (cachedban["matches_this_round"][ckey]) + newmatch = FALSE + + if (newmatch && cachedban) + var/list/newmatches = cachedban["matches_this_round"] + var/list/pendingmatches = cachedban["matches_this_round"] + var/list/newmatches_connected = cachedban["existing_user_matches_this_round"] + var/list/newmatches_admin = cachedban["admin_matches_this_round"] + + if (C) + newmatches_connected[ckey] = ckey + newmatches_connected = cachedban["existing_user_matches_this_round"] + pendingmatches[ckey] = ckey + sleep(STICKYBAN_ROGUE_CHECK_TIME) + pendingmatches -= ckey + if (admin) + newmatches_admin[ckey] = ckey + + if (cachedban["reverting"] || cachedban["timeout"]) + return null + + newmatches[ckey] = ckey + + + if (\ + newmatches.len+pendingmatches.len > STICKYBAN_MAX_MATCHES || \ + newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \ + newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \ + ) + + var/action + if (ban["fromdb"]) + cachedban["timeout"] = TRUE + action = "putting it on timeout for the remainder of the round" + else + cachedban["reverting"] = TRUE + action = "reverting to its roundstart state" + + world.SetConfig("ban", bannedckey, null) + + //we always report this + log_game("Stickyban on [bannedckey] detected as rogue, [action]") + message_admins("Stickyban on [bannedckey] detected as rogue, [action]") + //do not convert to timer. + spawn (5) + world.SetConfig("ban", bannedckey, null) + sleep(1) + world.SetConfig("ban", bannedckey, null) + if (!ban["fromdb"]) + cachedban = cachedban.Copy() //so old references to the list still see the ban as reverting + cachedban["matches_this_round"] = list() + cachedban["existing_user_matches_this_round"] = list() + cachedban["admin_matches_this_round"] = list() + cachedban -= "reverting" + SSstickyban.cache[bannedckey] = cachedban + world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) + return null + + if (ban["fromdb"]) + if(SSdbcore.Connect()) + INVOKE_ASYNC(SSdbcore, /datum/controller/subsystem/dbcore/proc.QuerySelect, list( + SSdbcore.NewQuery( + "INSERT INTO [format_table_name("stickyban_matched_ckey")] (matched_ckey, stickyban) VALUES (:ckey, :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", + list("ckey" = ckey, "bannedckey" = bannedckey) + ), + SSdbcore.NewQuery( + "INSERT INTO [format_table_name("stickyban_matched_ip")] (matched_ip, stickyban) VALUES (INET_ATON(:address), :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", + list("address" = address, "bannedckey" = bannedckey) + ), + SSdbcore.NewQuery( + "INSERT INTO [format_table_name("stickyban_matched_cid")] (matched_cid, stickyban) VALUES (:computer_id, :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", + list("computer_id" = computer_id, "bannedckey" = bannedckey) + ) + ), FALSE, TRUE) + + + //byond will not trigger isbanned() for "global" host bans, + //ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked) + //So it's safe to let admins walk thru host/sticky bans here + if (admin) + log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") + if (message) + message_admins("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") + addclientmessage(ckey,"You have been allowed to bypass a matching host/sticky ban on [bannedckey]") + return null + + if (C) //user is already connected!. + to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was automatically reversed.", confidential = TRUE) + + var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n" + . = list("reason" = "Stickyban", "desc" = desc) + log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]") + + return . + +/proc/restore_stickybans() + for (var/banned_ckey in GLOB.stickybanadmintexts) + world.SetConfig("ban", banned_ckey, GLOB.stickybanadmintexts[banned_ckey]) + GLOB.stickybanadminexemptions = list() + GLOB.stickybanadmintexts = list() + if (GLOB.stickbanadminexemptiontimerid) + deltimer(GLOB.stickbanadminexemptiontimerid) + GLOB.stickbanadminexemptiontimerid = null + +#undef STICKYBAN_MAX_MATCHES +#undef STICKYBAN_MAX_EXISTING_USER_MATCHES +#undef STICKYBAN_MAX_ADMIN_MATCHES diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index e2eb887d05e..da5e3dc7c38 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1,992 +1,992 @@ - -//////////////////////////////// -/proc/message_admins(msg) - msg = "ADMIN LOG: [msg]" - to_chat(GLOB.admins, msg, confidential = TRUE) - -/proc/relay_msg_admins(msg) - msg = "RELAY: [msg]" - to_chat(GLOB.admins, msg, confidential = TRUE) - - -///////////////////////////////////////////////////////////////////////////////////////////////Panels - -/datum/admins/proc/show_player_panel(mob/M in GLOB.mob_list) - set category = "Admin - Game" - set name = "Show Player Panel" - set desc="Edit player (respawn, ban, heal, etc)" - - if(!check_rights()) - return - - log_admin("[key_name(usr)] checked the individual player panel for [key_name(M)][isobserver(usr)?"":" while in game"].") - - if(!M) - to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.", confidential = TRUE) - return - - var/body = "Options for [M.key]" - body += "Options panel for [M]" - if(M.client) - body += " played by [M.client] " - body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\]" - if(CONFIG_GET(flag/use_exp_tracking)) - body += "\[" + M.client.get_exp_living(FALSE) + "\]" - - if(isnewplayer(M)) - body += " Hasn't Entered Game " - else - body += " \[Heal\] " - - if(M.client) - body += "
                \[First Seen: [M.client.player_join_date]\]\[Byond account registered on: [M.client.account_join_date]\]" - body += "

                Show related accounts by: " - body += "\[ CID | " - body += "IP \]" - var/rep = 0 - rep += SSpersistence.antag_rep[M.ckey] - body += "

                Antagonist reputation: [rep]" - body += "
                \[increase\] " - body += "\[decrease\] " - body += "\[set\] " - body += "\[zero\]" - var/full_version = "Unknown" - if(M.client.byond_version) - full_version = "[M.client.byond_version].[M.client.byond_build ? M.client.byond_build : "xxx"]" - body += "
                \[Byond version: [full_version]\]
                " - - - body += "

                \[ " - body += "VV - " - if(M.mind) - body += "TP - " - body += "SKILLS - " - else - body += "Init Mind - " - if (iscyborg(M)) - body += "BP - " - body += "PM - " - body += "SM - " - if (ishuman(M) && M.mind) - body += "HM - " - body += "FLW - " - //Default to client logs if available - var/source = LOGSRC_MOB - if(M.client) - source = LOGSRC_CLIENT - body += "LOGS\]
                " - - body += "Mob type = [M.type]

                " - - body += "Kick | " - if(M.client) - body += "Ban | " - else - body += "Ban | " - - body += "Notes | Messages | Watchlist | " - if(M.client) - body += "| Prison | " - body += "\ Send back to Lobby | " - var/muted = M.client.prefs.muted - body += "
                Mute: " - body += "\[IC | " - body += "OOC | " - body += "PRAY | " - body += "ADMINHELP | " - body += "DEADCHAT\]" - body += "(toggle all)" - - body += "

                " - body += "Jump to | " - body += "Get | " - body += "Send To" - - body += "

                " - body += "Traitor panel | " - body += "Narrate to | " - body += "Subtle message | " - body += "Play sound to | " - body += "Language Menu" - - if (M.client) - if(!isnewplayer(M)) - body += "

                " - body += "Transformation:" - body += "
                " - - //Human - if(ishuman(M)) - body += "Human | " - else - body += "Humanize | " - - //Monkey - if(ismonkey(M)) - body += "Monkeyized | " - else - body += "Monkeyize | " - - //Corgi - if(iscorgi(M)) - body += "Corgized | " - else - body += "Corgize | " - - //AI / Cyborg - if(isAI(M)) - body += "Is an AI " - else if(ishuman(M)) - body += "Make AI | " - body += "Make Robot | " - body += "Make Alien | " - body += "Make Slime | " - body += "Make Blob | " - - //Simple Animals - if(isanimal(M)) - body += "Re-Animalize | " - else - body += "Animalize | " - - body += "

                " - body += "Rudimentary transformation:
                These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.

                " - body += "Observer | " - body += "\[ Alien: Drone, " - body += "Hunter, " - body += "Sentinel, " - body += "Praetorian, " - body += "Queen, " - body += "Larva \] " - body += "Human " - body += "\[ slime: Baby, " - body += "Adult \] " - body += "Monkey | " - body += "Cyborg | " - body += "Cat | " - body += "Runtime | " - body += "Corgi | " - body += "Ian | " - body += "Crab | " - body += "Coffee | " - body += "\[ Construct: Juggernaut , " - body += "Artificer , " - body += "Wraith \] " - body += "Shade" - body += "
                " - - if (M.client) - body += "

                " - body += "Other actions:" - body += "
                " - body += "Forcesay | " - body += "Thunderdome 1 | " - body += "Thunderdome 2 | " - body += "Thunderdome Admin | " - body += "Thunderdome Observer | " - body += "Commend Behavior | " - - body += "
                " - body += "" - - usr << browse(body, "window=adminplayeropts-[REF(M)];size=550x515") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/datum/admins/proc/access_news_network() //MARKER - set category = "Admin - Events" - set name = "Access Newscaster Network" - set desc = "Allows you to view, add and edit news feeds." - - if (!istype(src, /datum/admins)) - src = usr.client.holder - if (!istype(src, /datum/admins)) - to_chat(usr, "Error: you are not an admin!", confidential = TRUE) - return - var/dat - dat = text("Admin Newscaster

                Admin Newscaster Unit

                ") - - switch(admincaster_screen) - if(0) - dat += "Welcome to the admin newscaster.
                Here you can add, edit and censor every newspiece on the network." - dat += "
                Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units." - dat += "
                Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!" - if(GLOB.news_network.wanted_issue.active) - dat+= "
                Read Wanted Issue" - dat+= "

                Create Feed Channel" - dat+= "
                View Feed Channels" - dat+= "
                Submit new Feed story" - dat+= "

                Exit" - var/wanted_already = 0 - if(GLOB.news_network.wanted_issue.active) - wanted_already = 1 - dat+="
                Feed Security functions:
                " - dat+="
                [(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue" - dat+="
                Censor Feed Stories" - dat+="
                Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel)." - dat+="

                The newscaster recognises you as:
                [src.admin_signature]
                " - if(1) - dat+= "Station Feed Channels
                " - if( !length(GLOB.news_network.network_channels) ) - dat+="No active channels found..." - else - for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) - if(CHANNEL.is_admin_channel) - dat+="[CHANNEL.channel_name]
                " - else - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                " - dat+="

                Refresh" - dat+="
                Back" - if(2) - dat+="Creating new Feed Channel..." - dat+="
                Channel Name: [src.admincaster_feed_channel.channel_name]
                " - dat+="Channel Author: [src.admin_signature]
                " - dat+="Will Accept Public Feeds: [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

                " - dat+="
                Submit

                Cancel
                " - if(3) - dat+="Creating new Feed Message..." - dat+="
                Receiving Channel: [src.admincaster_feed_channel.channel_name]
                " //MARK - dat+="Message Author: [src.admin_signature]
                " - dat+="Message Body: [src.admincaster_feed_message.returnBody(-1)]
                " - dat+="
                Submit

                Cancel
                " - if(4) - dat+="Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].

                " - dat+="
                Return
                " - if(5) - dat+="Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.

                " - dat+="
                Return
                " - if(6) - dat+="ERROR: Could not submit Feed story to Network.

                " - if(src.admincaster_feed_channel.channel_name=="") - dat+="•Invalid receiving channel name.
                " - if(src.admincaster_feed_message.returnBody(-1) == "" || src.admincaster_feed_message.returnBody(-1) == "\[REDACTED\]") - dat+="•Invalid message body.
                " - dat+="
                Return
                " - if(7) - dat+="ERROR: Could not submit Feed Channel to Network.

                " - if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]") - dat+="•Invalid channel name.
                " - var/check = 0 - for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == src.admincaster_feed_channel.channel_name) - check = 1 - break - if(check) - dat+="•Channel name already in use.
                " - dat+="
                Return
                " - if(9) - dat+="[admincaster_feed_channel.channel_name]: \[created by: [admincaster_feed_channel.returnAuthor(-1)]\]
                " - if(src.admincaster_feed_channel.censored) - dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                " - dat+="No further feed story additions are allowed while the D-Notice is in effect.

                " - else - if( !length(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
                " - else - var/i = 0 - for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - i++ - dat+="-[MESSAGE.returnBody(-1)]
                " - if(MESSAGE.img) - usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") - dat+="

                " - dat+="\[Story by [MESSAGE.returnAuthor(-1)]\]
                " - dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]:
                " - for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) - dat+="[comment.body]
                [comment.author] [comment.time_stamp]
                " - dat+="
                " - dat+="

                Refresh" - dat+="
                Back" - if(10) - dat+="Nanotrasen Feed Censorship Tool
                " - dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
                " - dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.
                " - dat+="
                Select Feed channel to get Stories from:
                " - if(!length(GLOB.news_network.network_channels)) - dat+="No feed channels found active...
                " - else - for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                " - dat+="
                Cancel" - if(11) - dat+="Nanotrasen D-Notice Handler
                " - dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's" - dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed" - dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
                " - if(!length(GLOB.news_network.network_channels)) - dat+="No feed channels found active...
                " - else - for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                " - - dat+="
                Back" - if(12) - dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \]
                " - dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
                " - - if( !length(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
                " - else - for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - dat+="-[MESSAGE.returnBody(-1)]
                \[Story by [MESSAGE.returnAuthor(-1)]\]
                " - dat+="[(MESSAGE.bodyCensor) ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.authorCensor) ? ("Undo Author Censorship") : ("Censor message Author")]
                " - dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]: [MESSAGE.locked ? "Unlock" : "Lock"]
                " - for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) - dat+="[comment.body] X
                [comment.author] [comment.time_stamp]
                " - dat+="
                Back" - if(13) - dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \]
                " - dat+="Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
                " - if(src.admincaster_feed_channel.censored) - dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                " - dat+="No further feed story additions are allowed while the D-Notice is in effect.

                " - else - if( !length(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
                " - else - for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - dat+="-[MESSAGE.returnBody(-1)]
                \[Story by [MESSAGE.returnAuthor(-1)]\]
                " - dat+="
                Back" - if(14) - dat+="Wanted Issue Handler:" - var/wanted_already = 0 - var/end_param = 1 - if(GLOB.news_network.wanted_issue.active) - wanted_already = 1 - end_param = 2 - if(wanted_already) - dat+="
                A wanted issue is already in Feed Circulation. You can edit or cancel it below.
                " - dat+="
                " - dat+="Criminal Name: [src.admincaster_wanted_message.criminal]
                " - dat+="Description: [src.admincaster_wanted_message.body]
                " - if(wanted_already) - dat+="Wanted Issue created by:[GLOB.news_network.wanted_issue.scannedUser]
                " - else - dat+="Wanted Issue will be created under prosecutor:[src.admin_signature]
                " - dat+="
                [(wanted_already) ? ("Edit Issue") : ("Submit")]" - if(wanted_already) - dat+="
                Take down Issue" - dat+="
                Cancel" - if(15) - dat+="Wanted issue for [src.admincaster_wanted_message.criminal] is now in Network Circulation.

                " - dat+="
                Return
                " - if(16) - dat+="ERROR: Wanted Issue rejected by Network.

                " - if(src.admincaster_wanted_message.criminal =="" || src.admincaster_wanted_message.criminal == "\[REDACTED\]") - dat+="•Invalid name for person wanted.
                " - if(src.admincaster_wanted_message.body == "" || src.admincaster_wanted_message.body == "\[REDACTED\]") - dat+="•Invalid description.
                " - dat+="
                Return
                " - if(17) - dat+="Wanted Issue successfully deleted from Circulation
                " - dat+="
                Return
                " - if(18) - dat+="-- STATIONWIDE WANTED ISSUE --
                \[Submitted by: [GLOB.news_network.wanted_issue.scannedUser]\]
                " - dat+="Criminal: [GLOB.news_network.wanted_issue.criminal]
                " - dat+="Description: [GLOB.news_network.wanted_issue.body]
                " - dat+="Photo:: " - if(GLOB.news_network.wanted_issue.img) - usr << browse_rsc(GLOB.news_network.wanted_issue.img, "tmp_photow.png") - dat+="
                " - else - dat+="None" - dat+="
                Back
                " - if(19) - dat+="Wanted issue for [src.admincaster_wanted_message.criminal] successfully edited.

                " - dat+="
                Return
                " - else - dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" - - usr << browse(dat, "window=admincaster_main;size=400x600") - onclose(usr, "admincaster_main") - - -/datum/admins/proc/Game() - if(!check_rights(0)) - return - - var/dat = {" -
                Game Panel

                \n - Change Game Mode
                - "} - if(GLOB.master_mode == "secret") - dat += "(Force Secret Mode)
                " - if(GLOB.master_mode == "dynamic") - if(SSticker.current_state <= GAME_STATE_PREGAME) - dat += "(Force Roundstart Rulesets)
                " - if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) - for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - dat += {"-> [rule.name] <-
                "} - dat += "(Clear Rulesets)
                " - dat += "(Dynamic mode options)
                " - else if (SSticker.IsRoundInProgress()) - dat += "(Force Next Latejoin Ruleset)
                " - if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - if (mode.forced_latejoin_rule) - dat += {"-> [mode.forced_latejoin_rule.name] <-
                "} - dat += "(Execute Midround Ruleset!)
                " - dat += "
                " - if(SSticker.IsRoundInProgress()) - dat += "(Game Mode Panel)
                " - dat += {" -
                - Create Object
                - Quick Create Object
                - Create Turf
                - Create Mob
                - "} - - if(marked_datum && istype(marked_datum, /atom)) - dat += "Duplicate Marked Datum
                " - - usr << browse(dat, "window=admin2;size=240x280") - return - -/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge -//i.e. buttons/verbs - - -/datum/admins/proc/restart() - set category = "Server" - set name = "Reboot World" - set desc="Restarts the world immediately" - if (!usr.client.holder) - return - - var/localhost_addresses = list("127.0.0.1", "::1") - var/list/options = list("Regular Restart", "Regular Restart (with delay)", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)") - if(world.TgsAvailable()) - options += "Server Restart (Kill and restart DD)"; - - if(SSticker.admin_delay_notice) - if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes") - return FALSE - - var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options - if(result) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]." - switch(result) - if("Regular Restart") - if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) - if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") - return FALSE - SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10) - if("Regular Restart (with delay)") - var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null - if(!delay) - return FALSE - if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) - if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") - return FALSE - SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10) - if("Hard Restart (No Delay, No Feeback Reason)") - to_chat(world, "World reboot - [init_by]") - world.Reboot() - if("Hardest Restart (No actions, just reboot)") - to_chat(world, "Hard world reboot - [init_by]") - world.Reboot(fast_track = TRUE) - if("Server Restart (Kill and restart DD)") - to_chat(world, "Server restart - [init_by]") - world.TgsEndProcess() - -/datum/admins/proc/end_round() - set category = "Server" - set name = "End Round" - set desc = "Attempts to produce a round end report and then restart the server organically." - - if (!usr.client.holder) - return - var/confirm = alert("End the round and restart the game world?", "End Round", "Yes", "Cancel") - if(confirm == "Cancel") - return - if(confirm == "Yes") - SSticker.force_ending = 1 - SSblackbox.record_feedback("tally", "admin_verb", 1, "End Round") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/datum/admins/proc/announce() - set category = "Admin" - set name = "Announce" - set desc="Announce your desires to the world" - if(!check_rights(0)) - return - - var/message = input("Global message to send:", "Admin Announce", null, null) as message - if(message) - if(!check_rights(R_SERVER,0)) - message = adminscrub(message,500) - to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:\n \t [message]", confidential = TRUE) - log_admin("Announce: [key_name(usr)] : [message]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Announce") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/set_admin_notice() - set category = "Server" - set name = "Set Admin Notice" - set desc ="Set an announcement that appears to everyone who joins the server. Only lasts this round" - if(!check_rights(0)) - return - - var/new_admin_notice = input(src,"Set a public notice for this round. Everyone who joins the server will see it.\n(Leaving it blank will delete the current notice):","Set Notice",GLOB.admin_notice) as message|null - if(new_admin_notice == null) - return - if(new_admin_notice == GLOB.admin_notice) - return - if(new_admin_notice == "") - message_admins("[key_name(usr)] removed the admin notice.") - log_admin("[key_name(usr)] removed the admin notice:\n[GLOB.admin_notice]") - else - message_admins("[key_name(usr)] set the admin notice.") - log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]") - to_chat(world, "Admin Notice:\n \t [new_admin_notice]", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Admin Notice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - GLOB.admin_notice = new_admin_notice - return - -/datum/admins/proc/toggleooc() - set category = "Server" - set desc="Toggle dis bitch" - set name="Toggle OOC" - toggle_ooc() - log_admin("[key_name(usr)] toggled OOC.") - message_admins("[key_name_admin(usr)] toggled OOC.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle OOC", "[GLOB.ooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleoocdead() - set category = "Server" - set desc="Toggle dis bitch" - set name="Toggle Dead OOC" - toggle_dooc() - - log_admin("[key_name(usr)] toggled OOC.") - message_admins("[key_name_admin(usr)] toggled Dead OOC.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dead OOC", "[GLOB.dooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/startnow() - set category = "Server" - set desc="Start the round RIGHT NOW" - set name="Start Now" - if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) - if(!SSticker.start_immediately) - var/localhost_addresses = list("127.0.0.1", "::1") - if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) - if(alert("Are you sure you want to start the round?","Start Now","Start Now","Cancel") != "Start Now") - return FALSE - SSticker.start_immediately = TRUE - log_admin("[usr.key] has started the game.") - var/msg = "" - if(SSticker.current_state == GAME_STATE_STARTUP) - msg = " (The server is still setting up, but the round will be \ - started as soon as possible.)" - message_admins("[usr.key] has started the game.[msg]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return TRUE - SSticker.start_immediately = FALSE - SSticker.SetTimeLeft(1800) - to_chat(world, "The game will start in 180 seconds.") - SEND_SOUND(world, sound('sound/ai/attention.ogg')) - message_admins("[usr.key] has cancelled immediate game start. Game will start in 180 seconds.") - log_admin("[usr.key] has cancelled immediate game start.") - else - to_chat(usr, "Error: Start Now: Game has already started.") - return FALSE - -/datum/admins/proc/toggleenter() - set category = "Server" - set desc="People can't enter" - set name="Toggle Entering" - GLOB.enter_allowed = !( GLOB.enter_allowed ) - if (!( GLOB.enter_allowed )) - to_chat(world, "New players may no longer enter the game.", confidential = TRUE) - else - to_chat(world, "New players may now enter the game.", confidential = TRUE) - log_admin("[key_name(usr)] toggled new player game entering.") - message_admins("[key_name_admin(usr)] toggled new player game entering.") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleAI() - set category = "Server" - set desc="People can't be AI" - set name="Toggle AI" - var/alai = CONFIG_GET(flag/allow_ai) - CONFIG_SET(flag/allow_ai, !alai) - if (alai) - to_chat(world, "The AI job is no longer chooseable.", confidential = TRUE) - else - to_chat(world, "The AI job is chooseable now.", confidential = TRUE) - log_admin("[key_name(usr)] toggled AI allowed.") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle AI", "[!alai ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleaban() - set category = "Server" - set desc="Respawn basically" - set name="Toggle Respawn" - var/new_nores = !CONFIG_GET(flag/norespawn) - CONFIG_SET(flag/norespawn, new_nores) - if (!new_nores) - to_chat(world, "You may now respawn.", confidential = TRUE) - else - to_chat(world, "You may no longer respawn :(", confidential = TRUE) - message_admins("[key_name_admin(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") - log_admin("[key_name(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Respawn", "[!new_nores ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/delay() - set category = "Server" - set desc="Delay the game start" - set name="Delay Pre-Game" - - var/newtime = input("Set a new time in seconds. Set -1 for indefinite delay.","Set Delay",round(SSticker.GetTimeLeft()/10)) as num|null - if(SSticker.current_state > GAME_STATE_PREGAME) - return alert("Too late... The game has already started!") - if(newtime) - newtime = newtime*10 - SSticker.SetTimeLeft(newtime) - SSticker.start_immediately = FALSE - if(newtime < 0) - to_chat(world, "The game start has been delayed.", confidential = TRUE) - log_admin("[key_name(usr)] delayed the round start.") - else - to_chat(world, "The game will start in [DisplayTimeText(newtime)].", confidential = TRUE) - SEND_SOUND(world, sound('sound/ai/attention.ogg')) - log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/unprison(mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Unprison" - if (is_centcom_level(M.z)) - SSjob.SendToLateJoin(M) - message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]") - log_admin("[key_name(usr)] has unprisoned [key_name(M)]") - else - alert("[M.name] is not prisoned.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Unprison") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS - -/datum/admins/proc/spawn_atom(object as text) - set category = "Debug" - set desc = "(atom path) Spawn an atom" - set name = "Spawn" - - if(!check_rights(R_SPAWN) || !object) - return - - var/list/preparsed = splittext(object,":") - var/path = preparsed[1] - var/amount = 1 - if(preparsed.len > 1) - amount = clamp(text2num(preparsed[2]),1,ADMIN_SPAWN_CAP) - - var/chosen = pick_closest_path(path) - if(!chosen) - return - var/turf/T = get_turf(usr) - - if(ispath(chosen, /turf)) - T.ChangeTurf(chosen) - else - for(var/i in 1 to amount) - var/atom/A = new chosen(T) - A.flags_1 |= ADMIN_SPAWNED_1 - - log_admin("[key_name(usr)] spawned [amount] x [chosen] at [AREACOORD(usr)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/podspawn_atom(object as text) - set category = "Debug" - set desc = "(atom path) Spawn an atom via supply drop" - set name = "Podspawn" - - if(!check_rights(R_SPAWN)) - return - - var/chosen = pick_closest_path(object) - if(!chosen) - return - var/turf/T = get_turf(usr) - - if(ispath(chosen, /turf)) - T.ChangeTurf(chosen) - else - var/obj/structure/closet/supplypod/centcompod/pod = new() - var/atom/A = new chosen(pod) - A.flags_1 |= ADMIN_SPAWNED_1 - new /obj/effect/pod_landingzone(T, pod) - - log_admin("[key_name(usr)] pod-spawned [chosen] at [AREACOORD(usr)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Podspawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/spawn_cargo(object as text) - set category = "Debug" - set desc = "(atom path) Spawn a cargo crate" - set name = "Spawn Cargo" - - if(!check_rights(R_SPAWN)) - return - - var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/datum/supply_pack))) - if(!chosen) - return - var/datum/supply_pack/S = new chosen - S.admin_spawned = TRUE - S.generate(get_turf(usr)) - - log_admin("[key_name(usr)] spawned cargo pack [chosen] at [AREACOORD(usr)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Cargo") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/show_traitor_panel(mob/target_mob in GLOB.mob_list) - set category = "Admin - Game" - set desc = "Edit mobs's memory and role" - set name = "Show Traitor Panel" - var/datum/mind/target_mind = target_mob.mind - if(!target_mind) - to_chat(usr, "This mob has no mind!", confidential = TRUE) - return - if(!istype(target_mob) && !istype(target_mind)) - to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) - return - target_mind.traitor_panel() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Traitor Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/show_skill_panel(var/target) - set category = "Admin - Game" - set desc = "Edit mobs's experience and skill levels" - set name = "Show Skill Panel" - var/datum/mind/target_mind - if(ismob(target)) - var/mob/target_mob = target - target_mind = target_mob.mind - else if (istype(target, /datum/mind)) - target_mind = target - else - to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) - return - var/datum/skill_panel/SP = new(usr, target_mind) - SP.ui_interact(usr) - -/datum/admins/proc/toggletintedweldhelmets() - set category = "Debug" - set desc="Reduces view range when wearing welding helmets" - set name="Toggle tinted welding helmes" - GLOB.tinted_weldhelh = !( GLOB.tinted_weldhelh ) - if (GLOB.tinted_weldhelh) - to_chat(world, "The tinted_weldhelh has been enabled!", confidential = TRUE) - else - to_chat(world, "The tinted_weldhelh has been disabled!", confidential = TRUE) - log_admin("[key_name(usr)] toggled tinted_weldhelh.") - message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Tinted Welding Helmets", "[GLOB.tinted_weldhelh ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleguests() - set category = "Server" - set desc="Guests can't enter" - set name="Toggle guests" - var/new_guest_ban = !CONFIG_GET(flag/guest_ban) - CONFIG_SET(flag/guest_ban, new_guest_ban) - if (new_guest_ban) - to_chat(world, "Guests may no longer enter the game.", confidential = TRUE) - else - to_chat(world, "Guests may now enter the game.", confidential = TRUE) - log_admin("[key_name(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") - message_admins("[key_name_admin(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Guests", "[!new_guest_ban ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/output_ai_laws() - var/ai_number = 0 - for(var/i in GLOB.silicon_mobs) - var/mob/living/silicon/S = i - ai_number++ - if(isAI(S)) - to_chat(usr, "AI [key_name(S, usr)]'s laws:", confidential = TRUE) - else if(iscyborg(S)) - var/mob/living/silicon/robot/R = S - to_chat(usr, "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:", confidential = TRUE) - else if (ispAI(S)) - to_chat(usr, "pAI [key_name(S, usr)]'s laws:", confidential = TRUE) - else - to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:", confidential = TRUE) - - if (S.laws == null) - to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.", confidential = TRUE) - else - S.laws.show_laws(usr) - if(!ai_number) - to_chat(usr, "No AIs located" , confidential = TRUE) - -/datum/admins/proc/output_all_devil_info() - var/devil_number = 0 - for(var/datum/mind/D in SSticker.mode.devils) - devil_number++ - var/datum/antagonist/devil/devil = D.has_antag_datum(/datum/antagonist/devil) - to_chat(usr, "Devil #[devil_number]:

                " + devil.printdevilinfo(), confidential = TRUE) - if(!devil_number) - to_chat(usr, "No Devils located" , confidential = TRUE) - -/datum/admins/proc/output_devil_info(mob/living/M) - if(is_devil(M)) - var/datum/antagonist/devil/devil = M.mind.has_antag_datum(/datum/antagonist/devil) - to_chat(usr, devil.printdevilinfo(), confidential = TRUE) - else - to_chat(usr, "[M] is not a devil.", confidential = TRUE) - -/datum/admins/proc/manage_free_slots() - if(!check_rights()) - return - var/datum/browser/browser = new(usr, "jobmanagement", "Manage Free Slots", 520) - var/list/dat = list() - var/count = 0 - - if(!SSjob.initialized) - alert(usr, "You cannot manage jobs before the job subsystem is initialized!") - return - - dat += "" - - for(var/j in SSjob.occupations) - var/datum/job/job = j - count++ - var/J_title = html_encode(job.title) - var/J_opPos = html_encode(job.total_positions - (job.total_positions - job.current_positions)) - var/J_totPos = html_encode(job.total_positions) - dat += "" - dat += "" - else - dat += "Limit" - - browser.height = min(100 + count * 20, 650) - browser.set_content(dat.Join()) - browser.open() - -/datum/admins/proc/dynamic_mode_options(mob/user) - var/dat = {" -

                Dynamic Mode Options


                -
                -

                Common options

                - All these options can be changed midround.
                -
                - Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"]. -
                This will force the round to be extended. No rulesets will be drafted.
                -
                - No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"]. -
                Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
                -
                - Classic secret mode: - Option is [GLOB.dynamic_classic_secret ? "ON" : "OFF"]. -
                Only one roundstart ruleset will be drafted. Only traitors and minor roles will latespawn.
                -
                -
                - Forced threat level: Current value : [GLOB.dynamic_forced_threat_level]. -
                The value threat is set to if it is higher than -1.
                -
                -
                - Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit]. -
                The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
                -

                Advanced parameters

                - Curve centre: -> [GLOB.dynamic_curve_centre] <-
                - Curve width: -> [GLOB.dynamic_curve_width] <-
                - Latejoin injection delay:
                - Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
                - Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
                - Midround injection delay:
                - Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
                - Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
                - "} - - user << browse(dat, "window=dyn_mode_options;size=900x650") - -/datum/admins/proc/create_or_modify_area() - set category = "Debug" - set name = "Create or modify area" - create_area(usr) - -// -// -//ALL DONE -//********************************************************************************************************* -//TO-DO: -// -// - -//RIP ferry snowflakes - -//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked -//defaults to kicking everyone (afk + non afk clients in the lobby) -//returns a list of ckeys of the kicked clients -/proc/kick_clients_in_lobby(message, kick_only_afk = 0) - var/list/kicked_client_names = list() - for(var/client/C in GLOB.clients) - if(isnewplayer(C.mob)) - if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk - continue - if(message) - to_chat(C, message, confidential = TRUE) - kicked_client_names.Add("[C.key]") - qdel(C) - return kicked_client_names - -//returns TRUE to let the dragdrop code know we are trapping this event -//returns FALSE if we don't plan to trap the event -/datum/admins/proc/cmd_ghost_drag(mob/dead/observer/frommob, mob/tomob) - - //this is the exact two check rights checks required to edit a ckey with vv. - if (!check_rights(R_VAREDIT,0) || !check_rights(R_SPAWN|R_DEBUG,0)) - return FALSE - - if (!frommob.ckey) - return FALSE - - var/question = "" - if (tomob.ckey) - question = "This mob already has a user ([tomob.key]) in control of it! " - question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?" - - var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No") - if (ask != "Yes") - return TRUE - - if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response - return TRUE - - // Disassociates observer mind from the body mind - if(tomob.client) - tomob.ghostize(FALSE) - else - for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) - if(tomob.mind == ghost.mind) - ghost.mind = null - - message_admins("[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name].") - log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control") - - tomob.ckey = frommob.ckey - qdel(frommob) - - return TRUE - -/client/proc/adminGreet(logout) - if(SSticker.HasRoundStarted()) - var/string - if(logout && CONFIG_GET(flag/announce_admin_logout)) - string = pick( - "Admin logout: [key_name(src)]") - else if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN)) - string = pick( - "Admin login: [key_name(src)]") - if(string) - message_admins("[string]") + +//////////////////////////////// +/proc/message_admins(msg) + msg = "ADMIN LOG: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + +/proc/relay_msg_admins(msg) + msg = "RELAY: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + + +///////////////////////////////////////////////////////////////////////////////////////////////Panels + +/datum/admins/proc/show_player_panel(mob/M in GLOB.mob_list) + set category = "Admin - Game" + set name = "Show Player Panel" + set desc="Edit player (respawn, ban, heal, etc)" + + if(!check_rights()) + return + + log_admin("[key_name(usr)] checked the individual player panel for [key_name(M)][isobserver(usr)?"":" while in game"].") + + if(!M) + to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.", confidential = TRUE) + return + + var/body = "Options for [M.key]" + body += "Options panel for [M]" + if(M.client) + body += " played by [M.client] " + body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\]" + if(CONFIG_GET(flag/use_exp_tracking)) + body += "\[" + M.client.get_exp_living(FALSE) + "\]" + + if(isnewplayer(M)) + body += " Hasn't Entered Game " + else + body += " \[Heal\] " + + if(M.client) + body += "
                \[First Seen: [M.client.player_join_date]\]\[Byond account registered on: [M.client.account_join_date]\]" + body += "

                Show related accounts by: " + body += "\[ CID | " + body += "IP \]" + var/rep = 0 + rep += SSpersistence.antag_rep[M.ckey] + body += "

                Antagonist reputation: [rep]" + body += "
                \[increase\] " + body += "\[decrease\] " + body += "\[set\] " + body += "\[zero\]" + var/full_version = "Unknown" + if(M.client.byond_version) + full_version = "[M.client.byond_version].[M.client.byond_build ? M.client.byond_build : "xxx"]" + body += "
                \[Byond version: [full_version]\]
                " + + + body += "

                \[ " + body += "VV - " + if(M.mind) + body += "TP - " + body += "SKILLS - " + else + body += "Init Mind - " + if (iscyborg(M)) + body += "BP - " + body += "PM - " + body += "SM - " + if (ishuman(M) && M.mind) + body += "HM - " + body += "FLW - " + //Default to client logs if available + var/source = LOGSRC_MOB + if(M.client) + source = LOGSRC_CLIENT + body += "LOGS\]
                " + + body += "Mob type = [M.type]

                " + + body += "Kick | " + if(M.client) + body += "Ban | " + else + body += "Ban | " + + body += "Notes | Messages | Watchlist | " + if(M.client) + body += "| Prison | " + body += "\ Send back to Lobby | " + var/muted = M.client.prefs.muted + body += "
                Mute: " + body += "\[IC | " + body += "OOC | " + body += "PRAY | " + body += "ADMINHELP | " + body += "DEADCHAT\]" + body += "(toggle all)" + + body += "

                " + body += "Jump to | " + body += "Get | " + body += "Send To" + + body += "

                " + body += "Traitor panel | " + body += "Narrate to | " + body += "Subtle message | " + body += "Play sound to | " + body += "Language Menu" + + if (M.client) + if(!isnewplayer(M)) + body += "

                " + body += "Transformation:" + body += "
                " + + //Human + if(ishuman(M)) + body += "Human | " + else + body += "Humanize | " + + //Monkey + if(ismonkey(M)) + body += "Monkeyized | " + else + body += "Monkeyize | " + + //Corgi + if(iscorgi(M)) + body += "Corgized | " + else + body += "Corgize | " + + //AI / Cyborg + if(isAI(M)) + body += "Is an AI " + else if(ishuman(M)) + body += "Make AI | " + body += "Make Robot | " + body += "Make Alien | " + body += "Make Slime | " + body += "Make Blob | " + + //Simple Animals + if(isanimal(M)) + body += "Re-Animalize | " + else + body += "Animalize | " + + body += "

                " + body += "Rudimentary transformation:
                These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.

                " + body += "Observer | " + body += "\[ Alien: Drone, " + body += "Hunter, " + body += "Sentinel, " + body += "Praetorian, " + body += "Queen, " + body += "Larva \] " + body += "Human " + body += "\[ slime: Baby, " + body += "Adult \] " + body += "Monkey | " + body += "Cyborg | " + body += "Cat | " + body += "Runtime | " + body += "Corgi | " + body += "Ian | " + body += "Crab | " + body += "Coffee | " + body += "\[ Construct: Juggernaut , " + body += "Artificer , " + body += "Wraith \] " + body += "Shade" + body += "
                " + + if (M.client) + body += "

                " + body += "Other actions:" + body += "
                " + body += "Forcesay | " + body += "Thunderdome 1 | " + body += "Thunderdome 2 | " + body += "Thunderdome Admin | " + body += "Thunderdome Observer | " + body += "Commend Behavior | " + + body += "
                " + body += "" + + usr << browse(body, "window=adminplayeropts-[REF(M)];size=550x515") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/datum/admins/proc/access_news_network() //MARKER + set category = "Admin - Events" + set name = "Access Newscaster Network" + set desc = "Allows you to view, add and edit news feeds." + + if (!istype(src, /datum/admins)) + src = usr.client.holder + if (!istype(src, /datum/admins)) + to_chat(usr, "Error: you are not an admin!", confidential = TRUE) + return + var/dat + dat = text("Admin Newscaster

                Admin Newscaster Unit

                ") + + switch(admincaster_screen) + if(0) + dat += "Welcome to the admin newscaster.
                Here you can add, edit and censor every newspiece on the network." + dat += "
                Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units." + dat += "
                Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!" + if(GLOB.news_network.wanted_issue.active) + dat+= "
                Read Wanted Issue" + dat+= "

                Create Feed Channel" + dat+= "
                View Feed Channels" + dat+= "
                Submit new Feed story" + dat+= "

                Exit" + var/wanted_already = 0 + if(GLOB.news_network.wanted_issue.active) + wanted_already = 1 + dat+="
                Feed Security functions:
                " + dat+="
                [(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue" + dat+="
                Censor Feed Stories" + dat+="
                Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel)." + dat+="

                The newscaster recognises you as:
                [src.admin_signature]
                " + if(1) + dat+= "Station Feed Channels
                " + if( !length(GLOB.news_network.network_channels) ) + dat+="No active channels found..." + else + for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) + if(CHANNEL.is_admin_channel) + dat+="[CHANNEL.channel_name]
                " + else + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                " + dat+="

                Refresh" + dat+="
                Back" + if(2) + dat+="Creating new Feed Channel..." + dat+="
                Channel Name: [src.admincaster_feed_channel.channel_name]
                " + dat+="Channel Author:[src.admin_signature]
                " + dat+="Will Accept Public Feeds: [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

                " + dat+="
                Submit

                Cancel
                " + if(3) + dat+="Creating new Feed Message..." + dat+="
                Receiving Channel: [src.admincaster_feed_channel.channel_name]
                " //MARK + dat+="Message Author:[src.admin_signature]
                " + dat+="Message Body: [src.admincaster_feed_message.returnBody(-1)]
                " + dat+="
                Submit

                Cancel
                " + if(4) + dat+="Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].

                " + dat+="
                Return
                " + if(5) + dat+="Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.

                " + dat+="
                Return
                " + if(6) + dat+="ERROR: Could not submit Feed story to Network.

                " + if(src.admincaster_feed_channel.channel_name=="") + dat+="•Invalid receiving channel name.
                " + if(src.admincaster_feed_message.returnBody(-1) == "" || src.admincaster_feed_message.returnBody(-1) == "\[REDACTED\]") + dat+="•Invalid message body.
                " + dat+="
                Return
                " + if(7) + dat+="ERROR: Could not submit Feed Channel to Network.

                " + if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]") + dat+="•Invalid channel name.
                " + var/check = 0 + for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) + if(FC.channel_name == src.admincaster_feed_channel.channel_name) + check = 1 + break + if(check) + dat+="•Channel name already in use.
                " + dat+="
                Return
                " + if(9) + dat+="[admincaster_feed_channel.channel_name]: \[created by: [admincaster_feed_channel.returnAuthor(-1)]\]
                " + if(src.admincaster_feed_channel.censored) + dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                " + dat+="No further feed story additions are allowed while the D-Notice is in effect.

                " + else + if( !length(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
                " + else + var/i = 0 + for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + i++ + dat+="-[MESSAGE.returnBody(-1)]
                " + if(MESSAGE.img) + usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") + dat+="

                " + dat+="\[Story by [MESSAGE.returnAuthor(-1)]\]
                " + dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]:
                " + for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) + dat+="[comment.body]
                [comment.author] [comment.time_stamp]
                " + dat+="
                " + dat+="

                Refresh" + dat+="
                Back" + if(10) + dat+="Nanotrasen Feed Censorship Tool
                " + dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
                " + dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.
                " + dat+="
                Select Feed channel to get Stories from:
                " + if(!length(GLOB.news_network.network_channels)) + dat+="No feed channels found active...
                " + else + for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                " + dat+="
                Cancel" + if(11) + dat+="Nanotrasen D-Notice Handler
                " + dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's" + dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed" + dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
                " + if(!length(GLOB.news_network.network_channels)) + dat+="No feed channels found active...
                " + else + for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                " + + dat+="
                Back" + if(12) + dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \]
                " + dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
                " + + if( !length(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
                " + else + for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + dat+="-[MESSAGE.returnBody(-1)]
                \[Story by [MESSAGE.returnAuthor(-1)]\]
                " + dat+="[(MESSAGE.bodyCensor) ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.authorCensor) ? ("Undo Author Censorship") : ("Censor message Author")]
                " + dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]: [MESSAGE.locked ? "Unlock" : "Lock"]
                " + for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) + dat+="[comment.body] X
                [comment.author] [comment.time_stamp]
                " + dat+="
                Back" + if(13) + dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \]
                " + dat+="Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
                " + if(src.admincaster_feed_channel.censored) + dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                " + dat+="No further feed story additions are allowed while the D-Notice is in effect.

                " + else + if( !length(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
                " + else + for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + dat+="-[MESSAGE.returnBody(-1)]
                \[Story by [MESSAGE.returnAuthor(-1)]\]
                " + dat+="
                Back" + if(14) + dat+="Wanted Issue Handler:" + var/wanted_already = 0 + var/end_param = 1 + if(GLOB.news_network.wanted_issue.active) + wanted_already = 1 + end_param = 2 + if(wanted_already) + dat+="
                A wanted issue is already in Feed Circulation. You can edit or cancel it below.
                " + dat+="
                " + dat+="Criminal Name: [src.admincaster_wanted_message.criminal]
                " + dat+="Description: [src.admincaster_wanted_message.body]
                " + if(wanted_already) + dat+="Wanted Issue created by:[GLOB.news_network.wanted_issue.scannedUser]
                " + else + dat+="Wanted Issue will be created under prosecutor:[src.admin_signature]
                " + dat+="
                [(wanted_already) ? ("Edit Issue") : ("Submit")]" + if(wanted_already) + dat+="
                Take down Issue" + dat+="
                Cancel" + if(15) + dat+="Wanted issue for [src.admincaster_wanted_message.criminal] is now in Network Circulation.

                " + dat+="
                Return
                " + if(16) + dat+="ERROR: Wanted Issue rejected by Network.

                " + if(src.admincaster_wanted_message.criminal =="" || src.admincaster_wanted_message.criminal == "\[REDACTED\]") + dat+="•Invalid name for person wanted.
                " + if(src.admincaster_wanted_message.body == "" || src.admincaster_wanted_message.body == "\[REDACTED\]") + dat+="•Invalid description.
                " + dat+="
                Return
                " + if(17) + dat+="Wanted Issue successfully deleted from Circulation
                " + dat+="
                Return
                " + if(18) + dat+="-- STATIONWIDE WANTED ISSUE --
                \[Submitted by: [GLOB.news_network.wanted_issue.scannedUser]\]
                " + dat+="Criminal: [GLOB.news_network.wanted_issue.criminal]
                " + dat+="Description: [GLOB.news_network.wanted_issue.body]
                " + dat+="Photo:: " + if(GLOB.news_network.wanted_issue.img) + usr << browse_rsc(GLOB.news_network.wanted_issue.img, "tmp_photow.png") + dat+="
                " + else + dat+="None" + dat+="
                Back
                " + if(19) + dat+="Wanted issue for [src.admincaster_wanted_message.criminal] successfully edited.

                " + dat+="
                Return
                " + else + dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" + + usr << browse(dat, "window=admincaster_main;size=400x600") + onclose(usr, "admincaster_main") + + +/datum/admins/proc/Game() + if(!check_rights(0)) + return + + var/dat = {" +
                Game Panel

                \n + Change Game Mode
                + "} + if(GLOB.master_mode == "secret") + dat += "(Force Secret Mode)
                " + if(GLOB.master_mode == "dynamic") + if(SSticker.current_state <= GAME_STATE_PREGAME) + dat += "(Force Roundstart Rulesets)
                " + if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) + for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) + dat += {"-> [rule.name] <-
                "} + dat += "(Clear Rulesets)
                " + dat += "(Dynamic mode options)
                " + else if (SSticker.IsRoundInProgress()) + dat += "(Force Next Latejoin Ruleset)
                " + if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + if (mode.forced_latejoin_rule) + dat += {"-> [mode.forced_latejoin_rule.name] <-
                "} + dat += "(Execute Midround Ruleset!)
                " + dat += "
                " + if(SSticker.IsRoundInProgress()) + dat += "(Game Mode Panel)
                " + dat += {" +
                + Create Object
                + Quick Create Object
                + Create Turf
                + Create Mob
                + "} + + if(marked_datum && istype(marked_datum, /atom)) + dat += "Duplicate Marked Datum
                " + + usr << browse(dat, "window=admin2;size=240x280") + return + +/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge +//i.e. buttons/verbs + + +/datum/admins/proc/restart() + set category = "Server" + set name = "Reboot World" + set desc="Restarts the world immediately" + if (!usr.client.holder) + return + + var/localhost_addresses = list("127.0.0.1", "::1") + var/list/options = list("Regular Restart", "Regular Restart (with delay)", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)") + if(world.TgsAvailable()) + options += "Server Restart (Kill and restart DD)"; + + if(SSticker.admin_delay_notice) + if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes") + return FALSE + + var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options + if(result) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]." + switch(result) + if("Regular Restart") + if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) + if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") + return FALSE + SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10) + if("Regular Restart (with delay)") + var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null + if(!delay) + return FALSE + if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) + if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") + return FALSE + SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10) + if("Hard Restart (No Delay, No Feeback Reason)") + to_chat(world, "World reboot - [init_by]") + world.Reboot() + if("Hardest Restart (No actions, just reboot)") + to_chat(world, "Hard world reboot - [init_by]") + world.Reboot(fast_track = TRUE) + if("Server Restart (Kill and restart DD)") + to_chat(world, "Server restart - [init_by]") + world.TgsEndProcess() + +/datum/admins/proc/end_round() + set category = "Server" + set name = "End Round" + set desc = "Attempts to produce a round end report and then restart the server organically." + + if (!usr.client.holder) + return + var/confirm = alert("End the round and restart the game world?", "End Round", "Yes", "Cancel") + if(confirm == "Cancel") + return + if(confirm == "Yes") + SSticker.force_ending = 1 + SSblackbox.record_feedback("tally", "admin_verb", 1, "End Round") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/datum/admins/proc/announce() + set category = "Admin" + set name = "Announce" + set desc="Announce your desires to the world" + if(!check_rights(0)) + return + + var/message = input("Global message to send:", "Admin Announce", null, null) as message + if(message) + if(!check_rights(R_SERVER,0)) + message = adminscrub(message,500) + to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:\n \t [message]", confidential = TRUE) + log_admin("Announce: [key_name(usr)] : [message]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Announce") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/set_admin_notice() + set category = "Server" + set name = "Set Admin Notice" + set desc ="Set an announcement that appears to everyone who joins the server. Only lasts this round" + if(!check_rights(0)) + return + + var/new_admin_notice = input(src,"Set a public notice for this round. Everyone who joins the server will see it.\n(Leaving it blank will delete the current notice):","Set Notice",GLOB.admin_notice) as message|null + if(new_admin_notice == null) + return + if(new_admin_notice == GLOB.admin_notice) + return + if(new_admin_notice == "") + message_admins("[key_name(usr)] removed the admin notice.") + log_admin("[key_name(usr)] removed the admin notice:\n[GLOB.admin_notice]") + else + message_admins("[key_name(usr)] set the admin notice.") + log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]") + to_chat(world, "Admin Notice:\n \t [new_admin_notice]", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Admin Notice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + GLOB.admin_notice = new_admin_notice + return + +/datum/admins/proc/toggleooc() + set category = "Server" + set desc="Toggle dis bitch" + set name="Toggle OOC" + toggle_ooc() + log_admin("[key_name(usr)] toggled OOC.") + message_admins("[key_name_admin(usr)] toggled OOC.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle OOC", "[GLOB.ooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleoocdead() + set category = "Server" + set desc="Toggle dis bitch" + set name="Toggle Dead OOC" + toggle_dooc() + + log_admin("[key_name(usr)] toggled OOC.") + message_admins("[key_name_admin(usr)] toggled Dead OOC.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dead OOC", "[GLOB.dooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/startnow() + set category = "Server" + set desc="Start the round RIGHT NOW" + set name="Start Now" + if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) + if(!SSticker.start_immediately) + var/localhost_addresses = list("127.0.0.1", "::1") + if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) + if(alert("Are you sure you want to start the round?","Start Now","Start Now","Cancel") != "Start Now") + return FALSE + SSticker.start_immediately = TRUE + log_admin("[usr.key] has started the game.") + var/msg = "" + if(SSticker.current_state == GAME_STATE_STARTUP) + msg = " (The server is still setting up, but the round will be \ + started as soon as possible.)" + message_admins("[usr.key] has started the game.[msg]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return TRUE + SSticker.start_immediately = FALSE + SSticker.SetTimeLeft(1800) + to_chat(world, "The game will start in 180 seconds.") + SEND_SOUND(world, sound('sound/ai/attention.ogg')) + message_admins("[usr.key] has cancelled immediate game start. Game will start in 180 seconds.") + log_admin("[usr.key] has cancelled immediate game start.") + else + to_chat(usr, "Error: Start Now: Game has already started.") + return FALSE + +/datum/admins/proc/toggleenter() + set category = "Server" + set desc="People can't enter" + set name="Toggle Entering" + GLOB.enter_allowed = !( GLOB.enter_allowed ) + if (!( GLOB.enter_allowed )) + to_chat(world, "New players may no longer enter the game.", confidential = TRUE) + else + to_chat(world, "New players may now enter the game.", confidential = TRUE) + log_admin("[key_name(usr)] toggled new player game entering.") + message_admins("[key_name_admin(usr)] toggled new player game entering.") + world.update_status() + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleAI() + set category = "Server" + set desc="People can't be AI" + set name="Toggle AI" + var/alai = CONFIG_GET(flag/allow_ai) + CONFIG_SET(flag/allow_ai, !alai) + if (alai) + to_chat(world, "The AI job is no longer chooseable.", confidential = TRUE) + else + to_chat(world, "The AI job is chooseable now.", confidential = TRUE) + log_admin("[key_name(usr)] toggled AI allowed.") + world.update_status() + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle AI", "[!alai ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleaban() + set category = "Server" + set desc="Respawn basically" + set name="Toggle Respawn" + var/new_nores = !CONFIG_GET(flag/norespawn) + CONFIG_SET(flag/norespawn, new_nores) + if (!new_nores) + to_chat(world, "You may now respawn.", confidential = TRUE) + else + to_chat(world, "You may no longer respawn :(", confidential = TRUE) + message_admins("[key_name_admin(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") + log_admin("[key_name(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") + world.update_status() + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Respawn", "[!new_nores ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/delay() + set category = "Server" + set desc="Delay the game start" + set name="Delay Pre-Game" + + var/newtime = input("Set a new time in seconds. Set -1 for indefinite delay.","Set Delay",round(SSticker.GetTimeLeft()/10)) as num|null + if(SSticker.current_state > GAME_STATE_PREGAME) + return alert("Too late... The game has already started!") + if(newtime) + newtime = newtime*10 + SSticker.SetTimeLeft(newtime) + SSticker.start_immediately = FALSE + if(newtime < 0) + to_chat(world, "The game start has been delayed.", confidential = TRUE) + log_admin("[key_name(usr)] delayed the round start.") + else + to_chat(world, "The game will start in [DisplayTimeText(newtime)].", confidential = TRUE) + SEND_SOUND(world, sound('sound/ai/attention.ogg')) + log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/unprison(mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Unprison" + if (is_centcom_level(M.z)) + SSjob.SendToLateJoin(M) + message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]") + log_admin("[key_name(usr)] has unprisoned [key_name(M)]") + else + alert("[M.name] is not prisoned.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Unprison") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS + +/datum/admins/proc/spawn_atom(object as text) + set category = "Debug" + set desc = "(atom path) Spawn an atom" + set name = "Spawn" + + if(!check_rights(R_SPAWN) || !object) + return + + var/list/preparsed = splittext(object,":") + var/path = preparsed[1] + var/amount = 1 + if(preparsed.len > 1) + amount = clamp(text2num(preparsed[2]),1,ADMIN_SPAWN_CAP) + + var/chosen = pick_closest_path(path) + if(!chosen) + return + var/turf/T = get_turf(usr) + + if(ispath(chosen, /turf)) + T.ChangeTurf(chosen) + else + for(var/i in 1 to amount) + var/atom/A = new chosen(T) + A.flags_1 |= ADMIN_SPAWNED_1 + + log_admin("[key_name(usr)] spawned [amount] x [chosen] at [AREACOORD(usr)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/podspawn_atom(object as text) + set category = "Debug" + set desc = "(atom path) Spawn an atom via supply drop" + set name = "Podspawn" + + if(!check_rights(R_SPAWN)) + return + + var/chosen = pick_closest_path(object) + if(!chosen) + return + var/turf/T = get_turf(usr) + + if(ispath(chosen, /turf)) + T.ChangeTurf(chosen) + else + var/obj/structure/closet/supplypod/centcompod/pod = new() + var/atom/A = new chosen(pod) + A.flags_1 |= ADMIN_SPAWNED_1 + new /obj/effect/pod_landingzone(T, pod) + + log_admin("[key_name(usr)] pod-spawned [chosen] at [AREACOORD(usr)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Podspawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/spawn_cargo(object as text) + set category = "Debug" + set desc = "(atom path) Spawn a cargo crate" + set name = "Spawn Cargo" + + if(!check_rights(R_SPAWN)) + return + + var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/datum/supply_pack))) + if(!chosen) + return + var/datum/supply_pack/S = new chosen + S.admin_spawned = TRUE + S.generate(get_turf(usr)) + + log_admin("[key_name(usr)] spawned cargo pack [chosen] at [AREACOORD(usr)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Cargo") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/show_traitor_panel(mob/target_mob in GLOB.mob_list) + set category = "Admin - Game" + set desc = "Edit mobs's memory and role" + set name = "Show Traitor Panel" + var/datum/mind/target_mind = target_mob.mind + if(!target_mind) + to_chat(usr, "This mob has no mind!", confidential = TRUE) + return + if(!istype(target_mob) && !istype(target_mind)) + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + target_mind.traitor_panel() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Traitor Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/show_skill_panel(var/target) + set category = "Admin - Game" + set desc = "Edit mobs's experience and skill levels" + set name = "Show Skill Panel" + var/datum/mind/target_mind + if(ismob(target)) + var/mob/target_mob = target + target_mind = target_mob.mind + else if (istype(target, /datum/mind)) + target_mind = target + else + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + var/datum/skill_panel/SP = new(usr, target_mind) + SP.ui_interact(usr) + +/datum/admins/proc/toggletintedweldhelmets() + set category = "Debug" + set desc="Reduces view range when wearing welding helmets" + set name="Toggle tinted welding helmes" + GLOB.tinted_weldhelh = !( GLOB.tinted_weldhelh ) + if (GLOB.tinted_weldhelh) + to_chat(world, "The tinted_weldhelh has been enabled!", confidential = TRUE) + else + to_chat(world, "The tinted_weldhelh has been disabled!", confidential = TRUE) + log_admin("[key_name(usr)] toggled tinted_weldhelh.") + message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Tinted Welding Helmets", "[GLOB.tinted_weldhelh ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleguests() + set category = "Server" + set desc="Guests can't enter" + set name="Toggle guests" + var/new_guest_ban = !CONFIG_GET(flag/guest_ban) + CONFIG_SET(flag/guest_ban, new_guest_ban) + if (new_guest_ban) + to_chat(world, "Guests may no longer enter the game.", confidential = TRUE) + else + to_chat(world, "Guests may now enter the game.", confidential = TRUE) + log_admin("[key_name(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") + message_admins("[key_name_admin(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Guests", "[!new_guest_ban ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/output_ai_laws() + var/ai_number = 0 + for(var/i in GLOB.silicon_mobs) + var/mob/living/silicon/S = i + ai_number++ + if(isAI(S)) + to_chat(usr, "AI [key_name(S, usr)]'s laws:", confidential = TRUE) + else if(iscyborg(S)) + var/mob/living/silicon/robot/R = S + to_chat(usr, "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:", confidential = TRUE) + else if (ispAI(S)) + to_chat(usr, "pAI [key_name(S, usr)]'s laws:", confidential = TRUE) + else + to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:", confidential = TRUE) + + if (S.laws == null) + to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.", confidential = TRUE) + else + S.laws.show_laws(usr) + if(!ai_number) + to_chat(usr, "No AIs located" , confidential = TRUE) + +/datum/admins/proc/output_all_devil_info() + var/devil_number = 0 + for(var/datum/mind/D in SSticker.mode.devils) + devil_number++ + var/datum/antagonist/devil/devil = D.has_antag_datum(/datum/antagonist/devil) + to_chat(usr, "Devil #[devil_number]:

                " + devil.printdevilinfo(), confidential = TRUE) + if(!devil_number) + to_chat(usr, "No Devils located" , confidential = TRUE) + +/datum/admins/proc/output_devil_info(mob/living/M) + if(is_devil(M)) + var/datum/antagonist/devil/devil = M.mind.has_antag_datum(/datum/antagonist/devil) + to_chat(usr, devil.printdevilinfo(), confidential = TRUE) + else + to_chat(usr, "[M] is not a devil.", confidential = TRUE) + +/datum/admins/proc/manage_free_slots() + if(!check_rights()) + return + var/datum/browser/browser = new(usr, "jobmanagement", "Manage Free Slots", 520) + var/list/dat = list() + var/count = 0 + + if(!SSjob.initialized) + alert(usr, "You cannot manage jobs before the job subsystem is initialized!") + return + + dat += "
                [J_title]: [J_opPos]/[job.total_positions < 0 ? " (unlimited)" : J_totPos]" - - dat += "" - if(job.total_positions >= 0) - dat += "Custom | " - dat += "Add 1 | " - if(job.total_positions > job.current_positions) - dat += "Remove | " - else - dat += "Remove | " - dat += "Unlimit
                " + + for(var/j in SSjob.occupations) + var/datum/job/job = j + count++ + var/J_title = html_encode(job.title) + var/J_opPos = html_encode(job.total_positions - (job.total_positions - job.current_positions)) + var/J_totPos = html_encode(job.total_positions) + dat += "" + dat += "" + else + dat += "Limit" + + browser.height = min(100 + count * 20, 650) + browser.set_content(dat.Join()) + browser.open() + +/datum/admins/proc/dynamic_mode_options(mob/user) + var/dat = {" +

                Dynamic Mode Options


                +
                +

                Common options

                + All these options can be changed midround.
                +
                + Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"]. +
                This will force the round to be extended. No rulesets will be drafted.
                +
                + No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"]. +
                Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
                +
                + Classic secret mode: - Option is [GLOB.dynamic_classic_secret ? "ON" : "OFF"]. +
                Only one roundstart ruleset will be drafted. Only traitors and minor roles will latespawn.
                +
                +
                + Forced threat level: Current value : [GLOB.dynamic_forced_threat_level]. +
                The value threat is set to if it is higher than -1.
                +
                +
                + Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit]. +
                The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
                +

                Advanced parameters

                + Curve centre: -> [GLOB.dynamic_curve_centre] <-
                + Curve width: -> [GLOB.dynamic_curve_width] <-
                + Latejoin injection delay:
                + Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
                + Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
                + Midround injection delay:
                + Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
                + Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
                + "} + + user << browse(dat, "window=dyn_mode_options;size=900x650") + +/datum/admins/proc/create_or_modify_area() + set category = "Debug" + set name = "Create or modify area" + create_area(usr) + +// +// +//ALL DONE +//********************************************************************************************************* +//TO-DO: +// +// + +//RIP ferry snowflakes + +//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked +//defaults to kicking everyone (afk + non afk clients in the lobby) +//returns a list of ckeys of the kicked clients +/proc/kick_clients_in_lobby(message, kick_only_afk = 0) + var/list/kicked_client_names = list() + for(var/client/C in GLOB.clients) + if(isnewplayer(C.mob)) + if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk + continue + if(message) + to_chat(C, message, confidential = TRUE) + kicked_client_names.Add("[C.key]") + qdel(C) + return kicked_client_names + +//returns TRUE to let the dragdrop code know we are trapping this event +//returns FALSE if we don't plan to trap the event +/datum/admins/proc/cmd_ghost_drag(mob/dead/observer/frommob, mob/tomob) + + //this is the exact two check rights checks required to edit a ckey with vv. + if (!check_rights(R_VAREDIT,0) || !check_rights(R_SPAWN|R_DEBUG,0)) + return FALSE + + if (!frommob.ckey) + return FALSE + + var/question = "" + if (tomob.ckey) + question = "This mob already has a user ([tomob.key]) in control of it! " + question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?" + + var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No") + if (ask != "Yes") + return TRUE + + if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response + return TRUE + + // Disassociates observer mind from the body mind + if(tomob.client) + tomob.ghostize(FALSE) + else + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) + if(tomob.mind == ghost.mind) + ghost.mind = null + + message_admins("[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name].") + log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control") + + tomob.ckey = frommob.ckey + qdel(frommob) + + return TRUE + +/client/proc/adminGreet(logout) + if(SSticker.HasRoundStarted()) + var/string + if(logout && CONFIG_GET(flag/announce_admin_logout)) + string = pick( + "Admin logout: [key_name(src)]") + else if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN)) + string = pick( + "Admin login: [key_name(src)]") + if(string) + message_admins("[string]") diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm index c89d3d0f026..e278d885c8e 100644 --- a/code/modules/admin/admin_investigate.dm +++ b/code/modules/admin/admin_investigate.dm @@ -1,42 +1,42 @@ -/atom/proc/investigate_log(message, subject) - if(!message || !subject) - return - var/F = file("[GLOB.log_directory]/[subject].html") - WRITE_FILE(F, "[time_stamp()] [REF(src)] ([x],[y],[z]) || [src] [message]
                ") - -/client/proc/investigate_show() - set name = "Investigate" - set category = "Admin - Game" - if(!holder) - return - - var/list/investigates = list(INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_NANITES, INVESTIGATE_PRESENTS) - - var/list/logs_present = list("notes, memos, watchlist") - var/list/logs_missing = list("---") - - for(var/subject in investigates) - var/temp_file = file("[GLOB.log_directory]/[subject].html") - if(fexists(temp_file)) - logs_present += subject - else - logs_missing += "[subject] (empty)" - - var/list/combined = sortList(logs_present) + sortList(logs_missing) - - var/selected = input("Investigate what?", "Investigate") as null|anything in combined - - if(!(selected in combined) || selected == "---") - return - - selected = replacetext(selected, " (empty)", "") - - if(selected == "notes, memos, watchlist" && check_rights(R_ADMIN)) - browse_messages() - return - - var/F = file("[GLOB.log_directory]/[selected].html") - if(!fexists(F)) - to_chat(src, "No [selected] logfile was found.", confidential = TRUE) - return - src << browse(F,"window=investigate[selected];size=800x300") +/atom/proc/investigate_log(message, subject) + if(!message || !subject) + return + var/F = file("[GLOB.log_directory]/[subject].html") + WRITE_FILE(F, "[time_stamp()] [REF(src)] ([x],[y],[z]) || [src] [message]
                ") + +/client/proc/investigate_show() + set name = "Investigate" + set category = "Admin - Game" + if(!holder) + return + + var/list/investigates = list(INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_NANITES, INVESTIGATE_PRESENTS) + + var/list/logs_present = list("notes, memos, watchlist") + var/list/logs_missing = list("---") + + for(var/subject in investigates) + var/temp_file = file("[GLOB.log_directory]/[subject].html") + if(fexists(temp_file)) + logs_present += subject + else + logs_missing += "[subject] (empty)" + + var/list/combined = sortList(logs_present) + sortList(logs_missing) + + var/selected = input("Investigate what?", "Investigate") as null|anything in combined + + if(!(selected in combined) || selected == "---") + return + + selected = replacetext(selected, " (empty)", "") + + if(selected == "notes, memos, watchlist" && check_rights(R_ADMIN)) + browse_messages() + return + + var/F = file("[GLOB.log_directory]/[selected].html") + if(!fexists(F)) + to_chat(src, "No [selected] logfile was found.", confidential = TRUE) + return + src << browse(F,"window=investigate[selected];size=800x300") diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 646731cd66f..a666c7afc4d 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -1,292 +1,292 @@ -GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums -GLOBAL_PROTECT(admin_ranks) - -GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt -GLOBAL_PROTECT(protected_ranks) - -/datum/admin_rank - var/name = "NoRank" - var/rights = R_DEFAULT - var/exclude_rights = 0 - var/include_rights = 0 - var/can_edit_rights = 0 - -/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins - QDEL_IN(src, 0) - CRASH("Admin proc call creation of admin datum") - return - name = init_name - if(!name) - qdel(src) - CRASH("Admin rank created without name.") - if(init_rights) - rights = init_rights - include_rights = rights - if(init_exclude_rights) - exclude_rights = init_exclude_rights - rights &= ~exclude_rights - if(init_edit_rights) - can_edit_rights = init_edit_rights - -/datum/admin_rank/Destroy() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return QDEL_HINT_LETMELIVE - . = ..() - -/datum/admin_rank/vv_edit_var(var_name, var_value) - return FALSE - -// Adds/removes rights to this admin_rank -/datum/admin_rank/proc/process_keyword(group, group_count, datum/admin_rank/previous_rank) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - var/list/keywords = splittext(group, " ") - var/flag = 0 - for(var/k in keywords) - switch(k) - if("BUILD") - flag = R_BUILD - if("ADMIN") - flag = R_ADMIN - if("BAN") - flag = R_BAN - if("FUN") - flag = R_FUN - if("SERVER") - flag = R_SERVER - if("DEBUG") - flag = R_DEBUG - if("PERMISSIONS") - flag = R_PERMISSIONS - if("POSSESS") - flag = R_POSSESS - if("STEALTH") - flag = R_STEALTH - if("POLL") - flag = R_POLL - if("VAREDIT") - flag = R_VAREDIT - if("EVERYTHING") - flag = R_EVERYTHING - if("SOUND") - flag = R_SOUND - if("SPAWN") - flag = R_SPAWN - if("AUTOADMIN") - flag = R_AUTOADMIN - if("DBRANKS") - flag = R_DBRANKS - if("@") - if(previous_rank) - switch(group_count) - if(1) - flag = previous_rank.include_rights - if(2) - flag = previous_rank.exclude_rights - if(3) - flag = previous_rank.can_edit_rights - else - continue - switch(group_count) - if(1) - rights |= flag - include_rights |= flag - if(2) - rights &= ~flag - exclude_rights |= flag - if(3) - can_edit_rights |= flag - -/proc/sync_ranks_with_db() - set waitfor = FALSE - - if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.", confidential = TRUE) - return - - var/list/sql_ranks = list() - for(var/datum/admin_rank/R in GLOB.protected_ranks) - sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights)) - SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE) - -//load our rank - > rights associations -/proc/load_admin_ranks(dbfail, no_update) - if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin Reload blocked: Advanced ProcCall detected.", confidential = TRUE) - return - GLOB.admin_ranks.Cut() - GLOB.protected_ranks.Cut() - //load text from file and process each entry - var/ranks_text = file2text("[global.config.directory]/admin_ranks.txt") - var/datum/admin_rank/previous_rank - var/regex/admin_ranks_regex = new(@"^Name\s*=\s*(.+?)\s*\n+Include\s*=\s*([\l @]*?)\s*\n+Exclude\s*=\s*([\l @]*?)\s*\n+Edit\s*=\s*([\l @]*?)\s*\n*$", "gm") - while(admin_ranks_regex.Find(ranks_text)) - var/datum/admin_rank/R = new(admin_ranks_regex.group[1]) - if(!R) - continue - var/count = 1 - for(var/i in admin_ranks_regex.group - admin_ranks_regex.group[1]) - if(i) - R.process_keyword(i, count, previous_rank) - count++ - GLOB.admin_ranks += R - GLOB.protected_ranks += R - previous_rank = R - if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) - if(CONFIG_GET(flag/load_legacy_ranks_only)) - if(!no_update) - sync_ranks_with_db() - else - var/datum/db_query/query_load_admin_ranks = SSdbcore.NewQuery("SELECT `rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") - if(!query_load_admin_ranks.Execute()) - message_admins("Error loading admin ranks from database. Loading from backup.") - log_sql("Error loading admin ranks from database. Loading from backup.") - dbfail = 1 - else - while(query_load_admin_ranks.NextRow()) - var/skip - var/rank_name = query_load_admin_ranks.item[1] - for(var/datum/admin_rank/R in GLOB.admin_ranks) - if(R.name == rank_name) //this rank was already loaded from txt override - skip = 1 - break - if(!skip) - var/rank_flags = text2num(query_load_admin_ranks.item[2]) - var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3]) - var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4]) - var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags) - if(!R) - continue - GLOB.admin_ranks += R - qdel(query_load_admin_ranks) - //load ranks from backup file - if(dbfail) - var/backup_file = file2text("data/admins_backup.json") - if(backup_file == null) - log_world("Unable to locate admins backup file.") - return FALSE - var/list/json = json_decode(backup_file) - for(var/J in json["ranks"]) - var/skip - for(var/datum/admin_rank/R in GLOB.admin_ranks) - if(R.name == "[J]") //this rank was already loaded from txt override - skip = TRUE - if(skip) - continue - var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"]) - if(!R) - continue - GLOB.admin_ranks += R - return json - #ifdef TESTING - var/msg = "Permission Sets Built:\n" - for(var/datum/admin_rank/R in GLOB.admin_ranks) - msg += "\t[R.name]" - var/rights = rights2text(R.rights,"\n\t\t") - if(rights) - msg += "\t\t[rights]\n" - testing(msg) - #endif - -/proc/load_admins(no_update) - var/dbfail - if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect()) - message_admins("Failed to connect to database while loading admins. Loading from backup.") - log_sql("Failed to connect to database while loading admins. Loading from backup.") - dbfail = 1 - //clear the datums references - GLOB.admin_datums.Cut() - for(var/client/C in GLOB.admins) - C.remove_admin_verbs() - C.holder = null - GLOB.admins.Cut() - GLOB.protected_admins.Cut() - GLOB.deadmins.Cut() - var/list/backup_file_json = load_admin_ranks(dbfail, no_update) - dbfail = backup_file_json != null - //Clear profile access - for(var/A in world.GetConfig("admin")) - world.SetConfig("APP/admin", A, null) - var/list/rank_names = list() - for(var/datum/admin_rank/R in GLOB.admin_ranks) - rank_names[R.name] = R - //ckeys listed in admins.txt are always made admins before sql loading is attempted - var/admins_text = file2text("[global.config.directory]/admins.txt") - var/regex/admins_regex = new(@"^(?!#)(.+?)\s+=\s+(.+)", "gm") - while(admins_regex.Find(admins_text)) - new /datum/admins(rank_names[admins_regex.group[2]], ckey(admins_regex.group[1]), FALSE, TRUE) - if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) - var/datum/db_query/query_load_admins = SSdbcore.NewQuery("SELECT ckey, `rank` FROM [format_table_name("admin")] ORDER BY `rank`") - if(!query_load_admins.Execute()) - message_admins("Error loading admins from database. Loading from backup.") - log_sql("Error loading admins from database. Loading from backup.") - dbfail = 1 - else - while(query_load_admins.NextRow()) - var/admin_ckey = ckey(query_load_admins.item[1]) - var/admin_rank = query_load_admins.item[2] - var/skip - if(rank_names[admin_rank] == null) - message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].") - skip = 1 - if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey]) - skip = 1 - if(!skip) - new /datum/admins(rank_names[admin_rank], admin_ckey) - qdel(query_load_admins) - //load admins from backup file - if(dbfail) - if(!backup_file_json) - if(backup_file_json != null) - //already tried - return - var/backup_file = file2text("data/admins_backup.json") - if(backup_file == null) - log_world("Unable to locate admins backup file.") - return - backup_file_json = json_decode(backup_file) - for(var/J in backup_file_json["admins"]) - var/skip - for(var/A in GLOB.admin_datums + GLOB.deadmins) - if(A == "[J]") //this admin was already loaded from txt override - skip = TRUE - if(skip) - continue - new /datum/admins(rank_names[backup_file_json["admins"]["[J]"]], ckey("[J]")) - #ifdef TESTING - var/msg = "Admins Built:\n" - for(var/ckey in GLOB.admin_datums) - var/datum/admins/D = GLOB.admin_datums[ckey] - msg += "\t[ckey] - [D.rank.name]\n" - testing(msg) - #endif - return dbfail - -#ifdef TESTING -/client/verb/changerank(newrank in GLOB.admin_ranks) - if(holder) - holder.rank = newrank - else - holder = new /datum/admins(newrank, ckey) - remove_admin_verbs() - holder.associate(src) - -/client/verb/changerights(newrights as num) - if(holder) - holder.rank.rights = newrights - else - holder = new /datum/admins("testing", newrights, ckey) - remove_admin_verbs() - holder.associate(src) -#endif +GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums +GLOBAL_PROTECT(admin_ranks) + +GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt +GLOBAL_PROTECT(protected_ranks) + +/datum/admin_rank + var/name = "NoRank" + var/rights = R_DEFAULT + var/exclude_rights = 0 + var/include_rights = 0 + var/can_edit_rights = 0 + +/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins + QDEL_IN(src, 0) + CRASH("Admin proc call creation of admin datum") + return + name = init_name + if(!name) + qdel(src) + CRASH("Admin rank created without name.") + if(init_rights) + rights = init_rights + include_rights = rights + if(init_exclude_rights) + exclude_rights = init_exclude_rights + rights &= ~exclude_rights + if(init_edit_rights) + can_edit_rights = init_edit_rights + +/datum/admin_rank/Destroy() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return QDEL_HINT_LETMELIVE + . = ..() + +/datum/admin_rank/vv_edit_var(var_name, var_value) + return FALSE + +// Adds/removes rights to this admin_rank +/datum/admin_rank/proc/process_keyword(group, group_count, datum/admin_rank/previous_rank) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + var/list/keywords = splittext(group, " ") + var/flag = 0 + for(var/k in keywords) + switch(k) + if("BUILD") + flag = R_BUILD + if("ADMIN") + flag = R_ADMIN + if("BAN") + flag = R_BAN + if("FUN") + flag = R_FUN + if("SERVER") + flag = R_SERVER + if("DEBUG") + flag = R_DEBUG + if("PERMISSIONS") + flag = R_PERMISSIONS + if("POSSESS") + flag = R_POSSESS + if("STEALTH") + flag = R_STEALTH + if("POLL") + flag = R_POLL + if("VAREDIT") + flag = R_VAREDIT + if("EVERYTHING") + flag = R_EVERYTHING + if("SOUND") + flag = R_SOUND + if("SPAWN") + flag = R_SPAWN + if("AUTOADMIN") + flag = R_AUTOADMIN + if("DBRANKS") + flag = R_DBRANKS + if("@") + if(previous_rank) + switch(group_count) + if(1) + flag = previous_rank.include_rights + if(2) + flag = previous_rank.exclude_rights + if(3) + flag = previous_rank.can_edit_rights + else + continue + switch(group_count) + if(1) + rights |= flag + include_rights |= flag + if(2) + rights &= ~flag + exclude_rights |= flag + if(3) + can_edit_rights |= flag + +/proc/sync_ranks_with_db() + set waitfor = FALSE + + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.", confidential = TRUE) + return + + var/list/sql_ranks = list() + for(var/datum/admin_rank/R in GLOB.protected_ranks) + sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights)) + SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE) + +//load our rank - > rights associations +/proc/load_admin_ranks(dbfail, no_update) + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin Reload blocked: Advanced ProcCall detected.", confidential = TRUE) + return + GLOB.admin_ranks.Cut() + GLOB.protected_ranks.Cut() + //load text from file and process each entry + var/ranks_text = file2text("[global.config.directory]/admin_ranks.txt") + var/datum/admin_rank/previous_rank + var/regex/admin_ranks_regex = new(@"^Name\s*=\s*(.+?)\s*\n+Include\s*=\s*([\l @]*?)\s*\n+Exclude\s*=\s*([\l @]*?)\s*\n+Edit\s*=\s*([\l @]*?)\s*\n*$", "gm") + while(admin_ranks_regex.Find(ranks_text)) + var/datum/admin_rank/R = new(admin_ranks_regex.group[1]) + if(!R) + continue + var/count = 1 + for(var/i in admin_ranks_regex.group - admin_ranks_regex.group[1]) + if(i) + R.process_keyword(i, count, previous_rank) + count++ + GLOB.admin_ranks += R + GLOB.protected_ranks += R + previous_rank = R + if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) + if(CONFIG_GET(flag/load_legacy_ranks_only)) + if(!no_update) + sync_ranks_with_db() + else + var/datum/db_query/query_load_admin_ranks = SSdbcore.NewQuery("SELECT `rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") + if(!query_load_admin_ranks.Execute()) + message_admins("Error loading admin ranks from database. Loading from backup.") + log_sql("Error loading admin ranks from database. Loading from backup.") + dbfail = 1 + else + while(query_load_admin_ranks.NextRow()) + var/skip + var/rank_name = query_load_admin_ranks.item[1] + for(var/datum/admin_rank/R in GLOB.admin_ranks) + if(R.name == rank_name) //this rank was already loaded from txt override + skip = 1 + break + if(!skip) + var/rank_flags = text2num(query_load_admin_ranks.item[2]) + var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3]) + var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4]) + var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags) + if(!R) + continue + GLOB.admin_ranks += R + qdel(query_load_admin_ranks) + //load ranks from backup file + if(dbfail) + var/backup_file = file2text("data/admins_backup.json") + if(backup_file == null) + log_world("Unable to locate admins backup file.") + return FALSE + var/list/json = json_decode(backup_file) + for(var/J in json["ranks"]) + var/skip + for(var/datum/admin_rank/R in GLOB.admin_ranks) + if(R.name == "[J]") //this rank was already loaded from txt override + skip = TRUE + if(skip) + continue + var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"]) + if(!R) + continue + GLOB.admin_ranks += R + return json + #ifdef TESTING + var/msg = "Permission Sets Built:\n" + for(var/datum/admin_rank/R in GLOB.admin_ranks) + msg += "\t[R.name]" + var/rights = rights2text(R.rights,"\n\t\t") + if(rights) + msg += "\t\t[rights]\n" + testing(msg) + #endif + +/proc/load_admins(no_update) + var/dbfail + if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect()) + message_admins("Failed to connect to database while loading admins. Loading from backup.") + log_sql("Failed to connect to database while loading admins. Loading from backup.") + dbfail = 1 + //clear the datums references + GLOB.admin_datums.Cut() + for(var/client/C in GLOB.admins) + C.remove_admin_verbs() + C.holder = null + GLOB.admins.Cut() + GLOB.protected_admins.Cut() + GLOB.deadmins.Cut() + var/list/backup_file_json = load_admin_ranks(dbfail, no_update) + dbfail = backup_file_json != null + //Clear profile access + for(var/A in world.GetConfig("admin")) + world.SetConfig("APP/admin", A, null) + var/list/rank_names = list() + for(var/datum/admin_rank/R in GLOB.admin_ranks) + rank_names[R.name] = R + //ckeys listed in admins.txt are always made admins before sql loading is attempted + var/admins_text = file2text("[global.config.directory]/admins.txt") + var/regex/admins_regex = new(@"^(?!#)(.+?)\s+=\s+(.+)", "gm") + while(admins_regex.Find(admins_text)) + new /datum/admins(rank_names[admins_regex.group[2]], ckey(admins_regex.group[1]), FALSE, TRUE) + if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) + var/datum/db_query/query_load_admins = SSdbcore.NewQuery("SELECT ckey, `rank` FROM [format_table_name("admin")] ORDER BY `rank`") + if(!query_load_admins.Execute()) + message_admins("Error loading admins from database. Loading from backup.") + log_sql("Error loading admins from database. Loading from backup.") + dbfail = 1 + else + while(query_load_admins.NextRow()) + var/admin_ckey = ckey(query_load_admins.item[1]) + var/admin_rank = query_load_admins.item[2] + var/skip + if(rank_names[admin_rank] == null) + message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].") + skip = 1 + if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey]) + skip = 1 + if(!skip) + new /datum/admins(rank_names[admin_rank], admin_ckey) + qdel(query_load_admins) + //load admins from backup file + if(dbfail) + if(!backup_file_json) + if(backup_file_json != null) + //already tried + return + var/backup_file = file2text("data/admins_backup.json") + if(backup_file == null) + log_world("Unable to locate admins backup file.") + return + backup_file_json = json_decode(backup_file) + for(var/J in backup_file_json["admins"]) + var/skip + for(var/A in GLOB.admin_datums + GLOB.deadmins) + if(A == "[J]") //this admin was already loaded from txt override + skip = TRUE + if(skip) + continue + new /datum/admins(rank_names[backup_file_json["admins"]["[J]"]], ckey("[J]")) + #ifdef TESTING + var/msg = "Admins Built:\n" + for(var/ckey in GLOB.admin_datums) + var/datum/admins/D = GLOB.admin_datums[ckey] + msg += "\t[ckey] - [D.rank.name]\n" + testing(msg) + #endif + return dbfail + +#ifdef TESTING +/client/verb/changerank(newrank in GLOB.admin_ranks) + if(holder) + holder.rank = newrank + else + holder = new /datum/admins(newrank, ckey) + remove_admin_verbs() + holder.associate(src) + +/client/verb/changerights(newrights as num) + if(holder) + holder.rank.rights = newrights + else + holder = new /datum/admins("testing", newrights, ckey) + remove_admin_verbs() + holder.associate(src) +#endif diff --git a/code/modules/admin/chat_commands.dm b/code/modules/admin/chat_commands.dm index ef9f448be4e..3f0e17c6da4 100644 --- a/code/modules/admin/chat_commands.dm +++ b/code/modules/admin/chat_commands.dm @@ -1,109 +1,109 @@ -#define TGS_STATUS_THROTTLE 5 - -/datum/tgs_chat_command/tgsstatus - name = "status" - help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server" - admin_only = TRUE - -/datum/tgs_chat_command/tgsstatus/Run(datum/tgs_chat_user/sender, params) - var/list/adm = get_admin_counts() - var/list/allmins = adm["total"] - var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). " - status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]." - return status - -/datum/tgs_chat_command/tgscheck - name = "check" - help_text = "Gets the playercount, gamemode, and address of the server" - -/datum/tgs_chat_command/tgscheck/Run(datum/tgs_chat_user/sender, params) - var/server = CONFIG_GET(string/server) - return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" - -/datum/tgs_chat_command/ahelp - name = "ahelp" - help_text = " |list>>" - admin_only = TRUE - -/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params) - var/list/all_params = splittext(params, " ") - if(all_params.len < 2) - return "Insufficient parameters" - var/target = all_params[1] - all_params.Cut(1, 2) - var/id = text2num(target) - if(id != null) - var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id) - if(AH) - target = AH.initiator_ckey - else - return "Ticket #[id] not found!" - var/res = TgsPm(target, all_params.Join(" "), sender.friendly_name) - if(res != "Message Successful") - return res - -/datum/tgs_chat_command/namecheck - name = "namecheck" - help_text = "Returns info on the specified target" - admin_only = TRUE - -/datum/tgs_chat_command/namecheck/Run(datum/tgs_chat_user/sender, params) - params = trim(params) - if(!params) - return "Insufficient parameters" - log_admin("Chat Name Check: [sender.friendly_name] on [params]") - message_admins("Name checking [params] from [sender.friendly_name]") - return keywords_lookup(params, 1) - -/datum/tgs_chat_command/adminwho - name = "adminwho" - help_text = "Lists administrators currently on the server" - admin_only = TRUE - -/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params) - return tgsadminwho() - -GLOBAL_LIST(round_end_notifiees) - -/datum/tgs_chat_command/endnotify - name = "endnotify" - help_text = "Pings the invoker when the round ends" - admin_only = TRUE - -/datum/tgs_chat_command/endnotify/Run(datum/tgs_chat_user/sender, params) - if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted()) - return "[sender.mention], the round has already ended!" - LAZYINITLIST(GLOB.round_end_notifiees) - GLOB.round_end_notifiees[sender.mention] = TRUE - return "I will notify [sender.mention] when the round ends." - -/datum/tgs_chat_command/sdql - name = "sdql" - help_text = "Runs an SDQL query" - admin_only = TRUE - -/datum/tgs_chat_command/sdql/Run(datum/tgs_chat_user/sender, params) - if(GLOB.AdminProcCaller) - return "Unable to run query, another admin proc call is in progress. Try again later." - GLOB.AdminProcCaller = "CHAT_[sender.friendly_name]" //_ won't show up in ckeys so it'll never match with a real admin - var/list/results = world.SDQL2_query(params, GLOB.AdminProcCaller, GLOB.AdminProcCaller) - GLOB.AdminProcCaller = null - if(!results) - return "Query produced no output" - var/list/text_res = results.Copy(1, 3) - var/list/refs = results.len > 3 ? results.Copy(4) : null - . = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]" - -/datum/tgs_chat_command/reload_admins - name = "reload_admins" - help_text = "Forces the server to reload admins." - admin_only = TRUE - -/datum/tgs_chat_command/reload_admins/Run(datum/tgs_chat_user/sender, params) - ReloadAsync() - log_admin("[sender.friendly_name] reloaded admins via chat command.") - return "Admins reloaded." - -/datum/tgs_chat_command/reload_admins/proc/ReloadAsync() - set waitfor = FALSE - load_admins() +#define TGS_STATUS_THROTTLE 5 + +/datum/tgs_chat_command/tgsstatus + name = "status" + help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server" + admin_only = TRUE + +/datum/tgs_chat_command/tgsstatus/Run(datum/tgs_chat_user/sender, params) + var/list/adm = get_admin_counts() + var/list/allmins = adm["total"] + var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). " + status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]." + return status + +/datum/tgs_chat_command/tgscheck + name = "check" + help_text = "Gets the playercount, gamemode, and address of the server" + +/datum/tgs_chat_command/tgscheck/Run(datum/tgs_chat_user/sender, params) + var/server = CONFIG_GET(string/server) + return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" + +/datum/tgs_chat_command/ahelp + name = "ahelp" + help_text = " |list>>" + admin_only = TRUE + +/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params) + var/list/all_params = splittext(params, " ") + if(all_params.len < 2) + return "Insufficient parameters" + var/target = all_params[1] + all_params.Cut(1, 2) + var/id = text2num(target) + if(id != null) + var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id) + if(AH) + target = AH.initiator_ckey + else + return "Ticket #[id] not found!" + var/res = TgsPm(target, all_params.Join(" "), sender.friendly_name) + if(res != "Message Successful") + return res + +/datum/tgs_chat_command/namecheck + name = "namecheck" + help_text = "Returns info on the specified target" + admin_only = TRUE + +/datum/tgs_chat_command/namecheck/Run(datum/tgs_chat_user/sender, params) + params = trim(params) + if(!params) + return "Insufficient parameters" + log_admin("Chat Name Check: [sender.friendly_name] on [params]") + message_admins("Name checking [params] from [sender.friendly_name]") + return keywords_lookup(params, 1) + +/datum/tgs_chat_command/adminwho + name = "adminwho" + help_text = "Lists administrators currently on the server" + admin_only = TRUE + +/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params) + return tgsadminwho() + +GLOBAL_LIST(round_end_notifiees) + +/datum/tgs_chat_command/endnotify + name = "endnotify" + help_text = "Pings the invoker when the round ends" + admin_only = TRUE + +/datum/tgs_chat_command/endnotify/Run(datum/tgs_chat_user/sender, params) + if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted()) + return "[sender.mention], the round has already ended!" + LAZYINITLIST(GLOB.round_end_notifiees) + GLOB.round_end_notifiees[sender.mention] = TRUE + return "I will notify [sender.mention] when the round ends." + +/datum/tgs_chat_command/sdql + name = "sdql" + help_text = "Runs an SDQL query" + admin_only = TRUE + +/datum/tgs_chat_command/sdql/Run(datum/tgs_chat_user/sender, params) + if(GLOB.AdminProcCaller) + return "Unable to run query, another admin proc call is in progress. Try again later." + GLOB.AdminProcCaller = "CHAT_[sender.friendly_name]" //_ won't show up in ckeys so it'll never match with a real admin + var/list/results = world.SDQL2_query(params, GLOB.AdminProcCaller, GLOB.AdminProcCaller) + GLOB.AdminProcCaller = null + if(!results) + return "Query produced no output" + var/list/text_res = results.Copy(1, 3) + var/list/refs = results.len > 3 ? results.Copy(4) : null + . = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]" + +/datum/tgs_chat_command/reload_admins + name = "reload_admins" + help_text = "Forces the server to reload admins." + admin_only = TRUE + +/datum/tgs_chat_command/reload_admins/Run(datum/tgs_chat_user/sender, params) + ReloadAsync() + log_admin("[sender.friendly_name] reloaded admins via chat command.") + return "Admins reloaded." + +/datum/tgs_chat_command/reload_admins/proc/ReloadAsync() + set waitfor = FALSE + load_admins() diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index 99afd9261c2..594aae2bafc 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -1,41 +1,41 @@ - -/datum/admins/proc/create_mob(mob/user) - var/static/create_mob_html - if (!create_mob_html) - var/mobjs = null - mobjs = jointext(typesof(/mob), ";") - create_mob_html = file2text('html/create_object.html') - create_mob_html = replacetext(create_mob_html, "Create Object", "Create Mob") - create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"") - - user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=425x475") - -/proc/randomize_human(mob/living/carbon/human/H) - H.gender = pick(MALE, FEMALE) - H.body_type = H.gender - H.real_name = random_unique_name(H.gender) - H.name = H.real_name - H.underwear = random_underwear(H.gender) - H.underwear_color = random_short_color() - H.skin_tone = random_skin_tone() - H.hairstyle = random_hairstyle(H.gender) - H.facial_hairstyle = random_facial_hairstyle(H.gender) - H.hair_color = random_short_color() - H.facial_hair_color = H.hair_color - H.eye_color = random_eye_color() - H.dna.blood_type = random_blood_type() - - // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. - H.dna.features["mcolor"] = random_short_color() - H.dna.features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] - H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard) - H.dna.features["snout"] = pick(GLOB.snouts_list) - H.dna.features["horns"] = pick(GLOB.horns_list) - H.dna.features["frills"] = pick(GLOB.frills_list) - H.dna.features["spines"] = pick(GLOB.spines_list) - H.dna.features["body_markings"] = pick(GLOB.body_markings_list) - H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list) - - H.update_body() - H.update_hair() - H.update_body_parts() + +/datum/admins/proc/create_mob(mob/user) + var/static/create_mob_html + if (!create_mob_html) + var/mobjs = null + mobjs = jointext(typesof(/mob), ";") + create_mob_html = file2text('html/create_object.html') + create_mob_html = replacetext(create_mob_html, "Create Object", "Create Mob") + create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"") + + user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=425x475") + +/proc/randomize_human(mob/living/carbon/human/H) + H.gender = pick(MALE, FEMALE) + H.body_type = H.gender + H.real_name = random_unique_name(H.gender) + H.name = H.real_name + H.underwear = random_underwear(H.gender) + H.underwear_color = random_short_color() + H.skin_tone = random_skin_tone() + H.hairstyle = random_hairstyle(H.gender) + H.facial_hairstyle = random_facial_hairstyle(H.gender) + H.hair_color = random_short_color() + H.facial_hair_color = H.hair_color + H.eye_color = random_eye_color() + H.dna.blood_type = random_blood_type() + + // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. + H.dna.features["mcolor"] = random_short_color() + H.dna.features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] + H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard) + H.dna.features["snout"] = pick(GLOB.snouts_list) + H.dna.features["horns"] = pick(GLOB.horns_list) + H.dna.features["frills"] = pick(GLOB.frills_list) + H.dna.features["spines"] = pick(GLOB.spines_list) + H.dna.features["body_markings"] = pick(GLOB.body_markings_list) + H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list) + + H.update_body() + H.update_hair() + H.update_body_parts() diff --git a/code/modules/admin/create_object.dm b/code/modules/admin/create_object.dm index 83baf826638..00f642c7946 100644 --- a/code/modules/admin/create_object.dm +++ b/code/modules/admin/create_object.dm @@ -1,32 +1,32 @@ -/datum/admins/proc/create_panel_helper(template) - var/final_html = replacetext(template, "/* ref src */", "[REF(src)];[HrefToken()]") - final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]") - return final_html - -/datum/admins/proc/create_object(mob/user) - var/static/create_object_html = null - if (!create_object_html) - var/objectjs = null - objectjs = jointext(typesof(/obj), ";") - create_object_html = file2text('html/create_object.html') - create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"") - - user << browse(create_panel_helper(create_object_html), "window=create_object;size=425x475") - -/datum/admins/proc/quick_create_object(mob/user) - var/static/list/create_object_forms = list( - /obj, /obj/structure, /obj/machinery, /obj/effect, - /obj/item, /obj/item/clothing, /obj/item/stack, /obj/item, - /obj/item/reagent_containers, /obj/item/gun) - - var/path = input("Select the path of the object you wish to create.", "Path", /obj) in sortList(create_object_forms, /proc/cmp_typepaths_asc) - var/html_form = create_object_forms[path] - - if (!html_form) - var/objectjs = jointext(typesof(path), ";") - html_form = file2text('html/create_object.html') - html_form = replacetext(html_form, "Create Object", "Create [path]") - html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"") - create_object_forms[path] = html_form - - user << browse(create_panel_helper(html_form), "window=qco[path];size=425x475") +/datum/admins/proc/create_panel_helper(template) + var/final_html = replacetext(template, "/* ref src */", "[REF(src)];[HrefToken()]") + final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]") + return final_html + +/datum/admins/proc/create_object(mob/user) + var/static/create_object_html = null + if (!create_object_html) + var/objectjs = null + objectjs = jointext(typesof(/obj), ";") + create_object_html = file2text('html/create_object.html') + create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"") + + user << browse(create_panel_helper(create_object_html), "window=create_object;size=425x475") + +/datum/admins/proc/quick_create_object(mob/user) + var/static/list/create_object_forms = list( + /obj, /obj/structure, /obj/machinery, /obj/effect, + /obj/item, /obj/item/clothing, /obj/item/stack, /obj/item, + /obj/item/reagent_containers, /obj/item/gun) + + var/path = input("Select the path of the object you wish to create.", "Path", /obj) in sortList(create_object_forms, /proc/cmp_typepaths_asc) + var/html_form = create_object_forms[path] + + if (!html_form) + var/objectjs = jointext(typesof(path), ";") + html_form = file2text('html/create_object.html') + html_form = replacetext(html_form, "Create Object", "Create [path]") + html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"") + create_object_forms[path] = html_form + + user << browse(create_panel_helper(html_form), "window=qco[path];size=425x475") diff --git a/code/modules/admin/create_turf.dm b/code/modules/admin/create_turf.dm index 4742cac4420..86e83f38aee 100644 --- a/code/modules/admin/create_turf.dm +++ b/code/modules/admin/create_turf.dm @@ -1,10 +1,10 @@ -/datum/admins/proc/create_turf(mob/user) - var/static/create_turf_html - if (!create_turf_html) - var/turfjs = null - turfjs = jointext(typesof(/turf), ";") - create_turf_html = file2text('html/create_object.html') - create_turf_html = replacetext(create_turf_html, "Create Object", "Create Turf") - create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"") - - user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=425x475") +/datum/admins/proc/create_turf(mob/user) + var/static/create_turf_html + if (!create_turf_html) + var/turfjs = null + turfjs = jointext(typesof(/turf), ";") + create_turf_html = file2text('html/create_object.html') + create_turf_html = replacetext(create_turf_html, "Create Object", "Create Turf") + create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"") + + user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=425x475") diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 19000028048..b8ae48a8940 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -1,208 +1,208 @@ -GLOBAL_LIST_EMPTY(admin_datums) -GLOBAL_PROTECT(admin_datums) -GLOBAL_LIST_EMPTY(protected_admins) -GLOBAL_PROTECT(protected_admins) - -GLOBAL_VAR_INIT(href_token, GenerateToken()) -GLOBAL_PROTECT(href_token) - -/datum/admins - var/datum/admin_rank/rank - - var/target - var/name = "nobody's admin datum (no rank)" //Makes for better runtimes - var/client/owner = null - var/fakekey = null - - var/datum/marked_datum - - var/spamcooldown = 0 - - var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable - var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message - var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message - var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel - var/admin_signature - - var/href_token - - var/deadmined - -/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins - QDEL_IN(src, 0) - CRASH("Admin proc call creation of admin datum") - return - if(!ckey) - QDEL_IN(src, 0) - CRASH("Admin datum created without a ckey") - if(!istype(R)) - QDEL_IN(src, 0) - CRASH("Admin datum created without a rank") - target = ckey - name = "[ckey]'s admin datum ([R])" - rank = R - admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" - href_token = GenerateToken() - if(R.rights & R_DEBUG) //grant profile access - world.SetConfig("APP/admin", ckey, "role=admin") - //only admins with +ADMIN start admined - if(protected) - GLOB.protected_admins[target] = src - if (force_active || (R.rights & R_AUTOADMIN)) - activate() - else - deactivate() - -/datum/admins/Destroy() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return QDEL_HINT_LETMELIVE - . = ..() - -/datum/admins/proc/activate() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - GLOB.deadmins -= target - GLOB.admin_datums[target] = src - deadmined = FALSE - if (GLOB.directory[target]) - associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us - - -/datum/admins/proc/deactivate() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - GLOB.deadmins[target] = src - GLOB.admin_datums -= target - deadmined = TRUE - var/client/C - if ((C = owner) || (C = GLOB.directory[target])) - disassociate() - C.verbs += /client/proc/readmin - -/datum/admins/proc/associate(client/C) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - - if(istype(C)) - if(C.ckey != target) - var/msg = " has attempted to associate with [target]'s admin datum" - message_admins("[key_name_admin(C)][msg]") - log_admin("[key_name(C)][msg]") - return - if (deadmined) - activate() - owner = C - owner.holder = src - owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system - owner.verbs -= /client/proc/readmin - GLOB.admins |= C - -/datum/admins/proc/disassociate() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - if(owner) - GLOB.admins -= owner - owner.remove_admin_verbs() - owner.holder = null - owner = null - -/datum/admins/proc/check_for_rights(rights_required) - if(rights_required && !(rights_required & rank.rights)) - return 0 - return 1 - - -/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other) - if(!other) - return 1 //they have no rights - if(rank.rights == R_EVERYTHING) - return 1 //we have all the rights - if(src == other) - return 1 //you always have more rights than yourself - if(rank.rights != other.rank.rights) - if( (rank.rights & other.rank.rights) == other.rank.rights ) - return 1 //we have all the rights they have and more - return 0 - -/datum/admins/vv_edit_var(var_name, var_value) - return FALSE //nice try trialmin - -/* -checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags) -if rights_required == 0, then it simply checks if they are an admin. -if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed -generally it would be used like so: - -/proc/admin_proc() - if(!check_rights(R_ADMIN)) - return - to_chat(world, "you have enough rights!", confidential = TRUE) - -NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call -you will have to do something like if(client.rights & R_ADMIN) yourself. -*/ -/proc/check_rights(rights_required, show_msg=1) - if(usr && usr.client) - if (check_rights_for(usr.client, rights_required)) - return 1 - else - if(show_msg) - to_chat(usr, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].", confidential = TRUE) - return 0 - -//probably a bit iffy - will hopefully figure out a better solution -/proc/check_if_greater_rights_than(client/other) - if(usr && usr.client) - if(usr.client.holder) - if(!other || !other.holder) - return 1 - return usr.client.holder.check_if_greater_rights_than_holder(other.holder) - return 0 - -//This proc checks whether subject has at least ONE of the rights specified in rights_required. -/proc/check_rights_for(client/subject, rights_required) - if(subject && subject.holder) - return subject.holder.check_for_rights(rights_required) - return 0 - -/proc/GenerateToken() - . = "" - for(var/I in 1 to 32) - . += "[rand(10)]" - -/proc/RawHrefToken(forceGlobal = FALSE) - var/tok = GLOB.href_token - if(!forceGlobal && usr) - var/client/C = usr.client - if(!C) - CRASH("No client for HrefToken()!") - var/datum/admins/holder = C.holder - if(holder) - tok = holder.href_token - return tok - -/proc/HrefToken(forceGlobal = FALSE) - return "admin_token=[RawHrefToken(forceGlobal)]" - -/proc/HrefTokenFormField(forceGlobal = FALSE) - return "" +GLOBAL_LIST_EMPTY(admin_datums) +GLOBAL_PROTECT(admin_datums) +GLOBAL_LIST_EMPTY(protected_admins) +GLOBAL_PROTECT(protected_admins) + +GLOBAL_VAR_INIT(href_token, GenerateToken()) +GLOBAL_PROTECT(href_token) + +/datum/admins + var/datum/admin_rank/rank + + var/target + var/name = "nobody's admin datum (no rank)" //Makes for better runtimes + var/client/owner = null + var/fakekey = null + + var/datum/marked_datum + + var/spamcooldown = 0 + + var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable + var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message + var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message + var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel + var/admin_signature + + var/href_token + + var/deadmined + +/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins + QDEL_IN(src, 0) + CRASH("Admin proc call creation of admin datum") + return + if(!ckey) + QDEL_IN(src, 0) + CRASH("Admin datum created without a ckey") + if(!istype(R)) + QDEL_IN(src, 0) + CRASH("Admin datum created without a rank") + target = ckey + name = "[ckey]'s admin datum ([R])" + rank = R + admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" + href_token = GenerateToken() + if(R.rights & R_DEBUG) //grant profile access + world.SetConfig("APP/admin", ckey, "role=admin") + //only admins with +ADMIN start admined + if(protected) + GLOB.protected_admins[target] = src + if (force_active || (R.rights & R_AUTOADMIN)) + activate() + else + deactivate() + +/datum/admins/Destroy() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return QDEL_HINT_LETMELIVE + . = ..() + +/datum/admins/proc/activate() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + GLOB.deadmins -= target + GLOB.admin_datums[target] = src + deadmined = FALSE + if (GLOB.directory[target]) + associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us + + +/datum/admins/proc/deactivate() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + GLOB.deadmins[target] = src + GLOB.admin_datums -= target + deadmined = TRUE + var/client/C + if ((C = owner) || (C = GLOB.directory[target])) + disassociate() + C.verbs += /client/proc/readmin + +/datum/admins/proc/associate(client/C) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + + if(istype(C)) + if(C.ckey != target) + var/msg = " has attempted to associate with [target]'s admin datum" + message_admins("[key_name_admin(C)][msg]") + log_admin("[key_name(C)][msg]") + return + if (deadmined) + activate() + owner = C + owner.holder = src + owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system + owner.verbs -= /client/proc/readmin + GLOB.admins |= C + +/datum/admins/proc/disassociate() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + if(owner) + GLOB.admins -= owner + owner.remove_admin_verbs() + owner.holder = null + owner = null + +/datum/admins/proc/check_for_rights(rights_required) + if(rights_required && !(rights_required & rank.rights)) + return 0 + return 1 + + +/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other) + if(!other) + return 1 //they have no rights + if(rank.rights == R_EVERYTHING) + return 1 //we have all the rights + if(src == other) + return 1 //you always have more rights than yourself + if(rank.rights != other.rank.rights) + if( (rank.rights & other.rank.rights) == other.rank.rights ) + return 1 //we have all the rights they have and more + return 0 + +/datum/admins/vv_edit_var(var_name, var_value) + return FALSE //nice try trialmin + +/* +checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags) +if rights_required == 0, then it simply checks if they are an admin. +if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed +generally it would be used like so: + +/proc/admin_proc() + if(!check_rights(R_ADMIN)) + return + to_chat(world, "you have enough rights!", confidential = TRUE) + +NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call +you will have to do something like if(client.rights & R_ADMIN) yourself. +*/ +/proc/check_rights(rights_required, show_msg=1) + if(usr && usr.client) + if (check_rights_for(usr.client, rights_required)) + return 1 + else + if(show_msg) + to_chat(usr, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].", confidential = TRUE) + return 0 + +//probably a bit iffy - will hopefully figure out a better solution +/proc/check_if_greater_rights_than(client/other) + if(usr && usr.client) + if(usr.client.holder) + if(!other || !other.holder) + return 1 + return usr.client.holder.check_if_greater_rights_than_holder(other.holder) + return 0 + +//This proc checks whether subject has at least ONE of the rights specified in rights_required. +/proc/check_rights_for(client/subject, rights_required) + if(subject && subject.holder) + return subject.holder.check_for_rights(rights_required) + return 0 + +/proc/GenerateToken() + . = "" + for(var/I in 1 to 32) + . += "[rand(10)]" + +/proc/RawHrefToken(forceGlobal = FALSE) + var/tok = GLOB.href_token + if(!forceGlobal && usr) + var/client/C = usr.client + if(!C) + CRASH("No client for HrefToken()!") + var/datum/admins/holder = C.holder + if(holder) + tok = holder.href_token + return tok + +/proc/HrefToken(forceGlobal = FALSE) + return "admin_token=[RawHrefToken(forceGlobal)]" + +/proc/HrefTokenFormField(forceGlobal = FALSE) + return "" diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index 641a65a5a3d..46b742eab90 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -1,328 +1,328 @@ -/datum/admins/proc/player_panel_new()//The new one - if(!check_rights()) - return - log_admin("[key_name(usr)] checked the player panel.") - var/dat = "Player Panel" - - //javascript, the part that does most of the work~ - dat += {" - - - - - - - "} - - //body tag start + onload and onkeypress (onkeyup) javascript event calls - dat += "" - - //title + search bar - dat += {" - -
                [J_title]: [J_opPos]/[job.total_positions < 0 ? " (unlimited)" : J_totPos]" + + dat += "" + if(job.total_positions >= 0) + dat += "Custom | " + dat += "Add 1 | " + if(job.total_positions > job.current_positions) + dat += "Remove | " + else + dat += "Remove | " + dat += "Unlimit
                - - - - - - -
                - Player panel
                - Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby -

                -

                - Search: -
                - - "} - - //player table header - dat += {" - - "} - - var/list/mobs = sortmobs() - var/i = 1 - for(var/mob/M in mobs) - if(M.ckey) - - var/color = "#e6e6e6" - if(i%2 == 0) - color = "#f2f2f2" - var/is_antagonist = is_special_character(M) - - var/M_job = "" - - if(isliving(M)) - - if(iscarbon(M)) //Carbon stuff - if(ishuman(M)) - M_job = M.job - else if(ismonkey(M)) - M_job = "Monkey" - else if(isalien(M)) //aliens - if(islarva(M)) - M_job = "Alien larva" - else - M_job = ROLE_ALIEN - else - M_job = "Carbon-based" - - else if(issilicon(M)) //silicon - if(isAI(M)) - M_job = "AI" - else if(ispAI(M)) - M_job = ROLE_PAI - else if(iscyborg(M)) - M_job = "Cyborg" - else - M_job = "Silicon-based" - - else if(isanimal(M)) //simple animals - if(iscorgi(M)) - M_job = "Corgi" - else if(isslime(M)) - M_job = "slime" - else - M_job = "Animal" - - else - M_job = "Living" - - else if(isnewplayer(M)) - M_job = "New player" - - else if(isobserver(M)) - var/mob/dead/observer/O = M - if(O.started_as_observer)//Did they get BTFO or are they just not trying? - M_job = "Observer" - else - M_job = "Ghost" - - var/M_name = html_encode(M.name) - var/M_rname = html_encode(M.real_name) - var/M_key = html_encode(M.key) - var/previous_names = "" - if(M_key) - var/datum/player_details/P = GLOB.player_details[ckey(M_key)] - if(P) - previous_names = P.played_names.Join(",") - previous_names = html_encode(previous_names) - - //output for each mob - dat += {" - - - - - - "} - - i++ - - - //player table ending - dat += {" -
                - - - [M_name] - [M_rname] - [M_key] ([M_job]) - - - - - - - - - - -
                -
                -
                - - - - "} - - usr << browse(dat, "window=players;size=600x480") +/datum/admins/proc/player_panel_new()//The new one + if(!check_rights()) + return + log_admin("[key_name(usr)] checked the player panel.") + var/dat = "Player Panel" + + //javascript, the part that does most of the work~ + dat += {" + + + + + + + "} + + //body tag start + onload and onkeypress (onkeyup) javascript event calls + dat += "" + + //title + search bar + dat += {" + + + + + + + + +
                + Player panel
                + Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby +

                +

                + Search: +
                + + "} + + //player table header + dat += {" + + "} + + var/list/mobs = sortmobs() + var/i = 1 + for(var/mob/M in mobs) + if(M.ckey) + + var/color = "#e6e6e6" + if(i%2 == 0) + color = "#f2f2f2" + var/is_antagonist = is_special_character(M) + + var/M_job = "" + + if(isliving(M)) + + if(iscarbon(M)) //Carbon stuff + if(ishuman(M)) + M_job = M.job + else if(ismonkey(M)) + M_job = "Monkey" + else if(isalien(M)) //aliens + if(islarva(M)) + M_job = "Alien larva" + else + M_job = ROLE_ALIEN + else + M_job = "Carbon-based" + + else if(issilicon(M)) //silicon + if(isAI(M)) + M_job = "AI" + else if(ispAI(M)) + M_job = ROLE_PAI + else if(iscyborg(M)) + M_job = "Cyborg" + else + M_job = "Silicon-based" + + else if(isanimal(M)) //simple animals + if(iscorgi(M)) + M_job = "Corgi" + else if(isslime(M)) + M_job = "slime" + else + M_job = "Animal" + + else + M_job = "Living" + + else if(isnewplayer(M)) + M_job = "New player" + + else if(isobserver(M)) + var/mob/dead/observer/O = M + if(O.started_as_observer)//Did they get BTFO or are they just not trying? + M_job = "Observer" + else + M_job = "Ghost" + + var/M_name = html_encode(M.name) + var/M_rname = html_encode(M.real_name) + var/M_key = html_encode(M.key) + var/previous_names = "" + if(M_key) + var/datum/player_details/P = GLOB.player_details[ckey(M_key)] + if(P) + previous_names = P.played_names.Join(",") + previous_names = html_encode(previous_names) + + //output for each mob + dat += {" + + + + + + "} + + i++ + + + //player table ending + dat += {" +
                + + + [M_name] - [M_rname] - [M_key] ([M_job]) + + + + + + + + + + +
                +
                +
                + + + + "} + + usr << browse(dat, "window=players;size=600x480") diff --git a/code/modules/admin/skill_panel.dm b/code/modules/admin/skill_panel.dm index 00ab79913bd..7fc621ac213 100644 --- a/code/modules/admin/skill_panel.dm +++ b/code/modules/admin/skill_panel.dm @@ -1,55 +1,55 @@ -/datum/skill_panel - var/datum/mind/targetmind - var/client/holder //client of whoever is using this datum - -/datum/skill_panel/New(user, datum/mind/mind)//H can either be a client or a mob due to byondcode(tm) - targetmind = mind - if (istype(user,/client)) - var/client/userClient = user - holder = userClient //if its a client, assign it to holder - else - var/mob/userMob = user - holder = userMob.client //if its a mob, assign the mob's client to holder - -/datum/skill_panel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, \ -force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)//ui_interact is called when the client verb is called. - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "SkillPanel", "Manage Skills", 600, 500, master_ui, state) - ui.open() - -/datum/skill_panel/ui_data(mob/user) //Sends info about the skills to UI - . = list() - for (var/type in GLOB.skill_types) - var/datum/skill/S = GetSkillRef(type) - var/lvl_num = targetmind.get_skill_level(type) - var/lvl_name = uppertext(targetmind.get_skill_level_name(type)) - var/exp = targetmind.get_skill_exp(type) - var/xp_prog_to_level = targetmind.exp_needed_to_level_up(type) - var/xp_req_to_level = 0 - if (xp_prog_to_level)//is it even possible to level up? - xp_req_to_level = SKILL_EXP_LIST[lvl_num+1] - SKILL_EXP_LIST[lvl_num] - var/exp_percent = exp / SKILL_EXP_LIST[SKILL_LEVEL_LEGENDARY] - .["skills"] += list(list("playername" = targetmind.current, "path" = type, "name" = S.name, "desc" = S.desc, "lvlnum" = lvl_num, "lvl" = lvl_name, "exp" = exp, "exp_prog" = xp_req_to_level - xp_prog_to_level, "exp_req" = xp_req_to_level, "exp_percent" = exp_percent, "max_exp" = SKILL_EXP_LIST[length(SKILL_EXP_LIST)])) - -/datum/skill_panel/ui_act(action, params) - . = ..() - if(.) - return - switch (action) - if ("adj_exp") - var/skill = text2path(params["skill"]) - var/number = input("Please insert the amount of experience you'd like to add/subtract:") as num|null - if (number) - targetmind.adjust_experience(skill, number) - if ("set_exp") - var/skill = text2path(params["skill"]) - var/number = input("Please insert the number you want to set the player's exp to:") as num|null - if (number) - targetmind.set_experience(skill, number) - if ("set_lvl") - var/skill = text2path(params["skill"]) - var/max_skill = length(SKILL_EXP_LIST) - var/number = input("Please insert a whole number between 1 (NONE) and [max_skill] (LEGENDARY) corresponding to the level you'd like to set the player to.") as num|null - if (number > 0 && number <= max_skill ) - targetmind.set_level(skill, number) +/datum/skill_panel + var/datum/mind/targetmind + var/client/holder //client of whoever is using this datum + +/datum/skill_panel/New(user, datum/mind/mind)//H can either be a client or a mob due to byondcode(tm) + targetmind = mind + if (istype(user,/client)) + var/client/userClient = user + holder = userClient //if its a client, assign it to holder + else + var/mob/userMob = user + holder = userMob.client //if its a mob, assign the mob's client to holder + +/datum/skill_panel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, \ +force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)//ui_interact is called when the client verb is called. + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "SkillPanel", "Manage Skills", 600, 500, master_ui, state) + ui.open() + +/datum/skill_panel/ui_data(mob/user) //Sends info about the skills to UI + . = list() + for (var/type in GLOB.skill_types) + var/datum/skill/S = GetSkillRef(type) + var/lvl_num = targetmind.get_skill_level(type) + var/lvl_name = uppertext(targetmind.get_skill_level_name(type)) + var/exp = targetmind.get_skill_exp(type) + var/xp_prog_to_level = targetmind.exp_needed_to_level_up(type) + var/xp_req_to_level = 0 + if (xp_prog_to_level)//is it even possible to level up? + xp_req_to_level = SKILL_EXP_LIST[lvl_num+1] - SKILL_EXP_LIST[lvl_num] + var/exp_percent = exp / SKILL_EXP_LIST[SKILL_LEVEL_LEGENDARY] + .["skills"] += list(list("playername" = targetmind.current, "path" = type, "name" = S.name, "desc" = S.desc, "lvlnum" = lvl_num, "lvl" = lvl_name, "exp" = exp, "exp_prog" = xp_req_to_level - xp_prog_to_level, "exp_req" = xp_req_to_level, "exp_percent" = exp_percent, "max_exp" = SKILL_EXP_LIST[length(SKILL_EXP_LIST)])) + +/datum/skill_panel/ui_act(action, params) + . = ..() + if(.) + return + switch (action) + if ("adj_exp") + var/skill = text2path(params["skill"]) + var/number = input("Please insert the amount of experience you'd like to add/subtract:") as num|null + if (number) + targetmind.adjust_experience(skill, number) + if ("set_exp") + var/skill = text2path(params["skill"]) + var/number = input("Please insert the number you want to set the player's exp to:") as num|null + if (number) + targetmind.set_experience(skill, number) + if ("set_lvl") + var/skill = text2path(params["skill"]) + var/max_skill = length(SKILL_EXP_LIST) + var/number = input("Please insert a whole number between 1 (NONE) and [max_skill] (LEGENDARY) corresponding to the level you'd like to set the player to.") as num|null + if (number > 0 && number <= max_skill ) + targetmind.set_level(skill, number) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index ccc0d86fffe..ab966654972 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1,2291 +1,2291 @@ -/datum/admins/proc/CheckAdminHref(href, href_list) - var/auth = href_list["admin_token"] - . = auth && (auth == href_token || auth == GLOB.href_token) - if(.) - return - var/msg = !auth ? "no" : "a bad" - message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") - if(CONFIG_GET(flag/debug_admin_hrefs)) - message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") - log_world("UAH: [href]") - return TRUE - log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") - -/datum/admins/Topic(href, href_list) - ..() - - if(usr.client != src.owner || !check_rights(0)) - message_admins("[usr.key] has attempted to override the admin panel!") - log_admin("[key_name(usr)] tried to use the admin panel without authorization.") - return - - if(!CheckAdminHref(href, href_list)) - return - - if(href_list["ahelp"]) - if(!check_rights(R_ADMIN, TRUE)) - return - - var/ahelp_ref = href_list["ahelp"] - var/datum/admin_help/AH = locate(ahelp_ref) - if(AH) - AH.Action(href_list["ahelp_action"]) - else - to_chat(usr, "Ticket [ahelp_ref] has been deleted!", confidential = TRUE) - - else if(href_list["ahelp_tickets"]) - GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"])) - - else if(href_list["stickyban"]) - stickyban(href_list["stickyban"],href_list) - - else if(href_list["getplaytimewindow"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["getplaytimewindow"]) in GLOB.mob_list - if(!M) - to_chat(usr, "ERROR: Mob not found.", confidential = TRUE) - return - cmd_show_exp_panel(M.client) - - else if(href_list["toggleexempt"]) - if(!check_rights(R_ADMIN)) - return - var/client/C = locate(href_list["toggleexempt"]) in GLOB.clients - if(!C) - to_chat(usr, "ERROR: Client not found.", confidential = TRUE) - return - toggle_exempt_status(C) - - else if(href_list["makeAntag"]) - if(!check_rights(R_ADMIN)) - return - if (!SSticker.mode) - to_chat(usr, "Not until the round starts!", confidential = TRUE) - return - switch(href_list["makeAntag"]) - if("traitors") - if(src.makeTraitors()) - message_admins("[key_name_admin(usr)] created traitors.") - log_admin("[key_name(usr)] created traitors.") - else - message_admins("[key_name_admin(usr)] tried to create traitors. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create traitors.") - if("changelings") - if(src.makeChangelings()) - message_admins("[key_name(usr)] created changelings.") - log_admin("[key_name(usr)] created changelings.") - else - message_admins("[key_name_admin(usr)] tried to create changelings. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create changelings.") - if("revs") - if(src.makeRevs()) - message_admins("[key_name(usr)] started a revolution.") - log_admin("[key_name(usr)] started a revolution.") - else - message_admins("[key_name_admin(usr)] tried to start a revolution. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a revolution.") - if("cult") - if(src.makeCult()) - message_admins("[key_name(usr)] started a cult.") - log_admin("[key_name(usr)] started a cult.") - else - message_admins("[key_name_admin(usr)] tried to start a cult. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a cult.") - if("wizard") - message_admins("[key_name(usr)] is creating a wizard...") - if(src.makeWizard()) - message_admins("[key_name(usr)] created a wizard.") - log_admin("[key_name(usr)] created a wizard.") - else - message_admins("[key_name_admin(usr)] tried to create a wizard. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a wizard.") - if("nukeops") - message_admins("[key_name(usr)] is creating a nuke team...") - if(src.makeNukeTeam()) - message_admins("[key_name(usr)] created a nuke team.") - log_admin("[key_name(usr)] created a nuke team.") - else - message_admins("[key_name_admin(usr)] tried to create a nuke team. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a nuke team.") - if("ninja") - message_admins("[key_name(usr)] spawned a ninja.") - log_admin("[key_name(usr)] spawned a ninja.") - src.makeSpaceNinja() - if("aliens") - message_admins("[key_name(usr)] started an alien infestation.") - log_admin("[key_name(usr)] started an alien infestation.") - src.makeAliens() - if("deathsquad") - message_admins("[key_name(usr)] is creating a death squad...") - if(src.makeDeathsquad()) - message_admins("[key_name(usr)] created a death squad.") - log_admin("[key_name(usr)] created a death squad.") - else - message_admins("[key_name_admin(usr)] tried to create a death squad. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a death squad.") - if("blob") - var/strength = input("Set Blob Resource Gain Rate","Set Resource Rate",1) as num|null - if(!strength) - return - message_admins("[key_name(usr)] spawned a blob with base resource gain [strength].") - log_admin("[key_name(usr)] spawned a blob with base resource gain [strength].") - new/datum/round_event/ghost_role/blob(TRUE, strength) - if("centcom") - message_admins("[key_name(usr)] is creating a CentCom response team...") - if(src.makeEmergencyresponseteam()) - message_admins("[key_name(usr)] created a CentCom response team.") - log_admin("[key_name(usr)] created a CentCom response team.") - else - message_admins("[key_name_admin(usr)] tried to create a CentCom response team. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a CentCom response team.") - if("abductors") - message_admins("[key_name(usr)] is creating an abductor team...") - if(src.makeAbductorTeam()) - message_admins("[key_name(usr)] created an abductor team.") - log_admin("[key_name(usr)] created an abductor team.") - else - message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunately there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create an abductor team.") - if("revenant") - if(src.makeRevenant()) - message_admins("[key_name(usr)] created a revenant.") - log_admin("[key_name(usr)] created a revenant.") - else - message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a revenant.") - - else if(href_list["forceevent"]) - if(!check_rights(R_FUN)) - return - var/datum/round_event_control/E = locate(href_list["forceevent"]) in SSevents.control - if(E) - E.admin_setup(usr) - var/datum/round_event/event = E.runEvent() - if(event.announceWhen>0) - event.processing = FALSE - var/prompt = alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel") - switch(prompt) - if("Yes") - event.announceChance = 100 - if("Cancel") - event.kill() - return - if("No") - event.announceChance = 0 - event.processing = TRUE - message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])") - log_admin("[key_name(usr)] has triggered an event. ([E.name])") - return - - else if(href_list["editrightsbrowser"]) - edit_admin_permissions(0) - - else if(href_list["editrightsbrowserlog"]) - edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"]) - - if(href_list["editrightsbrowsermanage"]) - if(href_list["editrightschange"]) - change_admin_rank(ckey(href_list["editrightschange"]), href_list["editrightschange"], TRUE) - else if(href_list["editrightsremove"]) - remove_admin(ckey(href_list["editrightsremove"]), href_list["editrightsremove"], TRUE) - else if(href_list["editrightsremoverank"]) - remove_rank(href_list["editrightsremoverank"]) - edit_admin_permissions(2) - - else if(href_list["editrights"]) - edit_rights_topic(href_list) - - else if(href_list["gamemode_panel"]) - if(!check_rights(R_ADMIN)) - return - SSticker.mode.admin_panel() - - else if(href_list["call_shuttle"]) - if(!check_rights(R_ADMIN)) - return - - - switch(href_list["call_shuttle"]) - if("1") - if(EMERGENCY_AT_LEAST_DOCKED) - return - SSshuttle.emergency.request() - log_admin("[key_name(usr)] called the Emergency Shuttle.") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") - - if("2") - if(EMERGENCY_AT_LEAST_DOCKED) - return - switch(SSshuttle.emergency.mode) - if(SHUTTLE_CALL) - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] sent the Emergency Shuttle back.") - message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back.") - else - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] called the Emergency Shuttle.") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") - - - - else if(href_list["edit_shuttle_time"]) - if(!check_rights(R_SERVER)) - return - - var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num|null - if(!timer) - return - SSshuttle.emergency.setTimer(timer*10) - log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") - minor_announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") - message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") - else if(href_list["trigger_centcom_recall"]) - if(!check_rights(R_ADMIN)) - return - - usr.client.trigger_centcom_recall() - - else if(href_list["toggle_continuous"]) - if(!check_rights(R_ADMIN)) - return - var/list/continuous = CONFIG_GET(keyed_list/continuous) - if(!continuous[SSticker.mode.config_tag]) - continuous[SSticker.mode.config_tag] = TRUE - else - continuous[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") - check_antagonists() - - else if(href_list["toggle_midround_antag"]) - if(!check_rights(R_ADMIN)) - return - - var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) - if(!midround_antag[SSticker.mode.config_tag]) - midround_antag[SSticker.mode.config_tag] = TRUE - else - midround_antag[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") - check_antagonists() - - else if(href_list["alter_midround_time_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null - if(!timer) - return - CONFIG_SET(number/midround_antag_time_check, timer) - message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") - check_antagonists() - - else if(href_list["alter_midround_life_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num|null - if(!ratio) - return - CONFIG_SET(number/midround_antag_life_check, ratio / 100) - - message_admins("[key_name_admin(usr)] edited the midround antagonist living crew ratio to [ratio]% alive.") - check_antagonists() - - else if(href_list["toggle_noncontinuous_behavior"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.mode.round_ends_with_antag_death) - SSticker.mode.round_ends_with_antag_death = 1 - else - SSticker.mode.round_ends_with_antag_death = 0 - - message_admins("[key_name_admin(usr)] edited the midround antagonist system to [SSticker.mode.round_ends_with_antag_death ? "end the round" : "continue as extended"] upon failure.") - check_antagonists() - - else if(href_list["delay_round_end"]) - if(!check_rights(R_SERVER)) - return - if(!SSticker.delay_end) - SSticker.admin_delay_notice = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text - if(isnull(SSticker.admin_delay_notice)) - return - else - if(alert(usr, "Really cancel current round end delay? The reason for the current delay is: \"[SSticker.admin_delay_notice]\"", "Undelay round end", "Yes", "No") != "Yes") - return - SSticker.admin_delay_notice = null - SSticker.delay_end = !SSticker.delay_end - var/reason = SSticker.delay_end ? "for reason: [SSticker.admin_delay_notice]" : "."//laziness - var/msg = "[SSticker.delay_end ? "delayed" : "undelayed"] the round end [reason]" - log_admin("[key_name(usr)] [msg]") - message_admins("[key_name_admin(usr)] [msg]") - if(SSticker.ready_for_reboot && !SSticker.delay_end) //we undelayed after standard reboot would occur - SSticker.standard_reboot() - - else if(href_list["end_round"]) - if(!check_rights(R_ADMIN)) - return - - message_admins("[key_name_admin(usr)] is considering ending the round.") - if(alert(usr, "This will end the round, are you SURE you want to do this?", "Confirmation", "Yes", "No") == "Yes") - if(alert(usr, "Final Confirmation: End the round NOW?", "Confirmation", "Yes", "No") == "Yes") - message_admins("[key_name_admin(usr)] has ended the round.") - SSticker.force_ending = 1 //Yeah there we go APC destroyed mission accomplished - return - else - message_admins("[key_name_admin(usr)] decided against ending the round.") - else - message_admins("[key_name_admin(usr)] decided against ending the round.") - - else if(href_list["simplemake"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/M = locate(href_list["mob"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - - var/delmob = TRUE - if(!isobserver(M)) - switch(alert("Delete old mob?","Message","Yes","No","Cancel")) - if("Cancel") - return - if("No") - delmob = FALSE - - log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") - message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") - switch(href_list["simplemake"]) - if("observer") - M.change_mob_type( /mob/dead/observer , null, null, delmob ) - if("drone") - M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) - if("hunter") - M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) - if("queen") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) - if("praetorian") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) - if("sentinel") - M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) - if("larva") - M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) - if("human") - var/posttransformoutfit = usr.client.robust_dress_shop() - if (!posttransformoutfit) - return - var/mob/living/carbon/human/newmob = M.change_mob_type( /mob/living/carbon/human , null, null, delmob ) - if(posttransformoutfit && istype(newmob)) - newmob.equipOutfit(posttransformoutfit) - if("slime") - M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) - if("monkey") - M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) - if("robot") - M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) - if("cat") - M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) - if("runtime") - M.change_mob_type( /mob/living/simple_animal/pet/cat/runtime , null, null, delmob ) - if("corgi") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) - if("ian") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/ian , null, null, delmob ) - if("pug") - M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) - if("crab") - M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) - if("coffee") - M.change_mob_type( /mob/living/simple_animal/crab/coffee , null, null, delmob ) - if("parrot") - M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) - if("polyparrot") - M.change_mob_type( /mob/living/simple_animal/parrot/poly , null, null, delmob ) - if("constructjuggernaut") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/juggernaut , null, null, delmob ) - if("constructartificer") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/artificer , null, null, delmob ) - if("constructwraith") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob ) - if("shade") - M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob ) - - else if(href_list["boot2"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["boot2"]) - if(ismob(M)) - if(!check_if_greater_rights_than(M.client)) - to_chat(usr, "Error: They have more rights than you do.", confidential = TRUE) - return - if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") - return - if(!M) - to_chat(usr, "Error: [M] no longer exists!", confidential = TRUE) - return - if(!M.client) - to_chat(usr, "Error: [M] no longer has a client!", confidential = TRUE) - return - to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", confidential = TRUE) - log_admin("[key_name(usr)] kicked [key_name(M)].") - message_admins("[key_name_admin(usr)] kicked [key_name_admin(M)].") - qdel(M.client) - - else if(href_list["addmessage"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addmessage"] - create_message("message", target_key, secret = 0) - - else if(href_list["addnote"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addnote"] - create_message("note", target_key) - - else if(href_list["addwatch"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addwatch"] - create_message("watchlist entry", target_key, secret = 1) - - else if(href_list["addmemo"]) - if(!check_rights(R_ADMIN)) - return - create_message("memo", secret = 0, browse = 1) - - else if(href_list["addmessageempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("message", secret = 0) - - else if(href_list["addnoteempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("note") - - else if(href_list["addwatchempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("watchlist entry", secret = 1) - - else if(href_list["deletemessage"]) - if(!check_rights(R_ADMIN)) - return - var/safety = alert("Delete message/note?",,"Yes","No"); - if (safety == "Yes") - var/message_id = href_list["deletemessage"] - delete_message(message_id) - - else if(href_list["deletemessageempty"]) - if(!check_rights(R_ADMIN)) - return - var/safety = alert("Delete message/note?",,"Yes","No"); - if (safety == "Yes") - var/message_id = href_list["deletemessageempty"] - delete_message(message_id, browse = TRUE) - - else if(href_list["editmessage"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessage"] - edit_message(message_id) - - else if(href_list["editmessageempty"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageempty"] - edit_message(message_id, browse = 1) - - else if(href_list["editmessageexpiry"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageexpiry"] - edit_message_expiry(message_id) - - else if(href_list["editmessageexpiryempty"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageexpiryempty"] - edit_message_expiry(message_id, browse = 1) - - else if(href_list["editmessageseverity"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageseverity"] - edit_message_severity(message_id) - - else if(href_list["secretmessage"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["secretmessage"] - toggle_message_secrecy(message_id) - - else if(href_list["searchmessages"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["searchmessages"] - browse_messages(index = target) - - else if(href_list["nonalpha"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["nonalpha"] - target = text2num(target) - browse_messages(index = target) - - else if(href_list["showmessages"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["showmessages"] - browse_messages(index = target) - - else if(href_list["showmemo"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("memo") - - else if(href_list["showwatch"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("watchlist entry") - - else if(href_list["showwatchfilter"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("watchlist entry", filter = 1) - - else if(href_list["showmessageckey"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["showmessageckey"] - var/agegate = TRUE - if (href_list["showall"]) - agegate = FALSE - browse_messages(target_ckey = target, agegate = agegate) - - else if(href_list["showmessageckeylinkless"]) - var/target = href_list["showmessageckeylinkless"] - browse_messages(target_ckey = target, linkless = 1) - - else if(href_list["messageedits"]) - if(!check_rights(R_ADMIN)) - return - var/datum/db_query/query_get_message_edits = SSdbcore.NewQuery( - "SELECT edits FROM [format_table_name("messages")] WHERE id = :message_id", - list("message_id" = href_list["messageedits"]) - ) - if(!query_get_message_edits.warn_execute()) - qdel(query_get_message_edits) - return - if(query_get_message_edits.NextRow()) - var/edit_log = query_get_message_edits.item[1] - if(!QDELETED(usr)) - var/datum/browser/browser = new(usr, "Note edits", "Note edits") - browser.set_content(jointext(edit_log, "")) - browser.open() - qdel(query_get_message_edits) - - else if(href_list["mute"]) - if(!check_rights(R_ADMIN)) - return - cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) - - else if(href_list["c_mode"]) - return HandleCMode() - - else if(href_list["f_secret"]) - return HandleFSecret() - - else if(href_list["f_dynamic_roundstart"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) - var/roundstart_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) - var/datum/dynamic_ruleset/roundstart/newrule = new rule() - roundstart_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in sortList(roundstart_rules) - if (added_rule) - GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") - message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) - Game() - - else if(href_list["f_dynamic_roundstart_clear"]) - if(!check_rights(R_ADMIN)) - return - GLOB.dynamic_forced_roundstart_ruleset = list() - Game() - log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") - message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) - - else if(href_list["f_dynamic_roundstart_remove"]) - if(!check_rights(R_ADMIN)) - return - var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) - GLOB.dynamic_forced_roundstart_ruleset -= rule - Game() - log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") - message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) - - else if(href_list["f_dynamic_latejoin"]) - if(!check_rights(R_ADMIN)) - return - if(!SSticker || !SSticker.mode) - return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/latejoin_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin)) - var/datum/dynamic_ruleset/latejoin/newrule = new rule() - latejoin_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in sortList(latejoin_rules) - if (added_rule) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.forced_latejoin_rule = latejoin_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") - message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1) - Game() - - else if(href_list["f_dynamic_latejoin_clear"]) - if(!check_rights(R_ADMIN)) - return - if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.forced_latejoin_rule = null - Game() - log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") - message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1) - - else if(href_list["f_dynamic_midround"]) - if(!check_rights(R_ADMIN)) - return - if(!SSticker || !SSticker.mode) - return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/midround_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/midround)) - var/datum/dynamic_ruleset/midround/newrule = new rule() - midround_rules[newrule.name] = rule - var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in sortList(midround_rules) - if (added_rule) - var/datum/game_mode/dynamic/mode = SSticker.mode - log_admin("[key_name(usr)] executed the [added_rule] ruleset.") - message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1) - mode.picking_specific_rule(midround_rules[added_rule],1) - - else if (href_list["f_dynamic_options"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_centre"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num - if (new_centre < -5 || new_centre > 5) - return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") - message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) - GLOB.dynamic_curve_centre = new_centre - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_width"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num - if (new_width < 0.5 || new_width > 4) - return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") - message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) - GLOB.dynamic_curve_width = new_width - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_midround_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_midround_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_force_extended"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended - log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_no_stacking"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_classic_secret"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret - log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_stacking_limit"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num - log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_forced_threat"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num - if (new_value > 100) - return alert(usr, "The value must be be under 100.", null, null, null, null) - GLOB.dynamic_forced_threat_level = new_value - - log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - dynamic_mode_options(usr) - - else if(href_list["c_mode2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if (SSticker.HasRoundStarted()) - if (askuser(usr, "The game has already started. Would you like to save this as the default mode effective next round?", "Save mode", "Yes", "Cancel", Timeout = null) == 1) - SSticker.save_mode(href_list["c_mode2"]) - HandleCMode() - return - GLOB.master_mode = href_list["c_mode2"] - log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") - message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") - to_chat(world, "The mode is now: [GLOB.master_mode]", confidential = TRUE) - Game() // updates the main game menu - if (askuser(usr, "Would you like to save this as the default mode for the server?", "Save mode", "Yes", "No", Timeout = null) == 1) - SSticker.save_mode(GLOB.master_mode) - HandleCMode() - - else if(href_list["f_secret2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - GLOB.secret_force_mode = href_list["f_secret2"] - log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - Game() // updates the main game menu - HandleFSecret() - - else if(href_list["monkeyone"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - log_admin("[key_name(usr)] attempting to monkeyize [key_name(H)].") - message_admins("[key_name_admin(usr)] attempting to monkeyize [key_name_admin(H)].") - H.monkeyize() - - else if(href_list["humanone"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/monkey/Mo = locate(href_list["humanone"]) - if(!istype(Mo)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/monkey.", confidential = TRUE) - return - - log_admin("[key_name(usr)] attempting to humanize [key_name(Mo)].") - message_admins("[key_name_admin(usr)] attempting to humanize [key_name_admin(Mo)].") - Mo.humanize() - - else if(href_list["corgione"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["corgione"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - log_admin("[key_name(usr)] attempting to corgize [key_name(H)].") - message_admins("[key_name_admin(usr)] attempting to corgize [key_name_admin(H)].") - H.corgize() - - - else if(href_list["forcespeech"]) - if(!check_rights(R_FUN)) - return - - var/mob/M = locate(href_list["forcespeech"]) - if(!ismob(M)) - to_chat(usr, "this can only be used on instances of type /mob.", confidential = TRUE) - - var/speech = input("What will [key_name(M)] say?", "Force speech", "")// Don't need to sanitize, since it does that in say(), we also trust our admins. - if(!speech) - return - M.say(speech, forced = "admin speech") - speech = sanitize(speech) // Nah, we don't trust them - log_admin("[key_name(usr)] forced [key_name(M)] to say: [speech]") - message_admins("[key_name_admin(usr)] forced [key_name_admin(M)] to say: [speech]") - - else if(href_list["sendtoprison"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendtoprison"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - - if(alert(usr, "Send [key_name(M)] to Prison?", "Message", "Yes", "No") != "Yes") - return - - M.forceMove(pick(GLOB.prisonwarp)) - to_chat(M, "You have been sent to Prison!", confidential = TRUE) - - log_admin("[key_name(usr)] has sent [key_name(M)] to Prison!") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to Prison!") - - else if(href_list["sendbacktolobby"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendbacktolobby"]) - - if(!isobserver(M)) - to_chat(usr, "You can only send ghost players back to the Lobby.", confidential = TRUE) - return - - if(!M.client) - to_chat(usr, "[M] doesn't seem to have an active client.", confidential = TRUE) - return - - if(alert(usr, "Send [key_name(M)] back to Lobby?", "Message", "Yes", "No") != "Yes") - return - - log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") - message_admins("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") - - var/mob/dead/new_player/NP = new() - NP.ckey = M.ckey - qdel(M) - - else if(href_list["tdome1"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdome1"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdome1)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 1)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 1)") - - else if(href_list["tdome2"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdome2"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdome2)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 2)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 2)") - - else if(href_list["tdomeadmin"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdomeadmin"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeadmin)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Admin.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Admin.)") - - else if(href_list["tdomeobserve"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdomeobserve"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - if(ishuman(L)) - var/mob/living/carbon/human/observer = L - observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit/black(observer), ITEM_SLOT_ICLOTHING) - observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), ITEM_SLOT_FEET) - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeobserve)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Observer.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Observer.)") - - else if(href_list["revive"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/living/L = locate(href_list["revive"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - - L.revive(full_heal = TRUE, admin_revive = TRUE) - message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!") - log_admin("[key_name(usr)] healed / Revived [key_name(L)].") - - else if(href_list["makeai"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeai"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") - log_admin("[key_name(usr)] AIized [key_name(H)].") - H.AIize(TRUE, H.client) - - else if(href_list["makealien"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makealien"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_alienize(H) - - else if(href_list["makeslime"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeslime"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_slimeize(H) - - else if(href_list["makeblob"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeblob"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_blobize(H) - - - else if(href_list["makerobot"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makerobot"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_robotize(H) - - else if(href_list["makeanimal"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/M = locate(href_list["makeanimal"]) - if(isnewplayer(M)) - to_chat(usr, "This cannot be used on instances of type /mob/dead/new_player.", confidential = TRUE) - return - - usr.client.cmd_admin_animalize(M) - - else if(href_list["adminplayeropts"]) - var/mob/M = locate(href_list["adminplayeropts"]) - show_player_panel(M) - - else if(href_list["adminplayerobservefollow"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/atom/movable/AM = locate(href_list["adminplayerobservefollow"]) - - var/client/C = usr.client - var/can_ghost = TRUE - if(!isobserver(usr)) - can_ghost = C.admin_ghost() - - if(!can_ghost) - return - var/mob/dead/observer/A = C.mob - A.ManualFollow(AM) - - else if(href_list["admingetmovable"]) - if(!check_rights(R_ADMIN)) - return - - var/atom/movable/AM = locate(href_list["admingetmovable"]) - if(QDELETED(AM)) - return - AM.forceMove(get_turf(usr)) - - else if(href_list["adminplayerobservecoodjump"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/x = text2num(href_list["X"]) - var/y = text2num(href_list["Y"]) - var/z = text2num(href_list["Z"]) - - var/client/C = usr.client - if(!isobserver(usr)) - C.admin_ghost() - sleep(2) - C.jumptocoord(x,y,z) - - else if(href_list["adminchecklaws"]) - if(!check_rights(R_ADMIN)) - return - output_ai_laws() - - else if(href_list["admincheckdevilinfo"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["admincheckdevilinfo"]) - output_devil_info(M) - - else if(href_list["adminmoreinfo"]) - var/mob/M = locate(href_list["adminmoreinfo"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - - var/location_description = "" - var/special_role_description = "" - var/health_description = "" - var/gender_description = "" - var/turf/T = get_turf(M) - - //Location - if(isturf(T)) - if(isarea(T.loc)) - location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z] in area [T.loc])" - else - location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z])" - - //Job + antagonist - if(M.mind) - special_role_description = "Role: [M.mind.assigned_role]; Antagonist: [M.mind.special_role]" - else - special_role_description = "Role: Mind datum missing Antagonist: Mind datum missing" - - //Health - if(isliving(M)) - var/mob/living/L = M - var/status - switch (M.stat) - if(CONSCIOUS) - status = "Alive" - if(SOFT_CRIT) - status = "Dying" - if(UNCONSCIOUS) - status = "[L.InCritical() ? "Unconscious and Dying" : "Unconscious"]" - if(DEAD) - status = "Dead" - health_description = "Status = [status]" - health_description += "
                Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getOrganLoss(ORGAN_SLOT_BRAIN)] - Stamina: [L.getStaminaLoss()]" - else - health_description = "This mob type has no health to speak of." - - //Gender - switch(M.gender) - if(MALE,FEMALE,PLURAL) - gender_description = "[M.gender]" - else - gender_description = "[M.gender]" - - to_chat(src.owner, "Info about [M.name]: ", confidential = TRUE) - to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]", confidential = TRUE) - to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];", confidential = TRUE) - to_chat(src.owner, "Location = [location_description];", confidential = TRUE) - to_chat(src.owner, "[special_role_description]", confidential = TRUE) - to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M), confidential = TRUE) - - else if(href_list["addjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Add = href_list["addjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Add) - job.total_positions += 1 - break - - src.manage_free_slots() - - - else if(href_list["customjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Add = href_list["customjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Add) - var/newtime = null - newtime = input(usr, "How many jebs do you want?", "Add wanted posters", "[newtime]") as num|null - if(!newtime) - to_chat(src.owner, "Setting to amount of positions filled for the job", confidential = TRUE) - job.total_positions = job.current_positions - break - job.total_positions = newtime - - src.manage_free_slots() - - else if(href_list["removejobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Remove = href_list["removejobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Remove && job.total_positions - job.current_positions > 0) - job.total_positions -= 1 - break - - src.manage_free_slots() - - else if(href_list["unlimitjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Unlimit = href_list["unlimitjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Unlimit) - job.total_positions = -1 - break - - src.manage_free_slots() - - else if(href_list["limitjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Limit = href_list["limitjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Limit) - job.total_positions = job.current_positions - break - - src.manage_free_slots() - - - else if(href_list["adminspawncookie"]) - if(!check_rights(R_ADMIN|R_FUN)) - return - - var/mob/living/carbon/human/H = locate(href_list["adminspawncookie"]) - if(!ishuman(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - //let's keep it simple - //milk to plasmemes and skeletons, meat to lizards, electricity bars to ethereals, cookies to everyone else - var/cookiealt = /obj/item/reagent_containers/food/snacks/cookie - if(isskeleton(H)) - cookiealt = /obj/item/reagent_containers/food/condiment/milk - else if(isplasmaman(H)) - cookiealt = /obj/item/reagent_containers/food/condiment/milk - else if(isethereal(H)) - cookiealt = /obj/item/reagent_containers/food/snacks/energybar - else if(islizard(H)) - cookiealt = /obj/item/reagent_containers/food/snacks/meat/slab - var/obj/item/new_item = new cookiealt(H) - if(H.put_in_hands(new_item)) - H.update_inv_hands() - else - qdel(new_item) - log_admin("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") - return - - log_admin("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") - SSblackbox.record_feedback("amount", "admin_cookies_spawned", 1) - to_chat(H, "Your prayers have been answered!! You received the best [new_item.name]!", confidential = TRUE) - SEND_SOUND(H, sound('sound/effects/pray_chaplain.ogg')) - - else if(href_list["adminsmite"]) - if(!check_rights(R_ADMIN|R_FUN)) - return - - var/mob/living/carbon/human/H = locate(href_list["adminsmite"]) in GLOB.mob_list - if(!H || !istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) - return - - usr.client.smite(H) - - else if(href_list["CentComReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["CentComReply"]) - usr.client.admin_headset_message(M, RADIO_CHANNEL_CENTCOM) - - else if(href_list["SyndicateReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["SyndicateReply"]) - usr.client.admin_headset_message(M, RADIO_CHANNEL_SYNDICATE) - - else if(href_list["HeadsetMessage"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["HeadsetMessage"]) - usr.client.admin_headset_message(M) - - else if(href_list["reject_custom_name"]) - if(!check_rights(R_ADMIN)) - return - var/obj/item/station_charter/charter = locate(href_list["reject_custom_name"]) - if(istype(charter)) - charter.reject_proposed(usr) - else if(href_list["jumpto"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["jumpto"]) - usr.client.jumptomob(M) - - else if(href_list["getmob"]) - if(!check_rights(R_ADMIN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - var/mob/M = locate(href_list["getmob"]) - usr.client.Getmob(M) - - else if(href_list["sendmob"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendmob"]) - usr.client.sendmob(M) - - else if(href_list["narrateto"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["narrateto"]) - usr.client.cmd_admin_direct_narrate(M) - - else if(href_list["subtlemessage"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["subtlemessage"]) - usr.client.cmd_admin_subtle_message(M) - - else if(href_list["playsoundto"]) - if(!check_rights(R_SOUND)) - return - - var/mob/M = locate(href_list["playsoundto"]) - var/S = input("", "Select a sound file",) as null|sound - if(S) - usr.client.play_direct_mob_sound(S, M) - - else if(href_list["individuallog"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["individuallog"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - - show_individual_logging_panel(M, href_list["log_src"], href_list["log_type"]) - else if(href_list["languagemenu"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["languagemenu"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) - - else if(href_list["traitor"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - return - - var/mob/M = locate(href_list["traitor"]) - if(!ismob(M)) - var/datum/mind/D = M - if(!istype(D)) - to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) - return - else - D.traitor_panel() - else - show_traitor_panel(M) - - else if(href_list["skill"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - return - - var/target = locate(href_list["skill"]) - var/datum/mind/target_mind - if(ismob(target)) - var/mob/target_mob = target - target_mind = target_mob.mind - else if (istype(target, /datum/mind)) - target_mind = target - else - to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) - return - show_skill_panel(target_mind) - - else if(href_list["borgpanel"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["borgpanel"]) - if(!iscyborg(M)) - to_chat(usr, "This can only be used on cyborgs", confidential = TRUE) - else - open_borgopanel(M) - - else if(href_list["initmind"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["initmind"]) - if(!ismob(M) || M.mind) - to_chat(usr, "This can only be used on instances on mindless mobs", confidential = TRUE) - return - M.mind_initialize() - - else if(href_list["create_object"]) - if(!check_rights(R_SPAWN)) - return - return create_object(usr) - - else if(href_list["quick_create_object"]) - if(!check_rights(R_SPAWN)) - return - return quick_create_object(usr) - - else if(href_list["create_turf"]) - if(!check_rights(R_SPAWN)) - return - return create_turf(usr) - - else if(href_list["create_mob"]) - if(!check_rights(R_SPAWN)) - return - return create_mob(usr) - - else if(href_list["dupe_marked_datum"]) - if(!check_rights(R_SPAWN)) - return - return DuplicateObject(marked_datum, perfectcopy=1, newloc=get_turf(usr)) - - else if(href_list["object_list"]) //this is the laggiest thing ever - if(!check_rights(R_SPAWN)) - return - - var/atom/loc = usr.loc - - var/dirty_paths - if (istext(href_list["object_list"])) - dirty_paths = list(href_list["object_list"]) - else if (istype(href_list["object_list"], /list)) - dirty_paths = href_list["object_list"] - - var/paths = list() - - for(var/dirty_path in dirty_paths) - var/path = text2path(dirty_path) - if(!path) - continue - else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob)) - continue - paths += path - - if(!paths) - alert("The path list you sent is empty.") - return - if(length(paths) > 5) - alert("Select fewer object types, (max 5).") - return - - var/list/offset = splittext(href_list["offset"],",") - var/number = clamp(text2num(href_list["object_count"]), 1, ADMIN_SPAWN_CAP) - var/X = offset.len > 0 ? text2num(offset[1]) : 0 - var/Y = offset.len > 1 ? text2num(offset[2]) : 0 - var/Z = offset.len > 2 ? text2num(offset[3]) : 0 - var/obj_dir = text2num(href_list["object_dir"]) - if(obj_dir && !(obj_dir in list(1,2,4,8,5,6,9,10))) - obj_dir = null - var/obj_name = sanitize(href_list["object_name"]) - - - var/atom/target //Where the object will be spawned - var/where = href_list["object_where"] - if (!( where in list("onfloor","frompod","inhand","inmarked") )) - where = "onfloor" - - - switch(where) - if("inhand") - if (!iscarbon(usr) && !iscyborg(usr)) - to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.", confidential = TRUE) - where = "onfloor" - target = usr - - if("onfloor", "frompod") - switch(href_list["offset_type"]) - if ("absolute") - target = locate(0 + X,0 + Y,0 + Z) - if ("relative") - target = locate(loc.x + X,loc.y + Y,loc.z + Z) - if("inmarked") - if(!marked_datum) - to_chat(usr, "You don't have any object marked. Abandoning spawn.", confidential = TRUE) - return - else if(!istype(marked_datum, /atom)) - to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.", confidential = TRUE) - return - else - target = marked_datum - - var/obj/structure/closet/supplypod/centcompod/pod - - if(target) - if(where == "frompod") - pod = new() - - for (var/path in paths) - for (var/i = 0; i < number; i++) - if(path in typesof(/turf)) - var/turf/O = target - var/turf/N = O.ChangeTurf(path) - if(N && obj_name) - N.name = obj_name - else - var/atom/O - if(where == "frompod") - O = new path(pod) - else - O = new path(target) - - if(!QDELETED(O)) - O.flags_1 |= ADMIN_SPAWNED_1 - if(obj_dir) - O.setDir(obj_dir) - if(obj_name) - O.name = obj_name - if(ismob(O)) - var/mob/M = O - M.real_name = obj_name - if(where == "inhand" && isliving(usr) && isitem(O)) - var/mob/living/L = usr - var/obj/item/I = O - L.put_in_hands(I) - if(iscyborg(L)) - var/mob/living/silicon/robot/R = L - if(R.module) - R.module.add_module(I, TRUE, TRUE) - R.activate_module(I) - - if(pod) - new /obj/effect/pod_landingzone(target, pod) - - if (number == 1) - log_admin("[key_name(usr)] created a [english_list(paths)]") - for(var/path in paths) - if(ispath(path, /mob)) - message_admins("[key_name_admin(usr)] created a [english_list(paths)]") - break - else - log_admin("[key_name(usr)] created [number]ea [english_list(paths)]") - for(var/path in paths) - if(ispath(path, /mob)) - message_admins("[key_name_admin(usr)] created [number]ea [english_list(paths)]") - break - return - - else if(href_list["secrets"]) - Secrets_topic(href_list["secrets"],href_list) - - else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen = 18 //The ac_ prefix before the hrefs stands for AdminCaster. - src.access_news_network() - - else if(href_list["ac_set_channel_name"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_channel.channel_name = stripped_input(usr, "Provide a Feed Channel Name.", "Network Channel Handler", "") - src.access_news_network() - - else if(href_list["ac_set_channel_lock"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked - src.access_news_network() - - else if(href_list["ac_submit_new_channel"]) - if(!check_rights(R_ADMIN)) - return - var/check = 0 - for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == src.admincaster_feed_channel.channel_name) - check = 1 - break - if(src.admincaster_feed_channel.channel_name == "" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]" || check ) - src.admincaster_screen=7 - else - var/choice = alert("Please confirm Feed channel creation.","Network Channel Handler","Confirm","Cancel") - if(choice=="Confirm") - GLOB.news_network.CreateFeedChannel(src.admincaster_feed_channel.channel_name, src.admin_signature, src.admincaster_feed_channel.locked, 1) - SSblackbox.record_feedback("tally", "newscaster_channels", 1, src.admincaster_feed_channel.channel_name) - log_admin("[key_name(usr)] created command feed channel: [src.admincaster_feed_channel.channel_name]!") - src.admincaster_screen=5 - src.access_news_network() - - else if(href_list["ac_set_channel_receiving"]) - if(!check_rights(R_ADMIN)) - return - var/list/available_channels = list() - for(var/datum/newscaster/feed_channel/F in GLOB.news_network.network_channels) - available_channels += F.channel_name - src.admincaster_feed_channel.channel_name = adminscrub(input(usr, "Choose receiving Feed Channel.", "Network Channel Handler") in sortList(available_channels) ) - src.access_news_network() - - else if(href_list["ac_set_new_message"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_message.body = adminscrub(stripped_input(usr, "Write your Feed story.", "Network Channel Handler", "")) - src.access_news_network() - - else if(href_list["ac_submit_new_message"]) - if(!check_rights(R_ADMIN)) - return - if(src.admincaster_feed_message.returnBody(-1) =="" || src.admincaster_feed_message.returnBody(-1) =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) - src.admincaster_screen = 6 - else - GLOB.news_network.SubmitArticle(src.admincaster_feed_message.returnBody(-1), src.admin_signature, src.admincaster_feed_channel.channel_name, null, 1) - SSblackbox.record_feedback("amount", "newscaster_stories", 1) - src.admincaster_screen=4 - - for(var/obj/machinery/newscaster/NEWSCASTER in GLOB.allCasters) - NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) - - log_admin("[key_name(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") - src.access_news_network() - - else if(href_list["ac_create_channel"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=2 - src.access_news_network() - - else if(href_list["ac_create_feed_story"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=3 - src.access_news_network() - - else if(href_list["ac_menu_censor_story"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=10 - src.access_news_network() - - else if(href_list["ac_menu_censor_channel"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=11 - src.access_news_network() - - else if(href_list["ac_menu_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/already_wanted = 0 - if(GLOB.news_network.wanted_issue.active) - already_wanted = 1 - - if(already_wanted) - src.admincaster_wanted_message.criminal = GLOB.news_network.wanted_issue.criminal - src.admincaster_wanted_message.body = GLOB.news_network.wanted_issue.body - src.admincaster_screen = 14 - src.access_news_network() - - else if(href_list["ac_set_wanted_name"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_wanted_message.criminal = adminscrub(stripped_input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) - src.access_news_network() - - else if(href_list["ac_set_wanted_desc"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_wanted_message.body = adminscrub(stripped_input(usr, "Provide the a description of the Wanted person and any other details you deem important.", "Network Security Handler", "")) - src.access_news_network() - - else if(href_list["ac_submit_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/input_param = text2num(href_list["ac_submit_wanted"]) - if(src.admincaster_wanted_message.criminal == "" || src.admincaster_wanted_message.body == "") - src.admincaster_screen = 16 - else - var/choice = alert("Please confirm Wanted Issue [(input_param==1) ? ("creation.") : ("edit.")]","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - if(input_param==1) //If input_param == 1 we're submitting a new wanted issue. At 2 we're just editing an existing one. See the else below - GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature, null, 1, 1) - src.admincaster_screen = 15 - else - GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature) - src.admincaster_screen = 19 - log_admin("[key_name(usr)] issued a Station-wide Wanted Notification for [src.admincaster_wanted_message.criminal]!") - src.access_news_network() - - else if(href_list["ac_cancel_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/choice = alert("Please confirm Wanted Issue removal.","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - GLOB.news_network.deleteWanted() - src.admincaster_screen=17 - src.access_news_network() - - else if(href_list["ac_censor_channel_author"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) - FC.toggleCensorAuthor() - src.access_news_network() - - else if(href_list["ac_censor_channel_story_author"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) - MSG.toggleCensorAuthor() - src.access_news_network() - - else if(href_list["ac_censor_channel_story_body"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) - MSG.toggleCensorBody() - src.access_news_network() - - else if(href_list["ac_pick_d_notice"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_d_notice"]) - src.admincaster_feed_channel = FC - src.admincaster_screen=13 - src.access_news_network() - - else if(href_list["ac_toggle_d_notice"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) - FC.toggleCensorDclass() - src.access_news_network() - - else if(href_list["ac_view"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=1 - src.access_news_network() - - else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen = text2num(href_list["ac_setScreen"]) - if (src.admincaster_screen == 0) - if(src.admincaster_feed_channel) - src.admincaster_feed_channel = new /datum/newscaster/feed_channel - if(src.admincaster_feed_message) - src.admincaster_feed_message = new /datum/newscaster/feed_message - if(admincaster_wanted_message) - admincaster_wanted_message = new /datum/newscaster/wanted_message - src.access_news_network() - - else if(href_list["ac_show_channel"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_show_channel"]) - src.admincaster_feed_channel = FC - src.admincaster_screen = 9 - src.access_news_network() - - else if(href_list["ac_pick_censor_channel"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_censor_channel"]) - src.admincaster_feed_channel = FC - src.admincaster_screen = 12 - src.access_news_network() - - else if(href_list["ac_refresh"]) - if(!check_rights(R_ADMIN)) - return - src.access_news_network() - - else if(href_list["ac_set_signature"]) - if(!check_rights(R_ADMIN)) - return - src.admin_signature = adminscrub(input(usr, "Provide your desired signature.", "Network Identity Handler", "")) - src.access_news_network() - - else if(href_list["ac_del_comment"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_comment/FC = locate(href_list["ac_del_comment"]) - var/datum/newscaster/feed_message/FM = locate(href_list["ac_del_comment_msg"]) - FM.comments -= FC - qdel(FC) - src.access_news_network() - - else if(href_list["ac_lock_comment"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/FM = locate(href_list["ac_lock_comment"]) - FM.locked ^= 1 - src.access_news_network() - - else if(href_list["check_antagonist"]) - if(!check_rights(R_ADMIN)) - return - usr.client.check_antagonists() - - else if(href_list["kick_all_from_lobby"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker.IsRoundInProgress()) - var/afkonly = text2num(href_list["afkonly"]) - if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby??","Message","Yes","Cancel") != "Yes") - to_chat(usr, "Kick clients from lobby aborted", confidential = TRUE) - return - var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", afkonly) - - var/strkicked = "" - for(var/name in listkicked) - strkicked += "[name], " - message_admins("[key_name_admin(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") - log_admin("[key_name(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") - else - to_chat(usr, "You may only use this when the game is running.", confidential = TRUE) - - else if(href_list["create_outfit_finalize"]) - if(!check_rights(R_ADMIN)) - return - create_outfit_finalize(usr,href_list) - else if(href_list["load_outfit"]) - if(!check_rights(R_ADMIN)) - return - load_outfit(usr) - else if(href_list["create_outfit_menu"]) - if(!check_rights(R_ADMIN)) - return - create_outfit(usr) - else if(href_list["delete_outfit"]) - if(!check_rights(R_ADMIN)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - delete_outfit(usr,O) - else if(href_list["save_outfit"]) - if(!check_rights(R_ADMIN)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - save_outfit(usr,O) - else if(href_list["set_selfdestruct_code"]) - if(!check_rights(R_ADMIN)) - return - var/code = random_nukecode() - for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) - SD.r_code = code - message_admins("[key_name_admin(usr)] has set the self-destruct \ - code to \"[code]\".") - - else if(href_list["add_station_goal"]) - if(!check_rights(R_ADMIN)) - return - var/list/type_choices = typesof(/datum/station_goal) - var/picked = input("Choose goal type") in type_choices|null - if(!picked) - return - var/datum/station_goal/G = new picked() - if(picked == /datum/station_goal) - var/newname = input("Enter goal name:") as text|null - if(!newname) - return - G.name = newname - var/description = input("Enter CentCom message contents:") as message|null - if(!description) - return - G.report_message = description - message_admins("[key_name(usr)] created \"[G.name]\" station goal.") - SSticker.mode.station_goals += G - modify_goals() - - else if(href_list["viewruntime"]) - var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) - if(!istype(error_viewer)) - to_chat(usr, "That runtime viewer no longer exists.", confidential = TRUE) - return - - if(href_list["viewruntime_backto"]) - error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) - else - error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) - - else if(href_list["showrelatedacc"]) - if(!check_rights(R_ADMIN)) - return - var/client/C = locate(href_list["client"]) in GLOB.clients - var/thing_to_check - if(href_list["showrelatedacc"] == "cid") - thing_to_check = C.related_accounts_cid - else - thing_to_check = C.related_accounts_ip - thing_to_check = splittext(thing_to_check, ", ") - - - var/list/dat = list("Related accounts by [uppertext(href_list["showrelatedacc"])]:") - dat += thing_to_check - - usr << browse(dat.Join("
                "), "window=related_[C];size=420x300") - - else if(href_list["modantagrep"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["mob"]) in GLOB.mob_list - var/client/C = M.client - usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) - show_player_panel(M) - - else if(href_list["slowquery"]) - if(!check_rights(R_ADMIN)) - return - var/answer = href_list["slowquery"] - if(answer == "yes") - log_query_debug("[usr.key] | Reported a server hang") - if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") - var/response = input(usr,"What were you just doing?","Query server hang report") as null|text - if(response) - log_query_debug("[usr.key] | [response]") - else if(answer == "no") - log_query_debug("[usr.key] | Reported no server hang") - - else if(href_list["ctf_toggle"]) - if(!check_rights(R_ADMIN)) - return - toggle_all_ctf(usr) - - else if(href_list["rebootworld"]) - if(!check_rights(R_ADMIN)) - return - var/confirm = alert("Are you sure you want to reboot the server?", "Confirm Reboot", "Yes", "No") - if(confirm == "No") - return - if(confirm == "Yes") - restart() - - else if(href_list["check_teams"]) - if(!check_rights(R_ADMIN)) - return - check_teams() - - else if(href_list["team_command"]) - if(!check_rights(R_ADMIN)) - return - switch(href_list["team_command"]) - if("create_team") - admin_create_team(usr) - if("rename_team") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_rename(usr) - if("communicate") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_communicate(usr) - if("delete_team") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_delete(usr) - if("add_objective") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_add_objective(usr) - if("remove_objective") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(!T) - return - var/datum/objective/O = locate(href_list["tobjective"]) in T.objectives - if(O) - T.admin_remove_objective(usr,O) - if("add_member") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_add_member(usr) - if("remove_member") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(!T) - return - var/datum/mind/M = locate(href_list["tmember"]) in T.members - if(M) - T.admin_remove_member(usr,M) - check_teams() - - else if(href_list["newbankey"]) - var/player_key = href_list["newbankey"] - var/player_ip = href_list["newbanip"] - var/player_cid = href_list["newbancid"] - ban_panel(player_key, player_ip, player_cid) - - else if(href_list["intervaltype"]) //check for ban panel, intervaltype is used as it's the only value which will always be present - if(href_list["roleban_delimiter"]) - ban_parse_href(href_list) - else - ban_parse_href(href_list, TRUE) - - else if(href_list["searchunbankey"] || href_list["searchunbanadminkey"] || href_list["searchunbanip"] || href_list["searchunbancid"]) - var/player_key = href_list["searchunbankey"] - var/admin_key = href_list["searchunbanadminkey"] - var/player_ip = href_list["searchunbanip"] - var/player_cid = href_list["searchunbancid"] - unban_panel(player_key, admin_key, player_ip, player_cid) - - else if(href_list["unbanpagecount"]) - var/page = href_list["unbanpagecount"] - var/player_key = href_list["unbankey"] - var/admin_key = href_list["unbanadminkey"] - var/player_ip = href_list["unbanip"] - var/player_cid = href_list["unbancid"] - unban_panel(player_key, admin_key, player_ip, player_cid, page) - - else if(href_list["editbanid"]) - var/edit_id = href_list["editbanid"] - var/player_key = href_list["editbankey"] - var/player_ip = href_list["editbanip"] - var/player_cid = href_list["editbancid"] - var/role = href_list["editbanrole"] - var/duration = href_list["editbanduration"] - var/applies_to_admins = text2num(href_list["editbanadmins"]) - var/reason = url_decode(href_list["editbanreason"]) - var/page = href_list["editbanpage"] - var/admin_key = href_list["editbanadminkey"] - ban_panel(player_key, player_ip, player_cid, role, duration, applies_to_admins, reason, edit_id, page, admin_key) - - else if(href_list["unbanid"]) - var/ban_id = href_list["unbanid"] - var/player_key = href_list["unbankey"] - var/player_ip = href_list["unbanip"] - var/player_cid = href_list["unbancid"] - var/role = href_list["unbanrole"] - var/page = href_list["unbanpage"] - var/admin_key = href_list["unbanadminkey"] - unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key) - - else if(href_list["unbanlog"]) - var/ban_id = href_list["unbanlog"] - ban_log(ban_id) - - else if(href_list["beakerpanel"]) - beaker_panel_act(href_list) - - else if(href_list["reloadpolls"]) - GLOB.polls.Cut() - GLOB.poll_options.Cut() - load_poll_data() - poll_list_panel() - - else if(href_list["newpoll"]) - poll_management_panel() - - else if(href_list["editpoll"]) - var/datum/poll_question/poll = locate(href_list["editpoll"]) in GLOB.polls - poll_management_panel(poll) - - else if(href_list["deletepoll"]) - var/datum/poll_question/poll = locate(href_list["deletepoll"]) in GLOB.polls - poll.delete_poll() - poll_list_panel() - - else if(href_list["initializepoll"]) - poll_parse_href(href_list) - - else if(href_list["submitpoll"]) - var/datum/poll_question/poll = locate(href_list["submitpoll"]) in GLOB.polls - poll_parse_href(href_list, poll) - - else if(href_list["clearpollvotes"]) - var/datum/poll_question/poll = locate(href_list["clearpollvotes"]) in GLOB.polls - poll.clear_poll_votes() - poll_management_panel(poll) - - else if(href_list["addpolloption"]) - var/datum/poll_question/poll = locate(href_list["addpolloption"]) in GLOB.polls - poll_option_panel(poll) - - else if(href_list["editpolloption"]) - var/datum/poll_option/option = locate(href_list["editpolloption"]) in GLOB.poll_options - var/datum/poll_question/poll = locate(href_list["parentpoll"]) in GLOB.polls - poll_option_panel(poll, option) - - else if(href_list["deletepolloption"]) - var/datum/poll_option/option = locate(href_list["deletepolloption"]) in GLOB.poll_options - var/datum/poll_question/poll = option.delete_option() - poll_management_panel(poll) - - else if(href_list["submitoption"]) - var/datum/poll_option/option = locate(href_list["submitoption"]) in GLOB.poll_options - var/datum/poll_question/poll = locate(href_list["submitoptionpoll"]) in GLOB.polls - poll_option_parse_href(href_list, poll, option) - - else if(href_list["admincommend"]) - if(!SSticker.IsRoundInProgress()) - to_chat(usr, "The round must be in progress to use this!") - return - var/mob/heart_recepient = locate(href_list["admincommend"]) - if(tgalert(usr, "Are you sure you'd like to anonymously commend [heart_recepient.ckey]? NOTE: This is logged, please use this sparingly and only for actual kind behavior, not as a reward for your friends.", "<3?", "Yes", "No") == "No") - return - usr.nominate_heart(heart_recepient) - -/datum/admins/proc/HandleCMode() - if(!check_rights(R_ADMIN)) - return - - var/dat = {"What mode do you wish to play?
                "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
                "} - dat += {"Secret
                "} - dat += {"Random
                "} - dat += {"Now: [GLOB.master_mode]"} - usr << browse(dat, "window=c_mode") - -/datum/admins/proc/HandleFSecret() - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
                "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
                "} - dat += {"Random (default)
                "} - dat += {"Now: [GLOB.secret_force_mode]"} - usr << browse(dat, "window=f_secret") +/datum/admins/proc/CheckAdminHref(href, href_list) + var/auth = href_list["admin_token"] + . = auth && (auth == href_token || auth == GLOB.href_token) + if(.) + return + var/msg = !auth ? "no" : "a bad" + message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") + if(CONFIG_GET(flag/debug_admin_hrefs)) + message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") + log_world("UAH: [href]") + return TRUE + log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") + +/datum/admins/Topic(href, href_list) + ..() + + if(usr.client != src.owner || !check_rights(0)) + message_admins("[usr.key] has attempted to override the admin panel!") + log_admin("[key_name(usr)] tried to use the admin panel without authorization.") + return + + if(!CheckAdminHref(href, href_list)) + return + + if(href_list["ahelp"]) + if(!check_rights(R_ADMIN, TRUE)) + return + + var/ahelp_ref = href_list["ahelp"] + var/datum/admin_help/AH = locate(ahelp_ref) + if(AH) + AH.Action(href_list["ahelp_action"]) + else + to_chat(usr, "Ticket [ahelp_ref] has been deleted!", confidential = TRUE) + + else if(href_list["ahelp_tickets"]) + GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"])) + + else if(href_list["stickyban"]) + stickyban(href_list["stickyban"],href_list) + + else if(href_list["getplaytimewindow"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["getplaytimewindow"]) in GLOB.mob_list + if(!M) + to_chat(usr, "ERROR: Mob not found.", confidential = TRUE) + return + cmd_show_exp_panel(M.client) + + else if(href_list["toggleexempt"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["toggleexempt"]) in GLOB.clients + if(!C) + to_chat(usr, "ERROR: Client not found.", confidential = TRUE) + return + toggle_exempt_status(C) + + else if(href_list["makeAntag"]) + if(!check_rights(R_ADMIN)) + return + if (!SSticker.mode) + to_chat(usr, "Not until the round starts!", confidential = TRUE) + return + switch(href_list["makeAntag"]) + if("traitors") + if(src.makeTraitors()) + message_admins("[key_name_admin(usr)] created traitors.") + log_admin("[key_name(usr)] created traitors.") + else + message_admins("[key_name_admin(usr)] tried to create traitors. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create traitors.") + if("changelings") + if(src.makeChangelings()) + message_admins("[key_name(usr)] created changelings.") + log_admin("[key_name(usr)] created changelings.") + else + message_admins("[key_name_admin(usr)] tried to create changelings. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create changelings.") + if("revs") + if(src.makeRevs()) + message_admins("[key_name(usr)] started a revolution.") + log_admin("[key_name(usr)] started a revolution.") + else + message_admins("[key_name_admin(usr)] tried to start a revolution. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a revolution.") + if("cult") + if(src.makeCult()) + message_admins("[key_name(usr)] started a cult.") + log_admin("[key_name(usr)] started a cult.") + else + message_admins("[key_name_admin(usr)] tried to start a cult. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a cult.") + if("wizard") + message_admins("[key_name(usr)] is creating a wizard...") + if(src.makeWizard()) + message_admins("[key_name(usr)] created a wizard.") + log_admin("[key_name(usr)] created a wizard.") + else + message_admins("[key_name_admin(usr)] tried to create a wizard. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a wizard.") + if("nukeops") + message_admins("[key_name(usr)] is creating a nuke team...") + if(src.makeNukeTeam()) + message_admins("[key_name(usr)] created a nuke team.") + log_admin("[key_name(usr)] created a nuke team.") + else + message_admins("[key_name_admin(usr)] tried to create a nuke team. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a nuke team.") + if("ninja") + message_admins("[key_name(usr)] spawned a ninja.") + log_admin("[key_name(usr)] spawned a ninja.") + src.makeSpaceNinja() + if("aliens") + message_admins("[key_name(usr)] started an alien infestation.") + log_admin("[key_name(usr)] started an alien infestation.") + src.makeAliens() + if("deathsquad") + message_admins("[key_name(usr)] is creating a death squad...") + if(src.makeDeathsquad()) + message_admins("[key_name(usr)] created a death squad.") + log_admin("[key_name(usr)] created a death squad.") + else + message_admins("[key_name_admin(usr)] tried to create a death squad. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a death squad.") + if("blob") + var/strength = input("Set Blob Resource Gain Rate","Set Resource Rate",1) as num|null + if(!strength) + return + message_admins("[key_name(usr)] spawned a blob with base resource gain [strength].") + log_admin("[key_name(usr)] spawned a blob with base resource gain [strength].") + new/datum/round_event/ghost_role/blob(TRUE, strength) + if("centcom") + message_admins("[key_name(usr)] is creating a CentCom response team...") + if(src.makeEmergencyresponseteam()) + message_admins("[key_name(usr)] created a CentCom response team.") + log_admin("[key_name(usr)] created a CentCom response team.") + else + message_admins("[key_name_admin(usr)] tried to create a CentCom response team. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a CentCom response team.") + if("abductors") + message_admins("[key_name(usr)] is creating an abductor team...") + if(src.makeAbductorTeam()) + message_admins("[key_name(usr)] created an abductor team.") + log_admin("[key_name(usr)] created an abductor team.") + else + message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunately there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create an abductor team.") + if("revenant") + if(src.makeRevenant()) + message_admins("[key_name(usr)] created a revenant.") + log_admin("[key_name(usr)] created a revenant.") + else + message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a revenant.") + + else if(href_list["forceevent"]) + if(!check_rights(R_FUN)) + return + var/datum/round_event_control/E = locate(href_list["forceevent"]) in SSevents.control + if(E) + E.admin_setup(usr) + var/datum/round_event/event = E.runEvent() + if(event.announceWhen>0) + event.processing = FALSE + var/prompt = alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel") + switch(prompt) + if("Yes") + event.announceChance = 100 + if("Cancel") + event.kill() + return + if("No") + event.announceChance = 0 + event.processing = TRUE + message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])") + log_admin("[key_name(usr)] has triggered an event. ([E.name])") + return + + else if(href_list["editrightsbrowser"]) + edit_admin_permissions(0) + + else if(href_list["editrightsbrowserlog"]) + edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"]) + + if(href_list["editrightsbrowsermanage"]) + if(href_list["editrightschange"]) + change_admin_rank(ckey(href_list["editrightschange"]), href_list["editrightschange"], TRUE) + else if(href_list["editrightsremove"]) + remove_admin(ckey(href_list["editrightsremove"]), href_list["editrightsremove"], TRUE) + else if(href_list["editrightsremoverank"]) + remove_rank(href_list["editrightsremoverank"]) + edit_admin_permissions(2) + + else if(href_list["editrights"]) + edit_rights_topic(href_list) + + else if(href_list["gamemode_panel"]) + if(!check_rights(R_ADMIN)) + return + SSticker.mode.admin_panel() + + else if(href_list["call_shuttle"]) + if(!check_rights(R_ADMIN)) + return + + + switch(href_list["call_shuttle"]) + if("1") + if(EMERGENCY_AT_LEAST_DOCKED) + return + SSshuttle.emergency.request() + log_admin("[key_name(usr)] called the Emergency Shuttle.") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") + + if("2") + if(EMERGENCY_AT_LEAST_DOCKED) + return + switch(SSshuttle.emergency.mode) + if(SHUTTLE_CALL) + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] sent the Emergency Shuttle back.") + message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back.") + else + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] called the Emergency Shuttle.") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") + + + + else if(href_list["edit_shuttle_time"]) + if(!check_rights(R_SERVER)) + return + + var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num|null + if(!timer) + return + SSshuttle.emergency.setTimer(timer*10) + log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") + minor_announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") + message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") + else if(href_list["trigger_centcom_recall"]) + if(!check_rights(R_ADMIN)) + return + + usr.client.trigger_centcom_recall() + + else if(href_list["toggle_continuous"]) + if(!check_rights(R_ADMIN)) + return + var/list/continuous = CONFIG_GET(keyed_list/continuous) + if(!continuous[SSticker.mode.config_tag]) + continuous[SSticker.mode.config_tag] = TRUE + else + continuous[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") + check_antagonists() + + else if(href_list["toggle_midround_antag"]) + if(!check_rights(R_ADMIN)) + return + + var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) + if(!midround_antag[SSticker.mode.config_tag]) + midround_antag[SSticker.mode.config_tag] = TRUE + else + midround_antag[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") + check_antagonists() + + else if(href_list["alter_midround_time_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null + if(!timer) + return + CONFIG_SET(number/midround_antag_time_check, timer) + message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") + check_antagonists() + + else if(href_list["alter_midround_life_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num|null + if(!ratio) + return + CONFIG_SET(number/midround_antag_life_check, ratio / 100) + + message_admins("[key_name_admin(usr)] edited the midround antagonist living crew ratio to [ratio]% alive.") + check_antagonists() + + else if(href_list["toggle_noncontinuous_behavior"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.mode.round_ends_with_antag_death) + SSticker.mode.round_ends_with_antag_death = 1 + else + SSticker.mode.round_ends_with_antag_death = 0 + + message_admins("[key_name_admin(usr)] edited the midround antagonist system to [SSticker.mode.round_ends_with_antag_death ? "end the round" : "continue as extended"] upon failure.") + check_antagonists() + + else if(href_list["delay_round_end"]) + if(!check_rights(R_SERVER)) + return + if(!SSticker.delay_end) + SSticker.admin_delay_notice = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text + if(isnull(SSticker.admin_delay_notice)) + return + else + if(alert(usr, "Really cancel current round end delay? The reason for the current delay is: \"[SSticker.admin_delay_notice]\"", "Undelay round end", "Yes", "No") != "Yes") + return + SSticker.admin_delay_notice = null + SSticker.delay_end = !SSticker.delay_end + var/reason = SSticker.delay_end ? "for reason: [SSticker.admin_delay_notice]" : "."//laziness + var/msg = "[SSticker.delay_end ? "delayed" : "undelayed"] the round end [reason]" + log_admin("[key_name(usr)] [msg]") + message_admins("[key_name_admin(usr)] [msg]") + if(SSticker.ready_for_reboot && !SSticker.delay_end) //we undelayed after standard reboot would occur + SSticker.standard_reboot() + + else if(href_list["end_round"]) + if(!check_rights(R_ADMIN)) + return + + message_admins("[key_name_admin(usr)] is considering ending the round.") + if(alert(usr, "This will end the round, are you SURE you want to do this?", "Confirmation", "Yes", "No") == "Yes") + if(alert(usr, "Final Confirmation: End the round NOW?", "Confirmation", "Yes", "No") == "Yes") + message_admins("[key_name_admin(usr)] has ended the round.") + SSticker.force_ending = 1 //Yeah there we go APC destroyed mission accomplished + return + else + message_admins("[key_name_admin(usr)] decided against ending the round.") + else + message_admins("[key_name_admin(usr)] decided against ending the round.") + + else if(href_list["simplemake"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/M = locate(href_list["mob"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + + var/delmob = TRUE + if(!isobserver(M)) + switch(alert("Delete old mob?","Message","Yes","No","Cancel")) + if("Cancel") + return + if("No") + delmob = FALSE + + log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") + message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") + switch(href_list["simplemake"]) + if("observer") + M.change_mob_type( /mob/dead/observer , null, null, delmob ) + if("drone") + M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) + if("hunter") + M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) + if("queen") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) + if("praetorian") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) + if("sentinel") + M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) + if("larva") + M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) + if("human") + var/posttransformoutfit = usr.client.robust_dress_shop() + if (!posttransformoutfit) + return + var/mob/living/carbon/human/newmob = M.change_mob_type( /mob/living/carbon/human , null, null, delmob ) + if(posttransformoutfit && istype(newmob)) + newmob.equipOutfit(posttransformoutfit) + if("slime") + M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) + if("monkey") + M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) + if("robot") + M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) + if("cat") + M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) + if("runtime") + M.change_mob_type( /mob/living/simple_animal/pet/cat/runtime , null, null, delmob ) + if("corgi") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) + if("ian") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/ian , null, null, delmob ) + if("pug") + M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) + if("crab") + M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) + if("coffee") + M.change_mob_type( /mob/living/simple_animal/crab/coffee , null, null, delmob ) + if("parrot") + M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) + if("polyparrot") + M.change_mob_type( /mob/living/simple_animal/parrot/poly , null, null, delmob ) + if("constructjuggernaut") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/juggernaut , null, null, delmob ) + if("constructartificer") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/artificer , null, null, delmob ) + if("constructwraith") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob ) + if("shade") + M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob ) + + else if(href_list["boot2"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["boot2"]) + if(ismob(M)) + if(!check_if_greater_rights_than(M.client)) + to_chat(usr, "Error: They have more rights than you do.", confidential = TRUE) + return + if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") + return + if(!M) + to_chat(usr, "Error: [M] no longer exists!", confidential = TRUE) + return + if(!M.client) + to_chat(usr, "Error: [M] no longer has a client!", confidential = TRUE) + return + to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", confidential = TRUE) + log_admin("[key_name(usr)] kicked [key_name(M)].") + message_admins("[key_name_admin(usr)] kicked [key_name_admin(M)].") + qdel(M.client) + + else if(href_list["addmessage"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addmessage"] + create_message("message", target_key, secret = 0) + + else if(href_list["addnote"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addnote"] + create_message("note", target_key) + + else if(href_list["addwatch"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addwatch"] + create_message("watchlist entry", target_key, secret = 1) + + else if(href_list["addmemo"]) + if(!check_rights(R_ADMIN)) + return + create_message("memo", secret = 0, browse = 1) + + else if(href_list["addmessageempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("message", secret = 0) + + else if(href_list["addnoteempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("note") + + else if(href_list["addwatchempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("watchlist entry", secret = 1) + + else if(href_list["deletemessage"]) + if(!check_rights(R_ADMIN)) + return + var/safety = alert("Delete message/note?",,"Yes","No"); + if (safety == "Yes") + var/message_id = href_list["deletemessage"] + delete_message(message_id) + + else if(href_list["deletemessageempty"]) + if(!check_rights(R_ADMIN)) + return + var/safety = alert("Delete message/note?",,"Yes","No"); + if (safety == "Yes") + var/message_id = href_list["deletemessageempty"] + delete_message(message_id, browse = TRUE) + + else if(href_list["editmessage"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessage"] + edit_message(message_id) + + else if(href_list["editmessageempty"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageempty"] + edit_message(message_id, browse = 1) + + else if(href_list["editmessageexpiry"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageexpiry"] + edit_message_expiry(message_id) + + else if(href_list["editmessageexpiryempty"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageexpiryempty"] + edit_message_expiry(message_id, browse = 1) + + else if(href_list["editmessageseverity"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageseverity"] + edit_message_severity(message_id) + + else if(href_list["secretmessage"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["secretmessage"] + toggle_message_secrecy(message_id) + + else if(href_list["searchmessages"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["searchmessages"] + browse_messages(index = target) + + else if(href_list["nonalpha"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["nonalpha"] + target = text2num(target) + browse_messages(index = target) + + else if(href_list["showmessages"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["showmessages"] + browse_messages(index = target) + + else if(href_list["showmemo"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("memo") + + else if(href_list["showwatch"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("watchlist entry") + + else if(href_list["showwatchfilter"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("watchlist entry", filter = 1) + + else if(href_list["showmessageckey"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["showmessageckey"] + var/agegate = TRUE + if (href_list["showall"]) + agegate = FALSE + browse_messages(target_ckey = target, agegate = agegate) + + else if(href_list["showmessageckeylinkless"]) + var/target = href_list["showmessageckeylinkless"] + browse_messages(target_ckey = target, linkless = 1) + + else if(href_list["messageedits"]) + if(!check_rights(R_ADMIN)) + return + var/datum/db_query/query_get_message_edits = SSdbcore.NewQuery( + "SELECT edits FROM [format_table_name("messages")] WHERE id = :message_id", + list("message_id" = href_list["messageedits"]) + ) + if(!query_get_message_edits.warn_execute()) + qdel(query_get_message_edits) + return + if(query_get_message_edits.NextRow()) + var/edit_log = query_get_message_edits.item[1] + if(!QDELETED(usr)) + var/datum/browser/browser = new(usr, "Note edits", "Note edits") + browser.set_content(jointext(edit_log, "")) + browser.open() + qdel(query_get_message_edits) + + else if(href_list["mute"]) + if(!check_rights(R_ADMIN)) + return + cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) + + else if(href_list["c_mode"]) + return HandleCMode() + + else if(href_list["f_secret"]) + return HandleFSecret() + + else if(href_list["f_dynamic_roundstart"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) + var/roundstart_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) + var/datum/dynamic_ruleset/roundstart/newrule = new rule() + roundstart_rules[newrule.name] = newrule + var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in sortList(roundstart_rules) + if (added_rule) + GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] + log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") + message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) + Game() + + else if(href_list["f_dynamic_roundstart_clear"]) + if(!check_rights(R_ADMIN)) + return + GLOB.dynamic_forced_roundstart_ruleset = list() + Game() + log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") + message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) + + else if(href_list["f_dynamic_roundstart_remove"]) + if(!check_rights(R_ADMIN)) + return + var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) + GLOB.dynamic_forced_roundstart_ruleset -= rule + Game() + log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") + message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) + + else if(href_list["f_dynamic_latejoin"]) + if(!check_rights(R_ADMIN)) + return + if(!SSticker || !SSticker.mode) + return alert(usr, "The game must start first.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/latejoin_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin)) + var/datum/dynamic_ruleset/latejoin/newrule = new rule() + latejoin_rules[newrule.name] = newrule + var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in sortList(latejoin_rules) + if (added_rule) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.forced_latejoin_rule = latejoin_rules[added_rule] + log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") + message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1) + Game() + + else if(href_list["f_dynamic_latejoin_clear"]) + if(!check_rights(R_ADMIN)) + return + if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.forced_latejoin_rule = null + Game() + log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") + message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1) + + else if(href_list["f_dynamic_midround"]) + if(!check_rights(R_ADMIN)) + return + if(!SSticker || !SSticker.mode) + return alert(usr, "The game must start first.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/midround_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/midround)) + var/datum/dynamic_ruleset/midround/newrule = new rule() + midround_rules[newrule.name] = rule + var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in sortList(midround_rules) + if (added_rule) + var/datum/game_mode/dynamic/mode = SSticker.mode + log_admin("[key_name(usr)] executed the [added_rule] ruleset.") + message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1) + mode.picking_specific_rule(midround_rules[added_rule],1) + + else if (href_list["f_dynamic_options"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_centre"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num + if (new_centre < -5 || new_centre > 5) + return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) + + log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") + message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) + GLOB.dynamic_curve_centre = new_centre + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_width"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num + if (new_width < 0.5 || new_width > 4) + return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) + + log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") + message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) + GLOB.dynamic_curve_width = new_width + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_latejoin_min"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num + if(new_min <= 0) + return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) + if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) + return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") + message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) + GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_latejoin_max"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num + if(new_max <= 0) + return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) + if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) + return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") + message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) + GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_midround_min"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num + if(new_min <= 0) + return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) + if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) + return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") + message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) + GLOB.dynamic_midround_delay_min = (new_min MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_midround_max"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num + if(new_max <= 0) + return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) + if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) + return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") + message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) + GLOB.dynamic_midround_delay_max = (new_max MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_force_extended"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended + log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") + message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_no_stacking"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking + log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") + message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_classic_secret"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret + log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") + message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_stacking_limit"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num + log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") + message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_forced_threat"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num + if (new_value > 100) + return alert(usr, "The value must be be under 100.", null, null, null, null) + GLOB.dynamic_forced_threat_level = new_value + + log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") + message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") + dynamic_mode_options(usr) + + else if(href_list["c_mode2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if (SSticker.HasRoundStarted()) + if (askuser(usr, "The game has already started. Would you like to save this as the default mode effective next round?", "Save mode", "Yes", "Cancel", Timeout = null) == 1) + SSticker.save_mode(href_list["c_mode2"]) + HandleCMode() + return + GLOB.master_mode = href_list["c_mode2"] + log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") + message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") + to_chat(world, "The mode is now: [GLOB.master_mode]", confidential = TRUE) + Game() // updates the main game menu + if (askuser(usr, "Would you like to save this as the default mode for the server?", "Save mode", "Yes", "No", Timeout = null) == 1) + SSticker.save_mode(GLOB.master_mode) + HandleCMode() + + else if(href_list["f_secret2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + GLOB.secret_force_mode = href_list["f_secret2"] + log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + Game() // updates the main game menu + HandleFSecret() + + else if(href_list["monkeyone"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + log_admin("[key_name(usr)] attempting to monkeyize [key_name(H)].") + message_admins("[key_name_admin(usr)] attempting to monkeyize [key_name_admin(H)].") + H.monkeyize() + + else if(href_list["humanone"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/monkey/Mo = locate(href_list["humanone"]) + if(!istype(Mo)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/monkey.", confidential = TRUE) + return + + log_admin("[key_name(usr)] attempting to humanize [key_name(Mo)].") + message_admins("[key_name_admin(usr)] attempting to humanize [key_name_admin(Mo)].") + Mo.humanize() + + else if(href_list["corgione"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["corgione"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + log_admin("[key_name(usr)] attempting to corgize [key_name(H)].") + message_admins("[key_name_admin(usr)] attempting to corgize [key_name_admin(H)].") + H.corgize() + + + else if(href_list["forcespeech"]) + if(!check_rights(R_FUN)) + return + + var/mob/M = locate(href_list["forcespeech"]) + if(!ismob(M)) + to_chat(usr, "this can only be used on instances of type /mob.", confidential = TRUE) + + var/speech = input("What will [key_name(M)] say?", "Force speech", "")// Don't need to sanitize, since it does that in say(), we also trust our admins. + if(!speech) + return + M.say(speech, forced = "admin speech") + speech = sanitize(speech) // Nah, we don't trust them + log_admin("[key_name(usr)] forced [key_name(M)] to say: [speech]") + message_admins("[key_name_admin(usr)] forced [key_name_admin(M)] to say: [speech]") + + else if(href_list["sendtoprison"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendtoprison"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + + if(alert(usr, "Send [key_name(M)] to Prison?", "Message", "Yes", "No") != "Yes") + return + + M.forceMove(pick(GLOB.prisonwarp)) + to_chat(M, "You have been sent to Prison!", confidential = TRUE) + + log_admin("[key_name(usr)] has sent [key_name(M)] to Prison!") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to Prison!") + + else if(href_list["sendbacktolobby"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendbacktolobby"]) + + if(!isobserver(M)) + to_chat(usr, "You can only send ghost players back to the Lobby.", confidential = TRUE) + return + + if(!M.client) + to_chat(usr, "[M] doesn't seem to have an active client.", confidential = TRUE) + return + + if(alert(usr, "Send [key_name(M)] back to Lobby?", "Message", "Yes", "No") != "Yes") + return + + log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") + message_admins("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") + + var/mob/dead/new_player/NP = new() + NP.ckey = M.ckey + qdel(M) + + else if(href_list["tdome1"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdome1"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdome1)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 1)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 1)") + + else if(href_list["tdome2"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdome2"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdome2)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 2)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 2)") + + else if(href_list["tdomeadmin"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdomeadmin"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeadmin)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Admin.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Admin.)") + + else if(href_list["tdomeobserve"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdomeobserve"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + if(ishuman(L)) + var/mob/living/carbon/human/observer = L + observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit/black(observer), ITEM_SLOT_ICLOTHING) + observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), ITEM_SLOT_FEET) + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeobserve)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Observer.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Observer.)") + + else if(href_list["revive"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/living/L = locate(href_list["revive"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + + L.revive(full_heal = TRUE, admin_revive = TRUE) + message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!") + log_admin("[key_name(usr)] healed / Revived [key_name(L)].") + + else if(href_list["makeai"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeai"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") + log_admin("[key_name(usr)] AIized [key_name(H)].") + H.AIize(TRUE, H.client) + + else if(href_list["makealien"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makealien"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_alienize(H) + + else if(href_list["makeslime"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeslime"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_slimeize(H) + + else if(href_list["makeblob"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeblob"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_blobize(H) + + + else if(href_list["makerobot"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makerobot"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_robotize(H) + + else if(href_list["makeanimal"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/M = locate(href_list["makeanimal"]) + if(isnewplayer(M)) + to_chat(usr, "This cannot be used on instances of type /mob/dead/new_player.", confidential = TRUE) + return + + usr.client.cmd_admin_animalize(M) + + else if(href_list["adminplayeropts"]) + var/mob/M = locate(href_list["adminplayeropts"]) + show_player_panel(M) + + else if(href_list["adminplayerobservefollow"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/atom/movable/AM = locate(href_list["adminplayerobservefollow"]) + + var/client/C = usr.client + var/can_ghost = TRUE + if(!isobserver(usr)) + can_ghost = C.admin_ghost() + + if(!can_ghost) + return + var/mob/dead/observer/A = C.mob + A.ManualFollow(AM) + + else if(href_list["admingetmovable"]) + if(!check_rights(R_ADMIN)) + return + + var/atom/movable/AM = locate(href_list["admingetmovable"]) + if(QDELETED(AM)) + return + AM.forceMove(get_turf(usr)) + + else if(href_list["adminplayerobservecoodjump"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/x = text2num(href_list["X"]) + var/y = text2num(href_list["Y"]) + var/z = text2num(href_list["Z"]) + + var/client/C = usr.client + if(!isobserver(usr)) + C.admin_ghost() + sleep(2) + C.jumptocoord(x,y,z) + + else if(href_list["adminchecklaws"]) + if(!check_rights(R_ADMIN)) + return + output_ai_laws() + + else if(href_list["admincheckdevilinfo"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["admincheckdevilinfo"]) + output_devil_info(M) + + else if(href_list["adminmoreinfo"]) + var/mob/M = locate(href_list["adminmoreinfo"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + + var/location_description = "" + var/special_role_description = "" + var/health_description = "" + var/gender_description = "" + var/turf/T = get_turf(M) + + //Location + if(isturf(T)) + if(isarea(T.loc)) + location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z] in area [T.loc])" + else + location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z])" + + //Job + antagonist + if(M.mind) + special_role_description = "Role: [M.mind.assigned_role]; Antagonist: [M.mind.special_role]" + else + special_role_description = "Role: Mind datum missing Antagonist: Mind datum missing" + + //Health + if(isliving(M)) + var/mob/living/L = M + var/status + switch (M.stat) + if(CONSCIOUS) + status = "Alive" + if(SOFT_CRIT) + status = "Dying" + if(UNCONSCIOUS) + status = "[L.InCritical() ? "Unconscious and Dying" : "Unconscious"]" + if(DEAD) + status = "Dead" + health_description = "Status = [status]" + health_description += "
                Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getOrganLoss(ORGAN_SLOT_BRAIN)] - Stamina: [L.getStaminaLoss()]" + else + health_description = "This mob type has no health to speak of." + + //Gender + switch(M.gender) + if(MALE,FEMALE,PLURAL) + gender_description = "[M.gender]" + else + gender_description = "[M.gender]" + + to_chat(src.owner, "Info about [M.name]: ", confidential = TRUE) + to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]", confidential = TRUE) + to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];", confidential = TRUE) + to_chat(src.owner, "Location = [location_description];", confidential = TRUE) + to_chat(src.owner, "[special_role_description]", confidential = TRUE) + to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M), confidential = TRUE) + + else if(href_list["addjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Add = href_list["addjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Add) + job.total_positions += 1 + break + + src.manage_free_slots() + + + else if(href_list["customjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Add = href_list["customjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Add) + var/newtime = null + newtime = input(usr, "How many jebs do you want?", "Add wanted posters", "[newtime]") as num|null + if(!newtime) + to_chat(src.owner, "Setting to amount of positions filled for the job", confidential = TRUE) + job.total_positions = job.current_positions + break + job.total_positions = newtime + + src.manage_free_slots() + + else if(href_list["removejobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Remove = href_list["removejobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Remove && job.total_positions - job.current_positions > 0) + job.total_positions -= 1 + break + + src.manage_free_slots() + + else if(href_list["unlimitjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Unlimit = href_list["unlimitjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Unlimit) + job.total_positions = -1 + break + + src.manage_free_slots() + + else if(href_list["limitjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Limit = href_list["limitjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Limit) + job.total_positions = job.current_positions + break + + src.manage_free_slots() + + + else if(href_list["adminspawncookie"]) + if(!check_rights(R_ADMIN|R_FUN)) + return + + var/mob/living/carbon/human/H = locate(href_list["adminspawncookie"]) + if(!ishuman(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + //let's keep it simple + //milk to plasmemes and skeletons, meat to lizards, electricity bars to ethereals, cookies to everyone else + var/cookiealt = /obj/item/reagent_containers/food/snacks/cookie + if(isskeleton(H)) + cookiealt = /obj/item/reagent_containers/food/condiment/milk + else if(isplasmaman(H)) + cookiealt = /obj/item/reagent_containers/food/condiment/milk + else if(isethereal(H)) + cookiealt = /obj/item/reagent_containers/food/snacks/energybar + else if(islizard(H)) + cookiealt = /obj/item/reagent_containers/food/snacks/meat/slab + var/obj/item/new_item = new cookiealt(H) + if(H.put_in_hands(new_item)) + H.update_inv_hands() + else + qdel(new_item) + log_admin("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") + return + + log_admin("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") + SSblackbox.record_feedback("amount", "admin_cookies_spawned", 1) + to_chat(H, "Your prayers have been answered!! You received the best [new_item.name]!", confidential = TRUE) + SEND_SOUND(H, sound('sound/effects/pray_chaplain.ogg')) + + else if(href_list["adminsmite"]) + if(!check_rights(R_ADMIN|R_FUN)) + return + + var/mob/living/carbon/human/H = locate(href_list["adminsmite"]) in GLOB.mob_list + if(!H || !istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) + return + + usr.client.smite(H) + + else if(href_list["CentComReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["CentComReply"]) + usr.client.admin_headset_message(M, RADIO_CHANNEL_CENTCOM) + + else if(href_list["SyndicateReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["SyndicateReply"]) + usr.client.admin_headset_message(M, RADIO_CHANNEL_SYNDICATE) + + else if(href_list["HeadsetMessage"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["HeadsetMessage"]) + usr.client.admin_headset_message(M) + + else if(href_list["reject_custom_name"]) + if(!check_rights(R_ADMIN)) + return + var/obj/item/station_charter/charter = locate(href_list["reject_custom_name"]) + if(istype(charter)) + charter.reject_proposed(usr) + else if(href_list["jumpto"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["jumpto"]) + usr.client.jumptomob(M) + + else if(href_list["getmob"]) + if(!check_rights(R_ADMIN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + var/mob/M = locate(href_list["getmob"]) + usr.client.Getmob(M) + + else if(href_list["sendmob"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendmob"]) + usr.client.sendmob(M) + + else if(href_list["narrateto"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["narrateto"]) + usr.client.cmd_admin_direct_narrate(M) + + else if(href_list["subtlemessage"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["subtlemessage"]) + usr.client.cmd_admin_subtle_message(M) + + else if(href_list["playsoundto"]) + if(!check_rights(R_SOUND)) + return + + var/mob/M = locate(href_list["playsoundto"]) + var/S = input("", "Select a sound file",) as null|sound + if(S) + usr.client.play_direct_mob_sound(S, M) + + else if(href_list["individuallog"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["individuallog"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + + show_individual_logging_panel(M, href_list["log_src"], href_list["log_type"]) + else if(href_list["languagemenu"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["languagemenu"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + var/datum/language_holder/H = M.get_language_holder() + H.open_language_menu(usr) + + else if(href_list["traitor"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + + var/mob/M = locate(href_list["traitor"]) + if(!ismob(M)) + var/datum/mind/D = M + if(!istype(D)) + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + else + D.traitor_panel() + else + show_traitor_panel(M) + + else if(href_list["skill"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + + var/target = locate(href_list["skill"]) + var/datum/mind/target_mind + if(ismob(target)) + var/mob/target_mob = target + target_mind = target_mob.mind + else if (istype(target, /datum/mind)) + target_mind = target + else + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + show_skill_panel(target_mind) + + else if(href_list["borgpanel"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["borgpanel"]) + if(!iscyborg(M)) + to_chat(usr, "This can only be used on cyborgs", confidential = TRUE) + else + open_borgopanel(M) + + else if(href_list["initmind"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["initmind"]) + if(!ismob(M) || M.mind) + to_chat(usr, "This can only be used on instances on mindless mobs", confidential = TRUE) + return + M.mind_initialize() + + else if(href_list["create_object"]) + if(!check_rights(R_SPAWN)) + return + return create_object(usr) + + else if(href_list["quick_create_object"]) + if(!check_rights(R_SPAWN)) + return + return quick_create_object(usr) + + else if(href_list["create_turf"]) + if(!check_rights(R_SPAWN)) + return + return create_turf(usr) + + else if(href_list["create_mob"]) + if(!check_rights(R_SPAWN)) + return + return create_mob(usr) + + else if(href_list["dupe_marked_datum"]) + if(!check_rights(R_SPAWN)) + return + return DuplicateObject(marked_datum, perfectcopy=1, newloc=get_turf(usr)) + + else if(href_list["object_list"]) //this is the laggiest thing ever + if(!check_rights(R_SPAWN)) + return + + var/atom/loc = usr.loc + + var/dirty_paths + if (istext(href_list["object_list"])) + dirty_paths = list(href_list["object_list"]) + else if (istype(href_list["object_list"], /list)) + dirty_paths = href_list["object_list"] + + var/paths = list() + + for(var/dirty_path in dirty_paths) + var/path = text2path(dirty_path) + if(!path) + continue + else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob)) + continue + paths += path + + if(!paths) + alert("The path list you sent is empty.") + return + if(length(paths) > 5) + alert("Select fewer object types, (max 5).") + return + + var/list/offset = splittext(href_list["offset"],",") + var/number = clamp(text2num(href_list["object_count"]), 1, ADMIN_SPAWN_CAP) + var/X = offset.len > 0 ? text2num(offset[1]) : 0 + var/Y = offset.len > 1 ? text2num(offset[2]) : 0 + var/Z = offset.len > 2 ? text2num(offset[3]) : 0 + var/obj_dir = text2num(href_list["object_dir"]) + if(obj_dir && !(obj_dir in list(1,2,4,8,5,6,9,10))) + obj_dir = null + var/obj_name = sanitize(href_list["object_name"]) + + + var/atom/target //Where the object will be spawned + var/where = href_list["object_where"] + if (!( where in list("onfloor","frompod","inhand","inmarked") )) + where = "onfloor" + + + switch(where) + if("inhand") + if (!iscarbon(usr) && !iscyborg(usr)) + to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.", confidential = TRUE) + where = "onfloor" + target = usr + + if("onfloor", "frompod") + switch(href_list["offset_type"]) + if ("absolute") + target = locate(0 + X,0 + Y,0 + Z) + if ("relative") + target = locate(loc.x + X,loc.y + Y,loc.z + Z) + if("inmarked") + if(!marked_datum) + to_chat(usr, "You don't have any object marked. Abandoning spawn.", confidential = TRUE) + return + else if(!istype(marked_datum, /atom)) + to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.", confidential = TRUE) + return + else + target = marked_datum + + var/obj/structure/closet/supplypod/centcompod/pod + + if(target) + if(where == "frompod") + pod = new() + + for (var/path in paths) + for (var/i = 0; i < number; i++) + if(path in typesof(/turf)) + var/turf/O = target + var/turf/N = O.ChangeTurf(path) + if(N && obj_name) + N.name = obj_name + else + var/atom/O + if(where == "frompod") + O = new path(pod) + else + O = new path(target) + + if(!QDELETED(O)) + O.flags_1 |= ADMIN_SPAWNED_1 + if(obj_dir) + O.setDir(obj_dir) + if(obj_name) + O.name = obj_name + if(ismob(O)) + var/mob/M = O + M.real_name = obj_name + if(where == "inhand" && isliving(usr) && isitem(O)) + var/mob/living/L = usr + var/obj/item/I = O + L.put_in_hands(I) + if(iscyborg(L)) + var/mob/living/silicon/robot/R = L + if(R.module) + R.module.add_module(I, TRUE, TRUE) + R.activate_module(I) + + if(pod) + new /obj/effect/pod_landingzone(target, pod) + + if (number == 1) + log_admin("[key_name(usr)] created a [english_list(paths)]") + for(var/path in paths) + if(ispath(path, /mob)) + message_admins("[key_name_admin(usr)] created a [english_list(paths)]") + break + else + log_admin("[key_name(usr)] created [number]ea [english_list(paths)]") + for(var/path in paths) + if(ispath(path, /mob)) + message_admins("[key_name_admin(usr)] created [number]ea [english_list(paths)]") + break + return + + else if(href_list["secrets"]) + Secrets_topic(href_list["secrets"],href_list) + + else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen = 18 //The ac_ prefix before the hrefs stands for AdminCaster. + src.access_news_network() + + else if(href_list["ac_set_channel_name"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_channel.channel_name = stripped_input(usr, "Provide a Feed Channel Name.", "Network Channel Handler", "") + src.access_news_network() + + else if(href_list["ac_set_channel_lock"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked + src.access_news_network() + + else if(href_list["ac_submit_new_channel"]) + if(!check_rights(R_ADMIN)) + return + var/check = 0 + for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) + if(FC.channel_name == src.admincaster_feed_channel.channel_name) + check = 1 + break + if(src.admincaster_feed_channel.channel_name == "" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]" || check ) + src.admincaster_screen=7 + else + var/choice = alert("Please confirm Feed channel creation.","Network Channel Handler","Confirm","Cancel") + if(choice=="Confirm") + GLOB.news_network.CreateFeedChannel(src.admincaster_feed_channel.channel_name, src.admin_signature, src.admincaster_feed_channel.locked, 1) + SSblackbox.record_feedback("tally", "newscaster_channels", 1, src.admincaster_feed_channel.channel_name) + log_admin("[key_name(usr)] created command feed channel: [src.admincaster_feed_channel.channel_name]!") + src.admincaster_screen=5 + src.access_news_network() + + else if(href_list["ac_set_channel_receiving"]) + if(!check_rights(R_ADMIN)) + return + var/list/available_channels = list() + for(var/datum/newscaster/feed_channel/F in GLOB.news_network.network_channels) + available_channels += F.channel_name + src.admincaster_feed_channel.channel_name = adminscrub(input(usr, "Choose receiving Feed Channel.", "Network Channel Handler") in sortList(available_channels) ) + src.access_news_network() + + else if(href_list["ac_set_new_message"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_message.body = adminscrub(stripped_input(usr, "Write your Feed story.", "Network Channel Handler", "")) + src.access_news_network() + + else if(href_list["ac_submit_new_message"]) + if(!check_rights(R_ADMIN)) + return + if(src.admincaster_feed_message.returnBody(-1) =="" || src.admincaster_feed_message.returnBody(-1) =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) + src.admincaster_screen = 6 + else + GLOB.news_network.SubmitArticle(src.admincaster_feed_message.returnBody(-1), src.admin_signature, src.admincaster_feed_channel.channel_name, null, 1) + SSblackbox.record_feedback("amount", "newscaster_stories", 1) + src.admincaster_screen=4 + + for(var/obj/machinery/newscaster/NEWSCASTER in GLOB.allCasters) + NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) + + log_admin("[key_name(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") + src.access_news_network() + + else if(href_list["ac_create_channel"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=2 + src.access_news_network() + + else if(href_list["ac_create_feed_story"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=3 + src.access_news_network() + + else if(href_list["ac_menu_censor_story"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=10 + src.access_news_network() + + else if(href_list["ac_menu_censor_channel"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=11 + src.access_news_network() + + else if(href_list["ac_menu_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/already_wanted = 0 + if(GLOB.news_network.wanted_issue.active) + already_wanted = 1 + + if(already_wanted) + src.admincaster_wanted_message.criminal = GLOB.news_network.wanted_issue.criminal + src.admincaster_wanted_message.body = GLOB.news_network.wanted_issue.body + src.admincaster_screen = 14 + src.access_news_network() + + else if(href_list["ac_set_wanted_name"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_wanted_message.criminal = adminscrub(stripped_input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) + src.access_news_network() + + else if(href_list["ac_set_wanted_desc"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_wanted_message.body = adminscrub(stripped_input(usr, "Provide the a description of the Wanted person and any other details you deem important.", "Network Security Handler", "")) + src.access_news_network() + + else if(href_list["ac_submit_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/input_param = text2num(href_list["ac_submit_wanted"]) + if(src.admincaster_wanted_message.criminal == "" || src.admincaster_wanted_message.body == "") + src.admincaster_screen = 16 + else + var/choice = alert("Please confirm Wanted Issue [(input_param==1) ? ("creation.") : ("edit.")]","Network Security Handler","Confirm","Cancel") + if(choice=="Confirm") + if(input_param==1) //If input_param == 1 we're submitting a new wanted issue. At 2 we're just editing an existing one. See the else below + GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature, null, 1, 1) + src.admincaster_screen = 15 + else + GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature) + src.admincaster_screen = 19 + log_admin("[key_name(usr)] issued a Station-wide Wanted Notification for [src.admincaster_wanted_message.criminal]!") + src.access_news_network() + + else if(href_list["ac_cancel_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/choice = alert("Please confirm Wanted Issue removal.","Network Security Handler","Confirm","Cancel") + if(choice=="Confirm") + GLOB.news_network.deleteWanted() + src.admincaster_screen=17 + src.access_news_network() + + else if(href_list["ac_censor_channel_author"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) + FC.toggleCensorAuthor() + src.access_news_network() + + else if(href_list["ac_censor_channel_story_author"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) + MSG.toggleCensorAuthor() + src.access_news_network() + + else if(href_list["ac_censor_channel_story_body"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) + MSG.toggleCensorBody() + src.access_news_network() + + else if(href_list["ac_pick_d_notice"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_d_notice"]) + src.admincaster_feed_channel = FC + src.admincaster_screen=13 + src.access_news_network() + + else if(href_list["ac_toggle_d_notice"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) + FC.toggleCensorDclass() + src.access_news_network() + + else if(href_list["ac_view"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=1 + src.access_news_network() + + else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen = text2num(href_list["ac_setScreen"]) + if (src.admincaster_screen == 0) + if(src.admincaster_feed_channel) + src.admincaster_feed_channel = new /datum/newscaster/feed_channel + if(src.admincaster_feed_message) + src.admincaster_feed_message = new /datum/newscaster/feed_message + if(admincaster_wanted_message) + admincaster_wanted_message = new /datum/newscaster/wanted_message + src.access_news_network() + + else if(href_list["ac_show_channel"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_show_channel"]) + src.admincaster_feed_channel = FC + src.admincaster_screen = 9 + src.access_news_network() + + else if(href_list["ac_pick_censor_channel"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_censor_channel"]) + src.admincaster_feed_channel = FC + src.admincaster_screen = 12 + src.access_news_network() + + else if(href_list["ac_refresh"]) + if(!check_rights(R_ADMIN)) + return + src.access_news_network() + + else if(href_list["ac_set_signature"]) + if(!check_rights(R_ADMIN)) + return + src.admin_signature = adminscrub(input(usr, "Provide your desired signature.", "Network Identity Handler", "")) + src.access_news_network() + + else if(href_list["ac_del_comment"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_comment/FC = locate(href_list["ac_del_comment"]) + var/datum/newscaster/feed_message/FM = locate(href_list["ac_del_comment_msg"]) + FM.comments -= FC + qdel(FC) + src.access_news_network() + + else if(href_list["ac_lock_comment"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/FM = locate(href_list["ac_lock_comment"]) + FM.locked ^= 1 + src.access_news_network() + + else if(href_list["check_antagonist"]) + if(!check_rights(R_ADMIN)) + return + usr.client.check_antagonists() + + else if(href_list["kick_all_from_lobby"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker.IsRoundInProgress()) + var/afkonly = text2num(href_list["afkonly"]) + if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby??","Message","Yes","Cancel") != "Yes") + to_chat(usr, "Kick clients from lobby aborted", confidential = TRUE) + return + var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", afkonly) + + var/strkicked = "" + for(var/name in listkicked) + strkicked += "[name], " + message_admins("[key_name_admin(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") + log_admin("[key_name(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") + else + to_chat(usr, "You may only use this when the game is running.", confidential = TRUE) + + else if(href_list["create_outfit_finalize"]) + if(!check_rights(R_ADMIN)) + return + create_outfit_finalize(usr,href_list) + else if(href_list["load_outfit"]) + if(!check_rights(R_ADMIN)) + return + load_outfit(usr) + else if(href_list["create_outfit_menu"]) + if(!check_rights(R_ADMIN)) + return + create_outfit(usr) + else if(href_list["delete_outfit"]) + if(!check_rights(R_ADMIN)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + delete_outfit(usr,O) + else if(href_list["save_outfit"]) + if(!check_rights(R_ADMIN)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + save_outfit(usr,O) + else if(href_list["set_selfdestruct_code"]) + if(!check_rights(R_ADMIN)) + return + var/code = random_nukecode() + for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) + SD.r_code = code + message_admins("[key_name_admin(usr)] has set the self-destruct \ + code to \"[code]\".") + + else if(href_list["add_station_goal"]) + if(!check_rights(R_ADMIN)) + return + var/list/type_choices = typesof(/datum/station_goal) + var/picked = input("Choose goal type") in type_choices|null + if(!picked) + return + var/datum/station_goal/G = new picked() + if(picked == /datum/station_goal) + var/newname = input("Enter goal name:") as text|null + if(!newname) + return + G.name = newname + var/description = input("Enter CentCom message contents:") as message|null + if(!description) + return + G.report_message = description + message_admins("[key_name(usr)] created \"[G.name]\" station goal.") + SSticker.mode.station_goals += G + modify_goals() + + else if(href_list["viewruntime"]) + var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) + if(!istype(error_viewer)) + to_chat(usr, "That runtime viewer no longer exists.", confidential = TRUE) + return + + if(href_list["viewruntime_backto"]) + error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) + else + error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) + + else if(href_list["showrelatedacc"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["client"]) in GLOB.clients + var/thing_to_check + if(href_list["showrelatedacc"] == "cid") + thing_to_check = C.related_accounts_cid + else + thing_to_check = C.related_accounts_ip + thing_to_check = splittext(thing_to_check, ", ") + + + var/list/dat = list("Related accounts by [uppertext(href_list["showrelatedacc"])]:") + dat += thing_to_check + + usr << browse(dat.Join("
                "), "window=related_[C];size=420x300") + + else if(href_list["modantagrep"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["mob"]) in GLOB.mob_list + var/client/C = M.client + usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) + show_player_panel(M) + + else if(href_list["slowquery"]) + if(!check_rights(R_ADMIN)) + return + var/answer = href_list["slowquery"] + if(answer == "yes") + log_query_debug("[usr.key] | Reported a server hang") + if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") + var/response = input(usr,"What were you just doing?","Query server hang report") as null|text + if(response) + log_query_debug("[usr.key] | [response]") + else if(answer == "no") + log_query_debug("[usr.key] | Reported no server hang") + + else if(href_list["ctf_toggle"]) + if(!check_rights(R_ADMIN)) + return + toggle_all_ctf(usr) + + else if(href_list["rebootworld"]) + if(!check_rights(R_ADMIN)) + return + var/confirm = alert("Are you sure you want to reboot the server?", "Confirm Reboot", "Yes", "No") + if(confirm == "No") + return + if(confirm == "Yes") + restart() + + else if(href_list["check_teams"]) + if(!check_rights(R_ADMIN)) + return + check_teams() + + else if(href_list["team_command"]) + if(!check_rights(R_ADMIN)) + return + switch(href_list["team_command"]) + if("create_team") + admin_create_team(usr) + if("rename_team") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_rename(usr) + if("communicate") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_communicate(usr) + if("delete_team") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_delete(usr) + if("add_objective") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_add_objective(usr) + if("remove_objective") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(!T) + return + var/datum/objective/O = locate(href_list["tobjective"]) in T.objectives + if(O) + T.admin_remove_objective(usr,O) + if("add_member") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_add_member(usr) + if("remove_member") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(!T) + return + var/datum/mind/M = locate(href_list["tmember"]) in T.members + if(M) + T.admin_remove_member(usr,M) + check_teams() + + else if(href_list["newbankey"]) + var/player_key = href_list["newbankey"] + var/player_ip = href_list["newbanip"] + var/player_cid = href_list["newbancid"] + ban_panel(player_key, player_ip, player_cid) + + else if(href_list["intervaltype"]) //check for ban panel, intervaltype is used as it's the only value which will always be present + if(href_list["roleban_delimiter"]) + ban_parse_href(href_list) + else + ban_parse_href(href_list, TRUE) + + else if(href_list["searchunbankey"] || href_list["searchunbanadminkey"] || href_list["searchunbanip"] || href_list["searchunbancid"]) + var/player_key = href_list["searchunbankey"] + var/admin_key = href_list["searchunbanadminkey"] + var/player_ip = href_list["searchunbanip"] + var/player_cid = href_list["searchunbancid"] + unban_panel(player_key, admin_key, player_ip, player_cid) + + else if(href_list["unbanpagecount"]) + var/page = href_list["unbanpagecount"] + var/player_key = href_list["unbankey"] + var/admin_key = href_list["unbanadminkey"] + var/player_ip = href_list["unbanip"] + var/player_cid = href_list["unbancid"] + unban_panel(player_key, admin_key, player_ip, player_cid, page) + + else if(href_list["editbanid"]) + var/edit_id = href_list["editbanid"] + var/player_key = href_list["editbankey"] + var/player_ip = href_list["editbanip"] + var/player_cid = href_list["editbancid"] + var/role = href_list["editbanrole"] + var/duration = href_list["editbanduration"] + var/applies_to_admins = text2num(href_list["editbanadmins"]) + var/reason = url_decode(href_list["editbanreason"]) + var/page = href_list["editbanpage"] + var/admin_key = href_list["editbanadminkey"] + ban_panel(player_key, player_ip, player_cid, role, duration, applies_to_admins, reason, edit_id, page, admin_key) + + else if(href_list["unbanid"]) + var/ban_id = href_list["unbanid"] + var/player_key = href_list["unbankey"] + var/player_ip = href_list["unbanip"] + var/player_cid = href_list["unbancid"] + var/role = href_list["unbanrole"] + var/page = href_list["unbanpage"] + var/admin_key = href_list["unbanadminkey"] + unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key) + + else if(href_list["unbanlog"]) + var/ban_id = href_list["unbanlog"] + ban_log(ban_id) + + else if(href_list["beakerpanel"]) + beaker_panel_act(href_list) + + else if(href_list["reloadpolls"]) + GLOB.polls.Cut() + GLOB.poll_options.Cut() + load_poll_data() + poll_list_panel() + + else if(href_list["newpoll"]) + poll_management_panel() + + else if(href_list["editpoll"]) + var/datum/poll_question/poll = locate(href_list["editpoll"]) in GLOB.polls + poll_management_panel(poll) + + else if(href_list["deletepoll"]) + var/datum/poll_question/poll = locate(href_list["deletepoll"]) in GLOB.polls + poll.delete_poll() + poll_list_panel() + + else if(href_list["initializepoll"]) + poll_parse_href(href_list) + + else if(href_list["submitpoll"]) + var/datum/poll_question/poll = locate(href_list["submitpoll"]) in GLOB.polls + poll_parse_href(href_list, poll) + + else if(href_list["clearpollvotes"]) + var/datum/poll_question/poll = locate(href_list["clearpollvotes"]) in GLOB.polls + poll.clear_poll_votes() + poll_management_panel(poll) + + else if(href_list["addpolloption"]) + var/datum/poll_question/poll = locate(href_list["addpolloption"]) in GLOB.polls + poll_option_panel(poll) + + else if(href_list["editpolloption"]) + var/datum/poll_option/option = locate(href_list["editpolloption"]) in GLOB.poll_options + var/datum/poll_question/poll = locate(href_list["parentpoll"]) in GLOB.polls + poll_option_panel(poll, option) + + else if(href_list["deletepolloption"]) + var/datum/poll_option/option = locate(href_list["deletepolloption"]) in GLOB.poll_options + var/datum/poll_question/poll = option.delete_option() + poll_management_panel(poll) + + else if(href_list["submitoption"]) + var/datum/poll_option/option = locate(href_list["submitoption"]) in GLOB.poll_options + var/datum/poll_question/poll = locate(href_list["submitoptionpoll"]) in GLOB.polls + poll_option_parse_href(href_list, poll, option) + + else if(href_list["admincommend"]) + if(!SSticker.IsRoundInProgress()) + to_chat(usr, "The round must be in progress to use this!") + return + var/mob/heart_recepient = locate(href_list["admincommend"]) + if(tgalert(usr, "Are you sure you'd like to anonymously commend [heart_recepient.ckey]? NOTE: This is logged, please use this sparingly and only for actual kind behavior, not as a reward for your friends.", "<3?", "Yes", "No") == "No") + return + usr.nominate_heart(heart_recepient) + +/datum/admins/proc/HandleCMode() + if(!check_rights(R_ADMIN)) + return + + var/dat = {"What mode do you wish to play?
                "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
                "} + dat += {"Secret
                "} + dat += {"Random
                "} + dat += {"Now: [GLOB.master_mode]"} + usr << browse(dat, "window=c_mode") + +/datum/admins/proc/HandleFSecret() + if(!check_rights(R_ADMIN)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
                "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
                "} + dat += {"Random (default)
                "} + dat += {"Now: [GLOB.secret_force_mode]"} + usr << browse(dat, "window=f_secret") diff --git a/code/modules/admin/verbs/BrokenInhands.dm b/code/modules/admin/verbs/BrokenInhands.dm index f5ef44f1fa4..ebdeb24ebcb 100644 --- a/code/modules/admin/verbs/BrokenInhands.dm +++ b/code/modules/admin/verbs/BrokenInhands.dm @@ -1,35 +1,35 @@ -/proc/getbrokeninhands() - var/text - for(var/A in typesof(/obj/item)) - var/obj/item/O = new A( locate(1,1,1) ) - if(!O) - continue - var/icon/IL = new(O.lefthand_file) - var/list/Lstates = IL.IconStates() - var/icon/IR = new(O.righthand_file) - var/list/Rstates = IR.IconStates() - var/icon/J = new(O.icon) - var/list/istates = J.IconStates() - if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.inhand_icon_state)) - if(O.icon_state) - text += "[O.type] WANTS IN LEFT HAND CALLED\n\"[O.icon_state]\".\n" - if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.inhand_icon_state)) - if(O.icon_state) - text += "[O.type] WANTS IN RIGHT HAND CALLED\n\"[O.icon_state]\".\n" - - - if(O.icon_state) - if(!istates.Find(O.icon_state)) - text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.icon_state]\" IN \"[O.icon]\"\n" - if(O.inhand_icon_state) - if(!istates.Find(O.inhand_icon_state)) - text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.inhand_icon_state]\" IN \"[O.icon]\"\n" - text+="\n" - qdel(O) - if(text) - var/F = file("broken_icons.txt") - fdel(F) - WRITE_FILE(F, text) - to_chat(world, "Completely successfully and written to [F]", confidential = TRUE) - - +/proc/getbrokeninhands() + var/text + for(var/A in typesof(/obj/item)) + var/obj/item/O = new A( locate(1,1,1) ) + if(!O) + continue + var/icon/IL = new(O.lefthand_file) + var/list/Lstates = IL.IconStates() + var/icon/IR = new(O.righthand_file) + var/list/Rstates = IR.IconStates() + var/icon/J = new(O.icon) + var/list/istates = J.IconStates() + if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.inhand_icon_state)) + if(O.icon_state) + text += "[O.type] WANTS IN LEFT HAND CALLED\n\"[O.icon_state]\".\n" + if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.inhand_icon_state)) + if(O.icon_state) + text += "[O.type] WANTS IN RIGHT HAND CALLED\n\"[O.icon_state]\".\n" + + + if(O.icon_state) + if(!istates.Find(O.icon_state)) + text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.icon_state]\" IN \"[O.icon]\"\n" + if(O.inhand_icon_state) + if(!istates.Find(O.inhand_icon_state)) + text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.inhand_icon_state]\" IN \"[O.icon]\"\n" + text+="\n" + qdel(O) + if(text) + var/F = file("broken_icons.txt") + fdel(F) + WRITE_FILE(F, text) + to_chat(world, "Completely successfully and written to [F]", confidential = TRUE) + + diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 76a72dffe0b..5141b612ed3 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -1,720 +1,720 @@ -/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb -/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with - -// -//TICKET MANAGER -// - -GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) - -/datum/admin_help_tickets - var/list/active_tickets = list() - var/list/closed_tickets = list() - var/list/resolved_tickets = list() - - var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE) - var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED) - var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED) - -/datum/admin_help_tickets/Destroy() - QDEL_LIST(active_tickets) - QDEL_LIST(closed_tickets) - QDEL_LIST(resolved_tickets) - QDEL_NULL(astatclick) - QDEL_NULL(cstatclick) - QDEL_NULL(rstatclick) - return ..() - -/datum/admin_help_tickets/proc/TicketByID(id) - var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) - for(var/I in lists) - for(var/J in I) - var/datum/admin_help/AH = J - if(AH.id == id) - return J - -/datum/admin_help_tickets/proc/TicketsByCKey(ckey) - . = list() - var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) - for(var/I in lists) - for(var/J in I) - var/datum/admin_help/AH = J - if(AH.initiator_ckey == ckey) - . += AH - -//private -/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket) - var/list/ticket_list - switch(new_ticket.state) - if(AHELP_ACTIVE) - ticket_list = active_tickets - if(AHELP_CLOSED) - ticket_list = closed_tickets - if(AHELP_RESOLVED) - ticket_list = resolved_tickets - else - CRASH("Invalid ticket state: [new_ticket.state]") - var/num_closed = ticket_list.len - if(num_closed) - for(var/I in 1 to num_closed) - var/datum/admin_help/AH = ticket_list[I] - if(AH.id > new_ticket.id) - ticket_list.Insert(I, new_ticket) - return - ticket_list += new_ticket - -//opens the ticket listings for one of the 3 states -/datum/admin_help_tickets/proc/BrowseTickets(state) - var/list/l2b - var/title - switch(state) - if(AHELP_ACTIVE) - l2b = active_tickets - title = "Active Tickets" - if(AHELP_CLOSED) - l2b = closed_tickets - title = "Closed Tickets" - if(AHELP_RESOLVED) - l2b = resolved_tickets - title = "Resolved Tickets" - if(!l2b) - return - var/list/dat = list("[title]") - dat += "Refresh

                " - for(var/I in l2b) - var/datum/admin_help/AH = I - dat += "Ticket #[AH.id]: [AH.initiator_key_name]: [AH.name]
                " - - usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480") - -//Tickets statpanel -/datum/admin_help_tickets/proc/stat_entry() - var/num_disconnected = 0 - stat("Active Tickets:", astatclick.update("[active_tickets.len]")) - for(var/I in active_tickets) - var/datum/admin_help/AH = I - if(AH.initiator) - stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update()) - else - ++num_disconnected - if(num_disconnected) - stat("Disconnected:", astatclick.update("[num_disconnected]")) - stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]")) - stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]")) - -//Reassociate still open ticket if one exists -/datum/admin_help_tickets/proc/ClientLogin(client/C) - C.current_ticket = CKey2ActiveTicket(C.ckey) - if(C.current_ticket) - C.current_ticket.initiator = C - C.current_ticket.AddInteraction("Client reconnected.") - SSblackbox.LogAhelp(C.current_ticket.id, "Reconnected", "Client reconnected", C.ckey) - -//Dissasociate ticket -/datum/admin_help_tickets/proc/ClientLogout(client/C) - if(C.current_ticket) - var/datum/admin_help/T = C.current_ticket - T.AddInteraction("Client disconnected.") - SSblackbox.LogAhelp(T, "Disconnected", "Client disconnected", C.ckey) - T.initiator = null - -//Get a ticket given a ckey -/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey) - for(var/I in active_tickets) - var/datum/admin_help/AH = I - if(AH.initiator_ckey == ckey) - return AH - -// -//TICKET LIST STATCLICK -// - -/obj/effect/statclick/ticket_list - var/current_state - -/obj/effect/statclick/ticket_list/New(loc, name, state) - current_state = state - ..() - -/obj/effect/statclick/ticket_list/Click() - GLOB.ahelp_tickets.BrowseTickets(current_state) - -// -//TICKET DATUM -// - -/datum/admin_help - var/id - var/name - var/state = AHELP_ACTIVE - - var/opened_at - var/closed_at - - var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked - var/initiator_ckey - var/initiator_key_name - var/heard_by_no_admins = FALSE - - var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log() - - var/obj/effect/statclick/ahelp/statclick - - var/static/ticket_counter = 0 - -//call this on its own to create a ticket, don't manually assign current_ticket -//msg is the title of the ticket: usually the ahelp text -//is_bwoink is TRUE if this ticket was started by an admin PM -/datum/admin_help/New(msg, client/C, is_bwoink) - //clean the input msg - msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) - if(!msg || !C || !C.mob) - qdel(src) - return - - id = ++ticket_counter - opened_at = world.time - - name = copytext_char(msg, 1, 100) - - initiator = C - initiator_ckey = initiator.ckey - initiator_key_name = key_name(initiator, FALSE, TRUE) - if(initiator.current_ticket) //This is a bug - stack_trace("Multiple ahelp current_tickets") - initiator.current_ticket.AddInteraction("Ticket erroneously left open by code") - initiator.current_ticket.Close() - initiator.current_ticket = src - - TimeoutVerb() - - statclick = new(null, src) - _interactions = list() - - if(is_bwoink) - AddInteraction("[key_name_admin(usr)] PM'd [LinkedReplyName()]") - message_admins("Ticket [TicketHref("#[id]")] created") - else - MessageNoRecipient(msg) - - //send it to TGS if nobody is on and tell us how many were on - var/admin_number_present = send2tgs_adminless_only(initiator_ckey, "Ticket #[id]: [msg]") - log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") - if(admin_number_present <= 0) - to_chat(C, "No active admins are online, your adminhelp was sent through TGS to admins who are available. This may use IRC or Discord.", confidential = TRUE) - heard_by_no_admins = TRUE - GLOB.ahelp_tickets.active_tickets += src - -/datum/admin_help/Destroy() - RemoveActive() - GLOB.ahelp_tickets.closed_tickets -= src - GLOB.ahelp_tickets.resolved_tickets -= src - return ..() - -/datum/admin_help/proc/AddInteraction(formatted_message) - if(heard_by_no_admins && usr && usr.ckey != initiator_ckey) - heard_by_no_admins = FALSE - send2adminchat(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") - _interactions += "[time_stamp()]: [formatted_message]" - -//Removes the ahelp verb and returns it after 2 minutes -/datum/admin_help/proc/TimeoutVerb() - initiator.verbs -= /client/verb/adminhelp - initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps - -//private -/datum/admin_help/proc/FullMonty(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - . = ADMIN_FULLMONTY_NONAME(initiator.mob) - if(state == AHELP_ACTIVE) - . += ClosureLinks(ref_src) - -//private -/datum/admin_help/proc/ClosureLinks(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - . = " (REJT)" - . += " (IC)" - . += " (CLOSE)" - . += " (RSLVE)" - -//private -/datum/admin_help/proc/LinkedReplyName(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - return "[initiator_key_name]" - -//private -/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") - if(!ref_src) - ref_src = "[REF(src)]" - return "[msg]" - -//message from the initiator without a target, all admins will see this -//won't bug irc/discord -/datum/admin_help/proc/MessageNoRecipient(msg) - msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) - var/ref_src = "[REF(src)]" - //Message to be sent to all admins - var/admin_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [keywords_lookup(msg)]" - - AddInteraction("[LinkedReplyName(ref_src)]: [msg]") - log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]") - - //send this msg to all admins - for(var/client/X in GLOB.admins) - if(X.prefs.toggles & SOUND_ADMINHELP) - SEND_SOUND(X, sound('sound/effects/adminhelp.ogg')) - window_flash(X, ignorepref = TRUE) - to_chat(X, admin_msg, confidential = TRUE) - - //show it to the person adminhelping too - to_chat(initiator, "PM to-Admins: [msg]", confidential = TRUE) - SSblackbox.LogAhelp(id, "Ticket Opened", msg, null, initiator.ckey) - -//Reopen a closed ticket -/datum/admin_help/proc/Reopen() - if(state == AHELP_ACTIVE) - to_chat(usr, "This ticket is already open.", confidential = TRUE) - return - - if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey)) - to_chat(usr, "This user already has an active ticket, cannot reopen this one.", confidential = TRUE) - return - - statclick = new(null, src) - GLOB.ahelp_tickets.active_tickets += src - GLOB.ahelp_tickets.closed_tickets -= src - GLOB.ahelp_tickets.resolved_tickets -= src - switch(state) - if(AHELP_CLOSED) - SSblackbox.record_feedback("tally", "ahelp_stats", -1, "closed") - if(AHELP_RESOLVED) - SSblackbox.record_feedback("tally", "ahelp_stats", -1, "resolved") - state = AHELP_ACTIVE - closed_at = null - if(initiator) - initiator.current_ticket = src - - AddInteraction("Reopened by [key_name_admin(usr)]") - var/msg = "Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)]." - message_admins(msg) - log_admin_private(msg) - SSblackbox.LogAhelp(id, "Reopened", "Reopened by [usr.key]", usr.ckey) - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "reopened") - TicketPanel() //can only be done from here, so refresh it - -//private -/datum/admin_help/proc/RemoveActive() - if(state != AHELP_ACTIVE) - return - closed_at = world.time - QDEL_NULL(statclick) - GLOB.ahelp_tickets.active_tickets -= src - if(initiator && initiator.current_ticket == src) - initiator.current_ticket = null - -//Mark open ticket as closed/meme -/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE) - if(state != AHELP_ACTIVE) - return - RemoveActive() - state = AHELP_CLOSED - GLOB.ahelp_tickets.ListInsert(src) - AddInteraction("Closed by [key_name].") - if(!silent) - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "closed") - var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]." - message_admins(msg) - SSblackbox.LogAhelp(id, "Closed", "Closed by [usr.key]", null, usr.ckey) - log_admin_private(msg) - -//Mark open ticket as resolved/legitimate, returns ahelp verb -/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE) - if(state != AHELP_ACTIVE) - return - RemoveActive() - state = AHELP_RESOLVED - GLOB.ahelp_tickets.ListInsert(src) - - addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 50) - - AddInteraction("Resolved by [key_name].") - to_chat(initiator, "Your ticket has been resolved by an admin. The Adminhelp verb will be returned to you shortly.", confidential = TRUE) - if(!silent) - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved") - var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]" - message_admins(msg) - SSblackbox.LogAhelp(id, "Resolved", "Resolved by [usr.key]", null, usr.ckey) - log_admin_private(msg) - -//Close and return ahelp verb, use if ticket is incoherent -/datum/admin_help/proc/Reject(key_name = key_name_admin(usr)) - if(state != AHELP_ACTIVE) - return - - if(initiator) - initiator.giveadminhelpverb() - - SEND_SOUND(initiator, sound('sound/effects/adminhelp.ogg')) - - to_chat(initiator, "- AdminHelp Rejected! -", confidential = TRUE) - to_chat(initiator, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.", confidential = TRUE) - to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.", confidential = TRUE) - - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "rejected") - var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]" - message_admins(msg) - log_admin_private(msg) - AddInteraction("Rejected by [key_name].") - SSblackbox.LogAhelp(id, "Rejected", "Rejected by [usr.key]", null, usr.ckey) - Close(silent = TRUE) - -//Resolve ticket with IC Issue message -/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr)) - if(state != AHELP_ACTIVE) - return - - var/msg = "- AdminHelp marked as IC issue! -
                " - msg += "Your issue has been determined by an administrator to be an in character issue and does NOT require administrator intervention at this time. For further resolution you should pursue options that are in character." - - if(initiator) - to_chat(initiator, msg, confidential = TRUE) - - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "IC") - msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]" - message_admins(msg) - log_admin_private(msg) - AddInteraction("Marked as IC issue by [key_name]") - SSblackbox.LogAhelp(id, "IC Issue", "Marked as IC issue by [usr.key]", null, usr.ckey) - Resolve(silent = TRUE) - -//Show the ticket panel -/datum/admin_help/proc/TicketPanel() - var/list/dat = list("Ticket #[id]") - var/ref_src = "[REF(src)]" - dat += "

                Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]

                " - dat += "State: " - switch(state) - if(AHELP_ACTIVE) - dat += "OPEN" - if(AHELP_RESOLVED) - dat += "RESOLVED" - if(AHELP_CLOSED) - dat += "CLOSED" - else - dat += "UNKNOWN" - dat += "[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]" - if(state != AHELP_ACTIVE) - dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]" - dat += "

                Opened at: [gameTimestamp(wtime = opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" - if(closed_at) - dat += "
                Closed at: [gameTimestamp(wtime = closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)" - dat += "

                " - if(initiator) - dat += "Actions: [FullMonty(ref_src)]
                " - else - dat += "DISCONNECTED[FOURSPACES][ClosureLinks(ref_src)]
                " - dat += "
                Log:

                " - for(var/I in _interactions) - dat += "[I]
                " - - usr << browse(dat.Join(), "window=ahelp[id];size=620x480") - -/datum/admin_help/proc/Retitle() - var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null - if(new_title) - name = new_title - //not saying the original name cause it could be a long ass message - var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]" - message_admins(msg) - log_admin_private(msg) - TicketPanel() //we have to be here to do this - -//Forwarded action from admin/Topic -/datum/admin_help/proc/Action(action) - testing("Ahelp action: [action]") - switch(action) - if("ticket") - TicketPanel() - if("retitle") - Retitle() - if("reject") - Reject() - if("reply") - usr.client.cmd_ahelp_reply(initiator) - if("icissue") - ICIssue() - if("close") - Close() - if("resolve") - Resolve() - if("reopen") - Reopen() - -// -// TICKET STATCLICK -// - -/obj/effect/statclick/ahelp - var/datum/admin_help/ahelp_datum - -/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH) - ahelp_datum = AH - . = ..() - -/obj/effect/statclick/ahelp/update() - return ..(ahelp_datum.name) - -/obj/effect/statclick/ahelp/Click() - ahelp_datum.TicketPanel() - -/obj/effect/statclick/ahelp/Destroy() - ahelp_datum = null - return ..() - -// -// CLIENT PROCS -// - -/client/proc/giveadminhelpverb() - src.verbs |= /client/verb/adminhelp - deltimer(adminhelptimerid) - adminhelptimerid = 0 - -// Used for methods where input via arg doesn't work -/client/proc/get_adminhelp() - var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as message|null - adminhelp(msg) - -/client/verb/adminhelp(msg as message) - set category = "Admin" - set name = "Adminhelp" - - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) - return - - //handle muting and automuting - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).", confidential = TRUE) - return - if(handle_spam_prevention(msg,MUTE_ADMINHELP)) - return - - msg = trim(msg) - - if(!msg) - return - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - if(current_ticket) - if(alert(usr, "You already have a ticket open. Is this for the same issue?",,"Yes","No") != "No") - if(current_ticket) - current_ticket.MessageNoRecipient(msg) - current_ticket.TimeoutVerb() - return - else - to_chat(usr, "Ticket not found, creating new one...", confidential = TRUE) - else - current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.") - current_ticket.Close() - - new /datum/admin_help(msg, src, FALSE) - -// -// LOGGING -// - -//Use this proc when an admin takes action that may be related to an open ticket on what -//what can be a client, ckey, or mob -/proc/admin_ticket_log(what, message) - var/client/C - var/mob/Mob = what - if(istype(Mob)) - C = Mob.client - else - C = what - if(istype(C) && C.current_ticket) - C.current_ticket.AddInteraction(message) - return C.current_ticket - if(istext(what)) //ckey - var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what) - if(AH) - AH.AddInteraction(message) - return AH - -// -// HELPER PROCS -// - -/proc/get_admin_counts(requiredflags = R_BAN) - . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) - for(var/client/X in GLOB.admins) - .["total"] += X - if(requiredflags != 0 && !check_rights_for(X, requiredflags)) - .["noflags"] += X - else if(X.is_afk()) - .["afk"] += X - else if(X.holder.fakekey) - .["stealth"] += X - else - .["present"] += X - -/proc/send2tgs_adminless_only(source, msg, requiredflags = R_BAN) - var/list/adm = get_admin_counts(requiredflags) - var/list/activemins = adm["present"] - . = activemins.len - if(. <= 0) - var/final = "" - var/list/afkmins = adm["afk"] - var/list/stealthmins = adm["stealth"] - var/list/powerlessmins = adm["noflags"] - var/list/allmins = adm["total"] - if(!afkmins.len && !stealthmins.len && !powerlessmins.len) - final = "[msg] - No admins online" - else - final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] " - send2adminchat(source,final) - send2otherserver(source,final) - -// -/proc/send2otherserver(source,msg,type = "Ahelp",target_servers) - var/comms_key = CONFIG_GET(string/comms_key) - if(!comms_key) - return - - var/our_id = CONFIG_GET(string/cross_comms_name) - var/list/message = list() - message["message_sender"] = source - message["message"] = msg - message["source"] = "([our_id])" - message["key"] = comms_key - message += type - - var/list/servers = CONFIG_GET(keyed_list/cross_server) - for(var/I in servers) - if(I == our_id) //No sending to ourselves - continue - if(target_servers && !(I in target_servers)) - continue - world.Export("[servers[I]]?[list2params(message)]") - - -/proc/tgsadminwho() - var/list/message = list("Admins: ") - var/list/admin_keys = list() - for(var/adm in GLOB.admins) - var/client/C = adm - admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]" - - for(var/admin in admin_keys) - if(LAZYLEN(message) > 1) - message += ", [admin]" - else - message += "[admin]" - - return jointext(message, "") - -/proc/keywords_lookup(msg,external) - - //This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! - var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i") - - //explode the input msg into a list - var/list/msglist = splittext(msg, " ") - - //generate keywords lookup - var/list/surnames = list() - var/list/forenames = list() - var/list/ckeys = list() - var/founds = "" - for(var/mob/M in GLOB.mob_list) - var/list/indexing = list(M.real_name, M.name) - if(M.mind) - indexing += M.mind.name - - for(var/string in indexing) - var/list/L = splittext(string, " ") - var/surname_found = 0 - //surnames - for(var/i=L.len, i>=1, i--) - var/word = ckey(L[i]) - if(word) - surnames[word] = M - surname_found = i - break - //forenames - for(var/i=1, i(?|F) " - continue - msg += "[original_word] " - if(external) - if(founds == "") - return "Search Failed" - else - return founds - - return msg - -/proc/get_mob_by_name(msg) - //This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! - var/list/ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i") - - //explode the input msg into a list - var/list/msglist = splittext(msg, " ") - - //who might fit the shoe - var/list/potential_hits = list() - - for(var/i in GLOB.mob_list) - var/mob/M = i - var/list/nameWords = list() - if(!M.mind) - continue - - for(var/string in splittext(lowertext(M.real_name), " ")) - if(!(string in ignored_words)) - nameWords += string - for(var/string in splittext(lowertext(M.name), " ")) - if(!(string in ignored_words)) - nameWords += string - - for(var/string in nameWords) - testing("Name word [string]") - if(string in msglist) - potential_hits += M - break - - return potential_hits +/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb +/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with + +// +//TICKET MANAGER +// + +GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) + +/datum/admin_help_tickets + var/list/active_tickets = list() + var/list/closed_tickets = list() + var/list/resolved_tickets = list() + + var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE) + var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED) + var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED) + +/datum/admin_help_tickets/Destroy() + QDEL_LIST(active_tickets) + QDEL_LIST(closed_tickets) + QDEL_LIST(resolved_tickets) + QDEL_NULL(astatclick) + QDEL_NULL(cstatclick) + QDEL_NULL(rstatclick) + return ..() + +/datum/admin_help_tickets/proc/TicketByID(id) + var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) + for(var/I in lists) + for(var/J in I) + var/datum/admin_help/AH = J + if(AH.id == id) + return J + +/datum/admin_help_tickets/proc/TicketsByCKey(ckey) + . = list() + var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) + for(var/I in lists) + for(var/J in I) + var/datum/admin_help/AH = J + if(AH.initiator_ckey == ckey) + . += AH + +//private +/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket) + var/list/ticket_list + switch(new_ticket.state) + if(AHELP_ACTIVE) + ticket_list = active_tickets + if(AHELP_CLOSED) + ticket_list = closed_tickets + if(AHELP_RESOLVED) + ticket_list = resolved_tickets + else + CRASH("Invalid ticket state: [new_ticket.state]") + var/num_closed = ticket_list.len + if(num_closed) + for(var/I in 1 to num_closed) + var/datum/admin_help/AH = ticket_list[I] + if(AH.id > new_ticket.id) + ticket_list.Insert(I, new_ticket) + return + ticket_list += new_ticket + +//opens the ticket listings for one of the 3 states +/datum/admin_help_tickets/proc/BrowseTickets(state) + var/list/l2b + var/title + switch(state) + if(AHELP_ACTIVE) + l2b = active_tickets + title = "Active Tickets" + if(AHELP_CLOSED) + l2b = closed_tickets + title = "Closed Tickets" + if(AHELP_RESOLVED) + l2b = resolved_tickets + title = "Resolved Tickets" + if(!l2b) + return + var/list/dat = list("[title]") + dat += "Refresh

                " + for(var/I in l2b) + var/datum/admin_help/AH = I + dat += "Ticket #[AH.id]: [AH.initiator_key_name]: [AH.name]
                " + + usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480") + +//Tickets statpanel +/datum/admin_help_tickets/proc/stat_entry() + var/num_disconnected = 0 + stat("Active Tickets:", astatclick.update("[active_tickets.len]")) + for(var/I in active_tickets) + var/datum/admin_help/AH = I + if(AH.initiator) + stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update()) + else + ++num_disconnected + if(num_disconnected) + stat("Disconnected:", astatclick.update("[num_disconnected]")) + stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]")) + stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]")) + +//Reassociate still open ticket if one exists +/datum/admin_help_tickets/proc/ClientLogin(client/C) + C.current_ticket = CKey2ActiveTicket(C.ckey) + if(C.current_ticket) + C.current_ticket.initiator = C + C.current_ticket.AddInteraction("Client reconnected.") + SSblackbox.LogAhelp(C.current_ticket.id, "Reconnected", "Client reconnected", C.ckey) + +//Dissasociate ticket +/datum/admin_help_tickets/proc/ClientLogout(client/C) + if(C.current_ticket) + var/datum/admin_help/T = C.current_ticket + T.AddInteraction("Client disconnected.") + SSblackbox.LogAhelp(T, "Disconnected", "Client disconnected", C.ckey) + T.initiator = null + +//Get a ticket given a ckey +/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey) + for(var/I in active_tickets) + var/datum/admin_help/AH = I + if(AH.initiator_ckey == ckey) + return AH + +// +//TICKET LIST STATCLICK +// + +/obj/effect/statclick/ticket_list + var/current_state + +/obj/effect/statclick/ticket_list/New(loc, name, state) + current_state = state + ..() + +/obj/effect/statclick/ticket_list/Click() + GLOB.ahelp_tickets.BrowseTickets(current_state) + +// +//TICKET DATUM +// + +/datum/admin_help + var/id + var/name + var/state = AHELP_ACTIVE + + var/opened_at + var/closed_at + + var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked + var/initiator_ckey + var/initiator_key_name + var/heard_by_no_admins = FALSE + + var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log() + + var/obj/effect/statclick/ahelp/statclick + + var/static/ticket_counter = 0 + +//call this on its own to create a ticket, don't manually assign current_ticket +//msg is the title of the ticket: usually the ahelp text +//is_bwoink is TRUE if this ticket was started by an admin PM +/datum/admin_help/New(msg, client/C, is_bwoink) + //clean the input msg + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + if(!msg || !C || !C.mob) + qdel(src) + return + + id = ++ticket_counter + opened_at = world.time + + name = copytext_char(msg, 1, 100) + + initiator = C + initiator_ckey = initiator.ckey + initiator_key_name = key_name(initiator, FALSE, TRUE) + if(initiator.current_ticket) //This is a bug + stack_trace("Multiple ahelp current_tickets") + initiator.current_ticket.AddInteraction("Ticket erroneously left open by code") + initiator.current_ticket.Close() + initiator.current_ticket = src + + TimeoutVerb() + + statclick = new(null, src) + _interactions = list() + + if(is_bwoink) + AddInteraction("[key_name_admin(usr)] PM'd [LinkedReplyName()]") + message_admins("Ticket [TicketHref("#[id]")] created") + else + MessageNoRecipient(msg) + + //send it to TGS if nobody is on and tell us how many were on + var/admin_number_present = send2tgs_adminless_only(initiator_ckey, "Ticket #[id]: [msg]") + log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") + if(admin_number_present <= 0) + to_chat(C, "No active admins are online, your adminhelp was sent through TGS to admins who are available. This may use IRC or Discord.", confidential = TRUE) + heard_by_no_admins = TRUE + GLOB.ahelp_tickets.active_tickets += src + +/datum/admin_help/Destroy() + RemoveActive() + GLOB.ahelp_tickets.closed_tickets -= src + GLOB.ahelp_tickets.resolved_tickets -= src + return ..() + +/datum/admin_help/proc/AddInteraction(formatted_message) + if(heard_by_no_admins && usr && usr.ckey != initiator_ckey) + heard_by_no_admins = FALSE + send2adminchat(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") + _interactions += "[time_stamp()]: [formatted_message]" + +//Removes the ahelp verb and returns it after 2 minutes +/datum/admin_help/proc/TimeoutVerb() + initiator.verbs -= /client/verb/adminhelp + initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps + +//private +/datum/admin_help/proc/FullMonty(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + . = ADMIN_FULLMONTY_NONAME(initiator.mob) + if(state == AHELP_ACTIVE) + . += ClosureLinks(ref_src) + +//private +/datum/admin_help/proc/ClosureLinks(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + . = " (REJT)" + . += " (IC)" + . += " (CLOSE)" + . += " (RSLVE)" + +//private +/datum/admin_help/proc/LinkedReplyName(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + return "[initiator_key_name]" + +//private +/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") + if(!ref_src) + ref_src = "[REF(src)]" + return "[msg]" + +//message from the initiator without a target, all admins will see this +//won't bug irc/discord +/datum/admin_help/proc/MessageNoRecipient(msg) + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + var/ref_src = "[REF(src)]" + //Message to be sent to all admins + var/admin_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [keywords_lookup(msg)]" + + AddInteraction("[LinkedReplyName(ref_src)]: [msg]") + log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]") + + //send this msg to all admins + for(var/client/X in GLOB.admins) + if(X.prefs.toggles & SOUND_ADMINHELP) + SEND_SOUND(X, sound('sound/effects/adminhelp.ogg')) + window_flash(X, ignorepref = TRUE) + to_chat(X, admin_msg, confidential = TRUE) + + //show it to the person adminhelping too + to_chat(initiator, "PM to-Admins: [msg]", confidential = TRUE) + SSblackbox.LogAhelp(id, "Ticket Opened", msg, null, initiator.ckey) + +//Reopen a closed ticket +/datum/admin_help/proc/Reopen() + if(state == AHELP_ACTIVE) + to_chat(usr, "This ticket is already open.", confidential = TRUE) + return + + if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey)) + to_chat(usr, "This user already has an active ticket, cannot reopen this one.", confidential = TRUE) + return + + statclick = new(null, src) + GLOB.ahelp_tickets.active_tickets += src + GLOB.ahelp_tickets.closed_tickets -= src + GLOB.ahelp_tickets.resolved_tickets -= src + switch(state) + if(AHELP_CLOSED) + SSblackbox.record_feedback("tally", "ahelp_stats", -1, "closed") + if(AHELP_RESOLVED) + SSblackbox.record_feedback("tally", "ahelp_stats", -1, "resolved") + state = AHELP_ACTIVE + closed_at = null + if(initiator) + initiator.current_ticket = src + + AddInteraction("Reopened by [key_name_admin(usr)]") + var/msg = "Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)]." + message_admins(msg) + log_admin_private(msg) + SSblackbox.LogAhelp(id, "Reopened", "Reopened by [usr.key]", usr.ckey) + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "reopened") + TicketPanel() //can only be done from here, so refresh it + +//private +/datum/admin_help/proc/RemoveActive() + if(state != AHELP_ACTIVE) + return + closed_at = world.time + QDEL_NULL(statclick) + GLOB.ahelp_tickets.active_tickets -= src + if(initiator && initiator.current_ticket == src) + initiator.current_ticket = null + +//Mark open ticket as closed/meme +/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE) + if(state != AHELP_ACTIVE) + return + RemoveActive() + state = AHELP_CLOSED + GLOB.ahelp_tickets.ListInsert(src) + AddInteraction("Closed by [key_name].") + if(!silent) + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "closed") + var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]." + message_admins(msg) + SSblackbox.LogAhelp(id, "Closed", "Closed by [usr.key]", null, usr.ckey) + log_admin_private(msg) + +//Mark open ticket as resolved/legitimate, returns ahelp verb +/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE) + if(state != AHELP_ACTIVE) + return + RemoveActive() + state = AHELP_RESOLVED + GLOB.ahelp_tickets.ListInsert(src) + + addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 50) + + AddInteraction("Resolved by [key_name].") + to_chat(initiator, "Your ticket has been resolved by an admin. The Adminhelp verb will be returned to you shortly.", confidential = TRUE) + if(!silent) + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved") + var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]" + message_admins(msg) + SSblackbox.LogAhelp(id, "Resolved", "Resolved by [usr.key]", null, usr.ckey) + log_admin_private(msg) + +//Close and return ahelp verb, use if ticket is incoherent +/datum/admin_help/proc/Reject(key_name = key_name_admin(usr)) + if(state != AHELP_ACTIVE) + return + + if(initiator) + initiator.giveadminhelpverb() + + SEND_SOUND(initiator, sound('sound/effects/adminhelp.ogg')) + + to_chat(initiator, "- AdminHelp Rejected! -", confidential = TRUE) + to_chat(initiator, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.", confidential = TRUE) + to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.", confidential = TRUE) + + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "rejected") + var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]" + message_admins(msg) + log_admin_private(msg) + AddInteraction("Rejected by [key_name].") + SSblackbox.LogAhelp(id, "Rejected", "Rejected by [usr.key]", null, usr.ckey) + Close(silent = TRUE) + +//Resolve ticket with IC Issue message +/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr)) + if(state != AHELP_ACTIVE) + return + + var/msg = "- AdminHelp marked as IC issue! -
                " + msg += "Your issue has been determined by an administrator to be an in character issue and does NOT require administrator intervention at this time. For further resolution you should pursue options that are in character." + + if(initiator) + to_chat(initiator, msg, confidential = TRUE) + + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "IC") + msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]" + message_admins(msg) + log_admin_private(msg) + AddInteraction("Marked as IC issue by [key_name]") + SSblackbox.LogAhelp(id, "IC Issue", "Marked as IC issue by [usr.key]", null, usr.ckey) + Resolve(silent = TRUE) + +//Show the ticket panel +/datum/admin_help/proc/TicketPanel() + var/list/dat = list("Ticket #[id]") + var/ref_src = "[REF(src)]" + dat += "

                Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]

                " + dat += "State: " + switch(state) + if(AHELP_ACTIVE) + dat += "OPEN" + if(AHELP_RESOLVED) + dat += "RESOLVED" + if(AHELP_CLOSED) + dat += "CLOSED" + else + dat += "UNKNOWN" + dat += "[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]" + if(state != AHELP_ACTIVE) + dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]" + dat += "

                Opened at: [gameTimestamp(wtime = opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" + if(closed_at) + dat += "
                Closed at: [gameTimestamp(wtime = closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)" + dat += "

                " + if(initiator) + dat += "Actions: [FullMonty(ref_src)]
                " + else + dat += "DISCONNECTED[FOURSPACES][ClosureLinks(ref_src)]
                " + dat += "
                Log:

                " + for(var/I in _interactions) + dat += "[I]
                " + + usr << browse(dat.Join(), "window=ahelp[id];size=620x480") + +/datum/admin_help/proc/Retitle() + var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null + if(new_title) + name = new_title + //not saying the original name cause it could be a long ass message + var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]" + message_admins(msg) + log_admin_private(msg) + TicketPanel() //we have to be here to do this + +//Forwarded action from admin/Topic +/datum/admin_help/proc/Action(action) + testing("Ahelp action: [action]") + switch(action) + if("ticket") + TicketPanel() + if("retitle") + Retitle() + if("reject") + Reject() + if("reply") + usr.client.cmd_ahelp_reply(initiator) + if("icissue") + ICIssue() + if("close") + Close() + if("resolve") + Resolve() + if("reopen") + Reopen() + +// +// TICKET STATCLICK +// + +/obj/effect/statclick/ahelp + var/datum/admin_help/ahelp_datum + +/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH) + ahelp_datum = AH + . = ..() + +/obj/effect/statclick/ahelp/update() + return ..(ahelp_datum.name) + +/obj/effect/statclick/ahelp/Click() + ahelp_datum.TicketPanel() + +/obj/effect/statclick/ahelp/Destroy() + ahelp_datum = null + return ..() + +// +// CLIENT PROCS +// + +/client/proc/giveadminhelpverb() + src.verbs |= /client/verb/adminhelp + deltimer(adminhelptimerid) + adminhelptimerid = 0 + +// Used for methods where input via arg doesn't work +/client/proc/get_adminhelp() + var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as message|null + adminhelp(msg) + +/client/verb/adminhelp(msg as message) + set category = "Admin" + set name = "Adminhelp" + + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) + return + + //handle muting and automuting + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).", confidential = TRUE) + return + if(handle_spam_prevention(msg,MUTE_ADMINHELP)) + return + + msg = trim(msg) + + if(!msg) + return + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(current_ticket) + if(alert(usr, "You already have a ticket open. Is this for the same issue?",,"Yes","No") != "No") + if(current_ticket) + current_ticket.MessageNoRecipient(msg) + current_ticket.TimeoutVerb() + return + else + to_chat(usr, "Ticket not found, creating new one...", confidential = TRUE) + else + current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.") + current_ticket.Close() + + new /datum/admin_help(msg, src, FALSE) + +// +// LOGGING +// + +//Use this proc when an admin takes action that may be related to an open ticket on what +//what can be a client, ckey, or mob +/proc/admin_ticket_log(what, message) + var/client/C + var/mob/Mob = what + if(istype(Mob)) + C = Mob.client + else + C = what + if(istype(C) && C.current_ticket) + C.current_ticket.AddInteraction(message) + return C.current_ticket + if(istext(what)) //ckey + var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what) + if(AH) + AH.AddInteraction(message) + return AH + +// +// HELPER PROCS +// + +/proc/get_admin_counts(requiredflags = R_BAN) + . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) + for(var/client/X in GLOB.admins) + .["total"] += X + if(requiredflags != 0 && !check_rights_for(X, requiredflags)) + .["noflags"] += X + else if(X.is_afk()) + .["afk"] += X + else if(X.holder.fakekey) + .["stealth"] += X + else + .["present"] += X + +/proc/send2tgs_adminless_only(source, msg, requiredflags = R_BAN) + var/list/adm = get_admin_counts(requiredflags) + var/list/activemins = adm["present"] + . = activemins.len + if(. <= 0) + var/final = "" + var/list/afkmins = adm["afk"] + var/list/stealthmins = adm["stealth"] + var/list/powerlessmins = adm["noflags"] + var/list/allmins = adm["total"] + if(!afkmins.len && !stealthmins.len && !powerlessmins.len) + final = "[msg] - No admins online" + else + final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] " + send2adminchat(source,final) + send2otherserver(source,final) + +// +/proc/send2otherserver(source,msg,type = "Ahelp",target_servers) + var/comms_key = CONFIG_GET(string/comms_key) + if(!comms_key) + return + + var/our_id = CONFIG_GET(string/cross_comms_name) + var/list/message = list() + message["message_sender"] = source + message["message"] = msg + message["source"] = "([our_id])" + message["key"] = comms_key + message += type + + var/list/servers = CONFIG_GET(keyed_list/cross_server) + for(var/I in servers) + if(I == our_id) //No sending to ourselves + continue + if(target_servers && !(I in target_servers)) + continue + world.Export("[servers[I]]?[list2params(message)]") + + +/proc/tgsadminwho() + var/list/message = list("Admins: ") + var/list/admin_keys = list() + for(var/adm in GLOB.admins) + var/client/C = adm + admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]" + + for(var/admin in admin_keys) + if(LAZYLEN(message) > 1) + message += ", [admin]" + else + message += "[admin]" + + return jointext(message, "") + +/proc/keywords_lookup(msg,external) + + //This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! + var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i") + + //explode the input msg into a list + var/list/msglist = splittext(msg, " ") + + //generate keywords lookup + var/list/surnames = list() + var/list/forenames = list() + var/list/ckeys = list() + var/founds = "" + for(var/mob/M in GLOB.mob_list) + var/list/indexing = list(M.real_name, M.name) + if(M.mind) + indexing += M.mind.name + + for(var/string in indexing) + var/list/L = splittext(string, " ") + var/surname_found = 0 + //surnames + for(var/i=L.len, i>=1, i--) + var/word = ckey(L[i]) + if(word) + surnames[word] = M + surname_found = i + break + //forenames + for(var/i=1, i(?|F) " + continue + msg += "[original_word] " + if(external) + if(founds == "") + return "Search Failed" + else + return founds + + return msg + +/proc/get_mob_by_name(msg) + //This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! + var/list/ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i") + + //explode the input msg into a list + var/list/msglist = splittext(msg, " ") + + //who might fit the shoe + var/list/potential_hits = list() + + for(var/i in GLOB.mob_list) + var/mob/M = i + var/list/nameWords = list() + if(!M.mind) + continue + + for(var/string in splittext(lowertext(M.real_name), " ")) + if(!(string in ignored_words)) + nameWords += string + for(var/string in splittext(lowertext(M.name), " ")) + if(!(string in ignored_words)) + nameWords += string + + for(var/string in nameWords) + testing("Name word [string]") + if(string in msglist) + potential_hits += M + break + + return potential_hits diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index 4c167205a2a..a47c13c95ba 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -1,160 +1,160 @@ -/client/proc/jumptoarea(area/A in GLOB.sortedAreas) - set name = "Jump to Area" - set desc = "Area to jump to" - set category = "Admin - Game" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - if(!A) - return - - var/list/turfs = list() - for(var/turf/T in A) - if(T.density) - continue - turfs.Add(T) - - if(length(turfs)) - var/turf/T = pick(turfs) - usr.forceMove(T) - log_admin("[key_name(usr)] jumped to [AREACOORD(T)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Area") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - else - to_chat(src, "Nowhere to jump to!", confidential = TRUE) - return - - -/client/proc/jumptoturf(turf/T in world) - set name = "Jump to Turf" - set category = "Admin - Game" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - log_admin("[key_name(usr)] jumped to [AREACOORD(T)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]") - usr.forceMove(T) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Turf") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/jumptomob(mob/M in GLOB.mob_list) - set category = "Admin - Game" - set name = "Jump to Mob" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - log_admin("[key_name(usr)] jumped to [key_name(M)]") - message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)] at [AREACOORD(M)]") - if(src.mob) - var/mob/A = src.mob - var/turf/T = get_turf(M) - if(T && isturf(T)) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - A.forceMove(M.loc) - else - to_chat(A, "This mob is not located in the game world.", confidential = TRUE) - -/client/proc/jumptocoord(tx as num, ty as num, tz as num) - set category = "Admin - Game" - set name = "Jump to Coordinate" - - if (!holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - if(src.mob) - var/mob/A = src.mob - var/turf/T = locate(tx,ty,tz) - A.forceMove(T) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Coordiate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]") - -/client/proc/jumptokey() - set category = "Admin - Game" - set name = "Jump to Key" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - var/list/keys = list() - for(var/mob/M in GLOB.player_list) - keys += M.client - var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) - if(!selection) - to_chat(src, "No keys found.", confidential = TRUE) - return - var/mob/M = selection.mob - log_admin("[key_name(usr)] jumped to [key_name(M)]") - message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)]") - - usr.forceMove(M.loc) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/Getmob(mob/M in GLOB.mob_list - GLOB.dummy_mob_list) - set category = "Admin - Game" - set name = "Get Mob" - set desc = "Mob to teleport" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - var/atom/loc = get_turf(usr) - log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(loc)]") - var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [ADMIN_VERBOSEJMP(loc)]" - message_admins(msg) - admin_ticket_log(M, msg) - M.forceMove(loc) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/Getkey() - set category = "Admin - Game" - set name = "Get Key" - set desc = "Key to teleport" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - var/list/keys = list() - for(var/mob/M in GLOB.player_list) - keys += M.client - var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) - if(!selection) - return - var/mob/M = selection.mob - - if(!M) - return - log_admin("[key_name(usr)] teleported [key_name(M)]") - var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)]" - message_admins(msg) - admin_ticket_log(M, msg) - if(M) - M.forceMove(get_turf(usr)) - usr.forceMove(M.loc) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/sendmob(mob/M in sortmobs()) - set category = "Admin - Game" - set name = "Send Mob" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null - if(A && istype(A)) - var/list/turfs = get_area_turfs(A) - if(length(turfs) && M.forceMove(pick(turfs))) - - log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(M)]") - var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(M)]" - message_admins(msg) - admin_ticket_log(M, msg) - else - to_chat(src, "Failed to move mob to a valid location.", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/jumptoarea(area/A in GLOB.sortedAreas) + set name = "Jump to Area" + set desc = "Area to jump to" + set category = "Admin - Game" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + if(!A) + return + + var/list/turfs = list() + for(var/turf/T in A) + if(T.density) + continue + turfs.Add(T) + + if(length(turfs)) + var/turf/T = pick(turfs) + usr.forceMove(T) + log_admin("[key_name(usr)] jumped to [AREACOORD(T)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Area") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + else + to_chat(src, "Nowhere to jump to!", confidential = TRUE) + return + + +/client/proc/jumptoturf(turf/T in world) + set name = "Jump to Turf" + set category = "Admin - Game" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + log_admin("[key_name(usr)] jumped to [AREACOORD(T)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]") + usr.forceMove(T) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Turf") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/jumptomob(mob/M in GLOB.mob_list) + set category = "Admin - Game" + set name = "Jump to Mob" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + log_admin("[key_name(usr)] jumped to [key_name(M)]") + message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)] at [AREACOORD(M)]") + if(src.mob) + var/mob/A = src.mob + var/turf/T = get_turf(M) + if(T && isturf(T)) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + A.forceMove(M.loc) + else + to_chat(A, "This mob is not located in the game world.", confidential = TRUE) + +/client/proc/jumptocoord(tx as num, ty as num, tz as num) + set category = "Admin - Game" + set name = "Jump to Coordinate" + + if (!holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + if(src.mob) + var/mob/A = src.mob + var/turf/T = locate(tx,ty,tz) + A.forceMove(T) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Coordiate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]") + +/client/proc/jumptokey() + set category = "Admin - Game" + set name = "Jump to Key" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + var/list/keys = list() + for(var/mob/M in GLOB.player_list) + keys += M.client + var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) + if(!selection) + to_chat(src, "No keys found.", confidential = TRUE) + return + var/mob/M = selection.mob + log_admin("[key_name(usr)] jumped to [key_name(M)]") + message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)]") + + usr.forceMove(M.loc) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/Getmob(mob/M in GLOB.mob_list - GLOB.dummy_mob_list) + set category = "Admin - Game" + set name = "Get Mob" + set desc = "Mob to teleport" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + var/atom/loc = get_turf(usr) + log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(loc)]") + var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [ADMIN_VERBOSEJMP(loc)]" + message_admins(msg) + admin_ticket_log(M, msg) + M.forceMove(loc) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/Getkey() + set category = "Admin - Game" + set name = "Get Key" + set desc = "Key to teleport" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + var/list/keys = list() + for(var/mob/M in GLOB.player_list) + keys += M.client + var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) + if(!selection) + return + var/mob/M = selection.mob + + if(!M) + return + log_admin("[key_name(usr)] teleported [key_name(M)]") + var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)]" + message_admins(msg) + admin_ticket_log(M, msg) + if(M) + M.forceMove(get_turf(usr)) + usr.forceMove(M.loc) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/sendmob(mob/M in sortmobs()) + set category = "Admin - Game" + set name = "Send Mob" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null + if(A && istype(A)) + var/list/turfs = get_area_turfs(A) + if(length(turfs) && M.forceMove(pick(turfs))) + + log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(M)]") + var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(M)]" + message_admins(msg) + admin_ticket_log(M, msg) + else + to_chat(src, "Failed to move mob to a valid location.", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 64cbdc2a05e..34f30b9c698 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -1,331 +1,331 @@ -#define EXTERNALREPLYCOUNT 2 - -//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm -/client/proc/cmd_admin_pm_context(mob/M in GLOB.mob_list) - set category = null - set name = "Admin PM Mob" - if(!holder) - to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.", confidential = TRUE) - return - if( !ismob(M) || !M.client ) - return - cmd_admin_pm(M.client,null) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm -/client/proc/cmd_admin_pm_panel() - set category = "Admin" - set name = "Admin PM" - if(!holder) - to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.", confidential = TRUE) - return - var/list/client/targets[0] - for(var/client/T) - if(T.mob) - if(isnewplayer(T.mob)) - targets["(New Player) - [T]"] = T - else if(isobserver(T.mob)) - targets["[T.mob.name](Ghost) - [T]"] = T - else - targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T - else - targets["(No Mob) - [T]"] = T - var/target = input(src,"To whom shall we send a message?","Admin PM",null) as null|anything in sortList(targets) - cmd_admin_pm(targets[target],null) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_ahelp_reply(whom) - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) - return - var/client/C - if(istext(whom)) - if(whom[1] == "@") - whom = findStealthKey(whom) - C = GLOB.directory[whom] - else if(istype(whom, /client)) - C = whom - if(!C) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) - return - - var/datum/admin_help/AH = C.current_ticket - - if(AH) - message_admins("[key_name_admin(src)] has started replying to [key_name_admin(C, 0, 0)]'s admin help.") - var/msg = input(src,"Message:", "Private message to [C.holder?.fakekey ? "an Administrator" : key_name(C, 0, 0)].") as message|null - if (!msg) - message_admins("[key_name_admin(src)] has cancelled their reply to [key_name_admin(C, 0, 0)]'s admin help.") - return - cmd_admin_pm(whom, msg) - -//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM. -//Fetching a message if needed. src is the sender and C is the target client -/client/proc/cmd_admin_pm(whom, msg) - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) - return - - if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo - to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.", confidential = TRUE) - to_chat(src, "Message: [msg]", confidential = TRUE) - return - - var/client/recipient - var/external = 0 - if(istext(whom)) - if(whom[1] == "@") - whom = findStealthKey(whom) - if(whom == "IRCKEY") - external = 1 - else - recipient = GLOB.directory[whom] - else if(istype(whom, /client)) - recipient = whom - - - if(external) - if(!externalreplyamount) //to prevent people from spamming irc/discord - return - if(!msg) - msg = input(src,"Message:", "Private message to Administrator") as message|null - - if(!msg) - return - if(holder) - to_chat(src, "Error: Use the admin IRC/Discord channel, nerd.", confidential = TRUE) - return - - - else - if(!recipient) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) - if(msg) - to_chat(src, msg, confidential = TRUE) - return - else if(msg) // you want to continue if there's no message instead of returning now - current_ticket.MessageNoRecipient(msg) - return - - //get message text, limit it's length.and clean/escape html - if(!msg) - msg = input(src,"Message:", "Private message to [recipient.holder?.fakekey ? "an Administrator" : key_name(recipient, 0, 0)].") as message|null - msg = trim(msg) - if(!msg) - return - - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) - return - - if(!recipient) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) - else - current_ticket.MessageNoRecipient(msg) - return - - if (src.handle_spam_prevention(msg,MUTE_ADMINHELP)) - return - - //clean the message if it's not sent by a high-rank admin - if(!check_rights(R_SERVER|R_DEBUG,0)||external)//no sending html to the poor bots - msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) - if(!msg) - return - - var/rawmsg = msg - - if(holder) - msg = emoji_parse(msg) - - var/keywordparsedmsg = keywords_lookup(msg) - - if(external) - to_chat(src, "PM to-Admins: [rawmsg]", confidential = TRUE) - var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to External: [keywordparsedmsg]") - externalreplyamount-- - send2adminchat("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) - else - if(recipient.holder) - if(holder) //both are admins - to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]", confidential = TRUE) - to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]", confidential = TRUE) - - //omg this is dumb, just fill in both their tickets - var/interaction_message = "PM from-[key_name(src, recipient, 1)] to-[key_name(recipient, src, 1)]: [keywordparsedmsg]" - admin_ticket_log(src, interaction_message) - if(recipient != src) //reeee - admin_ticket_log(recipient, interaction_message) - SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) - else //recipient is an admin but sender is not - var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]" - admin_ticket_log(src, "[replymsg]") - to_chat(recipient, "[replymsg]", confidential = TRUE) - to_chat(src, "PM to-Admins: [msg]", confidential = TRUE) - SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) - - //play the receiving admin the adminhelp sound (if they have them enabled) - if(recipient.prefs.toggles & SOUND_ADMINHELP) - SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) - - else - if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT - var/already_logged = FALSE - if(!recipient.current_ticket) - new /datum/admin_help(msg, recipient, TRUE) - already_logged = TRUE - SSblackbox.LogAhelp(recipient.current_ticket.id, "Ticket Opened", msg, recipient.ckey, src.ckey) - - to_chat(recipient, "-- Administrator private message --", confidential = TRUE) - to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]", confidential = TRUE) - to_chat(recipient, "Click on the administrator's name to reply.", confidential = TRUE) - to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]", confidential = TRUE) - - admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]") - - if(!already_logged) //Reply to an existing ticket - SSblackbox.LogAhelp(recipient.current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) - - - //always play non-admin recipients the adminhelp sound - SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) - - //AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn - if(CONFIG_GET(flag/popup_admin_pm)) - INVOKE_ASYNC(src, .proc/popup_admin_pm, recipient, msg) - - else //neither are admins - to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.", confidential = TRUE) - return - - if(external) - log_admin_private("PM: [key_name(src)]->External: [rawmsg]") - for(var/client/X in GLOB.admins) - to_chat(X, "PM: [key_name(src, X, 0)]->External: [keywordparsedmsg]", confidential = TRUE) - else - window_flash(recipient, ignorepref = TRUE) - log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]") - //we don't use message_admins here because the sender/receiver might get it too - for(var/client/X in GLOB.admins) - if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient - to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" , confidential = TRUE) - -/client/proc/popup_admin_pm(client/recipient, msg) - var/sender = src - var/sendername = key - var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply - if(recipient && reply) - if(sender) - recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them - else - adminhelp(reply) //sender has left, adminhelp instead - -#define TGS_AHELP_USAGE "Usage: ticket " -/proc/TgsPm(target,msg,sender) - target = ckey(target) - var/client/C = GLOB.directory[target] - - var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target) - var/compliant_msg = trim(lowertext(msg)) - var/tgs_tagged = "[sender](TGS/External)" - var/list/splits = splittext(compliant_msg, " ") - if(splits.len && splits[1] == "ticket") - if(splits.len < 2) - return TGS_AHELP_USAGE - switch(splits[2]) - if("close") - if(ticket) - ticket.Close(tgs_tagged) - return "Ticket #[ticket.id] successfully closed" - if("resolve") - if(ticket) - ticket.Resolve(tgs_tagged) - return "Ticket #[ticket.id] successfully resolved" - if("icissue") - if(ticket) - ticket.ICIssue(tgs_tagged) - return "Ticket #[ticket.id] successfully marked as IC issue" - if("reject") - if(ticket) - ticket.Reject(tgs_tagged) - return "Ticket #[ticket.id] successfully rejected" - if("reopen") - if(ticket) - return "Error: [target] already has ticket #[ticket.id] open" - var/fail = splits.len < 3 ? null : -1 - if(!isnull(fail)) - fail = text2num(splits[3]) - if(isnull(fail)) - return "Error: No/Invalid ticket id specified. [TGS_AHELP_USAGE]" - var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail) - if(!AH) - return "Error: Ticket #[fail] not found" - if(AH.initiator_ckey != target) - return "Error: Ticket #[fail] belongs to [AH.initiator_ckey]" - AH.Reopen() - return "Ticket #[ticket.id] successfully reopened" - if("list") - var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target) - if(!tickets.len) - return "None" - . = "" - for(var/I in tickets) - var/datum/admin_help/AH = I - if(.) - . += ", " - if(AH == ticket) - . += "Active: " - . += "#[AH.id]" - return - else - return TGS_AHELP_USAGE - return "Error: Ticket could not be found" - - var/static/stealthkey - var/adminname = CONFIG_GET(flag/show_irc_name) ? tgs_tagged : "Administrator" - - if(!C) - return "Error: No client" - - if(!stealthkey) - stealthkey = GenTgsStealthKey() - - msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) - if(!msg) - return "Error: No message" - - message_admins("External message from [sender] to [key_name_admin(C)] : [msg]") - log_admin_private("External PM: [sender] -> [key_name(C)] : [msg]") - msg = emoji_parse(msg) - - to_chat(C, "-- Administrator private message --", confidential = TRUE) - to_chat(C, "Admin PM from-[adminname]: [msg]", confidential = TRUE) - to_chat(C, "Click on the administrator's name to reply.", confidential = TRUE) - - admin_ticket_log(C, "PM From [tgs_tagged]: [msg]") - - window_flash(C, ignorepref = TRUE) - //always play non-admin recipients the adminhelp sound - SEND_SOUND(C, 'sound/effects/adminhelp.ogg') - - C.externalreplyamount = EXTERNALREPLYCOUNT - - return "Message Successful" - -/proc/GenTgsStealthKey() - var/num = (rand(0,1000)) - var/i = 0 - while(i == 0) - i = 1 - for(var/P in GLOB.stealthminID) - if(num == GLOB.stealthminID[P]) - num++ - i = 0 - var/stealth = "@[num2text(num)]" - GLOB.stealthminID["IRCKEY"] = stealth - return stealth - -#undef EXTERNALREPLYCOUNT +#define EXTERNALREPLYCOUNT 2 + +//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm +/client/proc/cmd_admin_pm_context(mob/M in GLOB.mob_list) + set category = null + set name = "Admin PM Mob" + if(!holder) + to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.", confidential = TRUE) + return + if( !ismob(M) || !M.client ) + return + cmd_admin_pm(M.client,null) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm +/client/proc/cmd_admin_pm_panel() + set category = "Admin" + set name = "Admin PM" + if(!holder) + to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.", confidential = TRUE) + return + var/list/client/targets[0] + for(var/client/T) + if(T.mob) + if(isnewplayer(T.mob)) + targets["(New Player) - [T]"] = T + else if(isobserver(T.mob)) + targets["[T.mob.name](Ghost) - [T]"] = T + else + targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T + else + targets["(No Mob) - [T]"] = T + var/target = input(src,"To whom shall we send a message?","Admin PM",null) as null|anything in sortList(targets) + cmd_admin_pm(targets[target],null) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_ahelp_reply(whom) + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) + return + var/client/C + if(istext(whom)) + if(whom[1] == "@") + whom = findStealthKey(whom) + C = GLOB.directory[whom] + else if(istype(whom, /client)) + C = whom + if(!C) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) + return + + var/datum/admin_help/AH = C.current_ticket + + if(AH) + message_admins("[key_name_admin(src)] has started replying to [key_name_admin(C, 0, 0)]'s admin help.") + var/msg = input(src,"Message:", "Private message to [C.holder?.fakekey ? "an Administrator" : key_name(C, 0, 0)].") as message|null + if (!msg) + message_admins("[key_name_admin(src)] has cancelled their reply to [key_name_admin(C, 0, 0)]'s admin help.") + return + cmd_admin_pm(whom, msg) + +//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM. +//Fetching a message if needed. src is the sender and C is the target client +/client/proc/cmd_admin_pm(whom, msg) + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) + return + + if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo + to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.", confidential = TRUE) + to_chat(src, "Message: [msg]", confidential = TRUE) + return + + var/client/recipient + var/external = 0 + if(istext(whom)) + if(whom[1] == "@") + whom = findStealthKey(whom) + if(whom == "IRCKEY") + external = 1 + else + recipient = GLOB.directory[whom] + else if(istype(whom, /client)) + recipient = whom + + + if(external) + if(!externalreplyamount) //to prevent people from spamming irc/discord + return + if(!msg) + msg = input(src,"Message:", "Private message to Administrator") as message|null + + if(!msg) + return + if(holder) + to_chat(src, "Error: Use the admin IRC/Discord channel, nerd.", confidential = TRUE) + return + + + else + if(!recipient) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) + if(msg) + to_chat(src, msg, confidential = TRUE) + return + else if(msg) // you want to continue if there's no message instead of returning now + current_ticket.MessageNoRecipient(msg) + return + + //get message text, limit it's length.and clean/escape html + if(!msg) + msg = input(src,"Message:", "Private message to [recipient.holder?.fakekey ? "an Administrator" : key_name(recipient, 0, 0)].") as message|null + msg = trim(msg) + if(!msg) + return + + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) + return + + if(!recipient) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) + else + current_ticket.MessageNoRecipient(msg) + return + + if (src.handle_spam_prevention(msg,MUTE_ADMINHELP)) + return + + //clean the message if it's not sent by a high-rank admin + if(!check_rights(R_SERVER|R_DEBUG,0)||external)//no sending html to the poor bots + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + if(!msg) + return + + var/rawmsg = msg + + if(holder) + msg = emoji_parse(msg) + + var/keywordparsedmsg = keywords_lookup(msg) + + if(external) + to_chat(src, "PM to-Admins: [rawmsg]", confidential = TRUE) + var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to External: [keywordparsedmsg]") + externalreplyamount-- + send2adminchat("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) + else + if(recipient.holder) + if(holder) //both are admins + to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]", confidential = TRUE) + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]", confidential = TRUE) + + //omg this is dumb, just fill in both their tickets + var/interaction_message = "PM from-[key_name(src, recipient, 1)] to-[key_name(recipient, src, 1)]: [keywordparsedmsg]" + admin_ticket_log(src, interaction_message) + if(recipient != src) //reeee + admin_ticket_log(recipient, interaction_message) + SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) + else //recipient is an admin but sender is not + var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]" + admin_ticket_log(src, "[replymsg]") + to_chat(recipient, "[replymsg]", confidential = TRUE) + to_chat(src, "PM to-Admins: [msg]", confidential = TRUE) + SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) + + //play the receiving admin the adminhelp sound (if they have them enabled) + if(recipient.prefs.toggles & SOUND_ADMINHELP) + SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) + + else + if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT + var/already_logged = FALSE + if(!recipient.current_ticket) + new /datum/admin_help(msg, recipient, TRUE) + already_logged = TRUE + SSblackbox.LogAhelp(recipient.current_ticket.id, "Ticket Opened", msg, recipient.ckey, src.ckey) + + to_chat(recipient, "-- Administrator private message --", confidential = TRUE) + to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]", confidential = TRUE) + to_chat(recipient, "Click on the administrator's name to reply.", confidential = TRUE) + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]", confidential = TRUE) + + admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]") + + if(!already_logged) //Reply to an existing ticket + SSblackbox.LogAhelp(recipient.current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) + + + //always play non-admin recipients the adminhelp sound + SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) + + //AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn + if(CONFIG_GET(flag/popup_admin_pm)) + INVOKE_ASYNC(src, .proc/popup_admin_pm, recipient, msg) + + else //neither are admins + to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.", confidential = TRUE) + return + + if(external) + log_admin_private("PM: [key_name(src)]->External: [rawmsg]") + for(var/client/X in GLOB.admins) + to_chat(X, "PM: [key_name(src, X, 0)]->External: [keywordparsedmsg]", confidential = TRUE) + else + window_flash(recipient, ignorepref = TRUE) + log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]") + //we don't use message_admins here because the sender/receiver might get it too + for(var/client/X in GLOB.admins) + if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient + to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" , confidential = TRUE) + +/client/proc/popup_admin_pm(client/recipient, msg) + var/sender = src + var/sendername = key + var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply + if(recipient && reply) + if(sender) + recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them + else + adminhelp(reply) //sender has left, adminhelp instead + +#define TGS_AHELP_USAGE "Usage: ticket " +/proc/TgsPm(target,msg,sender) + target = ckey(target) + var/client/C = GLOB.directory[target] + + var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target) + var/compliant_msg = trim(lowertext(msg)) + var/tgs_tagged = "[sender](TGS/External)" + var/list/splits = splittext(compliant_msg, " ") + if(splits.len && splits[1] == "ticket") + if(splits.len < 2) + return TGS_AHELP_USAGE + switch(splits[2]) + if("close") + if(ticket) + ticket.Close(tgs_tagged) + return "Ticket #[ticket.id] successfully closed" + if("resolve") + if(ticket) + ticket.Resolve(tgs_tagged) + return "Ticket #[ticket.id] successfully resolved" + if("icissue") + if(ticket) + ticket.ICIssue(tgs_tagged) + return "Ticket #[ticket.id] successfully marked as IC issue" + if("reject") + if(ticket) + ticket.Reject(tgs_tagged) + return "Ticket #[ticket.id] successfully rejected" + if("reopen") + if(ticket) + return "Error: [target] already has ticket #[ticket.id] open" + var/fail = splits.len < 3 ? null : -1 + if(!isnull(fail)) + fail = text2num(splits[3]) + if(isnull(fail)) + return "Error: No/Invalid ticket id specified. [TGS_AHELP_USAGE]" + var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail) + if(!AH) + return "Error: Ticket #[fail] not found" + if(AH.initiator_ckey != target) + return "Error: Ticket #[fail] belongs to [AH.initiator_ckey]" + AH.Reopen() + return "Ticket #[ticket.id] successfully reopened" + if("list") + var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target) + if(!tickets.len) + return "None" + . = "" + for(var/I in tickets) + var/datum/admin_help/AH = I + if(.) + . += ", " + if(AH == ticket) + . += "Active: " + . += "#[AH.id]" + return + else + return TGS_AHELP_USAGE + return "Error: Ticket could not be found" + + var/static/stealthkey + var/adminname = CONFIG_GET(flag/show_irc_name) ? tgs_tagged : "Administrator" + + if(!C) + return "Error: No client" + + if(!stealthkey) + stealthkey = GenTgsStealthKey() + + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + if(!msg) + return "Error: No message" + + message_admins("External message from [sender] to [key_name_admin(C)] : [msg]") + log_admin_private("External PM: [sender] -> [key_name(C)] : [msg]") + msg = emoji_parse(msg) + + to_chat(C, "-- Administrator private message --", confidential = TRUE) + to_chat(C, "Admin PM from-[adminname]: [msg]", confidential = TRUE) + to_chat(C, "Click on the administrator's name to reply.", confidential = TRUE) + + admin_ticket_log(C, "PM From [tgs_tagged]: [msg]") + + window_flash(C, ignorepref = TRUE) + //always play non-admin recipients the adminhelp sound + SEND_SOUND(C, 'sound/effects/adminhelp.ogg') + + C.externalreplyamount = EXTERNALREPLYCOUNT + + return "Message Successful" + +/proc/GenTgsStealthKey() + var/num = (rand(0,1000)) + var/i = 0 + while(i == 0) + i = 1 + for(var/P in GLOB.stealthminID) + if(num == GLOB.stealthminID[P]) + num++ + i = 0 + var/stealth = "@[num2text(num)]" + GLOB.stealthminID["IRCKEY"] = stealth + return stealth + +#undef EXTERNALREPLYCOUNT diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index 0d3e510836c..5793ff6d3c2 100644 --- a/code/modules/admin/verbs/adminsay.dm +++ b/code/modules/admin/verbs/adminsay.dm @@ -1,22 +1,22 @@ -/client/proc/cmd_admin_say(msg as text) - set category = "Admin" - set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite - set hidden = 1 - if(!check_rights(0)) - return - - msg = emoji_parse(copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)) - if(!msg) - return - - mob.log_talk(msg, LOG_ASAY) - msg = keywords_lookup(msg) - var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : "" - msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][msg][custom_asay_color ? "":null]" - to_chat(GLOB.admins, msg, confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/get_admin_say() - var/msg = input(src, null, "asay \"text\"") as text|null - cmd_admin_say(msg) +/client/proc/cmd_admin_say(msg as text) + set category = "Admin" + set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite + set hidden = 1 + if(!check_rights(0)) + return + + msg = emoji_parse(copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)) + if(!msg) + return + + mob.log_talk(msg, LOG_ASAY) + msg = keywords_lookup(msg) + var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : "" + msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][msg][custom_asay_color ? "":null]" + to_chat(GLOB.admins, msg, confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/get_admin_say() + var/msg = input(src, null, "asay \"text\"") as text|null + cmd_admin_say(msg) diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm index 42a9e07214e..5bddb039752 100644 --- a/code/modules/admin/verbs/atmosdebug.dm +++ b/code/modules/admin/verbs/atmosdebug.dm @@ -1,56 +1,56 @@ -/client/proc/atmosscan() - set category = "Mapping" - set name = "Check Plumbing" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Plumbing") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - //all plumbing - yes, some things might get stated twice, doesn't matter. - for(var/obj/machinery/atmospherics/components/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) - - //Manifolds - for(var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) - - //Pipes - for(var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) - -/client/proc/powerdebug() - set category = "Mapping" - set name = "Check Power" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - var/list/results = list() - - for (var/datum/powernet/PN in GLOB.powernets) - if (!PN.nodes || !PN.nodes.len) - if(PN.cables && (PN.cables.len > 1)) - var/obj/structure/cable/C = PN.cables[1] - results += "Powernet with no nodes! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" - - if (!PN.cables || (PN.cables.len < 10)) - if(PN.cables && (PN.cables.len > 1)) - var/obj/structure/cable/C = PN.cables[1] - results += "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" - - for(var/turf/T in world.contents) - var/found_one = FALSE - for(var/obj/structure/cable/C in T.contents) - if(found_one) - results += "Doubled wire at [ADMIN_VERBOSEJMP(C)]" - else - found_one = TRUE - var/obj/machinery/power/terminal/term = locate(/obj/machinery/power/terminal) in T.contents - if(term) - var/obj/structure/cable/C = locate(/obj/structure/cable) in T.contents - if(!C) - results += "Unwired terminal at [ADMIN_VERBOSEJMP(term)]" - to_chat(usr, "[results.Join("\n")]", confidential = TRUE) +/client/proc/atmosscan() + set category = "Mapping" + set name = "Check Plumbing" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Plumbing") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + //all plumbing - yes, some things might get stated twice, doesn't matter. + for(var/obj/machinery/atmospherics/components/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) + + //Manifolds + for(var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) + + //Pipes + for(var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) + +/client/proc/powerdebug() + set category = "Mapping" + set name = "Check Power" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + var/list/results = list() + + for (var/datum/powernet/PN in GLOB.powernets) + if (!PN.nodes || !PN.nodes.len) + if(PN.cables && (PN.cables.len > 1)) + var/obj/structure/cable/C = PN.cables[1] + results += "Powernet with no nodes! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" + + if (!PN.cables || (PN.cables.len < 10)) + if(PN.cables && (PN.cables.len > 1)) + var/obj/structure/cable/C = PN.cables[1] + results += "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" + + for(var/turf/T in world.contents) + var/found_one = FALSE + for(var/obj/structure/cable/C in T.contents) + if(found_one) + results += "Doubled wire at [ADMIN_VERBOSEJMP(C)]" + else + found_one = TRUE + var/obj/machinery/power/terminal/term = locate(/obj/machinery/power/terminal) in T.contents + if(term) + var/obj/structure/cable/C = locate(/obj/structure/cable) in T.contents + if(!C) + results += "Unwired terminal at [ADMIN_VERBOSEJMP(term)]" + to_chat(usr, "[results.Join("\n")]", confidential = TRUE) diff --git a/code/modules/admin/verbs/cinematic.dm b/code/modules/admin/verbs/cinematic.dm index 1ef35187740..455cfaaedd7 100644 --- a/code/modules/admin/verbs/cinematic.dm +++ b/code/modules/admin/verbs/cinematic.dm @@ -1,11 +1,11 @@ -/client/proc/cinematic() - set name = "cinematic" - set category = "Fun" - set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted. - set hidden = 1 - if(!SSticker) - return - - var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as anything in sortList(subtypesof(/datum/cinematic), /proc/cmp_typepaths_asc) - if(choice) - Cinematic(initial(choice.id),world,null) +/client/proc/cinematic() + set name = "cinematic" + set category = "Fun" + set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted. + set hidden = 1 + if(!SSticker) + return + + var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as anything in sortList(subtypesof(/datum/cinematic), /proc/cmp_typepaths_asc) + if(choice) + Cinematic(initial(choice.id),world,null) diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm index a1af36e23a1..e0b35fb0c80 100644 --- a/code/modules/admin/verbs/deadsay.dm +++ b/code/modules/admin/verbs/deadsay.dm @@ -1,43 +1,43 @@ -/client/proc/dsay(msg as text) - set category = "Admin - Game" - set name = "Dsay" - set hidden = 1 - if(!holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - if(!mob) - return - if(prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot send DSAY messages (muted).", confidential = TRUE) - return - - if (handle_spam_prevention(msg,MUTE_DEADCHAT)) - return - - msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) - mob.log_talk(msg, LOG_DSAY) - - if (!msg) - return - var/rank_name = holder.rank - var/admin_name = key - if(holder.fakekey) - rank_name = pick(strings("admin_nicknames.json", "ranks", "config")) - admin_name = pick(strings("admin_nicknames.json", "names", "config")) - var/rendered = "DEAD: [rank_name]([admin_name]) says, \"[emoji_parse(msg)]\"" - - for (var/mob/M in GLOB.player_list) - if(isnewplayer(M)) - continue - if (M.stat == DEAD || (M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above - to_chat(M, rendered, confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/get_dead_say() - var/msg = input(src, null, "dsay \"text\"") as text|null - - if (isnull(msg)) - return - - dsay(msg) +/client/proc/dsay(msg as text) + set category = "Admin - Game" + set name = "Dsay" + set hidden = 1 + if(!holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + if(!mob) + return + if(prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot send DSAY messages (muted).", confidential = TRUE) + return + + if (handle_spam_prevention(msg,MUTE_DEADCHAT)) + return + + msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) + mob.log_talk(msg, LOG_DSAY) + + if (!msg) + return + var/rank_name = holder.rank + var/admin_name = key + if(holder.fakekey) + rank_name = pick(strings("admin_nicknames.json", "ranks", "config")) + admin_name = pick(strings("admin_nicknames.json", "names", "config")) + var/rendered = "DEAD: [rank_name]([admin_name]) says, \"[emoji_parse(msg)]\"" + + for (var/mob/M in GLOB.player_list) + if(isnewplayer(M)) + continue + if (M.stat == DEAD || (M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above + to_chat(M, rendered, confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/get_dead_say() + var/msg = input(src, null, "dsay \"text\"") as text|null + + if (isnull(msg)) + return + + dsay(msg) diff --git a/code/modules/admin/verbs/fps.dm b/code/modules/admin/verbs/fps.dm index 4eb8dd8bc10..a3e7c5f5df0 100644 --- a/code/modules/admin/verbs/fps.dm +++ b/code/modules/admin/verbs/fps.dm @@ -1,26 +1,26 @@ -//replaces the old Ticklag verb, fps is easier to understand -/client/proc/set_server_fps() - set category = "Debug" - set name = "Set Server FPS" - set desc = "Sets game speed in frames-per-second. Can potentially break the game" - - if(!check_rights(R_DEBUG)) - return - - var/cfg_fps = CONFIG_GET(number/fps) - var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null) - - if(new_fps <= 0) - to_chat(src, "Error: set_server_fps(): Invalid world.fps value. No changes made.", confidential = TRUE) - return - if(new_fps > cfg_fps * 1.5) - if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm") - return - - var/msg = "[key_name(src)] has modified world.fps to [new_fps]" - log_admin(msg, 0) - message_admins(msg, 0) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - CONFIG_SET(number/fps, new_fps) - world.change_fps(new_fps) +//replaces the old Ticklag verb, fps is easier to understand +/client/proc/set_server_fps() + set category = "Debug" + set name = "Set Server FPS" + set desc = "Sets game speed in frames-per-second. Can potentially break the game" + + if(!check_rights(R_DEBUG)) + return + + var/cfg_fps = CONFIG_GET(number/fps) + var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null) + + if(new_fps <= 0) + to_chat(src, "Error: set_server_fps(): Invalid world.fps value. No changes made.", confidential = TRUE) + return + if(new_fps > cfg_fps * 1.5) + if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm") + return + + var/msg = "[key_name(src)] has modified world.fps to [new_fps]" + log_admin(msg, 0) + message_admins(msg, 0) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + CONFIG_SET(number/fps, new_fps) + world.change_fps(new_fps) diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm index a07e1fb1e9e..1f706091c6b 100644 --- a/code/modules/admin/verbs/getlogs.dm +++ b/code/modules/admin/verbs/getlogs.dm @@ -1,35 +1,35 @@ -//This proc allows download of past server logs saved within the data/logs/ folder. -/client/proc/getserverlogs() - set name = "Get Server Logs" - set desc = "View/retrieve logfiles." - set category = "Admin" - - browseserverlogs() - -/client/proc/getcurrentlogs() - set name = "Get Current Logs" - set desc = "View/retrieve logfiles for the current round." - set category = "Admin" - - browseserverlogs(current=TRUE) - -/client/proc/browseserverlogs(current=FALSE) - var/path = browse_files(current ? BROWSE_ROOT_CURRENT_LOGS : BROWSE_ROOT_ALL_LOGS) - if(!path) - return - - if(file_spam_check()) - return - - message_admins("[key_name_admin(src)] accessed file: [path]") - switch(alert("View (in game), Open (in your system's text editor), or Download?", path, "View", "Open", "Download")) - if ("View") - src << browse("
                [html_encode(file2text(file(path)))]
                ", list2params(list("window" = "viewfile.[path]"))) - if ("Open") - src << run(file(path)) - if ("Download") - src << ftp(file(path)) - else - return - to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.", confidential = TRUE) - return +//This proc allows download of past server logs saved within the data/logs/ folder. +/client/proc/getserverlogs() + set name = "Get Server Logs" + set desc = "View/retrieve logfiles." + set category = "Admin" + + browseserverlogs() + +/client/proc/getcurrentlogs() + set name = "Get Current Logs" + set desc = "View/retrieve logfiles for the current round." + set category = "Admin" + + browseserverlogs(current=TRUE) + +/client/proc/browseserverlogs(current=FALSE) + var/path = browse_files(current ? BROWSE_ROOT_CURRENT_LOGS : BROWSE_ROOT_ALL_LOGS) + if(!path) + return + + if(file_spam_check()) + return + + message_admins("[key_name_admin(src)] accessed file: [path]") + switch(alert("View (in game), Open (in your system's text editor), or Download?", path, "View", "Open", "Download")) + if ("View") + src << browse("
                [html_encode(file2text(file(path)))]
                ", list2params(list("window" = "viewfile.[path]"))) + if ("Open") + src << run(file(path)) + if ("Download") + src << ftp(file(path)) + else + return + to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.", confidential = TRUE) + return diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 9762d7fefd6..9b1426b91d9 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -1,377 +1,377 @@ -//- Are all the floors with or without air, as they should be? (regular or airless) -//- Does the area have an APC? -//- Does the area have an Air Alarm? -//- Does the area have a Request Console? -//- Does the area have lights? -//- Does the area have a light switch? -//- Does the area have enough intercoms? -//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug) -//- Is the area connected to the scrubbers air loop? -//- Is the area connected to the vent air loop? (vent pumps) -//- Is everything wired properly? -//- Does the area have a fire alarm and firedoors? -//- Do all pod doors work properly? -//- Are accesses set properly on doors, pod buttons, etc. -//- Are all items placed properly? (not below vents, scrubbers, tables) -//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room? -//- Check for any misplaced or stacked piece of pipe (air and disposal) -//- Check for any misplaced or stacked piece of wire -//- Identify how hard it is to break into the area and where the weak points are -//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels. - -GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( - /client/proc/camera_view, //-errorage - /client/proc/sec_camera_report, //-errorage - /client/proc/intercom_view, //-errorage - /client/proc/air_status, //Air things - /client/proc/Cell, //More air things - /client/proc/atmosscan, //check plumbing - /client/proc/powerdebug, //check power - /client/proc/count_objects_on_z_level, - /client/proc/count_objects_all, - /client/proc/cmd_assume_direct_control, //-errorage - /client/proc/cmd_give_direct_control, - /client/proc/startSinglo, - /client/proc/set_server_fps, //allows you to set the ticklag. - /client/proc/cmd_admin_grantfullaccess, - /client/proc/cmd_admin_areatest_all, - /client/proc/cmd_admin_areatest_station, - #ifdef TESTING - /client/proc/see_dirty_varedits, - #endif - /client/proc/cmd_admin_test_atmos_controllers, - /client/proc/cmd_admin_rejuvenate, - /datum/admins/proc/show_traitor_panel, - /client/proc/disable_communication, - /client/proc/cmd_show_at_list, - /client/proc/cmd_show_at_markers, - /client/proc/manipulate_organs, - /client/proc/start_line_profiling, - /client/proc/stop_line_profiling, - /client/proc/show_line_profiling, - /client/proc/create_mapping_job_icons, - /client/proc/debug_z_levels, - /client/proc/place_ruin -)) -GLOBAL_PROTECT(admin_verbs_debug_mapping) - -/obj/effect/debugging/mapfix_marker - name = "map fix marker" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "mapfixmarker" - desc = "I am a mappers mistake." - -/obj/effect/debugging/marker - icon = 'icons/turf/areas.dmi' - icon_state = "yellow" - -/obj/effect/debugging/marker/Move() - return 0 - -/client/proc/camera_view() - set category = "Mapping" - set name = "Camera Range Display" - - var/on = FALSE - for(var/turf/T in world) - if(T.maptext) - on = TRUE - T.maptext = null - - if(!on) - var/list/seen = list() - for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) - for(var/turf/T in C.can_see()) - seen[T]++ - for(var/turf/T in seen) - T.maptext = "[seen[T]]" - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") - -#ifdef TESTING -GLOBAL_LIST_EMPTY(dirty_vars) - -/client/proc/see_dirty_varedits() - set category = "Mapping" - set name = "Dirty Varedits" - - var/list/dat = list() - dat += "

                Abandon all hope ye who enter here



                " - for(var/thing in GLOB.dirty_vars) - dat += "[thing]
                " - CHECK_TICK - var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750) - popup.set_content(dat.Join()) - popup.open() -#endif - -/client/proc/sec_camera_report() - set category = "Mapping" - set name = "Camera Report" - - if(!Master) - alert(usr,"Master_controller not found.","Sec Camera Report") - return 0 - - var/list/obj/machinery/camera/CL = list() - - for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) - CL += C - - var/output = {"Camera Abnormalities Report
                -The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.
                  "} - - for(var/obj/machinery/camera/C1 in CL) - for(var/obj/machinery/camera/C2 in CL) - if(C1 != C2) - if(C1.c_tag == C2.c_tag) - output += "
                • c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]
                • " - if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y) - output += "
                • FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                • " - if(C1.loc == C2.loc) - output += "
                • Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                • " - var/turf/T = get_step(C1,turn(C1.dir,180)) - if(!T || !isturf(T) || !T.density ) - if(!(locate(/obj/structure/grille) in T)) - var/window_check = 0 - for(var/obj/structure/window/W in T) - if (W.dir == turn(C1.dir,180) || (W.dir in list(NORTHEAST,SOUTHEAST,NORTHWEST,SOUTHWEST)) ) - window_check = 1 - break - if(!window_check) - output += "
                • Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]
                • " - - output += "
                " - usr << browse(output,"window=airreport;size=1000x500") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/intercom_view() - set category = "Mapping" - set name = "Intercom Range Display" - - var/static/intercom_range_display_status = FALSE - intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something - - for(var/obj/effect/debugging/marker/M in world) - qdel(M) - - if(intercom_range_display_status) - for(var/obj/item/radio/intercom/I in world) - for(var/turf/T in orange(7,I)) - var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T) - if (!(F in view(7,I.loc))) - qdel(F) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_show_at_list() - set category = "Mapping" - set name = "Show roundstart AT list" - set desc = "Displays a list of active turfs coordinates at roundstart" - - var/dat = {"Coordinate list of Active Turfs at Roundstart -
                Real-time Active Turfs list you can see in Air Subsystem at active_turfs var
                "} - - for(var/t in GLOB.active_turfs_startlist) - var/turf/T = t - dat += "[ADMIN_VERBOSEJMP(T)]\n" - dat += "
                " - - usr << browse(dat, "window=at_list") - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_show_at_markers() - set category = "Mapping" - set name = "Show roundstart AT markers" - set desc = "Places a marker on all active-at-roundstart turfs" - - var/count = 0 - for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers) - qdel(AT) - count++ - - if(count) - to_chat(usr, "[count] AT markers removed.", confidential = TRUE) - else - for(var/t in GLOB.active_turfs_startlist) - new /obj/effect/abstract/marker/at(t) - count++ - to_chat(usr, "[count] AT markers placed.", confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers") - -/client/proc/enable_debug_verbs() - set category = "Debug" - set name = "Debug verbs - Enable" - if(!check_rights(R_DEBUG)) - return - verbs -= /client/proc/enable_debug_verbs - verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/disable_debug_verbs() - set category = "Debug" - set name = "Debug verbs - Disable" - verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) - verbs += /client/proc/enable_debug_verbs - SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/count_objects_on_z_level() - set category = "Mapping" - set name = "Count Objects On Level" - var/level = input("Which z-level?","Level?") as text|null - if(!level) - return - var/num_level = text2num(level) - if(!num_level) - return - if(!isnum(num_level)) - return - - var/type_text = input("Which type path?","Path?") as text|null - if(!type_text) - return - var/type_path = text2path(type_text) - if(!type_path) - return - - var/count = 0 - - var/list/atom/atom_list = list() - - for(var/atom/A in world) - if(istype(A,type_path)) - var/atom/B = A - while(!(isturf(B.loc))) - if(B && B.loc) - B = B.loc - else - break - if(B) - if(B.z == num_level) - count++ - atom_list += A - - to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/count_objects_all() - set category = "Mapping" - set name = "Count Objects All" - - var/type_text = input("Which type path?","") as text|null - if(!type_text) - return - var/type_path = text2path(type_text) - if(!type_path) - return - - var/count = 0 - - for(var/atom/A in world) - if(istype(A,type_path)) - count++ - - to_chat(world, "There are [count] objects of type [type_path] in the game world", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -//This proc is intended to detect lag problems relating to communication procs -GLOBAL_VAR_INIT(say_disabled, FALSE) -/client/proc/disable_communication() - set category = "Mapping" - set name = "Disable all communication verbs" - - GLOB.say_disabled = !GLOB.say_disabled - if(GLOB.say_disabled) - message_admins("[key] used 'Disable all communication verbs', killing all communication methods.") - else - message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.") - -//This generates the icon states for job starting location landmarks. -/client/proc/create_mapping_job_icons() - set name = "Generate job landmarks icons" - set category = "Mapping" - var/icon/final = icon() - var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted - D.setDir(SOUTH) - for(var/job in subtypesof(/datum/job)) - var/datum/job/JB = new job - switch(JB.title) - if("AI") - final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI") - if("Cyborg") - final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg") - else - for(var/obj/item/I in D) - qdel(I) - randomize_human(D) - JB.equip(D, TRUE, FALSE) - COMPILE_OVERLAYS(D) - var/icon/I = icon(getFlatIcon(D), frame = 1) - final.Insert(I, JB.title) - qdel(D) - //Also add the x - for(var/x_number in 1 to 4) - final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]") - fcopy(final, "icons/mob/landmarks.dmi") - -/client/proc/debug_z_levels() - set name = "Debug Z-Levels" - set category = "Mapping" - - var/list/z_list = SSmapping.z_list - var/list/messages = list() - messages += "World: [world.maxx] x [world.maxy] x [world.maxz]
                " - - var/list/linked_levels = list() - var/min_x = INFINITY - var/min_y = INFINITY - var/max_x = -INFINITY - var/max_y = -INFINITY - - for(var/z in 1 to max(world.maxz, z_list.len)) - if (z > z_list.len) - messages += "[z]: Unmanaged (out of bounds)
                " - continue - var/datum/space_level/S = z_list[z] - if (!S) - messages += "[z]: Unmanaged (null)
                " - continue - var/linkage - switch (S.linkage) - if (UNAFFECTED) - linkage = "no linkage" - if (SELFLOOPING) - linkage = "self-looping" - if (CROSSLINKED) - linkage = "linked at ([S.xi], [S.yi])" - linked_levels += S - min_x = min(min_x, S.xi) - min_y = min(min_y, S.yi) - max_x = max(max_x, S.xi) - max_y = max(max_y, S.yi) - else - linkage = "unknown linkage '[S.linkage]'" - - messages += "[z]: [S.name], [linkage], traits: [json_encode(S.traits)]
                " - if (S.z_value != z) - messages += "-- z_value is [S.z_value], should be [z]
                " - if (S.name == initial(S.name)) - messages += "-- name not set
                " - if (z > world.maxz) - messages += "-- exceeds max z" - - var/grid[max_x - min_x + 1][max_y - min_y + 1] - for(var/datum/space_level/S in linked_levels) - grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value - - messages += "" - for(var/y in max_y to min_y step -1) - var/list/part = list() - for(var/x in min_x to max_x) - part += "[grid[x - min_x + 1][y - min_y + 1]]" - messages += "" - messages += "
                [part.Join("")]
                " - - to_chat(src, messages.Join(""), confidential = TRUE) +//- Are all the floors with or without air, as they should be? (regular or airless) +//- Does the area have an APC? +//- Does the area have an Air Alarm? +//- Does the area have a Request Console? +//- Does the area have lights? +//- Does the area have a light switch? +//- Does the area have enough intercoms? +//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug) +//- Is the area connected to the scrubbers air loop? +//- Is the area connected to the vent air loop? (vent pumps) +//- Is everything wired properly? +//- Does the area have a fire alarm and firedoors? +//- Do all pod doors work properly? +//- Are accesses set properly on doors, pod buttons, etc. +//- Are all items placed properly? (not below vents, scrubbers, tables) +//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room? +//- Check for any misplaced or stacked piece of pipe (air and disposal) +//- Check for any misplaced or stacked piece of wire +//- Identify how hard it is to break into the area and where the weak points are +//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels. + +GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( + /client/proc/camera_view, //-errorage + /client/proc/sec_camera_report, //-errorage + /client/proc/intercom_view, //-errorage + /client/proc/air_status, //Air things + /client/proc/Cell, //More air things + /client/proc/atmosscan, //check plumbing + /client/proc/powerdebug, //check power + /client/proc/count_objects_on_z_level, + /client/proc/count_objects_all, + /client/proc/cmd_assume_direct_control, //-errorage + /client/proc/cmd_give_direct_control, + /client/proc/startSinglo, + /client/proc/set_server_fps, //allows you to set the ticklag. + /client/proc/cmd_admin_grantfullaccess, + /client/proc/cmd_admin_areatest_all, + /client/proc/cmd_admin_areatest_station, + #ifdef TESTING + /client/proc/see_dirty_varedits, + #endif + /client/proc/cmd_admin_test_atmos_controllers, + /client/proc/cmd_admin_rejuvenate, + /datum/admins/proc/show_traitor_panel, + /client/proc/disable_communication, + /client/proc/cmd_show_at_list, + /client/proc/cmd_show_at_markers, + /client/proc/manipulate_organs, + /client/proc/start_line_profiling, + /client/proc/stop_line_profiling, + /client/proc/show_line_profiling, + /client/proc/create_mapping_job_icons, + /client/proc/debug_z_levels, + /client/proc/place_ruin +)) +GLOBAL_PROTECT(admin_verbs_debug_mapping) + +/obj/effect/debugging/mapfix_marker + name = "map fix marker" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "mapfixmarker" + desc = "I am a mappers mistake." + +/obj/effect/debugging/marker + icon = 'icons/turf/areas.dmi' + icon_state = "yellow" + +/obj/effect/debugging/marker/Move() + return 0 + +/client/proc/camera_view() + set category = "Mapping" + set name = "Camera Range Display" + + var/on = FALSE + for(var/turf/T in world) + if(T.maptext) + on = TRUE + T.maptext = null + + if(!on) + var/list/seen = list() + for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) + for(var/turf/T in C.can_see()) + seen[T]++ + for(var/turf/T in seen) + T.maptext = "[seen[T]]" + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") + +#ifdef TESTING +GLOBAL_LIST_EMPTY(dirty_vars) + +/client/proc/see_dirty_varedits() + set category = "Mapping" + set name = "Dirty Varedits" + + var/list/dat = list() + dat += "

                Abandon all hope ye who enter here



                " + for(var/thing in GLOB.dirty_vars) + dat += "[thing]
                " + CHECK_TICK + var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750) + popup.set_content(dat.Join()) + popup.open() +#endif + +/client/proc/sec_camera_report() + set category = "Mapping" + set name = "Camera Report" + + if(!Master) + alert(usr,"Master_controller not found.","Sec Camera Report") + return 0 + + var/list/obj/machinery/camera/CL = list() + + for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) + CL += C + + var/output = {"Camera Abnormalities Report
                +The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.
                  "} + + for(var/obj/machinery/camera/C1 in CL) + for(var/obj/machinery/camera/C2 in CL) + if(C1 != C2) + if(C1.c_tag == C2.c_tag) + output += "
                • c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]
                • " + if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y) + output += "
                • FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                • " + if(C1.loc == C2.loc) + output += "
                • Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                • " + var/turf/T = get_step(C1,turn(C1.dir,180)) + if(!T || !isturf(T) || !T.density ) + if(!(locate(/obj/structure/grille) in T)) + var/window_check = 0 + for(var/obj/structure/window/W in T) + if (W.dir == turn(C1.dir,180) || (W.dir in list(NORTHEAST,SOUTHEAST,NORTHWEST,SOUTHWEST)) ) + window_check = 1 + break + if(!window_check) + output += "
                • Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]
                • " + + output += "
                " + usr << browse(output,"window=airreport;size=1000x500") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/intercom_view() + set category = "Mapping" + set name = "Intercom Range Display" + + var/static/intercom_range_display_status = FALSE + intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something + + for(var/obj/effect/debugging/marker/M in world) + qdel(M) + + if(intercom_range_display_status) + for(var/obj/item/radio/intercom/I in world) + for(var/turf/T in orange(7,I)) + var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T) + if (!(F in view(7,I.loc))) + qdel(F) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_show_at_list() + set category = "Mapping" + set name = "Show roundstart AT list" + set desc = "Displays a list of active turfs coordinates at roundstart" + + var/dat = {"Coordinate list of Active Turfs at Roundstart +
                Real-time Active Turfs list you can see in Air Subsystem at active_turfs var
                "} + + for(var/t in GLOB.active_turfs_startlist) + var/turf/T = t + dat += "[ADMIN_VERBOSEJMP(T)]\n" + dat += "
                " + + usr << browse(dat, "window=at_list") + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_show_at_markers() + set category = "Mapping" + set name = "Show roundstart AT markers" + set desc = "Places a marker on all active-at-roundstart turfs" + + var/count = 0 + for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers) + qdel(AT) + count++ + + if(count) + to_chat(usr, "[count] AT markers removed.", confidential = TRUE) + else + for(var/t in GLOB.active_turfs_startlist) + new /obj/effect/abstract/marker/at(t) + count++ + to_chat(usr, "[count] AT markers placed.", confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers") + +/client/proc/enable_debug_verbs() + set category = "Debug" + set name = "Debug verbs - Enable" + if(!check_rights(R_DEBUG)) + return + verbs -= /client/proc/enable_debug_verbs + verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/disable_debug_verbs() + set category = "Debug" + set name = "Debug verbs - Disable" + verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) + verbs += /client/proc/enable_debug_verbs + SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/count_objects_on_z_level() + set category = "Mapping" + set name = "Count Objects On Level" + var/level = input("Which z-level?","Level?") as text|null + if(!level) + return + var/num_level = text2num(level) + if(!num_level) + return + if(!isnum(num_level)) + return + + var/type_text = input("Which type path?","Path?") as text|null + if(!type_text) + return + var/type_path = text2path(type_text) + if(!type_path) + return + + var/count = 0 + + var/list/atom/atom_list = list() + + for(var/atom/A in world) + if(istype(A,type_path)) + var/atom/B = A + while(!(isturf(B.loc))) + if(B && B.loc) + B = B.loc + else + break + if(B) + if(B.z == num_level) + count++ + atom_list += A + + to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/count_objects_all() + set category = "Mapping" + set name = "Count Objects All" + + var/type_text = input("Which type path?","") as text|null + if(!type_text) + return + var/type_path = text2path(type_text) + if(!type_path) + return + + var/count = 0 + + for(var/atom/A in world) + if(istype(A,type_path)) + count++ + + to_chat(world, "There are [count] objects of type [type_path] in the game world", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +//This proc is intended to detect lag problems relating to communication procs +GLOBAL_VAR_INIT(say_disabled, FALSE) +/client/proc/disable_communication() + set category = "Mapping" + set name = "Disable all communication verbs" + + GLOB.say_disabled = !GLOB.say_disabled + if(GLOB.say_disabled) + message_admins("[key] used 'Disable all communication verbs', killing all communication methods.") + else + message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.") + +//This generates the icon states for job starting location landmarks. +/client/proc/create_mapping_job_icons() + set name = "Generate job landmarks icons" + set category = "Mapping" + var/icon/final = icon() + var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted + D.setDir(SOUTH) + for(var/job in subtypesof(/datum/job)) + var/datum/job/JB = new job + switch(JB.title) + if("AI") + final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI") + if("Cyborg") + final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg") + else + for(var/obj/item/I in D) + qdel(I) + randomize_human(D) + JB.equip(D, TRUE, FALSE) + COMPILE_OVERLAYS(D) + var/icon/I = icon(getFlatIcon(D), frame = 1) + final.Insert(I, JB.title) + qdel(D) + //Also add the x + for(var/x_number in 1 to 4) + final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]") + fcopy(final, "icons/mob/landmarks.dmi") + +/client/proc/debug_z_levels() + set name = "Debug Z-Levels" + set category = "Mapping" + + var/list/z_list = SSmapping.z_list + var/list/messages = list() + messages += "World: [world.maxx] x [world.maxy] x [world.maxz]
                " + + var/list/linked_levels = list() + var/min_x = INFINITY + var/min_y = INFINITY + var/max_x = -INFINITY + var/max_y = -INFINITY + + for(var/z in 1 to max(world.maxz, z_list.len)) + if (z > z_list.len) + messages += "[z]: Unmanaged (out of bounds)
                " + continue + var/datum/space_level/S = z_list[z] + if (!S) + messages += "[z]: Unmanaged (null)
                " + continue + var/linkage + switch (S.linkage) + if (UNAFFECTED) + linkage = "no linkage" + if (SELFLOOPING) + linkage = "self-looping" + if (CROSSLINKED) + linkage = "linked at ([S.xi], [S.yi])" + linked_levels += S + min_x = min(min_x, S.xi) + min_y = min(min_y, S.yi) + max_x = max(max_x, S.xi) + max_y = max(max_y, S.yi) + else + linkage = "unknown linkage '[S.linkage]'" + + messages += "[z]: [S.name], [linkage], traits: [json_encode(S.traits)]
                " + if (S.z_value != z) + messages += "-- z_value is [S.z_value], should be [z]
                " + if (S.name == initial(S.name)) + messages += "-- name not set
                " + if (z > world.maxz) + messages += "-- exceeds max z" + + var/grid[max_x - min_x + 1][max_y - min_y + 1] + for(var/datum/space_level/S in linked_levels) + grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value + + messages += "" + for(var/y in max_y to min_y step -1) + var/list/part = list() + for(var/x in min_x to max_x) + part += "[grid[x - min_x + 1][y - min_y + 1]]" + messages += "" + messages += "
                [part.Join("")]
                " + + to_chat(src, messages.Join(""), confidential = TRUE) diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm index 33da462ce8e..3bbaca9529f 100644 --- a/code/modules/admin/verbs/onlyone.dm +++ b/code/modules/admin/verbs/onlyone.dm @@ -1,31 +1,31 @@ -GLOBAL_VAR_INIT(highlander, FALSE) -/client/proc/only_one() //Gives everyone kilts, berets, claymores, and pinpointers, with the objective to hijack the emergency shuttle. - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - return - GLOB.highlander = TRUE - - send_to_playing_players("THERE CAN BE ONLY ONE") - - for(var/obj/item/disk/nuclear/N in GLOB.poi_list) - var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving) - if (component) - component.relocate() //Gets it out of bags and such - - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(H.stat == DEAD) - continue - H.make_scottish() - - message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ONE!") - log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.") - addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50) - -/client/proc/only_one_delayed() - send_to_playing_players("Bagpipes begin to blare. You feel Scottish pride coming over you.") - message_admins("[key_name_admin(usr)] used (delayed) THERE CAN BE ONLY ONE!") - log_admin("[key_name(usr)] used delayed THERE CAN BE ONLY ONE.") - addtimer(CALLBACK(src, .proc/only_one), 420) - -/mob/living/carbon/human/proc/make_scottish() - mind.add_antag_datum(/datum/antagonist/highlander) +GLOBAL_VAR_INIT(highlander, FALSE) +/client/proc/only_one() //Gives everyone kilts, berets, claymores, and pinpointers, with the objective to hijack the emergency shuttle. + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + GLOB.highlander = TRUE + + send_to_playing_players("THERE CAN BE ONLY ONE") + + for(var/obj/item/disk/nuclear/N in GLOB.poi_list) + var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving) + if (component) + component.relocate() //Gets it out of bags and such + + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(H.stat == DEAD) + continue + H.make_scottish() + + message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ONE!") + log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.") + addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50) + +/client/proc/only_one_delayed() + send_to_playing_players("Bagpipes begin to blare. You feel Scottish pride coming over you.") + message_admins("[key_name_admin(usr)] used (delayed) THERE CAN BE ONLY ONE!") + log_admin("[key_name(usr)] used delayed THERE CAN BE ONLY ONE.") + addtimer(CALLBACK(src, .proc/only_one), 420) + +/mob/living/carbon/human/proc/make_scottish() + mind.add_antag_datum(/datum/antagonist/highlander) diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index b731f85e21d..91fb01ccdf8 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -1,177 +1,177 @@ -/client/proc/play_sound(S as sound) - set category = "Fun" - set name = "Play Global Sound" - if(!check_rights(R_SOUND)) - return - - var/freq = 1 - var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num - if(!vol) - return - vol = clamp(vol, 1, 100) - - var/sound/admin_sound = new() - admin_sound.file = S - admin_sound.priority = 250 - admin_sound.channel = CHANNEL_ADMIN - admin_sound.frequency = freq - admin_sound.wait = 1 - admin_sound.repeat = 0 - admin_sound.status = SOUND_STREAM - admin_sound.volume = vol - - var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel") - switch(res) - if("Yes") - to_chat(world, "An admin played: [S]", confidential = TRUE) - if("Cancel") - return - - log_admin("[key_name(src)] played sound [S]") - message_admins("[key_name_admin(src)] played sound [S]") - - for(var/mob/M in GLOB.player_list) - if(M.client.prefs.toggles & SOUND_MIDI) - var/user_vol = M.client.chatOutput.adminMusicVolume - if(user_vol) - admin_sound.volume = vol * (user_vol / 100) - SEND_SOUND(M, admin_sound) - admin_sound.volume = vol - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/play_local_sound(S as sound) - set category = "Fun" - set name = "Play Local Sound" - if(!check_rights(R_SOUND)) - return - - log_admin("[key_name(src)] played a local sound [S]") - message_admins("[key_name_admin(src)] played a local sound [S]") - playsound(get_turf(src.mob), S, 50, FALSE, FALSE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/play_direct_mob_sound(S as sound, mob/M) - set category = "Fun" - set name = "Play Direct Mob Sound" - if(!check_rights(R_SOUND)) - return - - if(!M) - M = input(usr, "Choose a mob to play the sound to. Only they will hear it.", "Play Mob Sound") as null|anything in sortNames(GLOB.player_list) - if(!M || QDELETED(M)) - return - log_admin("[key_name(src)] played a direct mob sound [S] to [M].") - message_admins("[key_name_admin(src)] played a direct mob sound [S] to [ADMIN_LOOKUPFLW(M)].") - SEND_SOUND(M, S) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Direct Mob Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/play_web_sound() - set category = "Fun" - set name = "Play Internet Sound" - if(!check_rights(R_SOUND)) - return - - var/ytdl = CONFIG_GET(string/invoke_youtubedl) - if(!ytdl) - to_chat(src, "Youtube-dl was not configured, action unavailable", confidential = TRUE) //Check config.txt for the INVOKE_YOUTUBEDL value - return - - var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null - if(istext(web_sound_input)) - var/web_sound_url = "" - var/stop_web_sounds = FALSE - var/list/music_extra_data = list() - if(length(web_sound_input)) - - web_sound_input = trim(web_sound_input) - if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol)) - to_chat(src, "Non-http(s) URIs are not allowed.", confidential = TRUE) - to_chat(src, "For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.", confidential = TRUE) - return - var/shell_scrubbed_input = shell_url_scrub(web_sound_input) - var/list/output = world.shelleo("[ytdl] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"") - var/errorlevel = output[SHELLEO_ERRORLEVEL] - var/stdout = output[SHELLEO_STDOUT] - var/stderr = output[SHELLEO_STDERR] - if(!errorlevel) - var/list/data - try - data = json_decode(stdout) - catch(var/exception/e) - to_chat(src, "Youtube-dl JSON parsing FAILED:", confidential = TRUE) - to_chat(src, "[e]: [stdout]", confidential = TRUE) - return - - if (data["url"]) - web_sound_url = data["url"] - var/title = "[data["title"]]" - var/webpage_url = title - if (data["webpage_url"]) - webpage_url = "[title]" - music_extra_data["start"] = data["start_time"] - music_extra_data["end"] = data["end_time"] - - var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel") - switch(res) - if("Yes") - to_chat(world, "An admin played: [webpage_url]", confidential = TRUE) - if("Cancel") - return - - SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]")) - log_admin("[key_name(src)] played web sound: [web_sound_input]") - message_admins("[key_name(src)] played web sound: [web_sound_input]") - else - to_chat(src, "Youtube-dl URL retrieval FAILED:", confidential = TRUE) - to_chat(src, "[stderr]", confidential = TRUE) - - else //pressed ok with blank - log_admin("[key_name(src)] stopped web sound") - message_admins("[key_name(src)] stopped web sound") - web_sound_url = null - stop_web_sounds = TRUE - - if(web_sound_url && !findtext(web_sound_url, GLOB.is_http_protocol)) - to_chat(src, "BLOCKED: Content URL not using http(s) protocol", confidential = TRUE) - to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol", confidential = TRUE) - return - if(web_sound_url || stop_web_sounds) - for(var/m in GLOB.player_list) - var/mob/M = m - var/client/C = M.client - if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - if(!stop_web_sounds) - C.chatOutput.sendMusic(web_sound_url, music_extra_data) - else - C.chatOutput.stopMusic() - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound") - -/client/proc/set_round_end_sound(S as sound) - set category = "Fun" - set name = "Set Round End Sound" - if(!check_rights(R_SOUND)) - return - - SSticker.SetRoundEndSound(S) - - log_admin("[key_name(src)] set the round end sound to [S]") - message_admins("[key_name_admin(src)] set the round end sound to [S]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/stop_sounds() - set category = "Debug" - set name = "Stop All Playing Sounds" - if(!src.holder) - return - - log_admin("[key_name(src)] stopped all currently playing sounds.") - message_admins("[key_name_admin(src)] stopped all currently playing sounds.") - for(var/mob/M in GLOB.player_list) - SEND_SOUND(M, sound(null)) - var/client/C = M.client - if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - C.chatOutput.stopMusic() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop All Playing Sounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/play_sound(S as sound) + set category = "Fun" + set name = "Play Global Sound" + if(!check_rights(R_SOUND)) + return + + var/freq = 1 + var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num + if(!vol) + return + vol = clamp(vol, 1, 100) + + var/sound/admin_sound = new() + admin_sound.file = S + admin_sound.priority = 250 + admin_sound.channel = CHANNEL_ADMIN + admin_sound.frequency = freq + admin_sound.wait = 1 + admin_sound.repeat = 0 + admin_sound.status = SOUND_STREAM + admin_sound.volume = vol + + var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel") + switch(res) + if("Yes") + to_chat(world, "An admin played: [S]", confidential = TRUE) + if("Cancel") + return + + log_admin("[key_name(src)] played sound [S]") + message_admins("[key_name_admin(src)] played sound [S]") + + for(var/mob/M in GLOB.player_list) + if(M.client.prefs.toggles & SOUND_MIDI) + var/user_vol = M.client.chatOutput.adminMusicVolume + if(user_vol) + admin_sound.volume = vol * (user_vol / 100) + SEND_SOUND(M, admin_sound) + admin_sound.volume = vol + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/play_local_sound(S as sound) + set category = "Fun" + set name = "Play Local Sound" + if(!check_rights(R_SOUND)) + return + + log_admin("[key_name(src)] played a local sound [S]") + message_admins("[key_name_admin(src)] played a local sound [S]") + playsound(get_turf(src.mob), S, 50, FALSE, FALSE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/play_direct_mob_sound(S as sound, mob/M) + set category = "Fun" + set name = "Play Direct Mob Sound" + if(!check_rights(R_SOUND)) + return + + if(!M) + M = input(usr, "Choose a mob to play the sound to. Only they will hear it.", "Play Mob Sound") as null|anything in sortNames(GLOB.player_list) + if(!M || QDELETED(M)) + return + log_admin("[key_name(src)] played a direct mob sound [S] to [M].") + message_admins("[key_name_admin(src)] played a direct mob sound [S] to [ADMIN_LOOKUPFLW(M)].") + SEND_SOUND(M, S) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Direct Mob Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/play_web_sound() + set category = "Fun" + set name = "Play Internet Sound" + if(!check_rights(R_SOUND)) + return + + var/ytdl = CONFIG_GET(string/invoke_youtubedl) + if(!ytdl) + to_chat(src, "Youtube-dl was not configured, action unavailable", confidential = TRUE) //Check config.txt for the INVOKE_YOUTUBEDL value + return + + var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null + if(istext(web_sound_input)) + var/web_sound_url = "" + var/stop_web_sounds = FALSE + var/list/music_extra_data = list() + if(length(web_sound_input)) + + web_sound_input = trim(web_sound_input) + if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol)) + to_chat(src, "Non-http(s) URIs are not allowed.", confidential = TRUE) + to_chat(src, "For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.", confidential = TRUE) + return + var/shell_scrubbed_input = shell_url_scrub(web_sound_input) + var/list/output = world.shelleo("[ytdl] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"") + var/errorlevel = output[SHELLEO_ERRORLEVEL] + var/stdout = output[SHELLEO_STDOUT] + var/stderr = output[SHELLEO_STDERR] + if(!errorlevel) + var/list/data + try + data = json_decode(stdout) + catch(var/exception/e) + to_chat(src, "Youtube-dl JSON parsing FAILED:", confidential = TRUE) + to_chat(src, "[e]: [stdout]", confidential = TRUE) + return + + if (data["url"]) + web_sound_url = data["url"] + var/title = "[data["title"]]" + var/webpage_url = title + if (data["webpage_url"]) + webpage_url = "[title]" + music_extra_data["start"] = data["start_time"] + music_extra_data["end"] = data["end_time"] + + var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel") + switch(res) + if("Yes") + to_chat(world, "An admin played: [webpage_url]", confidential = TRUE) + if("Cancel") + return + + SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]")) + log_admin("[key_name(src)] played web sound: [web_sound_input]") + message_admins("[key_name(src)] played web sound: [web_sound_input]") + else + to_chat(src, "Youtube-dl URL retrieval FAILED:", confidential = TRUE) + to_chat(src, "[stderr]", confidential = TRUE) + + else //pressed ok with blank + log_admin("[key_name(src)] stopped web sound") + message_admins("[key_name(src)] stopped web sound") + web_sound_url = null + stop_web_sounds = TRUE + + if(web_sound_url && !findtext(web_sound_url, GLOB.is_http_protocol)) + to_chat(src, "BLOCKED: Content URL not using http(s) protocol", confidential = TRUE) + to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol", confidential = TRUE) + return + if(web_sound_url || stop_web_sounds) + for(var/m in GLOB.player_list) + var/mob/M = m + var/client/C = M.client + if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + if(!stop_web_sounds) + C.chatOutput.sendMusic(web_sound_url, music_extra_data) + else + C.chatOutput.stopMusic() + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound") + +/client/proc/set_round_end_sound(S as sound) + set category = "Fun" + set name = "Set Round End Sound" + if(!check_rights(R_SOUND)) + return + + SSticker.SetRoundEndSound(S) + + log_admin("[key_name(src)] set the round end sound to [S]") + message_admins("[key_name_admin(src)] set the round end sound to [S]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/stop_sounds() + set category = "Debug" + set name = "Stop All Playing Sounds" + if(!src.holder) + return + + log_admin("[key_name(src)] stopped all currently playing sounds.") + message_admins("[key_name_admin(src)] stopped all currently playing sounds.") + for(var/mob/M in GLOB.player_list) + SEND_SOUND(M, sound(null)) + var/client/C = M.client + if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.stopMusic() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop All Playing Sounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/possess.dm b/code/modules/admin/verbs/possess.dm index 9b226a97e42..8fee260c903 100644 --- a/code/modules/admin/verbs/possess.dm +++ b/code/modules/admin/verbs/possess.dm @@ -1,53 +1,53 @@ -/proc/possess(obj/O in world) - set name = "Possess Obj" - set category = "Object" - - if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession)) - to_chat(usr, "[O] is too powerful for you to possess.", confidential = TRUE) - return - - var/turf/T = get_turf(O) - - if(T) - log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") - message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") - else - log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") - message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") - - if(!usr.control_object) //If you're not already possessing something... - usr.name_archive = usr.real_name - - usr.loc = O - usr.real_name = O.name - usr.name = O.name - usr.reset_perspective(O) - usr.control_object = O - SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/proc/release() - set name = "Release Obj" - set category = "Object" - //usr.loc = get_turf(usr) - - if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object - usr.real_name = usr.name_archive - usr.name_archive = "" - usr.name = usr.real_name - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.name = H.get_visible_name() - - - usr.loc = get_turf(usr.control_object) - usr.reset_perspective() - usr.control_object = null - SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/proc/givetestverbs(mob/M in GLOB.mob_list) - set desc = "Give this guy possess/release verbs" - set category = "Debug" - set name = "Give Possessing Verbs" - M.verbs += /proc/possess - M.verbs += /proc/release - SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Possessing Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/proc/possess(obj/O in world) + set name = "Possess Obj" + set category = "Object" + + if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession)) + to_chat(usr, "[O] is too powerful for you to possess.", confidential = TRUE) + return + + var/turf/T = get_turf(O) + + if(T) + log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") + message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") + else + log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") + message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") + + if(!usr.control_object) //If you're not already possessing something... + usr.name_archive = usr.real_name + + usr.loc = O + usr.real_name = O.name + usr.name = O.name + usr.reset_perspective(O) + usr.control_object = O + SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/proc/release() + set name = "Release Obj" + set category = "Object" + //usr.loc = get_turf(usr) + + if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object + usr.real_name = usr.name_archive + usr.name_archive = "" + usr.name = usr.real_name + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.name = H.get_visible_name() + + + usr.loc = get_turf(usr.control_object) + usr.reset_perspective() + usr.control_object = null + SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/proc/givetestverbs(mob/M in GLOB.mob_list) + set desc = "Give this guy possess/release verbs" + set category = "Debug" + set name = "Give Possessing Verbs" + M.verbs += /proc/possess + M.verbs += /proc/release + SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Possessing Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index 53191212087..a11c9a24596 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -1,75 +1,75 @@ -/mob/verb/pray(msg as text) - set category = "IC" - set name = "Pray" - - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) - return - - msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) - if(!msg) - return - log_prayer("[src.key]/([src.name]): [msg]") - if(usr.client) - if(usr.client.prefs.muted & MUTE_PRAY) - to_chat(usr, "You cannot pray (muted).", confidential = TRUE) - return - if(src.client.handle_spam_prevention(msg,MUTE_PRAY)) - return - - var/mutable_appearance/cross = mutable_appearance('icons/obj/storage.dmi', "bible") - var/font_color = "purple" - var/prayer_type = "PRAYER" - var/deity - if(usr.job == "Chaplain") - cross.icon_state = "kingyellow" - font_color = "blue" - prayer_type = "CHAPLAIN PRAYER" - if(GLOB.deity) - deity = GLOB.deity - else if(iscultist(usr)) - cross.icon_state = "tome" - font_color = "red" - prayer_type = "CULTIST PRAYER" - deity = "Nar'Sie" - else if(isliving(usr)) - var/mob/living/L = usr - if(HAS_TRAIT(L, TRAIT_SPIRITUAL)) - cross.icon_state = "holylight" - font_color = "blue" - prayer_type = "SPIRITUAL PRAYER" - - var/msg_tmp = msg - msg = "[icon2html(cross, GLOB.admins)][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [msg]" - - for(var/client/C in GLOB.admins) - if(C.prefs.chat_toggles & CHAT_PRAYER) - to_chat(C, msg, confidential = TRUE) - if(C.prefs.toggles & SOUND_PRAYERS) - if(usr.job == "Chaplain") - SEND_SOUND(C, sound('sound/effects/pray.ogg')) - to_chat(usr, "You pray to the gods: \"[msg_tmp]\"", confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Prayer") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - //log_admin("HELP: [key_name(src)]: [msg]") - -/proc/CentCom_announce(text , mob/Sender) - var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "CENTCOM:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]: [msg]" - to_chat(GLOB.admins, msg, confidential = TRUE) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() - -/proc/Syndicate_announce(text , mob/Sender) - var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "SYNDICATE:[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]: [msg]" - to_chat(GLOB.admins, msg, confidential = TRUE) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() - -/proc/Nuke_request(text , mob/Sender) - var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "NUKE CODE REQUEST:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]: [msg]" - to_chat(GLOB.admins, msg, confidential = TRUE) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() +/mob/verb/pray(msg as text) + set category = "IC" + set name = "Pray" + + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) + return + + msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) + if(!msg) + return + log_prayer("[src.key]/([src.name]): [msg]") + if(usr.client) + if(usr.client.prefs.muted & MUTE_PRAY) + to_chat(usr, "You cannot pray (muted).", confidential = TRUE) + return + if(src.client.handle_spam_prevention(msg,MUTE_PRAY)) + return + + var/mutable_appearance/cross = mutable_appearance('icons/obj/storage.dmi', "bible") + var/font_color = "purple" + var/prayer_type = "PRAYER" + var/deity + if(usr.job == "Chaplain") + cross.icon_state = "kingyellow" + font_color = "blue" + prayer_type = "CHAPLAIN PRAYER" + if(GLOB.deity) + deity = GLOB.deity + else if(iscultist(usr)) + cross.icon_state = "tome" + font_color = "red" + prayer_type = "CULTIST PRAYER" + deity = "Nar'Sie" + else if(isliving(usr)) + var/mob/living/L = usr + if(HAS_TRAIT(L, TRAIT_SPIRITUAL)) + cross.icon_state = "holylight" + font_color = "blue" + prayer_type = "SPIRITUAL PRAYER" + + var/msg_tmp = msg + msg = "[icon2html(cross, GLOB.admins)][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [msg]" + + for(var/client/C in GLOB.admins) + if(C.prefs.chat_toggles & CHAT_PRAYER) + to_chat(C, msg, confidential = TRUE) + if(C.prefs.toggles & SOUND_PRAYERS) + if(usr.job == "Chaplain") + SEND_SOUND(C, sound('sound/effects/pray.ogg')) + to_chat(usr, "You pray to the gods: \"[msg_tmp]\"", confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Prayer") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + //log_admin("HELP: [key_name(src)]: [msg]") + +/proc/CentCom_announce(text , mob/Sender) + var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) + msg = "CENTCOM:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() + +/proc/Syndicate_announce(text , mob/Sender) + var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) + msg = "SYNDICATE:[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() + +/proc/Nuke_request(text , mob/Sender) + var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) + msg = "NUKE CODE REQUEST:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index ad557217dc4..b074c2c743b 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1,1314 +1,1314 @@ -/client/proc/cmd_admin_drop_everything(mob/M in GLOB.mob_list) - set category = null - set name = "Drop Everything" - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Make [M] drop everything?", "Message", "Yes", "No") - if(confirm != "Yes") - return - - for(var/obj/item/W in M) - if(!M.dropItemToGround(W)) - qdel(W) - M.regenerate_icons() - - log_admin("[key_name(usr)] made [key_name(M)] drop everything!") - var/msg = "[key_name_admin(usr)] made [ADMIN_LOOKUPFLW(M)] drop everything!" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Everything") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_subtle_message(mob/M in GLOB.mob_list) - set category = "Admin - Events" - set name = "Subtle Message" - - if(!ismob(M)) - return - if(!check_rights(R_ADMIN)) - return - - message_admins("[key_name_admin(src)] has started answering [ADMIN_LOOKUPFLW(M)]'s prayer.") - var/msg = input("Message:", text("Subtle PM to [M.key]")) as text|null - - if(!msg) - message_admins("[key_name_admin(src)] decided not to answer [ADMIN_LOOKUPFLW(M)]'s prayer") - return - if(usr) - if (usr.client) - if(usr.client.holder) - to_chat(M, "You hear a voice in your head... [msg]", confidential = TRUE) - - log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") - msg = " SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Subtle Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_headset_message(mob/M in GLOB.mob_list) - set category = "Admin - Events" - set name = "Headset Message" - - admin_headset_message(M) - -/client/proc/admin_headset_message(mob/M in GLOB.mob_list, sender = null) - var/mob/living/carbon/human/H = M - - if(!check_rights(R_ADMIN)) - return - - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) - return - if(!istype(H.ears, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset.", confidential = TRUE) - return - - if (!sender) - sender = input("Who is the message from?", "Sender") as null|anything in list(RADIO_CHANNEL_CENTCOM,RADIO_CHANNEL_SYNDICATE) - if(!sender) - return - - message_admins("[key_name_admin(src)] has started answering [key_name_admin(H)]'s [sender] request.") - var/input = input("Please enter a message to reply to [key_name(H)] via their headset.","Outgoing message from [sender]", "") as text|null - if(!input) - message_admins("[key_name_admin(src)] decided not to answer [key_name_admin(H)]'s [sender] request.") - return - - log_directed_talk(mob, H, input, LOG_ADMIN, "reply") - message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") - to_chat(H, "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]. Message ends.\"", confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Headset Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_mod_antag_rep(client/C in GLOB.clients, operation) - set category = "null" - set name = "Modify Antagonist Reputation" - - if(!check_rights(R_ADMIN)) - return - - var/msg = "" - var/log_text = "" - - if(operation == "zero") - log_text = "Set to 0" - SSpersistence.antag_rep -= C.ckey - else - var/prompt = "Please enter the amount of reputation to [operation]:" - - if(operation == "set") - prompt = "Please enter the new reputation value:" - - msg = input("Message:", prompt) as num|null - - if (!msg) - return - - var/ANTAG_REP_MAXIMUM = CONFIG_GET(number/antag_rep_maximum) - - if(operation == "set") - log_text = "Set to [num2text(msg)]" - SSpersistence.antag_rep[C.ckey] = max(0, min(msg, ANTAG_REP_MAXIMUM)) - else if(operation == "add") - log_text = "Added [num2text(msg)]" - SSpersistence.antag_rep[C.ckey] = min(SSpersistence.antag_rep[C.ckey]+msg, ANTAG_REP_MAXIMUM) - else if(operation == "subtract") - log_text = "Subtracted [num2text(msg)]" - SSpersistence.antag_rep[C.ckey] = max(SSpersistence.antag_rep[C.ckey]-msg, 0) - else - to_chat(src, "Invalid operation for antag rep modification: [operation] by user [key_name(usr)]", confidential = TRUE) - return - - if(SSpersistence.antag_rep[C.ckey] <= 0) - SSpersistence.antag_rep -= C.ckey - - log_admin("[key_name(usr)]: Modified [key_name(C)]'s antagonist reputation [log_text]") - message_admins("[key_name_admin(usr)]: Modified [key_name(C)]'s antagonist reputation ([log_text])") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Modify Antagonist Reputation") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_world_narrate() - set category = "Admin - Events" - set name = "Global Narrate" - - if(!check_rights(R_ADMIN)) - return - - var/msg = input("Message:", text("Enter the text you wish to appear to everyone:")) as text|null - - if (!msg) - return - to_chat(world, "[msg]", confidential = TRUE) - log_admin("GlobalNarrate: [key_name(usr)] : [msg]") - message_admins("[key_name_admin(usr)] Sent a global narrate") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Global Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_direct_narrate(mob/M) - set category = "Admin - Events" - set name = "Direct Narrate" - - if(!check_rights(R_ADMIN)) - return - - if(!M) - M = input("Direct narrate to whom?", "Active Players") as null|anything in GLOB.player_list - - if(!M) - return - - var/msg = input("Message:", text("Enter the text you wish to appear to your target:")) as text|null - - if( !msg ) - return - - to_chat(M, msg, confidential = TRUE) - log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]") - msg = " DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
                " - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Direct Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_local_narrate(atom/A) - set category = "Admin - Events" - set name = "Local Narrate" - - if(!check_rights(R_ADMIN)) - return - if(!A) - return - var/range = input("Range:", "Narrate to mobs within how many tiles:", 7) as num|null - if(!range) - return - var/msg = input("Message:", text("Enter the text you wish to appear to everyone within view:")) as text|null - if (!msg) - return - for(var/mob/M in view(range,A)) - to_chat(M, msg, confidential = TRUE) - - log_admin("LocalNarrate: [key_name(usr)] at [AREACOORD(A)]: [msg]") - message_admins(" LocalNarrate: [key_name_admin(usr)] at [ADMIN_VERBOSEJMP(A)]: [msg]
                ") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Local Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_godmode(mob/M in GLOB.mob_list) - set category = "Admin - Game" - set name = "Godmode" - if(!check_rights(R_ADMIN)) - return - - M.status_flags ^= GODMODE - to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]", confidential = TRUE) - - log_admin("[key_name(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") - var/msg = "[key_name_admin(usr)] has toggled [ADMIN_LOOKUPFLW(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Godmode", "[M.status_flags & GODMODE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/proc/cmd_admin_mute(whom, mute_type, automute = 0) - if(!whom) - return - - var/muteunmute - var/mute_string - var/feedback_string - switch(mute_type) - if(MUTE_IC) - mute_string = "IC (say and emote)" - feedback_string = "IC" - if(MUTE_OOC) - mute_string = "OOC" - feedback_string = "OOC" - if(MUTE_PRAY) - mute_string = "pray" - feedback_string = "Pray" - if(MUTE_ADMINHELP) - mute_string = "adminhelp, admin PM and ASAY" - feedback_string = "Adminhelp" - if(MUTE_DEADCHAT) - mute_string = "deadchat and DSAY" - feedback_string = "Deadchat" - if(MUTE_ALL) - mute_string = "everything" - feedback_string = "Everything" - else - return - - var/client/C - if(istype(whom, /client)) - C = whom - else if(istext(whom)) - C = GLOB.directory[whom] - else - return - - var/datum/preferences/P - if(C) - P = C.prefs - else - P = GLOB.preferences_datums[whom] - if(!P) - return - - if(automute) - if(!CONFIG_GET(flag/automute_on)) - return - else - if(!check_rights()) - return - - if(automute) - muteunmute = "auto-muted" - P.muted |= mute_type - log_admin("SPAM AUTOMUTE: [muteunmute] [key_name(whom)] from [mute_string]") - message_admins("SPAM AUTOMUTE: [muteunmute] [key_name_admin(whom)] from [mute_string].") - if(C) - to_chat(C, "You have been [muteunmute] from [mute_string] by the SPAM AUTOMUTE system. Contact an admin.", confidential = TRUE) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Auto Mute [feedback_string]", "1")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - - if(P.muted & mute_type) - muteunmute = "unmuted" - P.muted &= ~mute_type - else - muteunmute = "muted" - P.muted |= mute_type - - log_admin("[key_name(usr)] has [muteunmute] [key_name(whom)] from [mute_string]") - message_admins("[key_name_admin(usr)] has [muteunmute] [key_name_admin(whom)] from [mute_string].") - if(C) - to_chat(C, "You have been [muteunmute] from [mute_string] by [key_name(usr, include_name = FALSE)].", confidential = TRUE) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Mute [feedback_string]", "[P.muted & mute_type]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -//I use this proc for respawn character too. /N -/proc/create_xeno(ckey) - if(!ckey) - var/list/candidates = list() - for(var/mob/M in GLOB.player_list) - if(M.stat != DEAD) - continue //we are not dead! - if(!(ROLE_ALIEN in M.client.prefs.be_special)) - continue //we don't want to be an alium - if(M.client.is_afk()) - continue //we are afk - if(M.mind && M.mind.current && M.mind.current.stat != DEAD) - continue //we have a live body we are tied to - candidates += M.ckey - if(candidates.len) - ckey = input("Pick the player you want to respawn as a xeno.", "Suitable Candidates") as null|anything in sortKey(candidates) - else - to_chat(usr, "Error: create_xeno(): no suitable candidates.", confidential = TRUE) - if(!istext(ckey)) - return 0 - - var/alien_caste = input(usr, "Please choose which caste to spawn.","Pick a caste",null) as null|anything in list("Queen","Praetorian","Hunter","Sentinel","Drone","Larva") - var/obj/effect/landmark/spawn_here = GLOB.xeno_spawn.len ? pick(GLOB.xeno_spawn) : null - var/mob/living/carbon/alien/new_xeno - switch(alien_caste) - if("Queen") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(spawn_here) - if("Praetorian") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(spawn_here) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(spawn_here) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(spawn_here) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(spawn_here) - if("Larva") - new_xeno = new /mob/living/carbon/alien/larva(spawn_here) - else - return 0 - if(!spawn_here) - SSjob.SendToLateJoin(new_xeno, FALSE) - - new_xeno.ckey = ckey - var/msg = "[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste]." - message_admins(msg) - admin_ticket_log(new_xeno, msg) - return 1 - -/* -If a guy was gibbed and you want to revive him, this is a good way to do so. -Works kind of like entering the game with a new character. Character receives a new mind if they didn't have one. -Traitors and the like can also be revived with the previous role mostly intact. -/N */ -/client/proc/respawn_character() - set category = "Admin - Game" - set name = "Respawn Character" - set desc = "Respawn a person that has been gibbed/dusted/killed. They must be a ghost for this to work and preferably should not have a body to go back into." - if(!check_rights(R_ADMIN)) - return - - var/input = ckey(input(src, "Please specify which key will be respawned.", "Key", "")) - if(!input) - return - - var/mob/dead/observer/G_found - for(var/mob/dead/observer/G in GLOB.player_list) - if(G.ckey == input) - G_found = G - break - - if(!G_found)//If a ghost was not found. - to_chat(usr, "There is no active key like that in the game or the person is not currently a ghost.", confidential = TRUE) - return - - if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something - //Check if they were an alien - if(G_found.mind.assigned_role == ROLE_ALIEN) - if(alert("This character appears to have been an alien. Would you like to respawn them as such?",,"Yes","No")=="Yes") - var/turf/T - if(GLOB.xeno_spawn.len) - T = pick(GLOB.xeno_spawn) - - var/mob/living/carbon/alien/new_xeno - switch(G_found.mind.special_role)//If they have a mind, we can determine which caste they were. - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(T) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(T) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(T) - if("Praetorian") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(T) - if("Queen") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(T) - else//If we don't know what special role they have, for whatever reason, or they're a larva. - create_xeno(G_found.ckey) - return - - if(!T) - SSjob.SendToLateJoin(new_xeno, FALSE) - - //Now to give them their mind back. - G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_xeno.key = G_found.key - to_chat(new_xeno, "You have been fully respawned. Enjoy the game.", confidential = TRUE) - var/msg = "[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno." - message_admins(msg) - admin_ticket_log(new_xeno, msg) - return //all done. The ghost is auto-deleted - - //check if they were a monkey - else if(findtext(G_found.real_name,"monkey")) - if(alert("This character appears to have been a monkey. Would you like to respawn them as such?",,"Yes","No")=="Yes") - var/mob/living/carbon/monkey/new_monkey = new - SSjob.SendToLateJoin(new_monkey) - G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_monkey.key = G_found.key - to_chat(new_monkey, "You have been fully respawned. Enjoy the game.", confidential = TRUE) - var/msg = "[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno." - message_admins(msg) - admin_ticket_log(new_monkey, msg) - return //all done. The ghost is auto-deleted - - - //Ok, it's not a xeno or a monkey. So, spawn a human. - var/mob/living/carbon/human/new_character = new//The mob being spawned. - SSjob.SendToLateJoin(new_character) - - var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. - if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something - /*Try and locate a record for the person being respawned through GLOB.data_core. - This isn't an exact science but it does the trick more often than not.*/ - var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") - - record_found = find_record("id", id, GLOB.data_core.locked) - - if(record_found)//If they have a record we can determine a few things. - new_character.real_name = record_found.fields["name"] - new_character.gender = record_found.fields["gender"] - new_character.age = record_found.fields["age"] - new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], null, record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"]) - else - var/datum/preferences/A = new() - A.copy_to(new_character) - A.real_name = G_found.real_name - new_character.dna.update_dna_identity() - - new_character.name = new_character.real_name - - if(G_found.mind && !G_found.mind.active) - G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use - else - new_character.mind_initialize() - if(!new_character.mind.assigned_role) - new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role. - - new_character.key = G_found.key - - /* - The code below functions with the assumption that the mob is already a traitor if they have a special role. - So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. - If they don't have a mind, they obviously don't have a special role. - */ - - //Two variables to properly announce later on. - var/admin = key_name_admin(src) - var/player_key = G_found.key - - //Now for special roles and equipment. - var/datum/antagonist/traitor/traitordatum = new_character.mind.has_antag_datum(/datum/antagonist/traitor) - if(traitordatum) - SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1) - traitordatum.equip() - - - switch(new_character.mind.special_role) - if(ROLE_WIZARD) - new_character.forceMove(pick(GLOB.wizardstart)) - var/datum/antagonist/wizard/A = new_character.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) - A.equip_wizard() - if(ROLE_SYNDICATE) - new_character.forceMove(pick(GLOB.nukeop_start)) - var/datum/antagonist/nukeop/N = new_character.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) - N.equip_op() - if(ROLE_NINJA) - var/list/ninja_spawn = list() - for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) - ninja_spawn += L - var/datum/antagonist/ninja/ninjadatum = new_character.mind.has_antag_datum(/datum/antagonist/ninja) - ninjadatum.equip_space_ninja() - if(ninja_spawn.len) - new_character.forceMove(pick(ninja_spawn)) - - else//They may also be a cyborg or AI. - switch(new_character.mind.assigned_role) - if("Cyborg")//More rigging to make em' work and check if they're traitor. - new_character = new_character.Robotize(TRUE) - if("AI") - new_character = new_character.AIize() - else - SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. - - //Announces the character on all the systems, based on the record. - if(!issilicon(new_character))//If they are not a cyborg/AI. - if(!record_found&&new_character.mind.assigned_role!=new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. MODE people are not announced anyway. - //Power to the user! - if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") - GLOB.data_core.manifest_inject(new_character) - - if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") - AnnounceArrival(new_character, new_character.mind.assigned_role) - - var/msg = "[admin] has respawned [player_key] as [new_character.real_name]." - message_admins(msg) - admin_ticket_log(new_character, msg) - - to_chat(new_character, "You have been fully respawned. Enjoy the game.", confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Respawn Character") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return new_character - -/client/proc/cmd_admin_add_freeform_ai_law() - set category = "Admin - Events" - set name = "Add Custom AI law" - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Please enter anything you want the AI to do. Anything. Serious.", "What?", "") as text|null - if(!input) - return - - log_admin("Admin [key_name(usr)] has added a new AI law - [input]") - message_admins("Admin [key_name_admin(usr)] has added a new AI law - [input]") - - var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") - var/announce_ion_laws = (show_log == "Yes" ? 100 : 0) - - var/datum/round_event/ion_storm/add_law_only/ion = new() - ion.announceChance = announce_ion_laws - ion.ionMessage = input - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Add Custom AI Law") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_rejuvenate(mob/living/M in GLOB.mob_list) - set category = "Debug" - set name = "Rejuvenate" - - if(!check_rights(R_ADMIN)) - return - - if(!mob) - return - if(!istype(M)) - alert("Cannot revive a ghost") - return - M.revive(full_heal = TRUE, admin_revive = TRUE) - - log_admin("[key_name(usr)] healed / revived [key_name(M)]") - var/msg = "Admin [key_name_admin(usr)] healed / revived [ADMIN_LOOKUPFLW(M)]!" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Rejuvenate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_create_centcom_report() - set category = "Admin - Events" - set name = "Create Command Report" - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Enter a Command Report. Ensure it makes sense IC. Command's name is currently set to [command_name()].", "What?", "") as message|null - if(!input) - return - - var/confirm = alert(src, "Do you want to announce the contents of the report to the crew?", "Announce", "Yes", "No", "Cancel") - var/announce_command_report = TRUE - switch(confirm) - if("Yes") - priority_announce(input, null, 'sound/ai/commandreport.ogg') - announce_command_report = FALSE - if("Cancel") - return - - print_command_report(input, "[announce_command_report ? "Classified " : ""][command_name()] Update", announce_command_report) - - log_admin("[key_name(src)] has created a command report: [input]") - message_admins("[key_name_admin(src)] has created a command report") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Create Command Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_change_command_name() - set category = "Admin - Events" - set name = "Change Command Name" - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Please input a new name for Central Command.", "What?", "") as text|null - if(!input) - return - change_command_name(input) - message_admins("[key_name_admin(src)] has changed Central Command's name to [input]") - log_admin("[key_name(src)] has changed the Central Command name to: [input]") - -/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in world) - set category = "Debug" - set name = "Delete" - - if(!check_rights(R_SPAWN|R_DEBUG)) - return - - admin_delete(A) - -/client/proc/cmd_admin_list_open_jobs() - set category = "Admin - Game" - set name = "Manage Job Slots" - - if(!check_rights(R_ADMIN)) - return - holder.manage_free_slots() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Manage Job Slots") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_explosion(atom/O as obj|mob|turf in world) - set category = "Fun" - set name = "Explosion" - - if(!check_rights(R_ADMIN)) - return - - var/devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null - if(devastation == null) - return - var/heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null - if(heavy == null) - return - var/light = input("Range of light impact. -1 to none", text("Input")) as num|null - if(light == null) - return - var/flash = input("Range of flash. -1 to none", text("Input")) as num|null - if(flash == null) - return - var/flames = input("Range of flames. -1 to none", text("Input")) as num|null - if(flames == null) - return - - if ((devastation != -1) || (heavy != -1) || (light != -1) || (flash != -1) || (flames != -1)) - if ((devastation > 20) || (heavy > 20) || (light > 20) || (flames > 20)) - if (alert(src, "Are you sure you want to do this? It will laaag.", "Confirmation", "Yes", "No") == "No") - return - - explosion(O, devastation, heavy, light, flash, null, null,flames) - log_admin("[key_name(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") - message_admins("[key_name_admin(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Explosion") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - else - return - -/client/proc/cmd_admin_emp(atom/O as obj|mob|turf in world) - set category = "Fun" - set name = "EM Pulse" - - if(!check_rights(R_ADMIN)) - return - - var/heavy = input("Range of heavy pulse.", text("Input")) as num|null - if(heavy == null) - return - var/light = input("Range of light pulse.", text("Input")) as num|null - if(light == null) - return - - if (heavy || light) - - empulse(O, heavy, light) - log_admin("[key_name(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") - message_admins("[key_name_admin(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "EM Pulse") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - return - else - return - -/client/proc/cmd_admin_gib(mob/M in GLOB.mob_list) - set category = "Fun" - set name = "Gib" - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Drop a brain?", "Confirm", "Yes", "No","Cancel") - if(confirm == "Cancel") - return - //Due to the delay here its easy for something to have happened to the mob - if(!M) - return - - log_admin("[key_name(usr)] has gibbed [key_name(M)]") - message_admins("[key_name_admin(usr)] has gibbed [key_name_admin(M)]") - - if(isobserver(M)) - new /obj/effect/gibspawner/generic(get_turf(M)) - return - if(confirm == "Yes") - M.gib() - else - M.gib(1) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_gib_self() - set name = "Gibself" - set category = "Fun" - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm == "Yes") - log_admin("[key_name(usr)] used gibself.") - message_admins("[key_name_admin(usr)] used gibself.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib Self") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - mob.gib(1, 1, 1) - -/client/proc/cmd_admin_check_contents(mob/living/M in GLOB.mob_list) - set category = "Debug" - set name = "Check Contents" - - var/list/L = M.get_contents() - for(var/t in L) - to_chat(usr, "[t]", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Contents") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_view_range() - set category = "Admin - Game" - set name = "Change View Range" - set desc = "switches between 1x and custom views" - - if(view_size.getView() == view_size.default) - view_size.setTo(input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128) - 7) - else - view_size.resetToDefault(getScreenSize(prefs.widescreenpref)) - - log_admin("[key_name(usr)] changed their view range to [view].") - //message_admins("\blue [key_name_admin(usr)] changed their view range to [view].") //why? removed by order of XSI - - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Change View Range", "[view]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/admin_call_shuttle() - - set category = "Admin - Events" - set name = "Call Shuttle" - - if(EMERGENCY_AT_LEAST_DOCKED) - return - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm != "Yes") - return - - SSshuttle.emergency.request() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Call Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] admin-called the emergency shuttle.") - message_admins("[key_name_admin(usr)] admin-called the emergency shuttle.") - return - -/client/proc/admin_cancel_shuttle() - set category = "Admin - Events" - set name = "Cancel Shuttle" - if(!check_rights(0)) - return - if(alert(src, "You sure?", "Confirm", "Yes", "No") != "Yes") - return - - if(EMERGENCY_AT_LEAST_DOCKED) - return - - SSshuttle.emergency.cancel() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Cancel Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] admin-recalled the emergency shuttle.") - message_admins("[key_name_admin(usr)] admin-recalled the emergency shuttle.") - - return - -/client/proc/everyone_random() - set category = "Fun" - set name = "Make Everyone Random" - set desc = "Make everyone have a random appearance. You can only use this before rounds!" - - if(SSticker.HasRoundStarted()) - to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!", confidential = TRUE) - return - - var/frn = CONFIG_GET(flag/force_random_names) - if(frn) - CONFIG_SET(flag/force_random_names, FALSE) - message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.") - to_chat(usr, "Disabled.", confidential = TRUE) - return - - - var/notifyplayers = alert(src, "Do you want to notify the players?", "Options", "Yes", "No", "Cancel") - if(notifyplayers == "Cancel") - return - - log_admin("Admin [key_name(src)] has forced the players to have random appearances.") - message_admins("Admin [key_name_admin(usr)] has forced the players to have random appearances.") - - if(notifyplayers == "Yes") - to_chat(world, "Admin [usr.key] has forced the players to have completely random identities!", confidential = TRUE) - - to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.", confidential = TRUE) - - CONFIG_SET(flag/force_random_names, TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Make Everyone Random") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/toggle_random_events() - set category = "Server" - set name = "Toggle random events on/off" - set desc = "Toggles random events such as meteors, black holes, blob (but not space dust) on/off" - var/new_are = !CONFIG_GET(flag/allow_random_events) - CONFIG_SET(flag/allow_random_events, new_are) - if(new_are) - to_chat(usr, "Random events enabled", confidential = TRUE) - message_admins("Admin [key_name_admin(usr)] has enabled random events.") - else - to_chat(usr, "Random events disabled", confidential = TRUE) - message_admins("Admin [key_name_admin(usr)] has disabled random events.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Random Events", "[new_are ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/admin_change_sec_level() - set category = "Admin - Events" - set name = "Set Security Level" - set desc = "Changes the security level. Announcement only, i.e. setting to Delta won't activate nuke" - - if(!check_rights(R_ADMIN)) - return - - var/level = input("Select security level to change to","Set Security Level") as null|anything in list("green","blue","red","delta") - if(level) - set_security_level(level) - - log_admin("[key_name(usr)] changed the security level to [level]") - message_admins("[key_name_admin(usr)] changed the security level to [level]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Security Level [capitalize(level)]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_nuke(obj/machinery/nuclearbomb/N in GLOB.nuke_list) - set name = "Toggle Nuke" - set category = "Admin - Events" - set popup_menu = 0 - if(!check_rights(R_DEBUG)) - return - - if(!N.timing) - var/newtime = input(usr, "Set activation timer.", "Activate Nuke", "[N.timer_set]") as num|null - if(!newtime) - return - N.timer_set = newtime - N.set_safety() - N.set_active() - - log_admin("[key_name(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [AREACOORD(N)].") - message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_combo_hud() - set category = "Admin - Game" - set name = "Toggle Combo HUD" - set desc = "Toggles the Admin Combo HUD (antag, sci, med, eng)" - - if(!check_rights(R_ADMIN)) - return - - var/adding_hud = !has_antag_hud() - - for(var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) // add data huds - var/datum/atom_hud/H = GLOB.huds[hudtype] - (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) - for(var/datum/atom_hud/antag/H in GLOB.huds) // add antag huds - (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) - - if(prefs.toggles & COMBOHUD_LIGHTING) - if(adding_hud) - mob.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE - else - mob.lighting_alpha = initial(mob.lighting_alpha) - - mob.update_sight() - - to_chat(usr, "You toggled your admin combo HUD [adding_hud ? "ON" : "OFF"].", confidential = TRUE) - message_admins("[key_name_admin(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") - log_admin("[key_name(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[adding_hud ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/has_antag_hud() - var/datum/atom_hud/A = GLOB.huds[ANTAG_HUD_TRAITOR] - return A.hudusers[mob] - - -/client/proc/run_weather() - set category = "Admin - Events" - set name = "Run Weather" - set desc = "Triggers a weather on the z-level you choose." - - if(!holder) - return - - var/weather_type = input("Choose a weather", "Weather") as null|anything in sortList(subtypesof(/datum/weather), /proc/cmp_typepaths_asc) - if(!weather_type) - return - - var/turf/T = get_turf(mob) - var/z_level = input("Z-Level to target?", "Z-Level", T?.z) as num|null - if(!isnum(z_level)) - return - - SSweather.run_weather(weather_type, z_level) - - message_admins("[key_name_admin(usr)] started weather of type [weather_type] on the z-level [z_level].") - log_admin("[key_name(usr)] started weather of type [weather_type] on the z-level [z_level].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Run Weather") - -/client/proc/mass_zombie_infection() - set category = "Fun" - set name = "Mass Zombie Infection" - set desc = "Infects all humans with a latent organ that will zombify \ - them on death." - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Please confirm you want to add latent zombie organs in all humans?", "Confirm Zombies", "Yes", "No") - if(confirm != "Yes") - return - - for(var/i in GLOB.human_list) - var/mob/living/carbon/human/H = i - new /obj/item/organ/zombie_infection/nodamage(H) - - message_admins("[key_name_admin(usr)] added a latent zombie infection to all humans.") - log_admin("[key_name(usr)] added a latent zombie infection to all humans.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Infection") - -/client/proc/mass_zombie_cure() - set category = "Fun" - set name = "Mass Zombie Cure" - set desc = "Removes the zombie infection from all humans, returning them to normal." - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Please confirm you want to cure all zombies?", "Confirm Zombie Cure", "Yes", "No") - if(confirm != "Yes") - return - - for(var/obj/item/organ/zombie_infection/nodamage/I in GLOB.zombie_infection_list) - qdel(I) - - message_admins("[key_name_admin(usr)] cured all zombies.") - log_admin("[key_name(usr)] cured all zombies.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Cure") - -/client/proc/polymorph_all() - set category = "Fun" - set name = "Polymorph All" - set desc = "Applies the effects of the bolt of change to every single mob." - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Please confirm you want polymorph all mobs?", "Confirm Polymorph", "Yes", "No") - if(confirm != "Yes") - return - - var/list/mobs = shuffle(GLOB.alive_mob_list.Copy()) // might change while iterating - var/who_did_it = key_name_admin(usr) - - message_admins("[key_name_admin(usr)] started polymorphed all living mobs.") - log_admin("[key_name(usr)] polymorphed all living mobs.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Polymorph All") - - for(var/mob/living/M in mobs) - CHECK_TICK - - if(!M) - continue - - M.audible_message("...wabbajack...wabbajack...") - playsound(M.loc, 'sound/magic/staff_change.ogg', 50, TRUE, -1) - - wabbajack(M) - - message_admins("Mass polymorph started by [who_did_it] is complete.") - - -/client/proc/show_tip() - set category = "Admin" - set name = "Show Tip" - set desc = "Sends a tip (that you specify) to all players. After all \ - you're the experienced player here." - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Please specify your tip that you want to send to the players.", "Tip", "") as message|null - if(!input) - return - - if(!SSticker) - return - - SSticker.selected_tip = input - - // If we've already tipped, then send it straight away. - if(SSticker.tipped) - SSticker.send_tip_of_the_round() - - - message_admins("[key_name_admin(usr)] sent a tip of the round.") - log_admin("[key_name(usr)] sent \"[input]\" as the Tip of the Round.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Tip") - -/client/proc/modify_goals() - set category = "Debug" - set name = "Modify goals" - - if(!check_rights(R_ADMIN)) - return - - holder.modify_goals() - -/datum/admins/proc/modify_goals() - var/dat = "" - for(var/datum/station_goal/S in SSticker.mode.station_goals) - dat += "[S.name] - Announce | Remove
                " - dat += "
                Add New Goal" - usr << browse(dat, "window=goals;size=400x400") - -/proc/immerse_player(mob/living/carbon/target, toggle=TRUE, remove=FALSE) - var/list/immersion_components = list(/datum/component/manual_breathing, /datum/component/manual_blinking) - - for(var/immersies in immersion_components) - var/has_component = target.GetComponent(immersies) - - if(has_component && (toggle || remove)) - qdel(has_component) - else if(toggle || !remove) - target.AddComponent(immersies) - -/proc/mass_immerse(remove=FALSE) - for(var/mob/living/carbon/M in GLOB.mob_list) - immerse_player(M, toggle=FALSE, remove=remove) - -/client/proc/toggle_hub() - set category = "Server" - set name = "Toggle Hub" - - world.update_hub_visibility(!GLOB.hub_visibility) - - log_admin("[key_name(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") - message_admins("[key_name_admin(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") - if (GLOB.hub_visibility && !world.reachable) - message_admins("WARNING: The server will not show up on the hub because byond is detecting that a filewall is blocking incoming connections.") - - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Hub Visibility", "[GLOB.hub_visibility ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/smite(mob/living/target as mob) - set name = "Smite" - set category = "Fun" - if(!check_rights(R_ADMIN) || !check_rights(R_FUN)) - return - - var/list/punishment_list = list(ADMIN_PUNISHMENT_LIGHTNING, - ADMIN_PUNISHMENT_BRAINDAMAGE, - ADMIN_PUNISHMENT_GIB, - ADMIN_PUNISHMENT_BSA, - ADMIN_PUNISHMENT_FIREBALL, - ADMIN_PUNISHMENT_ROD, - ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, - ADMIN_PUNISHMENT_SUPPLYPOD, - ADMIN_PUNISHMENT_MAZING, - ADMIN_PUNISHMENT_IMMERSE, - ADMIN_PUNISHMENT_FAT, - ADMIN_PUNISHMENT_FAKEBWOINK, - ADMIN_PUNISHMENT_NUGGET, - ADMIN_PUNISHMENT_CRACK, - ADMIN_PUNISHMENT_BLEED, - ADMIN_PUNISHMENT_SCARIFY, - ADMIN_PUNISHMENT_SHOES - ) - - var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in sortList(punishment_list) - - if(QDELETED(target) || !punishment) - return - - switch(punishment) - if(ADMIN_PUNISHMENT_LIGHTNING) - var/turf/T = get_step(get_step(target, NORTH), NORTH) - T.Beam(target, icon_state="lightning[rand(1,12)]", time = 5) - target.adjustFireLoss(75) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - H.electrocution_animation(40) - to_chat(target, "The gods have punished you for your sins!", confidential = TRUE) - if(ADMIN_PUNISHMENT_BRAINDAMAGE) - target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) - if(ADMIN_PUNISHMENT_GIB) - target.gib(FALSE) - if(ADMIN_PUNISHMENT_BSA) - bluespace_artillery(target) - if(ADMIN_PUNISHMENT_FIREBALL) - new /obj/effect/temp_visual/target(get_turf(target)) - if(ADMIN_PUNISHMENT_ROD) - var/turf/T = get_turf(target) - var/startside = pick(GLOB.cardinals) - var/turf/startT = spaceDebrisStartLoc(startside, T.z) - var/turf/endT = spaceDebrisFinishLoc(startside, T.z) - new /obj/effect/immovablerod(startT, endT,target) - if(ADMIN_PUNISHMENT_SUPPLYPOD_QUICK) - var/target_path = input(usr,"Enter typepath of an atom you'd like to send with the pod (type \"empty\" to send an empty pod):" ,"Typepath","/obj/item/reagent_containers/food/snacks/grown/harebell") as null|text - var/obj/structure/closet/supplypod/centcompod/pod = new() - pod.damage = 40 - pod.explosionSize = list(0,0,0,2) - pod.effectStun = TRUE - if (isnull(target_path)) //The user pressed "Cancel" - return - if (target_path != "empty")//if you didn't type empty, we want to load the pod with a delivery - var/delivery = text2path(target_path) - if(!ispath(delivery)) - delivery = pick_closest_path(target_path) - if(!delivery) - alert("ERROR: Incorrect / improper path given.") - return - new delivery(pod) - new /obj/effect/pod_landingzone(get_turf(target), pod) - if(ADMIN_PUNISHMENT_SUPPLYPOD) - var/datum/centcom_podlauncher/plaunch = new(usr) - if(!holder) - return - plaunch.specificTarget = target - plaunch.launchChoice = 0 - plaunch.damageChoice = 1 - plaunch.explosionChoice = 1 - plaunch.temp_pod.damage = 40//bring the mother fuckin ruckus - plaunch.temp_pod.explosionSize = list(0,0,0,2) - plaunch.temp_pod.effectStun = TRUE - plaunch.ui_interact(usr) - return //We return here because punish_log() is handled by the centcom_podlauncher datum - if(ADMIN_PUNISHMENT_MAZING) - if(!puzzle_imprison(target)) - to_chat(usr,"Imprisonment failed!", confidential = TRUE) - return - if(ADMIN_PUNISHMENT_IMMERSE) - immerse_player(target) - if(ADMIN_PUNISHMENT_FAT) - target.set_nutrition(NUTRITION_LEVEL_FAT*2) - if(ADMIN_PUNISHMENT_FAKEBWOINK) - SEND_SOUND(target, 'sound/effects/adminhelp.ogg') - if(ADMIN_PUNISHMENT_NUGGET) - if(!iscarbon(target)) - to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) - return - var/mob/living/carbon/C = target - var/timer = 2 SECONDS - for(var/obj/item/bodypart/thing in C.bodyparts) - if(thing.body_part == HEAD || thing.body_part == CHEST) - continue - addtimer(CALLBACK(thing, /obj/item/bodypart/.proc/dismember), timer) - addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, C, 'sound/effects/cartoon_pop.ogg', 70), timer) - addtimer(CALLBACK(C, /mob/living/.proc/spin, 4, 1), timer - 0.4 SECONDS) - timer += 2 SECONDS - if(ADMIN_PUNISHMENT_CRACK) - if(!iscarbon(target)) - to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) - return - var/mob/living/carbon/C = target - for(var/obj/item/bodypart/squish_part in C.bodyparts) - var/type_wound = pick(list(/datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/moderate)) - squish_part.force_wound_upwards(type_wound, smited=TRUE) - if(ADMIN_PUNISHMENT_BLEED) - if(!iscarbon(target)) - to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) - return - var/mob/living/carbon/C = target - for(var/obj/item/bodypart/slice_part in C.bodyparts) - var/type_wound = pick(list(/datum/wound/brute/cut/severe, /datum/wound/brute/cut/moderate)) - slice_part.force_wound_upwards(type_wound, smited=TRUE) - type_wound = pick(list(/datum/wound/brute/cut/critical, /datum/wound/brute/cut/severe, /datum/wound/brute/cut/moderate)) - slice_part.force_wound_upwards(type_wound, smited=TRUE) - type_wound = pick(list(/datum/wound/brute/cut/critical, /datum/wound/brute/cut/severe)) - slice_part.force_wound_upwards(type_wound, smited=TRUE) - if(ADMIN_PUNISHMENT_SCARIFY) - if(!iscarbon(target)) - to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) - return - var/mob/living/carbon/C = target - C.generate_fake_scars(rand(1, 4)) - to_chat(C, "You feel your body grow jaded and torn...") - if(ADMIN_PUNISHMENT_SHOES) - if(!iscarbon(target)) - to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) - return - var/mob/living/carbon/C = target - var/obj/item/clothing/shoes/sick_kicks = C.shoes - if(!sick_kicks?.can_be_tied) - to_chat(usr,"[C] does not have knottable shoes!", confidential = TRUE) - return - sick_kicks.adjust_laces(SHOES_KNOTTED) - - punish_log(target, punishment) - -/client/proc/punish_log(whom, punishment) - var/msg = "[key_name_admin(usr)] punished [key_name_admin(whom)] with [punishment]." - message_admins(msg) - admin_ticket_log(whom, msg) - log_admin("[key_name(usr)] punished [key_name(whom)] with [punishment].") - -/client/proc/trigger_centcom_recall() - if(!check_rights(R_ADMIN)) - return - var/message = pick(GLOB.admiral_messages) - message = input("Enter message from the on-call admiral to be put in the recall report.", "Admiral Message", message) as text|null - - if(!message) - return - - message_admins("[key_name_admin(usr)] triggered a CentCom recall, with the admiral message of: [message]") - log_game("[key_name(usr)] triggered a CentCom recall, with the message of: [message]") - SSshuttle.centcom_recall(SSshuttle.emergency.timer, message) - -/client/proc/cmd_admin_check_player_exp() //Allows admins to determine who the newer players are. - set category = "Admin" - set name = "Player Playtime" - if(!check_rights(R_ADMIN)) - return - - if(!CONFIG_GET(flag/use_exp_tracking)) - to_chat(usr, "Tracking is disabled in the server configuration file.", confidential = TRUE) - return - - var/list/msg = list() - msg += "Playtime ReportPlaytime:
                " - src << browse(msg.Join(), "window=Player_playtime_check") - -/datum/admins/proc/cmd_show_exp_panel(client/C) - if(!check_rights(R_ADMIN)) - return - if(!C) - to_chat(usr, "ERROR: Client not found.", confidential = TRUE) - return - if(!CONFIG_GET(flag/use_exp_tracking)) - to_chat(usr, "Tracking is disabled in the server configuration file.", confidential = TRUE) - return - - var/list/body = list() - body += "Playtime for [C.key]
                Playtime:" - body += C.get_exp_report() - body += "Toggle Exempt status" - body += "" - usr << browse(body.Join(), "window=playerplaytime[C.ckey];size=550x615") - -/datum/admins/proc/toggle_exempt_status(client/C) - if(!check_rights(R_ADMIN)) - return - if(!C) - to_chat(usr, "ERROR: Client not found.", confidential = TRUE) - return - - if(!C.set_db_player_flags()) - to_chat(usr, "ERROR: Unable read player flags from database. Please check logs.", confidential = TRUE) - var/dbflags = C.prefs.db_flags - var/newstate = FALSE - if(dbflags & DB_FLAG_EXEMPT) - newstate = FALSE - else - newstate = TRUE - - if(C.update_flag_db(DB_FLAG_EXEMPT, newstate)) - to_chat(usr, "ERROR: Unable to update player flags. Please check logs.", confidential = TRUE) - else - message_admins("[key_name_admin(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name_admin(C)]") - log_admin("[key_name(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name(C)]") - -/// Allow admin to add or remove traits of datum -/datum/admins/proc/modify_traits(datum/D) - if(!D) - return - - var/add_or_remove = input("Remove/Add?", "Trait Remove/Add") as null|anything in list("Add","Remove") - if(!add_or_remove) - return - var/list/availible_traits = list() - - switch(add_or_remove) - if("Add") - for(var/key in GLOB.traits_by_type) - if(istype(D,key)) - availible_traits += GLOB.traits_by_type[key] - if("Remove") - if(!GLOB.trait_name_map) - GLOB.trait_name_map = generate_trait_name_map() - for(var/trait in D.status_traits) - var/name = GLOB.trait_name_map[trait] || trait - availible_traits[name] = trait - - var/chosen_trait = input("Select trait to modify", "Trait") as null|anything in sortList(availible_traits) - if(!chosen_trait) - return - chosen_trait = availible_traits[chosen_trait] - - var/source = "adminabuse" - switch(add_or_remove) - if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it. - ADD_TRAIT(D,chosen_trait,source) - if("Remove") - var/specific = input("All or specific source ?", "Trait Remove/Add") as null|anything in list("All","Specific") - if(!specific) - return - switch(specific) - if("All") - source = null - if("Specific") - source = input("Source to be removed","Trait Remove/Add") as null|anything in sortList(D.status_traits[chosen_trait]) - if(!source) - return - REMOVE_TRAIT(D,chosen_trait,source) +/client/proc/cmd_admin_drop_everything(mob/M in GLOB.mob_list) + set category = null + set name = "Drop Everything" + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Make [M] drop everything?", "Message", "Yes", "No") + if(confirm != "Yes") + return + + for(var/obj/item/W in M) + if(!M.dropItemToGround(W)) + qdel(W) + M.regenerate_icons() + + log_admin("[key_name(usr)] made [key_name(M)] drop everything!") + var/msg = "[key_name_admin(usr)] made [ADMIN_LOOKUPFLW(M)] drop everything!" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Everything") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_subtle_message(mob/M in GLOB.mob_list) + set category = "Admin - Events" + set name = "Subtle Message" + + if(!ismob(M)) + return + if(!check_rights(R_ADMIN)) + return + + message_admins("[key_name_admin(src)] has started answering [ADMIN_LOOKUPFLW(M)]'s prayer.") + var/msg = input("Message:", text("Subtle PM to [M.key]")) as text|null + + if(!msg) + message_admins("[key_name_admin(src)] decided not to answer [ADMIN_LOOKUPFLW(M)]'s prayer") + return + if(usr) + if (usr.client) + if(usr.client.holder) + to_chat(M, "You hear a voice in your head... [msg]", confidential = TRUE) + + log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") + msg = " SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Subtle Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_headset_message(mob/M in GLOB.mob_list) + set category = "Admin - Events" + set name = "Headset Message" + + admin_headset_message(M) + +/client/proc/admin_headset_message(mob/M in GLOB.mob_list, sender = null) + var/mob/living/carbon/human/H = M + + if(!check_rights(R_ADMIN)) + return + + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) + return + if(!istype(H.ears, /obj/item/radio/headset)) + to_chat(usr, "The person you are trying to contact is not wearing a headset.", confidential = TRUE) + return + + if (!sender) + sender = input("Who is the message from?", "Sender") as null|anything in list(RADIO_CHANNEL_CENTCOM,RADIO_CHANNEL_SYNDICATE) + if(!sender) + return + + message_admins("[key_name_admin(src)] has started answering [key_name_admin(H)]'s [sender] request.") + var/input = input("Please enter a message to reply to [key_name(H)] via their headset.","Outgoing message from [sender]", "") as text|null + if(!input) + message_admins("[key_name_admin(src)] decided not to answer [key_name_admin(H)]'s [sender] request.") + return + + log_directed_talk(mob, H, input, LOG_ADMIN, "reply") + message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") + to_chat(H, "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]. Message ends.\"", confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Headset Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_mod_antag_rep(client/C in GLOB.clients, operation) + set category = "null" + set name = "Modify Antagonist Reputation" + + if(!check_rights(R_ADMIN)) + return + + var/msg = "" + var/log_text = "" + + if(operation == "zero") + log_text = "Set to 0" + SSpersistence.antag_rep -= C.ckey + else + var/prompt = "Please enter the amount of reputation to [operation]:" + + if(operation == "set") + prompt = "Please enter the new reputation value:" + + msg = input("Message:", prompt) as num|null + + if (!msg) + return + + var/ANTAG_REP_MAXIMUM = CONFIG_GET(number/antag_rep_maximum) + + if(operation == "set") + log_text = "Set to [num2text(msg)]" + SSpersistence.antag_rep[C.ckey] = max(0, min(msg, ANTAG_REP_MAXIMUM)) + else if(operation == "add") + log_text = "Added [num2text(msg)]" + SSpersistence.antag_rep[C.ckey] = min(SSpersistence.antag_rep[C.ckey]+msg, ANTAG_REP_MAXIMUM) + else if(operation == "subtract") + log_text = "Subtracted [num2text(msg)]" + SSpersistence.antag_rep[C.ckey] = max(SSpersistence.antag_rep[C.ckey]-msg, 0) + else + to_chat(src, "Invalid operation for antag rep modification: [operation] by user [key_name(usr)]", confidential = TRUE) + return + + if(SSpersistence.antag_rep[C.ckey] <= 0) + SSpersistence.antag_rep -= C.ckey + + log_admin("[key_name(usr)]: Modified [key_name(C)]'s antagonist reputation [log_text]") + message_admins("[key_name_admin(usr)]: Modified [key_name(C)]'s antagonist reputation ([log_text])") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Modify Antagonist Reputation") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_world_narrate() + set category = "Admin - Events" + set name = "Global Narrate" + + if(!check_rights(R_ADMIN)) + return + + var/msg = input("Message:", text("Enter the text you wish to appear to everyone:")) as text|null + + if (!msg) + return + to_chat(world, "[msg]", confidential = TRUE) + log_admin("GlobalNarrate: [key_name(usr)] : [msg]") + message_admins("[key_name_admin(usr)] Sent a global narrate") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Global Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_direct_narrate(mob/M) + set category = "Admin - Events" + set name = "Direct Narrate" + + if(!check_rights(R_ADMIN)) + return + + if(!M) + M = input("Direct narrate to whom?", "Active Players") as null|anything in GLOB.player_list + + if(!M) + return + + var/msg = input("Message:", text("Enter the text you wish to appear to your target:")) as text|null + + if( !msg ) + return + + to_chat(M, msg, confidential = TRUE) + log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]") + msg = " DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
                " + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Direct Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_local_narrate(atom/A) + set category = "Admin - Events" + set name = "Local Narrate" + + if(!check_rights(R_ADMIN)) + return + if(!A) + return + var/range = input("Range:", "Narrate to mobs within how many tiles:", 7) as num|null + if(!range) + return + var/msg = input("Message:", text("Enter the text you wish to appear to everyone within view:")) as text|null + if (!msg) + return + for(var/mob/M in view(range,A)) + to_chat(M, msg, confidential = TRUE) + + log_admin("LocalNarrate: [key_name(usr)] at [AREACOORD(A)]: [msg]") + message_admins(" LocalNarrate: [key_name_admin(usr)] at [ADMIN_VERBOSEJMP(A)]: [msg]
                ") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Local Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_godmode(mob/M in GLOB.mob_list) + set category = "Admin - Game" + set name = "Godmode" + if(!check_rights(R_ADMIN)) + return + + M.status_flags ^= GODMODE + to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]", confidential = TRUE) + + log_admin("[key_name(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") + var/msg = "[key_name_admin(usr)] has toggled [ADMIN_LOOKUPFLW(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Godmode", "[M.status_flags & GODMODE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/proc/cmd_admin_mute(whom, mute_type, automute = 0) + if(!whom) + return + + var/muteunmute + var/mute_string + var/feedback_string + switch(mute_type) + if(MUTE_IC) + mute_string = "IC (say and emote)" + feedback_string = "IC" + if(MUTE_OOC) + mute_string = "OOC" + feedback_string = "OOC" + if(MUTE_PRAY) + mute_string = "pray" + feedback_string = "Pray" + if(MUTE_ADMINHELP) + mute_string = "adminhelp, admin PM and ASAY" + feedback_string = "Adminhelp" + if(MUTE_DEADCHAT) + mute_string = "deadchat and DSAY" + feedback_string = "Deadchat" + if(MUTE_ALL) + mute_string = "everything" + feedback_string = "Everything" + else + return + + var/client/C + if(istype(whom, /client)) + C = whom + else if(istext(whom)) + C = GLOB.directory[whom] + else + return + + var/datum/preferences/P + if(C) + P = C.prefs + else + P = GLOB.preferences_datums[whom] + if(!P) + return + + if(automute) + if(!CONFIG_GET(flag/automute_on)) + return + else + if(!check_rights()) + return + + if(automute) + muteunmute = "auto-muted" + P.muted |= mute_type + log_admin("SPAM AUTOMUTE: [muteunmute] [key_name(whom)] from [mute_string]") + message_admins("SPAM AUTOMUTE: [muteunmute] [key_name_admin(whom)] from [mute_string].") + if(C) + to_chat(C, "You have been [muteunmute] from [mute_string] by the SPAM AUTOMUTE system. Contact an admin.", confidential = TRUE) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Auto Mute [feedback_string]", "1")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + + if(P.muted & mute_type) + muteunmute = "unmuted" + P.muted &= ~mute_type + else + muteunmute = "muted" + P.muted |= mute_type + + log_admin("[key_name(usr)] has [muteunmute] [key_name(whom)] from [mute_string]") + message_admins("[key_name_admin(usr)] has [muteunmute] [key_name_admin(whom)] from [mute_string].") + if(C) + to_chat(C, "You have been [muteunmute] from [mute_string] by [key_name(usr, include_name = FALSE)].", confidential = TRUE) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Mute [feedback_string]", "[P.muted & mute_type]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +//I use this proc for respawn character too. /N +/proc/create_xeno(ckey) + if(!ckey) + var/list/candidates = list() + for(var/mob/M in GLOB.player_list) + if(M.stat != DEAD) + continue //we are not dead! + if(!(ROLE_ALIEN in M.client.prefs.be_special)) + continue //we don't want to be an alium + if(M.client.is_afk()) + continue //we are afk + if(M.mind && M.mind.current && M.mind.current.stat != DEAD) + continue //we have a live body we are tied to + candidates += M.ckey + if(candidates.len) + ckey = input("Pick the player you want to respawn as a xeno.", "Suitable Candidates") as null|anything in sortKey(candidates) + else + to_chat(usr, "Error: create_xeno(): no suitable candidates.", confidential = TRUE) + if(!istext(ckey)) + return 0 + + var/alien_caste = input(usr, "Please choose which caste to spawn.","Pick a caste",null) as null|anything in list("Queen","Praetorian","Hunter","Sentinel","Drone","Larva") + var/obj/effect/landmark/spawn_here = GLOB.xeno_spawn.len ? pick(GLOB.xeno_spawn) : null + var/mob/living/carbon/alien/new_xeno + switch(alien_caste) + if("Queen") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(spawn_here) + if("Praetorian") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(spawn_here) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(spawn_here) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(spawn_here) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(spawn_here) + if("Larva") + new_xeno = new /mob/living/carbon/alien/larva(spawn_here) + else + return 0 + if(!spawn_here) + SSjob.SendToLateJoin(new_xeno, FALSE) + + new_xeno.ckey = ckey + var/msg = "[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste]." + message_admins(msg) + admin_ticket_log(new_xeno, msg) + return 1 + +/* +If a guy was gibbed and you want to revive him, this is a good way to do so. +Works kind of like entering the game with a new character. Character receives a new mind if they didn't have one. +Traitors and the like can also be revived with the previous role mostly intact. +/N */ +/client/proc/respawn_character() + set category = "Admin - Game" + set name = "Respawn Character" + set desc = "Respawn a person that has been gibbed/dusted/killed. They must be a ghost for this to work and preferably should not have a body to go back into." + if(!check_rights(R_ADMIN)) + return + + var/input = ckey(input(src, "Please specify which key will be respawned.", "Key", "")) + if(!input) + return + + var/mob/dead/observer/G_found + for(var/mob/dead/observer/G in GLOB.player_list) + if(G.ckey == input) + G_found = G + break + + if(!G_found)//If a ghost was not found. + to_chat(usr, "There is no active key like that in the game or the person is not currently a ghost.", confidential = TRUE) + return + + if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something + //Check if they were an alien + if(G_found.mind.assigned_role == ROLE_ALIEN) + if(alert("This character appears to have been an alien. Would you like to respawn them as such?",,"Yes","No")=="Yes") + var/turf/T + if(GLOB.xeno_spawn.len) + T = pick(GLOB.xeno_spawn) + + var/mob/living/carbon/alien/new_xeno + switch(G_found.mind.special_role)//If they have a mind, we can determine which caste they were. + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(T) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(T) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(T) + if("Praetorian") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(T) + if("Queen") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(T) + else//If we don't know what special role they have, for whatever reason, or they're a larva. + create_xeno(G_found.ckey) + return + + if(!T) + SSjob.SendToLateJoin(new_xeno, FALSE) + + //Now to give them their mind back. + G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use + new_xeno.key = G_found.key + to_chat(new_xeno, "You have been fully respawned. Enjoy the game.", confidential = TRUE) + var/msg = "[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno." + message_admins(msg) + admin_ticket_log(new_xeno, msg) + return //all done. The ghost is auto-deleted + + //check if they were a monkey + else if(findtext(G_found.real_name,"monkey")) + if(alert("This character appears to have been a monkey. Would you like to respawn them as such?",,"Yes","No")=="Yes") + var/mob/living/carbon/monkey/new_monkey = new + SSjob.SendToLateJoin(new_monkey) + G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use + new_monkey.key = G_found.key + to_chat(new_monkey, "You have been fully respawned. Enjoy the game.", confidential = TRUE) + var/msg = "[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno." + message_admins(msg) + admin_ticket_log(new_monkey, msg) + return //all done. The ghost is auto-deleted + + + //Ok, it's not a xeno or a monkey. So, spawn a human. + var/mob/living/carbon/human/new_character = new//The mob being spawned. + SSjob.SendToLateJoin(new_character) + + var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. + if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something + /*Try and locate a record for the person being respawned through GLOB.data_core. + This isn't an exact science but it does the trick more often than not.*/ + var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") + + record_found = find_record("id", id, GLOB.data_core.locked) + + if(record_found)//If they have a record we can determine a few things. + new_character.real_name = record_found.fields["name"] + new_character.gender = record_found.fields["gender"] + new_character.age = record_found.fields["age"] + new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], null, record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"]) + else + var/datum/preferences/A = new() + A.copy_to(new_character) + A.real_name = G_found.real_name + new_character.dna.update_dna_identity() + + new_character.name = new_character.real_name + + if(G_found.mind && !G_found.mind.active) + G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use + else + new_character.mind_initialize() + if(!new_character.mind.assigned_role) + new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role. + + new_character.key = G_found.key + + /* + The code below functions with the assumption that the mob is already a traitor if they have a special role. + So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. + If they don't have a mind, they obviously don't have a special role. + */ + + //Two variables to properly announce later on. + var/admin = key_name_admin(src) + var/player_key = G_found.key + + //Now for special roles and equipment. + var/datum/antagonist/traitor/traitordatum = new_character.mind.has_antag_datum(/datum/antagonist/traitor) + if(traitordatum) + SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1) + traitordatum.equip() + + + switch(new_character.mind.special_role) + if(ROLE_WIZARD) + new_character.forceMove(pick(GLOB.wizardstart)) + var/datum/antagonist/wizard/A = new_character.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) + A.equip_wizard() + if(ROLE_SYNDICATE) + new_character.forceMove(pick(GLOB.nukeop_start)) + var/datum/antagonist/nukeop/N = new_character.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) + N.equip_op() + if(ROLE_NINJA) + var/list/ninja_spawn = list() + for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) + ninja_spawn += L + var/datum/antagonist/ninja/ninjadatum = new_character.mind.has_antag_datum(/datum/antagonist/ninja) + ninjadatum.equip_space_ninja() + if(ninja_spawn.len) + new_character.forceMove(pick(ninja_spawn)) + + else//They may also be a cyborg or AI. + switch(new_character.mind.assigned_role) + if("Cyborg")//More rigging to make em' work and check if they're traitor. + new_character = new_character.Robotize(TRUE) + if("AI") + new_character = new_character.AIize() + else + SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. + + //Announces the character on all the systems, based on the record. + if(!issilicon(new_character))//If they are not a cyborg/AI. + if(!record_found&&new_character.mind.assigned_role!=new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. MODE people are not announced anyway. + //Power to the user! + if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") + GLOB.data_core.manifest_inject(new_character) + + if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") + AnnounceArrival(new_character, new_character.mind.assigned_role) + + var/msg = "[admin] has respawned [player_key] as [new_character.real_name]." + message_admins(msg) + admin_ticket_log(new_character, msg) + + to_chat(new_character, "You have been fully respawned. Enjoy the game.", confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Respawn Character") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return new_character + +/client/proc/cmd_admin_add_freeform_ai_law() + set category = "Admin - Events" + set name = "Add Custom AI law" + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Please enter anything you want the AI to do. Anything. Serious.", "What?", "") as text|null + if(!input) + return + + log_admin("Admin [key_name(usr)] has added a new AI law - [input]") + message_admins("Admin [key_name_admin(usr)] has added a new AI law - [input]") + + var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") + var/announce_ion_laws = (show_log == "Yes" ? 100 : 0) + + var/datum/round_event/ion_storm/add_law_only/ion = new() + ion.announceChance = announce_ion_laws + ion.ionMessage = input + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Add Custom AI Law") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_rejuvenate(mob/living/M in GLOB.mob_list) + set category = "Debug" + set name = "Rejuvenate" + + if(!check_rights(R_ADMIN)) + return + + if(!mob) + return + if(!istype(M)) + alert("Cannot revive a ghost") + return + M.revive(full_heal = TRUE, admin_revive = TRUE) + + log_admin("[key_name(usr)] healed / revived [key_name(M)]") + var/msg = "Admin [key_name_admin(usr)] healed / revived [ADMIN_LOOKUPFLW(M)]!" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Rejuvenate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_create_centcom_report() + set category = "Admin - Events" + set name = "Create Command Report" + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Enter a Command Report. Ensure it makes sense IC. Command's name is currently set to [command_name()].", "What?", "") as message|null + if(!input) + return + + var/confirm = alert(src, "Do you want to announce the contents of the report to the crew?", "Announce", "Yes", "No", "Cancel") + var/announce_command_report = TRUE + switch(confirm) + if("Yes") + priority_announce(input, null, 'sound/ai/commandreport.ogg') + announce_command_report = FALSE + if("Cancel") + return + + print_command_report(input, "[announce_command_report ? "Classified " : ""][command_name()] Update", announce_command_report) + + log_admin("[key_name(src)] has created a command report: [input]") + message_admins("[key_name_admin(src)] has created a command report") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Create Command Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_change_command_name() + set category = "Admin - Events" + set name = "Change Command Name" + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Please input a new name for Central Command.", "What?", "") as text|null + if(!input) + return + change_command_name(input) + message_admins("[key_name_admin(src)] has changed Central Command's name to [input]") + log_admin("[key_name(src)] has changed the Central Command name to: [input]") + +/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in world) + set category = "Debug" + set name = "Delete" + + if(!check_rights(R_SPAWN|R_DEBUG)) + return + + admin_delete(A) + +/client/proc/cmd_admin_list_open_jobs() + set category = "Admin - Game" + set name = "Manage Job Slots" + + if(!check_rights(R_ADMIN)) + return + holder.manage_free_slots() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Manage Job Slots") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_explosion(atom/O as obj|mob|turf in world) + set category = "Fun" + set name = "Explosion" + + if(!check_rights(R_ADMIN)) + return + + var/devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null + if(devastation == null) + return + var/heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null + if(heavy == null) + return + var/light = input("Range of light impact. -1 to none", text("Input")) as num|null + if(light == null) + return + var/flash = input("Range of flash. -1 to none", text("Input")) as num|null + if(flash == null) + return + var/flames = input("Range of flames. -1 to none", text("Input")) as num|null + if(flames == null) + return + + if ((devastation != -1) || (heavy != -1) || (light != -1) || (flash != -1) || (flames != -1)) + if ((devastation > 20) || (heavy > 20) || (light > 20) || (flames > 20)) + if (alert(src, "Are you sure you want to do this? It will laaag.", "Confirmation", "Yes", "No") == "No") + return + + explosion(O, devastation, heavy, light, flash, null, null,flames) + log_admin("[key_name(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") + message_admins("[key_name_admin(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Explosion") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + else + return + +/client/proc/cmd_admin_emp(atom/O as obj|mob|turf in world) + set category = "Fun" + set name = "EM Pulse" + + if(!check_rights(R_ADMIN)) + return + + var/heavy = input("Range of heavy pulse.", text("Input")) as num|null + if(heavy == null) + return + var/light = input("Range of light pulse.", text("Input")) as num|null + if(light == null) + return + + if (heavy || light) + + empulse(O, heavy, light) + log_admin("[key_name(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") + message_admins("[key_name_admin(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "EM Pulse") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + return + else + return + +/client/proc/cmd_admin_gib(mob/M in GLOB.mob_list) + set category = "Fun" + set name = "Gib" + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Drop a brain?", "Confirm", "Yes", "No","Cancel") + if(confirm == "Cancel") + return + //Due to the delay here its easy for something to have happened to the mob + if(!M) + return + + log_admin("[key_name(usr)] has gibbed [key_name(M)]") + message_admins("[key_name_admin(usr)] has gibbed [key_name_admin(M)]") + + if(isobserver(M)) + new /obj/effect/gibspawner/generic(get_turf(M)) + return + if(confirm == "Yes") + M.gib() + else + M.gib(1) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_gib_self() + set name = "Gibself" + set category = "Fun" + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm == "Yes") + log_admin("[key_name(usr)] used gibself.") + message_admins("[key_name_admin(usr)] used gibself.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib Self") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + mob.gib(1, 1, 1) + +/client/proc/cmd_admin_check_contents(mob/living/M in GLOB.mob_list) + set category = "Debug" + set name = "Check Contents" + + var/list/L = M.get_contents() + for(var/t in L) + to_chat(usr, "[t]", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Contents") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_view_range() + set category = "Admin - Game" + set name = "Change View Range" + set desc = "switches between 1x and custom views" + + if(view_size.getView() == view_size.default) + view_size.setTo(input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128) - 7) + else + view_size.resetToDefault(getScreenSize(prefs.widescreenpref)) + + log_admin("[key_name(usr)] changed their view range to [view].") + //message_admins("\blue [key_name_admin(usr)] changed their view range to [view].") //why? removed by order of XSI + + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Change View Range", "[view]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/admin_call_shuttle() + + set category = "Admin - Events" + set name = "Call Shuttle" + + if(EMERGENCY_AT_LEAST_DOCKED) + return + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm != "Yes") + return + + SSshuttle.emergency.request() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Call Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] admin-called the emergency shuttle.") + message_admins("[key_name_admin(usr)] admin-called the emergency shuttle.") + return + +/client/proc/admin_cancel_shuttle() + set category = "Admin - Events" + set name = "Cancel Shuttle" + if(!check_rights(0)) + return + if(alert(src, "You sure?", "Confirm", "Yes", "No") != "Yes") + return + + if(EMERGENCY_AT_LEAST_DOCKED) + return + + SSshuttle.emergency.cancel() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Cancel Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] admin-recalled the emergency shuttle.") + message_admins("[key_name_admin(usr)] admin-recalled the emergency shuttle.") + + return + +/client/proc/everyone_random() + set category = "Fun" + set name = "Make Everyone Random" + set desc = "Make everyone have a random appearance. You can only use this before rounds!" + + if(SSticker.HasRoundStarted()) + to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!", confidential = TRUE) + return + + var/frn = CONFIG_GET(flag/force_random_names) + if(frn) + CONFIG_SET(flag/force_random_names, FALSE) + message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.") + to_chat(usr, "Disabled.", confidential = TRUE) + return + + + var/notifyplayers = alert(src, "Do you want to notify the players?", "Options", "Yes", "No", "Cancel") + if(notifyplayers == "Cancel") + return + + log_admin("Admin [key_name(src)] has forced the players to have random appearances.") + message_admins("Admin [key_name_admin(usr)] has forced the players to have random appearances.") + + if(notifyplayers == "Yes") + to_chat(world, "Admin [usr.key] has forced the players to have completely random identities!", confidential = TRUE) + + to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.", confidential = TRUE) + + CONFIG_SET(flag/force_random_names, TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Make Everyone Random") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/toggle_random_events() + set category = "Server" + set name = "Toggle random events on/off" + set desc = "Toggles random events such as meteors, black holes, blob (but not space dust) on/off" + var/new_are = !CONFIG_GET(flag/allow_random_events) + CONFIG_SET(flag/allow_random_events, new_are) + if(new_are) + to_chat(usr, "Random events enabled", confidential = TRUE) + message_admins("Admin [key_name_admin(usr)] has enabled random events.") + else + to_chat(usr, "Random events disabled", confidential = TRUE) + message_admins("Admin [key_name_admin(usr)] has disabled random events.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Random Events", "[new_are ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/admin_change_sec_level() + set category = "Admin - Events" + set name = "Set Security Level" + set desc = "Changes the security level. Announcement only, i.e. setting to Delta won't activate nuke" + + if(!check_rights(R_ADMIN)) + return + + var/level = input("Select security level to change to","Set Security Level") as null|anything in list("green","blue","red","delta") + if(level) + set_security_level(level) + + log_admin("[key_name(usr)] changed the security level to [level]") + message_admins("[key_name_admin(usr)] changed the security level to [level]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Security Level [capitalize(level)]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_nuke(obj/machinery/nuclearbomb/N in GLOB.nuke_list) + set name = "Toggle Nuke" + set category = "Admin - Events" + set popup_menu = 0 + if(!check_rights(R_DEBUG)) + return + + if(!N.timing) + var/newtime = input(usr, "Set activation timer.", "Activate Nuke", "[N.timer_set]") as num|null + if(!newtime) + return + N.timer_set = newtime + N.set_safety() + N.set_active() + + log_admin("[key_name(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [AREACOORD(N)].") + message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_combo_hud() + set category = "Admin - Game" + set name = "Toggle Combo HUD" + set desc = "Toggles the Admin Combo HUD (antag, sci, med, eng)" + + if(!check_rights(R_ADMIN)) + return + + var/adding_hud = !has_antag_hud() + + for(var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) // add data huds + var/datum/atom_hud/H = GLOB.huds[hudtype] + (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) + for(var/datum/atom_hud/antag/H in GLOB.huds) // add antag huds + (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) + + if(prefs.toggles & COMBOHUD_LIGHTING) + if(adding_hud) + mob.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + else + mob.lighting_alpha = initial(mob.lighting_alpha) + + mob.update_sight() + + to_chat(usr, "You toggled your admin combo HUD [adding_hud ? "ON" : "OFF"].", confidential = TRUE) + message_admins("[key_name_admin(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") + log_admin("[key_name(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[adding_hud ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/has_antag_hud() + var/datum/atom_hud/A = GLOB.huds[ANTAG_HUD_TRAITOR] + return A.hudusers[mob] + + +/client/proc/run_weather() + set category = "Admin - Events" + set name = "Run Weather" + set desc = "Triggers a weather on the z-level you choose." + + if(!holder) + return + + var/weather_type = input("Choose a weather", "Weather") as null|anything in sortList(subtypesof(/datum/weather), /proc/cmp_typepaths_asc) + if(!weather_type) + return + + var/turf/T = get_turf(mob) + var/z_level = input("Z-Level to target?", "Z-Level", T?.z) as num|null + if(!isnum(z_level)) + return + + SSweather.run_weather(weather_type, z_level) + + message_admins("[key_name_admin(usr)] started weather of type [weather_type] on the z-level [z_level].") + log_admin("[key_name(usr)] started weather of type [weather_type] on the z-level [z_level].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Run Weather") + +/client/proc/mass_zombie_infection() + set category = "Fun" + set name = "Mass Zombie Infection" + set desc = "Infects all humans with a latent organ that will zombify \ + them on death." + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Please confirm you want to add latent zombie organs in all humans?", "Confirm Zombies", "Yes", "No") + if(confirm != "Yes") + return + + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + new /obj/item/organ/zombie_infection/nodamage(H) + + message_admins("[key_name_admin(usr)] added a latent zombie infection to all humans.") + log_admin("[key_name(usr)] added a latent zombie infection to all humans.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Infection") + +/client/proc/mass_zombie_cure() + set category = "Fun" + set name = "Mass Zombie Cure" + set desc = "Removes the zombie infection from all humans, returning them to normal." + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Please confirm you want to cure all zombies?", "Confirm Zombie Cure", "Yes", "No") + if(confirm != "Yes") + return + + for(var/obj/item/organ/zombie_infection/nodamage/I in GLOB.zombie_infection_list) + qdel(I) + + message_admins("[key_name_admin(usr)] cured all zombies.") + log_admin("[key_name(usr)] cured all zombies.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Cure") + +/client/proc/polymorph_all() + set category = "Fun" + set name = "Polymorph All" + set desc = "Applies the effects of the bolt of change to every single mob." + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Please confirm you want polymorph all mobs?", "Confirm Polymorph", "Yes", "No") + if(confirm != "Yes") + return + + var/list/mobs = shuffle(GLOB.alive_mob_list.Copy()) // might change while iterating + var/who_did_it = key_name_admin(usr) + + message_admins("[key_name_admin(usr)] started polymorphed all living mobs.") + log_admin("[key_name(usr)] polymorphed all living mobs.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Polymorph All") + + for(var/mob/living/M in mobs) + CHECK_TICK + + if(!M) + continue + + M.audible_message("...wabbajack...wabbajack...") + playsound(M.loc, 'sound/magic/staff_change.ogg', 50, TRUE, -1) + + wabbajack(M) + + message_admins("Mass polymorph started by [who_did_it] is complete.") + + +/client/proc/show_tip() + set category = "Admin" + set name = "Show Tip" + set desc = "Sends a tip (that you specify) to all players. After all \ + you're the experienced player here." + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Please specify your tip that you want to send to the players.", "Tip", "") as message|null + if(!input) + return + + if(!SSticker) + return + + SSticker.selected_tip = input + + // If we've already tipped, then send it straight away. + if(SSticker.tipped) + SSticker.send_tip_of_the_round() + + + message_admins("[key_name_admin(usr)] sent a tip of the round.") + log_admin("[key_name(usr)] sent \"[input]\" as the Tip of the Round.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Tip") + +/client/proc/modify_goals() + set category = "Debug" + set name = "Modify goals" + + if(!check_rights(R_ADMIN)) + return + + holder.modify_goals() + +/datum/admins/proc/modify_goals() + var/dat = "" + for(var/datum/station_goal/S in SSticker.mode.station_goals) + dat += "[S.name] - Announce | Remove
                " + dat += "
                Add New Goal" + usr << browse(dat, "window=goals;size=400x400") + +/proc/immerse_player(mob/living/carbon/target, toggle=TRUE, remove=FALSE) + var/list/immersion_components = list(/datum/component/manual_breathing, /datum/component/manual_blinking) + + for(var/immersies in immersion_components) + var/has_component = target.GetComponent(immersies) + + if(has_component && (toggle || remove)) + qdel(has_component) + else if(toggle || !remove) + target.AddComponent(immersies) + +/proc/mass_immerse(remove=FALSE) + for(var/mob/living/carbon/M in GLOB.mob_list) + immerse_player(M, toggle=FALSE, remove=remove) + +/client/proc/toggle_hub() + set category = "Server" + set name = "Toggle Hub" + + world.update_hub_visibility(!GLOB.hub_visibility) + + log_admin("[key_name(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") + message_admins("[key_name_admin(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") + if (GLOB.hub_visibility && !world.reachable) + message_admins("WARNING: The server will not show up on the hub because byond is detecting that a filewall is blocking incoming connections.") + + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Hub Visibility", "[GLOB.hub_visibility ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/smite(mob/living/target as mob) + set name = "Smite" + set category = "Fun" + if(!check_rights(R_ADMIN) || !check_rights(R_FUN)) + return + + var/list/punishment_list = list(ADMIN_PUNISHMENT_LIGHTNING, + ADMIN_PUNISHMENT_BRAINDAMAGE, + ADMIN_PUNISHMENT_GIB, + ADMIN_PUNISHMENT_BSA, + ADMIN_PUNISHMENT_FIREBALL, + ADMIN_PUNISHMENT_ROD, + ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, + ADMIN_PUNISHMENT_SUPPLYPOD, + ADMIN_PUNISHMENT_MAZING, + ADMIN_PUNISHMENT_IMMERSE, + ADMIN_PUNISHMENT_FAT, + ADMIN_PUNISHMENT_FAKEBWOINK, + ADMIN_PUNISHMENT_NUGGET, + ADMIN_PUNISHMENT_CRACK, + ADMIN_PUNISHMENT_BLEED, + ADMIN_PUNISHMENT_SCARIFY, + ADMIN_PUNISHMENT_SHOES + ) + + var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in sortList(punishment_list) + + if(QDELETED(target) || !punishment) + return + + switch(punishment) + if(ADMIN_PUNISHMENT_LIGHTNING) + var/turf/T = get_step(get_step(target, NORTH), NORTH) + T.Beam(target, icon_state="lightning[rand(1,12)]", time = 5) + target.adjustFireLoss(75) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + H.electrocution_animation(40) + to_chat(target, "The gods have punished you for your sins!", confidential = TRUE) + if(ADMIN_PUNISHMENT_BRAINDAMAGE) + target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) + if(ADMIN_PUNISHMENT_GIB) + target.gib(FALSE) + if(ADMIN_PUNISHMENT_BSA) + bluespace_artillery(target) + if(ADMIN_PUNISHMENT_FIREBALL) + new /obj/effect/temp_visual/target(get_turf(target)) + if(ADMIN_PUNISHMENT_ROD) + var/turf/T = get_turf(target) + var/startside = pick(GLOB.cardinals) + var/turf/startT = spaceDebrisStartLoc(startside, T.z) + var/turf/endT = spaceDebrisFinishLoc(startside, T.z) + new /obj/effect/immovablerod(startT, endT,target) + if(ADMIN_PUNISHMENT_SUPPLYPOD_QUICK) + var/target_path = input(usr,"Enter typepath of an atom you'd like to send with the pod (type \"empty\" to send an empty pod):" ,"Typepath","/obj/item/reagent_containers/food/snacks/grown/harebell") as null|text + var/obj/structure/closet/supplypod/centcompod/pod = new() + pod.damage = 40 + pod.explosionSize = list(0,0,0,2) + pod.effectStun = TRUE + if (isnull(target_path)) //The user pressed "Cancel" + return + if (target_path != "empty")//if you didn't type empty, we want to load the pod with a delivery + var/delivery = text2path(target_path) + if(!ispath(delivery)) + delivery = pick_closest_path(target_path) + if(!delivery) + alert("ERROR: Incorrect / improper path given.") + return + new delivery(pod) + new /obj/effect/pod_landingzone(get_turf(target), pod) + if(ADMIN_PUNISHMENT_SUPPLYPOD) + var/datum/centcom_podlauncher/plaunch = new(usr) + if(!holder) + return + plaunch.specificTarget = target + plaunch.launchChoice = 0 + plaunch.damageChoice = 1 + plaunch.explosionChoice = 1 + plaunch.temp_pod.damage = 40//bring the mother fuckin ruckus + plaunch.temp_pod.explosionSize = list(0,0,0,2) + plaunch.temp_pod.effectStun = TRUE + plaunch.ui_interact(usr) + return //We return here because punish_log() is handled by the centcom_podlauncher datum + if(ADMIN_PUNISHMENT_MAZING) + if(!puzzle_imprison(target)) + to_chat(usr,"Imprisonment failed!", confidential = TRUE) + return + if(ADMIN_PUNISHMENT_IMMERSE) + immerse_player(target) + if(ADMIN_PUNISHMENT_FAT) + target.set_nutrition(NUTRITION_LEVEL_FAT*2) + if(ADMIN_PUNISHMENT_FAKEBWOINK) + SEND_SOUND(target, 'sound/effects/adminhelp.ogg') + if(ADMIN_PUNISHMENT_NUGGET) + if(!iscarbon(target)) + to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) + return + var/mob/living/carbon/C = target + var/timer = 2 SECONDS + for(var/obj/item/bodypart/thing in C.bodyparts) + if(thing.body_part == HEAD || thing.body_part == CHEST) + continue + addtimer(CALLBACK(thing, /obj/item/bodypart/.proc/dismember), timer) + addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, C, 'sound/effects/cartoon_pop.ogg', 70), timer) + addtimer(CALLBACK(C, /mob/living/.proc/spin, 4, 1), timer - 0.4 SECONDS) + timer += 2 SECONDS + if(ADMIN_PUNISHMENT_CRACK) + if(!iscarbon(target)) + to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) + return + var/mob/living/carbon/C = target + for(var/obj/item/bodypart/squish_part in C.bodyparts) + var/type_wound = pick(list(/datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/moderate)) + squish_part.force_wound_upwards(type_wound, smited=TRUE) + if(ADMIN_PUNISHMENT_BLEED) + if(!iscarbon(target)) + to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) + return + var/mob/living/carbon/C = target + for(var/obj/item/bodypart/slice_part in C.bodyparts) + var/type_wound = pick(list(/datum/wound/brute/cut/severe, /datum/wound/brute/cut/moderate)) + slice_part.force_wound_upwards(type_wound, smited=TRUE) + type_wound = pick(list(/datum/wound/brute/cut/critical, /datum/wound/brute/cut/severe, /datum/wound/brute/cut/moderate)) + slice_part.force_wound_upwards(type_wound, smited=TRUE) + type_wound = pick(list(/datum/wound/brute/cut/critical, /datum/wound/brute/cut/severe)) + slice_part.force_wound_upwards(type_wound, smited=TRUE) + if(ADMIN_PUNISHMENT_SCARIFY) + if(!iscarbon(target)) + to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) + return + var/mob/living/carbon/C = target + C.generate_fake_scars(rand(1, 4)) + to_chat(C, "You feel your body grow jaded and torn...") + if(ADMIN_PUNISHMENT_SHOES) + if(!iscarbon(target)) + to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE) + return + var/mob/living/carbon/C = target + var/obj/item/clothing/shoes/sick_kicks = C.shoes + if(!sick_kicks?.can_be_tied) + to_chat(usr,"[C] does not have knottable shoes!", confidential = TRUE) + return + sick_kicks.adjust_laces(SHOES_KNOTTED) + + punish_log(target, punishment) + +/client/proc/punish_log(whom, punishment) + var/msg = "[key_name_admin(usr)] punished [key_name_admin(whom)] with [punishment]." + message_admins(msg) + admin_ticket_log(whom, msg) + log_admin("[key_name(usr)] punished [key_name(whom)] with [punishment].") + +/client/proc/trigger_centcom_recall() + if(!check_rights(R_ADMIN)) + return + var/message = pick(GLOB.admiral_messages) + message = input("Enter message from the on-call admiral to be put in the recall report.", "Admiral Message", message) as text|null + + if(!message) + return + + message_admins("[key_name_admin(usr)] triggered a CentCom recall, with the admiral message of: [message]") + log_game("[key_name(usr)] triggered a CentCom recall, with the message of: [message]") + SSshuttle.centcom_recall(SSshuttle.emergency.timer, message) + +/client/proc/cmd_admin_check_player_exp() //Allows admins to determine who the newer players are. + set category = "Admin" + set name = "Player Playtime" + if(!check_rights(R_ADMIN)) + return + + if(!CONFIG_GET(flag/use_exp_tracking)) + to_chat(usr, "Tracking is disabled in the server configuration file.", confidential = TRUE) + return + + var/list/msg = list() + msg += "Playtime ReportPlaytime:
                " + src << browse(msg.Join(), "window=Player_playtime_check") + +/datum/admins/proc/cmd_show_exp_panel(client/C) + if(!check_rights(R_ADMIN)) + return + if(!C) + to_chat(usr, "ERROR: Client not found.", confidential = TRUE) + return + if(!CONFIG_GET(flag/use_exp_tracking)) + to_chat(usr, "Tracking is disabled in the server configuration file.", confidential = TRUE) + return + + var/list/body = list() + body += "Playtime for [C.key]
                Playtime:" + body += C.get_exp_report() + body += "Toggle Exempt status" + body += "" + usr << browse(body.Join(), "window=playerplaytime[C.ckey];size=550x615") + +/datum/admins/proc/toggle_exempt_status(client/C) + if(!check_rights(R_ADMIN)) + return + if(!C) + to_chat(usr, "ERROR: Client not found.", confidential = TRUE) + return + + if(!C.set_db_player_flags()) + to_chat(usr, "ERROR: Unable read player flags from database. Please check logs.", confidential = TRUE) + var/dbflags = C.prefs.db_flags + var/newstate = FALSE + if(dbflags & DB_FLAG_EXEMPT) + newstate = FALSE + else + newstate = TRUE + + if(C.update_flag_db(DB_FLAG_EXEMPT, newstate)) + to_chat(usr, "ERROR: Unable to update player flags. Please check logs.", confidential = TRUE) + else + message_admins("[key_name_admin(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name_admin(C)]") + log_admin("[key_name(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name(C)]") + +/// Allow admin to add or remove traits of datum +/datum/admins/proc/modify_traits(datum/D) + if(!D) + return + + var/add_or_remove = input("Remove/Add?", "Trait Remove/Add") as null|anything in list("Add","Remove") + if(!add_or_remove) + return + var/list/availible_traits = list() + + switch(add_or_remove) + if("Add") + for(var/key in GLOB.traits_by_type) + if(istype(D,key)) + availible_traits += GLOB.traits_by_type[key] + if("Remove") + if(!GLOB.trait_name_map) + GLOB.trait_name_map = generate_trait_name_map() + for(var/trait in D.status_traits) + var/name = GLOB.trait_name_map[trait] || trait + availible_traits[name] = trait + + var/chosen_trait = input("Select trait to modify", "Trait") as null|anything in sortList(availible_traits) + if(!chosen_trait) + return + chosen_trait = availible_traits[chosen_trait] + + var/source = "adminabuse" + switch(add_or_remove) + if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it. + ADD_TRAIT(D,chosen_trait,source) + if("Remove") + var/specific = input("All or specific source ?", "Trait Remove/Add") as null|anything in list("All","Specific") + if(!specific) + return + switch(specific) + if("All") + source = null + if("Specific") + source = input("Source to be removed","Trait Remove/Add") as null|anything in sortList(D.status_traits[chosen_trait]) + if(!source) + return + REMOVE_TRAIT(D,chosen_trait,source) diff --git a/code/modules/admin/verbs/shuttlepanel.dm b/code/modules/admin/verbs/shuttlepanel.dm index 0b9bb0fabca..f90dc049ce6 100644 --- a/code/modules/admin/verbs/shuttlepanel.dm +++ b/code/modules/admin/verbs/shuttlepanel.dm @@ -1,76 +1,76 @@ -/datum/admins/proc/open_shuttlepanel() - set category = "Admin - Events" - set name = "Shuttle Manipulator" - set desc = "Opens the shuttle manipulator UI." - - if(!check_rights(R_ADMIN)) - return - - SSshuttle.ui_interact(usr) - - -/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user) - var/list/options = list() - - for(var/port in SSshuttle.stationary) - if (istype(port, /obj/docking_port/stationary/transit)) - continue // please don't do this - var/obj/docking_port/stationary/S = port - if (canDock(S) == SHUTTLE_CAN_DOCK) - options[S.name || S.id] = S - - options += "--------" - options += "Infinite Transit" - options += "Delete Shuttle" - options += "Into The Sunset (delete & greentext 'escape')" - - var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options - if(!selection) - return - - switch(selection) - if("Infinite Transit") - destination = null - mode = SHUTTLE_IGNITING - setTimer(ignitionTime) - - if("Delete Shuttle") - if(alert(user, "Really delete [name || id]?", "Delete Shuttle", "Cancel", "Really!") != "Really!") - return - jumpToNullSpace() - - if("Into The Sunset (delete & greentext 'escape')") - if(alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", "Cancel", "Really!") != "Really!") - return - intoTheSunset() - - else - if(options[selection]) - request(options[selection]) - -/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user) - return // use the existing verbs for this - -/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user) - switch(alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", "Fly", "Retarget", "Cancel")) - if("Cancel") - return - if("Fly") - return ..() - - var/list/options = list() - - for(var/port in SSshuttle.stationary) - if (istype(port, /obj/docking_port/stationary/transit)) - continue // please don't do this - var/obj/docking_port/stationary/S = port - if (canDock(S) == SHUTTLE_CAN_DOCK) - options[S.name || S.id] = S - - var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options - if(!selection) - return - target_dock = options[selection] - if(!QDELETED(target_dock)) - destination = target_dock - +/datum/admins/proc/open_shuttlepanel() + set category = "Admin - Events" + set name = "Shuttle Manipulator" + set desc = "Opens the shuttle manipulator UI." + + if(!check_rights(R_ADMIN)) + return + + SSshuttle.ui_interact(usr) + + +/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user) + var/list/options = list() + + for(var/port in SSshuttle.stationary) + if (istype(port, /obj/docking_port/stationary/transit)) + continue // please don't do this + var/obj/docking_port/stationary/S = port + if (canDock(S) == SHUTTLE_CAN_DOCK) + options[S.name || S.id] = S + + options += "--------" + options += "Infinite Transit" + options += "Delete Shuttle" + options += "Into The Sunset (delete & greentext 'escape')" + + var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options + if(!selection) + return + + switch(selection) + if("Infinite Transit") + destination = null + mode = SHUTTLE_IGNITING + setTimer(ignitionTime) + + if("Delete Shuttle") + if(alert(user, "Really delete [name || id]?", "Delete Shuttle", "Cancel", "Really!") != "Really!") + return + jumpToNullSpace() + + if("Into The Sunset (delete & greentext 'escape')") + if(alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", "Cancel", "Really!") != "Really!") + return + intoTheSunset() + + else + if(options[selection]) + request(options[selection]) + +/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user) + return // use the existing verbs for this + +/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user) + switch(alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", "Fly", "Retarget", "Cancel")) + if("Cancel") + return + if("Fly") + return ..() + + var/list/options = list() + + for(var/port in SSshuttle.stationary) + if (istype(port, /obj/docking_port/stationary/transit)) + continue // please don't do this + var/obj/docking_port/stationary/S = port + if (canDock(S) == SHUTTLE_CAN_DOCK) + options[S.name || S.id] = S + + var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options + if(!selection) + return + target_dock = options[selection] + if(!QDELETED(target_dock)) + destination = target_dock + diff --git a/code/modules/admin/whitelist.dm b/code/modules/admin/whitelist.dm index 55206f6debb..263268a5ca1 100644 --- a/code/modules/admin/whitelist.dm +++ b/code/modules/admin/whitelist.dm @@ -1,23 +1,23 @@ -#define WHITELISTFILE "[global.config.directory]/whitelist.txt" - -GLOBAL_LIST(whitelist) -GLOBAL_PROTECT(whitelist) - -/proc/load_whitelist() - GLOB.whitelist = list() - for(var/line in world.file2list(WHITELISTFILE)) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - GLOB.whitelist += ckey(line) - - if(!GLOB.whitelist.len) - GLOB.whitelist = null - -/proc/check_whitelist(var/ckey) - if(!GLOB.whitelist) - return FALSE - . = (ckey in GLOB.whitelist) - -#undef WHITELISTFILE +#define WHITELISTFILE "[global.config.directory]/whitelist.txt" + +GLOBAL_LIST(whitelist) +GLOBAL_PROTECT(whitelist) + +/proc/load_whitelist() + GLOB.whitelist = list() + for(var/line in world.file2list(WHITELISTFILE)) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + GLOB.whitelist += ckey(line) + + if(!GLOB.whitelist.len) + GLOB.whitelist = null + +/proc/check_whitelist(var/ckey) + if(!GLOB.whitelist) + return FALSE + . = (ckey in GLOB.whitelist) + +#undef WHITELISTFILE diff --git a/code/modules/antagonists/_common/antag_hud.dm b/code/modules/antagonists/_common/antag_hud.dm index 11ddfe2e497..d6a4bdab18e 100644 --- a/code/modules/antagonists/_common/antag_hud.dm +++ b/code/modules/antagonists/_common/antag_hud.dm @@ -1,58 +1,58 @@ -/datum/atom_hud/antag - hud_icons = list(ANTAG_HUD) - var/self_visible = TRUE - var/icon_color //will set the icon color to this - -/datum/atom_hud/antag/hidden - self_visible = FALSE - -/datum/atom_hud/antag/proc/join_hud(mob/M) - //sees_hud should be set to 0 if the mob does not get to see it's own hud type. - if(!istype(M)) - CRASH("join_hud(): [M] ([M.type]) is not a mob!") - if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged - M.mind.antag_hud.leave_hud(M) - - if(ANTAG_HUD in M.hud_possible) //Current mob does not support antag huds ie newplayer - add_to_hud(M) - if(self_visible) - add_hud_to(M) - - M.mind.antag_hud = src - -/datum/atom_hud/antag/proc/leave_hud(mob/M) - if(!M) - return - if(!istype(M)) - CRASH("leave_hud(): [M] ([M.type]) is not a mob!") - remove_from_hud(M) - remove_hud_from(M) - if(M.mind) - M.mind.antag_hud = null - -//GAME_MODE PROCS -//called to set a mob's antag icon state -/proc/set_antag_hud(mob/M, new_icon_state, hudindex) - if(!istype(M)) - CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!") - var/image/holder = M.hud_list[ANTAG_HUD] - var/datum/atom_hud/antag/specific_hud = hudindex ? GLOB.huds[hudindex] : null - if(holder) - holder.icon_state = new_icon_state - holder.color = specific_hud?.icon_color - if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime - M.mind.antag_hud_icon_state = new_icon_state - - -//MIND PROCS -//these are called by mind.transfer_to() -/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud) - leave_all_antag_huds() - set_antag_hud(current, antag_hud_icon_state) - if(newhud) - newhud.join_hud(current) - -/datum/mind/proc/leave_all_antag_huds() - for(var/datum/atom_hud/antag/hud in GLOB.huds) - if(hud.hudusers[current]) - hud.leave_hud(current) +/datum/atom_hud/antag + hud_icons = list(ANTAG_HUD) + var/self_visible = TRUE + var/icon_color //will set the icon color to this + +/datum/atom_hud/antag/hidden + self_visible = FALSE + +/datum/atom_hud/antag/proc/join_hud(mob/M) + //sees_hud should be set to 0 if the mob does not get to see it's own hud type. + if(!istype(M)) + CRASH("join_hud(): [M] ([M.type]) is not a mob!") + if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged + M.mind.antag_hud.leave_hud(M) + + if(ANTAG_HUD in M.hud_possible) //Current mob does not support antag huds ie newplayer + add_to_hud(M) + if(self_visible) + add_hud_to(M) + + M.mind.antag_hud = src + +/datum/atom_hud/antag/proc/leave_hud(mob/M) + if(!M) + return + if(!istype(M)) + CRASH("leave_hud(): [M] ([M.type]) is not a mob!") + remove_from_hud(M) + remove_hud_from(M) + if(M.mind) + M.mind.antag_hud = null + +//GAME_MODE PROCS +//called to set a mob's antag icon state +/proc/set_antag_hud(mob/M, new_icon_state, hudindex) + if(!istype(M)) + CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!") + var/image/holder = M.hud_list[ANTAG_HUD] + var/datum/atom_hud/antag/specific_hud = hudindex ? GLOB.huds[hudindex] : null + if(holder) + holder.icon_state = new_icon_state + holder.color = specific_hud?.icon_color + if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime + M.mind.antag_hud_icon_state = new_icon_state + + +//MIND PROCS +//these are called by mind.transfer_to() +/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud) + leave_all_antag_huds() + set_antag_hud(current, antag_hud_icon_state) + if(newhud) + newhud.join_hud(current) + +/datum/mind/proc/leave_all_antag_huds() + for(var/datum/atom_hud/antag/hud in GLOB.huds) + if(hud.hudusers[current]) + hud.leave_hud(current) diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index f7d82891c9d..03e9d1d4675 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -1,277 +1,277 @@ -/obj/item/antag_spawner - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/used = FALSE - -/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) - return - -/obj/item/antag_spawner/proc/equip_antag(mob/target) - return - - -///////////WIZARD - -/obj/item/antag_spawner/contract - name = "contract" - desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid." - icon = 'icons/obj/wizard.dmi' - icon_state ="scroll2" - -/obj/item/antag_spawner/contract/attack_self(mob/user) - user.set_machine(src) - var/dat - if(used) - dat = "You have already summoned your apprentice.
                " - else - dat = "Contract of Apprenticeship:
                " - dat += "Using this contract, you may summon an apprentice to aid you on your mission.
                " - dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
                " - dat += "Which school of magic is your apprentice studying?:
                " - dat += "Destruction
                " - dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
                " - dat += "Bluespace Manipulation
                " - dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
                " - dat += "Healing
                " - dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
                " - dat += "Robeless
                " - dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
                " - user << browse(dat, "window=radio") - onclose(user, "radio") - return - -/obj/item/antag_spawner/contract/Topic(href, href_list) - ..() - var/mob/living/carbon/human/H = usr - - if(H.stat || H.restrained()) - return - if(!ishuman(H)) - return 1 - - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["school"]) - if(used) - to_chat(H, "You already used this contract!") - return - var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src) - if(LAZYLEN(candidates)) - if(QDELETED(src)) - return - if(used) - to_chat(H, "You already used this contract!") - return - used = TRUE - var/mob/dead/observer/C = pick(candidates) - spawn_antag(C.client, get_turf(src), href_list["school"],H.mind) - else - to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.") - -/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user) - new /obj/effect/particle_effect/smoke(T) - var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) - C.prefs.copy_to(M) - M.key = C.key - var/datum/mind/app_mind = M.mind - - var/datum/antagonist/wizard/apprentice/app = new() - app.master = user - app.school = kind - - var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard) - if(master_wizard) - if(!master_wizard.wiz_team) - master_wizard.create_wiz_team() - app.wiz_team = master_wizard.wiz_team - master_wizard.wiz_team.add_member(app_mind) - app_mind.add_antag_datum(app) - //TODO Kill these if possible - app_mind.assigned_role = "Apprentice" - app_mind.special_role = "apprentice" - // - SEND_SOUND(M, sound('sound/effects/magic.ogg')) - -///////////BORGS AND OPERATIVES - - -/obj/item/antag_spawner/nuke_ops - name = "syndicate operative teleporter" - desc = "A single-use teleporter designed to quickly reinforce operatives in the field." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - var/borg_to_spawn - -/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user) - if(used) - to_chat(user, "[src] is out of power!") - return FALSE - if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) - to_chat(user, "AUTHENTICATION FAILURE. ACCESS DENIED.") - return FALSE - return TRUE - - -/obj/item/antag_spawner/nuke_ops/attack_self(mob/user) - if(!(check_usability(user))) - return - - to_chat(user, "You activate [src] and wait for confirmation.") - var/list/nuke_candidates = pollGhostCandidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", ROLE_OPERATIVE, null, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE) - if(LAZYLEN(nuke_candidates)) - if(QDELETED(src) || !check_usability(user)) - return - used = TRUE - var/mob/dead/observer/G = pick(nuke_candidates) - spawn_antag(G.client, get_turf(src), "syndieborg", user.mind) - do_sparks(4, TRUE, src) - qdel(src) - else - to_chat(user, "Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded.") - -/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user) - var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) - C.prefs.copy_to(M) - M.key = C.key - - var/datum/antagonist/nukeop/new_op = new() - new_op.send_to_spawnpoint = FALSE - new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals - - var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) - if(creator_op) - M.mind.add_antag_datum(new_op,creator_op.nuke_team) - M.mind.special_role = "Nuclear Operative" - -//////CLOWN OP -/obj/item/antag_spawner/nuke_ops/clown - name = "clown operative teleporter" - desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field." - -/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user) - var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) - C.prefs.copy_to(M) - M.key = C.key - - var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop() - new_op.send_to_spawnpoint = FALSE - new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals - - var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE) - if(creator_op) - M.mind.add_antag_datum(new_op, creator_op.nuke_team) - M.mind.special_role = "Clown Operative" - - -//////SYNDICATE BORG -/obj/item/antag_spawner/nuke_ops/borg_tele - name = "syndicate cyborg teleporter" - desc = "A single-use teleporter designed to quickly reinforce operatives in the field." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - -/obj/item/antag_spawner/nuke_ops/borg_tele/assault - name = "syndicate assault cyborg teleporter" - borg_to_spawn = "Assault" - -/obj/item/antag_spawner/nuke_ops/borg_tele/medical - name = "syndicate medical teleporter" - borg_to_spawn = "Medical" - -/obj/item/antag_spawner/nuke_ops/borg_tele/saboteur - name = "syndicate saboteur teleporter" - borg_to_spawn = "Saboteur" - -/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user) - var/mob/living/silicon/robot/R - var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) - if(!creator_op) - return - - switch(borg_to_spawn) - if("Medical") - R = new /mob/living/silicon/robot/modules/syndicate/medical(T) - if("Saboteur") - R = new /mob/living/silicon/robot/modules/syndicate/saboteur(T) - else - R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default - - var/brainfirstname = pick(GLOB.first_names_male) - if(prob(50)) - brainfirstname = pick(GLOB.first_names_female) - var/brainopslastname = pick(GLOB.last_names) - if(creator_op.nuke_team.syndicate_name) //the brain inside the syndiborg has the same last name as the other ops. - brainopslastname = creator_op.nuke_team.syndicate_name - var/brainopsname = "[brainfirstname] [brainopslastname]" - - R.mmi.name = "[initial(R.mmi.name)]: [brainopsname]" - R.mmi.brain.name = "[brainopsname]'s brain" - R.mmi.brainmob.real_name = brainopsname - R.mmi.brainmob.name = brainopsname - R.real_name = R.name - - R.key = C.key - - var/datum/antagonist/nukeop/new_borg = new() - new_borg.send_to_spawnpoint = FALSE - R.mind.add_antag_datum(new_borg,creator_op.nuke_team) - R.mind.special_role = "Syndicate Cyborg" - -///////////SLAUGHTER DEMON - -/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game - name = "vial of blood" - desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures." - icon = 'icons/obj/wizard.dmi' - icon_state = "vial" - - var/shatter_msg = "You shatter the bottle, no turning back now!" - var/veil_msg = "You sense a dark presence lurking just beyond the veil..." - var/mob/living/demon_type = /mob/living/simple_animal/slaughter - var/antag_type = /datum/antagonist/slaughter - - -/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user) - if(!is_station_level(user.z)) - to_chat(user, "You should probably wait until you reach the station.") - return - if(used) - return - var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src) - if(LAZYLEN(candidates)) - if(used || QDELETED(src)) - return - used = TRUE - var/mob/dead/observer/C = pick(candidates) - spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind) - to_chat(user, shatter_msg) - to_chat(user, veil_msg) - playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, TRUE) - qdel(src) - else - to_chat(user, "You can't seem to work up the nerve to shatter the bottle! Perhaps you should try again later.") - - -/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) - var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T) - var/mob/living/simple_animal/slaughter/S = new demon_type(holder) - S.holder = holder - S.key = C.key - S.mind.assigned_role = S.name - S.mind.special_role = S.name - S.mind.add_antag_datum(antag_type) - to_chat(S, S.playstyle_string) - to_chat(S, "You are currently not currently in the same plane of existence as the station. \ - Ctrl+Click a blood pool to manifest.") - -/obj/item/antag_spawner/slaughter_demon/laughter - name = "vial of tickles" - desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures." - icon = 'icons/obj/wizard.dmi' - icon_state = "vial" - color = "#FF69B4" // HOT PINK - - veil_msg = "You sense an adorable presence lurking just beyond the veil..." - demon_type = /mob/living/simple_animal/slaughter/laughter - antag_type = /datum/antagonist/slaughter/laughter +/obj/item/antag_spawner + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/used = FALSE + +/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) + return + +/obj/item/antag_spawner/proc/equip_antag(mob/target) + return + + +///////////WIZARD + +/obj/item/antag_spawner/contract + name = "contract" + desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid." + icon = 'icons/obj/wizard.dmi' + icon_state ="scroll2" + +/obj/item/antag_spawner/contract/attack_self(mob/user) + user.set_machine(src) + var/dat + if(used) + dat = "You have already summoned your apprentice.
                " + else + dat = "Contract of Apprenticeship:
                " + dat += "Using this contract, you may summon an apprentice to aid you on your mission.
                " + dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
                " + dat += "Which school of magic is your apprentice studying?:
                " + dat += "Destruction
                " + dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
                " + dat += "Bluespace Manipulation
                " + dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
                " + dat += "Healing
                " + dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
                " + dat += "Robeless
                " + dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
                " + user << browse(dat, "window=radio") + onclose(user, "radio") + return + +/obj/item/antag_spawner/contract/Topic(href, href_list) + ..() + var/mob/living/carbon/human/H = usr + + if(H.stat || H.restrained()) + return + if(!ishuman(H)) + return 1 + + if(loc == H || (in_range(src, H) && isturf(loc))) + H.set_machine(src) + if(href_list["school"]) + if(used) + to_chat(H, "You already used this contract!") + return + var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src) + if(LAZYLEN(candidates)) + if(QDELETED(src)) + return + if(used) + to_chat(H, "You already used this contract!") + return + used = TRUE + var/mob/dead/observer/C = pick(candidates) + spawn_antag(C.client, get_turf(src), href_list["school"],H.mind) + else + to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.") + +/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user) + new /obj/effect/particle_effect/smoke(T) + var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) + C.prefs.copy_to(M) + M.key = C.key + var/datum/mind/app_mind = M.mind + + var/datum/antagonist/wizard/apprentice/app = new() + app.master = user + app.school = kind + + var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard) + if(master_wizard) + if(!master_wizard.wiz_team) + master_wizard.create_wiz_team() + app.wiz_team = master_wizard.wiz_team + master_wizard.wiz_team.add_member(app_mind) + app_mind.add_antag_datum(app) + //TODO Kill these if possible + app_mind.assigned_role = "Apprentice" + app_mind.special_role = "apprentice" + // + SEND_SOUND(M, sound('sound/effects/magic.ogg')) + +///////////BORGS AND OPERATIVES + + +/obj/item/antag_spawner/nuke_ops + name = "syndicate operative teleporter" + desc = "A single-use teleporter designed to quickly reinforce operatives in the field." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + var/borg_to_spawn + +/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user) + if(used) + to_chat(user, "[src] is out of power!") + return FALSE + if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) + to_chat(user, "AUTHENTICATION FAILURE. ACCESS DENIED.") + return FALSE + return TRUE + + +/obj/item/antag_spawner/nuke_ops/attack_self(mob/user) + if(!(check_usability(user))) + return + + to_chat(user, "You activate [src] and wait for confirmation.") + var/list/nuke_candidates = pollGhostCandidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", ROLE_OPERATIVE, null, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE) + if(LAZYLEN(nuke_candidates)) + if(QDELETED(src) || !check_usability(user)) + return + used = TRUE + var/mob/dead/observer/G = pick(nuke_candidates) + spawn_antag(G.client, get_turf(src), "syndieborg", user.mind) + do_sparks(4, TRUE, src) + qdel(src) + else + to_chat(user, "Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded.") + +/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user) + var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) + C.prefs.copy_to(M) + M.key = C.key + + var/datum/antagonist/nukeop/new_op = new() + new_op.send_to_spawnpoint = FALSE + new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals + + var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(creator_op) + M.mind.add_antag_datum(new_op,creator_op.nuke_team) + M.mind.special_role = "Nuclear Operative" + +//////CLOWN OP +/obj/item/antag_spawner/nuke_ops/clown + name = "clown operative teleporter" + desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field." + +/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user) + var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) + C.prefs.copy_to(M) + M.key = C.key + + var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop() + new_op.send_to_spawnpoint = FALSE + new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals + + var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE) + if(creator_op) + M.mind.add_antag_datum(new_op, creator_op.nuke_team) + M.mind.special_role = "Clown Operative" + + +//////SYNDICATE BORG +/obj/item/antag_spawner/nuke_ops/borg_tele + name = "syndicate cyborg teleporter" + desc = "A single-use teleporter designed to quickly reinforce operatives in the field." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + +/obj/item/antag_spawner/nuke_ops/borg_tele/assault + name = "syndicate assault cyborg teleporter" + borg_to_spawn = "Assault" + +/obj/item/antag_spawner/nuke_ops/borg_tele/medical + name = "syndicate medical teleporter" + borg_to_spawn = "Medical" + +/obj/item/antag_spawner/nuke_ops/borg_tele/saboteur + name = "syndicate saboteur teleporter" + borg_to_spawn = "Saboteur" + +/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user) + var/mob/living/silicon/robot/R + var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(!creator_op) + return + + switch(borg_to_spawn) + if("Medical") + R = new /mob/living/silicon/robot/modules/syndicate/medical(T) + if("Saboteur") + R = new /mob/living/silicon/robot/modules/syndicate/saboteur(T) + else + R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default + + var/brainfirstname = pick(GLOB.first_names_male) + if(prob(50)) + brainfirstname = pick(GLOB.first_names_female) + var/brainopslastname = pick(GLOB.last_names) + if(creator_op.nuke_team.syndicate_name) //the brain inside the syndiborg has the same last name as the other ops. + brainopslastname = creator_op.nuke_team.syndicate_name + var/brainopsname = "[brainfirstname] [brainopslastname]" + + R.mmi.name = "[initial(R.mmi.name)]: [brainopsname]" + R.mmi.brain.name = "[brainopsname]'s brain" + R.mmi.brainmob.real_name = brainopsname + R.mmi.brainmob.name = brainopsname + R.real_name = R.name + + R.key = C.key + + var/datum/antagonist/nukeop/new_borg = new() + new_borg.send_to_spawnpoint = FALSE + R.mind.add_antag_datum(new_borg,creator_op.nuke_team) + R.mind.special_role = "Syndicate Cyborg" + +///////////SLAUGHTER DEMON + +/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game + name = "vial of blood" + desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures." + icon = 'icons/obj/wizard.dmi' + icon_state = "vial" + + var/shatter_msg = "You shatter the bottle, no turning back now!" + var/veil_msg = "You sense a dark presence lurking just beyond the veil..." + var/mob/living/demon_type = /mob/living/simple_animal/slaughter + var/antag_type = /datum/antagonist/slaughter + + +/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user) + if(!is_station_level(user.z)) + to_chat(user, "You should probably wait until you reach the station.") + return + if(used) + return + var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src) + if(LAZYLEN(candidates)) + if(used || QDELETED(src)) + return + used = TRUE + var/mob/dead/observer/C = pick(candidates) + spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind) + to_chat(user, shatter_msg) + to_chat(user, veil_msg) + playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, TRUE) + qdel(src) + else + to_chat(user, "You can't seem to work up the nerve to shatter the bottle! Perhaps you should try again later.") + + +/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) + var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T) + var/mob/living/simple_animal/slaughter/S = new demon_type(holder) + S.holder = holder + S.key = C.key + S.mind.assigned_role = S.name + S.mind.special_role = S.name + S.mind.add_antag_datum(antag_type) + to_chat(S, S.playstyle_string) + to_chat(S, "You are currently not currently in the same plane of existence as the station. \ + Ctrl+Click a blood pool to manifest.") + +/obj/item/antag_spawner/slaughter_demon/laughter + name = "vial of tickles" + desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures." + icon = 'icons/obj/wizard.dmi' + icon_state = "vial" + color = "#FF69B4" // HOT PINK + + veil_msg = "You sense an adorable presence lurking just beyond the veil..." + demon_type = /mob/living/simple_animal/slaughter/laughter + antag_type = /datum/antagonist/slaughter/laughter diff --git a/code/modules/antagonists/abductor/equipment/orderable_gear.dm b/code/modules/antagonists/abductor/equipment/orderable_gear.dm index 0cc04fa68da..254b986741c 100644 --- a/code/modules/antagonists/abductor/equipment/orderable_gear.dm +++ b/code/modules/antagonists/abductor/equipment/orderable_gear.dm @@ -1,79 +1,79 @@ -GLOBAL_LIST_INIT(abductor_gear, subtypesof(/datum/abductor_gear)) - -/datum/abductor_gear - /// Name of the gear - var/name = "Generic Abductor Gear" - /// Description of the gear - var/description = "Generic description." - /// Unique ID of the gear - var/id = "abductor_generic" - /// Credit cost of the gear - var/cost = 1 - /// Build path of the gear itself - var/build_path = null - /// Category of the gear - var/category = "Basic Gear" - -/datum/abductor_gear/agent_helmet - name = "Agent Helmet" - description = "Abduct with style - spiky style. Prevents digital tracking." - id = "agent_helmet" - build_path = /obj/item/clothing/head/helmet/abductor - -/datum/abductor_gear/agent_vest - name = "Agent Vest" - description = "A vest outfitted with advanced stealth technology. It has two modes - combat and stealth." - id = "agent_vest" - build_path = /obj/item/clothing/suit/armor/abductor/vest - -/datum/abductor_gear/radio_silencer - name = "Radio Silencer" - description = "A compact device used to shut down communications equipment." - id = "radio_silencer" - build_path = /obj/item/abductor/silencer - -/datum/abductor_gear/science_tool - name = "Science Tool" - description = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras." - id = "science_tool" - build_path = /obj/item/abductor/gizmo - -/datum/abductor_gear/advanced_baton - name = "Advanced Baton" - description = "A quad-mode baton used for incapacitation and restraining of specimens." - id = "advanced_baton" - cost = 2 - build_path = /obj/item/melee/baton/abductor - -/datum/abductor_gear/superlingual_matrix - name = "Superlingual Matrix" - description = "A mysterious structure that allows for instant communication between users. Pretty impressive until you need to eat something." - id = "superlingual_matrix" - build_path = /obj/item/organ/tongue/abductor - category = "Advanced Gear" - -/datum/abductor_gear/mental_interface - name = "Mental Interface Device" - description = "A dual-mode tool for directly communicating with sentient brains. It can be used to send a direct message to a target, \ - or to send a command to a test subject with a charged gland." - id = "mental_interface" - cost = 2 - build_path = /obj/item/abductor/mind_device - category = "Advanced Gear" - -/datum/abductor_gear/reagent_synthesizer - name = "Reagent Synthesizer" - description = "Synthesizes a variety of reagents using proto-matter." - id = "reagent_synthesizer" - cost = 2 - build_path = /obj/item/abductor_machine_beacon/chem_dispenser - category = "Advanced Gear" - -/datum/abductor_gear/shrink_ray - name = "Shrink Ray Blaster" - description = "This is a piece of frightening alien tech that enhances the magnetic pull of atoms in a localized space to temporarily make an object shrink. \ - That or it's just space magic. Either way, it shrinks stuff." - id = "shrink_ray" - cost = 2 - build_path = /obj/item/gun/energy/shrink_ray - category = "Advanced Gear" +GLOBAL_LIST_INIT(abductor_gear, subtypesof(/datum/abductor_gear)) + +/datum/abductor_gear + /// Name of the gear + var/name = "Generic Abductor Gear" + /// Description of the gear + var/description = "Generic description." + /// Unique ID of the gear + var/id = "abductor_generic" + /// Credit cost of the gear + var/cost = 1 + /// Build path of the gear itself + var/build_path = null + /// Category of the gear + var/category = "Basic Gear" + +/datum/abductor_gear/agent_helmet + name = "Agent Helmet" + description = "Abduct with style - spiky style. Prevents digital tracking." + id = "agent_helmet" + build_path = /obj/item/clothing/head/helmet/abductor + +/datum/abductor_gear/agent_vest + name = "Agent Vest" + description = "A vest outfitted with advanced stealth technology. It has two modes - combat and stealth." + id = "agent_vest" + build_path = /obj/item/clothing/suit/armor/abductor/vest + +/datum/abductor_gear/radio_silencer + name = "Radio Silencer" + description = "A compact device used to shut down communications equipment." + id = "radio_silencer" + build_path = /obj/item/abductor/silencer + +/datum/abductor_gear/science_tool + name = "Science Tool" + description = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras." + id = "science_tool" + build_path = /obj/item/abductor/gizmo + +/datum/abductor_gear/advanced_baton + name = "Advanced Baton" + description = "A quad-mode baton used for incapacitation and restraining of specimens." + id = "advanced_baton" + cost = 2 + build_path = /obj/item/melee/baton/abductor + +/datum/abductor_gear/superlingual_matrix + name = "Superlingual Matrix" + description = "A mysterious structure that allows for instant communication between users. Pretty impressive until you need to eat something." + id = "superlingual_matrix" + build_path = /obj/item/organ/tongue/abductor + category = "Advanced Gear" + +/datum/abductor_gear/mental_interface + name = "Mental Interface Device" + description = "A dual-mode tool for directly communicating with sentient brains. It can be used to send a direct message to a target, \ + or to send a command to a test subject with a charged gland." + id = "mental_interface" + cost = 2 + build_path = /obj/item/abductor/mind_device + category = "Advanced Gear" + +/datum/abductor_gear/reagent_synthesizer + name = "Reagent Synthesizer" + description = "Synthesizes a variety of reagents using proto-matter." + id = "reagent_synthesizer" + cost = 2 + build_path = /obj/item/abductor_machine_beacon/chem_dispenser + category = "Advanced Gear" + +/datum/abductor_gear/shrink_ray + name = "Shrink Ray Blaster" + description = "This is a piece of frightening alien tech that enhances the magnetic pull of atoms in a localized space to temporarily make an object shrink. \ + That or it's just space magic. Either way, it shrinks stuff." + id = "shrink_ray" + cost = 2 + build_path = /obj/item/gun/energy/shrink_ray + category = "Advanced Gear" diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index 37ee4c6f194..5385636a08d 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -1,98 +1,98 @@ -/* - * Don't use the apostrophe in name or desc. Causes script errors.//probably no longer true - */ - -/datum/action/changeling - name = "Prototype Sting - Debug button, ahelp this" - background_icon_state = "bg_changeling" - icon_icon = 'icons/mob/actions/actions_changeling.dmi' - var/needs_button = TRUE//for passive abilities like hivemind that dont need a button - var/helptext = "" // Details - var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands) - var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased - var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1 - var/req_human = 0 //if you need to be human to use this ability - var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting - var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD - var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag - var/active = FALSE//used by a few powers that toggle - -/* -changeling code now relies on on_purchase to grant powers. -if you override it, MAKE SURE you call parent or it will not be usable -the same goes for Remove(). if you override Remove(), call parent or else your power won't be removed on respec -*/ - -/datum/action/changeling/proc/on_purchase(mob/user, is_respec) - if(!is_respec) - SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) - if(needs_button) - Grant(user)//how powers are added rather than the checks in mob.dm - -/datum/action/changeling/Trigger() - var/mob/user = owner - if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) - return - try_to_sting(user) - -/** - *Contrary to the name, this proc isn't just used by changeling stings. It handles the activation of the action and the deducation of its cost. - *The order of the proc chain is: - *can_sting(). Should this fail, the process gets aborted early. - *sting_action(). This proc usually handles the actual effect of the action. - *Should sting_action succeed the following will be done: - *sting_feedback(). Produces feedback on the performed action. Don't ask me why this isn't handled in sting_action() - *The deduction of the cost of this power. - *Returns TRUE on a successful activation. - */ -/datum/action/changeling/proc/try_to_sting(mob/user, mob/target) - if(!can_sting(user, target)) - return FALSE - var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(sting_action(user, target)) - sting_feedback(user, target) - c.chem_charges -= chemical_cost - return TRUE - return FALSE - -/datum/action/changeling/proc/sting_action(mob/user, mob/target) - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) - return 0 - -/datum/action/changeling/proc/sting_feedback(mob/user, mob/target) - return 0 - -//Fairly important to remember to return 1 on success >.< - -/datum/action/changeling/proc/can_sting(mob/living/user, mob/target) - if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards - return FALSE - if(req_human && !ishuman(user)) - to_chat(user, "We cannot do that in this form!") - return FALSE - var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(c.chem_charges < chemical_cost) - to_chat(user, "We require at least [chemical_cost] unit\s of chemicals to do that!") - return FALSE - if(c.absorbedcount < req_dna) - to_chat(user, "We require at least [req_dna] sample\s of compatible DNA.") - return FALSE - if(c.trueabsorbs < req_absorbs) - to_chat(user, "We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.") - return FALSE - if(req_stat < user.stat) - to_chat(user, "We are incapacitated.") - return FALSE - if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) - to_chat(user, "We are incapacitated.") - return FALSE - return TRUE - -/datum/action/changeling/proc/can_be_used_by(mob/user) - if(!user || QDELETED(user)) - return 0 - if(!ishuman(user) && !ismonkey(user)) - return FALSE - if(req_human && !ishuman(user)) - return FALSE - return TRUE +/* + * Don't use the apostrophe in name or desc. Causes script errors.//probably no longer true + */ + +/datum/action/changeling + name = "Prototype Sting - Debug button, ahelp this" + background_icon_state = "bg_changeling" + icon_icon = 'icons/mob/actions/actions_changeling.dmi' + var/needs_button = TRUE//for passive abilities like hivemind that dont need a button + var/helptext = "" // Details + var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands) + var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased + var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1 + var/req_human = 0 //if you need to be human to use this ability + var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting + var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD + var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag + var/active = FALSE//used by a few powers that toggle + +/* +changeling code now relies on on_purchase to grant powers. +if you override it, MAKE SURE you call parent or it will not be usable +the same goes for Remove(). if you override Remove(), call parent or else your power won't be removed on respec +*/ + +/datum/action/changeling/proc/on_purchase(mob/user, is_respec) + if(!is_respec) + SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) + if(needs_button) + Grant(user)//how powers are added rather than the checks in mob.dm + +/datum/action/changeling/Trigger() + var/mob/user = owner + if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) + return + try_to_sting(user) + +/** + *Contrary to the name, this proc isn't just used by changeling stings. It handles the activation of the action and the deducation of its cost. + *The order of the proc chain is: + *can_sting(). Should this fail, the process gets aborted early. + *sting_action(). This proc usually handles the actual effect of the action. + *Should sting_action succeed the following will be done: + *sting_feedback(). Produces feedback on the performed action. Don't ask me why this isn't handled in sting_action() + *The deduction of the cost of this power. + *Returns TRUE on a successful activation. + */ +/datum/action/changeling/proc/try_to_sting(mob/user, mob/target) + if(!can_sting(user, target)) + return FALSE + var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(sting_action(user, target)) + sting_feedback(user, target) + c.chem_charges -= chemical_cost + return TRUE + return FALSE + +/datum/action/changeling/proc/sting_action(mob/user, mob/target) + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) + return 0 + +/datum/action/changeling/proc/sting_feedback(mob/user, mob/target) + return 0 + +//Fairly important to remember to return 1 on success >.< + +/datum/action/changeling/proc/can_sting(mob/living/user, mob/target) + if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards + return FALSE + if(req_human && !ishuman(user)) + to_chat(user, "We cannot do that in this form!") + return FALSE + var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(c.chem_charges < chemical_cost) + to_chat(user, "We require at least [chemical_cost] unit\s of chemicals to do that!") + return FALSE + if(c.absorbedcount < req_dna) + to_chat(user, "We require at least [req_dna] sample\s of compatible DNA.") + return FALSE + if(c.trueabsorbs < req_absorbs) + to_chat(user, "We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.") + return FALSE + if(req_stat < user.stat) + to_chat(user, "We are incapacitated.") + return FALSE + if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) + to_chat(user, "We are incapacitated.") + return FALSE + return TRUE + +/datum/action/changeling/proc/can_be_used_by(mob/user) + if(!user || QDELETED(user)) + return 0 + if(!ishuman(user) && !ismonkey(user)) + return FALSE + if(req_human && !ishuman(user)) + return FALSE + return TRUE diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index 24b2ee6aed5..8e55b90517b 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -1,147 +1,147 @@ -/datum/action/changeling/absorb_dna - name = "Absorb DNA" - desc = "Absorb the DNA of our victim. Requires us to strangle them." - button_icon_state = "absorb_dna" - chemical_cost = 0 - dna_cost = 0 - req_human = 1 - -/datum/action/changeling/absorb_dna/can_sting(mob/living/carbon/user) - if(!..()) - return - - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.isabsorbing) - to_chat(user, "We are already absorbing!") - return - - if(!user.pulling || !iscarbon(user.pulling)) - to_chat(user, "We must be grabbing a creature to absorb them!") - return - if(user.grab_state <= GRAB_NECK) - to_chat(user, "We must have a tighter grip to absorb this creature!") - return - - var/mob/living/carbon/target = user.pulling - return changeling.can_absorb_dna(target) - - - -/datum/action/changeling/absorb_dna/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/mob/living/carbon/human/target = user.pulling - changeling.isabsorbing = 1 - for(var/i in 1 to 3) - switch(i) - if(1) - to_chat(user, "This creature is compatible. We must hold still...") - if(2) - user.visible_message("[user] extends a proboscis!", "We extend a proboscis.") - if(3) - user.visible_message("[user] stabs [target] with the proboscis!", "We stab [target] with the proboscis.") - to_chat(target, "You feel a sharp stabbing pain!") - target.take_overall_damage(40) - - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]")) - if(!do_mob(user, target, 150)) - to_chat(user, "Our absorption of [target] has been interrupted!") - changeling.isabsorbing = 0 - return - - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4")) - user.visible_message("[user] sucks the fluids from [target]!", "We have absorbed [target].") - to_chat(target, "You are absorbed by the changeling!") - - if(!changeling.has_dna(target.dna)) - changeling.add_new_profile(target) - changeling.trueabsorbs++ - - if(user.nutrition < NUTRITION_LEVEL_WELL_FED) - user.set_nutrition(min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)) - - // Absorb a lizard, speak Draconic. - owner.copy_languages(target, LANGUAGE_ABSORB) - - if(target.mind && user.mind)//if the victim and user have minds - var/datum/mind/suckedbrain = target.mind - user.mind.memory += "
                We've absorbed [target]'s memories into our own...
                [suckedbrain.memory]
                " - for(var/A in suckedbrain.antag_datums) - var/datum/antagonist/antag_types = A - var/list/all_objectives = antag_types.objectives.Copy() - if(antag_types.antag_memory) - user.mind.memory += "[antag_types.antag_memory]
                " - if(LAZYLEN(all_objectives)) - user.mind.memory += "Objectives:" - var/obj_count = 1 - for(var/O in all_objectives) - var/datum/objective/objective = O - user.mind.memory += "
                Objective #[obj_count++]: [objective.explanation_text]" - var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain - if(other_owners.len) - user.mind.memory += "
                  " - for(var/mind in other_owners) - var/datum/mind/M = mind - user.mind.memory += "
                • Conspirator: [M.name]
                • " - user.mind.memory += "
                " - user.mind.memory += "That's all [target] had.
                " - user.memory() //I can read your mind, kekeke. Output all their notes. - - //Some of target's recent speech, so the changeling can attempt to imitate them better. - //Recent as opposed to all because rounds tend to have a LOT of text. - - var/list/recent_speech = list() - var/list/say_log = list() - var/log_source = target.logging - for(var/log_type in log_source) - var/nlog_type = text2num(log_type) - if(nlog_type & LOG_SAY) - var/list/reversed = log_source[log_type] - if(islist(reversed)) - say_log = reverseRange(reversed.Copy()) - break - - if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) - recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list - else - for(var/spoken_memory in say_log) - if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH) - break - recent_speech[spoken_memory] = say_log[spoken_memory] - - if(recent_speech.len) - changeling.antag_memory += "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!
                " - to_chat(user, "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!") - for(var/spoken_memory in recent_speech) - changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"
                " - to_chat(user, "\"[recent_speech[spoken_memory]]\"") - changeling.antag_memory += "We have no more knowledge of [target]'s speech patterns.
                " - to_chat(user, "We have no more knowledge of [target]'s speech patterns.") - - - var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling) - if(target_ling)//If the target was a changeling, suck out their extra juice and objective points! - to_chat(user, "[target] was one of us. We have absorbed their power.") - target_ling.remove_changeling_powers() - changeling.geneticpoints += round(target_ling.geneticpoints/2) - changeling.total_geneticspoints = changeling.geneticpoints //updates the total sum of genetic points when you absorb another ling - target_ling.geneticpoints = 0 - target_ling.canrespec = 0 - changeling.chem_storage += round(target_ling.chem_storage/2) - changeling.total_chem_storage = changeling.chem_storage //updates the total sum of chemicals stored for when you absorb another ling - changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage) - target_ling.chem_charges = 0 - target_ling.chem_storage = 0 - changeling.absorbedcount += (target_ling.absorbedcount) - target_ling.stored_profiles.len = 1 - target_ling.absorbedcount = 0 - target_ling.was_absorbed = TRUE - - - changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) - - changeling.isabsorbing = 0 - changeling.canrespec = 1 - - target.death(0) - target.Drain() - return TRUE +/datum/action/changeling/absorb_dna + name = "Absorb DNA" + desc = "Absorb the DNA of our victim. Requires us to strangle them." + button_icon_state = "absorb_dna" + chemical_cost = 0 + dna_cost = 0 + req_human = 1 + +/datum/action/changeling/absorb_dna/can_sting(mob/living/carbon/user) + if(!..()) + return + + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.isabsorbing) + to_chat(user, "We are already absorbing!") + return + + if(!user.pulling || !iscarbon(user.pulling)) + to_chat(user, "We must be grabbing a creature to absorb them!") + return + if(user.grab_state <= GRAB_NECK) + to_chat(user, "We must have a tighter grip to absorb this creature!") + return + + var/mob/living/carbon/target = user.pulling + return changeling.can_absorb_dna(target) + + + +/datum/action/changeling/absorb_dna/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/mob/living/carbon/human/target = user.pulling + changeling.isabsorbing = 1 + for(var/i in 1 to 3) + switch(i) + if(1) + to_chat(user, "This creature is compatible. We must hold still...") + if(2) + user.visible_message("[user] extends a proboscis!", "We extend a proboscis.") + if(3) + user.visible_message("[user] stabs [target] with the proboscis!", "We stab [target] with the proboscis.") + to_chat(target, "You feel a sharp stabbing pain!") + target.take_overall_damage(40) + + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]")) + if(!do_mob(user, target, 150)) + to_chat(user, "Our absorption of [target] has been interrupted!") + changeling.isabsorbing = 0 + return + + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4")) + user.visible_message("[user] sucks the fluids from [target]!", "We have absorbed [target].") + to_chat(target, "You are absorbed by the changeling!") + + if(!changeling.has_dna(target.dna)) + changeling.add_new_profile(target) + changeling.trueabsorbs++ + + if(user.nutrition < NUTRITION_LEVEL_WELL_FED) + user.set_nutrition(min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)) + + // Absorb a lizard, speak Draconic. + owner.copy_languages(target, LANGUAGE_ABSORB) + + if(target.mind && user.mind)//if the victim and user have minds + var/datum/mind/suckedbrain = target.mind + user.mind.memory += "
                We've absorbed [target]'s memories into our own...
                [suckedbrain.memory]
                " + for(var/A in suckedbrain.antag_datums) + var/datum/antagonist/antag_types = A + var/list/all_objectives = antag_types.objectives.Copy() + if(antag_types.antag_memory) + user.mind.memory += "[antag_types.antag_memory]
                " + if(LAZYLEN(all_objectives)) + user.mind.memory += "Objectives:" + var/obj_count = 1 + for(var/O in all_objectives) + var/datum/objective/objective = O + user.mind.memory += "
                Objective #[obj_count++]: [objective.explanation_text]" + var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain + if(other_owners.len) + user.mind.memory += "
                  " + for(var/mind in other_owners) + var/datum/mind/M = mind + user.mind.memory += "
                • Conspirator: [M.name]
                • " + user.mind.memory += "
                " + user.mind.memory += "That's all [target] had.
                " + user.memory() //I can read your mind, kekeke. Output all their notes. + + //Some of target's recent speech, so the changeling can attempt to imitate them better. + //Recent as opposed to all because rounds tend to have a LOT of text. + + var/list/recent_speech = list() + var/list/say_log = list() + var/log_source = target.logging + for(var/log_type in log_source) + var/nlog_type = text2num(log_type) + if(nlog_type & LOG_SAY) + var/list/reversed = log_source[log_type] + if(islist(reversed)) + say_log = reverseRange(reversed.Copy()) + break + + if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) + recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list + else + for(var/spoken_memory in say_log) + if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH) + break + recent_speech[spoken_memory] = say_log[spoken_memory] + + if(recent_speech.len) + changeling.antag_memory += "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!
                " + to_chat(user, "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!") + for(var/spoken_memory in recent_speech) + changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"
                " + to_chat(user, "\"[recent_speech[spoken_memory]]\"") + changeling.antag_memory += "We have no more knowledge of [target]'s speech patterns.
                " + to_chat(user, "We have no more knowledge of [target]'s speech patterns.") + + + var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling) + if(target_ling)//If the target was a changeling, suck out their extra juice and objective points! + to_chat(user, "[target] was one of us. We have absorbed their power.") + target_ling.remove_changeling_powers() + changeling.geneticpoints += round(target_ling.geneticpoints/2) + changeling.total_geneticspoints = changeling.geneticpoints //updates the total sum of genetic points when you absorb another ling + target_ling.geneticpoints = 0 + target_ling.canrespec = 0 + changeling.chem_storage += round(target_ling.chem_storage/2) + changeling.total_chem_storage = changeling.chem_storage //updates the total sum of chemicals stored for when you absorb another ling + changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage) + target_ling.chem_charges = 0 + target_ling.chem_storage = 0 + changeling.absorbedcount += (target_ling.absorbedcount) + target_ling.stored_profiles.len = 1 + target_ling.absorbedcount = 0 + target_ling.was_absorbed = TRUE + + + changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) + + changeling.isabsorbing = 0 + changeling.canrespec = 1 + + target.death(0) + target.Drain() + return TRUE diff --git a/code/modules/antagonists/changeling/powers/digitalcamo.dm b/code/modules/antagonists/changeling/powers/digitalcamo.dm index 028f85b83e8..dbe034397ca 100644 --- a/code/modules/antagonists/changeling/powers/digitalcamo.dm +++ b/code/modules/antagonists/changeling/powers/digitalcamo.dm @@ -1,23 +1,23 @@ -/datum/action/changeling/digitalcamo - name = "Digital Camouflage" - desc = "By evolving the ability to distort our form and proportions, we defeat common algorithms used to detect lifeforms on cameras." - helptext = "We cannot be tracked by camera or seen by AI units while using this skill. However, humans looking at us will find us... uncanny." - button_icon_state = "digital_camo" - dna_cost = 1 - active = FALSE - -//Prevents AIs tracking you but makes you easily detectable to the human-eye. -/datum/action/changeling/digitalcamo/sting_action(mob/user) - ..() - if(active) - to_chat(user, "We return to normal.") - user.RemoveElement(/datum/element/digitalcamo) - else - to_chat(user, "We distort our form to hide from the AI.") - user.AddElement(/datum/element/digitalcamo) - active = !active - return TRUE - -/datum/action/changeling/digitalcamo/Remove(mob/user) - user.RemoveElement(/datum/element/digitalcamo) - ..() +/datum/action/changeling/digitalcamo + name = "Digital Camouflage" + desc = "By evolving the ability to distort our form and proportions, we defeat common algorithms used to detect lifeforms on cameras." + helptext = "We cannot be tracked by camera or seen by AI units while using this skill. However, humans looking at us will find us... uncanny." + button_icon_state = "digital_camo" + dna_cost = 1 + active = FALSE + +//Prevents AIs tracking you but makes you easily detectable to the human-eye. +/datum/action/changeling/digitalcamo/sting_action(mob/user) + ..() + if(active) + to_chat(user, "We return to normal.") + user.RemoveElement(/datum/element/digitalcamo) + else + to_chat(user, "We distort our form to hide from the AI.") + user.AddElement(/datum/element/digitalcamo) + active = !active + return TRUE + +/datum/action/changeling/digitalcamo/Remove(mob/user) + user.RemoveElement(/datum/element/digitalcamo) + ..() diff --git a/code/modules/antagonists/changeling/powers/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm index 63b59c6440e..4de311ca75f 100644 --- a/code/modules/antagonists/changeling/powers/fakedeath.dm +++ b/code/modules/antagonists/changeling/powers/fakedeath.dm @@ -1,70 +1,70 @@ -/datum/action/changeling/fakedeath - name = "Reviving Stasis" - desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals." - button_icon_state = "fake_death" - chemical_cost = 15 - dna_cost = 0 - req_dna = 1 - req_stat = DEAD - ignores_fakedeath = TRUE - var/revive_ready = FALSE - -//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. -/datum/action/changeling/fakedeath/sting_action(mob/living/user) - ..() - if(revive_ready) - INVOKE_ASYNC(src, .proc/revive, user) - revive_ready = FALSE - name = "Reviving Stasis" - desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." - button_icon_state = "fake_death" - UpdateButtonIcon() - chemical_cost = 15 - to_chat(user, "We have revived ourselves.") - else - to_chat(user, "We begin our stasis, preparing energy to arise once more.") - user.fakedeath("changeling") //play dead - user.update_mobility() - addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) - return TRUE - -/datum/action/changeling/fakedeath/proc/revive(mob/living/user) - if(!user || !istype(user)) - return - user.cure_fakedeath("changeling") - user.revive(full_heal = TRUE, admin_revive = FALSE) - var/list/missing = user.get_missing_limbs() - missing -= BODY_ZONE_HEAD // headless changelings are funny - if(missing.len) - playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) - user.visible_message("[user]'s missing limbs \ - reform, making a loud, grotesque sound!", - "Your limbs regrow, making a \ - loud, crunchy sound and giving you great pain!", - "You hear organic matter ripping \ - and tearing!") - user.emote("scream") - user.regenerate_limbs(0, list(BODY_ZONE_HEAD)) - user.regenerate_organs() - -/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user) - if(user && user.mind) - var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(C && C.purchasedpowers) - to_chat(user, "We are ready to revive.") - name = "Revive" - desc = "We arise once more." - button_icon_state = "revive" - UpdateButtonIcon() - chemical_cost = 0 - revive_ready = TRUE - -/datum/action/changeling/fakedeath/can_sting(mob/living/user) - if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready) - to_chat(user, "We are already reviving.") - return - if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death - switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No")) - if("No") - return - return ..() +/datum/action/changeling/fakedeath + name = "Reviving Stasis" + desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals." + button_icon_state = "fake_death" + chemical_cost = 15 + dna_cost = 0 + req_dna = 1 + req_stat = DEAD + ignores_fakedeath = TRUE + var/revive_ready = FALSE + +//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. +/datum/action/changeling/fakedeath/sting_action(mob/living/user) + ..() + if(revive_ready) + INVOKE_ASYNC(src, .proc/revive, user) + revive_ready = FALSE + name = "Reviving Stasis" + desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." + button_icon_state = "fake_death" + UpdateButtonIcon() + chemical_cost = 15 + to_chat(user, "We have revived ourselves.") + else + to_chat(user, "We begin our stasis, preparing energy to arise once more.") + user.fakedeath("changeling") //play dead + user.update_mobility() + addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) + return TRUE + +/datum/action/changeling/fakedeath/proc/revive(mob/living/user) + if(!user || !istype(user)) + return + user.cure_fakedeath("changeling") + user.revive(full_heal = TRUE, admin_revive = FALSE) + var/list/missing = user.get_missing_limbs() + missing -= BODY_ZONE_HEAD // headless changelings are funny + if(missing.len) + playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) + user.visible_message("[user]'s missing limbs \ + reform, making a loud, grotesque sound!", + "Your limbs regrow, making a \ + loud, crunchy sound and giving you great pain!", + "You hear organic matter ripping \ + and tearing!") + user.emote("scream") + user.regenerate_limbs(0, list(BODY_ZONE_HEAD)) + user.regenerate_organs() + +/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user) + if(user && user.mind) + var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(C && C.purchasedpowers) + to_chat(user, "We are ready to revive.") + name = "Revive" + desc = "We arise once more." + button_icon_state = "revive" + UpdateButtonIcon() + chemical_cost = 0 + revive_ready = TRUE + +/datum/action/changeling/fakedeath/can_sting(mob/living/user) + if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready) + to_chat(user, "We are already reviving.") + return + if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death + switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No")) + if("No") + return + return ..() diff --git a/code/modules/antagonists/changeling/powers/fleshmend.dm b/code/modules/antagonists/changeling/powers/fleshmend.dm index 3acc565e4bf..c586e35d5a0 100644 --- a/code/modules/antagonists/changeling/powers/fleshmend.dm +++ b/code/modules/antagonists/changeling/powers/fleshmend.dm @@ -1,21 +1,21 @@ -/datum/action/changeling/fleshmend - name = "Fleshmend" - desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath, as well as hiding all of our scars. Costs 20 chemicals." - helptext = "If we are on fire, the healing effect will not function. Does not regrow limbs or restore lost blood. Functions while unconscious." - button_icon_state = "fleshmend" - chemical_cost = 20 - dna_cost = 2 - req_stat = UNCONSCIOUS - -//Starts healing you every second for 10 seconds. -//Can be used whilst unconscious. -/datum/action/changeling/fleshmend/sting_action(mob/living/user) - if(user.has_status_effect(STATUS_EFFECT_FLESHMEND)) - to_chat(user, "We are already fleshmending!") - return - ..() - to_chat(user, "We begin to heal rapidly.") - user.apply_status_effect(STATUS_EFFECT_FLESHMEND) - return TRUE - -//Check buffs.dm for the fleshmend status effect code +/datum/action/changeling/fleshmend + name = "Fleshmend" + desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath, as well as hiding all of our scars. Costs 20 chemicals." + helptext = "If we are on fire, the healing effect will not function. Does not regrow limbs or restore lost blood. Functions while unconscious." + button_icon_state = "fleshmend" + chemical_cost = 20 + dna_cost = 2 + req_stat = UNCONSCIOUS + +//Starts healing you every second for 10 seconds. +//Can be used whilst unconscious. +/datum/action/changeling/fleshmend/sting_action(mob/living/user) + if(user.has_status_effect(STATUS_EFFECT_FLESHMEND)) + to_chat(user, "We are already fleshmending!") + return + ..() + to_chat(user, "We begin to heal rapidly.") + user.apply_status_effect(STATUS_EFFECT_FLESHMEND) + return TRUE + +//Check buffs.dm for the fleshmend status effect code diff --git a/code/modules/antagonists/changeling/powers/hivemind.dm b/code/modules/antagonists/changeling/powers/hivemind.dm index 6f503755a6e..ece6b788d3c 100644 --- a/code/modules/antagonists/changeling/powers/hivemind.dm +++ b/code/modules/antagonists/changeling/powers/hivemind.dm @@ -1,117 +1,117 @@ -//HIVEMIND COMMUNICATION (:g) -/datum/action/changeling/hivemind_comms - name = "Hivemind Communication" - desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings." - helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives." - needs_button = FALSE - dna_cost = 0 - chemical_cost = -1 - -/datum/action/changeling/hivemind_comms/on_purchase(mob/user, is_respec) - ..() - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.changeling_speak = 1 - to_chat(user, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with the other changelings.") - var/datum/action/changeling/hivemind_upload/S1 = new - if(!changeling.has_sting(S1)) - S1.Grant(user) - changeling.purchasedpowers+=S1 - var/datum/action/changeling/hivemind_download/S2 = new - if(!changeling.has_sting(S2)) - S2.Grant(user) - changeling.purchasedpowers+=S2 - -/datum/action/changeling/hivemind_comms/Remove(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.changeling_speak) - changeling.changeling_speak = FALSE - for(var/p in changeling.purchasedpowers) - var/datum/action/changeling/otherpower = p - if(istype(otherpower, /datum/action/changeling/hivemind_upload) || istype(otherpower, /datum/action/changeling/hivemind_download)) - changeling.purchasedpowers -= otherpower - otherpower.Remove(changeling.owner.current) - ..() - - -// HIVE MIND UPLOAD/DOWNLOAD DNA -GLOBAL_LIST_EMPTY(hivemind_bank) - -/datum/action/changeling/hivemind_upload - name = "Hive Channel DNA" - desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals." - button_icon_state = "hivemind_channel" - chemical_cost = 10 - dna_cost = -1 - -/datum/action/changeling/hivemind_upload/sting_action(var/mob/living/user) - if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) - to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") - return - ..() - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in changeling.stored_profiles) - if(!(prof in GLOB.hivemind_bank)) - names += prof.name - - if(names.len <= 0) - to_chat(user, "The airwaves already have all of our DNA!") - return - - var/chosen_name = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in sortList(names) - if(!chosen_name) - return - - var/datum/changelingprofile/chosen_dna = changeling.get_dna(chosen_name) - if(!chosen_dna) - return - - var/datum/changelingprofile/uploaded_dna = new chosen_dna.type - chosen_dna.copy_profile(uploaded_dna) - GLOB.hivemind_bank += uploaded_dna - to_chat(user, "We channel the DNA of [chosen_name] to the air.") - return TRUE - -/datum/action/changeling/hivemind_download - name = "Hive Absorb DNA" - desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals." - button_icon_state = "hive_absorb" - chemical_cost = 10 - dna_cost = -1 - -/datum/action/changeling/hivemind_download/can_sting(mob/living/carbon/user) - if(!..()) - return - if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) - to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/datum/changelingprofile/first_prof = changeling.stored_profiles[1] - if(first_prof.name == user.real_name)//If our current DNA is the stalest, we gotta ditch it. - to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") - return - return 1 - -/datum/action/changeling/hivemind_download/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in GLOB.hivemind_bank) - if(!(prof in changeling.stored_profiles)) - names[prof.name] = prof - - if(names.len <= 0) - to_chat(user, "There's no new DNA to absorb from the air!") - return - - var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in sortList(names) - if(!S) - return - var/datum/changelingprofile/chosen_prof = names[S] - if(!chosen_prof) - return - ..() - var/datum/changelingprofile/downloaded_prof = new chosen_prof.type - chosen_prof.copy_profile(downloaded_prof) - changeling.add_profile(downloaded_prof) - to_chat(user, "We absorb the DNA of [S] from the air.") - return TRUE +//HIVEMIND COMMUNICATION (:g) +/datum/action/changeling/hivemind_comms + name = "Hivemind Communication" + desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings." + helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives." + needs_button = FALSE + dna_cost = 0 + chemical_cost = -1 + +/datum/action/changeling/hivemind_comms/on_purchase(mob/user, is_respec) + ..() + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.changeling_speak = 1 + to_chat(user, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with the other changelings.") + var/datum/action/changeling/hivemind_upload/S1 = new + if(!changeling.has_sting(S1)) + S1.Grant(user) + changeling.purchasedpowers+=S1 + var/datum/action/changeling/hivemind_download/S2 = new + if(!changeling.has_sting(S2)) + S2.Grant(user) + changeling.purchasedpowers+=S2 + +/datum/action/changeling/hivemind_comms/Remove(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.changeling_speak) + changeling.changeling_speak = FALSE + for(var/p in changeling.purchasedpowers) + var/datum/action/changeling/otherpower = p + if(istype(otherpower, /datum/action/changeling/hivemind_upload) || istype(otherpower, /datum/action/changeling/hivemind_download)) + changeling.purchasedpowers -= otherpower + otherpower.Remove(changeling.owner.current) + ..() + + +// HIVE MIND UPLOAD/DOWNLOAD DNA +GLOBAL_LIST_EMPTY(hivemind_bank) + +/datum/action/changeling/hivemind_upload + name = "Hive Channel DNA" + desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals." + button_icon_state = "hivemind_channel" + chemical_cost = 10 + dna_cost = -1 + +/datum/action/changeling/hivemind_upload/sting_action(var/mob/living/user) + if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) + to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") + return + ..() + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in changeling.stored_profiles) + if(!(prof in GLOB.hivemind_bank)) + names += prof.name + + if(names.len <= 0) + to_chat(user, "The airwaves already have all of our DNA!") + return + + var/chosen_name = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in sortList(names) + if(!chosen_name) + return + + var/datum/changelingprofile/chosen_dna = changeling.get_dna(chosen_name) + if(!chosen_dna) + return + + var/datum/changelingprofile/uploaded_dna = new chosen_dna.type + chosen_dna.copy_profile(uploaded_dna) + GLOB.hivemind_bank += uploaded_dna + to_chat(user, "We channel the DNA of [chosen_name] to the air.") + return TRUE + +/datum/action/changeling/hivemind_download + name = "Hive Absorb DNA" + desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals." + button_icon_state = "hive_absorb" + chemical_cost = 10 + dna_cost = -1 + +/datum/action/changeling/hivemind_download/can_sting(mob/living/carbon/user) + if(!..()) + return + if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) + to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/datum/changelingprofile/first_prof = changeling.stored_profiles[1] + if(first_prof.name == user.real_name)//If our current DNA is the stalest, we gotta ditch it. + to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") + return + return 1 + +/datum/action/changeling/hivemind_download/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in GLOB.hivemind_bank) + if(!(prof in changeling.stored_profiles)) + names[prof.name] = prof + + if(names.len <= 0) + to_chat(user, "There's no new DNA to absorb from the air!") + return + + var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in sortList(names) + if(!S) + return + var/datum/changelingprofile/chosen_prof = names[S] + if(!chosen_prof) + return + ..() + var/datum/changelingprofile/downloaded_prof = new chosen_prof.type + chosen_prof.copy_profile(downloaded_prof) + changeling.add_profile(downloaded_prof) + to_chat(user, "We absorb the DNA of [S] from the air.") + return TRUE diff --git a/code/modules/antagonists/changeling/powers/humanform.dm b/code/modules/antagonists/changeling/powers/humanform.dm index 0acc59315c7..e109a5981bd 100644 --- a/code/modules/antagonists/changeling/powers/humanform.dm +++ b/code/modules/antagonists/changeling/powers/humanform.dm @@ -1,34 +1,34 @@ -/datum/action/changeling/humanform - name = "Human Form" - desc = "We change into a human. Costs 5 chemicals." - button_icon_state = "human_form" - chemical_cost = 5 - req_dna = 1 - -//Transform into a human. -/datum/action/changeling/humanform/sting_action(mob/living/carbon/user) - if(user.movement_type & VENTCRAWLING) - to_chat(user, "We must exit the pipes before we can transform back!") - return FALSE - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in changeling.stored_profiles) - names += "[prof.name]" - - var/chosen_name = input("Select the target DNA: ", "Target DNA", null) as null|anything in sortList(names) - if(!chosen_name) - return - - var/datum/changelingprofile/chosen_prof = changeling.get_dna(chosen_name) - if(!chosen_prof) - return - if(!user || user.notransform) - return FALSE - to_chat(user, "We transform our appearance.") - ..() - changeling.purchasedpowers -= src - - var/newmob = user.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - - changeling_transform(newmob, chosen_prof) - return TRUE +/datum/action/changeling/humanform + name = "Human Form" + desc = "We change into a human. Costs 5 chemicals." + button_icon_state = "human_form" + chemical_cost = 5 + req_dna = 1 + +//Transform into a human. +/datum/action/changeling/humanform/sting_action(mob/living/carbon/user) + if(user.movement_type & VENTCRAWLING) + to_chat(user, "We must exit the pipes before we can transform back!") + return FALSE + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in changeling.stored_profiles) + names += "[prof.name]" + + var/chosen_name = input("Select the target DNA: ", "Target DNA", null) as null|anything in sortList(names) + if(!chosen_name) + return + + var/datum/changelingprofile/chosen_prof = changeling.get_dna(chosen_name) + if(!chosen_prof) + return + if(!user || user.notransform) + return FALSE + to_chat(user, "We transform our appearance.") + ..() + changeling.purchasedpowers -= src + + var/newmob = user.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + + changeling_transform(newmob, chosen_prof) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/lesserform.dm b/code/modules/antagonists/changeling/powers/lesserform.dm index 25af1eb3560..b180916827d 100644 --- a/code/modules/antagonists/changeling/powers/lesserform.dm +++ b/code/modules/antagonists/changeling/powers/lesserform.dm @@ -1,17 +1,17 @@ -/datum/action/changeling/lesserform - name = "Lesser Form" - desc = "We debase ourselves and become lesser. We become a monkey. Costs 5 chemicals." - helptext = "The transformation greatly reduces our size, allowing us to slip out of cuffs and climb through vents." - button_icon_state = "lesser_form" - chemical_cost = 5 - dna_cost = 1 - req_human = 1 - -//Transform into a monkey. -/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) - if(!user || user.notransform) - return FALSE - to_chat(user, "Our genes cry out!") - ..() - user.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE | TR_KEEPSTAMINADAMAGE) - return TRUE +/datum/action/changeling/lesserform + name = "Lesser Form" + desc = "We debase ourselves and become lesser. We become a monkey. Costs 5 chemicals." + helptext = "The transformation greatly reduces our size, allowing us to slip out of cuffs and climb through vents." + button_icon_state = "lesser_form" + chemical_cost = 5 + dna_cost = 1 + req_human = 1 + +//Transform into a monkey. +/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) + if(!user || user.notransform) + return FALSE + to_chat(user, "Our genes cry out!") + ..() + user.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE | TR_KEEPSTAMINADAMAGE) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/mimic_voice.dm b/code/modules/antagonists/changeling/powers/mimic_voice.dm index 41d17eb3d47..d337bddda0d 100644 --- a/code/modules/antagonists/changeling/powers/mimic_voice.dm +++ b/code/modules/antagonists/changeling/powers/mimic_voice.dm @@ -1,27 +1,27 @@ -/datum/action/changeling/mimicvoice - name = "Mimic Voice" - desc = "We shape our vocal glands to sound like a desired voice. Maintaining this power slows chemical production." - button_icon_state = "mimic_voice" - helptext = "Will turn your voice into the name that you enter. We must constantly expend chemicals to maintain our form like this." - chemical_cost = 0//constant chemical drain hardcoded - dna_cost = 1 - req_human = 1 - -// Fake Voice -/datum/action/changeling/mimicvoice/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.mimicing) - changeling.mimicing = "" - changeling.chem_recharge_slowdown -= 0.5 - to_chat(user, "We return our vocal glands to their original position.") - return - - var/mimic_voice = sanitize_name(stripped_input(user, "Enter a name to mimic.", "Mimic Voice", null, MAX_NAME_LEN)) - if(!mimic_voice) - return - ..() - changeling.mimicing = mimic_voice - changeling.chem_recharge_slowdown += 0.5 - to_chat(user, "We shape our glands to take the voice of [mimic_voice], this will slow down regenerating chemicals while active.") - to_chat(user, "Use this power again to return to our original voice and return chemical production to normal levels.") - return TRUE +/datum/action/changeling/mimicvoice + name = "Mimic Voice" + desc = "We shape our vocal glands to sound like a desired voice. Maintaining this power slows chemical production." + button_icon_state = "mimic_voice" + helptext = "Will turn your voice into the name that you enter. We must constantly expend chemicals to maintain our form like this." + chemical_cost = 0//constant chemical drain hardcoded + dna_cost = 1 + req_human = 1 + +// Fake Voice +/datum/action/changeling/mimicvoice/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.mimicing) + changeling.mimicing = "" + changeling.chem_recharge_slowdown -= 0.5 + to_chat(user, "We return our vocal glands to their original position.") + return + + var/mimic_voice = sanitize_name(stripped_input(user, "Enter a name to mimic.", "Mimic Voice", null, MAX_NAME_LEN)) + if(!mimic_voice) + return + ..() + changeling.mimicing = mimic_voice + changeling.chem_recharge_slowdown += 0.5 + to_chat(user, "We shape our glands to take the voice of [mimic_voice], this will slow down regenerating chemicals while active.") + to_chat(user, "Use this power again to return to our original voice and return chemical production to normal levels.") + return TRUE diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm index 44be1f0056d..1098f541635 100644 --- a/code/modules/antagonists/changeling/powers/panacea.dm +++ b/code/modules/antagonists/changeling/powers/panacea.dm @@ -1,45 +1,45 @@ -/datum/action/changeling/panacea - name = "Anatomic Panacea" - desc = "Expels impurifications from our form; curing diseases, removing parasites, sobering us, purging toxins and radiation, curing traumas and brain damage, and resetting our genetic code completely. Costs 20 chemicals." - helptext = "Can be used while unconscious." - button_icon_state = "panacea" - chemical_cost = 20 - dna_cost = 1 - req_stat = UNCONSCIOUS - -//Heals the things that the other regenerative abilities don't. -/datum/action/changeling/panacea/sting_action(mob/user) - to_chat(user, "We cleanse impurities from our form.") - ..() - var/list/bad_organs = list( - user.getorgan(/obj/item/organ/body_egg), - user.getorgan(/obj/item/organ/zombie_infection)) - - for(var/o in bad_organs) - var/obj/item/organ/O = o - if(!istype(O)) - continue - - O.Remove(user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.vomit(0, toxic = TRUE) - O.forceMove(get_turf(user)) - - user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) - user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20) - user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10) - user.reagents.add_reagent(/datum/reagent/medicine/mannitol, 25) - - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) - - if(isliving(user)) - var/mob/living/L = user - for(var/thing in L.diseases) - var/datum/disease/D = thing - if(D.severity == DISEASE_SEVERITY_POSITIVE) - continue - D.cure() - return TRUE +/datum/action/changeling/panacea + name = "Anatomic Panacea" + desc = "Expels impurifications from our form; curing diseases, removing parasites, sobering us, purging toxins and radiation, curing traumas and brain damage, and resetting our genetic code completely. Costs 20 chemicals." + helptext = "Can be used while unconscious." + button_icon_state = "panacea" + chemical_cost = 20 + dna_cost = 1 + req_stat = UNCONSCIOUS + +//Heals the things that the other regenerative abilities don't. +/datum/action/changeling/panacea/sting_action(mob/user) + to_chat(user, "We cleanse impurities from our form.") + ..() + var/list/bad_organs = list( + user.getorgan(/obj/item/organ/body_egg), + user.getorgan(/obj/item/organ/zombie_infection)) + + for(var/o in bad_organs) + var/obj/item/organ/O = o + if(!istype(O)) + continue + + O.Remove(user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.vomit(0, toxic = TRUE) + O.forceMove(get_turf(user)) + + user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) + user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20) + user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10) + user.reagents.add_reagent(/datum/reagent/medicine/mannitol, 25) + + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) + + if(isliving(user)) + var/mob/living/L = user + for(var/thing in L.diseases) + var/datum/disease/D = thing + if(D.severity == DISEASE_SEVERITY_POSITIVE) + continue + D.cure() + return TRUE diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm index e30726da7b6..45d1af1dfe5 100644 --- a/code/modules/antagonists/changeling/powers/shriek.dm +++ b/code/modules/antagonists/changeling/powers/shriek.dm @@ -1,47 +1,47 @@ -/datum/action/changeling/resonant_shriek - name = "Resonant Shriek" - desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 20 chemicals." - helptext = "Emits a high-frequency sound that confuses and deafens humans, blows out nearby lights and overloads cyborg sensors." - button_icon_state = "resonant_shriek" - chemical_cost = 20 - dna_cost = 1 - req_human = 1 - -//A flashy ability, good for crowd control and sowing chaos. -/datum/action/changeling/resonant_shriek/sting_action(mob/user) - ..() - for(var/mob/living/M in get_hearers_in_view(4, user)) - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling)) - var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) - if(ears) - ears.adjustEarDamage(0, 30) - C.confused += 25 - C.Jitter(50) - else - SEND_SOUND(C, sound('sound/effects/screech.ogg')) - - if(issilicon(M)) - SEND_SOUND(M, sound('sound/weapons/flash.ogg')) - M.Paralyze(rand(100,200)) - - for(var/obj/machinery/light/L in range(4, user)) - L.on = 1 - L.break_light_tube() - return TRUE - -/datum/action/changeling/dissonant_shriek - name = "Dissonant Shriek" - desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby electronics. Costs 20 chemicals." - button_icon_state = "dissonant_shriek" - chemical_cost = 20 - dna_cost = 1 - -/datum/action/changeling/dissonant_shriek/sting_action(mob/user) - ..() - for(var/obj/machinery/light/L in range(5, usr)) - L.on = 1 - L.break_light_tube() - empulse(get_turf(user), 2, 5, 1) - return TRUE +/datum/action/changeling/resonant_shriek + name = "Resonant Shriek" + desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 20 chemicals." + helptext = "Emits a high-frequency sound that confuses and deafens humans, blows out nearby lights and overloads cyborg sensors." + button_icon_state = "resonant_shriek" + chemical_cost = 20 + dna_cost = 1 + req_human = 1 + +//A flashy ability, good for crowd control and sowing chaos. +/datum/action/changeling/resonant_shriek/sting_action(mob/user) + ..() + for(var/mob/living/M in get_hearers_in_view(4, user)) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling)) + var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) + if(ears) + ears.adjustEarDamage(0, 30) + C.confused += 25 + C.Jitter(50) + else + SEND_SOUND(C, sound('sound/effects/screech.ogg')) + + if(issilicon(M)) + SEND_SOUND(M, sound('sound/weapons/flash.ogg')) + M.Paralyze(rand(100,200)) + + for(var/obj/machinery/light/L in range(4, user)) + L.on = 1 + L.break_light_tube() + return TRUE + +/datum/action/changeling/dissonant_shriek + name = "Dissonant Shriek" + desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby electronics. Costs 20 chemicals." + button_icon_state = "dissonant_shriek" + chemical_cost = 20 + dna_cost = 1 + +/datum/action/changeling/dissonant_shriek/sting_action(mob/user) + ..() + for(var/obj/machinery/light/L in range(5, usr)) + L.on = 1 + L.break_light_tube() + empulse(get_turf(user), 2, 5, 1) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/spiders.dm b/code/modules/antagonists/changeling/powers/spiders.dm index bfc695bf875..ccd412fb223 100644 --- a/code/modules/antagonists/changeling/powers/spiders.dm +++ b/code/modules/antagonists/changeling/powers/spiders.dm @@ -1,14 +1,14 @@ -/datum/action/changeling/spiders - name = "Spread Infestation" - desc = "Our form divides, creating arachnids which will grow into deadly beasts. Costs 45 chemicals." - helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA absorptions." - button_icon_state = "spread_infestation" - chemical_cost = 45 - dna_cost = 1 - req_absorbs = 3 - -//Makes some spiderlings. Good for setting traps and causing general trouble. -/datum/action/changeling/spiders/sting_action(mob/user) - ..() - spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE) - return TRUE +/datum/action/changeling/spiders + name = "Spread Infestation" + desc = "Our form divides, creating arachnids which will grow into deadly beasts. Costs 45 chemicals." + helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA absorptions." + button_icon_state = "spread_infestation" + chemical_cost = 45 + dna_cost = 1 + req_absorbs = 3 + +//Makes some spiderlings. Good for setting traps and causing general trouble. +/datum/action/changeling/spiders/sting_action(mob/user) + ..() + spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index 163caf2bba2..dc2db854d48 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -1,243 +1,243 @@ -/datum/action/changeling/sting//parent path, not meant for users afaik - name = "Tiny Prick" - desc = "Stabby stabby" - -/datum/action/changeling/sting/Trigger() - var/mob/user = owner - if(!user || !user.mind) - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(!changeling) - return - if(!changeling.chosen_sting) - set_sting(user) - else - unset_sting(user) - return - -/datum/action/changeling/sting/proc/set_sting(mob/user) - to_chat(user, "We prepare our sting. Alt+click or click the middle mouse button on a target to sting them.") - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.chosen_sting = src - - user.hud_used.lingstingdisplay.icon_state = button_icon_state - user.hud_used.lingstingdisplay.invisibility = 0 - -/datum/action/changeling/sting/proc/unset_sting(mob/user) - to_chat(user, "We retract our sting, we can't sting anyone for now.") - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.chosen_sting = null - - user.hud_used.lingstingdisplay.icon_state = null - user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT - -/mob/living/carbon/proc/unset_sting() - if(mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling && changeling.chosen_sting) - changeling.chosen_sting.unset_sting(src) - -/datum/action/changeling/sting/can_sting(mob/user, mob/target) - if(!..()) - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(!changeling.chosen_sting) - to_chat(user, "We haven't prepared our sting yet!") - if(!iscarbon(target)) - return - if(!isturf(user.loc)) - return - if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = FALSE)) - return - if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) - sting_feedback(user, target) - changeling.chem_charges -= chemical_cost - return 1 - -/datum/action/changeling/sting/sting_feedback(mob/user, mob/target) - if(!target) - return - to_chat(user, "We stealthily sting [target.name].") - if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(target, "You feel a tiny prick.") - return 1 - - -/datum/action/changeling/sting/transformation - name = "Transformation Sting" - desc = "We silently sting a human, injecting a retrovirus that forces them to transform. Costs 50 chemicals." - helptext = "The victim will transform much like a changeling would. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human." - button_icon_state = "sting_transform" - chemical_cost = 50 - dna_cost = 3 - var/datum/changelingprofile/selected_dna = null - -/datum/action/changeling/sting/transformation/Trigger() - var/mob/user = usr - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.chosen_sting) - unset_sting(user) - return - selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA") - if(!selected_dna) - return - if(NOTRANSSTING in selected_dna.dna.species.species_traits) - to_chat(user, "That DNA is not compatible with changeling retrovirus!") - return - ..() - -/datum/action/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target) - if(!..()) - return - if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits)) - to_chat(user, "Our sting appears ineffective against its DNA.") - return 0 - return 1 - -/datum/action/changeling/sting/transformation/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") - var/datum/dna/NewDNA = selected_dna.dna - if(ismonkey(target)) - to_chat(user, "Our genes cry out as we sting [target.name]!") - - var/mob/living/carbon/C = target - . = TRUE - if(istype(C)) - C.real_name = NewDNA.real_name - NewDNA.transfer_identity(C) - if(ismonkey(C)) - C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) - C.updateappearance(mutcolor_update=1) - - -/datum/action/changeling/sting/false_armblade - name = "False Armblade Sting" - desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade. Costs 20 chemicals." - helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless." - button_icon_state = "sting_armblade" - chemical_cost = 20 - dna_cost = 1 - -/obj/item/melee/arm_blade/false - desc = "A grotesque mass of flesh that used to be your arm. Although it looks dangerous at first, you can tell it's actually quite dull and useless." - force = 5 //Basically as strong as a punch - fake = TRUE - -/datum/action/changeling/sting/false_armblade/can_sting(mob/user, mob/target) - if(!..()) - return - if(isliving(target)) - var/mob/living/L = target - if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna()) - to_chat(user, "Our sting appears ineffective against its DNA.") - return 0 - return 1 - -/datum/action/changeling/sting/false_armblade/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", object="false armblade sting") - - var/obj/item/held = target.get_active_held_item() - if(held && !target.dropItemToGround(held)) - to_chat(user, "[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!") - return - ..() - if(ismonkey(target)) - to_chat(user, "Our genes cry out as we sting [target.name]!") - - var/obj/item/melee/arm_blade/false/blade = new(target,1) - target.put_in_hands(blade) - target.visible_message("A grotesque blade forms around [target.name]\'s arm!", "Your arm twists and mutates, transforming into a horrific monstrosity!", "You hear organic matter ripping and tearing!") - playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) - - addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) - return TRUE - -/datum/action/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) - playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) - target.visible_message("With a sickening crunch, \ - [target] reforms [target.p_their()] [blade.name] into an arm!", - "[blade] reforms back to normal.", - "Your eyes burn horrifically!") - target.become_nearsighted(EYE_DAMAGE) - target.blind_eyes(20) - target.blur_eyes(40) - return TRUE - -/datum/action/changeling/sting/lsd - name = "Hallucination Sting" - desc = "We cause mass terror to our victim." - helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect occurs after 30 to 60 seconds." - button_icon_state = "sting_lsd" - chemical_cost = 10 - dna_cost = 1 - -/datum/action/changeling/sting/lsd/sting_action(mob/user, mob/living/carbon/target) - log_combat(user, target, "stung", "LSD sting") - addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(300,600)) - return TRUE - -/datum/action/changeling/sting/lsd/proc/hallucination_time(mob/living/carbon/target) - if(target) - target.hallucination = max(90, target.hallucination) - -/datum/action/changeling/sting/cryo - name = "Cryogenic Sting" - desc = "We silently sting our victim with a cocktail of chemicals that freezes them from the inside. Costs 15 chemicals." - helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing." - button_icon_state = "sting_cryo" - chemical_cost = 15 - dna_cost = 2 - -/datum/action/changeling/sting/cryo/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", "cryo sting") - if(target.reagents) - target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30) - return TRUE +/datum/action/changeling/sting//parent path, not meant for users afaik + name = "Tiny Prick" + desc = "Stabby stabby" + +/datum/action/changeling/sting/Trigger() + var/mob/user = owner + if(!user || !user.mind) + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(!changeling) + return + if(!changeling.chosen_sting) + set_sting(user) + else + unset_sting(user) + return + +/datum/action/changeling/sting/proc/set_sting(mob/user) + to_chat(user, "We prepare our sting. Alt+click or click the middle mouse button on a target to sting them.") + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.chosen_sting = src + + user.hud_used.lingstingdisplay.icon_state = button_icon_state + user.hud_used.lingstingdisplay.invisibility = 0 + +/datum/action/changeling/sting/proc/unset_sting(mob/user) + to_chat(user, "We retract our sting, we can't sting anyone for now.") + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.chosen_sting = null + + user.hud_used.lingstingdisplay.icon_state = null + user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT + +/mob/living/carbon/proc/unset_sting() + if(mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling && changeling.chosen_sting) + changeling.chosen_sting.unset_sting(src) + +/datum/action/changeling/sting/can_sting(mob/user, mob/target) + if(!..()) + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(!changeling.chosen_sting) + to_chat(user, "We haven't prepared our sting yet!") + if(!iscarbon(target)) + return + if(!isturf(user.loc)) + return + if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = FALSE)) + return + if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) + sting_feedback(user, target) + changeling.chem_charges -= chemical_cost + return 1 + +/datum/action/changeling/sting/sting_feedback(mob/user, mob/target) + if(!target) + return + to_chat(user, "We stealthily sting [target.name].") + if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(target, "You feel a tiny prick.") + return 1 + + +/datum/action/changeling/sting/transformation + name = "Transformation Sting" + desc = "We silently sting a human, injecting a retrovirus that forces them to transform. Costs 50 chemicals." + helptext = "The victim will transform much like a changeling would. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human." + button_icon_state = "sting_transform" + chemical_cost = 50 + dna_cost = 3 + var/datum/changelingprofile/selected_dna = null + +/datum/action/changeling/sting/transformation/Trigger() + var/mob/user = usr + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.chosen_sting) + unset_sting(user) + return + selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA") + if(!selected_dna) + return + if(NOTRANSSTING in selected_dna.dna.species.species_traits) + to_chat(user, "That DNA is not compatible with changeling retrovirus!") + return + ..() + +/datum/action/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target) + if(!..()) + return + if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits)) + to_chat(user, "Our sting appears ineffective against its DNA.") + return 0 + return 1 + +/datum/action/changeling/sting/transformation/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") + var/datum/dna/NewDNA = selected_dna.dna + if(ismonkey(target)) + to_chat(user, "Our genes cry out as we sting [target.name]!") + + var/mob/living/carbon/C = target + . = TRUE + if(istype(C)) + C.real_name = NewDNA.real_name + NewDNA.transfer_identity(C) + if(ismonkey(C)) + C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) + C.updateappearance(mutcolor_update=1) + + +/datum/action/changeling/sting/false_armblade + name = "False Armblade Sting" + desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade. Costs 20 chemicals." + helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless." + button_icon_state = "sting_armblade" + chemical_cost = 20 + dna_cost = 1 + +/obj/item/melee/arm_blade/false + desc = "A grotesque mass of flesh that used to be your arm. Although it looks dangerous at first, you can tell it's actually quite dull and useless." + force = 5 //Basically as strong as a punch + fake = TRUE + +/datum/action/changeling/sting/false_armblade/can_sting(mob/user, mob/target) + if(!..()) + return + if(isliving(target)) + var/mob/living/L = target + if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna()) + to_chat(user, "Our sting appears ineffective against its DNA.") + return 0 + return 1 + +/datum/action/changeling/sting/false_armblade/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", object="false armblade sting") + + var/obj/item/held = target.get_active_held_item() + if(held && !target.dropItemToGround(held)) + to_chat(user, "[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!") + return + ..() + if(ismonkey(target)) + to_chat(user, "Our genes cry out as we sting [target.name]!") + + var/obj/item/melee/arm_blade/false/blade = new(target,1) + target.put_in_hands(blade) + target.visible_message("A grotesque blade forms around [target.name]\'s arm!", "Your arm twists and mutates, transforming into a horrific monstrosity!", "You hear organic matter ripping and tearing!") + playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) + + addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) + return TRUE + +/datum/action/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) + playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) + target.visible_message("With a sickening crunch, \ + [target] reforms [target.p_their()] [blade.name] into an arm!", + "[blade] reforms back to normal.", + "Your eyes burn horrifically!") + target.become_nearsighted(EYE_DAMAGE) + target.blind_eyes(20) + target.blur_eyes(40) + return TRUE + +/datum/action/changeling/sting/lsd + name = "Hallucination Sting" + desc = "We cause mass terror to our victim." + helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect occurs after 30 to 60 seconds." + button_icon_state = "sting_lsd" + chemical_cost = 10 + dna_cost = 1 + +/datum/action/changeling/sting/lsd/sting_action(mob/user, mob/living/carbon/target) + log_combat(user, target, "stung", "LSD sting") + addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(300,600)) + return TRUE + +/datum/action/changeling/sting/lsd/proc/hallucination_time(mob/living/carbon/target) + if(target) + target.hallucination = max(90, target.hallucination) + +/datum/action/changeling/sting/cryo + name = "Cryogenic Sting" + desc = "We silently sting our victim with a cocktail of chemicals that freezes them from the inside. Costs 15 chemicals." + helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing." + button_icon_state = "sting_cryo" + chemical_cost = 15 + dna_cost = 2 + +/datum/action/changeling/sting/cryo/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", "cryo sting") + if(target.reagents) + target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/transform.dm b/code/modules/antagonists/changeling/powers/transform.dm index 4f5e9a6d07a..5afcfafc796 100644 --- a/code/modules/antagonists/changeling/powers/transform.dm +++ b/code/modules/antagonists/changeling/powers/transform.dm @@ -1,170 +1,170 @@ -/datum/action/changeling/transform - name = "Transform" - desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals." - button_icon_state = "transform" - chemical_cost = 5 - dna_cost = 0 - req_dna = 1 - req_human = 1 - -/obj/item/clothing/glasses/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/glasses/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/glasses/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/under/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/under/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/under/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/suit/changeling - name = "flesh" - allowed = list(/obj/item/changeling) - item_flags = DROPDEL - -/obj/item/clothing/suit/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/suit/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/head/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/head/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/head/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/shoes/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/shoes/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/shoes/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/gloves/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/gloves/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/gloves/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/mask/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/mask/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/mask/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/changeling - name = "flesh" - slot_flags = ALL - allowed = list(/obj/item/changeling) - item_flags = DROPDEL - -/obj/item/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -//Change our DNA to that of somebody we've absorbed. -/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/datum/changelingprofile/chosen_prof = changeling.select_dna("Select the target DNA: ", "Target DNA") - - if(!chosen_prof) - return - ..() - changeling_transform(user, chosen_prof) - return TRUE - -/datum/antagonist/changeling/proc/select_dna(prompt, title) - var/mob/living/carbon/user = owner.current - if(!istype(user)) - return - var/list/names = list("Drop Flesh Disguise") - for(var/datum/changelingprofile/prof in stored_profiles) - names += "[prof.name]" - - var/chosen_name = input(prompt, title, null) as null|anything in sortList(names) - if(!chosen_name) - return - - if(chosen_name == "Drop Flesh Disguise") - for(var/slot in GLOB.slots) - if(istype(user.vars[slot], GLOB.slot2type[slot])) - qdel(user.vars[slot]) - - var/datum/changelingprofile/prof = get_dna(chosen_name) - return prof +/datum/action/changeling/transform + name = "Transform" + desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals." + button_icon_state = "transform" + chemical_cost = 5 + dna_cost = 0 + req_dna = 1 + req_human = 1 + +/obj/item/clothing/glasses/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/glasses/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/glasses/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/under/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/under/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/under/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/suit/changeling + name = "flesh" + allowed = list(/obj/item/changeling) + item_flags = DROPDEL + +/obj/item/clothing/suit/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/suit/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/head/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/head/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/head/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/shoes/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/shoes/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/shoes/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/gloves/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/gloves/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/gloves/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/mask/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/mask/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/mask/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/changeling + name = "flesh" + slot_flags = ALL + allowed = list(/obj/item/changeling) + item_flags = DROPDEL + +/obj/item/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +//Change our DNA to that of somebody we've absorbed. +/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/datum/changelingprofile/chosen_prof = changeling.select_dna("Select the target DNA: ", "Target DNA") + + if(!chosen_prof) + return + ..() + changeling_transform(user, chosen_prof) + return TRUE + +/datum/antagonist/changeling/proc/select_dna(prompt, title) + var/mob/living/carbon/user = owner.current + if(!istype(user)) + return + var/list/names = list("Drop Flesh Disguise") + for(var/datum/changelingprofile/prof in stored_profiles) + names += "[prof.name]" + + var/chosen_name = input(prompt, title, null) as null|anything in sortList(names) + if(!chosen_name) + return + + if(chosen_name == "Drop Flesh Disguise") + for(var/slot in GLOB.slots) + if(istype(user.vars[slot], GLOB.slot2type[slot])) + qdel(user.vars[slot]) + + var/datum/changelingprofile/prof = get_dna(chosen_name) + return prof diff --git a/code/modules/antagonists/nukeop/equipment/pinpointer.dm b/code/modules/antagonists/nukeop/equipment/pinpointer.dm index 8a88d68514e..1b8efa529c9 100644 --- a/code/modules/antagonists/nukeop/equipment/pinpointer.dm +++ b/code/modules/antagonists/nukeop/equipment/pinpointer.dm @@ -1,90 +1,90 @@ -/obj/item/pinpointer/nuke - var/mode = TRACK_NUKE_DISK - -/obj/item/pinpointer/nuke/examine(mob/user) - . = ..() - var/msg = "Its tracking indicator reads " - switch(mode) - if(TRACK_NUKE_DISK) - msg += "\"nuclear_disk\"." - if(TRACK_MALF_AI) - msg += "\"01000001 01001001\"." - if(TRACK_INFILTRATOR) - msg += "\"vasvygengbefuvc\"." - else - msg = "Its tracking indicator is blank." - . += msg - for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) - if(bomb.timing) - . += "Extreme danger. Arming signal detected. Time remaining: [bomb.get_time_left()]." - -/obj/item/pinpointer/nuke/process() - ..() - if(active) // If shit's going down - for(var/obj/machinery/nuclearbomb/bomb in GLOB.nuke_list) - if(bomb.timing) - if(!alert) - alert = TRUE - playsound(src, 'sound/items/nuke_toy_lowpower.ogg', 50, FALSE) - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "Your [name] vibrates and lets out a tinny alarm. Uh oh.") - -/obj/item/pinpointer/nuke/scan_for_target() - target = null - switch(mode) - if(TRACK_NUKE_DISK) - var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list - target = N - if(TRACK_MALF_AI) - for(var/V in GLOB.ai_list) - var/mob/living/silicon/ai/A = V - if(A.nuking) - target = A - for(var/V in GLOB.apcs_list) - var/obj/machinery/power/apc/A = V - if(A.malfhack && A.occupier) - target = A - if(TRACK_INFILTRATOR) - target = SSshuttle.getShuttle("syndicate") - ..() - -/obj/item/pinpointer/nuke/proc/switch_mode_to(new_mode) - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "Your [name] beeps as it reconfigures it's tracking algorithms.") - playsound(L, 'sound/machines/triple_beep.ogg', 50, TRUE) - mode = new_mode - scan_for_target() - -/obj/item/pinpointer/nuke/syndicate // Syndicate pinpointers automatically point towards the infiltrator once the nuke is active. - name = "syndicate pinpointer" - desc = "A handheld tracking device that locks onto certain signals. It's configured to switch tracking modes once it detects the activation signal of a nuclear device." - icon_state = "pinpointer_syndicate" - -/obj/item/pinpointer/syndicate_cyborg // Cyborg pinpointers just look for a random operative. - name = "cyborg syndicate pinpointer" - desc = "An integrated tracking device, jury-rigged to search for living Syndicate operatives." - flags_1 = NONE - -/obj/item/pinpointer/syndicate_cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/pinpointer/syndicate_cyborg/cyborg_unequip(mob/user) - if(!active) - return - toggle_on() - -/obj/item/pinpointer/syndicate_cyborg/scan_for_target() - target = null - var/list/possible_targets = list() - var/turf/here = get_turf(src) - for(var/V in get_antag_minds(/datum/antagonist/nukeop)) - var/datum/mind/M = V - if(ishuman(M.current) && M.current.stat != DEAD) - possible_targets |= M.current - var/mob/living/closest_operative = get_closest_atom(/mob/living/carbon/human, possible_targets, here) - if(closest_operative) - target = closest_operative - ..() +/obj/item/pinpointer/nuke + var/mode = TRACK_NUKE_DISK + +/obj/item/pinpointer/nuke/examine(mob/user) + . = ..() + var/msg = "Its tracking indicator reads " + switch(mode) + if(TRACK_NUKE_DISK) + msg += "\"nuclear_disk\"." + if(TRACK_MALF_AI) + msg += "\"01000001 01001001\"." + if(TRACK_INFILTRATOR) + msg += "\"vasvygengbefuvc\"." + else + msg = "Its tracking indicator is blank." + . += msg + for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) + if(bomb.timing) + . += "Extreme danger. Arming signal detected. Time remaining: [bomb.get_time_left()]." + +/obj/item/pinpointer/nuke/process() + ..() + if(active) // If shit's going down + for(var/obj/machinery/nuclearbomb/bomb in GLOB.nuke_list) + if(bomb.timing) + if(!alert) + alert = TRUE + playsound(src, 'sound/items/nuke_toy_lowpower.ogg', 50, FALSE) + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "Your [name] vibrates and lets out a tinny alarm. Uh oh.") + +/obj/item/pinpointer/nuke/scan_for_target() + target = null + switch(mode) + if(TRACK_NUKE_DISK) + var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list + target = N + if(TRACK_MALF_AI) + for(var/V in GLOB.ai_list) + var/mob/living/silicon/ai/A = V + if(A.nuking) + target = A + for(var/V in GLOB.apcs_list) + var/obj/machinery/power/apc/A = V + if(A.malfhack && A.occupier) + target = A + if(TRACK_INFILTRATOR) + target = SSshuttle.getShuttle("syndicate") + ..() + +/obj/item/pinpointer/nuke/proc/switch_mode_to(new_mode) + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "Your [name] beeps as it reconfigures it's tracking algorithms.") + playsound(L, 'sound/machines/triple_beep.ogg', 50, TRUE) + mode = new_mode + scan_for_target() + +/obj/item/pinpointer/nuke/syndicate // Syndicate pinpointers automatically point towards the infiltrator once the nuke is active. + name = "syndicate pinpointer" + desc = "A handheld tracking device that locks onto certain signals. It's configured to switch tracking modes once it detects the activation signal of a nuclear device." + icon_state = "pinpointer_syndicate" + +/obj/item/pinpointer/syndicate_cyborg // Cyborg pinpointers just look for a random operative. + name = "cyborg syndicate pinpointer" + desc = "An integrated tracking device, jury-rigged to search for living Syndicate operatives." + flags_1 = NONE + +/obj/item/pinpointer/syndicate_cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/pinpointer/syndicate_cyborg/cyborg_unequip(mob/user) + if(!active) + return + toggle_on() + +/obj/item/pinpointer/syndicate_cyborg/scan_for_target() + target = null + var/list/possible_targets = list() + var/turf/here = get_turf(src) + for(var/V in get_antag_minds(/datum/antagonist/nukeop)) + var/datum/mind/M = V + if(ishuman(M.current) && M.current.stat != DEAD) + possible_targets |= M.current + var/mob/living/closest_operative = get_closest_atom(/mob/living/carbon/human, possible_targets, here) + if(closest_operative) + target = closest_operative + ..() diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index 7a25fb55118..585ec34c4a9 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -1,477 +1,477 @@ - -//Apprenticeship contract - moved to antag_spawner.dm - -///////////////////////////Veil Render////////////////////// - -/obj/item/veilrender - name = "veil render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast city." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - inhand_icon_state = "knife" - lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' - force = 15 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - hitsound = 'sound/weapons/bladeslice.ogg' - var/charges = 1 - var/spawn_type = /obj/singularity/wizard - var/spawn_amt = 1 - var/activate_descriptor = "reality" - var/rend_desc = "You should run now." - var/spawn_fast = 0 //if 1, ignores checking for mobs on loc before spawning - -/obj/item/veilrender/attack_self(mob/user) - if(charges > 0) - new /obj/effect/rend(get_turf(user), spawn_type, spawn_amt, rend_desc, spawn_fast) - charges-- - user.visible_message("[src] hums with power as [user] deals a blow to [activate_descriptor] itself!") - else - to_chat(user, "The unearthly energies that powered the blade are now dormant.") - -/obj/effect/rend - name = "tear in the fabric of reality" - desc = "You should run now." - icon = 'icons/effects/effects.dmi' - icon_state = "rift" - density = TRUE - anchored = TRUE - var/spawn_path = /mob/living/simple_animal/cow //defaulty cows to prevent unintentional narsies - var/spawn_amt_left = 20 - var/spawn_fast = 0 - -/obj/effect/rend/New(loc, spawn_type, spawn_amt, desc, spawn_fast) - src.spawn_path = spawn_type - src.spawn_amt_left = spawn_amt - src.desc = desc - src.spawn_fast = spawn_fast - START_PROCESSING(SSobj, src) - return - -/obj/effect/rend/process() - if(!spawn_fast) - if(locate(/mob) in loc) - return - new spawn_path(loc) - spawn_amt_left-- - if(spawn_amt_left <= 0) - qdel(src) - -/obj/effect/rend/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/nullrod)) - user.visible_message("[user] seals \the [src] with \the [I].") - qdel(src) - return - else - return ..() - -/obj/effect/rend/singularity_act() - return - -/obj/effect/rend/singularity_pull() - return - -/obj/item/veilrender/vealrender - name = "veal render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast farm." - spawn_type = /mob/living/simple_animal/cow - spawn_amt = 20 - activate_descriptor = "hunger" - rend_desc = "Reverberates with the sound of ten thousand moos." - -/obj/item/veilrender/honkrender - name = "honk render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus." - spawn_type = /mob/living/simple_animal/hostile/retaliate/clown - spawn_amt = 10 - activate_descriptor = "depression" - rend_desc = "Gently wafting with the sounds of endless laughter." - icon_state = "clownrender" - -/obj/item/veilrender/honkrender/honkhulkrender - name = "superior honk render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus. This one gleams with a special light." - spawn_type = /mob/living/simple_animal/hostile/retaliate/clown/clownhulk - spawn_amt = 5 - activate_descriptor = "depression" - rend_desc = "Gently wafting with the sounds of mirthful grunting." - icon_state = "clownrender" - -////TEAR IN REALITY - -/obj/singularity/wizard - name = "tear in the fabric of reality" - desc = "This isn't right." - icon = 'icons/effects/224x224.dmi' - icon_state = "reality" - pixel_x = -96 - pixel_y = -96 - dissipate = 0 - move_self = 0 - consume_range = 3 - grav_pull = 4 - current_size = STAGE_FOUR - allowed_size = STAGE_FOUR - -/obj/singularity/wizard/process() - move() - eat() - return - -/obj/singularity/wizard/attack_tk(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood) - if(insaneinthemembrane.sanity < 15) - return //they've already seen it and are about to die, or are just too insane to care - to_chat(C, "OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!") - insaneinthemembrane.sanity = 0 - for(var/lore in typesof(/datum/brain_trauma/severe)) - C.gain_trauma(lore) - addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100) - -/obj/singularity/wizard/proc/deranged(mob/living/carbon/C) - if(!C || C.stat == DEAD) - return - C.vomit(0, TRUE, TRUE, 3, TRUE) - C.spew_organ(3, 2) - C.death() - -/obj/singularity/wizard/mapped/admin_investigate_setup() - return - -/////////////////////////////////////////Scrying/////////////////// - -/obj/item/scrying - name = "scrying orb" - desc = "An incandescent orb of otherworldly energy, merely holding it gives you vision and hearing beyond mortal means, and staring into it lets you see the entire universe." - icon = 'icons/obj/projectiles.dmi' - icon_state ="bluespace" - throw_speed = 3 - throw_range = 7 - throwforce = 15 - damtype = BURN - force = 15 - hitsound = 'sound/items/welder2.ogg' - - var/mob/current_owner - -/obj/item/scrying/Initialize(mapload) - . = ..() - START_PROCESSING(SSobj, src) - -/obj/item/scrying/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/scrying/process() - var/mob/holder = get(loc, /mob) - if(current_owner && current_owner != holder) - - to_chat(current_owner, "Your otherworldly vision fades...") - - REMOVE_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) - REMOVE_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) - current_owner.update_sight() - - current_owner = null - - if(!current_owner && holder) - current_owner = holder - - to_chat(current_owner, "You can see...everything!") - - ADD_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) - ADD_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) - current_owner.update_sight() - -/obj/item/scrying/attack_self(mob/user) - visible_message("[user] stares into [src], their eyes glazing over.") - user.ghostize(1) - -/////////////////////////////////////////Necromantic Stone/////////////////// - -/obj/item/necromantic_stone - name = "necromantic stone" - desc = "A shard capable of resurrecting humans as skeleton thralls." - icon = 'icons/obj/wizard.dmi' - icon_state = "necrostone" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - var/list/spooky_scaries = list() - var/unlimited = 0 - -/obj/item/necromantic_stone/unlimited - unlimited = 1 - -/obj/item/necromantic_stone/attack(mob/living/carbon/human/M, mob/living/carbon/human/user) - if(!istype(M)) - return ..() - - if(!istype(user) || !user.canUseTopic(M, BE_CLOSE)) - return - - if(M.stat != DEAD) - to_chat(user, "This artifact can only affect the dead!") - return - - for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) //excludes new players - if(ghost.mind && ghost.mind.current == M && ghost.client) //the dead mobs list can contain clientless mobs - ghost.reenter_corpse() - break - - if(!M.mind || !M.client) - to_chat(user, "There is no soul connected to this body...") - return - - check_spooky()//clean out/refresh the list - if(spooky_scaries.len >= 3 && !unlimited) - to_chat(user, "This artifact can only affect three undead at a time!") - return - - M.set_species(/datum/species/skeleton, icon_update=0) - M.revive(full_heal = TRUE, admin_revive = TRUE) - spooky_scaries |= M - to_chat(M, "You have been revived by [user.real_name]!") - to_chat(M, "[user.p_theyre(TRUE)] your master now, assist [user.p_them()] even if it costs you your new life!") - - equip_roman_skeleton(M) - - desc = "A shard capable of resurrecting humans as skeleton thralls[unlimited ? "." : ", [spooky_scaries.len]/3 active thralls."]" - -/obj/item/necromantic_stone/proc/check_spooky() - if(unlimited) //no point, the list isn't used. - return - - for(var/X in spooky_scaries) - if(!ishuman(X)) - spooky_scaries.Remove(X) - continue - var/mob/living/carbon/human/H = X - if(H.stat == DEAD) - H.dust(TRUE) - spooky_scaries.Remove(X) - continue - listclearnulls(spooky_scaries) - -//Funny gimmick, skeletons always seem to wear roman/ancient armour -/obj/item/necromantic_stone/proc/equip_roman_skeleton(mob/living/carbon/human/H) - for(var/obj/item/I in H) - H.dropItemToGround(I) - - var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionnaire) - H.equip_to_slot_or_del(new hat(H), ITEM_SLOT_HEAD) - H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(H), ITEM_SLOT_ICLOTHING) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), ITEM_SLOT_FEET) - H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE) - H.put_in_hands(new /obj/item/claymore(H), TRUE) - H.equip_to_slot_or_del(new /obj/item/spear(H), ITEM_SLOT_BACK) - - -/obj/item/voodoo - name = "wicker doll" - desc = "Something creepy about it." - icon = 'icons/obj/wizard.dmi' - icon_state = "voodoo" - inhand_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - var/mob/living/carbon/human/target = null - var/list/mob/living/carbon/human/possible = list() - var/obj/item/voodoo_link = null - var/cooldown_time = 30 //3s - var/cooldown = 0 - max_integrity = 10 - resistance_flags = FLAMMABLE - -/obj/item/voodoo/attackby(obj/item/I, mob/user, params) - if(target && cooldown < world.time) - if(I.get_temperature()) - to_chat(target, "You suddenly feel very hot!") - target.adjust_bodytemperature(50) - GiveHint(target) - else if(is_pointed(I)) - to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!") - target.Paralyze(40) - GiveHint(target) - else if(istype(I, /obj/item/bikehorn)) - to_chat(target, "HONK") - SEND_SOUND(target, 'sound/items/airhorn.ogg') - var/obj/item/organ/ears/ears = user.getorganslot(ORGAN_SLOT_EARS) - if(ears) - ears.adjustEarDamage(0, 3) - GiveHint(target) - cooldown = world.time +cooldown_time - return - - if(!voodoo_link) - if(I.loc == user && istype(I) && I.w_class <= WEIGHT_CLASS_SMALL) - if (user.transferItemToLoc(I,src)) - voodoo_link = I - to_chat(user, "You attach [I] to the doll.") - update_targets() - -/obj/item/voodoo/check_eye(mob/user) - if(loc != user) - user.reset_perspective(null) - user.unset_machine() - -/obj/item/voodoo/attack_self(mob/user) - if(!target && possible.len) - target = input(user, "Select your victim!", "Voodoo") as null|anything in sortNames(possible) - return - - if(user.zone_selected == BODY_ZONE_CHEST) - if(voodoo_link) - target = null - voodoo_link.forceMove(drop_location()) - to_chat(user, "You remove the [voodoo_link] from the doll.") - voodoo_link = null - update_targets() - return - - if(target && cooldown < world.time) - switch(user.zone_selected) - if(BODY_ZONE_PRECISE_MOUTH) - var/wgw = sanitize(input(user, "What would you like the victim to say", "Voodoo", null) as text) - target.say(wgw, forced = "voodoo doll") - log_game("[key_name(user)] made [key_name(target)] say [wgw] with a voodoo doll.") - if(BODY_ZONE_PRECISE_EYES) - user.set_machine(src) - user.reset_perspective(target) - addtimer(CALLBACK(src, .proc/reset, user), 10 SECONDS) - if(BODY_ZONE_R_LEG,BODY_ZONE_L_LEG) - to_chat(user, "You move the doll's legs around.") - var/turf/T = get_step(target,pick(GLOB.cardinals)) - target.Move(T) - if(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM) - target.click_random_mob() - GiveHint(target) - if(BODY_ZONE_HEAD) - to_chat(user, "You smack the doll's head with your hand.") - target.Dizzy(10) - to_chat(target, "You suddenly feel as if your head was hit with a hammer!") - GiveHint(target,user) - cooldown = world.time + cooldown_time - -/obj/item/voodoo/proc/reset(mob/user) - if(QDELETED(user)) - return - user.reset_perspective(null) - user.unset_machine() - -/obj/item/voodoo/proc/update_targets() - possible = list() - if(!voodoo_link) - return - var/list/prints = voodoo_link.return_fingerprints() - if(!length(prints)) - return FALSE - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(prints[md5(H.dna.uni_identity)]) - possible |= H - -/obj/item/voodoo/proc/GiveHint(mob/victim,force=0) - if(prob(50) || force) - var/way = dir2text(get_dir(victim,get_turf(src))) - to_chat(victim, "You feel a dark presence from [way].") - if(prob(20) || force) - var/area/A = get_area(src) - to_chat(victim, "You feel a dark presence from [A.name].") - -/obj/item/voodoo/suicide_act(mob/living/carbon/user) - user.visible_message("[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!") - user.gib() - return(BRUTELOSS) - -/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume) - if(target) - target.adjust_fire_stacks(20) - target.IgniteMob() - GiveHint(target,1) - return ..() - -//Provides a decent heal, need to pump every 6 seconds -/obj/item/organ/heart/cursed/wizard - pump_delay = 60 - heal_brute = 25 - heal_burn = 25 - heal_oxy = 25 - -//Warp Whistle: Provides uncontrolled long distance teleportation. - -/obj/item/warpwhistle - name = "warp whistle" - desc = "One toot on this whistle will send you to a far away land!" - icon = 'icons/obj/wizard.dmi' - icon_state = "whistle" - var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown - var/mob/living/carbon/last_user - -/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user) - if(!user || QDELETED(src) || user.notransform) - on_cooldown = FALSE - return TRUE - return FALSE - -/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user) - user.invisibility = initial(user.invisibility) - user.status_flags &= ~GODMODE - user.update_mobility() - -/obj/item/warpwhistle/attack_self(mob/living/carbon/user) - if(!istype(user) || on_cooldown) - return - on_cooldown = TRUE - last_user = user - var/turf/T = get_turf(user) - playsound(T,'sound/magic/warpwhistle.ogg', 200, TRUE) - user.mobility_flags &= ~MOBILITY_MOVE - new /obj/effect/temp_visual/tornado(T) - sleep(20) - if(interrupted(user)) - return - user.invisibility = INVISIBILITY_MAXIMUM - user.status_flags |= GODMODE - sleep(20) - if(interrupted(user)) - end_effect(user) - return - var/breakout = 0 - while(breakout < 50) - var/turf/potential_T = find_safe_turf() - if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout) - do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC) - user.mobility_flags &= ~MOBILITY_MOVE - T = potential_T - break - breakout += 1 - new /obj/effect/temp_visual/tornado(T) - sleep(20) - end_effect(user) - if(interrupted(user)) - return - on_cooldown = 2 - addtimer(VARSET_CALLBACK(src, on_cooldown, 0), 4 SECONDS) - -/obj/item/warpwhistle/Destroy() - if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport - end_effect(last_user) - return ..() - -/obj/effect/temp_visual/tornado - icon = 'icons/obj/wizard.dmi' - icon_state = "tornado" - name = "tornado" - desc = "This thing sucks!" - layer = FLY_LAYER - randomdir = 0 - duration = 40 - pixel_x = 500 - -/obj/effect/temp_visual/tornado/Initialize() - . = ..() - animate(src, pixel_x = -500, time = 40) + +//Apprenticeship contract - moved to antag_spawner.dm + +///////////////////////////Veil Render////////////////////// + +/obj/item/veilrender + name = "veil render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast city." + icon = 'icons/obj/wizard.dmi' + icon_state = "render" + inhand_icon_state = "knife" + lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' + force = 15 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + hitsound = 'sound/weapons/bladeslice.ogg' + var/charges = 1 + var/spawn_type = /obj/singularity/wizard + var/spawn_amt = 1 + var/activate_descriptor = "reality" + var/rend_desc = "You should run now." + var/spawn_fast = 0 //if 1, ignores checking for mobs on loc before spawning + +/obj/item/veilrender/attack_self(mob/user) + if(charges > 0) + new /obj/effect/rend(get_turf(user), spawn_type, spawn_amt, rend_desc, spawn_fast) + charges-- + user.visible_message("[src] hums with power as [user] deals a blow to [activate_descriptor] itself!") + else + to_chat(user, "The unearthly energies that powered the blade are now dormant.") + +/obj/effect/rend + name = "tear in the fabric of reality" + desc = "You should run now." + icon = 'icons/effects/effects.dmi' + icon_state = "rift" + density = TRUE + anchored = TRUE + var/spawn_path = /mob/living/simple_animal/cow //defaulty cows to prevent unintentional narsies + var/spawn_amt_left = 20 + var/spawn_fast = 0 + +/obj/effect/rend/New(loc, spawn_type, spawn_amt, desc, spawn_fast) + src.spawn_path = spawn_type + src.spawn_amt_left = spawn_amt + src.desc = desc + src.spawn_fast = spawn_fast + START_PROCESSING(SSobj, src) + return + +/obj/effect/rend/process() + if(!spawn_fast) + if(locate(/mob) in loc) + return + new spawn_path(loc) + spawn_amt_left-- + if(spawn_amt_left <= 0) + qdel(src) + +/obj/effect/rend/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/nullrod)) + user.visible_message("[user] seals \the [src] with \the [I].") + qdel(src) + return + else + return ..() + +/obj/effect/rend/singularity_act() + return + +/obj/effect/rend/singularity_pull() + return + +/obj/item/veilrender/vealrender + name = "veal render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast farm." + spawn_type = /mob/living/simple_animal/cow + spawn_amt = 20 + activate_descriptor = "hunger" + rend_desc = "Reverberates with the sound of ten thousand moos." + +/obj/item/veilrender/honkrender + name = "honk render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus." + spawn_type = /mob/living/simple_animal/hostile/retaliate/clown + spawn_amt = 10 + activate_descriptor = "depression" + rend_desc = "Gently wafting with the sounds of endless laughter." + icon_state = "clownrender" + +/obj/item/veilrender/honkrender/honkhulkrender + name = "superior honk render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus. This one gleams with a special light." + spawn_type = /mob/living/simple_animal/hostile/retaliate/clown/clownhulk + spawn_amt = 5 + activate_descriptor = "depression" + rend_desc = "Gently wafting with the sounds of mirthful grunting." + icon_state = "clownrender" + +////TEAR IN REALITY + +/obj/singularity/wizard + name = "tear in the fabric of reality" + desc = "This isn't right." + icon = 'icons/effects/224x224.dmi' + icon_state = "reality" + pixel_x = -96 + pixel_y = -96 + dissipate = 0 + move_self = 0 + consume_range = 3 + grav_pull = 4 + current_size = STAGE_FOUR + allowed_size = STAGE_FOUR + +/obj/singularity/wizard/process() + move() + eat() + return + +/obj/singularity/wizard/attack_tk(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood) + if(insaneinthemembrane.sanity < 15) + return //they've already seen it and are about to die, or are just too insane to care + to_chat(C, "OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!") + insaneinthemembrane.sanity = 0 + for(var/lore in typesof(/datum/brain_trauma/severe)) + C.gain_trauma(lore) + addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100) + +/obj/singularity/wizard/proc/deranged(mob/living/carbon/C) + if(!C || C.stat == DEAD) + return + C.vomit(0, TRUE, TRUE, 3, TRUE) + C.spew_organ(3, 2) + C.death() + +/obj/singularity/wizard/mapped/admin_investigate_setup() + return + +/////////////////////////////////////////Scrying/////////////////// + +/obj/item/scrying + name = "scrying orb" + desc = "An incandescent orb of otherworldly energy, merely holding it gives you vision and hearing beyond mortal means, and staring into it lets you see the entire universe." + icon = 'icons/obj/projectiles.dmi' + icon_state ="bluespace" + throw_speed = 3 + throw_range = 7 + throwforce = 15 + damtype = BURN + force = 15 + hitsound = 'sound/items/welder2.ogg' + + var/mob/current_owner + +/obj/item/scrying/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/scrying/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/scrying/process() + var/mob/holder = get(loc, /mob) + if(current_owner && current_owner != holder) + + to_chat(current_owner, "Your otherworldly vision fades...") + + REMOVE_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) + REMOVE_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) + current_owner.update_sight() + + current_owner = null + + if(!current_owner && holder) + current_owner = holder + + to_chat(current_owner, "You can see...everything!") + + ADD_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) + ADD_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) + current_owner.update_sight() + +/obj/item/scrying/attack_self(mob/user) + visible_message("[user] stares into [src], their eyes glazing over.") + user.ghostize(1) + +/////////////////////////////////////////Necromantic Stone/////////////////// + +/obj/item/necromantic_stone + name = "necromantic stone" + desc = "A shard capable of resurrecting humans as skeleton thralls." + icon = 'icons/obj/wizard.dmi' + icon_state = "necrostone" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + var/list/spooky_scaries = list() + var/unlimited = 0 + +/obj/item/necromantic_stone/unlimited + unlimited = 1 + +/obj/item/necromantic_stone/attack(mob/living/carbon/human/M, mob/living/carbon/human/user) + if(!istype(M)) + return ..() + + if(!istype(user) || !user.canUseTopic(M, BE_CLOSE)) + return + + if(M.stat != DEAD) + to_chat(user, "This artifact can only affect the dead!") + return + + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) //excludes new players + if(ghost.mind && ghost.mind.current == M && ghost.client) //the dead mobs list can contain clientless mobs + ghost.reenter_corpse() + break + + if(!M.mind || !M.client) + to_chat(user, "There is no soul connected to this body...") + return + + check_spooky()//clean out/refresh the list + if(spooky_scaries.len >= 3 && !unlimited) + to_chat(user, "This artifact can only affect three undead at a time!") + return + + M.set_species(/datum/species/skeleton, icon_update=0) + M.revive(full_heal = TRUE, admin_revive = TRUE) + spooky_scaries |= M + to_chat(M, "You have been revived by [user.real_name]!") + to_chat(M, "[user.p_theyre(TRUE)] your master now, assist [user.p_them()] even if it costs you your new life!") + + equip_roman_skeleton(M) + + desc = "A shard capable of resurrecting humans as skeleton thralls[unlimited ? "." : ", [spooky_scaries.len]/3 active thralls."]" + +/obj/item/necromantic_stone/proc/check_spooky() + if(unlimited) //no point, the list isn't used. + return + + for(var/X in spooky_scaries) + if(!ishuman(X)) + spooky_scaries.Remove(X) + continue + var/mob/living/carbon/human/H = X + if(H.stat == DEAD) + H.dust(TRUE) + spooky_scaries.Remove(X) + continue + listclearnulls(spooky_scaries) + +//Funny gimmick, skeletons always seem to wear roman/ancient armour +/obj/item/necromantic_stone/proc/equip_roman_skeleton(mob/living/carbon/human/H) + for(var/obj/item/I in H) + H.dropItemToGround(I) + + var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionnaire) + H.equip_to_slot_or_del(new hat(H), ITEM_SLOT_HEAD) + H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(H), ITEM_SLOT_ICLOTHING) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), ITEM_SLOT_FEET) + H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE) + H.put_in_hands(new /obj/item/claymore(H), TRUE) + H.equip_to_slot_or_del(new /obj/item/spear(H), ITEM_SLOT_BACK) + + +/obj/item/voodoo + name = "wicker doll" + desc = "Something creepy about it." + icon = 'icons/obj/wizard.dmi' + icon_state = "voodoo" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + var/mob/living/carbon/human/target = null + var/list/mob/living/carbon/human/possible = list() + var/obj/item/voodoo_link = null + var/cooldown_time = 30 //3s + var/cooldown = 0 + max_integrity = 10 + resistance_flags = FLAMMABLE + +/obj/item/voodoo/attackby(obj/item/I, mob/user, params) + if(target && cooldown < world.time) + if(I.get_temperature()) + to_chat(target, "You suddenly feel very hot!") + target.adjust_bodytemperature(50) + GiveHint(target) + else if(is_pointed(I)) + to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!") + target.Paralyze(40) + GiveHint(target) + else if(istype(I, /obj/item/bikehorn)) + to_chat(target, "HONK") + SEND_SOUND(target, 'sound/items/airhorn.ogg') + var/obj/item/organ/ears/ears = user.getorganslot(ORGAN_SLOT_EARS) + if(ears) + ears.adjustEarDamage(0, 3) + GiveHint(target) + cooldown = world.time +cooldown_time + return + + if(!voodoo_link) + if(I.loc == user && istype(I) && I.w_class <= WEIGHT_CLASS_SMALL) + if (user.transferItemToLoc(I,src)) + voodoo_link = I + to_chat(user, "You attach [I] to the doll.") + update_targets() + +/obj/item/voodoo/check_eye(mob/user) + if(loc != user) + user.reset_perspective(null) + user.unset_machine() + +/obj/item/voodoo/attack_self(mob/user) + if(!target && possible.len) + target = input(user, "Select your victim!", "Voodoo") as null|anything in sortNames(possible) + return + + if(user.zone_selected == BODY_ZONE_CHEST) + if(voodoo_link) + target = null + voodoo_link.forceMove(drop_location()) + to_chat(user, "You remove the [voodoo_link] from the doll.") + voodoo_link = null + update_targets() + return + + if(target && cooldown < world.time) + switch(user.zone_selected) + if(BODY_ZONE_PRECISE_MOUTH) + var/wgw = sanitize(input(user, "What would you like the victim to say", "Voodoo", null) as text) + target.say(wgw, forced = "voodoo doll") + log_game("[key_name(user)] made [key_name(target)] say [wgw] with a voodoo doll.") + if(BODY_ZONE_PRECISE_EYES) + user.set_machine(src) + user.reset_perspective(target) + addtimer(CALLBACK(src, .proc/reset, user), 10 SECONDS) + if(BODY_ZONE_R_LEG,BODY_ZONE_L_LEG) + to_chat(user, "You move the doll's legs around.") + var/turf/T = get_step(target,pick(GLOB.cardinals)) + target.Move(T) + if(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM) + target.click_random_mob() + GiveHint(target) + if(BODY_ZONE_HEAD) + to_chat(user, "You smack the doll's head with your hand.") + target.Dizzy(10) + to_chat(target, "You suddenly feel as if your head was hit with a hammer!") + GiveHint(target,user) + cooldown = world.time + cooldown_time + +/obj/item/voodoo/proc/reset(mob/user) + if(QDELETED(user)) + return + user.reset_perspective(null) + user.unset_machine() + +/obj/item/voodoo/proc/update_targets() + possible = list() + if(!voodoo_link) + return + var/list/prints = voodoo_link.return_fingerprints() + if(!length(prints)) + return FALSE + for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) + if(prints[md5(H.dna.uni_identity)]) + possible |= H + +/obj/item/voodoo/proc/GiveHint(mob/victim,force=0) + if(prob(50) || force) + var/way = dir2text(get_dir(victim,get_turf(src))) + to_chat(victim, "You feel a dark presence from [way].") + if(prob(20) || force) + var/area/A = get_area(src) + to_chat(victim, "You feel a dark presence from [A.name].") + +/obj/item/voodoo/suicide_act(mob/living/carbon/user) + user.visible_message("[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!") + user.gib() + return(BRUTELOSS) + +/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume) + if(target) + target.adjust_fire_stacks(20) + target.IgniteMob() + GiveHint(target,1) + return ..() + +//Provides a decent heal, need to pump every 6 seconds +/obj/item/organ/heart/cursed/wizard + pump_delay = 60 + heal_brute = 25 + heal_burn = 25 + heal_oxy = 25 + +//Warp Whistle: Provides uncontrolled long distance teleportation. + +/obj/item/warpwhistle + name = "warp whistle" + desc = "One toot on this whistle will send you to a far away land!" + icon = 'icons/obj/wizard.dmi' + icon_state = "whistle" + var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown + var/mob/living/carbon/last_user + +/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user) + if(!user || QDELETED(src) || user.notransform) + on_cooldown = FALSE + return TRUE + return FALSE + +/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user) + user.invisibility = initial(user.invisibility) + user.status_flags &= ~GODMODE + user.update_mobility() + +/obj/item/warpwhistle/attack_self(mob/living/carbon/user) + if(!istype(user) || on_cooldown) + return + on_cooldown = TRUE + last_user = user + var/turf/T = get_turf(user) + playsound(T,'sound/magic/warpwhistle.ogg', 200, TRUE) + user.mobility_flags &= ~MOBILITY_MOVE + new /obj/effect/temp_visual/tornado(T) + sleep(20) + if(interrupted(user)) + return + user.invisibility = INVISIBILITY_MAXIMUM + user.status_flags |= GODMODE + sleep(20) + if(interrupted(user)) + end_effect(user) + return + var/breakout = 0 + while(breakout < 50) + var/turf/potential_T = find_safe_turf() + if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout) + do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC) + user.mobility_flags &= ~MOBILITY_MOVE + T = potential_T + break + breakout += 1 + new /obj/effect/temp_visual/tornado(T) + sleep(20) + end_effect(user) + if(interrupted(user)) + return + on_cooldown = 2 + addtimer(VARSET_CALLBACK(src, on_cooldown, 0), 4 SECONDS) + +/obj/item/warpwhistle/Destroy() + if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport + end_effect(last_user) + return ..() + +/obj/effect/temp_visual/tornado + icon = 'icons/obj/wizard.dmi' + icon_state = "tornado" + name = "tornado" + desc = "This thing sucks!" + layer = FLY_LAYER + randomdir = 0 + duration = 40 + pixel_x = 500 + +/obj/effect/temp_visual/tornado/Initialize() + . = ..() + animate(src, pixel_x = -500, time = 40) diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm index 9a1ff11e582..b3e9714f5cb 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook.dm @@ -1,769 +1,769 @@ -/datum/spellbook_entry - var/name = "Entry Name" - - var/spell_type = null - var/desc = "" - var/category = "Offensive" - var/cost = 2 - var/refundable = TRUE - var/surplus = -1 // -1 for infinite, not used by anything atm - var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell - var/buy_word = "Learn" - var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook - var/list/no_coexistance_typecache //Used so you can't have specific spells together - -/datum/spellbook_entry/New() - ..() - no_coexistance_typecache = typecacheof(no_coexistance_typecache) - -/datum/spellbook_entry/proc/IsAvailible() // For config prefs / gamemode restrictions - these are round applied - return TRUE - -/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances - if(book.uses= aspell.level_max) - to_chat(user, "This spell cannot be improved further!") - return FALSE - else - aspell.name = initial(aspell.name) - aspell.spell_level++ - aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) - if(aspell.charge_max < aspell.charge_counter) - aspell.charge_counter = aspell.charge_max - switch(aspell.spell_level) - if(1) - to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") - aspell.name = "Efficient [aspell.name]" - if(2) - to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") - aspell.name = "Quickened [aspell.name]" - if(3) - to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") - aspell.name = "Free [aspell.name]" - if(4) - to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") - aspell.name = "Instant [aspell.name]" - if(aspell.spell_level >= aspell.level_max) - to_chat(user, "This spell cannot be strengthened any further!") - SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) - return TRUE - //No same spell found - just learn it - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - user.mind.AddSpell(S) - to_chat(user, "You have learned [S.name].") - return TRUE - -/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) - if(!refundable) - return FALSE - if(!S) - S = new spell_type() - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - return TRUE - return FALSE - -/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure - var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station] - if(!(user in A.contents)) - to_chat(user, "You can only refund spells at the wizard lair!") - return -1 - if(!S) - S = new spell_type() - var/spell_levels = 0 - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - spell_levels = aspell.spell_level - user.mind.spell_list.Remove(aspell) - qdel(S) - return cost * (spell_levels+1) - return -1 -/datum/spellbook_entry/proc/GetInfo() - if(!S) - S = new spell_type() - var/dat ="" - dat += "[initial(S.name)]" - if(S.charge_type == "recharge") - dat += " Cooldown:[S.charge_max/10]" - dat += " Cost:[cost]
                " - dat += "[S.desc][desc]
                " - dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
                " - return dat - -/datum/spellbook_entry/fireball - name = "Fireball" - spell_type = /obj/effect/proc_holder/spell/aimed/fireball - -/datum/spellbook_entry/spell_cards - name = "Spell Cards" - spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards - -/datum/spellbook_entry/rod_form - name = "Rod Form" - spell_type = /obj/effect/proc_holder/spell/targeted/rod_form - -/datum/spellbook_entry/magicm - name = "Magic Missile" - spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - category = "Defensive" - -/datum/spellbook_entry/disintegrate - name = "Smite" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate - -/datum/spellbook_entry/disabletech - name = "Disable Tech" - spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/repulse - name = "Repulse" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse - category = "Defensive" - -/datum/spellbook_entry/lightning_packet - name = "Thrown Lightning" - spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket - category = "Defensive" - -/datum/spellbook_entry/timestop - name = "Time Stop" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/timestop - category = "Defensive" - -/datum/spellbook_entry/smoke - name = "Smoke" - spell_type = /obj/effect/proc_holder/spell/targeted/smoke - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blind - name = "Blind" - spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind - cost = 1 - -/datum/spellbook_entry/mindswap - name = "Mindswap" - spell_type = /obj/effect/proc_holder/spell/pointed/mind_transfer - category = "Mobility" - -/datum/spellbook_entry/forcewall - name = "Force Wall" - spell_type = /obj/effect/proc_holder/spell/targeted/forcewall - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blink - name = "Blink" - spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - category = "Mobility" - -/datum/spellbook_entry/teleport - name = "Teleport" - spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport - category = "Mobility" - -/datum/spellbook_entry/mutate - name = "Mutate" - spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate - -/datum/spellbook_entry/jaunt - name = "Ethereal Jaunt" - spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt - category = "Mobility" - -/datum/spellbook_entry/knock - name = "Knock" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock - category = "Mobility" - cost = 1 - -/datum/spellbook_entry/fleshtostone - name = "Flesh to Stone" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone - -/datum/spellbook_entry/summonitem - name = "Summon Item" - spell_type = /obj/effect/proc_holder/spell/targeted/summonitem - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/lichdom - name = "Bind Soul" - spell_type = /obj/effect/proc_holder/spell/targeted/lichdom - category = "Defensive" - -/datum/spellbook_entry/teslablast - name = "Tesla Blast" - spell_type = /obj/effect/proc_holder/spell/targeted/tesla - -/datum/spellbook_entry/lightningbolt - name = "Lightning Bolt" - spell_type = /obj/effect/proc_holder/spell/aimed/lightningbolt - cost = 1 - -/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return TRUE on success - . = ..() - ADD_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") - -/datum/spellbook_entry/lightningbolt/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) - . = ..() - REMOVE_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") - -/datum/spellbook_entry/infinite_guns - name = "Lesser Summon Guns" - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - cost = 3 - no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - -/datum/spellbook_entry/arcane_barrage - name = "Arcane Barrage" - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - cost = 3 - no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - -/datum/spellbook_entry/barnyard - name = "Barnyard Curse" - spell_type = /obj/effect/proc_holder/spell/pointed/barnyardcurse - -/datum/spellbook_entry/charge - name = "Charge" - spell_type = /obj/effect/proc_holder/spell/targeted/charge - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/shapeshift - name = "Wild Shapeshift" - spell_type = /obj/effect/proc_holder/spell/targeted/shapeshift - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/tap - name = "Soul Tap" - spell_type = /obj/effect/proc_holder/spell/self/tap - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/spacetime_dist - name = "Spacetime Distortion" - spell_type = /obj/effect/proc_holder/spell/spacetime_dist - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/the_traps - name = "The Traps!" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps - category = "Defensive" - cost = 1 - - -/datum/spellbook_entry/item - name = "Buy Item" - refundable = FALSE - buy_word = "Summon" - var/item_path= null - - -/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - new item_path(get_turf(user)) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - return TRUE - -/datum/spellbook_entry/item/GetInfo() - var/dat ="" - dat += "[name]" - dat += " Cost:[cost]
                " - dat += "[desc]
                " - if(surplus>=0) - dat += "[surplus] left.
                " - return dat - -/datum/spellbook_entry/item/staffchange - name = "Staff of Change" - desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." - item_path = /obj/item/gun/magic/staff/change - -/datum/spellbook_entry/item/staffanimation - name = "Staff of Animation" - desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." - item_path = /obj/item/gun/magic/staff/animate - category = "Assistance" - -/datum/spellbook_entry/item/staffchaos - name = "Staff of Chaos" - desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." - item_path = /obj/item/gun/magic/staff/chaos - -/datum/spellbook_entry/item/spellblade - name = "Spellblade" - desc = "A sword capable of firing blasts of energy which rip targets limb from limb." - item_path = /obj/item/gun/magic/staff/spellblade - -/datum/spellbook_entry/item/staffdoor - name = "Staff of Door Creation" - desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." - item_path = /obj/item/gun/magic/staff/door - cost = 1 - category = "Mobility" - -/datum/spellbook_entry/item/staffhealing - name = "Staff of Healing" - desc = "An altruistic staff that can heal the lame and raise the dead." - item_path = /obj/item/gun/magic/staff/healing - cost = 1 - category = "Defensive" - -/datum/spellbook_entry/item/lockerstaff - name = "Staff of the Locker" - desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." - item_path = /obj/item/gun/magic/staff/locker - category = "Defensive" - -/datum/spellbook_entry/item/scryingorb - name = "Scrying Orb" - desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." - item_path = /obj/item/scrying - category = "Defensive" - -/datum/spellbook_entry/item/soulstones - name = "Six Soul Stone Shards and the spell Artificer" - desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." - item_path = /obj/item/storage/belt/soulstone/full - category = "Assistance" - -/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . =..() - if(.) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) - return . - -/datum/spellbook_entry/item/necrostone - name = "A Necromantic Stone" - desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." - item_path = /obj/item/necromantic_stone - category = "Assistance" - -/datum/spellbook_entry/item/wands - name = "Wand Assortment" - desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." - item_path = /obj/item/storage/belt/wands/full - category = "Defensive" - -/datum/spellbook_entry/item/armor - name = "Mastercrafted Armor Set" - desc = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space." - item_path = /obj/item/clothing/suit/space/hardsuit/wizard - category = "Defensive" - -/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/tank/internals/oxygen(get_turf(user)) //i need to BREATHE - new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. - new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit - -/datum/spellbook_entry/item/contract - name = "Contract of Apprenticeship" - desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." - item_path = /obj/item/antag_spawner/contract - category = "Assistance" - -/datum/spellbook_entry/item/guardian - name = "Guardian Deck" - desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ - It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." - item_path = /obj/item/guardiancreator/choose/wizard - category = "Assistance" - -/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) - -/datum/spellbook_entry/item/bloodbottle - name = "Bottle of Blood" - desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." - item_path = /obj/item/antag_spawner/slaughter_demon - limit = 3 - category = "Assistance" - -/datum/spellbook_entry/item/hugbottle - name = "Bottle of Tickles" - desc = "A bottle of magically infused fun, the smell of which will \ - attract adorable extradimensional beings when broken. These beings \ - are similar to slaughter demons, but they do not permamently kill \ - their victims, instead putting them in an extradimensional hugspace, \ - to be released on the demon's death. Chaotic, but not ultimately \ - damaging. The crew's reaction to the other hand could be very \ - destructive." - item_path = /obj/item/antag_spawner/slaughter_demon/laughter - cost = 1 //non-destructive; it's just a jape, sibling! - limit = 3 - category = "Assistance" - -/datum/spellbook_entry/item/mjolnir - name = "Mjolnir" - desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." - item_path = /obj/item/mjollnir - -/datum/spellbook_entry/item/singularity_hammer - name = "Singularity Hammer" - desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact." - item_path = /obj/item/singularityhammer - -/datum/spellbook_entry/item/battlemage - name = "Battlemage Armour" - desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted." - item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard - limit = 1 - category = "Defensive" - -/datum/spellbook_entry/item/battlemage/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. - new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit - -/datum/spellbook_entry/item/battlemage_charge - name = "Battlemage Armour Charges" - desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour." - item_path = /obj/item/wizard_armour_charge - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/item/warpwhistle - name = "Warp Whistle" - desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use." - item_path = /obj/item/warpwhistle - category = "Mobility" - cost = 1 - -/datum/spellbook_entry/summon - name = "Summon Stuff" - category = "Rituals" - refundable = FALSE - buy_word = "Cast" - var/active = FALSE - -/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) - return ..() && !active - -/datum/spellbook_entry/summon/GetInfo() - var/dat ="" - dat += "[name]" - if(cost>0) - dat += " Cost:[cost]
                " - else - dat += " No Cost
                " - dat += "[desc]
                " - if(active) - dat += "Already cast!
                " - return dat - -/datum/spellbook_entry/summon/ghosts - name = "Summon Ghosts" - desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." - cost = 0 - -/datum/spellbook_entry/summon/ghosts/IsAvailible() - if(!SSticker.mode) - return FALSE - else - return TRUE - -/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - new /datum/round_event/wizard/ghost() - active = TRUE - to_chat(user, "You have cast summon ghosts!") - playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, TRUE) - return TRUE - -/datum/spellbook_entry/summon/guns - name = "Summon Guns" - desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." - -/datum/spellbook_entry/summon/guns/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - return FALSE - return !CONFIG_GET(flag/no_summon_guns) - -/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_GUNS, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon guns!") - return TRUE - -/datum/spellbook_entry/summon/magic - name = "Summon Magic" - desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." - -/datum/spellbook_entry/summon/magic/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - return FALSE - return !CONFIG_GET(flag/no_summon_magic) - -/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_MAGIC, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon magic!") - return TRUE - -/datum/spellbook_entry/summon/events - name = "Summon Events" - desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events." - cost = 2 - limit = 1 - var/times = 0 - -/datum/spellbook_entry/summon/events/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - return FALSE - return !CONFIG_GET(flag/no_summon_events) - -/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - summonevents() - times++ - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon events.") - return TRUE - -/datum/spellbook_entry/summon/events/GetInfo() - . = ..() - if(times>0) - . += "You cast it [times] times.
                " - return . - -/datum/spellbook_entry/summon/curse_of_madness - name = "Curse of Madness" - desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." - cost = 4 - -/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - active = TRUE - var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") - if(!message) - return FALSE - curse_of_madness(user, message) - to_chat(user, "You have cast the curse of insanity!") - playsound(user, 'sound/magic/mandswap.ogg', 50, TRUE) - return TRUE - -/obj/item/spellbook - name = "spell book" - desc = "An unearthly tome that glows with power." - icon = 'icons/obj/library.dmi' - icon_state ="book" - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/uses = 10 - var/temp = null - var/tab = null - var/mob/living/carbon/human/owner - var/list/datum/spellbook_entry/entries = list() - var/list/categories = list() - -/obj/item/spellbook/examine(mob/user) - . = ..() - if(owner) - . += {"There is a small signature on the front cover: "[owner]"."} - else - . += "It appears to have no author." - -/obj/item/spellbook/Initialize() - . = ..() - prepare_spells() - -/obj/item/spellbook/proc/prepare_spells() - var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - for(var/T in entry_types) - var/datum/spellbook_entry/E = new T - if(E.IsAvailible()) - entries |= E - categories |= E.category - else - qdel(E) - tab = categories[1] - -/obj/item/spellbook/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/antag_spawner/contract)) - var/obj/item/antag_spawner/contract/contract = O - if(contract.used) - to_chat(user, "The contract has been used, you can't get your points back now!") - else - to_chat(user, "You feed the contract back into the spellbook, refunding your points.") - uses += 2 - for(var/datum/spellbook_entry/item/contract/CT in entries) - if(!isnull(CT.limit)) - CT.limit++ - qdel(O) - else if(istype(O, /obj/item/antag_spawner/slaughter_demon)) - to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.") - if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) - uses += 1 - for(var/datum/spellbook_entry/item/hugbottle/HB in entries) - if(!isnull(HB.limit)) - HB.limit++ - else - uses += 2 - for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) - if(!isnull(BB.limit)) - BB.limit++ - qdel(O) - -/obj/item/spellbook/proc/GetCategoryHeader(category) - var/dat = "" - switch(category) - if("Offensive") - dat += "Spells and items geared towards debilitating and destroying.

                " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " - dat += "For spells: the number after the spell name is the cooldown time.
                " - dat += "You can reduce this number by spending more points on the spell.
                " - if("Defensive") - dat += "Spells and items geared towards improving your survivability or reducing foes' ability to attack.

                " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " - dat += "For spells: the number after the spell name is the cooldown time.
                " - dat += "You can reduce this number by spending more points on the spell.
                " - if("Mobility") - dat += "Spells and items geared towards improving your ability to move. It is a good idea to take at least one.

                " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " - dat += "For spells: the number after the spell name is the cooldown time.
                " - dat += "You can reduce this number by spending more points on the spell.
                " - if("Assistance") - dat += "Spells and items geared towards bringing in outside forces to aid you or improving upon your other items and abilities.

                " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " - dat += "For spells: the number after the spell name is the cooldown time.
                " - dat += "You can reduce this number by spending more points on the spell.
                " - if("Challenges") - dat += "The Wizard Federation typically has hard limits on the potency and number of spells brought to the station based on risk.
                " - dat += "Arming the station against you will increases the risk, but will grant you one more charge for your spellbook.
                " - if("Rituals") - dat += "These powerful spells change the very fabric of reality. Not always in your favour.
                " - return dat - -/obj/item/spellbook/proc/wrap(content) - var/dat = "" - dat +="Spellbook" - dat += {" - - - - "} - dat += {"[content]"} - return dat - -/obj/item/spellbook/attack_self(mob/user) - if(!owner) - to_chat(user, "You bind the spellbook to yourself.") - owner = user - return - if(user != owner) - to_chat(user, "The [name] does not recognize you as its owner and refuses to open!") - return - user.set_machine(src) - var/dat = "" - - dat += "" - - var/datum/spellbook_entry/E - for(var/i=1,i<=entries.len,i++) - var/spell_info = "" - E = entries[i] - spell_info += E.GetInfo() - if(E.CanBuy(user,src)) - spell_info+= "[E.buy_word]
                " - else - spell_info+= "Can't [E.buy_word]
                " - if(E.CanRefund(user,src)) - spell_info+= "Refund
                " - spell_info += "
                " - if(cat_dat[E.category]) - cat_dat[E.category] += spell_info - - for(var/category in categories) - dat += "
                " - dat += GetCategoryHeader(category) - dat += cat_dat[category] - dat += "
                " - - user << browse(wrap(dat), "window=spellbook;size=700x500") - onclose(user, "spellbook") - return - -/obj/item/spellbook/Topic(href, href_list) - ..() - var/mob/living/carbon/human/H = usr - - if(H.stat || H.restrained()) - return - if(!ishuman(H)) - return TRUE - - if(H.mind.special_role == "apprentice") - temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." - return - - var/datum/spellbook_entry/E = null - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["buy"]) - E = entries[text2num(href_list["buy"])] - if(E && E.CanBuy(H,src)) - if(E.Buy(H,src)) - if(E.limit) - E.limit-- - uses -= E.cost - else if(href_list["refund"]) - E = entries[text2num(href_list["refund"])] - if(E && E.refundable) - var/result = E.Refund(H,src) - if(result > 0) - if(!isnull(E.limit)) - E.limit += result - uses += result - else if(href_list["page"]) - tab = sanitize(href_list["page"]) - attack_self(H) - return +/datum/spellbook_entry + var/name = "Entry Name" + + var/spell_type = null + var/desc = "" + var/category = "Offensive" + var/cost = 2 + var/refundable = TRUE + var/surplus = -1 // -1 for infinite, not used by anything atm + var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell + var/buy_word = "Learn" + var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook + var/list/no_coexistance_typecache //Used so you can't have specific spells together + +/datum/spellbook_entry/New() + ..() + no_coexistance_typecache = typecacheof(no_coexistance_typecache) + +/datum/spellbook_entry/proc/IsAvailible() // For config prefs / gamemode restrictions - these are round applied + return TRUE + +/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances + if(book.uses= aspell.level_max) + to_chat(user, "This spell cannot be improved further!") + return FALSE + else + aspell.name = initial(aspell.name) + aspell.spell_level++ + aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) + if(aspell.charge_max < aspell.charge_counter) + aspell.charge_counter = aspell.charge_max + switch(aspell.spell_level) + if(1) + to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") + aspell.name = "Efficient [aspell.name]" + if(2) + to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") + aspell.name = "Quickened [aspell.name]" + if(3) + to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") + aspell.name = "Free [aspell.name]" + if(4) + to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") + aspell.name = "Instant [aspell.name]" + if(aspell.spell_level >= aspell.level_max) + to_chat(user, "This spell cannot be strengthened any further!") + SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) + return TRUE + //No same spell found - just learn it + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + user.mind.AddSpell(S) + to_chat(user, "You have learned [S.name].") + return TRUE + +/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) + if(!refundable) + return FALSE + if(!S) + S = new spell_type() + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + return TRUE + return FALSE + +/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure + var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station] + if(!(user in A.contents)) + to_chat(user, "You can only refund spells at the wizard lair!") + return -1 + if(!S) + S = new spell_type() + var/spell_levels = 0 + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + spell_levels = aspell.spell_level + user.mind.spell_list.Remove(aspell) + qdel(S) + return cost * (spell_levels+1) + return -1 +/datum/spellbook_entry/proc/GetInfo() + if(!S) + S = new spell_type() + var/dat ="" + dat += "[initial(S.name)]" + if(S.charge_type == "recharge") + dat += " Cooldown:[S.charge_max/10]" + dat += " Cost:[cost]
                " + dat += "[S.desc][desc]
                " + dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
                " + return dat + +/datum/spellbook_entry/fireball + name = "Fireball" + spell_type = /obj/effect/proc_holder/spell/aimed/fireball + +/datum/spellbook_entry/spell_cards + name = "Spell Cards" + spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards + +/datum/spellbook_entry/rod_form + name = "Rod Form" + spell_type = /obj/effect/proc_holder/spell/targeted/rod_form + +/datum/spellbook_entry/magicm + name = "Magic Missile" + spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile + category = "Defensive" + +/datum/spellbook_entry/disintegrate + name = "Smite" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate + +/datum/spellbook_entry/disabletech + name = "Disable Tech" + spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/repulse + name = "Repulse" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse + category = "Defensive" + +/datum/spellbook_entry/lightning_packet + name = "Thrown Lightning" + spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket + category = "Defensive" + +/datum/spellbook_entry/timestop + name = "Time Stop" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/timestop + category = "Defensive" + +/datum/spellbook_entry/smoke + name = "Smoke" + spell_type = /obj/effect/proc_holder/spell/targeted/smoke + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/blind + name = "Blind" + spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind + cost = 1 + +/datum/spellbook_entry/mindswap + name = "Mindswap" + spell_type = /obj/effect/proc_holder/spell/pointed/mind_transfer + category = "Mobility" + +/datum/spellbook_entry/forcewall + name = "Force Wall" + spell_type = /obj/effect/proc_holder/spell/targeted/forcewall + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/blink + name = "Blink" + spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink + category = "Mobility" + +/datum/spellbook_entry/teleport + name = "Teleport" + spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport + category = "Mobility" + +/datum/spellbook_entry/mutate + name = "Mutate" + spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate + +/datum/spellbook_entry/jaunt + name = "Ethereal Jaunt" + spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt + category = "Mobility" + +/datum/spellbook_entry/knock + name = "Knock" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/fleshtostone + name = "Flesh to Stone" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone + +/datum/spellbook_entry/summonitem + name = "Summon Item" + spell_type = /obj/effect/proc_holder/spell/targeted/summonitem + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/lichdom + name = "Bind Soul" + spell_type = /obj/effect/proc_holder/spell/targeted/lichdom + category = "Defensive" + +/datum/spellbook_entry/teslablast + name = "Tesla Blast" + spell_type = /obj/effect/proc_holder/spell/targeted/tesla + +/datum/spellbook_entry/lightningbolt + name = "Lightning Bolt" + spell_type = /obj/effect/proc_holder/spell/aimed/lightningbolt + cost = 1 + +/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return TRUE on success + . = ..() + ADD_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") + +/datum/spellbook_entry/lightningbolt/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) + . = ..() + REMOVE_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") + +/datum/spellbook_entry/infinite_guns + name = "Lesser Summon Guns" + spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun + cost = 3 + no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage + +/datum/spellbook_entry/arcane_barrage + name = "Arcane Barrage" + spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage + cost = 3 + no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun + +/datum/spellbook_entry/barnyard + name = "Barnyard Curse" + spell_type = /obj/effect/proc_holder/spell/pointed/barnyardcurse + +/datum/spellbook_entry/charge + name = "Charge" + spell_type = /obj/effect/proc_holder/spell/targeted/charge + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/shapeshift + name = "Wild Shapeshift" + spell_type = /obj/effect/proc_holder/spell/targeted/shapeshift + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/tap + name = "Soul Tap" + spell_type = /obj/effect/proc_holder/spell/self/tap + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/spacetime_dist + name = "Spacetime Distortion" + spell_type = /obj/effect/proc_holder/spell/spacetime_dist + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/the_traps + name = "The Traps!" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps + category = "Defensive" + cost = 1 + + +/datum/spellbook_entry/item + name = "Buy Item" + refundable = FALSE + buy_word = "Summon" + var/item_path= null + + +/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + new item_path(get_turf(user)) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + return TRUE + +/datum/spellbook_entry/item/GetInfo() + var/dat ="" + dat += "[name]" + dat += " Cost:[cost]
                " + dat += "[desc]
                " + if(surplus>=0) + dat += "[surplus] left.
                " + return dat + +/datum/spellbook_entry/item/staffchange + name = "Staff of Change" + desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." + item_path = /obj/item/gun/magic/staff/change + +/datum/spellbook_entry/item/staffanimation + name = "Staff of Animation" + desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." + item_path = /obj/item/gun/magic/staff/animate + category = "Assistance" + +/datum/spellbook_entry/item/staffchaos + name = "Staff of Chaos" + desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." + item_path = /obj/item/gun/magic/staff/chaos + +/datum/spellbook_entry/item/spellblade + name = "Spellblade" + desc = "A sword capable of firing blasts of energy which rip targets limb from limb." + item_path = /obj/item/gun/magic/staff/spellblade + +/datum/spellbook_entry/item/staffdoor + name = "Staff of Door Creation" + desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." + item_path = /obj/item/gun/magic/staff/door + cost = 1 + category = "Mobility" + +/datum/spellbook_entry/item/staffhealing + name = "Staff of Healing" + desc = "An altruistic staff that can heal the lame and raise the dead." + item_path = /obj/item/gun/magic/staff/healing + cost = 1 + category = "Defensive" + +/datum/spellbook_entry/item/lockerstaff + name = "Staff of the Locker" + desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." + item_path = /obj/item/gun/magic/staff/locker + category = "Defensive" + +/datum/spellbook_entry/item/scryingorb + name = "Scrying Orb" + desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." + item_path = /obj/item/scrying + category = "Defensive" + +/datum/spellbook_entry/item/soulstones + name = "Six Soul Stone Shards and the spell Artificer" + desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." + item_path = /obj/item/storage/belt/soulstone/full + category = "Assistance" + +/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . =..() + if(.) + user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) + return . + +/datum/spellbook_entry/item/necrostone + name = "A Necromantic Stone" + desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." + item_path = /obj/item/necromantic_stone + category = "Assistance" + +/datum/spellbook_entry/item/wands + name = "Wand Assortment" + desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." + item_path = /obj/item/storage/belt/wands/full + category = "Defensive" + +/datum/spellbook_entry/item/armor + name = "Mastercrafted Armor Set" + desc = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space." + item_path = /obj/item/clothing/suit/space/hardsuit/wizard + category = "Defensive" + +/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/tank/internals/oxygen(get_turf(user)) //i need to BREATHE + new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. + new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit + +/datum/spellbook_entry/item/contract + name = "Contract of Apprenticeship" + desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." + item_path = /obj/item/antag_spawner/contract + category = "Assistance" + +/datum/spellbook_entry/item/guardian + name = "Guardian Deck" + desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ + It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." + item_path = /obj/item/guardiancreator/choose/wizard + category = "Assistance" + +/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) + +/datum/spellbook_entry/item/bloodbottle + name = "Bottle of Blood" + desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." + item_path = /obj/item/antag_spawner/slaughter_demon + limit = 3 + category = "Assistance" + +/datum/spellbook_entry/item/hugbottle + name = "Bottle of Tickles" + desc = "A bottle of magically infused fun, the smell of which will \ + attract adorable extradimensional beings when broken. These beings \ + are similar to slaughter demons, but they do not permamently kill \ + their victims, instead putting them in an extradimensional hugspace, \ + to be released on the demon's death. Chaotic, but not ultimately \ + damaging. The crew's reaction to the other hand could be very \ + destructive." + item_path = /obj/item/antag_spawner/slaughter_demon/laughter + cost = 1 //non-destructive; it's just a jape, sibling! + limit = 3 + category = "Assistance" + +/datum/spellbook_entry/item/mjolnir + name = "Mjolnir" + desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." + item_path = /obj/item/mjollnir + +/datum/spellbook_entry/item/singularity_hammer + name = "Singularity Hammer" + desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact." + item_path = /obj/item/singularityhammer + +/datum/spellbook_entry/item/battlemage + name = "Battlemage Armour" + desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted." + item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard + limit = 1 + category = "Defensive" + +/datum/spellbook_entry/item/battlemage/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. + new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit + +/datum/spellbook_entry/item/battlemage_charge + name = "Battlemage Armour Charges" + desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour." + item_path = /obj/item/wizard_armour_charge + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/item/warpwhistle + name = "Warp Whistle" + desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use." + item_path = /obj/item/warpwhistle + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/summon + name = "Summon Stuff" + category = "Rituals" + refundable = FALSE + buy_word = "Cast" + var/active = FALSE + +/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) + return ..() && !active + +/datum/spellbook_entry/summon/GetInfo() + var/dat ="" + dat += "[name]" + if(cost>0) + dat += " Cost:[cost]
                " + else + dat += " No Cost
                " + dat += "[desc]
                " + if(active) + dat += "Already cast!
                " + return dat + +/datum/spellbook_entry/summon/ghosts + name = "Summon Ghosts" + desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." + cost = 0 + +/datum/spellbook_entry/summon/ghosts/IsAvailible() + if(!SSticker.mode) + return FALSE + else + return TRUE + +/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + new /datum/round_event/wizard/ghost() + active = TRUE + to_chat(user, "You have cast summon ghosts!") + playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, TRUE) + return TRUE + +/datum/spellbook_entry/summon/guns + name = "Summon Guns" + desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." + +/datum/spellbook_entry/summon/guns/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic + return FALSE + return !CONFIG_GET(flag/no_summon_guns) + +/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + rightandwrong(SUMMON_GUNS, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon guns!") + return TRUE + +/datum/spellbook_entry/summon/magic + name = "Summon Magic" + desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." + +/datum/spellbook_entry/summon/magic/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic + return FALSE + return !CONFIG_GET(flag/no_summon_magic) + +/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + rightandwrong(SUMMON_MAGIC, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon magic!") + return TRUE + +/datum/spellbook_entry/summon/events + name = "Summon Events" + desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events." + cost = 2 + limit = 1 + var/times = 0 + +/datum/spellbook_entry/summon/events/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic + return FALSE + return !CONFIG_GET(flag/no_summon_events) + +/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + summonevents() + times++ + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon events.") + return TRUE + +/datum/spellbook_entry/summon/events/GetInfo() + . = ..() + if(times>0) + . += "You cast it [times] times.
                " + return . + +/datum/spellbook_entry/summon/curse_of_madness + name = "Curse of Madness" + desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." + cost = 4 + +/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + active = TRUE + var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") + if(!message) + return FALSE + curse_of_madness(user, message) + to_chat(user, "You have cast the curse of insanity!") + playsound(user, 'sound/magic/mandswap.ogg', 50, TRUE) + return TRUE + +/obj/item/spellbook + name = "spell book" + desc = "An unearthly tome that glows with power." + icon = 'icons/obj/library.dmi' + icon_state ="book" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/uses = 10 + var/temp = null + var/tab = null + var/mob/living/carbon/human/owner + var/list/datum/spellbook_entry/entries = list() + var/list/categories = list() + +/obj/item/spellbook/examine(mob/user) + . = ..() + if(owner) + . += {"There is a small signature on the front cover: "[owner]"."} + else + . += "It appears to have no author." + +/obj/item/spellbook/Initialize() + . = ..() + prepare_spells() + +/obj/item/spellbook/proc/prepare_spells() + var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon + for(var/T in entry_types) + var/datum/spellbook_entry/E = new T + if(E.IsAvailible()) + entries |= E + categories |= E.category + else + qdel(E) + tab = categories[1] + +/obj/item/spellbook/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/antag_spawner/contract)) + var/obj/item/antag_spawner/contract/contract = O + if(contract.used) + to_chat(user, "The contract has been used, you can't get your points back now!") + else + to_chat(user, "You feed the contract back into the spellbook, refunding your points.") + uses += 2 + for(var/datum/spellbook_entry/item/contract/CT in entries) + if(!isnull(CT.limit)) + CT.limit++ + qdel(O) + else if(istype(O, /obj/item/antag_spawner/slaughter_demon)) + to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.") + if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) + uses += 1 + for(var/datum/spellbook_entry/item/hugbottle/HB in entries) + if(!isnull(HB.limit)) + HB.limit++ + else + uses += 2 + for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) + if(!isnull(BB.limit)) + BB.limit++ + qdel(O) + +/obj/item/spellbook/proc/GetCategoryHeader(category) + var/dat = "" + switch(category) + if("Offensive") + dat += "Spells and items geared towards debilitating and destroying.

                " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " + dat += "For spells: the number after the spell name is the cooldown time.
                " + dat += "You can reduce this number by spending more points on the spell.
                " + if("Defensive") + dat += "Spells and items geared towards improving your survivability or reducing foes' ability to attack.

                " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " + dat += "For spells: the number after the spell name is the cooldown time.
                " + dat += "You can reduce this number by spending more points on the spell.
                " + if("Mobility") + dat += "Spells and items geared towards improving your ability to move. It is a good idea to take at least one.

                " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " + dat += "For spells: the number after the spell name is the cooldown time.
                " + dat += "You can reduce this number by spending more points on the spell.
                " + if("Assistance") + dat += "Spells and items geared towards bringing in outside forces to aid you or improving upon your other items and abilities.

                " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                " + dat += "For spells: the number after the spell name is the cooldown time.
                " + dat += "You can reduce this number by spending more points on the spell.
                " + if("Challenges") + dat += "The Wizard Federation typically has hard limits on the potency and number of spells brought to the station based on risk.
                " + dat += "Arming the station against you will increases the risk, but will grant you one more charge for your spellbook.
                " + if("Rituals") + dat += "These powerful spells change the very fabric of reality. Not always in your favour.
                " + return dat + +/obj/item/spellbook/proc/wrap(content) + var/dat = "" + dat +="Spellbook" + dat += {" + + + + "} + dat += {"[content]"} + return dat + +/obj/item/spellbook/attack_self(mob/user) + if(!owner) + to_chat(user, "You bind the spellbook to yourself.") + owner = user + return + if(user != owner) + to_chat(user, "The [name] does not recognize you as its owner and refuses to open!") + return + user.set_machine(src) + var/dat = "" + + dat += "" + + var/datum/spellbook_entry/E + for(var/i=1,i<=entries.len,i++) + var/spell_info = "" + E = entries[i] + spell_info += E.GetInfo() + if(E.CanBuy(user,src)) + spell_info+= "[E.buy_word]
                " + else + spell_info+= "Can't [E.buy_word]
                " + if(E.CanRefund(user,src)) + spell_info+= "Refund
                " + spell_info += "
                " + if(cat_dat[E.category]) + cat_dat[E.category] += spell_info + + for(var/category in categories) + dat += "
                " + dat += GetCategoryHeader(category) + dat += cat_dat[category] + dat += "
                " + + user << browse(wrap(dat), "window=spellbook;size=700x500") + onclose(user, "spellbook") + return + +/obj/item/spellbook/Topic(href, href_list) + ..() + var/mob/living/carbon/human/H = usr + + if(H.stat || H.restrained()) + return + if(!ishuman(H)) + return TRUE + + if(H.mind.special_role == "apprentice") + temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." + return + + var/datum/spellbook_entry/E = null + if(loc == H || (in_range(src, H) && isturf(loc))) + H.set_machine(src) + if(href_list["buy"]) + E = entries[text2num(href_list["buy"])] + if(E && E.CanBuy(H,src)) + if(E.Buy(H,src)) + if(E.limit) + E.limit-- + uses -= E.cost + else if(href_list["refund"]) + E = entries[text2num(href_list["refund"])] + if(E && E.refundable) + var/result = E.Refund(H,src) + if(result > 0) + if(!isnull(E.limit)) + E.limit += result + uses += result + else if(href_list["page"]) + tab = sanitize(href_list["page"]) + attack_self(H) + return diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index 7fb5a7fefe4..070584546bb 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -1,127 +1,127 @@ -#define WIRE_RECEIVE (1<<0) -#define WIRE_PULSE (1<<1) -#define WIRE_PULSE_SPECIAL (1<<2) -#define WIRE_RADIO_RECEIVE (1<<3) -#define WIRE_RADIO_PULSE (1<<4) -#define ASSEMBLY_BEEP_VOLUME 5 - -/obj/item/assembly - name = "assembly" - desc = "A small electronic device that should never exist." - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = "" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=100) - throwforce = 2 - throw_speed = 3 - throw_range = 7 - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/is_position_sensitive = FALSE //set to true if the device has different icons for each position. - //This will prevent things such as visible lasers from facing the incorrect direction when transformed by assembly_holder's update_icon() - var/secured = TRUE - var/list/attached_overlays = null - var/obj/item/assembly_holder/holder = null - var/wire_type = WIRE_RECEIVE | WIRE_PULSE - var/attachable = FALSE // can this be attached to wires - var/datum/wires/connected = null - var/next_activate = 0 //When we're next allowed to activate - for spam control - -/obj/item/assembly/get_part_rating() - return 1 - -/obj/item/assembly/proc/on_attach() - -//Call this when detaching it from a device. handles any special functions that need to be updated ex post facto -/obj/item/assembly/proc/on_detach() - if(!holder) - return FALSE - forceMove(holder.drop_location()) - holder = null - return TRUE - -//Called when the holder is moved -/obj/item/assembly/proc/holder_movement() - if(!holder) - return FALSE - setDir(holder.dir) - return TRUE - -/obj/item/assembly/proc/is_secured(mob/user) - if(!secured) - to_chat(user, "The [name] is unsecured!") - return FALSE - return TRUE - -//Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs -/obj/item/assembly/proc/pulsed(radio = FALSE) - if(wire_type & WIRE_RECEIVE) - INVOKE_ASYNC(src, .proc/activate) - if(radio && (wire_type & WIRE_RADIO_RECEIVE)) - INVOKE_ASYNC(src, .proc/activate) - return TRUE - -//Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct -/obj/item/assembly/proc/pulse(radio = FALSE) - if(connected && wire_type) - connected.pulse_assembly(src) - return TRUE - if(holder && (wire_type & WIRE_PULSE)) - holder.process_activation(src, 1, 0) - if(holder && (wire_type & WIRE_PULSE_SPECIAL)) - holder.process_activation(src, 0, 1) - return TRUE - -// What the device does when turned on -/obj/item/assembly/proc/activate() - if(QDELETED(src) || !secured || (next_activate > world.time)) - return FALSE - next_activate = world.time + 30 - return TRUE - -/obj/item/assembly/proc/toggle_secure() - secured = !secured - update_icon() - return secured - -/obj/item/assembly/attackby(obj/item/W, mob/user, params) - if(isassembly(W)) - var/obj/item/assembly/A = W - if((!A.secured) && (!secured)) - holder = new/obj/item/assembly_holder(get_turf(src)) - holder.assemble(src,A,user) - to_chat(user, "You attach and secure \the [A] to \the [src]!") - else - to_chat(user, "Both devices must be in attachable mode to be attached together.") - return - ..() - -/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I) - if(..()) - return TRUE - if(toggle_secure()) - to_chat(user, "\The [src] is ready!") - else - to_chat(user, "\The [src] can now be attached!") - add_fingerprint(user) - return TRUE - -/obj/item/assembly/examine(mob/user) - . = ..() - . += "\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]" - -/obj/item/assembly/attack_self(mob/user) - if(!user) - return FALSE - user.set_machine(src) - interact(user) - return TRUE - -/obj/item/assembly/interact(mob/user) - return ui_interact(user) - -/obj/item/assembly/ui_host(mob/user) - if(holder) - return holder - return src +#define WIRE_RECEIVE (1<<0) +#define WIRE_PULSE (1<<1) +#define WIRE_PULSE_SPECIAL (1<<2) +#define WIRE_RADIO_RECEIVE (1<<3) +#define WIRE_RADIO_PULSE (1<<4) +#define ASSEMBLY_BEEP_VOLUME 5 + +/obj/item/assembly + name = "assembly" + desc = "A small electronic device that should never exist." + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=100) + throwforce = 2 + throw_speed = 3 + throw_range = 7 + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/is_position_sensitive = FALSE //set to true if the device has different icons for each position. + //This will prevent things such as visible lasers from facing the incorrect direction when transformed by assembly_holder's update_icon() + var/secured = TRUE + var/list/attached_overlays = null + var/obj/item/assembly_holder/holder = null + var/wire_type = WIRE_RECEIVE | WIRE_PULSE + var/attachable = FALSE // can this be attached to wires + var/datum/wires/connected = null + var/next_activate = 0 //When we're next allowed to activate - for spam control + +/obj/item/assembly/get_part_rating() + return 1 + +/obj/item/assembly/proc/on_attach() + +//Call this when detaching it from a device. handles any special functions that need to be updated ex post facto +/obj/item/assembly/proc/on_detach() + if(!holder) + return FALSE + forceMove(holder.drop_location()) + holder = null + return TRUE + +//Called when the holder is moved +/obj/item/assembly/proc/holder_movement() + if(!holder) + return FALSE + setDir(holder.dir) + return TRUE + +/obj/item/assembly/proc/is_secured(mob/user) + if(!secured) + to_chat(user, "The [name] is unsecured!") + return FALSE + return TRUE + +//Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs +/obj/item/assembly/proc/pulsed(radio = FALSE) + if(wire_type & WIRE_RECEIVE) + INVOKE_ASYNC(src, .proc/activate) + if(radio && (wire_type & WIRE_RADIO_RECEIVE)) + INVOKE_ASYNC(src, .proc/activate) + return TRUE + +//Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct +/obj/item/assembly/proc/pulse(radio = FALSE) + if(connected && wire_type) + connected.pulse_assembly(src) + return TRUE + if(holder && (wire_type & WIRE_PULSE)) + holder.process_activation(src, 1, 0) + if(holder && (wire_type & WIRE_PULSE_SPECIAL)) + holder.process_activation(src, 0, 1) + return TRUE + +// What the device does when turned on +/obj/item/assembly/proc/activate() + if(QDELETED(src) || !secured || (next_activate > world.time)) + return FALSE + next_activate = world.time + 30 + return TRUE + +/obj/item/assembly/proc/toggle_secure() + secured = !secured + update_icon() + return secured + +/obj/item/assembly/attackby(obj/item/W, mob/user, params) + if(isassembly(W)) + var/obj/item/assembly/A = W + if((!A.secured) && (!secured)) + holder = new/obj/item/assembly_holder(get_turf(src)) + holder.assemble(src,A,user) + to_chat(user, "You attach and secure \the [A] to \the [src]!") + else + to_chat(user, "Both devices must be in attachable mode to be attached together.") + return + ..() + +/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + if(toggle_secure()) + to_chat(user, "\The [src] is ready!") + else + to_chat(user, "\The [src] can now be attached!") + add_fingerprint(user) + return TRUE + +/obj/item/assembly/examine(mob/user) + . = ..() + . += "\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]" + +/obj/item/assembly/attack_self(mob/user) + if(!user) + return FALSE + user.set_machine(src) + interact(user) + return TRUE + +/obj/item/assembly/interact(mob/user) + return ui_interact(user) + +/obj/item/assembly/ui_host(mob/user) + if(holder) + return holder + return src diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index bb606ac452d..4910e212046 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -1,299 +1,299 @@ -#define CONFUSION_STACK_MAX_MULTIPLIER 2 -/obj/item/assembly/flash - name = "flash" - desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." - icon_state = "flash" - inhand_icon_state = "flashtool" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/iron = 300, /datum/material/glass = 300) - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - var/flashing_overlay = "flash-f" - var/times_used = 0 //Number of times it's been used. - var/burnt_out = FALSE //Is the flash burnt out? - var/burnout_resistance = 0 - var/last_used = 0 //last world.time it was used. - var/cooldown = 0 - var/last_trigger = 0 //Last time it was successfully triggered. - -/obj/item/assembly/flash/suicide_act(mob/living/user) - if(burnt_out) - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but it's burnt out!") - return SHAME - else if(user.is_blind()) - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but [user.p_theyre()] blind!") - return SHAME - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it! It looks like [user.p_theyre()] trying to commit suicide!") - attack(user,user) - return FIRELOSS - -/obj/item/assembly/flash/update_icon(flash = FALSE) - cut_overlays() - attached_overlays = list() - if(burnt_out) - add_overlay("flashburnt") - attached_overlays += "flashburnt" - if(flash) - add_overlay(flashing_overlay) - attached_overlays += flashing_overlay - addtimer(CALLBACK(src, /atom/.proc/update_icon), 5) - if(holder) - holder.update_icon() - -/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - flash_carbon(user, user, 15, 0) - return FALSE - return TRUE - -/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. - if(!burnt_out) - burnt_out = TRUE - update_icon() - if(ismob(loc)) - var/mob/M = loc - M.visible_message("[src] burns out!","[src] burns out!") - else - var/turf/T = get_turf(src) - T.visible_message("[src] burns out!") - -/obj/item/assembly/flash/proc/flash_recharge(interval = 10) - var/deciseconds_passed = world.time - last_used - for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval - times_used-- - last_used = world.time - times_used = max(0, times_used) //sanity - if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out - burn_out() - return FALSE - return TRUE - -//BYPASS CHECKS ALSO PREVENTS BURNOUT! -/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 3, power = 5, targeted = FALSE, mob/user) - if(!bypass_checks && !try_use_flash()) - return FALSE - var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE) - if(user) - targets -= user - for(var/mob/living/carbon/C in targets) - flash_carbon(C, user, power, targeted, TRUE) - return TRUE - -/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE) - if(!target_loc) - target_loc = loc - if(override_vision_checks) - return get_hearers_in_view(range, get_turf(target_loc)) - if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc))) - return viewers(range, get_turf(target_loc)) - else - return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living) - -/obj/item/assembly/flash/proc/try_use_flash(mob/user = null) - if(burnt_out || (world.time < last_trigger + cooldown)) - return FALSE - last_trigger = world.time - playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) - flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) - times_used++ - flash_recharge() - update_icon(TRUE) - if(user && !clown_check(user)) - return FALSE - return TRUE - -/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) - if(!istype(M)) - return - if(user) - log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src) - else //caused by emp/remote signal - M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK) - if(generic_message && M != user) - to_chat(M, "[src] emits a blinding light!") - if(targeted) - if(M.flash_act(1, 1)) - if(M.confused < power) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) - if(user) - terrible_conversion_proc(M, user) - visible_message("[user] blinds [M] with the flash!") - to_chat(user, "You blind [M] with the flash!") - to_chat(M, "[user] blinds you with the flash!") - else - to_chat(M, "You are blinded by [src]!") - M.Paralyze(rand(80,120)) - else if(user) - visible_message("[user] fails to blind [M] with the flash!") - to_chat(user, "You fail to blind [M] with the flash!") - to_chat(M, "[user] fails to blind you with the flash!") - else - to_chat(M, "[src] fails to blind you!") - else - if(M.flash_act()) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) - -/obj/item/assembly/flash/attack(mob/living/M, mob/user) - if(!try_use_flash(user)) - return FALSE - if(iscarbon(M)) - flash_carbon(M, user, 5, 1) - return TRUE - else if(issilicon(M)) - var/mob/living/silicon/robot/R = M - log_combat(user, R, "flashed", src) - update_icon(1) - R.Paralyze(rand(80,120)) - var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - R.confused += min(5, diff) - R.flash_act(affect_silicon = 1) - user.visible_message("[user] overloads [R]'s sensors with the flash!", "You overload [R]'s sensors with the flash!") - return TRUE - - user.visible_message("[user] fails to blind [M] with the flash!", "You fail to blind [M] with the flash!") - -/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) - if(holder) - return FALSE - if(!AOE_flash(FALSE, 3, 5, FALSE, user)) - return FALSE - to_chat(user, "[src] emits a blinding light!") - -/obj/item/assembly/flash/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(!try_use_flash()) - return - AOE_flash() - burn_out() - -/obj/item/assembly/flash/activate()//AOE flash on signal received - if(!..()) - return - AOE_flash() - -/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/H, mob/user) - if(istype(H) && H.stat != DEAD) - if(user.mind) - var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head) - if(!converter) - return - if(!H.client) - to_chat(user, "This mind is so vacant that it is not susceptible to influence!") - return - if(H.stat != CONSCIOUS) - to_chat(user, "They must be conscious before you can convert [H.p_them()]!") - return - if(converter.add_revolutionary(H.mind)) - if(prob(1) || SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) - H.say("You son of a bitch! I'm in.", forced = "That son of a bitch! They're in.") - times_used -- //Flashes less likely to burn out for headrevs when used for conversion - else - to_chat(user, "This mind seems resistant to the flash!") - - -/obj/item/assembly/flash/cyborg - -/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/assembly/flash/cyborg/attack_self(mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params) - return -/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return - -/obj/item/assembly/flash/memorizer - name = "memorizer" - desc = "If you see this, you're not likely to remember it any time soon." - icon = 'icons/obj/device.dmi' - icon_state = "memorizer" - inhand_icon_state = "nullrod" - -/obj/item/assembly/flash/handheld //this is now the regular pocket flashes - -/obj/item/assembly/flash/armimplant - name = "photon projector" - desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out." - var/flashcd = 20 - var/overheat = 0 - var/obj/item/organ/cyberimp/arm/flash/I = null - -/obj/item/assembly/flash/armimplant/burn_out() - if(I && I.owner) - to_chat(I.owner, "Your photon projector implant overheats and deactivates!") - I.Retract() - overheat = TRUE - addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2) - -/obj/item/assembly/flash/armimplant/try_use_flash(mob/user = null) - if(overheat) - if(I && I.owner) - to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!") - return FALSE - overheat = TRUE - addtimer(CALLBACK(src, .proc/cooldown), flashcd) - playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) - update_icon(1) - return TRUE - - -/obj/item/assembly/flash/armimplant/proc/cooldown() - overheat = FALSE - -/obj/item/assembly/flash/hypnotic - desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance." - flashing_overlay = "flash-hypno" - light_color = LIGHT_COLOR_PINK - cooldown = 20 - -/obj/item/assembly/flash/hypnotic/burn_out() - return - -/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) - if(!istype(M)) - return - if(user) - log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src) - else //caused by emp/remote signal - M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK) - if(generic_message && M != user) - to_chat(M, "[src] emits a soothing light...") - if(targeted) - if(M.flash_act(1, 1)) - var/hypnosis = FALSE - if(M.hypnosis_vulnerable()) - hypnosis = TRUE - if(user) - user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!") - - if(!hypnosis) - to_chat(M, "The light makes you feel oddly relaxed...") - M.confused += min(M.confused + 10, 20) - M.dizziness += min(M.dizziness + 10, 20) - M.drowsyness += min(M.drowsyness + 10, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) - else - M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) - - else if(user) - user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!") - else - to_chat(M, "[src] fails to blind you!") - - else if(M.flash_act()) - to_chat(M, "Such a pretty light...") - M.confused += min(M.confused + 4, 20) - M.dizziness += min(M.dizziness + 4, 20) - M.drowsyness += min(M.drowsyness + 4, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) +#define CONFUSION_STACK_MAX_MULTIPLIER 2 +/obj/item/assembly/flash + name = "flash" + desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." + icon_state = "flash" + inhand_icon_state = "flashtool" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/iron = 300, /datum/material/glass = 300) + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + var/flashing_overlay = "flash-f" + var/times_used = 0 //Number of times it's been used. + var/burnt_out = FALSE //Is the flash burnt out? + var/burnout_resistance = 0 + var/last_used = 0 //last world.time it was used. + var/cooldown = 0 + var/last_trigger = 0 //Last time it was successfully triggered. + +/obj/item/assembly/flash/suicide_act(mob/living/user) + if(burnt_out) + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but it's burnt out!") + return SHAME + else if(user.is_blind()) + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but [user.p_theyre()] blind!") + return SHAME + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it! It looks like [user.p_theyre()] trying to commit suicide!") + attack(user,user) + return FIRELOSS + +/obj/item/assembly/flash/update_icon(flash = FALSE) + cut_overlays() + attached_overlays = list() + if(burnt_out) + add_overlay("flashburnt") + attached_overlays += "flashburnt" + if(flash) + add_overlay(flashing_overlay) + attached_overlays += flashing_overlay + addtimer(CALLBACK(src, /atom/.proc/update_icon), 5) + if(holder) + holder.update_icon() + +/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + flash_carbon(user, user, 15, 0) + return FALSE + return TRUE + +/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. + if(!burnt_out) + burnt_out = TRUE + update_icon() + if(ismob(loc)) + var/mob/M = loc + M.visible_message("[src] burns out!","[src] burns out!") + else + var/turf/T = get_turf(src) + T.visible_message("[src] burns out!") + +/obj/item/assembly/flash/proc/flash_recharge(interval = 10) + var/deciseconds_passed = world.time - last_used + for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval + times_used-- + last_used = world.time + times_used = max(0, times_used) //sanity + if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out + burn_out() + return FALSE + return TRUE + +//BYPASS CHECKS ALSO PREVENTS BURNOUT! +/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 3, power = 5, targeted = FALSE, mob/user) + if(!bypass_checks && !try_use_flash()) + return FALSE + var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE) + if(user) + targets -= user + for(var/mob/living/carbon/C in targets) + flash_carbon(C, user, power, targeted, TRUE) + return TRUE + +/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE) + if(!target_loc) + target_loc = loc + if(override_vision_checks) + return get_hearers_in_view(range, get_turf(target_loc)) + if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc))) + return viewers(range, get_turf(target_loc)) + else + return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living) + +/obj/item/assembly/flash/proc/try_use_flash(mob/user = null) + if(burnt_out || (world.time < last_trigger + cooldown)) + return FALSE + last_trigger = world.time + playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) + flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) + times_used++ + flash_recharge() + update_icon(TRUE) + if(user && !clown_check(user)) + return FALSE + return TRUE + +/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) + if(!istype(M)) + return + if(user) + log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src) + else //caused by emp/remote signal + M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK) + if(generic_message && M != user) + to_chat(M, "[src] emits a blinding light!") + if(targeted) + if(M.flash_act(1, 1)) + if(M.confused < power) + var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + M.confused += min(power, diff) + if(user) + terrible_conversion_proc(M, user) + visible_message("[user] blinds [M] with the flash!") + to_chat(user, "You blind [M] with the flash!") + to_chat(M, "[user] blinds you with the flash!") + else + to_chat(M, "You are blinded by [src]!") + M.Paralyze(rand(80,120)) + else if(user) + visible_message("[user] fails to blind [M] with the flash!") + to_chat(user, "You fail to blind [M] with the flash!") + to_chat(M, "[user] fails to blind you with the flash!") + else + to_chat(M, "[src] fails to blind you!") + else + if(M.flash_act()) + var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + M.confused += min(power, diff) + +/obj/item/assembly/flash/attack(mob/living/M, mob/user) + if(!try_use_flash(user)) + return FALSE + if(iscarbon(M)) + flash_carbon(M, user, 5, 1) + return TRUE + else if(issilicon(M)) + var/mob/living/silicon/robot/R = M + log_combat(user, R, "flashed", src) + update_icon(1) + R.Paralyze(rand(80,120)) + var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + R.confused += min(5, diff) + R.flash_act(affect_silicon = 1) + user.visible_message("[user] overloads [R]'s sensors with the flash!", "You overload [R]'s sensors with the flash!") + return TRUE + + user.visible_message("[user] fails to blind [M] with the flash!", "You fail to blind [M] with the flash!") + +/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) + if(holder) + return FALSE + if(!AOE_flash(FALSE, 3, 5, FALSE, user)) + return FALSE + to_chat(user, "[src] emits a blinding light!") + +/obj/item/assembly/flash/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(!try_use_flash()) + return + AOE_flash() + burn_out() + +/obj/item/assembly/flash/activate()//AOE flash on signal received + if(!..()) + return + AOE_flash() + +/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/H, mob/user) + if(istype(H) && H.stat != DEAD) + if(user.mind) + var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head) + if(!converter) + return + if(!H.client) + to_chat(user, "This mind is so vacant that it is not susceptible to influence!") + return + if(H.stat != CONSCIOUS) + to_chat(user, "They must be conscious before you can convert [H.p_them()]!") + return + if(converter.add_revolutionary(H.mind)) + if(prob(1) || SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) + H.say("You son of a bitch! I'm in.", forced = "That son of a bitch! They're in.") + times_used -- //Flashes less likely to burn out for headrevs when used for conversion + else + to_chat(user, "This mind seems resistant to the flash!") + + +/obj/item/assembly/flash/cyborg + +/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/assembly/flash/cyborg/attack_self(mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params) + return +/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I) + return + +/obj/item/assembly/flash/memorizer + name = "memorizer" + desc = "If you see this, you're not likely to remember it any time soon." + icon = 'icons/obj/device.dmi' + icon_state = "memorizer" + inhand_icon_state = "nullrod" + +/obj/item/assembly/flash/handheld //this is now the regular pocket flashes + +/obj/item/assembly/flash/armimplant + name = "photon projector" + desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out." + var/flashcd = 20 + var/overheat = 0 + var/obj/item/organ/cyberimp/arm/flash/I = null + +/obj/item/assembly/flash/armimplant/burn_out() + if(I && I.owner) + to_chat(I.owner, "Your photon projector implant overheats and deactivates!") + I.Retract() + overheat = TRUE + addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2) + +/obj/item/assembly/flash/armimplant/try_use_flash(mob/user = null) + if(overheat) + if(I && I.owner) + to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!") + return FALSE + overheat = TRUE + addtimer(CALLBACK(src, .proc/cooldown), flashcd) + playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) + update_icon(1) + return TRUE + + +/obj/item/assembly/flash/armimplant/proc/cooldown() + overheat = FALSE + +/obj/item/assembly/flash/hypnotic + desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance." + flashing_overlay = "flash-hypno" + light_color = LIGHT_COLOR_PINK + cooldown = 20 + +/obj/item/assembly/flash/hypnotic/burn_out() + return + +/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) + if(!istype(M)) + return + if(user) + log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src) + else //caused by emp/remote signal + M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK) + if(generic_message && M != user) + to_chat(M, "[src] emits a soothing light...") + if(targeted) + if(M.flash_act(1, 1)) + var/hypnosis = FALSE + if(M.hypnosis_vulnerable()) + hypnosis = TRUE + if(user) + user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!") + + if(!hypnosis) + to_chat(M, "The light makes you feel oddly relaxed...") + M.confused += min(M.confused + 10, 20) + M.dizziness += min(M.dizziness + 10, 20) + M.drowsyness += min(M.drowsyness + 10, 20) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) + else + M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) + + else if(user) + user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!") + else + to_chat(M, "[src] fails to blind you!") + + else if(M.flash_act()) + to_chat(M, "Such a pretty light...") + M.confused += min(M.confused + 4, 20) + M.dizziness += min(M.dizziness + 4, 20) + M.drowsyness += min(M.drowsyness + 4, 20) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) diff --git a/code/modules/assembly/helpers.dm b/code/modules/assembly/helpers.dm index f1dc93b4465..2c39751a8bc 100644 --- a/code/modules/assembly/helpers.dm +++ b/code/modules/assembly/helpers.dm @@ -1,16 +1,16 @@ -// See _DEFINES/is_helpers.dm for type helpers - -/* -Name: IsSpecialAssembly -Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door -*/ - -/obj/proc/IsSpecialAssembly() - return FALSE - -/* -Name: IsAssemblyHolder -Desc: If true is an object that can hold an assemblyholder object -*/ -/obj/proc/IsAssemblyHolder() - return FALSE +// See _DEFINES/is_helpers.dm for type helpers + +/* +Name: IsSpecialAssembly +Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door +*/ + +/obj/proc/IsSpecialAssembly() + return FALSE + +/* +Name: IsAssemblyHolder +Desc: If true is an object that can hold an assemblyholder object +*/ +/obj/proc/IsAssemblyHolder() + return FALSE diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index 98310abe13a..2bf6787a336 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -1,145 +1,145 @@ -/obj/item/assembly_holder - name = "Assembly" - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = "holder" - inhand_icon_state = "assembly" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - throwforce = 5 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 2 - throw_range = 7 - - var/obj/item/assembly/a_left = null - var/obj/item/assembly/a_right = null - -/obj/item/assembly_holder/ComponentInitialize() - . = ..() - var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS - AddComponent(/datum/component/simple_rotation, rotation_flags) - -/obj/item/assembly_holder/IsAssemblyHolder() - return TRUE - - -/obj/item/assembly_holder/proc/assemble(obj/item/assembly/A, obj/item/assembly/A2, mob/user) - attach(A,user) - attach(A2,user) - name = "[A.name]-[A2.name] assembly" - update_icon() - SSblackbox.record_feedback("tally", "assembly_made", 1, "[initial(A.name)]-[initial(A2.name)]") - -/obj/item/assembly_holder/proc/attach(obj/item/assembly/A, mob/user) - if(!A.remove_item_from_storage(src)) - if(user) - user.transferItemToLoc(A, src) - else - A.forceMove(src) - A.holder = src - A.toggle_secure() - if(!a_left) - a_left = A - else - a_right = A - A.holder_movement() - -/obj/item/assembly_holder/update_icon() - cut_overlays() - if(a_left) - add_overlay("[a_left.icon_state]_left") - for(var/O in a_left.attached_overlays) - add_overlay("[O]_l") - - if(a_right) - if(a_right.is_position_sensitive) - add_overlay("[a_right.icon_state]_right") - for(var/O in a_right.attached_overlays) - add_overlay("[O]_r") - else - var/mutable_appearance/right = mutable_appearance(icon, "[a_right.icon_state]_left") - right.transform = matrix(-1, 0, 0, 0, 1, 0) - for(var/O in a_right.attached_overlays) - right.add_overlay("[O]_l") - add_overlay(right) - - if(master) - master.update_icon() - -/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj) - . = ..() - if(a_left) - a_left.Crossed(AM) - if(a_right) - a_right.Crossed(AM) - -/obj/item/assembly_holder/on_found(mob/finder) - if(a_left) - a_left.on_found(finder) - if(a_right) - a_right.on_found(finder) - -/obj/item/assembly_holder/setDir() - . = ..() - if(a_left) - a_left.holder_movement() - if(a_right) - a_right.holder_movement() - -/obj/item/assembly_holder/dropped(mob/user) - . = ..() - if(a_left) - a_left.dropped() - if(a_right) - a_right.dropped() - -/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess - . = ..() - if(.) - return - if(a_left) - a_left.attack_hand() - if(a_right) - a_right.attack_hand() - -/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool) - if(..()) - return TRUE - to_chat(user, "You disassemble [src]!") - if(a_left) - a_left.on_detach() - a_left = null - if(a_right) - a_right.on_detach() - a_right = null - qdel(src) - return TRUE - -/obj/item/assembly_holder/attack_self(mob/user) - src.add_fingerprint(user) - if(!a_left || !a_right) - to_chat(user, "Assembly part missing!") - return - if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code - switch(alert("Which side would you like to use?",,"Left","Right")) - if("Left") - a_left.attack_self(user) - if("Right") - a_right.attack_self(user) - return - else - a_left.attack_self(user) - a_right.attack_self(user) - - -/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1) - if(!D) - return FALSE - if((normal) && (a_right) && (a_left)) - if(a_right != D) - a_right.pulsed(FALSE) - if(a_left != D) - a_left.pulsed(FALSE) - if(master) - master.receive_signal() - return TRUE +/obj/item/assembly_holder + name = "Assembly" + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "holder" + inhand_icon_state = "assembly" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + throwforce = 5 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 2 + throw_range = 7 + + var/obj/item/assembly/a_left = null + var/obj/item/assembly/a_right = null + +/obj/item/assembly_holder/ComponentInitialize() + . = ..() + var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS + AddComponent(/datum/component/simple_rotation, rotation_flags) + +/obj/item/assembly_holder/IsAssemblyHolder() + return TRUE + + +/obj/item/assembly_holder/proc/assemble(obj/item/assembly/A, obj/item/assembly/A2, mob/user) + attach(A,user) + attach(A2,user) + name = "[A.name]-[A2.name] assembly" + update_icon() + SSblackbox.record_feedback("tally", "assembly_made", 1, "[initial(A.name)]-[initial(A2.name)]") + +/obj/item/assembly_holder/proc/attach(obj/item/assembly/A, mob/user) + if(!A.remove_item_from_storage(src)) + if(user) + user.transferItemToLoc(A, src) + else + A.forceMove(src) + A.holder = src + A.toggle_secure() + if(!a_left) + a_left = A + else + a_right = A + A.holder_movement() + +/obj/item/assembly_holder/update_icon() + cut_overlays() + if(a_left) + add_overlay("[a_left.icon_state]_left") + for(var/O in a_left.attached_overlays) + add_overlay("[O]_l") + + if(a_right) + if(a_right.is_position_sensitive) + add_overlay("[a_right.icon_state]_right") + for(var/O in a_right.attached_overlays) + add_overlay("[O]_r") + else + var/mutable_appearance/right = mutable_appearance(icon, "[a_right.icon_state]_left") + right.transform = matrix(-1, 0, 0, 0, 1, 0) + for(var/O in a_right.attached_overlays) + right.add_overlay("[O]_l") + add_overlay(right) + + if(master) + master.update_icon() + +/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj) + . = ..() + if(a_left) + a_left.Crossed(AM) + if(a_right) + a_right.Crossed(AM) + +/obj/item/assembly_holder/on_found(mob/finder) + if(a_left) + a_left.on_found(finder) + if(a_right) + a_right.on_found(finder) + +/obj/item/assembly_holder/setDir() + . = ..() + if(a_left) + a_left.holder_movement() + if(a_right) + a_right.holder_movement() + +/obj/item/assembly_holder/dropped(mob/user) + . = ..() + if(a_left) + a_left.dropped() + if(a_right) + a_right.dropped() + +/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess + . = ..() + if(.) + return + if(a_left) + a_left.attack_hand() + if(a_right) + a_right.attack_hand() + +/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool) + if(..()) + return TRUE + to_chat(user, "You disassemble [src]!") + if(a_left) + a_left.on_detach() + a_left = null + if(a_right) + a_right.on_detach() + a_right = null + qdel(src) + return TRUE + +/obj/item/assembly_holder/attack_self(mob/user) + src.add_fingerprint(user) + if(!a_left || !a_right) + to_chat(user, "Assembly part missing!") + return + if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code + switch(alert("Which side would you like to use?",,"Left","Right")) + if("Left") + a_left.attack_self(user) + if("Right") + a_right.attack_self(user) + return + else + a_left.attack_self(user) + a_right.attack_self(user) + + +/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1) + if(!D) + return FALSE + if((normal) && (a_right) && (a_left)) + if(a_right != D) + a_right.pulsed(FALSE) + if(a_left != D) + a_left.pulsed(FALSE) + if(master) + master.receive_signal() + return TRUE diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm index 330b441323b..e2c595cd46b 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -1,72 +1,72 @@ -#define EXPOSED_VOLUME 1000 -#define ROOM_TEMP 293 -#define MIN_FREEZE_TEMP 50 -#define MAX_FREEZE_TEMP 1000000 - -/obj/item/assembly/igniter - name = "igniter" - desc = "A small electronic device able to ignite combustible substances." - icon_state = "igniter" - custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) - var/datum/effect_system/spark_spread/sparks - heat = 1000 - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - -/obj/item/assembly/igniter/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - user.IgniteMob() - return FIRELOSS - -/obj/item/assembly/igniter/Initialize() - . = ..() - sparks = new - sparks.set_up(2, 0, src) - sparks.attach(src) - -/obj/item/assembly/igniter/Destroy() - if(sparks) - qdel(sparks) - sparks = null - . = ..() - -/obj/item/assembly/igniter/activate() - if(!..()) - return FALSE//Cooldown check - var/turf/location = get_turf(loc) - if(location) - location.hotspot_expose(heat, EXPOSED_VOLUME) - sparks.start() - return TRUE - -/obj/item/assembly/igniter/attack_self(mob/user) - activate() - add_fingerprint(user) - -/obj/item/assembly/igniter/ignition_effect(atom/A, mob/user) - . = "[user] fiddles with [src], and manages to light [A]." - activate() - add_fingerprint(user) - -//For the Condenser, which functions like the igniter but makes things colder. -/obj/item/assembly/igniter/condenser - name = "condenser" - desc = "A small electronic device able to chill their surroundings." - icon_state = "freezer" - custom_materials = list(/datum/material/iron=250, /datum/material/glass=300) - heat = 200 - -/obj/item/assembly/igniter/condenser/activate() - . = ..() - if(!.) - return //Cooldown check - var/turf/location = get_turf(loc) - if(location) - var/datum/gas_mixture/enviro = location.return_air() - enviro.temperature = clamp(min(ROOM_TEMP, enviro.temperature*0.85),MIN_FREEZE_TEMP,MAX_FREEZE_TEMP) - sparks.start() - -#undef EXPOSED_VOLUME -#undef ROOM_TEMP -#undef MIN_FREEZE_TEMP -#undef MAX_FREEZE_TEMP +#define EXPOSED_VOLUME 1000 +#define ROOM_TEMP 293 +#define MIN_FREEZE_TEMP 50 +#define MAX_FREEZE_TEMP 1000000 + +/obj/item/assembly/igniter + name = "igniter" + desc = "A small electronic device able to ignite combustible substances." + icon_state = "igniter" + custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) + var/datum/effect_system/spark_spread/sparks + heat = 1000 + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + +/obj/item/assembly/igniter/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + user.IgniteMob() + return FIRELOSS + +/obj/item/assembly/igniter/Initialize() + . = ..() + sparks = new + sparks.set_up(2, 0, src) + sparks.attach(src) + +/obj/item/assembly/igniter/Destroy() + if(sparks) + qdel(sparks) + sparks = null + . = ..() + +/obj/item/assembly/igniter/activate() + if(!..()) + return FALSE//Cooldown check + var/turf/location = get_turf(loc) + if(location) + location.hotspot_expose(heat, EXPOSED_VOLUME) + sparks.start() + return TRUE + +/obj/item/assembly/igniter/attack_self(mob/user) + activate() + add_fingerprint(user) + +/obj/item/assembly/igniter/ignition_effect(atom/A, mob/user) + . = "[user] fiddles with [src], and manages to light [A]." + activate() + add_fingerprint(user) + +//For the Condenser, which functions like the igniter but makes things colder. +/obj/item/assembly/igniter/condenser + name = "condenser" + desc = "A small electronic device able to chill their surroundings." + icon_state = "freezer" + custom_materials = list(/datum/material/iron=250, /datum/material/glass=300) + heat = 200 + +/obj/item/assembly/igniter/condenser/activate() + . = ..() + if(!.) + return //Cooldown check + var/turf/location = get_turf(loc) + if(location) + var/datum/gas_mixture/enviro = location.return_air() + enviro.temperature = clamp(min(ROOM_TEMP, enviro.temperature*0.85),MIN_FREEZE_TEMP,MAX_FREEZE_TEMP) + sparks.start() + +#undef EXPOSED_VOLUME +#undef ROOM_TEMP +#undef MIN_FREEZE_TEMP +#undef MAX_FREEZE_TEMP diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index ecf27d73502..d9330775e85 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -1,238 +1,238 @@ -/obj/item/assembly/infra - name = "infrared emitter" - desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted." - icon_state = "infrared" - custom_materials = list(/datum/material/iron=1000, /datum/material/glass=500) - is_position_sensitive = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 225 - var/ui_y = 110 - var/on = FALSE - var/visible = FALSE - var/maxlength = 8 - var/list/obj/effect/beam/i_beam/beams - var/olddir = 0 - var/turf/listeningTo - var/hearing_range = 3 - -/obj/item/assembly/infra/Initialize() - . = ..() - beams = list() - START_PROCESSING(SSobj, src) - -/obj/item/assembly/infra/ComponentInitialize() - . = ..() - var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS - AddComponent(/datum/component/simple_rotation, rotation_flags, after_rotation=CALLBACK(src,.proc/after_rotation)) - -/obj/item/assembly/infra/proc/after_rotation() - refreshBeam() - -/obj/item/assembly/infra/Destroy() - STOP_PROCESSING(SSobj, src) - listeningTo = null - QDEL_LIST(beams) - . = ..() - -/obj/item/assembly/infra/examine(mob/user) - . = ..() - . += "The infrared trigger is [on?"on":"off"]." - -/obj/item/assembly/infra/activate() - if(!..()) - return FALSE //Cooldown check - on = !on - refreshBeam() - update_icon() - return TRUE - -/obj/item/assembly/infra/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - refreshBeam() - else - QDEL_LIST(beams) - STOP_PROCESSING(SSobj, src) - update_icon() - return secured - -/obj/item/assembly/infra/update_icon() - cut_overlays() - attached_overlays = list() - if(on) - add_overlay("infrared_on") - attached_overlays += "infrared_on" - if(visible && secured) - add_overlay("infrared_visible") - attached_overlays += "infrared_visible" - - if(holder) - holder.update_icon() - return - -/obj/item/assembly/infra/dropped() - . = ..() - if(holder) - holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder - else - refreshBeam() - -/obj/item/assembly/infra/process() - if(!on || !secured) - refreshBeam() - return - -/obj/item/assembly/infra/proc/refreshBeam() - QDEL_LIST(beams) - if(throwing || !on || !secured) - return - if(holder) - if(holder.master) //incase the sensor is part of an assembly that's contained in another item, such as a single tank bomb - if(!holder.master.IsSpecialAssembly() || !isturf(holder.master.loc)) - return - else if(!isturf(holder.loc)) //else just check where the holder is - return - else if(!isturf(loc)) //or just where the fuck we are in general - return - var/turf/T = get_turf(src) - var/_dir = dir - var/turf/_T = get_step(T, _dir) - if(_T) - for(var/i in 1 to maxlength) - var/obj/effect/beam/i_beam/I = new(T) - if(istype(holder, /obj/item/assembly_holder)) - var/obj/item/assembly_holder/assembly_holder = holder - I.icon_state = "[initial(I.icon_state)]_[(assembly_holder.a_left == src) ? "l":"r"]" //Sync the offset of the beam with the position of the sensor. - else if(istype(holder, /obj/item/transfer_valve)) - I.icon_state = "[initial(I.icon_state)]_ttv" - I.density = TRUE - if(!I.Move(_T)) - qdel(I) - switchListener(_T) - break - I.density = FALSE - beams += I - I.master = src - I.setDir(_dir) - I.invisibility = visible? 0 : INVISIBILITY_ABSTRACT - T = _T - _T = get_step(_T, _dir) - CHECK_TICK - -/obj/item/assembly/infra/on_detach() - . = ..() - if(!.) - return - refreshBeam() - -/obj/item/assembly/infra/attack_hand() - . = ..() - refreshBeam() - -/obj/item/assembly/infra/Moved() - var/t = dir - . = ..() - setDir(t) - -/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - . = ..() - olddir = dir - -/obj/item/assembly/infra/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(!olddir) - return - setDir(olddir) - olddir = null - -/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location) - refreshBeam() - switchListener(location) - if(!secured || !on || next_activate > world.time) - return FALSE - pulse(FALSE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - next_activate = world.time + 30 - -/obj/item/assembly/infra/proc/switchListener(turf/newloc) - if(listeningTo == newloc) - return - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED) - RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit) - listeningTo = newloc - -/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender) - if(QDELETED(src)) - return - if(offender == src || istype(offender,/obj/effect/beam/i_beam)) - return - if (offender && isitem(offender)) - var/obj/item/I = offender - if (I.item_flags & ABSTRACT) - return - return refreshBeam() - -/obj/item/assembly/infra/setDir() - . = ..() - refreshBeam() - -/obj/item/assembly/infra/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/infra/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "InfraredEmitter", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/infra/ui_data(mob/user) - var/list/data = list() - data["on"] = on - data["visible"] = visible - return data - -/obj/item/assembly/infra/ui_act(action, params) - if(..()) - return - - switch(action) - if("power") - on = !on - . = TRUE - if("visibility") - visible = !visible - . = TRUE - - update_icon() - refreshBeam() - -/***************************IBeam*********************************/ - -/obj/effect/beam/i_beam - name = "infrared beam" - icon = 'icons/obj/projectiles.dmi' - icon_state = "ibeam" - anchored = TRUE - density = FALSE - pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW - var/obj/item/assembly/infra/master - -/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj) - . = ..() - if(istype(AM, /obj/effect/beam)) - return - if (isitem(AM)) - var/obj/item/I = AM - if (I.item_flags & ABSTRACT) - return - master.trigger_beam(AM, get_turf(src)) +/obj/item/assembly/infra + name = "infrared emitter" + desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted." + icon_state = "infrared" + custom_materials = list(/datum/material/iron=1000, /datum/material/glass=500) + is_position_sensitive = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 225 + var/ui_y = 110 + var/on = FALSE + var/visible = FALSE + var/maxlength = 8 + var/list/obj/effect/beam/i_beam/beams + var/olddir = 0 + var/turf/listeningTo + var/hearing_range = 3 + +/obj/item/assembly/infra/Initialize() + . = ..() + beams = list() + START_PROCESSING(SSobj, src) + +/obj/item/assembly/infra/ComponentInitialize() + . = ..() + var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS + AddComponent(/datum/component/simple_rotation, rotation_flags, after_rotation=CALLBACK(src,.proc/after_rotation)) + +/obj/item/assembly/infra/proc/after_rotation() + refreshBeam() + +/obj/item/assembly/infra/Destroy() + STOP_PROCESSING(SSobj, src) + listeningTo = null + QDEL_LIST(beams) + . = ..() + +/obj/item/assembly/infra/examine(mob/user) + . = ..() + . += "The infrared trigger is [on?"on":"off"]." + +/obj/item/assembly/infra/activate() + if(!..()) + return FALSE //Cooldown check + on = !on + refreshBeam() + update_icon() + return TRUE + +/obj/item/assembly/infra/toggle_secure() + secured = !secured + if(secured) + START_PROCESSING(SSobj, src) + refreshBeam() + else + QDEL_LIST(beams) + STOP_PROCESSING(SSobj, src) + update_icon() + return secured + +/obj/item/assembly/infra/update_icon() + cut_overlays() + attached_overlays = list() + if(on) + add_overlay("infrared_on") + attached_overlays += "infrared_on" + if(visible && secured) + add_overlay("infrared_visible") + attached_overlays += "infrared_visible" + + if(holder) + holder.update_icon() + return + +/obj/item/assembly/infra/dropped() + . = ..() + if(holder) + holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder + else + refreshBeam() + +/obj/item/assembly/infra/process() + if(!on || !secured) + refreshBeam() + return + +/obj/item/assembly/infra/proc/refreshBeam() + QDEL_LIST(beams) + if(throwing || !on || !secured) + return + if(holder) + if(holder.master) //incase the sensor is part of an assembly that's contained in another item, such as a single tank bomb + if(!holder.master.IsSpecialAssembly() || !isturf(holder.master.loc)) + return + else if(!isturf(holder.loc)) //else just check where the holder is + return + else if(!isturf(loc)) //or just where the fuck we are in general + return + var/turf/T = get_turf(src) + var/_dir = dir + var/turf/_T = get_step(T, _dir) + if(_T) + for(var/i in 1 to maxlength) + var/obj/effect/beam/i_beam/I = new(T) + if(istype(holder, /obj/item/assembly_holder)) + var/obj/item/assembly_holder/assembly_holder = holder + I.icon_state = "[initial(I.icon_state)]_[(assembly_holder.a_left == src) ? "l":"r"]" //Sync the offset of the beam with the position of the sensor. + else if(istype(holder, /obj/item/transfer_valve)) + I.icon_state = "[initial(I.icon_state)]_ttv" + I.density = TRUE + if(!I.Move(_T)) + qdel(I) + switchListener(_T) + break + I.density = FALSE + beams += I + I.master = src + I.setDir(_dir) + I.invisibility = visible? 0 : INVISIBILITY_ABSTRACT + T = _T + _T = get_step(_T, _dir) + CHECK_TICK + +/obj/item/assembly/infra/on_detach() + . = ..() + if(!.) + return + refreshBeam() + +/obj/item/assembly/infra/attack_hand() + . = ..() + refreshBeam() + +/obj/item/assembly/infra/Moved() + var/t = dir + . = ..() + setDir(t) + +/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + . = ..() + olddir = dir + +/obj/item/assembly/infra/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(!olddir) + return + setDir(olddir) + olddir = null + +/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location) + refreshBeam() + switchListener(location) + if(!secured || !on || next_activate > world.time) + return FALSE + pulse(FALSE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + next_activate = world.time + 30 + +/obj/item/assembly/infra/proc/switchListener(turf/newloc) + if(listeningTo == newloc) + return + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED) + RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit) + listeningTo = newloc + +/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender) + if(QDELETED(src)) + return + if(offender == src || istype(offender,/obj/effect/beam/i_beam)) + return + if (offender && isitem(offender)) + var/obj/item/I = offender + if (I.item_flags & ABSTRACT) + return + return refreshBeam() + +/obj/item/assembly/infra/setDir() + . = ..() + refreshBeam() + +/obj/item/assembly/infra/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/infra/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "InfraredEmitter", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/assembly/infra/ui_data(mob/user) + var/list/data = list() + data["on"] = on + data["visible"] = visible + return data + +/obj/item/assembly/infra/ui_act(action, params) + if(..()) + return + + switch(action) + if("power") + on = !on + . = TRUE + if("visibility") + visible = !visible + . = TRUE + + update_icon() + refreshBeam() + +/***************************IBeam*********************************/ + +/obj/effect/beam/i_beam + name = "infrared beam" + icon = 'icons/obj/projectiles.dmi' + icon_state = "ibeam" + anchored = TRUE + density = FALSE + pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW + var/obj/item/assembly/infra/master + +/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj) + . = ..() + if(istype(AM, /obj/effect/beam)) + return + if (isitem(AM)) + var/obj/item/I = AM + if (I.item_flags & ABSTRACT) + return + master.trigger_beam(AM, get_turf(src)) diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index 6cecc6f57b4..a4febbaecaa 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -1,144 +1,144 @@ -/obj/item/assembly/mousetrap - name = "mousetrap" - desc = "A handy little spring-loaded trap for catching pesty rodents." - icon_state = "mousetrap" - inhand_icon_state = "mousetrap" - custom_materials = list(/datum/material/iron=100) - attachable = TRUE - var/armed = FALSE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - - -/obj/item/assembly/mousetrap/examine(mob/user) - . = ..() - . += "The pressure plate is [armed?"primed":"safe"]." - -/obj/item/assembly/mousetrap/activate() - if(..()) - armed = !armed - if(!armed) - if(ishuman(usr)) - var/mob/living/carbon/human/user = usr - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - to_chat(user, "Your hand slips, setting off the trigger!") - pulse(FALSE) - update_icon() - playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) - -/obj/item/assembly/mousetrap/update_icon() - if(armed) - icon_state = "mousetraparmed" - else - icon_state = "mousetrap" - if(holder) - holder.update_icon() - -/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet") - if(!armed) - return - var/obj/item/bodypart/affecting = null - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - armed = FALSE - update_icon() - pulse(FALSE) - return FALSE - switch(type) - if("feet") - if(!H.shoes) - affecting = H.get_bodypart(pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - H.Paralyze(60) - if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) - if(!H.gloves) - affecting = H.get_bodypart(type) - H.Stun(60) - if(affecting) - if(affecting.receive_damage(1, 0)) - H.update_damage_overlays() - else if(ismouse(target)) - var/mob/living/simple_animal/mouse/M = target - visible_message("SPLAT!") - M.splat() - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - armed = FALSE - update_icon() - pulse(FALSE) - - -/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user) - if(!armed) - to_chat(user, "You arm [src].") - else - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - var/which_hand = BODY_ZONE_PRECISE_L_HAND - if(!(user.active_hand_index % 2)) - which_hand = BODY_ZONE_PRECISE_R_HAND - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - return - to_chat(user, "You disarm [src].") - armed = !armed - update_icon() - playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) - - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user) - if(armed) - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - var/which_hand = BODY_ZONE_PRECISE_L_HAND - if(!(user.active_hand_index % 2)) - which_hand = BODY_ZONE_PRECISE_R_HAND - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - return - return ..() - - -/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj) - if(armed) - if(ismob(AM)) - var/mob/MM = AM - if(!(MM.movement_type & FLYING)) - if(ishuman(AM)) - var/mob/living/carbon/H = AM - if(H.m_intent == MOVE_INTENT_RUN) - triggered(H) - H.visible_message("[H] accidentally steps on [src].", \ - "You accidentally step on [src]") - else if(ismouse(MM)) - triggered(MM) - else if(AM.density) // For mousetrap grenades, set off by anything heavy - triggered(AM) - ..() - - -/obj/item/assembly/mousetrap/on_found(mob/finder) - if(armed) - if(finder) - finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND) - return TRUE //end the search! - else - visible_message("[src] snaps shut!") - triggered(loc) - return FALSE - return FALSE - - -/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(!armed) - return ..() - visible_message("[src] is triggered by [AM].") - triggered(null) - - -/obj/item/assembly/mousetrap/armed - icon_state = "mousetraparmed" - armed = TRUE +/obj/item/assembly/mousetrap + name = "mousetrap" + desc = "A handy little spring-loaded trap for catching pesty rodents." + icon_state = "mousetrap" + inhand_icon_state = "mousetrap" + custom_materials = list(/datum/material/iron=100) + attachable = TRUE + var/armed = FALSE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + + +/obj/item/assembly/mousetrap/examine(mob/user) + . = ..() + . += "The pressure plate is [armed?"primed":"safe"]." + +/obj/item/assembly/mousetrap/activate() + if(..()) + armed = !armed + if(!armed) + if(ishuman(usr)) + var/mob/living/carbon/human/user = usr + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + to_chat(user, "Your hand slips, setting off the trigger!") + pulse(FALSE) + update_icon() + playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) + +/obj/item/assembly/mousetrap/update_icon() + if(armed) + icon_state = "mousetraparmed" + else + icon_state = "mousetrap" + if(holder) + holder.update_icon() + +/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet") + if(!armed) + return + var/obj/item/bodypart/affecting = null + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + armed = FALSE + update_icon() + pulse(FALSE) + return FALSE + switch(type) + if("feet") + if(!H.shoes) + affecting = H.get_bodypart(pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + H.Paralyze(60) + if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) + if(!H.gloves) + affecting = H.get_bodypart(type) + H.Stun(60) + if(affecting) + if(affecting.receive_damage(1, 0)) + H.update_damage_overlays() + else if(ismouse(target)) + var/mob/living/simple_animal/mouse/M = target + visible_message("SPLAT!") + M.splat() + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + armed = FALSE + update_icon() + pulse(FALSE) + + +/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user) + if(!armed) + to_chat(user, "You arm [src].") + else + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + var/which_hand = BODY_ZONE_PRECISE_L_HAND + if(!(user.active_hand_index % 2)) + which_hand = BODY_ZONE_PRECISE_R_HAND + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + return + to_chat(user, "You disarm [src].") + armed = !armed + update_icon() + playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) + + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user) + if(armed) + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + var/which_hand = BODY_ZONE_PRECISE_L_HAND + if(!(user.active_hand_index % 2)) + which_hand = BODY_ZONE_PRECISE_R_HAND + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + return + return ..() + + +/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj) + if(armed) + if(ismob(AM)) + var/mob/MM = AM + if(!(MM.movement_type & FLYING)) + if(ishuman(AM)) + var/mob/living/carbon/H = AM + if(H.m_intent == MOVE_INTENT_RUN) + triggered(H) + H.visible_message("[H] accidentally steps on [src].", \ + "You accidentally step on [src]") + else if(ismouse(MM)) + triggered(MM) + else if(AM.density) // For mousetrap grenades, set off by anything heavy + triggered(AM) + ..() + + +/obj/item/assembly/mousetrap/on_found(mob/finder) + if(armed) + if(finder) + finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND) + return TRUE //end the search! + else + visible_message("[src] snaps shut!") + triggered(loc) + return FALSE + return FALSE + + +/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(!armed) + return ..() + visible_message("[src] is triggered by [AM].") + triggered(null) + + +/obj/item/assembly/mousetrap/armed + icon_state = "mousetraparmed" + armed = TRUE diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index 285d9c3599e..ef931279dea 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -1,156 +1,156 @@ -/obj/item/assembly/prox_sensor - name = "proximity sensor" - desc = "Used for scanning and alerting when someone enters a certain proximity." - icon_state = "prox" - custom_materials = list(/datum/material/iron=800, /datum/material/glass=200) - attachable = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 250 - var/ui_y = 185 - var/scanning = FALSE - var/timing = FALSE - var/time = 10 - var/sensitivity = 1 - var/hearing_range = 3 - -/obj/item/assembly/prox_sensor/Initialize() - . = ..() - proximity_monitor = new(src, 0) - START_PROCESSING(SSobj, src) - -/obj/item/assembly/prox_sensor/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/assembly/prox_sensor/examine(mob/user) - . = ..() - . += "The proximity sensor is [timing ? "arming" : (scanning ? "armed" : "disarmed")]." - -/obj/item/assembly/prox_sensor/activate() - if(!..()) - return FALSE //Cooldown check - if(!scanning) - timing = !timing - else - scanning = FALSE - update_icon() - return TRUE - -/obj/item/assembly/prox_sensor/on_detach() - . = ..() - if(!.) - return - else - proximity_monitor.SetHost(src,src) - -/obj/item/assembly/prox_sensor/toggle_secure() - secured = !secured - if(!secured) - if(scanning) - toggle_scan() - proximity_monitor.SetHost(src,src) - timing = FALSE - STOP_PROCESSING(SSobj, src) - else - START_PROCESSING(SSobj, src) - proximity_monitor.SetHost(loc,src) - update_icon() - return secured - -/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj) - if (istype(AM, /obj/effect/beam)) - return - sense() - -/obj/item/assembly/prox_sensor/proc/sense() - if(!scanning || !secured || next_activate > world.time) - return FALSE - pulse(FALSE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - next_activate = world.time + 30 - return TRUE - -/obj/item/assembly/prox_sensor/process() - if(!timing) - return - time-- - if(time <= 0) - timing = FALSE - toggle_scan(TRUE) - time = initial(time) - -/obj/item/assembly/prox_sensor/proc/toggle_scan(scan) - if(!secured) - return FALSE - scanning = scan - proximity_monitor.SetRange(scanning ? sensitivity : 0) - update_icon() - -/obj/item/assembly/prox_sensor/proc/sensitivity_change(value) - var/sense = min(max(sensitivity + value, 0), 5) - sensitivity = sense - if(scanning && proximity_monitor.SetRange(sense)) - sense() - -/obj/item/assembly/prox_sensor/update_icon() - cut_overlays() - attached_overlays = list() - if(timing) - add_overlay("prox_timing") - attached_overlays += "prox_timing" - if(scanning) - add_overlay("prox_scanning") - attached_overlays += "prox_scanning" - if(holder) - holder.update_icon() - return - -/obj/item/assembly/prox_sensor/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/prox_sensor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "ProximitySensor", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/prox_sensor/ui_data(mob/user) - var/list/data = list() - data["seconds"] = round(time % 60) - data["minutes"] = round((time - data["seconds"]) / 60) - data["timing"] = timing - data["scanning"] = scanning - data["sensitivity"] = sensitivity - return data - -/obj/item/assembly/prox_sensor/ui_act(action, params) - if(..()) - return - - switch(action) - if("scanning") - toggle_scan(!scanning) - . = TRUE - if("sense") - var/value = text2num(params["range"]) - if(value) - sensitivity_change(value) - . = TRUE - if("time") - timing = !timing - update_icon() - . = TRUE - if("input") - var/value = text2num(params["adjust"]) - if(value) - value = round(time + value) - time = clamp(value, 0, 600) - . = TRUE +/obj/item/assembly/prox_sensor + name = "proximity sensor" + desc = "Used for scanning and alerting when someone enters a certain proximity." + icon_state = "prox" + custom_materials = list(/datum/material/iron=800, /datum/material/glass=200) + attachable = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 250 + var/ui_y = 185 + var/scanning = FALSE + var/timing = FALSE + var/time = 10 + var/sensitivity = 1 + var/hearing_range = 3 + +/obj/item/assembly/prox_sensor/Initialize() + . = ..() + proximity_monitor = new(src, 0) + START_PROCESSING(SSobj, src) + +/obj/item/assembly/prox_sensor/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/assembly/prox_sensor/examine(mob/user) + . = ..() + . += "The proximity sensor is [timing ? "arming" : (scanning ? "armed" : "disarmed")]." + +/obj/item/assembly/prox_sensor/activate() + if(!..()) + return FALSE //Cooldown check + if(!scanning) + timing = !timing + else + scanning = FALSE + update_icon() + return TRUE + +/obj/item/assembly/prox_sensor/on_detach() + . = ..() + if(!.) + return + else + proximity_monitor.SetHost(src,src) + +/obj/item/assembly/prox_sensor/toggle_secure() + secured = !secured + if(!secured) + if(scanning) + toggle_scan() + proximity_monitor.SetHost(src,src) + timing = FALSE + STOP_PROCESSING(SSobj, src) + else + START_PROCESSING(SSobj, src) + proximity_monitor.SetHost(loc,src) + update_icon() + return secured + +/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj) + if (istype(AM, /obj/effect/beam)) + return + sense() + +/obj/item/assembly/prox_sensor/proc/sense() + if(!scanning || !secured || next_activate > world.time) + return FALSE + pulse(FALSE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + next_activate = world.time + 30 + return TRUE + +/obj/item/assembly/prox_sensor/process() + if(!timing) + return + time-- + if(time <= 0) + timing = FALSE + toggle_scan(TRUE) + time = initial(time) + +/obj/item/assembly/prox_sensor/proc/toggle_scan(scan) + if(!secured) + return FALSE + scanning = scan + proximity_monitor.SetRange(scanning ? sensitivity : 0) + update_icon() + +/obj/item/assembly/prox_sensor/proc/sensitivity_change(value) + var/sense = min(max(sensitivity + value, 0), 5) + sensitivity = sense + if(scanning && proximity_monitor.SetRange(sense)) + sense() + +/obj/item/assembly/prox_sensor/update_icon() + cut_overlays() + attached_overlays = list() + if(timing) + add_overlay("prox_timing") + attached_overlays += "prox_timing" + if(scanning) + add_overlay("prox_scanning") + attached_overlays += "prox_scanning" + if(holder) + holder.update_icon() + return + +/obj/item/assembly/prox_sensor/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/prox_sensor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "ProximitySensor", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/assembly/prox_sensor/ui_data(mob/user) + var/list/data = list() + data["seconds"] = round(time % 60) + data["minutes"] = round((time - data["seconds"]) / 60) + data["timing"] = timing + data["scanning"] = scanning + data["sensitivity"] = sensitivity + return data + +/obj/item/assembly/prox_sensor/ui_act(action, params) + if(..()) + return + + switch(action) + if("scanning") + toggle_scan(!scanning) + . = TRUE + if("sense") + var/value = text2num(params["range"]) + if(value) + sensitivity_change(value) + . = TRUE + if("time") + timing = !timing + update_icon() + . = TRUE + if("input") + var/value = text2num(params["adjust"]) + if(value) + value = round(time + value) + time = clamp(value, 0, 600) + . = TRUE diff --git a/code/modules/assembly/shock_kit.dm b/code/modules/assembly/shock_kit.dm index 7a1c00bfc2f..dbca939b86e 100644 --- a/code/modules/assembly/shock_kit.dm +++ b/code/modules/assembly/shock_kit.dm @@ -1,40 +1,40 @@ -/obj/item/assembly/shock_kit - name = "electrohelmet assembly" - desc = "This appears to be made from both an electropack and a helmet." - icon = 'icons/obj/assemblies.dmi' - icon_state = "shock_kit" - var/obj/item/clothing/head/helmet/part1 = null - var/obj/item/electropack/part2 = null - w_class = WEIGHT_CLASS_HUGE - flags_1 = CONDUCT_1 - -/obj/item/assembly/shock_kit/Destroy() - qdel(part1) - qdel(part2) - return ..() - -/obj/item/assembly/shock_kit/wrench_act(mob/living/user, obj/item/I) - ..() - to_chat(user, "You disassemble [src].") - if(part1) - part1.forceMove(drop_location()) - part1.master = null - part1 = null - if(part2) - part2.forceMove(drop_location()) - part2.master = null - part2 = null - qdel(src) - return TRUE - -/obj/item/assembly/shock_kit/attack_self(mob/user) - part1.attack_self(user) - part2.attack_self(user) - add_fingerprint(user) - return - -/obj/item/assembly/shock_kit/receive_signal() - if(istype(loc, /obj/structure/chair/e_chair)) - var/obj/structure/chair/e_chair/C = loc - C.shock() - return +/obj/item/assembly/shock_kit + name = "electrohelmet assembly" + desc = "This appears to be made from both an electropack and a helmet." + icon = 'icons/obj/assemblies.dmi' + icon_state = "shock_kit" + var/obj/item/clothing/head/helmet/part1 = null + var/obj/item/electropack/part2 = null + w_class = WEIGHT_CLASS_HUGE + flags_1 = CONDUCT_1 + +/obj/item/assembly/shock_kit/Destroy() + qdel(part1) + qdel(part2) + return ..() + +/obj/item/assembly/shock_kit/wrench_act(mob/living/user, obj/item/I) + ..() + to_chat(user, "You disassemble [src].") + if(part1) + part1.forceMove(drop_location()) + part1.master = null + part1 = null + if(part2) + part2.forceMove(drop_location()) + part2.master = null + part2 = null + qdel(src) + return TRUE + +/obj/item/assembly/shock_kit/attack_self(mob/user) + part1.attack_self(user) + part2.attack_self(user) + add_fingerprint(user) + return + +/obj/item/assembly/shock_kit/receive_signal() + if(istype(loc, /obj/structure/chair/e_chair)) + var/obj/structure/chair/e_chair/C = loc + C.shock() + return diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index f9f35fdbe38..c52689df336 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -1,189 +1,189 @@ -/obj/item/assembly/signaler - name = "remote signaling device" - desc = "Used to remotely activate devices. Allows for syncing when using a secure signaler on another." - icon_state = "signaller" - inhand_icon_state = "signaler" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - custom_materials = list(/datum/material/iron=400, /datum/material/glass=120) - wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE - attachable = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 280 - var/ui_y = 132 - var/code = DEFAULT_SIGNALER_CODE - var/frequency = FREQ_SIGNALER - var/datum/radio_frequency/radio_connection - ///Holds the mind that commited suicide. - var/datum/mind/suicider - ///Holds a reference string to the mob, decides how much of a gamer you are. - var/suicide_mob - var/hearing_range = 1 - -/obj/item/assembly/signaler/suicide_act(mob/living/carbon/user) - user.visible_message("[user] eats \the [src]! If it is signaled, [user.p_they()] will die!") - playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) - moveToNullspace() - suicider = user.mind - suicide_mob = REF(user) - return MANUAL_SUICIDE_NONLETHAL - -/obj/item/assembly/signaler/proc/manual_suicide(datum/mind/suicidee) - var/mob/living/user = suicidee.current - if(!istype(user)) - return - if(suicide_mob == REF(user)) - user.visible_message("[user]'s [src] receives a signal, killing [user.p_them()] instantly!") - else - user.visible_message("[user]'s [src] receives a signal and [user.p_they()] die[user.p_s()] like a gamer!") - user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something. - user.death(0) - user.set_suicide(TRUE) - user.suicide_log() - playsound(user, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - qdel(src) - -/obj/item/assembly/signaler/Initialize() - . = ..() - set_frequency(frequency) - -/obj/item/assembly/signaler/Destroy() - SSradio.remove_object(src,frequency) - suicider = null - . = ..() - -/obj/item/assembly/signaler/activate() - if(!..())//cooldown processing - return FALSE - signal() - return TRUE - -/obj/item/assembly/signaler/update_icon() - if(holder) - holder.update_icon() - return - -/obj/item/assembly/signaler/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/signaler/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Signaler", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/signaler/ui_data(mob/user) - var/list/data = list() - data["frequency"] = frequency - data["code"] = code - data["minFrequency"] = MIN_FREE_FREQ - data["maxFrequency"] = MAX_FREE_FREQ - return data - -/obj/item/assembly/signaler/ui_act(action, params) - if(..()) - return - - switch(action) - if("signal") - INVOKE_ASYNC(src, .proc/signal) - . = TRUE - if("freq") - frequency = unformat_frequency(params["freq"]) - frequency = sanitize_frequency(frequency, TRUE) - set_frequency(frequency) - . = TRUE - if("code") - code = text2num(params["code"]) - code = round(code) - . = TRUE - if("reset") - if(params["reset"] == "freq") - frequency = initial(frequency) - else - code = initial(code) - . = TRUE - - update_icon() - -/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params) - if(issignaler(W)) - var/obj/item/assembly/signaler/signaler2 = W - if(secured && signaler2.secured) - code = signaler2.code - set_frequency(signaler2.frequency) - to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]") - ..() - -/obj/item/assembly/signaler/proc/signal() - if(!radio_connection) - return - - var/datum/signal/signal = new(list("code" = code)) - radio_connection.post_signal(src, signal) - - var/time = time2text(world.realtime,"hh:mm:ss") - var/turf/T = get_turf(src) - if(usr) - GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") - -/obj/item/assembly/signaler/receive_signal(datum/signal/signal) - . = FALSE - if(!signal) - return - if(signal.data["code"] != code) - return - if(!(src.wires & WIRE_RADIO_RECEIVE)) - return - if(suicider) - manual_suicide(suicider) - return - pulse(TRUE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - return TRUE - -/obj/item/assembly/signaler/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER) - return - -// Embedded signaller used in grenade construction. -// It's necessary because the signaler doens't have an off state. -// Generated during grenade construction. -Sayu -/obj/item/assembly/signaler/receiver - var/on = FALSE - -/obj/item/assembly/signaler/receiver/proc/toggle_safety() - on = !on - -/obj/item/assembly/signaler/receiver/activate() - toggle_safety() - return TRUE - -/obj/item/assembly/signaler/receiver/examine(mob/user) - . = ..() - . += "The radio receiver is [on?"on":"off"]." - -/obj/item/assembly/signaler/receiver/receive_signal(datum/signal/signal) - if(!on) - return - return ..(signal) - -/obj/item/assembly/signaler/anomaly/attack_self() - return - -/obj/item/assembly/signaler/cyborg - -/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) - return -/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return +/obj/item/assembly/signaler + name = "remote signaling device" + desc = "Used to remotely activate devices. Allows for syncing when using a secure signaler on another." + icon_state = "signaller" + inhand_icon_state = "signaler" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + custom_materials = list(/datum/material/iron=400, /datum/material/glass=120) + wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE + attachable = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 280 + var/ui_y = 132 + var/code = DEFAULT_SIGNALER_CODE + var/frequency = FREQ_SIGNALER + var/datum/radio_frequency/radio_connection + ///Holds the mind that commited suicide. + var/datum/mind/suicider + ///Holds a reference string to the mob, decides how much of a gamer you are. + var/suicide_mob + var/hearing_range = 1 + +/obj/item/assembly/signaler/suicide_act(mob/living/carbon/user) + user.visible_message("[user] eats \the [src]! If it is signaled, [user.p_they()] will die!") + playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) + moveToNullspace() + suicider = user.mind + suicide_mob = REF(user) + return MANUAL_SUICIDE_NONLETHAL + +/obj/item/assembly/signaler/proc/manual_suicide(datum/mind/suicidee) + var/mob/living/user = suicidee.current + if(!istype(user)) + return + if(suicide_mob == REF(user)) + user.visible_message("[user]'s [src] receives a signal, killing [user.p_them()] instantly!") + else + user.visible_message("[user]'s [src] receives a signal and [user.p_they()] die[user.p_s()] like a gamer!") + user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something. + user.death(0) + user.set_suicide(TRUE) + user.suicide_log() + playsound(user, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + qdel(src) + +/obj/item/assembly/signaler/Initialize() + . = ..() + set_frequency(frequency) + +/obj/item/assembly/signaler/Destroy() + SSradio.remove_object(src,frequency) + suicider = null + . = ..() + +/obj/item/assembly/signaler/activate() + if(!..())//cooldown processing + return FALSE + signal() + return TRUE + +/obj/item/assembly/signaler/update_icon() + if(holder) + holder.update_icon() + return + +/obj/item/assembly/signaler/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/signaler/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Signaler", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/assembly/signaler/ui_data(mob/user) + var/list/data = list() + data["frequency"] = frequency + data["code"] = code + data["minFrequency"] = MIN_FREE_FREQ + data["maxFrequency"] = MAX_FREE_FREQ + return data + +/obj/item/assembly/signaler/ui_act(action, params) + if(..()) + return + + switch(action) + if("signal") + INVOKE_ASYNC(src, .proc/signal) + . = TRUE + if("freq") + frequency = unformat_frequency(params["freq"]) + frequency = sanitize_frequency(frequency, TRUE) + set_frequency(frequency) + . = TRUE + if("code") + code = text2num(params["code"]) + code = round(code) + . = TRUE + if("reset") + if(params["reset"] == "freq") + frequency = initial(frequency) + else + code = initial(code) + . = TRUE + + update_icon() + +/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params) + if(issignaler(W)) + var/obj/item/assembly/signaler/signaler2 = W + if(secured && signaler2.secured) + code = signaler2.code + set_frequency(signaler2.frequency) + to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]") + ..() + +/obj/item/assembly/signaler/proc/signal() + if(!radio_connection) + return + + var/datum/signal/signal = new(list("code" = code)) + radio_connection.post_signal(src, signal) + + var/time = time2text(world.realtime,"hh:mm:ss") + var/turf/T = get_turf(src) + if(usr) + GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") + +/obj/item/assembly/signaler/receive_signal(datum/signal/signal) + . = FALSE + if(!signal) + return + if(signal.data["code"] != code) + return + if(!(src.wires & WIRE_RADIO_RECEIVE)) + return + if(suicider) + manual_suicide(suicider) + return + pulse(TRUE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + return TRUE + +/obj/item/assembly/signaler/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER) + return + +// Embedded signaller used in grenade construction. +// It's necessary because the signaler doens't have an off state. +// Generated during grenade construction. -Sayu +/obj/item/assembly/signaler/receiver + var/on = FALSE + +/obj/item/assembly/signaler/receiver/proc/toggle_safety() + on = !on + +/obj/item/assembly/signaler/receiver/activate() + toggle_safety() + return TRUE + +/obj/item/assembly/signaler/receiver/examine(mob/user) + . = ..() + . += "The radio receiver is [on?"on":"off"]." + +/obj/item/assembly/signaler/receiver/receive_signal(datum/signal/signal) + if(!on) + return + return ..(signal) + +/obj/item/assembly/signaler/anomaly/attack_self() + return + +/obj/item/assembly/signaler/cyborg + +/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) + return +/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) + return diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm index 2ebc929eb65..3224b2d3737 100644 --- a/code/modules/assembly/timer.dm +++ b/code/modules/assembly/timer.dm @@ -1,128 +1,128 @@ -/obj/item/assembly/timer - name = "timer" - desc = "Used to time things. Works well with contraptions which has to count down. Tick tock." - icon_state = "timer" - custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) - attachable = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 275 - var/ui_y = 115 - var/timing = FALSE - var/time = 5 - var/saved_time = 5 - var/loop = FALSE - var/hearing_range = 3 - -/obj/item/assembly/timer/suicide_act(mob/living/user) - user.visible_message("[user] looks at the timer and decides [user.p_their()] fate! It looks like [user.p_theyre()] going to commit suicide!") - activate()//doesnt rely on timer_end to prevent weird metas where one person can control the timer and therefore someone's life. (maybe that should be how it works...) - addtimer(CALLBACK(src, .proc/manual_suicide, user), time*10)//kill yourself once the time runs out - return MANUAL_SUICIDE - -/obj/item/assembly/timer/proc/manual_suicide(mob/living/user) - user.visible_message("[user]'s time is up!") - user.adjustOxyLoss(200) - user.death(0) - -/obj/item/assembly/timer/Initialize() - . = ..() - START_PROCESSING(SSobj, src) - -/obj/item/assembly/timer/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/assembly/timer/examine(mob/user) - . = ..() - . += "The timer is [timing ? "counting down from [time]":"set for [time] seconds"]." - -/obj/item/assembly/timer/activate() - if(!..()) - return FALSE//Cooldown check - timing = !timing - update_icon() - return TRUE - -/obj/item/assembly/timer/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - else - timing = FALSE - STOP_PROCESSING(SSobj, src) - update_icon() - return secured - -/obj/item/assembly/timer/proc/timer_end() - if(!secured || next_activate > world.time) - return FALSE - pulse(FALSE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - if(loop) - timing = TRUE - update_icon() - -/obj/item/assembly/timer/process() - if(!timing) - return - time-- - if(time <= 0) - timing = FALSE - timer_end() - time = saved_time - -/obj/item/assembly/timer/update_icon() - cut_overlays() - attached_overlays = list() - if(timing) - add_overlay("timer_timing") - attached_overlays += "timer_timing" - if(holder) - holder.update_icon() - -/obj/item/assembly/timer/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Timer", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/timer/ui_data(mob/user) - var/list/data = list() - data["seconds"] = round(time % 60) - data["minutes"] = round((time - data["seconds"]) / 60) - data["timing"] = timing - data["loop"] = loop - return data - -/obj/item/assembly/timer/ui_act(action, params) - if(..()) - return - - switch(action) - if("time") - timing = !timing - if(timing && istype(holder, /obj/item/transfer_valve)) - log_bomber(usr, "activated a", src, "attachment on [holder]") - update_icon() - . = TRUE - if("repeat") - loop = !loop - . = TRUE - if("input") - var/value = text2num(params["adjust"]) - if(value) - value = round(time + value) - time = clamp(value, 1, 600) - saved_time = time - . = TRUE +/obj/item/assembly/timer + name = "timer" + desc = "Used to time things. Works well with contraptions which has to count down. Tick tock." + icon_state = "timer" + custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) + attachable = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 275 + var/ui_y = 115 + var/timing = FALSE + var/time = 5 + var/saved_time = 5 + var/loop = FALSE + var/hearing_range = 3 + +/obj/item/assembly/timer/suicide_act(mob/living/user) + user.visible_message("[user] looks at the timer and decides [user.p_their()] fate! It looks like [user.p_theyre()] going to commit suicide!") + activate()//doesnt rely on timer_end to prevent weird metas where one person can control the timer and therefore someone's life. (maybe that should be how it works...) + addtimer(CALLBACK(src, .proc/manual_suicide, user), time*10)//kill yourself once the time runs out + return MANUAL_SUICIDE + +/obj/item/assembly/timer/proc/manual_suicide(mob/living/user) + user.visible_message("[user]'s time is up!") + user.adjustOxyLoss(200) + user.death(0) + +/obj/item/assembly/timer/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/assembly/timer/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/assembly/timer/examine(mob/user) + . = ..() + . += "The timer is [timing ? "counting down from [time]":"set for [time] seconds"]." + +/obj/item/assembly/timer/activate() + if(!..()) + return FALSE//Cooldown check + timing = !timing + update_icon() + return TRUE + +/obj/item/assembly/timer/toggle_secure() + secured = !secured + if(secured) + START_PROCESSING(SSobj, src) + else + timing = FALSE + STOP_PROCESSING(SSobj, src) + update_icon() + return secured + +/obj/item/assembly/timer/proc/timer_end() + if(!secured || next_activate > world.time) + return FALSE + pulse(FALSE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + if(loop) + timing = TRUE + update_icon() + +/obj/item/assembly/timer/process() + if(!timing) + return + time-- + if(time <= 0) + timing = FALSE + timer_end() + time = saved_time + +/obj/item/assembly/timer/update_icon() + cut_overlays() + attached_overlays = list() + if(timing) + add_overlay("timer_timing") + attached_overlays += "timer_timing" + if(holder) + holder.update_icon() + +/obj/item/assembly/timer/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Timer", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/assembly/timer/ui_data(mob/user) + var/list/data = list() + data["seconds"] = round(time % 60) + data["minutes"] = round((time - data["seconds"]) / 60) + data["timing"] = timing + data["loop"] = loop + return data + +/obj/item/assembly/timer/ui_act(action, params) + if(..()) + return + + switch(action) + if("time") + timing = !timing + if(timing && istype(holder, /obj/item/transfer_valve)) + log_bomber(usr, "activated a", src, "attachment on [holder]") + update_icon() + . = TRUE + if("repeat") + loop = !loop + . = TRUE + if("input") + var/value = text2num(params["adjust"]) + if(value) + value = round(time + value) + time = clamp(value, 1, 600) + saved_time = time + . = TRUE diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index dd572b83d69..4606104d999 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -1,104 +1,104 @@ -#define INCLUSIVE_MODE 1 -#define EXCLUSIVE_MODE 2 -#define RECOGNIZER_MODE 3 -#define VOICE_SENSOR_MODE 4 - -/obj/item/assembly/voice - name = "voice analyzer" - desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." - icon_state = "voice" - custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) - flags_1 = HEAR_1 - attachable = TRUE - verb_say = "beeps" - verb_ask = "beeps" - verb_exclaim = "beeps" - var/listening = FALSE - var/recorded = "" //the activation message - var/mode = 1 - var/static/list/modes = list("inclusive", - "exclusive", - "recognizer", - "voice sensor") - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - -/obj/item/assembly/voice/examine(mob/user) - . = ..() - . += "Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode." - -/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker == src) - return - - if(listening && !radio_freq) - record_speech(speaker, raw_message, message_language) - else - if(check_activation(speaker, raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) - switch(mode) - if(INCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(EXCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(RECOGNIZER_MODE) - recorded = speaker.GetVoice() - listening = FALSE - say("Your voice pattern is saved.", message_language) - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) - . = FALSE - switch(mode) - if(INCLUSIVE_MODE) - if(findtext(raw_message, recorded)) - . = TRUE - if(EXCLUSIVE_MODE) - if(raw_message == recorded) - . = TRUE - if(RECOGNIZER_MODE) - if(speaker.GetVoice() == recorded) - . = TRUE - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - . = TRUE - -/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) - ..() - mode %= modes.len - mode++ - to_chat(user, "You set [src] into [modes[mode]] mode.") - listening = FALSE - recorded = "" - return TRUE - -/obj/item/assembly/voice/activate() - if(!secured || holder) - return FALSE - listening = !listening - say("[listening ? "Now" : "No longer"] recording input.") - return TRUE - -/obj/item/assembly/voice/attack_self(mob/user) - if(!user) - return FALSE - activate() - return TRUE - -/obj/item/assembly/voice/toggle_secure() - . = ..() - listening = FALSE - -#undef INCLUSIVE_MODE -#undef EXCLUSIVE_MODE -#undef RECOGNIZER_MODE -#undef VOICE_SENSOR_MODE +#define INCLUSIVE_MODE 1 +#define EXCLUSIVE_MODE 2 +#define RECOGNIZER_MODE 3 +#define VOICE_SENSOR_MODE 4 + +/obj/item/assembly/voice + name = "voice analyzer" + desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." + icon_state = "voice" + custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) + flags_1 = HEAR_1 + attachable = TRUE + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + var/listening = FALSE + var/recorded = "" //the activation message + var/mode = 1 + var/static/list/modes = list("inclusive", + "exclusive", + "recognizer", + "voice sensor") + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + +/obj/item/assembly/voice/examine(mob/user) + . = ..() + . += "Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode." + +/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(speaker == src) + return + + if(listening && !radio_freq) + record_speech(speaker, raw_message, message_language) + else + if(check_activation(speaker, raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) + switch(mode) + if(INCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(EXCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(RECOGNIZER_MODE) + recorded = speaker.GetVoice() + listening = FALSE + say("Your voice pattern is saved.", message_language) + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) + . = FALSE + switch(mode) + if(INCLUSIVE_MODE) + if(findtext(raw_message, recorded)) + . = TRUE + if(EXCLUSIVE_MODE) + if(raw_message == recorded) + . = TRUE + if(RECOGNIZER_MODE) + if(speaker.GetVoice() == recorded) + . = TRUE + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + . = TRUE + +/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) + ..() + mode %= modes.len + mode++ + to_chat(user, "You set [src] into [modes[mode]] mode.") + listening = FALSE + recorded = "" + return TRUE + +/obj/item/assembly/voice/activate() + if(!secured || holder) + return FALSE + listening = !listening + say("[listening ? "Now" : "No longer"] recording input.") + return TRUE + +/obj/item/assembly/voice/attack_self(mob/user) + if(!user) + return FALSE + activate() + return TRUE + +/obj/item/assembly/voice/toggle_secure() + . = ..() + listening = FALSE + +#undef INCLUSIVE_MODE +#undef EXCLUSIVE_MODE +#undef RECOGNIZER_MODE +#undef VOICE_SENSOR_MODE diff --git a/code/modules/asset_cache/asset_cache.dm b/code/modules/asset_cache/asset_cache.dm index 4caed18db68..f702bf714e4 100644 --- a/code/modules/asset_cache/asset_cache.dm +++ b/code/modules/asset_cache/asset_cache.dm @@ -1,103 +1,103 @@ -/* -Asset cache quick users guide: - -Make a datum in asset_list_items.dm with your assets for your thing. -Checkout asset_list.dm for the helper subclasses -The simple subclass will most like be of use for most cases. -Then call get_asset_datum() with the type of the datum you created and store the return -Then call .send(client) on that stored return value. - -Note: If your code uses output() with assets you will need to call asset_flush on the client and wait for it to return before calling output(). You only need do this if .send(client) returned TRUE -*/ - -//When sending mutiple assets, how many before we give the client a quaint little sending resources message -#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8 - -//This proc sends the asset to the client, but only if it needs it. -//This proc blocks(sleeps) unless verify is set to false -/proc/send_asset(client/client, asset_name) - return send_asset_list(client, list(asset_name)) - -/// Sends a list of assets to a client -/// This proc will no longer block, use client.asset_flush() if you to need know when the client has all assets (such as for output()). (This is not required for browse() calls as they use the same message queue as asset sends) -/// client - a client or mob -/// asset_list - A list of asset filenames to be sent to the client. -/// Returns TRUE if any assets were sent. -/proc/send_asset_list(client/client, list/asset_list) - if(!istype(client)) - if(ismob(client)) - var/mob/M = client - if(M.client) - client = M.client - else - return - else - return - - var/list/unreceived = list() - - for (var/asset_name in asset_list) - var/datum/asset_cache_item/asset = SSassets.cache[asset_name] - if (!asset) - continue - var/asset_file = asset.resource - if (!asset_file) - continue - - var/asset_md5 = asset.md5 - if (client.sent_assets[asset_name] == asset_md5) - continue - unreceived[asset_name] = asset_md5 - - if (unreceived.len) - if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT) - to_chat(client, "Sending Resources...") - - for(var/asset in unreceived) - var/datum/asset_cache_item/ACI - if ((ACI = SSassets.cache[asset])) - log_asset("Sending asset [asset] to client [client]") - client << browse_rsc(ACI.resource, asset) - - client.sent_assets |= unreceived - addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) - return TRUE - return FALSE - -//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start. -//The proc calls procs that sleep for long times. -/proc/getFilesSlow(client/client, list/files, register_asset = TRUE, filerate = 3) - var/startingfilerate = filerate - for(var/file in files) - if (!client) - break - if (register_asset) - register_asset(file, files[file]) - - if (send_asset(client, file)) - if (!(--filerate)) - filerate = startingfilerate - client.asset_flush() - stoplag(0) //queuing calls like this too quickly can cause issues in some client versions - -//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up. -//icons and virtual assets get copied to the dyn rsc before use -/proc/register_asset(asset_name, asset) - var/datum/asset_cache_item/ACI = new(asset_name, asset) - - //this is technically never something that was supported and i want metrics on how often it happens if at all. - if (SSassets.cache[asset_name]) - var/datum/asset_cache_item/OACI = SSassets.cache[asset_name] - if (OACI.md5 != ACI.md5) - stack_trace("ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]") - else - var/list/stacktrace = gib_stack_trace() - log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]") - SSassets.cache[asset_name] = ACI - -//Generated names do not include file extention. -//Used mainly for code that deals with assets in a generic way -//The same asset will always lead to the same asset name -/proc/generate_asset_name(file) - return "asset.[md5(fcopy_rsc(file))]" - +/* +Asset cache quick users guide: + +Make a datum in asset_list_items.dm with your assets for your thing. +Checkout asset_list.dm for the helper subclasses +The simple subclass will most like be of use for most cases. +Then call get_asset_datum() with the type of the datum you created and store the return +Then call .send(client) on that stored return value. + +Note: If your code uses output() with assets you will need to call asset_flush on the client and wait for it to return before calling output(). You only need do this if .send(client) returned TRUE +*/ + +//When sending mutiple assets, how many before we give the client a quaint little sending resources message +#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8 + +//This proc sends the asset to the client, but only if it needs it. +//This proc blocks(sleeps) unless verify is set to false +/proc/send_asset(client/client, asset_name) + return send_asset_list(client, list(asset_name)) + +/// Sends a list of assets to a client +/// This proc will no longer block, use client.asset_flush() if you to need know when the client has all assets (such as for output()). (This is not required for browse() calls as they use the same message queue as asset sends) +/// client - a client or mob +/// asset_list - A list of asset filenames to be sent to the client. +/// Returns TRUE if any assets were sent. +/proc/send_asset_list(client/client, list/asset_list) + if(!istype(client)) + if(ismob(client)) + var/mob/M = client + if(M.client) + client = M.client + else + return + else + return + + var/list/unreceived = list() + + for (var/asset_name in asset_list) + var/datum/asset_cache_item/asset = SSassets.cache[asset_name] + if (!asset) + continue + var/asset_file = asset.resource + if (!asset_file) + continue + + var/asset_md5 = asset.md5 + if (client.sent_assets[asset_name] == asset_md5) + continue + unreceived[asset_name] = asset_md5 + + if (unreceived.len) + if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT) + to_chat(client, "Sending Resources...") + + for(var/asset in unreceived) + var/datum/asset_cache_item/ACI + if ((ACI = SSassets.cache[asset])) + log_asset("Sending asset [asset] to client [client]") + client << browse_rsc(ACI.resource, asset) + + client.sent_assets |= unreceived + addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + return TRUE + return FALSE + +//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start. +//The proc calls procs that sleep for long times. +/proc/getFilesSlow(client/client, list/files, register_asset = TRUE, filerate = 3) + var/startingfilerate = filerate + for(var/file in files) + if (!client) + break + if (register_asset) + register_asset(file, files[file]) + + if (send_asset(client, file)) + if (!(--filerate)) + filerate = startingfilerate + client.asset_flush() + stoplag(0) //queuing calls like this too quickly can cause issues in some client versions + +//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up. +//icons and virtual assets get copied to the dyn rsc before use +/proc/register_asset(asset_name, asset) + var/datum/asset_cache_item/ACI = new(asset_name, asset) + + //this is technically never something that was supported and i want metrics on how often it happens if at all. + if (SSassets.cache[asset_name]) + var/datum/asset_cache_item/OACI = SSassets.cache[asset_name] + if (OACI.md5 != ACI.md5) + stack_trace("ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]") + else + var/list/stacktrace = gib_stack_trace() + log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]") + SSassets.cache[asset_name] = ACI + +//Generated names do not include file extention. +//Used mainly for code that deals with assets in a generic way +//The same asset will always lead to the same asset name +/proc/generate_asset_name(file) + return "asset.[md5(fcopy_rsc(file))]" + diff --git a/code/modules/asset_cache/asset_cache_client.dm b/code/modules/asset_cache/asset_cache_client.dm index 0755a736af2..0f51520f13a 100644 --- a/code/modules/asset_cache/asset_cache_client.dm +++ b/code/modules/asset_cache/asset_cache_client.dm @@ -1,51 +1,51 @@ - -/// Process asset cache client topic calls for "asset_cache_confirm_arrival=[INT]" -/client/proc/asset_cache_confirm_arrival(job_id) - var/asset_cache_job = round(text2num(job_id)) - //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit. - if (asset_cache_job > 0 && asset_cache_job <= last_asset_job && !(completed_asset_jobs["[asset_cache_job]"])) - completed_asset_jobs["[asset_cache_job]"] = TRUE - last_completed_asset_job = max(last_completed_asset_job, asset_cache_job) - else - return asset_cache_job || TRUE - - -/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING] -/client/proc/asset_cache_preload_data(data) - /*var/jsonend = findtextEx(data, "{{{ENDJSONDATA}}}") - if (!jsonend) - CRASH("invalid asset_cache_preload_data, no jsonendmarker")*/ - //var/json = html_decode(copytext(data, 1, jsonend)) - var/json = data - var/list/preloaded_assets = json_decode(json) - - for (var/preloaded_asset in preloaded_assets) - if (copytext(preloaded_asset, findlasttext(preloaded_asset, ".")+1) in list("js", "jsm", "htm", "html")) - preloaded_assets -= preloaded_asset - continue - sent_assets |= preloaded_assets - - -/// Updates the client side stored html/json combo file used to keep track of what assets the client has between restarts/reconnects. -/client/proc/asset_cache_update_json(verify = FALSE, list/new_assets = list()) - if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection - return - if (!islist(new_assets)) - new_assets = list("[new_assets]" = md5(SSassets.cache[new_assets])) - - src << browse(json_encode(new_assets|sent_assets), "file=asset_data.json&display=0") - -/// Blocks until all currently sending browser assets have been sent. -/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends. -/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away. -/client/proc/asset_flush(timeout = 50) - var/job = ++last_asset_job - var/t = 0 - var/timeout_time = timeout - src << browse({""}, "window=asset_cache_browser&file=asset_cache_send_verify.htm") - - while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic() - stoplag(1) // Lock up the caller until this is received. - t++ - if (t < timeout_time) - return TRUE + +/// Process asset cache client topic calls for "asset_cache_confirm_arrival=[INT]" +/client/proc/asset_cache_confirm_arrival(job_id) + var/asset_cache_job = round(text2num(job_id)) + //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit. + if (asset_cache_job > 0 && asset_cache_job <= last_asset_job && !(completed_asset_jobs["[asset_cache_job]"])) + completed_asset_jobs["[asset_cache_job]"] = TRUE + last_completed_asset_job = max(last_completed_asset_job, asset_cache_job) + else + return asset_cache_job || TRUE + + +/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING] +/client/proc/asset_cache_preload_data(data) + /*var/jsonend = findtextEx(data, "{{{ENDJSONDATA}}}") + if (!jsonend) + CRASH("invalid asset_cache_preload_data, no jsonendmarker")*/ + //var/json = html_decode(copytext(data, 1, jsonend)) + var/json = data + var/list/preloaded_assets = json_decode(json) + + for (var/preloaded_asset in preloaded_assets) + if (copytext(preloaded_asset, findlasttext(preloaded_asset, ".")+1) in list("js", "jsm", "htm", "html")) + preloaded_assets -= preloaded_asset + continue + sent_assets |= preloaded_assets + + +/// Updates the client side stored html/json combo file used to keep track of what assets the client has between restarts/reconnects. +/client/proc/asset_cache_update_json(verify = FALSE, list/new_assets = list()) + if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection + return + if (!islist(new_assets)) + new_assets = list("[new_assets]" = md5(SSassets.cache[new_assets])) + + src << browse(json_encode(new_assets|sent_assets), "file=asset_data.json&display=0") + +/// Blocks until all currently sending browser assets have been sent. +/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends. +/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away. +/client/proc/asset_flush(timeout = 50) + var/job = ++last_asset_job + var/t = 0 + var/timeout_time = timeout + src << browse({""}, "window=asset_cache_browser&file=asset_cache_send_verify.htm") + + while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic() + stoplag(1) // Lock up the caller until this is received. + t++ + if (t < timeout_time) + return TRUE diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm index c705a173333..0e7d44a7ac3 100644 --- a/code/modules/asset_cache/asset_cache_item.dm +++ b/code/modules/asset_cache/asset_cache_item.dm @@ -1,21 +1,21 @@ -/** - * # asset_cache_item - * - * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed. -**/ -/datum/asset_cache_item - var/name - var/md5 - var/resource - -/datum/asset_cache_item/New(name, file) - if (!isfile(file)) - file = fcopy_rsc(file) - md5 = md5(file) - if (!md5) - md5 = md5(fcopy_rsc(file)) - if (!md5) - CRASH("invalid asset sent to asset cache") - debug_world_log("asset cache unexpected success of second fcopy_rsc") - src.name = name - resource = file +/** + * # asset_cache_item + * + * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed. +**/ +/datum/asset_cache_item + var/name + var/md5 + var/resource + +/datum/asset_cache_item/New(name, file) + if (!isfile(file)) + file = fcopy_rsc(file) + md5 = md5(file) + if (!md5) + md5 = md5(fcopy_rsc(file)) + if (!md5) + CRASH("invalid asset sent to asset cache") + debug_world_log("asset cache unexpected success of second fcopy_rsc") + src.name = name + resource = file diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm index 49ac4ece0b5..2e5881c67f3 100644 --- a/code/modules/asset_cache/asset_list.dm +++ b/code/modules/asset_cache/asset_list.dm @@ -1,230 +1,230 @@ - -//These datums are used to populate the asset cache, the proc "register()" does this. -//Place any asset datums you create in asset_list_items.dm - -//all of our asset datums, used for referring to these later -GLOBAL_LIST_EMPTY(asset_datums) - -//get an assetdatum or make a new one -/proc/get_asset_datum(type) - return GLOB.asset_datums[type] || new type() - -/datum/asset - var/_abstract = /datum/asset - -/datum/asset/New() - GLOB.asset_datums[type] = src - register() - -/datum/asset/proc/register() - return - -/datum/asset/proc/send(client) - return - - -//If you don't need anything complicated. -/datum/asset/simple - _abstract = /datum/asset/simple - var/assets = list() - -/datum/asset/simple/register() - for(var/asset_name in assets) - register_asset(asset_name, assets[asset_name]) - -/datum/asset/simple/send(client) - . = send_asset_list(client, assets) - - -// For registering or sending multiple others at once -/datum/asset/group - _abstract = /datum/asset/group - var/list/children - -/datum/asset/group/register() - for(var/type in children) - get_asset_datum(type) - -/datum/asset/group/send(client/C) - for(var/type in children) - var/datum/asset/A = get_asset_datum(type) - . = A.send(C) || . - - -// spritesheet implementation - coalesces various icons into a single .png file -// and uses CSS to select icons out of that file - saves on transferring some -// 1400-odd individual PNG files -#define SPR_SIZE 1 -#define SPR_IDX 2 -#define SPRSZ_COUNT 1 -#define SPRSZ_ICON 2 -#define SPRSZ_STRIPPED 3 - -/datum/asset/spritesheet - _abstract = /datum/asset/spritesheet - var/name - var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped) - var/list/sprites = list() // "foo_bar" -> list("32x32", 5) - -/datum/asset/spritesheet/register() - if (!name) - CRASH("spritesheet [type] cannot register without a name") - ensure_stripped() - - var/res_name = "spritesheet_[name].css" - var/fname = "data/spritesheets/[res_name]" - fdel(fname) - text2file(generate_css(), fname) - register_asset(res_name, fcopy_rsc(fname)) - fdel(fname) - - for(var/size_id in sizes) - var/size = sizes[size_id] - register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED]) - -/datum/asset/spritesheet/send(client/C) - if (!name) - return - var/all = list("spritesheet_[name].css") - for(var/size_id in sizes) - all += "[name]_[size_id].png" - . = send_asset_list(C, all) - -/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes) - for(var/size_id in sizes_to_strip) - var/size = sizes[size_id] - if (size[SPRSZ_STRIPPED]) - continue - - // save flattened version - var/fname = "data/spritesheets/[name]_[size_id].png" - fcopy(size[SPRSZ_ICON], fname) - var/error = rustg_dmi_strip_metadata(fname) - if(length(error)) - stack_trace("Failed to strip [name]_[size_id].png: [error]") - size[SPRSZ_STRIPPED] = icon(fname) - fdel(fname) - -/datum/asset/spritesheet/proc/generate_css() - var/list/out = list() - - for (var/size_id in sizes) - var/size = sizes[size_id] - var/icon/tiny = size[SPRSZ_ICON] - out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}" - - for (var/sprite_id in sprites) - var/sprite = sprites[sprite_id] - var/size_id = sprite[SPR_SIZE] - var/idx = sprite[SPR_IDX] - var/size = sizes[size_id] - - var/icon/tiny = size[SPRSZ_ICON] - var/icon/big = size[SPRSZ_STRIPPED] - var/per_line = big.Width() / tiny.Width() - var/x = (idx % per_line) * tiny.Width() - var/y = round(idx / per_line) * tiny.Height() - - out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}" - - return out.Join("\n") - -/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE) - I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving) - if (!I || !length(icon_states(I))) // that direction or state doesn't exist - return - var/size_id = "[I.Width()]x[I.Height()]" - var/size = sizes[size_id] - - if (sprites[sprite_name]) - CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])") - - if (size) - var/position = size[SPRSZ_COUNT]++ - var/icon/sheet = size[SPRSZ_ICON] - size[SPRSZ_STRIPPED] = null - sheet.Insert(I, icon_state=sprite_name) - sprites[sprite_name] = list(size_id, position) - else - sizes[size_id] = size = list(1, I, null) - sprites[sprite_name] = list(size_id, 0) - -/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions) - if (length(prefix)) - prefix = "[prefix]-" - - if (!directions) - directions = list(SOUTH) - - for (var/icon_state_name in icon_states(I)) - for (var/direction in directions) - var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : "" - Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction) - -/datum/asset/spritesheet/proc/css_tag() - return {""} - -/datum/asset/spritesheet/proc/icon_tag(sprite_name) - var/sprite = sprites[sprite_name] - if (!sprite) - return null - var/size_id = sprite[SPR_SIZE] - return {""} - -/datum/asset/spritesheet/proc/icon_class_name(sprite_name) - var/sprite = sprites[sprite_name] - if (!sprite) - return null - var/size_id = sprite[SPR_SIZE] - return {"[name][size_id] [sprite_name]"} - -#undef SPR_SIZE -#undef SPR_IDX -#undef SPRSZ_COUNT -#undef SPRSZ_ICON -#undef SPRSZ_STRIPPED - - -/datum/asset/spritesheet/simple - _abstract = /datum/asset/spritesheet/simple - var/list/assets - -/datum/asset/spritesheet/simple/register() - for (var/key in assets) - Insert(key, assets[key]) - ..() - -//Generates assets based on iconstates of a single icon -/datum/asset/simple/icon_states - _abstract = /datum/asset/simple/icon_states - var/icon - var/list/directions = list(SOUTH) - var/frame = 1 - var/movement_states = FALSE - - var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png" - var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format - -/datum/asset/simple/icon_states/register(_icon = icon) - for(var/icon_state_name in icon_states(_icon)) - for(var/direction in directions) - var/asset = icon(_icon, icon_state_name, direction, frame, movement_states) - if (!asset) - continue - asset = fcopy_rsc(asset) //dedupe - var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : "" - var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png") - if (generic_icon_names) - asset_name = "[generate_asset_name(asset)].png" - - register_asset(asset_name, asset) - -/datum/asset/simple/icon_states/multiple_icons - _abstract = /datum/asset/simple/icon_states/multiple_icons - var/list/icons - -/datum/asset/simple/icon_states/multiple_icons/register() - for(var/i in icons) - ..(i) - - + +//These datums are used to populate the asset cache, the proc "register()" does this. +//Place any asset datums you create in asset_list_items.dm + +//all of our asset datums, used for referring to these later +GLOBAL_LIST_EMPTY(asset_datums) + +//get an assetdatum or make a new one +/proc/get_asset_datum(type) + return GLOB.asset_datums[type] || new type() + +/datum/asset + var/_abstract = /datum/asset + +/datum/asset/New() + GLOB.asset_datums[type] = src + register() + +/datum/asset/proc/register() + return + +/datum/asset/proc/send(client) + return + + +//If you don't need anything complicated. +/datum/asset/simple + _abstract = /datum/asset/simple + var/assets = list() + +/datum/asset/simple/register() + for(var/asset_name in assets) + register_asset(asset_name, assets[asset_name]) + +/datum/asset/simple/send(client) + . = send_asset_list(client, assets) + + +// For registering or sending multiple others at once +/datum/asset/group + _abstract = /datum/asset/group + var/list/children + +/datum/asset/group/register() + for(var/type in children) + get_asset_datum(type) + +/datum/asset/group/send(client/C) + for(var/type in children) + var/datum/asset/A = get_asset_datum(type) + . = A.send(C) || . + + +// spritesheet implementation - coalesces various icons into a single .png file +// and uses CSS to select icons out of that file - saves on transferring some +// 1400-odd individual PNG files +#define SPR_SIZE 1 +#define SPR_IDX 2 +#define SPRSZ_COUNT 1 +#define SPRSZ_ICON 2 +#define SPRSZ_STRIPPED 3 + +/datum/asset/spritesheet + _abstract = /datum/asset/spritesheet + var/name + var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped) + var/list/sprites = list() // "foo_bar" -> list("32x32", 5) + +/datum/asset/spritesheet/register() + if (!name) + CRASH("spritesheet [type] cannot register without a name") + ensure_stripped() + + var/res_name = "spritesheet_[name].css" + var/fname = "data/spritesheets/[res_name]" + fdel(fname) + text2file(generate_css(), fname) + register_asset(res_name, fcopy_rsc(fname)) + fdel(fname) + + for(var/size_id in sizes) + var/size = sizes[size_id] + register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED]) + +/datum/asset/spritesheet/send(client/C) + if (!name) + return + var/all = list("spritesheet_[name].css") + for(var/size_id in sizes) + all += "[name]_[size_id].png" + . = send_asset_list(C, all) + +/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes) + for(var/size_id in sizes_to_strip) + var/size = sizes[size_id] + if (size[SPRSZ_STRIPPED]) + continue + + // save flattened version + var/fname = "data/spritesheets/[name]_[size_id].png" + fcopy(size[SPRSZ_ICON], fname) + var/error = rustg_dmi_strip_metadata(fname) + if(length(error)) + stack_trace("Failed to strip [name]_[size_id].png: [error]") + size[SPRSZ_STRIPPED] = icon(fname) + fdel(fname) + +/datum/asset/spritesheet/proc/generate_css() + var/list/out = list() + + for (var/size_id in sizes) + var/size = sizes[size_id] + var/icon/tiny = size[SPRSZ_ICON] + out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}" + + for (var/sprite_id in sprites) + var/sprite = sprites[sprite_id] + var/size_id = sprite[SPR_SIZE] + var/idx = sprite[SPR_IDX] + var/size = sizes[size_id] + + var/icon/tiny = size[SPRSZ_ICON] + var/icon/big = size[SPRSZ_STRIPPED] + var/per_line = big.Width() / tiny.Width() + var/x = (idx % per_line) * tiny.Width() + var/y = round(idx / per_line) * tiny.Height() + + out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}" + + return out.Join("\n") + +/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE) + I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving) + if (!I || !length(icon_states(I))) // that direction or state doesn't exist + return + var/size_id = "[I.Width()]x[I.Height()]" + var/size = sizes[size_id] + + if (sprites[sprite_name]) + CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])") + + if (size) + var/position = size[SPRSZ_COUNT]++ + var/icon/sheet = size[SPRSZ_ICON] + size[SPRSZ_STRIPPED] = null + sheet.Insert(I, icon_state=sprite_name) + sprites[sprite_name] = list(size_id, position) + else + sizes[size_id] = size = list(1, I, null) + sprites[sprite_name] = list(size_id, 0) + +/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions) + if (length(prefix)) + prefix = "[prefix]-" + + if (!directions) + directions = list(SOUTH) + + for (var/icon_state_name in icon_states(I)) + for (var/direction in directions) + var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : "" + Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction) + +/datum/asset/spritesheet/proc/css_tag() + return {""} + +/datum/asset/spritesheet/proc/icon_tag(sprite_name) + var/sprite = sprites[sprite_name] + if (!sprite) + return null + var/size_id = sprite[SPR_SIZE] + return {""} + +/datum/asset/spritesheet/proc/icon_class_name(sprite_name) + var/sprite = sprites[sprite_name] + if (!sprite) + return null + var/size_id = sprite[SPR_SIZE] + return {"[name][size_id] [sprite_name]"} + +#undef SPR_SIZE +#undef SPR_IDX +#undef SPRSZ_COUNT +#undef SPRSZ_ICON +#undef SPRSZ_STRIPPED + + +/datum/asset/spritesheet/simple + _abstract = /datum/asset/spritesheet/simple + var/list/assets + +/datum/asset/spritesheet/simple/register() + for (var/key in assets) + Insert(key, assets[key]) + ..() + +//Generates assets based on iconstates of a single icon +/datum/asset/simple/icon_states + _abstract = /datum/asset/simple/icon_states + var/icon + var/list/directions = list(SOUTH) + var/frame = 1 + var/movement_states = FALSE + + var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png" + var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format + +/datum/asset/simple/icon_states/register(_icon = icon) + for(var/icon_state_name in icon_states(_icon)) + for(var/direction in directions) + var/asset = icon(_icon, icon_state_name, direction, frame, movement_states) + if (!asset) + continue + asset = fcopy_rsc(asset) //dedupe + var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : "" + var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png") + if (generic_icon_names) + asset_name = "[generate_asset_name(asset)].png" + + register_asset(asset_name, asset) + +/datum/asset/simple/icon_states/multiple_icons + _abstract = /datum/asset/simple/icon_states/multiple_icons + var/list/icons + +/datum/asset/simple/icon_states/multiple_icons/register() + for(var/i in icons) + ..(i) + + diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index a6b9240ad8b..2eabc81a7a2 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -1,391 +1,391 @@ -//DEFINITIONS FOR ASSET DATUMS START HERE. - -/datum/asset/simple/tgui - assets = list( - "tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js', - "tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css', - ) - -/datum/asset/group/tgui - children = list( - /datum/asset/simple/tgui, - /datum/asset/simple/fontawesome - ) - -/datum/asset/simple/headers - assets = list( - "alarm_green.gif" = 'icons/program_icons/alarm_green.gif', - "alarm_red.gif" = 'icons/program_icons/alarm_red.gif', - "batt_5.gif" = 'icons/program_icons/batt_5.gif', - "batt_20.gif" = 'icons/program_icons/batt_20.gif', - "batt_40.gif" = 'icons/program_icons/batt_40.gif', - "batt_60.gif" = 'icons/program_icons/batt_60.gif', - "batt_80.gif" = 'icons/program_icons/batt_80.gif', - "batt_100.gif" = 'icons/program_icons/batt_100.gif', - "charging.gif" = 'icons/program_icons/charging.gif', - "downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif', - "downloader_running.gif" = 'icons/program_icons/downloader_running.gif', - "ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif', - "ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif', - "power_norm.gif" = 'icons/program_icons/power_norm.gif', - "power_warn.gif" = 'icons/program_icons/power_warn.gif', - "sig_high.gif" = 'icons/program_icons/sig_high.gif', - "sig_low.gif" = 'icons/program_icons/sig_low.gif', - "sig_lan.gif" = 'icons/program_icons/sig_lan.gif', - "sig_none.gif" = 'icons/program_icons/sig_none.gif', - "smmon_0.gif" = 'icons/program_icons/smmon_0.gif', - "smmon_1.gif" = 'icons/program_icons/smmon_1.gif', - "smmon_2.gif" = 'icons/program_icons/smmon_2.gif', - "smmon_3.gif" = 'icons/program_icons/smmon_3.gif', - "smmon_4.gif" = 'icons/program_icons/smmon_4.gif', - "smmon_5.gif" = 'icons/program_icons/smmon_5.gif', - "smmon_6.gif" = 'icons/program_icons/smmon_6.gif', - "borg_mon.gif" = 'icons/program_icons/borg_mon.gif' - ) - -/datum/asset/simple/radar_assets - assets = list( - "ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png', - "ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png', - "ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png' - ) - -/datum/asset/spritesheet/simple/pda - name = "pda" - assets = list( - "atmos" = 'icons/pda_icons/pda_atmos.png', - "back" = 'icons/pda_icons/pda_back.png', - "bell" = 'icons/pda_icons/pda_bell.png', - "blank" = 'icons/pda_icons/pda_blank.png', - "boom" = 'icons/pda_icons/pda_boom.png', - "bucket" = 'icons/pda_icons/pda_bucket.png', - "medbot" = 'icons/pda_icons/pda_medbot.png', - "floorbot" = 'icons/pda_icons/pda_floorbot.png', - "cleanbot" = 'icons/pda_icons/pda_cleanbot.png', - "crate" = 'icons/pda_icons/pda_crate.png', - "cuffs" = 'icons/pda_icons/pda_cuffs.png', - "eject" = 'icons/pda_icons/pda_eject.png', - "flashlight" = 'icons/pda_icons/pda_flashlight.png', - "honk" = 'icons/pda_icons/pda_honk.png', - "mail" = 'icons/pda_icons/pda_mail.png', - "medical" = 'icons/pda_icons/pda_medical.png', - "menu" = 'icons/pda_icons/pda_menu.png', - "mule" = 'icons/pda_icons/pda_mule.png', - "notes" = 'icons/pda_icons/pda_notes.png', - "power" = 'icons/pda_icons/pda_power.png', - "rdoor" = 'icons/pda_icons/pda_rdoor.png', - "reagent" = 'icons/pda_icons/pda_reagent.png', - "refresh" = 'icons/pda_icons/pda_refresh.png', - "scanner" = 'icons/pda_icons/pda_scanner.png', - "signaler" = 'icons/pda_icons/pda_signaler.png', - "skills" = 'icons/pda_icons/pda_skills.png', - "status" = 'icons/pda_icons/pda_status.png', - "dronephone" = 'icons/pda_icons/pda_dronephone.png', - "emoji" = 'icons/pda_icons/pda_emoji.png' - ) - -/datum/asset/spritesheet/simple/paper - name = "paper" - assets = list( - "stamp-clown" = 'icons/stamp_icons/large_stamp-clown.png', - "stamp-deny" = 'icons/stamp_icons/large_stamp-deny.png', - "stamp-ok" = 'icons/stamp_icons/large_stamp-ok.png', - "stamp-hop" = 'icons/stamp_icons/large_stamp-hop.png', - "stamp-cmo" = 'icons/stamp_icons/large_stamp-cmo.png', - "stamp-ce" = 'icons/stamp_icons/large_stamp-ce.png', - "stamp-hos" = 'icons/stamp_icons/large_stamp-hos.png', - "stamp-rd" = 'icons/stamp_icons/large_stamp-rd.png', - "stamp-cap" = 'icons/stamp_icons/large_stamp-cap.png', - "stamp-qm" = 'icons/stamp_icons/large_stamp-qm.png', - "stamp-law" = 'icons/stamp_icons/large_stamp-law.png', - "stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png', - "stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png', - "stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png', - "stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png' - ) - - -/datum/asset/simple/irv - assets = list( - "jquery-ui.custom-core-widgit-mouse-sortable-min.js" = 'html/IRV/jquery-ui.custom-core-widgit-mouse-sortable-min.js', - ) - -/datum/asset/group/irv - children = list( - /datum/asset/simple/jquery, - /datum/asset/simple/irv - ) - -/datum/asset/simple/changelog - assets = list( - "88x31.png" = 'html/88x31.png', - "bug-minus.png" = 'html/bug-minus.png', - "cross-circle.png" = 'html/cross-circle.png', - "hard-hat-exclamation.png" = 'html/hard-hat-exclamation.png', - "image-minus.png" = 'html/image-minus.png', - "image-plus.png" = 'html/image-plus.png', - "music-minus.png" = 'html/music-minus.png', - "music-plus.png" = 'html/music-plus.png', - "tick-circle.png" = 'html/tick-circle.png', - "wrench-screwdriver.png" = 'html/wrench-screwdriver.png', - "spell-check.png" = 'html/spell-check.png', - "burn-exclamation.png" = 'html/burn-exclamation.png', - "chevron.png" = 'html/chevron.png', - "chevron-expand.png" = 'html/chevron-expand.png', - "scales.png" = 'html/scales.png', - "coding.png" = 'html/coding.png', - "ban.png" = 'html/ban.png', - "chrome-wrench.png" = 'html/chrome-wrench.png', - "changelog.css" = 'html/changelog.css' - ) - -/datum/asset/group/goonchat - children = list( - /datum/asset/simple/jquery, - /datum/asset/simple/goonchat, - /datum/asset/spritesheet/goonchat, - /datum/asset/simple/fontawesome - ) - -/datum/asset/simple/jquery - assets = list( - "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js', - ) - -/datum/asset/simple/goonchat - assets = list( - "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js', - "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js', - "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', - "browserOutput_white.css" = 'code/modules/goonchat/browserassets/css/browserOutput_white.css', - ) - -/datum/asset/simple/fontawesome - assets = list( - "fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot', - "fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff', - "fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot', - "fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff', - "font-awesome.css" = 'html/font-awesome/css/all.min.css', - "v4shim.css" = 'html/font-awesome/css/v4-shims.min.css' - ) - -/datum/asset/spritesheet/goonchat - name = "chat" - -/datum/asset/spritesheet/goonchat/register() - InsertAll("emoji", 'icons/emoji.dmi') - - // pre-loading all lanugage icons also helps to avoid meta - InsertAll("language", 'icons/misc/language.dmi') - // catch languages which are pulling icons from another file - for(var/path in typesof(/datum/language)) - var/datum/language/L = path - var/icon = initial(L.icon) - if (icon != 'icons/misc/language.dmi') - var/icon_state = initial(L.icon_state) - Insert("language-[icon_state]", icon, icon_state=icon_state) - - ..() - -/datum/asset/simple/permissions - assets = list( - "padlock.png" = 'html/padlock.png' - ) - -/datum/asset/simple/notes - assets = list( - "high_button.png" = 'html/high_button.png', - "medium_button.png" = 'html/medium_button.png', - "minor_button.png" = 'html/minor_button.png', - "none_button.png" = 'html/none_button.png', - ) - -/datum/asset/simple/arcade - assets = list( - "boss1.gif" = 'icons/UI_Icons/Arcade/boss1.gif', - "boss2.gif" = 'icons/UI_Icons/Arcade/boss2.gif', - "boss3.gif" = 'icons/UI_Icons/Arcade/boss3.gif', - "boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif', - "boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif', - "boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif', - ) - -/datum/asset/spritesheet/simple/achievements - name ="achievements" - assets = list( - "default" = 'icons/UI_Icons/Achievements/default.png', - "basemisc" = 'icons/UI_Icons/Achievements/basemisc.png', - "baseboss" = 'icons/UI_Icons/Achievements/baseboss.png', - "baseskill" = 'icons/UI_Icons/Achievements/baseskill.png', - "bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png', - "colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png', - "hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png', - "legion" = 'icons/UI_Icons/Achievements/Boss/legion.png', - "miner" = 'icons/UI_Icons/Achievements/Boss/miner.png', - "swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png', - "tendril" = 'icons/UI_Icons/Achievements/Boss/tendril.png', - "featofstrength" = 'icons/UI_Icons/Achievements/Misc/featofstrength.png', - "helbital" = 'icons/UI_Icons/Achievements/Misc/helbital.png', - "jackpot" = 'icons/UI_Icons/Achievements/Misc/jackpot.png', - "meteors" = 'icons/UI_Icons/Achievements/Misc/meteors.png', - "timewaste" = 'icons/UI_Icons/Achievements/Misc/timewaste.png', - "upgrade" = 'icons/UI_Icons/Achievements/Misc/upgrade.png', - "clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png', - "clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png', - "rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png', - "snail" = 'icons/UI_Icons/Achievements/Misc/snail.png', - "mining" = 'icons/UI_Icons/Achievements/Skills/mining.png', - ) - -/datum/asset/spritesheet/simple/pills - name ="pills" - assets = list( - "pill1" = 'icons/UI_Icons/Pills/pill1.png', - "pill2" = 'icons/UI_Icons/Pills/pill2.png', - "pill3" = 'icons/UI_Icons/Pills/pill3.png', - "pill4" = 'icons/UI_Icons/Pills/pill4.png', - "pill5" = 'icons/UI_Icons/Pills/pill5.png', - "pill6" = 'icons/UI_Icons/Pills/pill6.png', - "pill7" = 'icons/UI_Icons/Pills/pill7.png', - "pill8" = 'icons/UI_Icons/Pills/pill8.png', - "pill9" = 'icons/UI_Icons/Pills/pill9.png', - "pill10" = 'icons/UI_Icons/Pills/pill10.png', - "pill11" = 'icons/UI_Icons/Pills/pill11.png', - "pill12" = 'icons/UI_Icons/Pills/pill12.png', - "pill13" = 'icons/UI_Icons/Pills/pill13.png', - "pill14" = 'icons/UI_Icons/Pills/pill14.png', - "pill15" = 'icons/UI_Icons/Pills/pill15.png', - "pill16" = 'icons/UI_Icons/Pills/pill16.png', - "pill17" = 'icons/UI_Icons/Pills/pill17.png', - "pill18" = 'icons/UI_Icons/Pills/pill18.png', - "pill19" = 'icons/UI_Icons/Pills/pill19.png', - "pill20" = 'icons/UI_Icons/Pills/pill20.png', - "pill21" = 'icons/UI_Icons/Pills/pill21.png', - "pill22" = 'icons/UI_Icons/Pills/pill22.png', - ) - -//this exists purely to avoid meta by pre-loading all language icons. -/datum/asset/language/register() - for(var/path in typesof(/datum/language)) - set waitfor = FALSE - var/datum/language/L = new path () - L.get_icon() - -/datum/asset/spritesheet/pipes - name = "pipes" - -/datum/asset/spritesheet/pipes/register() - for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi', 'icons/obj/atmospherics/pipes/transit_tube.dmi', 'icons/obj/plumbing/fluid_ducts.dmi')) - InsertAll("", each, GLOB.alldirs) - ..() - -// Representative icons for each research design -/datum/asset/spritesheet/research_designs - name = "design" - -/datum/asset/spritesheet/research_designs/register() - for (var/path in subtypesof(/datum/design)) - var/datum/design/D = path - - var/icon_file - var/icon_state - var/icon/I - - if(initial(D.research_icon) && initial(D.research_icon_state)) //If the design has an icon replacement skip the rest - icon_file = initial(D.research_icon) - icon_state = initial(D.research_icon_state) - if(!(icon_state in icon_states(icon_file))) - warning("design [D] with icon '[icon_file]' missing state '[icon_state]'") - continue - I = icon(icon_file, icon_state, SOUTH) - - else - // construct the icon and slap it into the resource cache - var/atom/item = initial(D.build_path) - if (!ispath(item, /atom)) - // biogenerator outputs to beakers by default - if (initial(D.build_type) & BIOGENERATOR) - item = /obj/item/reagent_containers/glass/beaker/large - else - continue // shouldn't happen, but just in case - - // circuit boards become their resulting machines or computers - if (ispath(item, /obj/item/circuitboard)) - var/obj/item/circuitboard/C = item - var/machine = initial(C.build_path) - if (machine) - item = machine - - icon_file = initial(item.icon) - icon_state = initial(item.icon_state) - - if(!(icon_state in icon_states(icon_file))) - warning("design [D] with icon '[icon_file]' missing state '[icon_state]'") - continue - I = icon(icon_file, icon_state, SOUTH) - - // computers (and snowflakes) get their screen and keyboard sprites - if (ispath(item, /obj/machinery/computer) || ispath(item, /obj/machinery/power/solar_control)) - var/obj/machinery/computer/C = item - var/screen = initial(C.icon_screen) - var/keyboard = initial(C.icon_keyboard) - var/all_states = icon_states(icon_file) - if (screen && (screen in all_states)) - I.Blend(icon(icon_file, screen, SOUTH), ICON_OVERLAY) - if (keyboard && (keyboard in all_states)) - I.Blend(icon(icon_file, keyboard, SOUTH), ICON_OVERLAY) - - Insert(initial(D.id), I) - return ..() - -/datum/asset/spritesheet/vending - name = "vending" - -/datum/asset/spritesheet/vending/register() - for (var/k in GLOB.vending_products) - var/atom/item = k - if (!ispath(item, /atom)) - continue - - var/icon_file = initial(item.icon) - var/icon_state = initial(item.icon_state) - var/icon/I - - var/icon_states_list = icon_states(icon_file) - if(icon_state in icon_states_list) - I = icon(icon_file, icon_state, SOUTH) - var/c = initial(item.color) - if (!isnull(c) && c != "#FFFFFF") - I.Blend(c, ICON_MULTIPLY) - else - var/icon_states_string - for (var/an_icon_state in icon_states_list) - if (!icon_states_string) - icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])" - else - icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])" - stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]") - I = icon('icons/turf/floors.dmi', "", SOUTH) - - var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-") - - Insert(imgid, I) - return ..() - -/datum/asset/simple/genetics - assets = list( - "dna_discovered.gif" = 'html/dna_discovered.gif', - "dna_undiscovered.gif" = 'html/dna_undiscovered.gif', - "dna_extra.gif" = 'html/dna_extra.gif' - ) - -/datum/asset/simple/orbit - assets = list( - "ghost.png" = 'html/ghost.png' - ) - -/datum/asset/simple/vv - assets = list( - "view_variables.css" = 'html/admin/view_variables.css' - ) +//DEFINITIONS FOR ASSET DATUMS START HERE. + +/datum/asset/simple/tgui + assets = list( + "tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js', + "tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css', + ) + +/datum/asset/group/tgui + children = list( + /datum/asset/simple/tgui, + /datum/asset/simple/fontawesome + ) + +/datum/asset/simple/headers + assets = list( + "alarm_green.gif" = 'icons/program_icons/alarm_green.gif', + "alarm_red.gif" = 'icons/program_icons/alarm_red.gif', + "batt_5.gif" = 'icons/program_icons/batt_5.gif', + "batt_20.gif" = 'icons/program_icons/batt_20.gif', + "batt_40.gif" = 'icons/program_icons/batt_40.gif', + "batt_60.gif" = 'icons/program_icons/batt_60.gif', + "batt_80.gif" = 'icons/program_icons/batt_80.gif', + "batt_100.gif" = 'icons/program_icons/batt_100.gif', + "charging.gif" = 'icons/program_icons/charging.gif', + "downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif', + "downloader_running.gif" = 'icons/program_icons/downloader_running.gif', + "ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif', + "ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif', + "power_norm.gif" = 'icons/program_icons/power_norm.gif', + "power_warn.gif" = 'icons/program_icons/power_warn.gif', + "sig_high.gif" = 'icons/program_icons/sig_high.gif', + "sig_low.gif" = 'icons/program_icons/sig_low.gif', + "sig_lan.gif" = 'icons/program_icons/sig_lan.gif', + "sig_none.gif" = 'icons/program_icons/sig_none.gif', + "smmon_0.gif" = 'icons/program_icons/smmon_0.gif', + "smmon_1.gif" = 'icons/program_icons/smmon_1.gif', + "smmon_2.gif" = 'icons/program_icons/smmon_2.gif', + "smmon_3.gif" = 'icons/program_icons/smmon_3.gif', + "smmon_4.gif" = 'icons/program_icons/smmon_4.gif', + "smmon_5.gif" = 'icons/program_icons/smmon_5.gif', + "smmon_6.gif" = 'icons/program_icons/smmon_6.gif', + "borg_mon.gif" = 'icons/program_icons/borg_mon.gif' + ) + +/datum/asset/simple/radar_assets + assets = list( + "ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png', + "ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png', + "ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png' + ) + +/datum/asset/spritesheet/simple/pda + name = "pda" + assets = list( + "atmos" = 'icons/pda_icons/pda_atmos.png', + "back" = 'icons/pda_icons/pda_back.png', + "bell" = 'icons/pda_icons/pda_bell.png', + "blank" = 'icons/pda_icons/pda_blank.png', + "boom" = 'icons/pda_icons/pda_boom.png', + "bucket" = 'icons/pda_icons/pda_bucket.png', + "medbot" = 'icons/pda_icons/pda_medbot.png', + "floorbot" = 'icons/pda_icons/pda_floorbot.png', + "cleanbot" = 'icons/pda_icons/pda_cleanbot.png', + "crate" = 'icons/pda_icons/pda_crate.png', + "cuffs" = 'icons/pda_icons/pda_cuffs.png', + "eject" = 'icons/pda_icons/pda_eject.png', + "flashlight" = 'icons/pda_icons/pda_flashlight.png', + "honk" = 'icons/pda_icons/pda_honk.png', + "mail" = 'icons/pda_icons/pda_mail.png', + "medical" = 'icons/pda_icons/pda_medical.png', + "menu" = 'icons/pda_icons/pda_menu.png', + "mule" = 'icons/pda_icons/pda_mule.png', + "notes" = 'icons/pda_icons/pda_notes.png', + "power" = 'icons/pda_icons/pda_power.png', + "rdoor" = 'icons/pda_icons/pda_rdoor.png', + "reagent" = 'icons/pda_icons/pda_reagent.png', + "refresh" = 'icons/pda_icons/pda_refresh.png', + "scanner" = 'icons/pda_icons/pda_scanner.png', + "signaler" = 'icons/pda_icons/pda_signaler.png', + "skills" = 'icons/pda_icons/pda_skills.png', + "status" = 'icons/pda_icons/pda_status.png', + "dronephone" = 'icons/pda_icons/pda_dronephone.png', + "emoji" = 'icons/pda_icons/pda_emoji.png' + ) + +/datum/asset/spritesheet/simple/paper + name = "paper" + assets = list( + "stamp-clown" = 'icons/stamp_icons/large_stamp-clown.png', + "stamp-deny" = 'icons/stamp_icons/large_stamp-deny.png', + "stamp-ok" = 'icons/stamp_icons/large_stamp-ok.png', + "stamp-hop" = 'icons/stamp_icons/large_stamp-hop.png', + "stamp-cmo" = 'icons/stamp_icons/large_stamp-cmo.png', + "stamp-ce" = 'icons/stamp_icons/large_stamp-ce.png', + "stamp-hos" = 'icons/stamp_icons/large_stamp-hos.png', + "stamp-rd" = 'icons/stamp_icons/large_stamp-rd.png', + "stamp-cap" = 'icons/stamp_icons/large_stamp-cap.png', + "stamp-qm" = 'icons/stamp_icons/large_stamp-qm.png', + "stamp-law" = 'icons/stamp_icons/large_stamp-law.png', + "stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png', + "stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png', + "stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png', + "stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png' + ) + + +/datum/asset/simple/irv + assets = list( + "jquery-ui.custom-core-widgit-mouse-sortable-min.js" = 'html/IRV/jquery-ui.custom-core-widgit-mouse-sortable-min.js', + ) + +/datum/asset/group/irv + children = list( + /datum/asset/simple/jquery, + /datum/asset/simple/irv + ) + +/datum/asset/simple/changelog + assets = list( + "88x31.png" = 'html/88x31.png', + "bug-minus.png" = 'html/bug-minus.png', + "cross-circle.png" = 'html/cross-circle.png', + "hard-hat-exclamation.png" = 'html/hard-hat-exclamation.png', + "image-minus.png" = 'html/image-minus.png', + "image-plus.png" = 'html/image-plus.png', + "music-minus.png" = 'html/music-minus.png', + "music-plus.png" = 'html/music-plus.png', + "tick-circle.png" = 'html/tick-circle.png', + "wrench-screwdriver.png" = 'html/wrench-screwdriver.png', + "spell-check.png" = 'html/spell-check.png', + "burn-exclamation.png" = 'html/burn-exclamation.png', + "chevron.png" = 'html/chevron.png', + "chevron-expand.png" = 'html/chevron-expand.png', + "scales.png" = 'html/scales.png', + "coding.png" = 'html/coding.png', + "ban.png" = 'html/ban.png', + "chrome-wrench.png" = 'html/chrome-wrench.png', + "changelog.css" = 'html/changelog.css' + ) + +/datum/asset/group/goonchat + children = list( + /datum/asset/simple/jquery, + /datum/asset/simple/goonchat, + /datum/asset/spritesheet/goonchat, + /datum/asset/simple/fontawesome + ) + +/datum/asset/simple/jquery + assets = list( + "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js', + ) + +/datum/asset/simple/goonchat + assets = list( + "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js', + "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js', + "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', + "browserOutput_white.css" = 'code/modules/goonchat/browserassets/css/browserOutput_white.css', + ) + +/datum/asset/simple/fontawesome + assets = list( + "fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot', + "fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff', + "fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot', + "fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff', + "font-awesome.css" = 'html/font-awesome/css/all.min.css', + "v4shim.css" = 'html/font-awesome/css/v4-shims.min.css' + ) + +/datum/asset/spritesheet/goonchat + name = "chat" + +/datum/asset/spritesheet/goonchat/register() + InsertAll("emoji", 'icons/emoji.dmi') + + // pre-loading all lanugage icons also helps to avoid meta + InsertAll("language", 'icons/misc/language.dmi') + // catch languages which are pulling icons from another file + for(var/path in typesof(/datum/language)) + var/datum/language/L = path + var/icon = initial(L.icon) + if (icon != 'icons/misc/language.dmi') + var/icon_state = initial(L.icon_state) + Insert("language-[icon_state]", icon, icon_state=icon_state) + + ..() + +/datum/asset/simple/permissions + assets = list( + "padlock.png" = 'html/padlock.png' + ) + +/datum/asset/simple/notes + assets = list( + "high_button.png" = 'html/high_button.png', + "medium_button.png" = 'html/medium_button.png', + "minor_button.png" = 'html/minor_button.png', + "none_button.png" = 'html/none_button.png', + ) + +/datum/asset/simple/arcade + assets = list( + "boss1.gif" = 'icons/UI_Icons/Arcade/boss1.gif', + "boss2.gif" = 'icons/UI_Icons/Arcade/boss2.gif', + "boss3.gif" = 'icons/UI_Icons/Arcade/boss3.gif', + "boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif', + "boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif', + "boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif', + ) + +/datum/asset/spritesheet/simple/achievements + name ="achievements" + assets = list( + "default" = 'icons/UI_Icons/Achievements/default.png', + "basemisc" = 'icons/UI_Icons/Achievements/basemisc.png', + "baseboss" = 'icons/UI_Icons/Achievements/baseboss.png', + "baseskill" = 'icons/UI_Icons/Achievements/baseskill.png', + "bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png', + "colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png', + "hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png', + "legion" = 'icons/UI_Icons/Achievements/Boss/legion.png', + "miner" = 'icons/UI_Icons/Achievements/Boss/miner.png', + "swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png', + "tendril" = 'icons/UI_Icons/Achievements/Boss/tendril.png', + "featofstrength" = 'icons/UI_Icons/Achievements/Misc/featofstrength.png', + "helbital" = 'icons/UI_Icons/Achievements/Misc/helbital.png', + "jackpot" = 'icons/UI_Icons/Achievements/Misc/jackpot.png', + "meteors" = 'icons/UI_Icons/Achievements/Misc/meteors.png', + "timewaste" = 'icons/UI_Icons/Achievements/Misc/timewaste.png', + "upgrade" = 'icons/UI_Icons/Achievements/Misc/upgrade.png', + "clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png', + "clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png', + "rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png', + "snail" = 'icons/UI_Icons/Achievements/Misc/snail.png', + "mining" = 'icons/UI_Icons/Achievements/Skills/mining.png', + ) + +/datum/asset/spritesheet/simple/pills + name ="pills" + assets = list( + "pill1" = 'icons/UI_Icons/Pills/pill1.png', + "pill2" = 'icons/UI_Icons/Pills/pill2.png', + "pill3" = 'icons/UI_Icons/Pills/pill3.png', + "pill4" = 'icons/UI_Icons/Pills/pill4.png', + "pill5" = 'icons/UI_Icons/Pills/pill5.png', + "pill6" = 'icons/UI_Icons/Pills/pill6.png', + "pill7" = 'icons/UI_Icons/Pills/pill7.png', + "pill8" = 'icons/UI_Icons/Pills/pill8.png', + "pill9" = 'icons/UI_Icons/Pills/pill9.png', + "pill10" = 'icons/UI_Icons/Pills/pill10.png', + "pill11" = 'icons/UI_Icons/Pills/pill11.png', + "pill12" = 'icons/UI_Icons/Pills/pill12.png', + "pill13" = 'icons/UI_Icons/Pills/pill13.png', + "pill14" = 'icons/UI_Icons/Pills/pill14.png', + "pill15" = 'icons/UI_Icons/Pills/pill15.png', + "pill16" = 'icons/UI_Icons/Pills/pill16.png', + "pill17" = 'icons/UI_Icons/Pills/pill17.png', + "pill18" = 'icons/UI_Icons/Pills/pill18.png', + "pill19" = 'icons/UI_Icons/Pills/pill19.png', + "pill20" = 'icons/UI_Icons/Pills/pill20.png', + "pill21" = 'icons/UI_Icons/Pills/pill21.png', + "pill22" = 'icons/UI_Icons/Pills/pill22.png', + ) + +//this exists purely to avoid meta by pre-loading all language icons. +/datum/asset/language/register() + for(var/path in typesof(/datum/language)) + set waitfor = FALSE + var/datum/language/L = new path () + L.get_icon() + +/datum/asset/spritesheet/pipes + name = "pipes" + +/datum/asset/spritesheet/pipes/register() + for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi', 'icons/obj/atmospherics/pipes/transit_tube.dmi', 'icons/obj/plumbing/fluid_ducts.dmi')) + InsertAll("", each, GLOB.alldirs) + ..() + +// Representative icons for each research design +/datum/asset/spritesheet/research_designs + name = "design" + +/datum/asset/spritesheet/research_designs/register() + for (var/path in subtypesof(/datum/design)) + var/datum/design/D = path + + var/icon_file + var/icon_state + var/icon/I + + if(initial(D.research_icon) && initial(D.research_icon_state)) //If the design has an icon replacement skip the rest + icon_file = initial(D.research_icon) + icon_state = initial(D.research_icon_state) + if(!(icon_state in icon_states(icon_file))) + warning("design [D] with icon '[icon_file]' missing state '[icon_state]'") + continue + I = icon(icon_file, icon_state, SOUTH) + + else + // construct the icon and slap it into the resource cache + var/atom/item = initial(D.build_path) + if (!ispath(item, /atom)) + // biogenerator outputs to beakers by default + if (initial(D.build_type) & BIOGENERATOR) + item = /obj/item/reagent_containers/glass/beaker/large + else + continue // shouldn't happen, but just in case + + // circuit boards become their resulting machines or computers + if (ispath(item, /obj/item/circuitboard)) + var/obj/item/circuitboard/C = item + var/machine = initial(C.build_path) + if (machine) + item = machine + + icon_file = initial(item.icon) + icon_state = initial(item.icon_state) + + if(!(icon_state in icon_states(icon_file))) + warning("design [D] with icon '[icon_file]' missing state '[icon_state]'") + continue + I = icon(icon_file, icon_state, SOUTH) + + // computers (and snowflakes) get their screen and keyboard sprites + if (ispath(item, /obj/machinery/computer) || ispath(item, /obj/machinery/power/solar_control)) + var/obj/machinery/computer/C = item + var/screen = initial(C.icon_screen) + var/keyboard = initial(C.icon_keyboard) + var/all_states = icon_states(icon_file) + if (screen && (screen in all_states)) + I.Blend(icon(icon_file, screen, SOUTH), ICON_OVERLAY) + if (keyboard && (keyboard in all_states)) + I.Blend(icon(icon_file, keyboard, SOUTH), ICON_OVERLAY) + + Insert(initial(D.id), I) + return ..() + +/datum/asset/spritesheet/vending + name = "vending" + +/datum/asset/spritesheet/vending/register() + for (var/k in GLOB.vending_products) + var/atom/item = k + if (!ispath(item, /atom)) + continue + + var/icon_file = initial(item.icon) + var/icon_state = initial(item.icon_state) + var/icon/I + + var/icon_states_list = icon_states(icon_file) + if(icon_state in icon_states_list) + I = icon(icon_file, icon_state, SOUTH) + var/c = initial(item.color) + if (!isnull(c) && c != "#FFFFFF") + I.Blend(c, ICON_MULTIPLY) + else + var/icon_states_string + for (var/an_icon_state in icon_states_list) + if (!icon_states_string) + icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])" + else + icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])" + stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]") + I = icon('icons/turf/floors.dmi', "", SOUTH) + + var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-") + + Insert(imgid, I) + return ..() + +/datum/asset/simple/genetics + assets = list( + "dna_discovered.gif" = 'html/dna_discovered.gif', + "dna_undiscovered.gif" = 'html/dna_undiscovered.gif', + "dna_extra.gif" = 'html/dna_extra.gif' + ) + +/datum/asset/simple/orbit + assets = list( + "ghost.png" = 'html/ghost.png' + ) + +/datum/asset/simple/vv + assets = list( + "view_variables.css" = 'html/admin/view_variables.css' + ) diff --git a/code/modules/asset_cache/validate_assets.html b/code/modules/asset_cache/validate_assets.html index 075772ce63d..b27a266c00d 100644 --- a/code/modules/asset_cache/validate_assets.html +++ b/code/modules/asset_cache/validate_assets.html @@ -1,29 +1,29 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 147527daccf..5085442516f 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -1,531 +1,531 @@ - /* -What are the archived variables for? - Calculations are done using the archived variables with the results merged into the regular variables. - This prevents race conditions that arise based on the order of tile processing. -*/ -#define MINIMUM_HEAT_CAPACITY 0.0003 -#define MINIMUM_MOLE_COUNT 0.01 -#define MOLAR_ACCURACY 1E-7 -#define QUANTIZE(variable) (round((variable), (MOLAR_ACCURACY)))/*I feel the need to document what happens here. Basically this is used - to catch most rounding errors, however its previous value made it so that - once gases got hot enough, most procedures wouldn't occur due to the fact that the mole - counts would get rounded away. Thus, we lowered it a few orders of magnitude - Edit: As far as I know this might have a bug caused by round(). When it has a second arg it will round up. - So for instance round(0.5, 1) == 1. Trouble is I haven't found any instances of it causing a bug, - and any attempts to fix it just killed atmos. I leave this to a greater man then I*/ -GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm -GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) - -/proc/init_gaslist_cache() - . = list() - for(var/id in GLOB.meta_gas_info) - var/list/cached_gas = new(3) - - .[id] = cached_gas - - cached_gas[MOLES] = 0 - cached_gas[ARCHIVE] = 0 - cached_gas[GAS_META] = GLOB.meta_gas_info[id] - -/datum/gas_mixture - var/list/gases - var/temperature = 0 //kelvins - var/tmp/temperature_archived = 0 - var/volume = CELL_VOLUME //liters - var/last_share = 0 - var/list/reaction_results - var/list/analyzer_results //used for analyzer feedback - not initialized until its used - var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures - -/datum/gas_mixture/New(volume) - gases = new - if (!isnull(volume)) - src.volume = volume - reaction_results = new - -//listmos procs -//use the macros in performance intensive areas. for their definitions, refer to code/__DEFINES/atmospherics.dm - - ///assert_gas(gas_id) - used to guarantee that the gas list for this id exists in gas_mixture.gases. - //Must be used before adding to a gas. May be used before reading from a gas. -/datum/gas_mixture/proc/assert_gas(gas_id) - ASSERT_GAS(gas_id, src) - - ///assert_gases(args) - shorthand for calling ASSERT_GAS() once for each gas type. -/datum/gas_mixture/proc/assert_gases(...) - for(var/id in args) - ASSERT_GAS(id, src) - - ///add_gas(gas_id) - similar to assert_gas(), but does not check for an existing gas list for this id. This can clobber existing gases. - ///Used instead of assert_gas() when you know the gas does not exist. Faster than assert_gas(). -/datum/gas_mixture/proc/add_gas(gas_id) - ADD_GAS(gas_id, gases) - - ///add_gases(args) - shorthand for calling add_gas() once for each gas_type. -/datum/gas_mixture/proc/add_gases(...) - var/cached_gases = gases - for(var/id in args) - ADD_GAS(id, cached_gases) - - ///garbage_collect() - removes any gas list which is empty. - ///If called with a list as an argument, only removes gas lists with IDs from that list. - ///Must be used after subtracting from a gas. Must be used after assert_gas() - ///if assert_gas() was called only to read from the gas. - ///By removing empty gases, processing speed is increased. -/datum/gas_mixture/proc/garbage_collect(list/tocheck) - var/list/cached_gases = gases - for(var/id in (tocheck || cached_gases)) - if(QUANTIZE(cached_gases[id][MOLES]) <= 0) - cached_gases -= id - - //PV = nRT - - ///joules per kelvin -/datum/gas_mixture/proc/heat_capacity(data = MOLES) - var/list/cached_gases = gases - . = 0 - for(var/id in cached_gases) - var/gas_data = cached_gases[id] - . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] - - /// Same as above except vacuums return HEAT_CAPACITY_VACUUM -/datum/gas_mixture/turf/heat_capacity(data = MOLES) - var/list/cached_gases = gases - . = 0 - for(var/id in cached_gases) - var/gas_data = cached_gases[id] - . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] - if(!.) - . += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space - - /// Calculate moles -/datum/gas_mixture/proc/total_moles() - var/cached_gases = gases - TOTAL_MOLES(cached_gases, .) - - /// Calculate pressure in kilopascals -/datum/gas_mixture/proc/return_pressure() - if(volume > 0) // to prevent division by zero - var/cached_gases = gases - TOTAL_MOLES(cached_gases, .) - . *= R_IDEAL_GAS_EQUATION * temperature / volume - return - return 0 - - /// Calculate temperature in kelvins -/datum/gas_mixture/proc/return_temperature() - return temperature - - /// Calculate volume in liters -/datum/gas_mixture/proc/return_volume() - return max(0, volume) - - /// Calculate thermal energy in joules -/datum/gas_mixture/proc/thermal_energy() - return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas - - ///Update archived versions of variables. Returns: 1 in all cases -/datum/gas_mixture/proc/archive() - var/list/cached_gases = gases - - temperature_archived = temperature - for(var/id in cached_gases) - cached_gases[id][ARCHIVE] = cached_gases[id][MOLES] - - return 1 - - ///Merges all air from giver into self. Deletes giver. Returns: 1 if we are mutable, 0 otherwise -/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) - if(!giver) - return 0 - - //heat transfer - if(abs(temperature - giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/self_heat_capacity = heat_capacity() - var/giver_heat_capacity = giver.heat_capacity() - var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity - if(combined_heat_capacity) - temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity - - var/list/cached_gases = gases //accessing datum vars is slower than proc vars - var/list/giver_gases = giver.gases - //gas transfer - for(var/giver_id in giver_gases) - ASSERT_GAS(giver_id, src) - cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] - - return 1 - - ///Proportionally removes amount of gas from the gas_mixture. - ///Returns: gas_mixture with the gases removed -/datum/gas_mixture/proc/remove(amount) - var/sum - var/list/cached_gases = gases - TOTAL_MOLES(cached_gases, sum) - amount = min(amount, sum) //Can not take more air than tile has! - if(amount <= 0) - return null - var/datum/gas_mixture/removed = new type - var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars - - removed.temperature = temperature - for(var/id in cached_gases) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE((cached_gases[id][MOLES] / sum) * amount) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] - garbage_collect() - - return removed - - ///Proportionally removes amount of gas from the gas_mixture. - ///Returns: gas_mixture with the gases removed -/datum/gas_mixture/proc/remove_ratio(ratio) - if(ratio <= 0) - return null - ratio = min(ratio, 1) - - var/list/cached_gases = gases - var/datum/gas_mixture/removed = new type - var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars - - removed.temperature = temperature - for(var/id in cached_gases) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] - - garbage_collect() - - return removed - - ///Removes an amount of a specific gas from the gas_mixture. - ///Returns: gas_mixture with the gas removed -/datum/gas_mixture/proc/remove_specific(gas_id, amount) - var/list/cached_gases = gases - amount = min(amount, cached_gases[gas_id][MOLES]) - if(amount <= 0) - return null - var/datum/gas_mixture/removed = new type - var/list/removed_gases = removed.gases - removed.temperature = temperature - ADD_GAS(gas_id, removed.gases) - removed_gases[gas_id][MOLES] = amount - cached_gases[gas_id][MOLES] -= amount - - garbage_collect(list(gas_id)) - return removed - - ///Distributes the contents of two mixes equally between themselves - //Returns: bool indicating whether gases moved between the two mixes -/datum/gas_mixture/proc/equalize(datum/gas_mixture/other) - . = FALSE - if(abs(return_temperature() - other.return_temperature()) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) - . = TRUE - var/self_heat_cap = heat_capacity() - var/other_heat_cap = other.heat_capacity() - var/new_temp = (temperature * self_heat_cap + other.temperature * other_heat_cap) / (self_heat_cap + other_heat_cap) - temperature = new_temp - other.temperature = new_temp - - var/min_p_delta = 0.1 - var/total_volume = volume + other.volume - var/list/gas_list = gases | other.gases - for(var/gas_id in gas_list) - assert_gas(gas_id) - other.assert_gas(gas_id) - //math is under the assumption temperatures are equal - if(abs(gases[gas_id][MOLES] / volume - other.gases[gas_id][MOLES] / other.volume) > min_p_delta / (R_IDEAL_GAS_EQUATION * temperature)) - . = TRUE - var/total_moles = gases[gas_id][MOLES] + other.gases[gas_id][MOLES] - gases[gas_id][MOLES] = total_moles * (volume/total_volume) - other.gases[gas_id][MOLES] = total_moles * (other.volume/total_volume) - - - ///Creates new, identical gas mixture - ///Returns: duplicate gas mixture -/datum/gas_mixture/proc/copy() - var/list/cached_gases = gases - var/datum/gas_mixture/copy = new type - var/list/copy_gases = copy.gases - - copy.temperature = temperature - for(var/id in cached_gases) - ADD_GAS(id, copy.gases) - copy_gases[id][MOLES] = cached_gases[id][MOLES] - - return copy - - ///Copies variables from sample, moles multiplicated by partial - ///Returns: 1 if we are mutable, 0 otherwise -/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample, partial = 1) - var/list/cached_gases = gases //accessing datum vars is slower than proc vars - var/list/sample_gases = sample.gases - - //remove all gases not in the sample - cached_gases &= sample_gases - - temperature = sample.temperature - for(var/id in sample_gases) - ASSERT_GAS(id,src) - cached_gases[id][MOLES] = sample_gases[id][MOLES] * partial - - return 1 - - ///Copies all gas info from the turf into the gas list along with temperature - ///Returns: 1 if we are mutable, 0 otherwise -/datum/gas_mixture/proc/copy_from_turf(turf/model) - parse_gas_string(model.initial_gas_mix) - - //acounts for changes in temperature - var/turf/model_parent = model.parent_type - if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature)) - temperature = model.temperature - - return 1 - - ///Copies variables from a particularly formatted string. - ///Returns: 1 if we are mutable, 0 otherwise -/datum/gas_mixture/proc/parse_gas_string(gas_string) - gas_string = SSair.preprocess_gas_string(gas_string) - - var/list/gases = src.gases - var/list/gas = params2list(gas_string) - if(gas["TEMP"]) - temperature = text2num(gas["TEMP"]) - gas -= "TEMP" - gases.Cut() - for(var/id in gas) - var/path = id - if(!ispath(path)) - path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around - ADD_GAS(path, gases) - gases[path][MOLES] = text2num(gas[id]) - return 1 - - ///Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length - ///Returns: amount of gas exchanged (+ if sharer received) -/datum/gas_mixture/proc/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) - var/list/cached_gases = gases - var/list/sharer_gases = sharer.gases - - var/temperature_delta = temperature_archived - sharer.temperature_archived - var/abs_temperature_delta = abs(temperature_delta) - - var/old_self_heat_capacity = 0 - var/old_sharer_heat_capacity = 0 - if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - old_self_heat_capacity = heat_capacity() - old_sharer_heat_capacity = sharer.heat_capacity() - - var/heat_capacity_self_to_sharer = 0 //heat capacity of the moles transferred from us to the sharer - var/heat_capacity_sharer_to_self = 0 //heat capacity of the moles transferred from the sharer to us - - var/moved_moles = 0 - var/abs_moved_moles = 0 - - //GAS TRANSFER - for(var/id in sharer_gases - cached_gases) // create gases not in our cache - ADD_GAS(id, gases) - for(var/id in cached_gases) // transfer gases - ASSERT_GAS(id, sharer) - - var/gas = cached_gases[id] - var/sharergas = sharer_gases[id] - - var/delta = QUANTIZE(gas[ARCHIVE] - sharergas[ARCHIVE])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures - - if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/gas_heat_capacity = delta * gas[GAS_META][META_GAS_SPECIFIC_HEAT] - if(delta > 0) - heat_capacity_self_to_sharer += gas_heat_capacity - else - heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative. - - gas[MOLES] -= delta - sharergas[MOLES] += delta - moved_moles += delta - abs_moved_moles += abs(delta) - - last_share = abs_moved_moles - - //THERMAL ENERGY TRANSFER - if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer - var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self - - //transfer of thermal energy (via changed heat capacity) between self and sharer - if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY) - temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity - - if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) - sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity - //thermal energy of the system (self and sharer) is unchanged - - if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY) - if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity - temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT) - - garbage_collect() - sharer.garbage_collect() - if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) - var/our_moles - TOTAL_MOLES(cached_gases,our_moles) - var/their_moles - TOTAL_MOLES(sharer_gases,their_moles) - return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume - - ///Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length - ///Returns: new temperature of the sharer -/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity) - //transfer of thermal energy (via conduction) between self and sharer - if(sharer) - sharer_temperature = sharer.temperature_archived - var/temperature_delta = temperature_archived - sharer_temperature - if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/self_heat_capacity = heat_capacity(ARCHIVE) - sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity(ARCHIVE) - - if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY)) - var/heat = conduction_coefficient*temperature_delta* \ - (self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity)) - - temperature = max(temperature - heat/self_heat_capacity, TCMB) - sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB) - if(sharer) - sharer.temperature = sharer_temperature - if (initial(sharer.gc_share)) - sharer.garbage_collect() - return sharer_temperature - //thermal energy of the system (self and sharer) is unchanged - - ///Compares sample to self to see if within acceptable ranges that group processing may be enabled - ///Returns: a string indicating what check failed, or "" if check passes -/datum/gas_mixture/proc/compare(datum/gas_mixture/sample) - var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars - var/list/cached_gases = gases - - for(var/id in cached_gases | sample_gases) // compare gases from either mixture - var/gas_moles = cached_gases[id] - gas_moles = gas_moles ? gas_moles[MOLES] : 0 - var/sample_moles = sample_gases[id] - sample_moles = sample_moles ? sample_moles[MOLES] : 0 - var/delta = abs(gas_moles - sample_moles) - if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \ - delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE) - return id - - var/our_moles - TOTAL_MOLES(cached_gases, our_moles) - if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE) - var/temp = temperature - var/sample_temp = sample.temperature - - var/temperature_delta = abs(temp - sample_temp) - if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) - return "temp" - - return "" - - ///Performs various reactions such as combustion or fusion (LOL) - ///Returns: 1 if any reaction took place; 0 otherwise -/datum/gas_mixture/proc/react(datum/holder) - . = NO_REACTION - var/list/cached_gases = gases - if(!length(cached_gases)) - return - var/list/reactions = list() - for(var/datum/gas_reaction/G in SSair.gas_reactions) - if(cached_gases[G.major_gas]) - reactions += G - if(!length(reactions)) - return - reaction_results = new - var/temp = temperature - var/ener = THERMAL_ENERGY(src) - - reaction_loop: - for(var/r in reactions) - var/datum/gas_reaction/reaction = r - - var/list/min_reqs = reaction.min_requirements - if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \ - || (min_reqs["ENER"] && ener < min_reqs["ENER"])) - continue - - for(var/id in min_reqs) - if (id == "TEMP" || id == "ENER") - continue - if(!cached_gases[id] || cached_gases[id][MOLES] < min_reqs[id]) - continue reaction_loop - - //at this point, all requirements for the reaction are satisfied. we can now react() - - . |= reaction.react(src, holder) - if (. & STOP_REACTIONS) - break - if(.) - garbage_collect() - -///Takes the amount of the gas you want to PP as an argument -///So I don't have to do some hacky switches/defines/magic strings -///eg: -///Tox_PP = get_partial_pressure(gas_mixture.toxins) -///O2_PP = get_partial_pressure(gas_mixture.oxygen) - -/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure) - return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME -///inverse -/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure) - return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature) - -///Mathematical proofs: -/** -get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp -get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles() - -10/20*5 = 2.5 -10 = 2.5/5*20 -**/ - -/// Pumps gas from src to output_air. Amount depends on target_pressure -/datum/gas_mixture/proc/pump_gas_to(datum/gas_mixture/output_air, target_pressure) - var/output_starting_pressure = output_air.return_pressure() - - if((target_pressure - output_starting_pressure) < 0.01) - //No need to pump gas if target is already reached! - return FALSE - - //Calculate necessary moles to transfer using PV=nRT - if((total_moles() > 0) && (temperature>0)) - var/pressure_delta = target_pressure - output_starting_pressure - var/transfer_moles = pressure_delta*output_air.volume/(temperature * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - var/datum/gas_mixture/removed = remove(transfer_moles) - output_air.merge(removed) - return TRUE - return FALSE - -/// Releases gas from src to output air. This means that it can not transfer air to gas mixture with higher pressure. -/datum/gas_mixture/proc/release_gas_to(datum/gas_mixture/output_air, target_pressure) - var/output_starting_pressure = output_air.return_pressure() - var/input_starting_pressure = return_pressure() - - if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10)) - //No need to pump gas if target is already reached or input pressure is too low - //Need at least 10 KPa difference to overcome friction in the mechanism - return FALSE - - //Calculate necessary moles to transfer using PV = nRT - if((total_moles() > 0) && (temperature>0)) - var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2) - //Can not have a pressure delta that would cause output_pressure > input_pressure - - var/transfer_moles = pressure_delta*output_air.volume/(temperature * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - var/datum/gas_mixture/removed = remove(transfer_moles) - output_air.merge(removed) - - return TRUE - return FALSE + /* +What are the archived variables for? + Calculations are done using the archived variables with the results merged into the regular variables. + This prevents race conditions that arise based on the order of tile processing. +*/ +#define MINIMUM_HEAT_CAPACITY 0.0003 +#define MINIMUM_MOLE_COUNT 0.01 +#define MOLAR_ACCURACY 1E-7 +#define QUANTIZE(variable) (round((variable), (MOLAR_ACCURACY)))/*I feel the need to document what happens here. Basically this is used + to catch most rounding errors, however its previous value made it so that + once gases got hot enough, most procedures wouldn't occur due to the fact that the mole + counts would get rounded away. Thus, we lowered it a few orders of magnitude + Edit: As far as I know this might have a bug caused by round(). When it has a second arg it will round up. + So for instance round(0.5, 1) == 1. Trouble is I haven't found any instances of it causing a bug, + and any attempts to fix it just killed atmos. I leave this to a greater man then I*/ +GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm +GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) + +/proc/init_gaslist_cache() + . = list() + for(var/id in GLOB.meta_gas_info) + var/list/cached_gas = new(3) + + .[id] = cached_gas + + cached_gas[MOLES] = 0 + cached_gas[ARCHIVE] = 0 + cached_gas[GAS_META] = GLOB.meta_gas_info[id] + +/datum/gas_mixture + var/list/gases + var/temperature = 0 //kelvins + var/tmp/temperature_archived = 0 + var/volume = CELL_VOLUME //liters + var/last_share = 0 + var/list/reaction_results + var/list/analyzer_results //used for analyzer feedback - not initialized until its used + var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures + +/datum/gas_mixture/New(volume) + gases = new + if (!isnull(volume)) + src.volume = volume + reaction_results = new + +//listmos procs +//use the macros in performance intensive areas. for their definitions, refer to code/__DEFINES/atmospherics.dm + + ///assert_gas(gas_id) - used to guarantee that the gas list for this id exists in gas_mixture.gases. + //Must be used before adding to a gas. May be used before reading from a gas. +/datum/gas_mixture/proc/assert_gas(gas_id) + ASSERT_GAS(gas_id, src) + + ///assert_gases(args) - shorthand for calling ASSERT_GAS() once for each gas type. +/datum/gas_mixture/proc/assert_gases(...) + for(var/id in args) + ASSERT_GAS(id, src) + + ///add_gas(gas_id) - similar to assert_gas(), but does not check for an existing gas list for this id. This can clobber existing gases. + ///Used instead of assert_gas() when you know the gas does not exist. Faster than assert_gas(). +/datum/gas_mixture/proc/add_gas(gas_id) + ADD_GAS(gas_id, gases) + + ///add_gases(args) - shorthand for calling add_gas() once for each gas_type. +/datum/gas_mixture/proc/add_gases(...) + var/cached_gases = gases + for(var/id in args) + ADD_GAS(id, cached_gases) + + ///garbage_collect() - removes any gas list which is empty. + ///If called with a list as an argument, only removes gas lists with IDs from that list. + ///Must be used after subtracting from a gas. Must be used after assert_gas() + ///if assert_gas() was called only to read from the gas. + ///By removing empty gases, processing speed is increased. +/datum/gas_mixture/proc/garbage_collect(list/tocheck) + var/list/cached_gases = gases + for(var/id in (tocheck || cached_gases)) + if(QUANTIZE(cached_gases[id][MOLES]) <= 0) + cached_gases -= id + + //PV = nRT + + ///joules per kelvin +/datum/gas_mixture/proc/heat_capacity(data = MOLES) + var/list/cached_gases = gases + . = 0 + for(var/id in cached_gases) + var/gas_data = cached_gases[id] + . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] + + /// Same as above except vacuums return HEAT_CAPACITY_VACUUM +/datum/gas_mixture/turf/heat_capacity(data = MOLES) + var/list/cached_gases = gases + . = 0 + for(var/id in cached_gases) + var/gas_data = cached_gases[id] + . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] + if(!.) + . += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space + + /// Calculate moles +/datum/gas_mixture/proc/total_moles() + var/cached_gases = gases + TOTAL_MOLES(cached_gases, .) + + /// Calculate pressure in kilopascals +/datum/gas_mixture/proc/return_pressure() + if(volume > 0) // to prevent division by zero + var/cached_gases = gases + TOTAL_MOLES(cached_gases, .) + . *= R_IDEAL_GAS_EQUATION * temperature / volume + return + return 0 + + /// Calculate temperature in kelvins +/datum/gas_mixture/proc/return_temperature() + return temperature + + /// Calculate volume in liters +/datum/gas_mixture/proc/return_volume() + return max(0, volume) + + /// Calculate thermal energy in joules +/datum/gas_mixture/proc/thermal_energy() + return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas + + ///Update archived versions of variables. Returns: 1 in all cases +/datum/gas_mixture/proc/archive() + var/list/cached_gases = gases + + temperature_archived = temperature + for(var/id in cached_gases) + cached_gases[id][ARCHIVE] = cached_gases[id][MOLES] + + return 1 + + ///Merges all air from giver into self. Deletes giver. Returns: 1 if we are mutable, 0 otherwise +/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) + if(!giver) + return 0 + + //heat transfer + if(abs(temperature - giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/self_heat_capacity = heat_capacity() + var/giver_heat_capacity = giver.heat_capacity() + var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity + if(combined_heat_capacity) + temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity + + var/list/cached_gases = gases //accessing datum vars is slower than proc vars + var/list/giver_gases = giver.gases + //gas transfer + for(var/giver_id in giver_gases) + ASSERT_GAS(giver_id, src) + cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] + + return 1 + + ///Proportionally removes amount of gas from the gas_mixture. + ///Returns: gas_mixture with the gases removed +/datum/gas_mixture/proc/remove(amount) + var/sum + var/list/cached_gases = gases + TOTAL_MOLES(cached_gases, sum) + amount = min(amount, sum) //Can not take more air than tile has! + if(amount <= 0) + return null + var/datum/gas_mixture/removed = new type + var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars + + removed.temperature = temperature + for(var/id in cached_gases) + ADD_GAS(id, removed.gases) + removed_gases[id][MOLES] = QUANTIZE((cached_gases[id][MOLES] / sum) * amount) + cached_gases[id][MOLES] -= removed_gases[id][MOLES] + garbage_collect() + + return removed + + ///Proportionally removes amount of gas from the gas_mixture. + ///Returns: gas_mixture with the gases removed +/datum/gas_mixture/proc/remove_ratio(ratio) + if(ratio <= 0) + return null + ratio = min(ratio, 1) + + var/list/cached_gases = gases + var/datum/gas_mixture/removed = new type + var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars + + removed.temperature = temperature + for(var/id in cached_gases) + ADD_GAS(id, removed.gases) + removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio) + cached_gases[id][MOLES] -= removed_gases[id][MOLES] + + garbage_collect() + + return removed + + ///Removes an amount of a specific gas from the gas_mixture. + ///Returns: gas_mixture with the gas removed +/datum/gas_mixture/proc/remove_specific(gas_id, amount) + var/list/cached_gases = gases + amount = min(amount, cached_gases[gas_id][MOLES]) + if(amount <= 0) + return null + var/datum/gas_mixture/removed = new type + var/list/removed_gases = removed.gases + removed.temperature = temperature + ADD_GAS(gas_id, removed.gases) + removed_gases[gas_id][MOLES] = amount + cached_gases[gas_id][MOLES] -= amount + + garbage_collect(list(gas_id)) + return removed + + ///Distributes the contents of two mixes equally between themselves + //Returns: bool indicating whether gases moved between the two mixes +/datum/gas_mixture/proc/equalize(datum/gas_mixture/other) + . = FALSE + if(abs(return_temperature() - other.return_temperature()) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) + . = TRUE + var/self_heat_cap = heat_capacity() + var/other_heat_cap = other.heat_capacity() + var/new_temp = (temperature * self_heat_cap + other.temperature * other_heat_cap) / (self_heat_cap + other_heat_cap) + temperature = new_temp + other.temperature = new_temp + + var/min_p_delta = 0.1 + var/total_volume = volume + other.volume + var/list/gas_list = gases | other.gases + for(var/gas_id in gas_list) + assert_gas(gas_id) + other.assert_gas(gas_id) + //math is under the assumption temperatures are equal + if(abs(gases[gas_id][MOLES] / volume - other.gases[gas_id][MOLES] / other.volume) > min_p_delta / (R_IDEAL_GAS_EQUATION * temperature)) + . = TRUE + var/total_moles = gases[gas_id][MOLES] + other.gases[gas_id][MOLES] + gases[gas_id][MOLES] = total_moles * (volume/total_volume) + other.gases[gas_id][MOLES] = total_moles * (other.volume/total_volume) + + + ///Creates new, identical gas mixture + ///Returns: duplicate gas mixture +/datum/gas_mixture/proc/copy() + var/list/cached_gases = gases + var/datum/gas_mixture/copy = new type + var/list/copy_gases = copy.gases + + copy.temperature = temperature + for(var/id in cached_gases) + ADD_GAS(id, copy.gases) + copy_gases[id][MOLES] = cached_gases[id][MOLES] + + return copy + + ///Copies variables from sample, moles multiplicated by partial + ///Returns: 1 if we are mutable, 0 otherwise +/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample, partial = 1) + var/list/cached_gases = gases //accessing datum vars is slower than proc vars + var/list/sample_gases = sample.gases + + //remove all gases not in the sample + cached_gases &= sample_gases + + temperature = sample.temperature + for(var/id in sample_gases) + ASSERT_GAS(id,src) + cached_gases[id][MOLES] = sample_gases[id][MOLES] * partial + + return 1 + + ///Copies all gas info from the turf into the gas list along with temperature + ///Returns: 1 if we are mutable, 0 otherwise +/datum/gas_mixture/proc/copy_from_turf(turf/model) + parse_gas_string(model.initial_gas_mix) + + //acounts for changes in temperature + var/turf/model_parent = model.parent_type + if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature)) + temperature = model.temperature + + return 1 + + ///Copies variables from a particularly formatted string. + ///Returns: 1 if we are mutable, 0 otherwise +/datum/gas_mixture/proc/parse_gas_string(gas_string) + gas_string = SSair.preprocess_gas_string(gas_string) + + var/list/gases = src.gases + var/list/gas = params2list(gas_string) + if(gas["TEMP"]) + temperature = text2num(gas["TEMP"]) + gas -= "TEMP" + gases.Cut() + for(var/id in gas) + var/path = id + if(!ispath(path)) + path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around + ADD_GAS(path, gases) + gases[path][MOLES] = text2num(gas[id]) + return 1 + + ///Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length + ///Returns: amount of gas exchanged (+ if sharer received) +/datum/gas_mixture/proc/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) + var/list/cached_gases = gases + var/list/sharer_gases = sharer.gases + + var/temperature_delta = temperature_archived - sharer.temperature_archived + var/abs_temperature_delta = abs(temperature_delta) + + var/old_self_heat_capacity = 0 + var/old_sharer_heat_capacity = 0 + if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + old_self_heat_capacity = heat_capacity() + old_sharer_heat_capacity = sharer.heat_capacity() + + var/heat_capacity_self_to_sharer = 0 //heat capacity of the moles transferred from us to the sharer + var/heat_capacity_sharer_to_self = 0 //heat capacity of the moles transferred from the sharer to us + + var/moved_moles = 0 + var/abs_moved_moles = 0 + + //GAS TRANSFER + for(var/id in sharer_gases - cached_gases) // create gases not in our cache + ADD_GAS(id, gases) + for(var/id in cached_gases) // transfer gases + ASSERT_GAS(id, sharer) + + var/gas = cached_gases[id] + var/sharergas = sharer_gases[id] + + var/delta = QUANTIZE(gas[ARCHIVE] - sharergas[ARCHIVE])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures + + if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/gas_heat_capacity = delta * gas[GAS_META][META_GAS_SPECIFIC_HEAT] + if(delta > 0) + heat_capacity_self_to_sharer += gas_heat_capacity + else + heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative. + + gas[MOLES] -= delta + sharergas[MOLES] += delta + moved_moles += delta + abs_moved_moles += abs(delta) + + last_share = abs_moved_moles + + //THERMAL ENERGY TRANSFER + if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer + var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self + + //transfer of thermal energy (via changed heat capacity) between self and sharer + if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY) + temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity + + if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) + sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity + //thermal energy of the system (self and sharer) is unchanged + + if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY) + if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity + temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT) + + garbage_collect() + sharer.garbage_collect() + if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) + var/our_moles + TOTAL_MOLES(cached_gases,our_moles) + var/their_moles + TOTAL_MOLES(sharer_gases,their_moles) + return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume + + ///Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length + ///Returns: new temperature of the sharer +/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity) + //transfer of thermal energy (via conduction) between self and sharer + if(sharer) + sharer_temperature = sharer.temperature_archived + var/temperature_delta = temperature_archived - sharer_temperature + if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/self_heat_capacity = heat_capacity(ARCHIVE) + sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity(ARCHIVE) + + if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY)) + var/heat = conduction_coefficient*temperature_delta* \ + (self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity)) + + temperature = max(temperature - heat/self_heat_capacity, TCMB) + sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB) + if(sharer) + sharer.temperature = sharer_temperature + if (initial(sharer.gc_share)) + sharer.garbage_collect() + return sharer_temperature + //thermal energy of the system (self and sharer) is unchanged + + ///Compares sample to self to see if within acceptable ranges that group processing may be enabled + ///Returns: a string indicating what check failed, or "" if check passes +/datum/gas_mixture/proc/compare(datum/gas_mixture/sample) + var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars + var/list/cached_gases = gases + + for(var/id in cached_gases | sample_gases) // compare gases from either mixture + var/gas_moles = cached_gases[id] + gas_moles = gas_moles ? gas_moles[MOLES] : 0 + var/sample_moles = sample_gases[id] + sample_moles = sample_moles ? sample_moles[MOLES] : 0 + var/delta = abs(gas_moles - sample_moles) + if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \ + delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE) + return id + + var/our_moles + TOTAL_MOLES(cached_gases, our_moles) + if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE) + var/temp = temperature + var/sample_temp = sample.temperature + + var/temperature_delta = abs(temp - sample_temp) + if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) + return "temp" + + return "" + + ///Performs various reactions such as combustion or fusion (LOL) + ///Returns: 1 if any reaction took place; 0 otherwise +/datum/gas_mixture/proc/react(datum/holder) + . = NO_REACTION + var/list/cached_gases = gases + if(!length(cached_gases)) + return + var/list/reactions = list() + for(var/datum/gas_reaction/G in SSair.gas_reactions) + if(cached_gases[G.major_gas]) + reactions += G + if(!length(reactions)) + return + reaction_results = new + var/temp = temperature + var/ener = THERMAL_ENERGY(src) + + reaction_loop: + for(var/r in reactions) + var/datum/gas_reaction/reaction = r + + var/list/min_reqs = reaction.min_requirements + if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \ + || (min_reqs["ENER"] && ener < min_reqs["ENER"])) + continue + + for(var/id in min_reqs) + if (id == "TEMP" || id == "ENER") + continue + if(!cached_gases[id] || cached_gases[id][MOLES] < min_reqs[id]) + continue reaction_loop + + //at this point, all requirements for the reaction are satisfied. we can now react() + + . |= reaction.react(src, holder) + if (. & STOP_REACTIONS) + break + if(.) + garbage_collect() + +///Takes the amount of the gas you want to PP as an argument +///So I don't have to do some hacky switches/defines/magic strings +///eg: +///Tox_PP = get_partial_pressure(gas_mixture.toxins) +///O2_PP = get_partial_pressure(gas_mixture.oxygen) + +/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure) + return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME +///inverse +/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure) + return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature) + +///Mathematical proofs: +/** +get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp +get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles() + +10/20*5 = 2.5 +10 = 2.5/5*20 +**/ + +/// Pumps gas from src to output_air. Amount depends on target_pressure +/datum/gas_mixture/proc/pump_gas_to(datum/gas_mixture/output_air, target_pressure) + var/output_starting_pressure = output_air.return_pressure() + + if((target_pressure - output_starting_pressure) < 0.01) + //No need to pump gas if target is already reached! + return FALSE + + //Calculate necessary moles to transfer using PV=nRT + if((total_moles() > 0) && (temperature>0)) + var/pressure_delta = target_pressure - output_starting_pressure + var/transfer_moles = pressure_delta*output_air.volume/(temperature * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + var/datum/gas_mixture/removed = remove(transfer_moles) + output_air.merge(removed) + return TRUE + return FALSE + +/// Releases gas from src to output air. This means that it can not transfer air to gas mixture with higher pressure. +/datum/gas_mixture/proc/release_gas_to(datum/gas_mixture/output_air, target_pressure) + var/output_starting_pressure = output_air.return_pressure() + var/input_starting_pressure = return_pressure() + + if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10)) + //No need to pump gas if target is already reached or input pressure is too low + //Need at least 10 KPa difference to overcome friction in the mechanism + return FALSE + + //Calculate necessary moles to transfer using PV = nRT + if((total_moles() > 0) && (temperature>0)) + var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2) + //Can not have a pressure delta that would cause output_pressure > input_pressure + + var/transfer_moles = pressure_delta*output_air.volume/(temperature * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + var/datum/gas_mixture/removed = remove(transfer_moles) + output_air.merge(removed) + + return TRUE + return FALSE diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index d775e10bfc4..a4df94dfb1b 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -1,338 +1,338 @@ -// Quick overview: -// -// Pipes combine to form pipelines -// Pipelines and other atmospheric objects combine to form pipe_networks -// Note: A single pipe_network represents a completely open space -// -// Pipes -> Pipelines -// Pipelines + Other Objects -> Pipe network - -#define PIPE_VISIBLE_LEVEL 2 -#define PIPE_HIDDEN_LEVEL 1 - -/obj/machinery/atmospherics - anchored = TRUE - move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. - idle_power_usage = 0 - active_power_usage = 0 - power_channel = AREA_USAGE_ENVIRON - layer = GAS_PIPE_HIDDEN_LAYER //under wires - resistance_flags = FIRE_PROOF - max_integrity = 200 - obj_flags = CAN_BE_HIT | ON_BLUEPRINTS - var/can_unwrench = 0 - var/initialize_directions = 0 - var/pipe_color - var/piping_layer = PIPING_LAYER_DEFAULT - var/pipe_flags = NONE - - ///This only works on pipes, because they have 1000 subtypes wich need to be visible and invisible under tiles, so we track this here - var/hide = TRUE - - var/static/list/iconsetids = list() - var/static/list/pipeimages = list() - - var/image/pipe_vision_img = null - - var/device_type = 0 - var/list/obj/machinery/atmospherics/nodes - - var/construction_type - var/pipe_state //icon_state as a pipe item - var/on = FALSE - -/obj/machinery/atmospherics/examine(mob/user) - . = ..() - if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) - var/mob/living/L = user - if(L.ventcrawler) - . += "Alt-click to crawl through it." - -/obj/machinery/atmospherics/New(loc, process = TRUE, setdir) - if(!isnull(setdir)) - setDir(setdir) - if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE) - normalize_cardinal_directions() - nodes = new(device_type) - if (!armor) - armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) - ..() - if(process) - SSair.atmos_machinery += src - SetInitDirections() - -/obj/machinery/atmospherics/Destroy() - for(var/i in 1 to device_type) - nullifyNode(i) - - SSair.atmos_machinery -= src - SSair.pipenets_needing_rebuilt -= src - if(SSair.currentpart == SSAIR_ATMOSMACHINERY) - SSair.currentrun -= src - - dropContents() - if(pipe_vision_img) - qdel(pipe_vision_img) - - return ..() - //return QDEL_HINT_FINDREFERENCE - -/obj/machinery/atmospherics/proc/destroy_network() - return - -/obj/machinery/atmospherics/proc/build_network() - // Called to build a network from this node - return - -/obj/machinery/atmospherics/proc/nullifyNode(i) - if(nodes[i]) - var/obj/machinery/atmospherics/N = nodes[i] - N.disconnect(src) - nodes[i] = null - -/obj/machinery/atmospherics/proc/getNodeConnects() - var/list/node_connects = list() - node_connects.len = device_type - - for(var/i in 1 to device_type) - for(var/D in GLOB.cardinals) - if(D & GetInitDirections()) - if(D in node_connects) - continue - node_connects[i] = D - break - return node_connects - -/obj/machinery/atmospherics/proc/normalize_cardinal_directions() - switch(dir) - if(SOUTH) - setDir(NORTH) - if(WEST) - setDir(EAST) - -//this is called just after the air controller sets up turfs -/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) - if(!node_connects) //for pipes where order of nodes doesn't matter - node_connects = getNodeConnects() - - for(var/i in 1 to device_type) - for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) - if(can_be_node(target, i)) - nodes[i] = target - break - update_icon() - -/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) - piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer - update_icon() - -/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) - return connection_check(target, piping_layer) - -//Find a connecting /obj/machinery/atmospherics in specified direction -/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) - for(var/obj/machinery/atmospherics/target in get_step(src, direction)) - if(target.initialize_directions & get_dir(target,src)) - if(connection_check(target, prompted_layer)) - return target - -/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) - if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) - if(isnull(given_layer)) - given_layer = piping_layer - if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/pipeline_expansion() - return nodes - -/obj/machinery/atmospherics/proc/SetInitDirections() - return - -/obj/machinery/atmospherics/proc/GetInitDirections() - return initialize_directions - -/obj/machinery/atmospherics/proc/returnPipenet() - return - -/obj/machinery/atmospherics/proc/returnPipenetAir() - return - -/obj/machinery/atmospherics/proc/setPipenet() - return - -/obj/machinery/atmospherics/proc/replacePipenet() - return - -/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) - if(istype(reference, /obj/machinery/atmospherics/pipe)) - var/obj/machinery/atmospherics/pipe/P = reference - P.destroy_network() - nodes[nodes.Find(reference)] = null - update_icon() - -/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pipe)) //lets you autodrop - var/obj/item/pipe/pipe = W - if(user.dropItemToGround(pipe)) - pipe.setPipingLayer(piping_layer) //align it with us - return TRUE - else - return ..() - -/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) - if(!can_unwrench(user)) - return ..() - - var/datum/gas_mixture/int_air = return_air() - var/datum/gas_mixture/env_air = loc.return_air() - add_fingerprint(user) - - var/unsafe_wrenching = FALSE - var/internal_pressure = int_air.return_pressure()-env_air.return_pressure() - - to_chat(user, "You begin to unfasten \the [src]...") - - if (internal_pressure > 2*ONE_ATMOSPHERE) - to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?") - unsafe_wrenching = TRUE //Oh dear oh dear - - if(I.use_tool(src, user, 20, volume=50)) - user.visible_message( \ - "[user] unfastens \the [src].", \ - "You unfasten \the [src].", \ - "You hear ratchet.") - investigate_log("was REMOVED by [key_name(usr)]", INVESTIGATE_ATMOS) - - //You unwrenched a pipe full of pressure? Let's splat you into the wall, silly. - if(unsafe_wrenching) - unsafe_pressure_release(user, internal_pressure) - deconstruct(TRUE) - return TRUE - -/obj/machinery/atmospherics/proc/can_unwrench(mob/user) - return can_unwrench - -// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure. -/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) - if(!user) - return - if(!pressures) - var/datum/gas_mixture/int_air = return_air() - var/datum/gas_mixture/env_air = loc.return_air() - pressures = int_air.return_pressure() - env_air.return_pressure() - - user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") - - // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir - // otherwise, edge_target_turf uses a random cardinal direction - // range is pressures / 250 - // speed is pressures / 1250 - user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) - -/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(can_unwrench) - var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) - stored.setPipingLayer(piping_layer) - if(!disassembled) - stored.obj_integrity = stored.max_integrity * 0.5 - transfer_fingerprints_to(stored) - ..() - -/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) - - //Add identifiers for the iconset - if(iconsetids[iconset] == null) - iconsetids[iconset] = num2text(iconsetids.len + 1) - - //Generate a unique identifier for this image combination - var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" - - if((!(. = pipeimages[identifier]))) - var/image/pipe_overlay - pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) - pipe_overlay.color = col - PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) - -/obj/machinery/atmospherics/on_construction(obj_color, set_layer) - if(can_unwrench) - add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) - pipe_color = obj_color - setPipingLayer(set_layer) - atmosinit() - var/list/nodes = pipeline_expansion() - for(var/obj/machinery/atmospherics/A in nodes) - A.atmosinit() - A.addMember(src) - build_network() - -/obj/machinery/atmospherics/Entered(atom/movable/AM) - if(istype(AM, /mob/living)) - var/mob/living/L = AM - L.ventcrawl_layer = piping_layer - return ..() - -/obj/machinery/atmospherics/singularity_pull(S, current_size) - if(current_size >= STAGE_FIVE) - deconstruct(FALSE) - return ..() - -#define VENT_SOUND_DELAY 30 - -/obj/machinery/atmospherics/relaymove(mob/living/user, direction) - direction &= initialize_directions - if(!direction || !(direction in GLOB.cardinals)) //cant go this way. - return - - if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug - return - - var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer) - if(target_move) - if(target_move.can_crawl_through()) - if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery)) - user.forceMove(target_move.loc) //handle entering and so on. - user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") - else - var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() - if(pipenetdiff.len) - user.update_pipe_vision(target_move) - user.forceMove(target_move) - user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement - if(world.time - user.last_played_vent > VENT_SOUND_DELAY) - user.last_played_vent = world.time - playsound(src, 'sound/machines/ventcrawl.ogg', 50, TRUE, -3) - else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent - user.forceMove(loc) - user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") - - //PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER? - -/obj/machinery/atmospherics/AltClick(mob/living/L) - if(istype(L) && is_type_in_list(src, GLOB.ventcrawl_machinery)) - L.handle_ventcrawl(src) - return - ..() - - -/obj/machinery/atmospherics/proc/can_crawl_through() - return TRUE - -/obj/machinery/atmospherics/proc/returnPipenets() - return list() - -/obj/machinery/atmospherics/update_remote_sight(mob/user) - user.sight |= (SEE_TURFS|BLIND) - -//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. -/obj/machinery/atmospherics/proc/can_see_pipes() - return TRUE - -/obj/machinery/atmospherics/proc/update_layer() - layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE +// Quick overview: +// +// Pipes combine to form pipelines +// Pipelines and other atmospheric objects combine to form pipe_networks +// Note: A single pipe_network represents a completely open space +// +// Pipes -> Pipelines +// Pipelines + Other Objects -> Pipe network + +#define PIPE_VISIBLE_LEVEL 2 +#define PIPE_HIDDEN_LEVEL 1 + +/obj/machinery/atmospherics + anchored = TRUE + move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. + idle_power_usage = 0 + active_power_usage = 0 + power_channel = AREA_USAGE_ENVIRON + layer = GAS_PIPE_HIDDEN_LAYER //under wires + resistance_flags = FIRE_PROOF + max_integrity = 200 + obj_flags = CAN_BE_HIT | ON_BLUEPRINTS + var/can_unwrench = 0 + var/initialize_directions = 0 + var/pipe_color + var/piping_layer = PIPING_LAYER_DEFAULT + var/pipe_flags = NONE + + ///This only works on pipes, because they have 1000 subtypes wich need to be visible and invisible under tiles, so we track this here + var/hide = TRUE + + var/static/list/iconsetids = list() + var/static/list/pipeimages = list() + + var/image/pipe_vision_img = null + + var/device_type = 0 + var/list/obj/machinery/atmospherics/nodes + + var/construction_type + var/pipe_state //icon_state as a pipe item + var/on = FALSE + +/obj/machinery/atmospherics/examine(mob/user) + . = ..() + if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) + var/mob/living/L = user + if(L.ventcrawler) + . += "Alt-click to crawl through it." + +/obj/machinery/atmospherics/New(loc, process = TRUE, setdir) + if(!isnull(setdir)) + setDir(setdir) + if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE) + normalize_cardinal_directions() + nodes = new(device_type) + if (!armor) + armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) + ..() + if(process) + SSair.atmos_machinery += src + SetInitDirections() + +/obj/machinery/atmospherics/Destroy() + for(var/i in 1 to device_type) + nullifyNode(i) + + SSair.atmos_machinery -= src + SSair.pipenets_needing_rebuilt -= src + if(SSair.currentpart == SSAIR_ATMOSMACHINERY) + SSair.currentrun -= src + + dropContents() + if(pipe_vision_img) + qdel(pipe_vision_img) + + return ..() + //return QDEL_HINT_FINDREFERENCE + +/obj/machinery/atmospherics/proc/destroy_network() + return + +/obj/machinery/atmospherics/proc/build_network() + // Called to build a network from this node + return + +/obj/machinery/atmospherics/proc/nullifyNode(i) + if(nodes[i]) + var/obj/machinery/atmospherics/N = nodes[i] + N.disconnect(src) + nodes[i] = null + +/obj/machinery/atmospherics/proc/getNodeConnects() + var/list/node_connects = list() + node_connects.len = device_type + + for(var/i in 1 to device_type) + for(var/D in GLOB.cardinals) + if(D & GetInitDirections()) + if(D in node_connects) + continue + node_connects[i] = D + break + return node_connects + +/obj/machinery/atmospherics/proc/normalize_cardinal_directions() + switch(dir) + if(SOUTH) + setDir(NORTH) + if(WEST) + setDir(EAST) + +//this is called just after the air controller sets up turfs +/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) + if(!node_connects) //for pipes where order of nodes doesn't matter + node_connects = getNodeConnects() + + for(var/i in 1 to device_type) + for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) + if(can_be_node(target, i)) + nodes[i] = target + break + update_icon() + +/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) + piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer + update_icon() + +/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) + return connection_check(target, piping_layer) + +//Find a connecting /obj/machinery/atmospherics in specified direction +/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) + for(var/obj/machinery/atmospherics/target in get_step(src, direction)) + if(target.initialize_directions & get_dir(target,src)) + if(connection_check(target, prompted_layer)) + return target + +/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) + if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) + if(isnull(given_layer)) + given_layer = piping_layer + if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/pipeline_expansion() + return nodes + +/obj/machinery/atmospherics/proc/SetInitDirections() + return + +/obj/machinery/atmospherics/proc/GetInitDirections() + return initialize_directions + +/obj/machinery/atmospherics/proc/returnPipenet() + return + +/obj/machinery/atmospherics/proc/returnPipenetAir() + return + +/obj/machinery/atmospherics/proc/setPipenet() + return + +/obj/machinery/atmospherics/proc/replacePipenet() + return + +/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) + if(istype(reference, /obj/machinery/atmospherics/pipe)) + var/obj/machinery/atmospherics/pipe/P = reference + P.destroy_network() + nodes[nodes.Find(reference)] = null + update_icon() + +/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pipe)) //lets you autodrop + var/obj/item/pipe/pipe = W + if(user.dropItemToGround(pipe)) + pipe.setPipingLayer(piping_layer) //align it with us + return TRUE + else + return ..() + +/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) + if(!can_unwrench(user)) + return ..() + + var/datum/gas_mixture/int_air = return_air() + var/datum/gas_mixture/env_air = loc.return_air() + add_fingerprint(user) + + var/unsafe_wrenching = FALSE + var/internal_pressure = int_air.return_pressure()-env_air.return_pressure() + + to_chat(user, "You begin to unfasten \the [src]...") + + if (internal_pressure > 2*ONE_ATMOSPHERE) + to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?") + unsafe_wrenching = TRUE //Oh dear oh dear + + if(I.use_tool(src, user, 20, volume=50)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "You unfasten \the [src].", \ + "You hear ratchet.") + investigate_log("was REMOVED by [key_name(usr)]", INVESTIGATE_ATMOS) + + //You unwrenched a pipe full of pressure? Let's splat you into the wall, silly. + if(unsafe_wrenching) + unsafe_pressure_release(user, internal_pressure) + deconstruct(TRUE) + return TRUE + +/obj/machinery/atmospherics/proc/can_unwrench(mob/user) + return can_unwrench + +// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure. +/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) + if(!user) + return + if(!pressures) + var/datum/gas_mixture/int_air = return_air() + var/datum/gas_mixture/env_air = loc.return_air() + pressures = int_air.return_pressure() - env_air.return_pressure() + + user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") + + // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir + // otherwise, edge_target_turf uses a random cardinal direction + // range is pressures / 250 + // speed is pressures / 1250 + user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) + +/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(can_unwrench) + var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) + stored.setPipingLayer(piping_layer) + if(!disassembled) + stored.obj_integrity = stored.max_integrity * 0.5 + transfer_fingerprints_to(stored) + ..() + +/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) + + //Add identifiers for the iconset + if(iconsetids[iconset] == null) + iconsetids[iconset] = num2text(iconsetids.len + 1) + + //Generate a unique identifier for this image combination + var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" + + if((!(. = pipeimages[identifier]))) + var/image/pipe_overlay + pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) + pipe_overlay.color = col + PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) + +/obj/machinery/atmospherics/on_construction(obj_color, set_layer) + if(can_unwrench) + add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) + pipe_color = obj_color + setPipingLayer(set_layer) + atmosinit() + var/list/nodes = pipeline_expansion() + for(var/obj/machinery/atmospherics/A in nodes) + A.atmosinit() + A.addMember(src) + build_network() + +/obj/machinery/atmospherics/Entered(atom/movable/AM) + if(istype(AM, /mob/living)) + var/mob/living/L = AM + L.ventcrawl_layer = piping_layer + return ..() + +/obj/machinery/atmospherics/singularity_pull(S, current_size) + if(current_size >= STAGE_FIVE) + deconstruct(FALSE) + return ..() + +#define VENT_SOUND_DELAY 30 + +/obj/machinery/atmospherics/relaymove(mob/living/user, direction) + direction &= initialize_directions + if(!direction || !(direction in GLOB.cardinals)) //cant go this way. + return + + if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug + return + + var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer) + if(target_move) + if(target_move.can_crawl_through()) + if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery)) + user.forceMove(target_move.loc) //handle entering and so on. + user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") + else + var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() + if(pipenetdiff.len) + user.update_pipe_vision(target_move) + user.forceMove(target_move) + user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement + if(world.time - user.last_played_vent > VENT_SOUND_DELAY) + user.last_played_vent = world.time + playsound(src, 'sound/machines/ventcrawl.ogg', 50, TRUE, -3) + else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent + user.forceMove(loc) + user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") + + //PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER? + +/obj/machinery/atmospherics/AltClick(mob/living/L) + if(istype(L) && is_type_in_list(src, GLOB.ventcrawl_machinery)) + L.handle_ventcrawl(src) + return + ..() + + +/obj/machinery/atmospherics/proc/can_crawl_through() + return TRUE + +/obj/machinery/atmospherics/proc/returnPipenets() + return list() + +/obj/machinery/atmospherics/update_remote_sight(mob/user) + user.sight |= (SEE_TURFS|BLIND) + +//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. +/obj/machinery/atmospherics/proc/can_see_pipes() + return TRUE + +/obj/machinery/atmospherics/proc/update_layer() + layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE diff --git a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm index c4b14274e03..fe50cfdce73 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm @@ -1,17 +1,17 @@ -/obj/machinery/atmospherics/components/binary - icon = 'icons/obj/atmospherics/components/binary_devices.dmi' - dir = SOUTH - initialize_directions = SOUTH|NORTH - use_power = IDLE_POWER_USE - device_type = BINARY - layer = GAS_PUMP_LAYER - -/obj/machinery/atmospherics/components/binary/SetInitDirections() - switch(dir) - if(NORTH, SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST, WEST) - initialize_directions = EAST|WEST - -/obj/machinery/atmospherics/components/binary/getNodeConnects() - return list(turn(dir, 180), dir) +/obj/machinery/atmospherics/components/binary + icon = 'icons/obj/atmospherics/components/binary_devices.dmi' + dir = SOUTH + initialize_directions = SOUTH|NORTH + use_power = IDLE_POWER_USE + device_type = BINARY + layer = GAS_PUMP_LAYER + +/obj/machinery/atmospherics/components/binary/SetInitDirections() + switch(dir) + if(NORTH, SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST, WEST) + initialize_directions = EAST|WEST + +/obj/machinery/atmospherics/components/binary/getNodeConnects() + return list(turn(dir, 180), dir) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm index b9c864a4b05..c164dc5ae30 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm @@ -1,190 +1,190 @@ -//node2, air2, network2 correspond to input -//node1, air1, network1 correspond to output -#define CIRCULATOR_HOT 0 -#define CIRCULATOR_COLD 1 - -/obj/machinery/atmospherics/components/binary/circulator - name = "circulator/heat exchanger" - desc = "A gas circulator pump and heat exchanger." - icon_state = "circ-off-0" - - var/active = FALSE - - var/last_pressure_delta = 0 - pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY - - density = TRUE - - - var/flipped = 0 - var/mode = CIRCULATOR_HOT - var/obj/machinery/power/generator/generator - -//default cold circ for mappers -/obj/machinery/atmospherics/components/binary/circulator/cold - mode = CIRCULATOR_COLD - -/obj/machinery/atmospherics/components/binary/circulator/Initialize(mapload) - .=..() - component_parts = list(new /obj/item/circuitboard/machine/circulator) - -/obj/machinery/atmospherics/components/binary/circulator/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) - -/obj/machinery/atmospherics/components/binary/circulator/Destroy() - if(generator) - disconnectFromGenerator() - return ..() - -/obj/machinery/atmospherics/components/binary/circulator/proc/return_transfer_air() - - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - - var/output_starting_pressure = air1.return_pressure() - var/input_starting_pressure = air2.return_pressure() - - if(output_starting_pressure >= input_starting_pressure-10) - //Need at least 10 KPa difference to overcome friction in the mechanism - last_pressure_delta = 0 - return null - - //Calculate necessary moles to transfer using PV = nRT - if(air2.temperature>0) - var/pressure_delta = (input_starting_pressure - output_starting_pressure)/2 - - var/transfer_moles = pressure_delta*air1.volume/(air2.temperature * R_IDEAL_GAS_EQUATION) - - last_pressure_delta = pressure_delta - - //Actually transfer the gas - var/datum/gas_mixture/removed = air2.remove(transfer_moles) - - update_parents() - - return removed - - else - last_pressure_delta = 0 - -/obj/machinery/atmospherics/components/binary/circulator/process_atmos() - ..() - update_icon() - -/obj/machinery/atmospherics/components/binary/circulator/update_icon() - if(!is_operational()) - icon_state = "circ-p-[flipped]" - else if(last_pressure_delta > 0) - if(last_pressure_delta > ONE_ATMOSPHERE) - icon_state = "circ-run-[flipped]" - else - icon_state = "circ-slow-[flipped]" - else - icon_state = "circ-off-[flipped]" - -/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I) - if(!panel_open) - return - anchored = !anchored - I.play_tool_sound(src) - if(generator) - disconnectFromGenerator() - to_chat(user, "You [anchored?"secure":"unsecure"] [src].") - - - var/obj/machinery/atmospherics/node1 = nodes[1] - var/obj/machinery/atmospherics/node2 = nodes[2] - - if(node1) - node1.disconnect(src) - nodes[1] = null - nullifyPipenet(parents[1]) - if(node2) - node2.disconnect(src) - nodes[2] = null - nullifyPipenet(parents[2]) - - if(anchored) - SetInitDirections() - atmosinit() - node1 = nodes[1] - if(node1) - node1.atmosinit() - node1.addMember(src) - node2 = nodes[2] - if(node2) - node2.atmosinit() - node2.addMember(src) - SSair.add_to_rebuild_queue(src) - - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/SetInitDirections() - switch(dir) - if(NORTH, SOUTH) - initialize_directions = EAST|WEST - if(EAST, WEST) - initialize_directions = NORTH|SOUTH - -/obj/machinery/atmospherics/components/binary/circulator/getNodeConnects() - if(flipped) - return list(turn(dir, 270), turn(dir, 90)) - return list(turn(dir, 90), turn(dir, 270)) - -/obj/machinery/atmospherics/components/binary/circulator/can_be_node(obj/machinery/atmospherics/target) - if(anchored) - return ..(target) - return FALSE - -/obj/machinery/atmospherics/components/binary/circulator/multitool_act(mob/living/user, obj/item/I) - if(generator) - disconnectFromGenerator() - mode = !mode - to_chat(user, "You set [src] to [mode?"cold":"hot"] mode.") - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/screwdriver_act(mob/user, obj/item/I) - if(..()) - return TRUE - panel_open = !panel_open - I.play_tool_sound(src) - to_chat(user, "You [panel_open?"open":"close"] the panel on [src].") - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/crowbar_act(mob/user, obj/item/I) - default_deconstruction_crowbar(I) - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/on_deconstruction() - if(generator) - disconnectFromGenerator() - -/obj/machinery/atmospherics/components/binary/circulator/proc/disconnectFromGenerator() - if(mode) - generator.cold_circ = null - else - generator.hot_circ = null - generator.update_icon() - generator = null - -/obj/machinery/atmospherics/components/binary/circulator/setPipingLayer(new_layer) - ..() - pixel_x = 0 - pixel_y = 0 - -/obj/machinery/atmospherics/components/binary/circulator/verb/circulator_flip() - set name = "Flip" - set category = "Object" - set src in oview(1) - - if(!ishuman(usr)) - return - - if(anchored) - to_chat(usr, "[src] is anchored!") - return - - flipped = !flipped - to_chat(usr, "You flip [src].") - update_icon() +//node2, air2, network2 correspond to input +//node1, air1, network1 correspond to output +#define CIRCULATOR_HOT 0 +#define CIRCULATOR_COLD 1 + +/obj/machinery/atmospherics/components/binary/circulator + name = "circulator/heat exchanger" + desc = "A gas circulator pump and heat exchanger." + icon_state = "circ-off-0" + + var/active = FALSE + + var/last_pressure_delta = 0 + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + + density = TRUE + + + var/flipped = 0 + var/mode = CIRCULATOR_HOT + var/obj/machinery/power/generator/generator + +//default cold circ for mappers +/obj/machinery/atmospherics/components/binary/circulator/cold + mode = CIRCULATOR_COLD + +/obj/machinery/atmospherics/components/binary/circulator/Initialize(mapload) + .=..() + component_parts = list(new /obj/item/circuitboard/machine/circulator) + +/obj/machinery/atmospherics/components/binary/circulator/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) + +/obj/machinery/atmospherics/components/binary/circulator/Destroy() + if(generator) + disconnectFromGenerator() + return ..() + +/obj/machinery/atmospherics/components/binary/circulator/proc/return_transfer_air() + + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + + var/output_starting_pressure = air1.return_pressure() + var/input_starting_pressure = air2.return_pressure() + + if(output_starting_pressure >= input_starting_pressure-10) + //Need at least 10 KPa difference to overcome friction in the mechanism + last_pressure_delta = 0 + return null + + //Calculate necessary moles to transfer using PV = nRT + if(air2.temperature>0) + var/pressure_delta = (input_starting_pressure - output_starting_pressure)/2 + + var/transfer_moles = pressure_delta*air1.volume/(air2.temperature * R_IDEAL_GAS_EQUATION) + + last_pressure_delta = pressure_delta + + //Actually transfer the gas + var/datum/gas_mixture/removed = air2.remove(transfer_moles) + + update_parents() + + return removed + + else + last_pressure_delta = 0 + +/obj/machinery/atmospherics/components/binary/circulator/process_atmos() + ..() + update_icon() + +/obj/machinery/atmospherics/components/binary/circulator/update_icon() + if(!is_operational()) + icon_state = "circ-p-[flipped]" + else if(last_pressure_delta > 0) + if(last_pressure_delta > ONE_ATMOSPHERE) + icon_state = "circ-run-[flipped]" + else + icon_state = "circ-slow-[flipped]" + else + icon_state = "circ-off-[flipped]" + +/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I) + if(!panel_open) + return + anchored = !anchored + I.play_tool_sound(src) + if(generator) + disconnectFromGenerator() + to_chat(user, "You [anchored?"secure":"unsecure"] [src].") + + + var/obj/machinery/atmospherics/node1 = nodes[1] + var/obj/machinery/atmospherics/node2 = nodes[2] + + if(node1) + node1.disconnect(src) + nodes[1] = null + nullifyPipenet(parents[1]) + if(node2) + node2.disconnect(src) + nodes[2] = null + nullifyPipenet(parents[2]) + + if(anchored) + SetInitDirections() + atmosinit() + node1 = nodes[1] + if(node1) + node1.atmosinit() + node1.addMember(src) + node2 = nodes[2] + if(node2) + node2.atmosinit() + node2.addMember(src) + SSair.add_to_rebuild_queue(src) + + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/SetInitDirections() + switch(dir) + if(NORTH, SOUTH) + initialize_directions = EAST|WEST + if(EAST, WEST) + initialize_directions = NORTH|SOUTH + +/obj/machinery/atmospherics/components/binary/circulator/getNodeConnects() + if(flipped) + return list(turn(dir, 270), turn(dir, 90)) + return list(turn(dir, 90), turn(dir, 270)) + +/obj/machinery/atmospherics/components/binary/circulator/can_be_node(obj/machinery/atmospherics/target) + if(anchored) + return ..(target) + return FALSE + +/obj/machinery/atmospherics/components/binary/circulator/multitool_act(mob/living/user, obj/item/I) + if(generator) + disconnectFromGenerator() + mode = !mode + to_chat(user, "You set [src] to [mode?"cold":"hot"] mode.") + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/screwdriver_act(mob/user, obj/item/I) + if(..()) + return TRUE + panel_open = !panel_open + I.play_tool_sound(src) + to_chat(user, "You [panel_open?"open":"close"] the panel on [src].") + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/crowbar_act(mob/user, obj/item/I) + default_deconstruction_crowbar(I) + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/on_deconstruction() + if(generator) + disconnectFromGenerator() + +/obj/machinery/atmospherics/components/binary/circulator/proc/disconnectFromGenerator() + if(mode) + generator.cold_circ = null + else + generator.hot_circ = null + generator.update_icon() + generator = null + +/obj/machinery/atmospherics/components/binary/circulator/setPipingLayer(new_layer) + ..() + pixel_x = 0 + pixel_y = 0 + +/obj/machinery/atmospherics/components/binary/circulator/verb/circulator_flip() + set name = "Flip" + set category = "Object" + set src in oview(1) + + if(!ishuman(usr)) + return + + if(anchored) + to_chat(usr, "[src] is anchored!") + return + + flipped = !flipped + to_chat(usr, "You flip [src].") + update_icon() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm index 1310b39b16a..d3c5959e2c2 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm @@ -1,242 +1,242 @@ -//Acts like a normal vent, but has an input AND output. - -#define EXT_BOUND 1 -#define INPUT_MIN 2 -#define OUTPUT_MAX 4 - -/obj/machinery/atmospherics/components/binary/dp_vent_pump - icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons! - icon_state = "dpvent_map-2" - - //node2 is output port - //node1 is input port - - name = "dual-port air vent" - desc = "Has a valve and pump attached to it. There are two ports." - - hide = TRUE - - var/frequency = 0 - var/id = null - var/datum/radio_frequency/radio_connection - - var/pump_direction = 1 //0 = siphoning, 1 = releasing - - var/external_pressure_bound = ONE_ATMOSPHERE - var/input_pressure_min = 0 - var/output_pressure_max = 0 - - var/pressure_checks = EXT_BOUND - - //EXT_BOUND: Do not pass external_pressure_bound - //INPUT_MIN: Do not pass input_pressure_min - //OUTPUT_MAX: Do not pass output_pressure_max - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy() - SSradio.remove_object(src, frequency) - return ..() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() - cut_overlays() - if(showpipe) - var/image/cap = getpipeimage(icon, "dpvent_cap", dir, piping_layer = piping_layer) - add_overlay(cap) - - if(!on || !is_operational()) - icon_state = "vent_off" - else - icon_state = pump_direction ? "vent_out" : "vent_in" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos() - ..() - - if(!on) - return - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - - var/datum/gas_mixture/environment = loc.return_air() - var/environment_pressure = environment.return_pressure() - - if(pump_direction) //input -> external - var/pressure_delta = 10000 - - if(pressure_checks&EXT_BOUND) - pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure)) - if(pressure_checks&INPUT_MIN) - pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min)) - - if(pressure_delta > 0) - if(air1.temperature > 0) - var/transfer_moles = pressure_delta*environment.volume/(air1.temperature * R_IDEAL_GAS_EQUATION) - - var/datum/gas_mixture/removed = air1.remove(transfer_moles) - //Removed can be null if there is no atmosphere in air1 - if(!removed) - return - - loc.assume_air(removed) - air_update_turf() - - var/datum/pipeline/parent1 = parents[1] - parent1.update = 1 - - else //external -> output - var/pressure_delta = 10000 - - if(pressure_checks&EXT_BOUND) - pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound)) - if(pressure_checks&INPUT_MIN) - pressure_delta = min(pressure_delta, (output_pressure_max - air2.return_pressure())) - - if(pressure_delta > 0) - if(environment.temperature > 0) - var/transfer_moles = pressure_delta*air2.volume/(environment.temperature * R_IDEAL_GAS_EQUATION) - - var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) - //removed can be null if there is no air in the location - if(!removed) - return - - air2.merge(removed) - air_update_turf() - - var/datum/pipeline/parent2 = parents[2] - parent2.update = 1 - - //Radio remote control - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - if(frequency) - radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA) - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status() - if(!radio_connection) - return - - var/datum/signal/signal = new(list( - "tag" = id, - "device" = "ADVP", - "power" = on, - "direction" = pump_direction?("release"):("siphon"), - "checks" = pressure_checks, - "input" = input_pressure_min, - "output" = output_pressure_max, - "external" = external_pressure_bound, - "sigtype" = "status" - )) - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit() - ..() - if(frequency) - set_frequency(frequency) - broadcast_status() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal) - if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command")) - return - - if("power" in signal.data) - on = text2num(signal.data["power"]) - - if("power_toggle" in signal.data) - on = !on - - if("set_direction" in signal.data) - pump_direction = text2num(signal.data["set_direction"]) - - if("checks" in signal.data) - pressure_checks = text2num(signal.data["checks"]) - - if("purge" in signal.data) - pressure_checks &= ~1 - pump_direction = 0 - - if("stabilize" in signal.data) - pressure_checks |= 1 - pump_direction = 1 - - if("set_input_pressure" in signal.data) - input_pressure_min = clamp(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50) - - if("set_output_pressure" in signal.data) - output_pressure_max = clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50) - - if("set_external_pressure" in signal.data) - external_pressure_bound = clamp(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50) - - addtimer(CALLBACK(src, .proc/broadcast_status), 2) - - if(!("status" in signal.data)) //do not update_icon - update_icon() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume - name = "large dual-port air vent" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New() - ..() - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - air1.volume = 1000 - air2.volume = 1000 - -// Mapping - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1 - piping_layer = 1 - icon_state = "dpvent_map-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 - piping_layer = 3 - icon_state = "dpvent_map-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on - on = TRUE - icon_state = "dpvent_map_on-2" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 - piping_layer = 1 - icon_state = "dpvent_map_on-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 - piping_layer = 3 - icon_state = "dpvent_map_on-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix - id = INCINERATOR_TOXMIX_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos - id = INCINERATOR_ATMOS_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava - id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1 - piping_layer = 1 - icon_state = "dpvent_map-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 - piping_layer = 3 - icon_state = "dpvent_map-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on - on = TRUE - icon_state = "dpvent_map_on-2" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 - piping_layer = 1 - icon_state = "dpvent_map_on-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 - piping_layer = 3 - icon_state = "dpvent_map_on-3" - -#undef EXT_BOUND -#undef INPUT_MIN -#undef OUTPUT_MAX +//Acts like a normal vent, but has an input AND output. + +#define EXT_BOUND 1 +#define INPUT_MIN 2 +#define OUTPUT_MAX 4 + +/obj/machinery/atmospherics/components/binary/dp_vent_pump + icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons! + icon_state = "dpvent_map-2" + + //node2 is output port + //node1 is input port + + name = "dual-port air vent" + desc = "Has a valve and pump attached to it. There are two ports." + + hide = TRUE + + var/frequency = 0 + var/id = null + var/datum/radio_frequency/radio_connection + + var/pump_direction = 1 //0 = siphoning, 1 = releasing + + var/external_pressure_bound = ONE_ATMOSPHERE + var/input_pressure_min = 0 + var/output_pressure_max = 0 + + var/pressure_checks = EXT_BOUND + + //EXT_BOUND: Do not pass external_pressure_bound + //INPUT_MIN: Do not pass input_pressure_min + //OUTPUT_MAX: Do not pass output_pressure_max + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy() + SSradio.remove_object(src, frequency) + return ..() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() + cut_overlays() + if(showpipe) + var/image/cap = getpipeimage(icon, "dpvent_cap", dir, piping_layer = piping_layer) + add_overlay(cap) + + if(!on || !is_operational()) + icon_state = "vent_off" + else + icon_state = pump_direction ? "vent_out" : "vent_in" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos() + ..() + + if(!on) + return + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + + var/datum/gas_mixture/environment = loc.return_air() + var/environment_pressure = environment.return_pressure() + + if(pump_direction) //input -> external + var/pressure_delta = 10000 + + if(pressure_checks&EXT_BOUND) + pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure)) + if(pressure_checks&INPUT_MIN) + pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min)) + + if(pressure_delta > 0) + if(air1.temperature > 0) + var/transfer_moles = pressure_delta*environment.volume/(air1.temperature * R_IDEAL_GAS_EQUATION) + + var/datum/gas_mixture/removed = air1.remove(transfer_moles) + //Removed can be null if there is no atmosphere in air1 + if(!removed) + return + + loc.assume_air(removed) + air_update_turf() + + var/datum/pipeline/parent1 = parents[1] + parent1.update = 1 + + else //external -> output + var/pressure_delta = 10000 + + if(pressure_checks&EXT_BOUND) + pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound)) + if(pressure_checks&INPUT_MIN) + pressure_delta = min(pressure_delta, (output_pressure_max - air2.return_pressure())) + + if(pressure_delta > 0) + if(environment.temperature > 0) + var/transfer_moles = pressure_delta*air2.volume/(environment.temperature * R_IDEAL_GAS_EQUATION) + + var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) + //removed can be null if there is no air in the location + if(!removed) + return + + air2.merge(removed) + air_update_turf() + + var/datum/pipeline/parent2 = parents[2] + parent2.update = 1 + + //Radio remote control + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA) + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status() + if(!radio_connection) + return + + var/datum/signal/signal = new(list( + "tag" = id, + "device" = "ADVP", + "power" = on, + "direction" = pump_direction?("release"):("siphon"), + "checks" = pressure_checks, + "input" = input_pressure_min, + "output" = output_pressure_max, + "external" = external_pressure_bound, + "sigtype" = "status" + )) + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit() + ..() + if(frequency) + set_frequency(frequency) + broadcast_status() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal) + if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command")) + return + + if("power" in signal.data) + on = text2num(signal.data["power"]) + + if("power_toggle" in signal.data) + on = !on + + if("set_direction" in signal.data) + pump_direction = text2num(signal.data["set_direction"]) + + if("checks" in signal.data) + pressure_checks = text2num(signal.data["checks"]) + + if("purge" in signal.data) + pressure_checks &= ~1 + pump_direction = 0 + + if("stabilize" in signal.data) + pressure_checks |= 1 + pump_direction = 1 + + if("set_input_pressure" in signal.data) + input_pressure_min = clamp(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50) + + if("set_output_pressure" in signal.data) + output_pressure_max = clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50) + + if("set_external_pressure" in signal.data) + external_pressure_bound = clamp(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50) + + addtimer(CALLBACK(src, .proc/broadcast_status), 2) + + if(!("status" in signal.data)) //do not update_icon + update_icon() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume + name = "large dual-port air vent" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New() + ..() + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + air1.volume = 1000 + air2.volume = 1000 + +// Mapping + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1 + piping_layer = 1 + icon_state = "dpvent_map-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 + piping_layer = 3 + icon_state = "dpvent_map-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on + on = TRUE + icon_state = "dpvent_map_on-2" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 + piping_layer = 1 + icon_state = "dpvent_map_on-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 + piping_layer = 3 + icon_state = "dpvent_map_on-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix + id = INCINERATOR_TOXMIX_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos + id = INCINERATOR_ATMOS_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava + id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1 + piping_layer = 1 + icon_state = "dpvent_map-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 + piping_layer = 3 + icon_state = "dpvent_map-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on + on = TRUE + icon_state = "dpvent_map_on-2" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 + piping_layer = 1 + icon_state = "dpvent_map_on-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 + piping_layer = 3 + icon_state = "dpvent_map_on-3" + +#undef EXT_BOUND +#undef INPUT_MIN +#undef OUTPUT_MAX diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm b/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm index 8ba105fdb12..b448c212567 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm @@ -1,48 +1,48 @@ -/obj/machinery/atmospherics/components/trinary - icon = 'icons/obj/atmospherics/components/trinary_devices.dmi' - dir = SOUTH - initialize_directions = SOUTH|NORTH|WEST - use_power = IDLE_POWER_USE - device_type = TRINARY - layer = GAS_FILTER_LAYER - pipe_flags = PIPING_ONE_PER_TURF - - var/flipped = FALSE - -/obj/machinery/atmospherics/components/trinary/SetInitDirections() - switch(dir) - if(NORTH) - initialize_directions = EAST|NORTH|SOUTH - if(SOUTH) - initialize_directions = SOUTH|WEST|NORTH - if(EAST) - initialize_directions = EAST|WEST|SOUTH - if(WEST) - initialize_directions = WEST|NORTH|EAST - -/* -Housekeeping and pipe network stuff -*/ - -/obj/machinery/atmospherics/components/trinary/getNodeConnects() - - //Mixer: - //1 and 2 is input - //Node 3 is output - //If we flip the mixer, 1 and 3 shall exchange positions - - //Filter: - //Node 1 is input - //Node 2 is filtered output - //Node 3 is rest output - //If we flip the filter, 1 and 3 shall exchange positions - - var/node1_connect = turn(dir, -180) - var/node2_connect = turn(dir, -90) - var/node3_connect = dir - - if(flipped) - node1_connect = turn(node1_connect, 180) - node3_connect = turn(node3_connect, 180) - - return list(node1_connect, node2_connect, node3_connect) +/obj/machinery/atmospherics/components/trinary + icon = 'icons/obj/atmospherics/components/trinary_devices.dmi' + dir = SOUTH + initialize_directions = SOUTH|NORTH|WEST + use_power = IDLE_POWER_USE + device_type = TRINARY + layer = GAS_FILTER_LAYER + pipe_flags = PIPING_ONE_PER_TURF + + var/flipped = FALSE + +/obj/machinery/atmospherics/components/trinary/SetInitDirections() + switch(dir) + if(NORTH) + initialize_directions = EAST|NORTH|SOUTH + if(SOUTH) + initialize_directions = SOUTH|WEST|NORTH + if(EAST) + initialize_directions = EAST|WEST|SOUTH + if(WEST) + initialize_directions = WEST|NORTH|EAST + +/* +Housekeeping and pipe network stuff +*/ + +/obj/machinery/atmospherics/components/trinary/getNodeConnects() + + //Mixer: + //1 and 2 is input + //Node 3 is output + //If we flip the mixer, 1 and 3 shall exchange positions + + //Filter: + //Node 1 is input + //Node 2 is filtered output + //Node 3 is rest output + //If we flip the filter, 1 and 3 shall exchange positions + + var/node1_connect = turn(dir, -180) + var/node2_connect = turn(dir, -90) + var/node3_connect = dir + + if(flipped) + node1_connect = turn(node1_connect, 180) + node3_connect = turn(node3_connect, 180) + + return list(node1_connect, node2_connect, node3_connect) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 0c53932a16a..81bebc8c6ff 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -1,504 +1,504 @@ -#define CRYOMOBS 'icons/obj/cryo_mobs.dmi' -///Max temperature allowed inside the cryotube, should break before reaching this heat -#define MAX_TEMPERATURE 4000 - -/obj/machinery/atmospherics/components/unary/cryo_cell - name = "cryo cell" - icon = 'icons/obj/cryogenics.dmi' - icon_state = "pod-off" - density = TRUE - max_integrity = 350 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) - layer = ABOVE_WINDOW_LAYER - state_open = FALSE - circuit = /obj/item/circuitboard/machine/cryo_tube - ui_x = 400 - ui_y = 550 - pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY - occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) - - var/autoeject = TRUE - var/volume = 100 - - var/efficiency = 1 - var/sleep_factor = 0.00125 - var/unconscious_factor = 0.001 - var/heat_capacity = 20000 - var/conduction_coefficient = 0.3 - - var/obj/item/reagent_containers/glass/beaker = null - var/reagent_transfer = 0 - - var/obj/item/radio/radio - var/radio_key = /obj/item/encryptionkey/headset_med - var/radio_channel = RADIO_CHANNEL_MEDICAL - - var/running_anim = FALSE - - var/escape_in_progress = FALSE - var/message_cooldown - var/breakout_time = 300 - ///Cryo will continue to treat people with 0 damage but existing wounds, but will sound off when damage healing is done in case doctors want to directly treat the wounds instead - var/treating_wounds = FALSE - fair_market_price = 10 - payment_department = ACCOUNT_MED - - -/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() - . = ..() - initialize_directions = dir - - radio = new(src) - radio.keyslot = new radio_key - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() - -/obj/machinery/atmospherics/components/unary/cryo_cell/Exited(atom/movable/AM, atom/newloc) - var/oldoccupant = occupant - . = ..() // Parent proc takes care of removing occupant if necessary - if (AM == oldoccupant) - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() - ..(dir, dir) - -/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() - var/C - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - C += M.rating - - efficiency = initial(efficiency) * C - sleep_factor = initial(sleep_factor) * C - unconscious_factor = initial(unconscious_factor) * C - heat_capacity = initial(heat_capacity) / C - conduction_coefficient = initial(conduction_coefficient) * C - -/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results" - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Efficiency at [efficiency*100]%." - -/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() - QDEL_NULL(radio) - QDEL_NULL(beaker) - ///Take the turf the cryotube is on - var/turf/T = get_turf(src) - if(T) - ///Take the air composition of the turf - var/datum/gas_mixture/env = T.return_air() - ///Take the air composition inside the cryotube - var/datum/gas_mixture/air1 = airs[1] - env.merge(air1) - T.air_update_turf() - - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) - ..() - if(beaker) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += beaker - if(EXPLODE_HEAVY) - SSexplosions.medobj += beaker - if(EXPLODE_LIGHT) - SSexplosions.lowobj += beaker - -/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A) - ..() - if(A == beaker) - beaker = null - updateUsrDialog() - -/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction() - if(beaker) - beaker.forceMove(drop_location()) - beaker = null - -/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon() - - cut_overlays() - - if(panel_open) - add_overlay("pod-panel") - - if(state_open) - icon_state = "pod-open" - return - - if(occupant) - var/image/occupant_overlay - - if(ismonkey(occupant)) // Monkey - occupant_overlay = image(CRYOMOBS, "monkey") - else if(isalienadult(occupant)) - if(isalienroyal(occupant)) // Queen and prae - occupant_overlay = image(CRYOMOBS, "alienq") - else if(isalienhunter(occupant)) // Hunter - occupant_overlay = image(CRYOMOBS, "alienh") - else if(isaliensentinel(occupant)) // Sentinel - occupant_overlay = image(CRYOMOBS, "aliens") - else // Drone or other - occupant_overlay = image(CRYOMOBS, "aliend") - - else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube - occupant_overlay = image(occupant.icon, occupant.icon_state) - occupant_overlay.copy_overlays(occupant) - - else - occupant_overlay = image(CRYOMOBS, "generic") - - occupant_overlay.dir = SOUTH - occupant_overlay.pixel_y = 22 - - if(on && !running_anim && is_operational()) - icon_state = "pod-on" - running_anim = TRUE - run_anim(TRUE, occupant_overlay) - else - icon_state = "pod-off" - add_overlay(occupant_overlay) - add_overlay("cover-off") - - else if(on && is_operational()) - icon_state = "pod-on" - add_overlay("cover-on") - else - icon_state = "pod-off" - add_overlay("cover-off") - -/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay) - if(!on || !occupant || !is_operational()) - running_anim = FALSE - return - cut_overlays() - if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24 - anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE - if(anim_up) - occupant_overlay.pixel_y++ - else - occupant_overlay.pixel_y-- - add_overlay(occupant_overlay) - add_overlay("cover-on") - addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE) - -/obj/machinery/atmospherics/components/unary/cryo_cell/nap_violation(mob/violator) - open_machine() - -/obj/machinery/atmospherics/components/unary/cryo_cell/process() - ..() - - if(!on) - return - if(!is_operational()) - on = FALSE - update_icon() - return - if(!occupant) - return - - var/mob/living/mob_occupant = occupant - if(mob_occupant.on_fire) - mob_occupant.ExtinguishMob() - if(!check_nap_violations()) - return - if(mob_occupant.stat == DEAD) // We don't bother with dead people. - return - if(mob_occupant.get_organic_health() >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. - if(iscarbon(mob_occupant)) - var/mob/living/carbon/C = mob_occupant - if(C.all_wounds) - if(!treating_wounds) // if we have wounds and haven't already alerted the doctors we're only dealing with the wounds, let them know - treating_wounds = TRUE - playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. - var/msg = "Patient vitals fully recovered, continuing automated wound treatment." - radio.talk_into(src, msg, radio_channel) - else // otherwise if we were only treating wounds and now we don't have any, turn off treating_wounds so we can boot 'em out - treating_wounds = FALSE - - if(!treating_wounds) - on = FALSE - update_icon() - playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. - var/msg = "Patient fully restored." - if(autoeject) // Eject if configured. - msg += " Auto ejecting patient now." - open_machine() - radio.talk_into(src, msg, radio_channel) - return - - var/datum/gas_mixture/air1 = airs[1] - - if(air1.gases.len) - if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic. - mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000) - mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000) - if(beaker) - if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic. - beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. - beaker.reagents.expose(occupant, VAPOR) - air1.gases[/datum/gas/oxygen][MOLES] -= max(0,air1.gases[/datum/gas/oxygen][MOLES] - 2 / efficiency) //Let's use gas for this - air1.garbage_collect() - if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). - reagent_transfer = 0 - - return 1 - -/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() - ..() - - if(!on) - return - - var/datum/gas_mixture/air1 = airs[1] - - if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen][MOLES] < 5) // Turn off if the machine won't work. - on = FALSE - update_icon() - return - - if(occupant) - var/mob/living/mob_occupant = occupant - var/cold_protection = 0 - var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. - - if(ishuman(occupant)) - var/mob/living/carbon/human/H = occupant - cold_protection = H.get_cold_protection(air1.temperature) - - if(abs(temperature_delta) > 1) - var/air_heat_capacity = air1.heat_capacity() - - var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) - - air1.temperature = clamp(air1.temperature - heat / air_heat_capacity, TCMB, MAX_TEMPERATURE) - mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) - - air1.gases[/datum/gas/oxygen][MOLES] = max(0,air1.gases[/datum/gas/oxygen][MOLES] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. - air1.garbage_collect() - - if(air1.temperature > 2000) - take_damage(clamp((air1.temperature)/200, 10, 20), BURN) - -/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - -/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = FALSE) - if(!state_open && !panel_open) - on = FALSE - for(var/mob/M in contents) //only drop mobs - M.forceMove(get_turf(src)) - if(isliving(M)) - var/mob/living/L = M - L.update_mobility() - occupant = null - flick("pod-open-anim", src) - ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) - treating_wounds = FALSE - if((isnull(user) || istype(user)) && state_open && !panel_open) - flick("pod-close-anim", src) - ..(user) - return occupant - -/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("You see [user] kicking against the glass of [src]!", \ - "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a thump from [src].") - if(do_after(user, breakout_time, target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - -/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) - . = ..() - if(occupant) - if(on) - . += "Someone's inside [src]!" - else - . += "You can barely make out a form floating in [src]." - else - . += "[src] seems empty." - -/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user) - if(user.incapacitated() || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) - return - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - close_machine(target) - else - user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") - if (do_after(user, 25, target=target)) - close_machine(target) - -/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/glass)) - . = 1 //no afterattack - if(beaker) - to_chat(user, "A beaker is already loaded into [src]!") - return - if(!user.transferItemToLoc(I, src)) - return - beaker = I - user.visible_message("[user] places [I] in [src].", \ - "You place [I] in [src].") - var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) - log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") - return - if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ - || default_change_direction_wrench(user, I) \ - || default_pry_open(I) \ - || default_deconstruction_crowbar(I)) - update_icon() - return - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - to_chat(user, "You can't access the maintenance panel while the pod is " \ - + (on ? "active" : (occupant ? "full" : "open")) + "!") - return - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Cryo", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data() - var/list/data = list() - data["isOperating"] = on - data["hasOccupant"] = occupant ? TRUE : FALSE - data["isOpen"] = state_open - data["autoEject"] = autoeject - - data["occupant"] = list() - if(occupant) - var/mob/living/mob_occupant = occupant - data["occupant"]["name"] = mob_occupant.name - switch(mob_occupant.stat) - if(CONSCIOUS) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "good" - if(SOFT_CRIT) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "average" - if(UNCONSCIOUS) - data["occupant"]["stat"] = "Unconscious" - data["occupant"]["statstate"] = "average" - if(DEAD) - data["occupant"]["stat"] = "Dead" - data["occupant"]["statstate"] = "bad" - data["occupant"]["health"] = round(mob_occupant.health, 1) - data["occupant"]["maxHealth"] = mob_occupant.maxHealth - data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD - data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1) - data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1) - data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1) - data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1) - data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1) - if(mob_occupant.bodytemperature < TCRYO) - data["occupant"]["temperaturestatus"] = "good" - else if(mob_occupant.bodytemperature < T0C) - data["occupant"]["temperaturestatus"] = "average" - else - data["occupant"]["temperaturestatus"] = "bad" - - var/datum/gas_mixture/air1 = airs[1] - data["cellTemperature"] = round(air1.temperature, 1) - - data["isBeakerLoaded"] = beaker ? TRUE : FALSE - var/beakerContents = list() - if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents += list(list("name" = R.name, "volume" = R.volume)) - data["beakerContents"] = beakerContents - return data - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params) - if(..()) - return - switch(action) - if("power") - if(on) - on = FALSE - else if(!state_open) - on = TRUE - update_icon() - . = TRUE - if("door") - if(state_open) - close_machine() - else - open_machine() - . = TRUE - if("autoeject") - autoeject = !autoeject - . = TRUE - if("ejectbeaker") - if(beaker) - beaker.forceMove(drop_location()) - if(Adjacent(usr) && !issilicon(usr)) - usr.put_in_hands(beaker) - beaker = null - . = TRUE - -/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) - if(can_interact(user) && !state_open) - on = !on - update_icon() - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user) - if(can_interact(user)) - if(state_open) - close_machine() - else - open_machine() - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) - return // we don't see the pipe network while inside cryo. - -/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through() - return // can't ventcrawl in or out of cryo. - -/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes() - return 0 // you can't see the pipe network when inside a cryo cell. - -/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature() - var/datum/gas_mixture/G = airs[1] - - if(G.total_moles() > 10) - return G.temperature - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W) - . = ..() - if(.) - SetInitDirections() - var/obj/machinery/atmospherics/node = nodes[1] - if(node) - node.disconnect(src) - nodes[1] = null - nullifyPipenet(parents[1]) - atmosinit() - node = nodes[1] - if(node) - node.atmosinit() - node.addMember(src) - SSair.add_to_rebuild_queue(src) - -#undef CRYOMOBS -#undef MAX_TEMPERATURE +#define CRYOMOBS 'icons/obj/cryo_mobs.dmi' +///Max temperature allowed inside the cryotube, should break before reaching this heat +#define MAX_TEMPERATURE 4000 + +/obj/machinery/atmospherics/components/unary/cryo_cell + name = "cryo cell" + icon = 'icons/obj/cryogenics.dmi' + icon_state = "pod-off" + density = TRUE + max_integrity = 350 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) + layer = ABOVE_WINDOW_LAYER + state_open = FALSE + circuit = /obj/item/circuitboard/machine/cryo_tube + ui_x = 400 + ui_y = 550 + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) + + var/autoeject = TRUE + var/volume = 100 + + var/efficiency = 1 + var/sleep_factor = 0.00125 + var/unconscious_factor = 0.001 + var/heat_capacity = 20000 + var/conduction_coefficient = 0.3 + + var/obj/item/reagent_containers/glass/beaker = null + var/reagent_transfer = 0 + + var/obj/item/radio/radio + var/radio_key = /obj/item/encryptionkey/headset_med + var/radio_channel = RADIO_CHANNEL_MEDICAL + + var/running_anim = FALSE + + var/escape_in_progress = FALSE + var/message_cooldown + var/breakout_time = 300 + ///Cryo will continue to treat people with 0 damage but existing wounds, but will sound off when damage healing is done in case doctors want to directly treat the wounds instead + var/treating_wounds = FALSE + fair_market_price = 10 + payment_department = ACCOUNT_MED + + +/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() + . = ..() + initialize_directions = dir + + radio = new(src) + radio.keyslot = new radio_key + radio.subspace_transmission = TRUE + radio.canhear_range = 0 + radio.recalculateChannels() + +/obj/machinery/atmospherics/components/unary/cryo_cell/Exited(atom/movable/AM, atom/newloc) + var/oldoccupant = occupant + . = ..() // Parent proc takes care of removing occupant if necessary + if (AM == oldoccupant) + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() + ..(dir, dir) + +/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() + var/C + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + C += M.rating + + efficiency = initial(efficiency) * C + sleep_factor = initial(sleep_factor) * C + unconscious_factor = initial(unconscious_factor) * C + heat_capacity = initial(heat_capacity) / C + conduction_coefficient = initial(conduction_coefficient) * C + +/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results" + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Efficiency at [efficiency*100]%." + +/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() + QDEL_NULL(radio) + QDEL_NULL(beaker) + ///Take the turf the cryotube is on + var/turf/T = get_turf(src) + if(T) + ///Take the air composition of the turf + var/datum/gas_mixture/env = T.return_air() + ///Take the air composition inside the cryotube + var/datum/gas_mixture/air1 = airs[1] + env.merge(air1) + T.air_update_turf() + + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) + ..() + if(beaker) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += beaker + if(EXPLODE_HEAVY) + SSexplosions.medobj += beaker + if(EXPLODE_LIGHT) + SSexplosions.lowobj += beaker + +/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A) + ..() + if(A == beaker) + beaker = null + updateUsrDialog() + +/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction() + if(beaker) + beaker.forceMove(drop_location()) + beaker = null + +/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon() + + cut_overlays() + + if(panel_open) + add_overlay("pod-panel") + + if(state_open) + icon_state = "pod-open" + return + + if(occupant) + var/image/occupant_overlay + + if(ismonkey(occupant)) // Monkey + occupant_overlay = image(CRYOMOBS, "monkey") + else if(isalienadult(occupant)) + if(isalienroyal(occupant)) // Queen and prae + occupant_overlay = image(CRYOMOBS, "alienq") + else if(isalienhunter(occupant)) // Hunter + occupant_overlay = image(CRYOMOBS, "alienh") + else if(isaliensentinel(occupant)) // Sentinel + occupant_overlay = image(CRYOMOBS, "aliens") + else // Drone or other + occupant_overlay = image(CRYOMOBS, "aliend") + + else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube + occupant_overlay = image(occupant.icon, occupant.icon_state) + occupant_overlay.copy_overlays(occupant) + + else + occupant_overlay = image(CRYOMOBS, "generic") + + occupant_overlay.dir = SOUTH + occupant_overlay.pixel_y = 22 + + if(on && !running_anim && is_operational()) + icon_state = "pod-on" + running_anim = TRUE + run_anim(TRUE, occupant_overlay) + else + icon_state = "pod-off" + add_overlay(occupant_overlay) + add_overlay("cover-off") + + else if(on && is_operational()) + icon_state = "pod-on" + add_overlay("cover-on") + else + icon_state = "pod-off" + add_overlay("cover-off") + +/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay) + if(!on || !occupant || !is_operational()) + running_anim = FALSE + return + cut_overlays() + if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24 + anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE + if(anim_up) + occupant_overlay.pixel_y++ + else + occupant_overlay.pixel_y-- + add_overlay(occupant_overlay) + add_overlay("cover-on") + addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE) + +/obj/machinery/atmospherics/components/unary/cryo_cell/nap_violation(mob/violator) + open_machine() + +/obj/machinery/atmospherics/components/unary/cryo_cell/process() + ..() + + if(!on) + return + if(!is_operational()) + on = FALSE + update_icon() + return + if(!occupant) + return + + var/mob/living/mob_occupant = occupant + if(mob_occupant.on_fire) + mob_occupant.ExtinguishMob() + if(!check_nap_violations()) + return + if(mob_occupant.stat == DEAD) // We don't bother with dead people. + return + if(mob_occupant.get_organic_health() >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. + if(iscarbon(mob_occupant)) + var/mob/living/carbon/C = mob_occupant + if(C.all_wounds) + if(!treating_wounds) // if we have wounds and haven't already alerted the doctors we're only dealing with the wounds, let them know + treating_wounds = TRUE + playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. + var/msg = "Patient vitals fully recovered, continuing automated wound treatment." + radio.talk_into(src, msg, radio_channel) + else // otherwise if we were only treating wounds and now we don't have any, turn off treating_wounds so we can boot 'em out + treating_wounds = FALSE + + if(!treating_wounds) + on = FALSE + update_icon() + playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. + var/msg = "Patient fully restored." + if(autoeject) // Eject if configured. + msg += " Auto ejecting patient now." + open_machine() + radio.talk_into(src, msg, radio_channel) + return + + var/datum/gas_mixture/air1 = airs[1] + + if(air1.gases.len) + if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic. + mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000) + mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000) + if(beaker) + if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic. + beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. + beaker.reagents.expose(occupant, VAPOR) + air1.gases[/datum/gas/oxygen][MOLES] -= max(0,air1.gases[/datum/gas/oxygen][MOLES] - 2 / efficiency) //Let's use gas for this + air1.garbage_collect() + if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). + reagent_transfer = 0 + + return 1 + +/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() + ..() + + if(!on) + return + + var/datum/gas_mixture/air1 = airs[1] + + if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen][MOLES] < 5) // Turn off if the machine won't work. + on = FALSE + update_icon() + return + + if(occupant) + var/mob/living/mob_occupant = occupant + var/cold_protection = 0 + var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. + + if(ishuman(occupant)) + var/mob/living/carbon/human/H = occupant + cold_protection = H.get_cold_protection(air1.temperature) + + if(abs(temperature_delta) > 1) + var/air_heat_capacity = air1.heat_capacity() + + var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) + + air1.temperature = clamp(air1.temperature - heat / air_heat_capacity, TCMB, MAX_TEMPERATURE) + mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) + + air1.gases[/datum/gas/oxygen][MOLES] = max(0,air1.gases[/datum/gas/oxygen][MOLES] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. + air1.garbage_collect() + + if(air1.temperature > 2000) + take_damage(clamp((air1.temperature)/200, 10, 20), BURN) + +/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + +/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = FALSE) + if(!state_open && !panel_open) + on = FALSE + for(var/mob/M in contents) //only drop mobs + M.forceMove(get_turf(src)) + if(isliving(M)) + var/mob/living/L = M + L.update_mobility() + occupant = null + flick("pod-open-anim", src) + ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) + treating_wounds = FALSE + if((isnull(user) || istype(user)) && state_open && !panel_open) + flick("pod-close-anim", src) + ..(user) + return occupant + +/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("You see [user] kicking against the glass of [src]!", \ + "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a thump from [src].") + if(do_after(user, breakout_time, target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + +/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) + . = ..() + if(occupant) + if(on) + . += "Someone's inside [src]!" + else + . += "You can barely make out a form floating in [src]." + else + . += "[src] seems empty." + +/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user) + if(user.incapacitated() || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + return + if(isliving(target)) + var/mob/living/L = target + if(L.incapacitated()) + close_machine(target) + else + user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") + if (do_after(user, 25, target=target)) + close_machine(target) + +/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/glass)) + . = 1 //no afterattack + if(beaker) + to_chat(user, "A beaker is already loaded into [src]!") + return + if(!user.transferItemToLoc(I, src)) + return + beaker = I + user.visible_message("[user] places [I] in [src].", \ + "You place [I] in [src].") + var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) + log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") + return + if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ + || default_change_direction_wrench(user, I) \ + || default_pry_open(I) \ + || default_deconstruction_crowbar(I)) + update_icon() + return + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + to_chat(user, "You can't access the maintenance panel while the pod is " \ + + (on ? "active" : (occupant ? "full" : "open")) + "!") + return + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Cryo", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data() + var/list/data = list() + data["isOperating"] = on + data["hasOccupant"] = occupant ? TRUE : FALSE + data["isOpen"] = state_open + data["autoEject"] = autoeject + + data["occupant"] = list() + if(occupant) + var/mob/living/mob_occupant = occupant + data["occupant"]["name"] = mob_occupant.name + switch(mob_occupant.stat) + if(CONSCIOUS) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "good" + if(SOFT_CRIT) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "average" + if(UNCONSCIOUS) + data["occupant"]["stat"] = "Unconscious" + data["occupant"]["statstate"] = "average" + if(DEAD) + data["occupant"]["stat"] = "Dead" + data["occupant"]["statstate"] = "bad" + data["occupant"]["health"] = round(mob_occupant.health, 1) + data["occupant"]["maxHealth"] = mob_occupant.maxHealth + data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1) + data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1) + data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1) + data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1) + data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1) + if(mob_occupant.bodytemperature < TCRYO) + data["occupant"]["temperaturestatus"] = "good" + else if(mob_occupant.bodytemperature < T0C) + data["occupant"]["temperaturestatus"] = "average" + else + data["occupant"]["temperaturestatus"] = "bad" + + var/datum/gas_mixture/air1 = airs[1] + data["cellTemperature"] = round(air1.temperature, 1) + + data["isBeakerLoaded"] = beaker ? TRUE : FALSE + var/beakerContents = list() + if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) + for(var/datum/reagent/R in beaker.reagents.reagent_list) + beakerContents += list(list("name" = R.name, "volume" = R.volume)) + data["beakerContents"] = beakerContents + return data + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params) + if(..()) + return + switch(action) + if("power") + if(on) + on = FALSE + else if(!state_open) + on = TRUE + update_icon() + . = TRUE + if("door") + if(state_open) + close_machine() + else + open_machine() + . = TRUE + if("autoeject") + autoeject = !autoeject + . = TRUE + if("ejectbeaker") + if(beaker) + beaker.forceMove(drop_location()) + if(Adjacent(usr) && !issilicon(usr)) + usr.put_in_hands(beaker) + beaker = null + . = TRUE + +/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) + if(can_interact(user) && !state_open) + on = !on + update_icon() + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user) + if(can_interact(user)) + if(state_open) + close_machine() + else + open_machine() + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) + return // we don't see the pipe network while inside cryo. + +/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through() + return // can't ventcrawl in or out of cryo. + +/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes() + return 0 // you can't see the pipe network when inside a cryo cell. + +/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature() + var/datum/gas_mixture/G = airs[1] + + if(G.total_moles() > 10) + return G.temperature + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W) + . = ..() + if(.) + SetInitDirections() + var/obj/machinery/atmospherics/node = nodes[1] + if(node) + node.disconnect(src) + nodes[1] = null + nullifyPipenet(parents[1]) + atmosinit() + node = nodes[1] + if(node) + node.atmosinit() + node.addMember(src) + SSair.add_to_rebuild_queue(src) + +#undef CRYOMOBS +#undef MAX_TEMPERATURE diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm index ad005278461..8fae543f27d 100644 --- a/code/modules/atmospherics/machinery/other/meter.dm +++ b/code/modules/atmospherics/machinery/other/meter.dm @@ -1,146 +1,146 @@ -/obj/machinery/meter - name = "gas flow meter" - desc = "It measures something." - icon = 'icons/obj/atmospherics/pipes/meter.dmi' - icon_state = "meterX" - layer = GAS_PUMP_LAYER - power_channel = AREA_USAGE_ENVIRON - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - max_integrity = 150 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) - var/frequency = 0 - var/atom/target - var/id_tag - var/target_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/meter/atmos - frequency = FREQ_ATMOS_STORAGE - -/obj/machinery/meter/atmos/atmos_waste_loop - name = "waste loop gas flow meter" - id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE - -/obj/machinery/meter/atmos/distro_loop - name = "distribution loop gas flow meter" - id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION - -/obj/machinery/meter/Destroy() - SSair.atmos_machinery -= src - target = null - return ..() - -/obj/machinery/meter/Initialize(mapload, new_piping_layer) - if(!isnull(new_piping_layer)) - target_layer = new_piping_layer - SSair.atmos_machinery += src - if(!target) - reattach_to_layer() - return ..() - -/obj/machinery/meter/proc/reattach_to_layer() - var/obj/machinery/atmospherics/candidate - for(var/obj/machinery/atmospherics/pipe/pipe in loc) - if(pipe.piping_layer == target_layer) - candidate = pipe - if(candidate) - target = candidate - setAttachLayer(candidate.piping_layer) - -/obj/machinery/meter/proc/setAttachLayer(new_layer) - target_layer = new_layer - PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) - -/obj/machinery/meter/process_atmos() - if(!(target?.flags_1 & INITIALIZED_1)) - icon_state = "meterX" - return 0 - - if(machine_stat & (BROKEN|NOPOWER)) - icon_state = "meter0" - return 0 - - use_power(5) - - var/datum/gas_mixture/environment = target.return_air() - if(!environment) - icon_state = "meterX" - return 0 - - var/env_pressure = environment.return_pressure() - if(env_pressure <= 0.15*ONE_ATMOSPHERE) - icon_state = "meter0" - else if(env_pressure <= 1.8*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5) - icon_state = "meter1_[val]" - else if(env_pressure <= 30*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1 - icon_state = "meter2_[val]" - else if(env_pressure <= 59*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1 - icon_state = "meter3_[val]" - else - icon_state = "meter4" - - if(frequency) - var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency) - - if(!radio_connection) - return - - var/datum/signal/signal = new(list( - "id_tag" = id_tag, - "device" = "AM", - "pressure" = round(env_pressure), - "sigtype" = "status" - )) - radio_connection.post_signal(src, signal) - -/obj/machinery/meter/proc/status() - if (target) - var/datum/gas_mixture/environment = target.return_air() - if(environment) - . = "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)] K ([round(environment.temperature-T0C,0.01)]°C)." - else - . = "The sensor error light is blinking." - else - . = "The connect error light is blinking." - -/obj/machinery/meter/examine(mob/user) - . = ..() - . += status() - -/obj/machinery/meter/wrench_act(mob/user, obj/item/I) - ..() - to_chat(user, "You begin to unfasten \the [src]...") - if (I.use_tool(src, user, 40, volume=50)) - user.visible_message( - "[user] unfastens \the [src].", - "You unfasten \the [src].", - "You hear ratchet.") - deconstruct() - return TRUE - -/obj/machinery/meter/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/pipe_meter(loc) - qdel(src) - -/obj/machinery/meter/interact(mob/user) - if(machine_stat & (NOPOWER|BROKEN)) - return - else - to_chat(user, status()) - -/obj/machinery/meter/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -// TURF METER - REPORTS A TILE'S AIR CONTENTS -// why are you yelling? -/obj/machinery/meter/turf - -/obj/machinery/meter/turf/reattach_to_layer() - target = loc +/obj/machinery/meter + name = "gas flow meter" + desc = "It measures something." + icon = 'icons/obj/atmospherics/pipes/meter.dmi' + icon_state = "meterX" + layer = GAS_PUMP_LAYER + power_channel = AREA_USAGE_ENVIRON + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + max_integrity = 150 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) + var/frequency = 0 + var/atom/target + var/id_tag + var/target_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/meter/atmos + frequency = FREQ_ATMOS_STORAGE + +/obj/machinery/meter/atmos/atmos_waste_loop + name = "waste loop gas flow meter" + id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE + +/obj/machinery/meter/atmos/distro_loop + name = "distribution loop gas flow meter" + id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION + +/obj/machinery/meter/Destroy() + SSair.atmos_machinery -= src + target = null + return ..() + +/obj/machinery/meter/Initialize(mapload, new_piping_layer) + if(!isnull(new_piping_layer)) + target_layer = new_piping_layer + SSair.atmos_machinery += src + if(!target) + reattach_to_layer() + return ..() + +/obj/machinery/meter/proc/reattach_to_layer() + var/obj/machinery/atmospherics/candidate + for(var/obj/machinery/atmospherics/pipe/pipe in loc) + if(pipe.piping_layer == target_layer) + candidate = pipe + if(candidate) + target = candidate + setAttachLayer(candidate.piping_layer) + +/obj/machinery/meter/proc/setAttachLayer(new_layer) + target_layer = new_layer + PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) + +/obj/machinery/meter/process_atmos() + if(!(target?.flags_1 & INITIALIZED_1)) + icon_state = "meterX" + return 0 + + if(machine_stat & (BROKEN|NOPOWER)) + icon_state = "meter0" + return 0 + + use_power(5) + + var/datum/gas_mixture/environment = target.return_air() + if(!environment) + icon_state = "meterX" + return 0 + + var/env_pressure = environment.return_pressure() + if(env_pressure <= 0.15*ONE_ATMOSPHERE) + icon_state = "meter0" + else if(env_pressure <= 1.8*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5) + icon_state = "meter1_[val]" + else if(env_pressure <= 30*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1 + icon_state = "meter2_[val]" + else if(env_pressure <= 59*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1 + icon_state = "meter3_[val]" + else + icon_state = "meter4" + + if(frequency) + var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency) + + if(!radio_connection) + return + + var/datum/signal/signal = new(list( + "id_tag" = id_tag, + "device" = "AM", + "pressure" = round(env_pressure), + "sigtype" = "status" + )) + radio_connection.post_signal(src, signal) + +/obj/machinery/meter/proc/status() + if (target) + var/datum/gas_mixture/environment = target.return_air() + if(environment) + . = "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)] K ([round(environment.temperature-T0C,0.01)]°C)." + else + . = "The sensor error light is blinking." + else + . = "The connect error light is blinking." + +/obj/machinery/meter/examine(mob/user) + . = ..() + . += status() + +/obj/machinery/meter/wrench_act(mob/user, obj/item/I) + ..() + to_chat(user, "You begin to unfasten \the [src]...") + if (I.use_tool(src, user, 40, volume=50)) + user.visible_message( + "[user] unfastens \the [src].", + "You unfasten \the [src].", + "You hear ratchet.") + deconstruct() + return TRUE + +/obj/machinery/meter/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/pipe_meter(loc) + qdel(src) + +/obj/machinery/meter/interact(mob/user) + if(machine_stat & (NOPOWER|BROKEN)) + return + else + to_chat(user, status()) + +/obj/machinery/meter/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +// TURF METER - REPORTS A TILE'S AIR CONTENTS +// why are you yelling? +/obj/machinery/meter/turf + +/obj/machinery/meter/turf/reattach_to_layer() + target = loc diff --git a/code/modules/atmospherics/machinery/pipes/layermanifold.dm b/code/modules/atmospherics/machinery/pipes/layermanifold.dm index 7046cab8b04..c16c99d0865 100644 --- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm +++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm @@ -1,133 +1,133 @@ -/obj/machinery/atmospherics/pipe/layer_manifold - name = "layer adaptor" - icon = 'icons/obj/atmospherics/pipes/manifold.dmi' - icon_state = "manifoldlayer" - desc = "A special pipe to bridge pipe layers with." - dir = SOUTH - initialize_directions = NORTH|SOUTH - pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE - piping_layer = PIPING_LAYER_DEFAULT - device_type = 0 - volume = 260 - construction_type = /obj/item/pipe/binary - pipe_state = "manifoldlayer" - - var/list/front_nodes - var/list/back_nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() - front_nodes = list() - back_nodes = list() - icon_state = "manifoldlayer_center" - return ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/Destroy() - nullifyAllNodes() - return ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes() - var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes() - front_nodes = null - back_nodes = null - nodes = list() - for(var/obj/machinery/atmospherics/A in needs_nullifying) - A.disconnect(src) - SSair.add_to_rebuild_queue(A) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes() - return front_nodes + back_nodes + nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/update_icon() //HEAVILY WIP FOR UPDATE ICONS!! - cut_overlays() - layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else. - - for(var/node in front_nodes) - add_attached_images(node) - for(var/node in back_nodes) - add_attached_images(node) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_images(obj/machinery/atmospherics/A) - if(!A) - return - if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) - for(var/i in PIPING_LAYER_MIN to PIPING_LAYER_MAX) - add_attached_image(get_dir(src, A), i) - return - add_attached_image(get_dir(src, A), A.piping_layer, A.pipe_color) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(p_dir, p_layer, p_color = null) - var/image/I - - if(p_color) - I = getpipeimage(icon, "pipe", p_dir, p_color, piping_layer = p_layer) - else - I = getpipeimage(icon, "pipe", p_dir, piping_layer = p_layer) - - I.layer = layer - 0.01 - add_overlay(I) - -/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections() - switch(dir) - if(NORTH || SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST || WEST) - initialize_directions = EAST|WEST - -/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer) - if(!given_layer) - return TRUE - . = ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections() - front_nodes = list() - back_nodes = list() - var/list/new_nodes = list() - for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX) - var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter) - var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter) - front_nodes += foundfront - back_nodes += foundback - if(foundfront && !QDELETED(foundfront)) - new_nodes += foundfront - if(foundback && !QDELETED(foundback)) - new_nodes += foundback - update_icon() - return new_nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit() - normalize_cardinal_directions() - findAllConnections() - -/obj/machinery/atmospherics/pipe/layer_manifold/setPipingLayer() - piping_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/atmospherics/pipe/layer_manifold/pipeline_expansion() - return get_all_connected_nodes() - -/obj/machinery/atmospherics/pipe/layer_manifold/disconnect(obj/machinery/atmospherics/reference) - if(istype(reference, /obj/machinery/atmospherics/pipe)) - var/obj/machinery/atmospherics/pipe/P = reference - P.destroy_network() - while(reference in get_all_connected_nodes()) - if(reference in nodes) - var/i = nodes.Find(reference) - nodes[i] = null - if(reference in front_nodes) - var/i = front_nodes.Find(reference) - front_nodes[i] = null - if(reference in back_nodes) - var/i = back_nodes.Find(reference) - back_nodes[i] = null - update_icon() - -/obj/machinery/atmospherics/pipe/layer_manifold/relaymove(mob/living/user, dir) - if(initialize_directions & dir) - return ..() - if((NORTH|EAST) & dir) - user.ventcrawl_layer = clamp(user.ventcrawl_layer + 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - if((SOUTH|WEST) & dir) - user.ventcrawl_layer = clamp(user.ventcrawl_layer - 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - to_chat(user, "You align yourself with the [user.ventcrawl_layer]\th output.") - -/obj/machinery/atmospherics/pipe/layer_manifold/visible - layer = GAS_PIPE_VISIBLE_LAYER +/obj/machinery/atmospherics/pipe/layer_manifold + name = "layer adaptor" + icon = 'icons/obj/atmospherics/pipes/manifold.dmi' + icon_state = "manifoldlayer" + desc = "A special pipe to bridge pipe layers with." + dir = SOUTH + initialize_directions = NORTH|SOUTH + pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE + piping_layer = PIPING_LAYER_DEFAULT + device_type = 0 + volume = 260 + construction_type = /obj/item/pipe/binary + pipe_state = "manifoldlayer" + + var/list/front_nodes + var/list/back_nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() + front_nodes = list() + back_nodes = list() + icon_state = "manifoldlayer_center" + return ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/Destroy() + nullifyAllNodes() + return ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes() + var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes() + front_nodes = null + back_nodes = null + nodes = list() + for(var/obj/machinery/atmospherics/A in needs_nullifying) + A.disconnect(src) + SSair.add_to_rebuild_queue(A) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes() + return front_nodes + back_nodes + nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/update_icon() //HEAVILY WIP FOR UPDATE ICONS!! + cut_overlays() + layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else. + + for(var/node in front_nodes) + add_attached_images(node) + for(var/node in back_nodes) + add_attached_images(node) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_images(obj/machinery/atmospherics/A) + if(!A) + return + if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) + for(var/i in PIPING_LAYER_MIN to PIPING_LAYER_MAX) + add_attached_image(get_dir(src, A), i) + return + add_attached_image(get_dir(src, A), A.piping_layer, A.pipe_color) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(p_dir, p_layer, p_color = null) + var/image/I + + if(p_color) + I = getpipeimage(icon, "pipe", p_dir, p_color, piping_layer = p_layer) + else + I = getpipeimage(icon, "pipe", p_dir, piping_layer = p_layer) + + I.layer = layer - 0.01 + add_overlay(I) + +/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections() + switch(dir) + if(NORTH || SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST || WEST) + initialize_directions = EAST|WEST + +/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer) + if(!given_layer) + return TRUE + . = ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections() + front_nodes = list() + back_nodes = list() + var/list/new_nodes = list() + for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX) + var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter) + var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter) + front_nodes += foundfront + back_nodes += foundback + if(foundfront && !QDELETED(foundfront)) + new_nodes += foundfront + if(foundback && !QDELETED(foundback)) + new_nodes += foundback + update_icon() + return new_nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit() + normalize_cardinal_directions() + findAllConnections() + +/obj/machinery/atmospherics/pipe/layer_manifold/setPipingLayer() + piping_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/atmospherics/pipe/layer_manifold/pipeline_expansion() + return get_all_connected_nodes() + +/obj/machinery/atmospherics/pipe/layer_manifold/disconnect(obj/machinery/atmospherics/reference) + if(istype(reference, /obj/machinery/atmospherics/pipe)) + var/obj/machinery/atmospherics/pipe/P = reference + P.destroy_network() + while(reference in get_all_connected_nodes()) + if(reference in nodes) + var/i = nodes.Find(reference) + nodes[i] = null + if(reference in front_nodes) + var/i = front_nodes.Find(reference) + front_nodes[i] = null + if(reference in back_nodes) + var/i = back_nodes.Find(reference) + back_nodes[i] = null + update_icon() + +/obj/machinery/atmospherics/pipe/layer_manifold/relaymove(mob/living/user, dir) + if(initialize_directions & dir) + return ..() + if((NORTH|EAST) & dir) + user.ventcrawl_layer = clamp(user.ventcrawl_layer + 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + if((SOUTH|WEST) & dir) + user.ventcrawl_layer = clamp(user.ventcrawl_layer - 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + to_chat(user, "You align yourself with the [user.ventcrawl_layer]\th output.") + +/obj/machinery/atmospherics/pipe/layer_manifold/visible + layer = GAS_PIPE_VISIBLE_LAYER diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index 1b03e3f179d..be59d2308f3 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -1,166 +1,166 @@ -/obj/machinery/portable_atmospherics - name = "portable_atmospherics" - icon = 'icons/obj/atmos.dmi' - use_power = NO_POWER_USE - max_integrity = 250 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) - anchored = FALSE - - var/datum/gas_mixture/air_contents - var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port - var/obj/item/tank/holding - - var/volume = 0 - - var/maximum_pressure = 90 * ONE_ATMOSPHERE - -/obj/machinery/portable_atmospherics/New() - ..() - SSair.atmos_machinery += src - -/obj/machinery/portable_atmospherics/Initialize() - . = ..() - air_contents = new - air_contents.volume = volume - air_contents.temperature = T20C - -/obj/machinery/portable_atmospherics/Destroy() - SSair.atmos_machinery -= src - - disconnect() - qdel(air_contents) - air_contents = null - - return ..() - -/obj/machinery/portable_atmospherics/ex_act(severity, target) - if(severity == 1 || target == src) - if(resistance_flags & INDESTRUCTIBLE) - return //Indestructable cans shouldn't release air - - //This explosion will destroy the can, release its air. - var/turf/T = get_turf(src) - T.assume_air(air_contents) - T.air_update_turf() - - return ..() - -/obj/machinery/portable_atmospherics/process_atmos() - if(!connected_port) // Pipe network handles reactions if connected. - air_contents.react(src) - -/obj/machinery/portable_atmospherics/return_air() - return air_contents - -/obj/machinery/portable_atmospherics/return_analyzable_air() - return air_contents - -/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) - //Make sure not already connected to something else - if(connected_port || !new_port || new_port.connected_device) - return FALSE - - //Make sure are close enough for a valid connection - if(new_port.loc != get_turf(src)) - return FALSE - - //Perform the connection - connected_port = new_port - connected_port.connected_device = src - var/datum/pipeline/connected_port_parent = connected_port.parents[1] - connected_port_parent.reconcile_air() - - anchored = TRUE //Prevent movement - pixel_x = new_port.pixel_x - pixel_y = new_port.pixel_y - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/Move() - . = ..() - if(.) - disconnect() - -/obj/machinery/portable_atmospherics/proc/disconnect() - if(!connected_port) - return FALSE - anchored = FALSE - connected_port.connected_device = null - connected_port = null - pixel_x = 0 - pixel_y = 0 - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/AltClick(mob/living/user) - . = ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user)) || !can_interact(user)) - return - if(holding) - to_chat(user, "You remove [holding] from [src].") - replace_tank(user, TRUE) - -/obj/machinery/portable_atmospherics/examine(mob/user) - . = ..() - if(holding) - . += "\The [src] contains [holding]. Alt-click [src] to remove it."+\ - "Click [src] with another gas tank to hot swap [holding]." - -/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) - if(!user) - return FALSE - if(holding) - user.put_in_hands(holding) - holding = null - if(new_tank) - holding = new_tank - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - if(!(machine_stat & BROKEN)) - var/obj/item/tank/T = W - if(!user.transferItemToLoc(T, src)) - return - to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") - investigate_log("had its internal [holding] swapped with [T] by [key_name(user)].", INVESTIGATE_ATMOS) - replace_tank(user, FALSE, T) - update_icon() - else if(W.tool_behaviour == TOOL_WRENCH) - if(!(machine_stat & BROKEN)) - if(connected_port) - investigate_log("was disconnected from [connected_port] by [key_name(user)].", INVESTIGATE_ATMOS) - disconnect() - W.play_tool_sound(src) - user.visible_message( \ - "[user] disconnects [src].", \ - "You unfasten [src] from the port.", \ - "You hear a ratchet.") - update_icon() - return - else - var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc - if(!possible_port) - to_chat(user, "Nothing happens.") - return - if(!connect(possible_port)) - to_chat(user, "[name] failed to connect to the port.") - return - W.play_tool_sound(src) - user.visible_message( \ - "[user] connects [src].", \ - "You fasten [src] to the port.", \ - "You hear a ratchet.") - update_icon() - investigate_log("was connected to [possible_port] by [key_name(user)].", INVESTIGATE_ATMOS) - else - return ..() - -/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) - if(I.force < 10 && !(machine_stat & BROKEN)) - take_damage(0) - else - investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS) - add_fingerprint(user) - ..() +/obj/machinery/portable_atmospherics + name = "portable_atmospherics" + icon = 'icons/obj/atmos.dmi' + use_power = NO_POWER_USE + max_integrity = 250 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) + anchored = FALSE + + var/datum/gas_mixture/air_contents + var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port + var/obj/item/tank/holding + + var/volume = 0 + + var/maximum_pressure = 90 * ONE_ATMOSPHERE + +/obj/machinery/portable_atmospherics/New() + ..() + SSair.atmos_machinery += src + +/obj/machinery/portable_atmospherics/Initialize() + . = ..() + air_contents = new + air_contents.volume = volume + air_contents.temperature = T20C + +/obj/machinery/portable_atmospherics/Destroy() + SSair.atmos_machinery -= src + + disconnect() + qdel(air_contents) + air_contents = null + + return ..() + +/obj/machinery/portable_atmospherics/ex_act(severity, target) + if(severity == 1 || target == src) + if(resistance_flags & INDESTRUCTIBLE) + return //Indestructable cans shouldn't release air + + //This explosion will destroy the can, release its air. + var/turf/T = get_turf(src) + T.assume_air(air_contents) + T.air_update_turf() + + return ..() + +/obj/machinery/portable_atmospherics/process_atmos() + if(!connected_port) // Pipe network handles reactions if connected. + air_contents.react(src) + +/obj/machinery/portable_atmospherics/return_air() + return air_contents + +/obj/machinery/portable_atmospherics/return_analyzable_air() + return air_contents + +/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) + //Make sure not already connected to something else + if(connected_port || !new_port || new_port.connected_device) + return FALSE + + //Make sure are close enough for a valid connection + if(new_port.loc != get_turf(src)) + return FALSE + + //Perform the connection + connected_port = new_port + connected_port.connected_device = src + var/datum/pipeline/connected_port_parent = connected_port.parents[1] + connected_port_parent.reconcile_air() + + anchored = TRUE //Prevent movement + pixel_x = new_port.pixel_x + pixel_y = new_port.pixel_y + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/Move() + . = ..() + if(.) + disconnect() + +/obj/machinery/portable_atmospherics/proc/disconnect() + if(!connected_port) + return FALSE + anchored = FALSE + connected_port.connected_device = null + connected_port = null + pixel_x = 0 + pixel_y = 0 + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/AltClick(mob/living/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user)) || !can_interact(user)) + return + if(holding) + to_chat(user, "You remove [holding] from [src].") + replace_tank(user, TRUE) + +/obj/machinery/portable_atmospherics/examine(mob/user) + . = ..() + if(holding) + . += "\The [src] contains [holding]. Alt-click [src] to remove it."+\ + "Click [src] with another gas tank to hot swap [holding]." + +/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) + if(!user) + return FALSE + if(holding) + user.put_in_hands(holding) + holding = null + if(new_tank) + holding = new_tank + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + if(!(machine_stat & BROKEN)) + var/obj/item/tank/T = W + if(!user.transferItemToLoc(T, src)) + return + to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") + investigate_log("had its internal [holding] swapped with [T] by [key_name(user)].", INVESTIGATE_ATMOS) + replace_tank(user, FALSE, T) + update_icon() + else if(W.tool_behaviour == TOOL_WRENCH) + if(!(machine_stat & BROKEN)) + if(connected_port) + investigate_log("was disconnected from [connected_port] by [key_name(user)].", INVESTIGATE_ATMOS) + disconnect() + W.play_tool_sound(src) + user.visible_message( \ + "[user] disconnects [src].", \ + "You unfasten [src] from the port.", \ + "You hear a ratchet.") + update_icon() + return + else + var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc + if(!possible_port) + to_chat(user, "Nothing happens.") + return + if(!connect(possible_port)) + to_chat(user, "[name] failed to connect to the port.") + return + W.play_tool_sound(src) + user.visible_message( \ + "[user] connects [src].", \ + "You fasten [src] to the port.", \ + "You hear a ratchet.") + update_icon() + investigate_log("was connected to [possible_port] by [key_name(user)].", INVESTIGATE_ATMOS) + else + return ..() + +/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) + if(I.force < 10 && !(machine_stat & BROKEN)) + take_damage(0) + else + investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS) + add_fingerprint(user) + ..() diff --git a/code/modules/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm index b0135134bd5..04c40115185 100644 --- a/code/modules/atmospherics/machinery/portable/pump.dm +++ b/code/modules/atmospherics/machinery/portable/pump.dm @@ -1,162 +1,162 @@ -#define PUMP_OUT "out" -#define PUMP_IN "in" -#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 25) -#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10) -#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE) - -/obj/machinery/portable_atmospherics/pump - name = "portable air pump" - icon_state = "psiphon:0" - density = TRUE - ui_x = 300 - ui_y = 315 - - max_integrity = 250 - ///Max amount of heat allowed inside of the canister before it starts to melt (different tiers have different limits) - var/heat_limit = 5000 - ///Max amount of pressure allowed inside of the canister before it starts to break (different tiers have different limits) - var/pressure_limit = 50000 - - var/on = FALSE - var/direction = PUMP_OUT - var/target_pressure = ONE_ATMOSPHERE - - volume = 1000 - -/obj/machinery/portable_atmospherics/pump/Destroy() - var/turf/T = get_turf(src) - T.assume_air(air_contents) - air_update_turf() - return ..() - -/obj/machinery/portable_atmospherics/pump/update_icon_state() - icon_state = "psiphon:[on]" - -/obj/machinery/portable_atmospherics/pump/update_overlays() - . = ..() - if(holding) - . += "siphon-open" - if(connected_port) - . += "siphon-connector" - -/obj/machinery/portable_atmospherics/pump/process_atmos() - ..() - - var/pressure = air_contents.return_pressure() - var/temperature = air_contents.return_temperature() - ///function used to check the limit of the pumps and also set the amount of damage that the pump can receive, if the heat and pressure are way higher than the limit the more damage will be done - if(temperature > heat_limit || pressure > pressure_limit) - take_damage(clamp((temperature/heat_limit) * (pressure/pressure_limit), 5, 50), BURN, 0) - return - - if(!on) - return - - var/turf/T = get_turf(src) - var/datum/gas_mixture/sending - var/datum/gas_mixture/receiving - if(direction == PUMP_OUT) // Hook up the internal pump. - sending = (holding ? holding.air_contents : air_contents) - receiving = (holding ? air_contents : T.return_air()) - else - sending = (holding ? air_contents : T.return_air()) - receiving = (holding ? holding.air_contents : air_contents) - - - if(sending.pump_gas_to(receiving, target_pressure) && !holding) - air_update_turf() // Update the environment if needed. - -/obj/machinery/portable_atmospherics/pump/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(is_operational()) - if(prob(50 / severity)) - on = !on - if(prob(100 / severity)) - direction = PUMP_OUT - target_pressure = rand(0, 100 * ONE_ATMOSPHERE) - update_icon() - -/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve) - . = ..() - if(.) - if(close_valve) - if(on) - on = FALSE - update_icon() - else if(on && holding && direction == PUMP_OUT) - investigate_log("[key_name(user)] started a transfer into [holding].", INVESTIGATE_ATMOS) - - -/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "PortablePump", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/portable_atmospherics/pump/ui_data() - var/data = list() - data["on"] = on - data["direction"] = direction == PUMP_IN ? TRUE : FALSE - data["connected"] = connected_port ? TRUE : FALSE - data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - data["target_pressure"] = round(target_pressure ? target_pressure : 0) - data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE) - data["min_pressure"] = round(PUMP_MIN_PRESSURE) - data["max_pressure"] = round(PUMP_MAX_PRESSURE) - - if(holding) - data["holding"] = list() - data["holding"]["name"] = holding.name - data["holding"]["pressure"] = round(holding.air_contents.return_pressure()) - else - data["holding"] = null - return data - -/obj/machinery/portable_atmospherics/pump/ui_act(action, params) - if(..()) - return - switch(action) - if("power") - on = !on - if(on && !holding) - var/plasma = air_contents.gases[/datum/gas/plasma] - var/n2o = air_contents.gases[/datum/gas/nitrous_oxide] - if(n2o || plasma) - message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]") - log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]") - else if(on && direction == PUMP_OUT) - investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) - . = TRUE - if("direction") - if(direction == PUMP_OUT) - direction = PUMP_IN - else - if(on && holding) - investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) - direction = PUMP_OUT - . = TRUE - if("pressure") - var/pressure = params["pressure"] - if(pressure == "reset") - pressure = PUMP_DEFAULT_PRESSURE - . = TRUE - else if(pressure == "min") - pressure = PUMP_MIN_PRESSURE - . = TRUE - else if(pressure == "max") - pressure = PUMP_MAX_PRESSURE - . = TRUE - else if(text2num(pressure) != null) - pressure = text2num(pressure) - . = TRUE - if(.) - target_pressure = clamp(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) - investigate_log("was set to [target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS) - if("eject") - if(holding) - replace_tank(usr, FALSE) - . = TRUE - update_icon() +#define PUMP_OUT "out" +#define PUMP_IN "in" +#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 25) +#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10) +#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE) + +/obj/machinery/portable_atmospherics/pump + name = "portable air pump" + icon_state = "psiphon:0" + density = TRUE + ui_x = 300 + ui_y = 315 + + max_integrity = 250 + ///Max amount of heat allowed inside of the canister before it starts to melt (different tiers have different limits) + var/heat_limit = 5000 + ///Max amount of pressure allowed inside of the canister before it starts to break (different tiers have different limits) + var/pressure_limit = 50000 + + var/on = FALSE + var/direction = PUMP_OUT + var/target_pressure = ONE_ATMOSPHERE + + volume = 1000 + +/obj/machinery/portable_atmospherics/pump/Destroy() + var/turf/T = get_turf(src) + T.assume_air(air_contents) + air_update_turf() + return ..() + +/obj/machinery/portable_atmospherics/pump/update_icon_state() + icon_state = "psiphon:[on]" + +/obj/machinery/portable_atmospherics/pump/update_overlays() + . = ..() + if(holding) + . += "siphon-open" + if(connected_port) + . += "siphon-connector" + +/obj/machinery/portable_atmospherics/pump/process_atmos() + ..() + + var/pressure = air_contents.return_pressure() + var/temperature = air_contents.return_temperature() + ///function used to check the limit of the pumps and also set the amount of damage that the pump can receive, if the heat and pressure are way higher than the limit the more damage will be done + if(temperature > heat_limit || pressure > pressure_limit) + take_damage(clamp((temperature/heat_limit) * (pressure/pressure_limit), 5, 50), BURN, 0) + return + + if(!on) + return + + var/turf/T = get_turf(src) + var/datum/gas_mixture/sending + var/datum/gas_mixture/receiving + if(direction == PUMP_OUT) // Hook up the internal pump. + sending = (holding ? holding.air_contents : air_contents) + receiving = (holding ? air_contents : T.return_air()) + else + sending = (holding ? air_contents : T.return_air()) + receiving = (holding ? holding.air_contents : air_contents) + + + if(sending.pump_gas_to(receiving, target_pressure) && !holding) + air_update_turf() // Update the environment if needed. + +/obj/machinery/portable_atmospherics/pump/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(is_operational()) + if(prob(50 / severity)) + on = !on + if(prob(100 / severity)) + direction = PUMP_OUT + target_pressure = rand(0, 100 * ONE_ATMOSPHERE) + update_icon() + +/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve) + . = ..() + if(.) + if(close_valve) + if(on) + on = FALSE + update_icon() + else if(on && holding && direction == PUMP_OUT) + investigate_log("[key_name(user)] started a transfer into [holding].", INVESTIGATE_ATMOS) + + +/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "PortablePump", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/portable_atmospherics/pump/ui_data() + var/data = list() + data["on"] = on + data["direction"] = direction == PUMP_IN ? TRUE : FALSE + data["connected"] = connected_port ? TRUE : FALSE + data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) + data["target_pressure"] = round(target_pressure ? target_pressure : 0) + data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE) + data["min_pressure"] = round(PUMP_MIN_PRESSURE) + data["max_pressure"] = round(PUMP_MAX_PRESSURE) + + if(holding) + data["holding"] = list() + data["holding"]["name"] = holding.name + data["holding"]["pressure"] = round(holding.air_contents.return_pressure()) + else + data["holding"] = null + return data + +/obj/machinery/portable_atmospherics/pump/ui_act(action, params) + if(..()) + return + switch(action) + if("power") + on = !on + if(on && !holding) + var/plasma = air_contents.gases[/datum/gas/plasma] + var/n2o = air_contents.gases[/datum/gas/nitrous_oxide] + if(n2o || plasma) + message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]") + log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]") + else if(on && direction == PUMP_OUT) + investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) + . = TRUE + if("direction") + if(direction == PUMP_OUT) + direction = PUMP_IN + else + if(on && holding) + investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) + direction = PUMP_OUT + . = TRUE + if("pressure") + var/pressure = params["pressure"] + if(pressure == "reset") + pressure = PUMP_DEFAULT_PRESSURE + . = TRUE + else if(pressure == "min") + pressure = PUMP_MIN_PRESSURE + . = TRUE + else if(pressure == "max") + pressure = PUMP_MAX_PRESSURE + . = TRUE + else if(text2num(pressure) != null) + pressure = text2num(pressure) + . = TRUE + if(.) + target_pressure = clamp(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) + investigate_log("was set to [target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS) + if("eject") + if(holding) + replace_tank(usr, FALSE) + . = TRUE + update_icon() diff --git a/code/modules/awaymissions/bluespaceartillery.dm b/code/modules/awaymissions/bluespaceartillery.dm index 9bebcfe5462..deb05920e22 100644 --- a/code/modules/awaymissions/bluespaceartillery.dm +++ b/code/modules/awaymissions/bluespaceartillery.dm @@ -1,55 +1,55 @@ - - -/obj/machinery/artillerycontrol - var/reload = 60 - var/reload_cooldown = 60 - var/explosiondev = 3 - var/explosionmed = 6 - var/explosionlight = 12 - name = "bluespace artillery control" - icon_state = "control_boxp1" - icon = 'icons/obj/machines/particle_accelerator.dmi' - density = TRUE - -/obj/machinery/artillerycontrol/process() - if(reload < reload_cooldown) - reload++ - -/obj/structure/artilleryplaceholder - name = "artillery" - icon = 'icons/obj/machines/artillery.dmi' - anchored = TRUE - density = TRUE - -/obj/structure/artilleryplaceholder/decorative - density = FALSE - -/obj/machinery/artillerycontrol/ui_interact(mob/user) - . = ..() - var/dat = "Bluespace Artillery Control:
                " - dat += "Locked on
                " - dat += "Charge progress: [reload]/[reload_cooldown]:
                " - dat += "Open Fire
                " - dat += "Deployment of weapon authorized by
                Nanotrasen Naval Command

                Remember, friendly fire is grounds for termination of your contract and life.
                " - user << browse(dat, "window=scroll") - onclose(user, "scroll") - -/obj/machinery/artillerycontrol/Topic(href, href_list) - if(..()) - return - var/A - A = input("Area to bombard", "Open Fire", A) in GLOB.teleportlocs - var/area/thearea = GLOB.teleportlocs[A] - if(usr.stat || usr.restrained()) - return - if(reload < reload_cooldown) - return - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) - priority_announce("Bluespace artillery fire detected. Brace for impact.") - message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.") - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - L+=T - var/loc = pick(L) - explosion(loc,explosiondev,explosionmed,explosionlight) - reload = 0 + + +/obj/machinery/artillerycontrol + var/reload = 60 + var/reload_cooldown = 60 + var/explosiondev = 3 + var/explosionmed = 6 + var/explosionlight = 12 + name = "bluespace artillery control" + icon_state = "control_boxp1" + icon = 'icons/obj/machines/particle_accelerator.dmi' + density = TRUE + +/obj/machinery/artillerycontrol/process() + if(reload < reload_cooldown) + reload++ + +/obj/structure/artilleryplaceholder + name = "artillery" + icon = 'icons/obj/machines/artillery.dmi' + anchored = TRUE + density = TRUE + +/obj/structure/artilleryplaceholder/decorative + density = FALSE + +/obj/machinery/artillerycontrol/ui_interact(mob/user) + . = ..() + var/dat = "Bluespace Artillery Control:
                " + dat += "Locked on
                " + dat += "Charge progress: [reload]/[reload_cooldown]:
                " + dat += "Open Fire
                " + dat += "Deployment of weapon authorized by
                Nanotrasen Naval Command

                Remember, friendly fire is grounds for termination of your contract and life.
                " + user << browse(dat, "window=scroll") + onclose(user, "scroll") + +/obj/machinery/artillerycontrol/Topic(href, href_list) + if(..()) + return + var/A + A = input("Area to bombard", "Open Fire", A) in GLOB.teleportlocs + var/area/thearea = GLOB.teleportlocs[A] + if(usr.stat || usr.restrained()) + return + if(reload < reload_cooldown) + return + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) + priority_announce("Bluespace artillery fire detected. Brace for impact.") + message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.") + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + L+=T + var/loc = pick(L) + explosion(loc,explosiondev,explosionmed,explosionlight) + reload = 0 diff --git a/code/modules/awaymissions/exile.dm b/code/modules/awaymissions/exile.dm index 361f8169b9e..976ab184433 100644 --- a/code/modules/awaymissions/exile.dm +++ b/code/modules/awaymissions/exile.dm @@ -1,9 +1,9 @@ - -/obj/structure/closet/secure_closet/exile - name = "exile implants" - req_access = list(ACCESS_HOS) - -/obj/structure/closet/secure_closet/exile/PopulateContents() - new /obj/item/implanter/exile(src) - for(var/i in 1 to 5) - new /obj/item/implantcase/exile(src) + +/obj/structure/closet/secure_closet/exile + name = "exile implants" + req_access = list(ACCESS_HOS) + +/obj/structure/closet/secure_closet/exile/PopulateContents() + new /obj/item/implanter/exile(src) + for(var/i in 1 to 5) + new /obj/item/implantcase/exile(src) diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm index 2f7ae0f9065..6fcb5695975 100644 --- a/code/modules/awaymissions/gateway.dm +++ b/code/modules/awaymissions/gateway.dm @@ -1,327 +1,327 @@ -/// Station home gateway -GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation) -/// List of possible gateway destinations. -GLOBAL_LIST_EMPTY(gateway_destinations) - -/** - * Corresponds to single entry in gateway control. - * - * Will NOT be added automatically to GLOB.gateway_destinations list. - */ -/datum/gateway_destination - var/name = "Unknown Destination" - var/wait = 0 /// How long after roundstart this destination becomes active - var/enabled = TRUE /// If disabled, the destination won't be availible - var/hidden = FALSE /// Will not show on gateway controls at all. - -/* Can a gateway link to this destination right now. */ -/datum/gateway_destination/proc/is_availible() - return enabled && (world.time - SSticker.round_start_time >= wait) - -/* Returns user-friendly description why you can't connect to this destination, displayed in UI */ -/datum/gateway_destination/proc/get_availible_reason() - . = "Unreachable" - if(world.time - SSticker.round_start_time < wait) - . = "Connection desynchronized. Recalibration in progress." - -/* Check if the movable is allowed to arrive at this destination (exile implants mostly) */ -/datum/gateway_destination/proc/incoming_pass_check(atom/movable/AM) - return TRUE - -/* Get the actual turf we'll arrive at */ -/datum/gateway_destination/proc/get_target_turf() - CRASH("get target turf not implemented for this destination type") - -/* Called after moving the movable to target turf */ -/datum/gateway_destination/proc/post_transfer(atom/movable/AM) - if (ismob(AM)) - var/mob/M = AM - if (M.client) - M.client.move_delay = max(world.time + 5, M.client.move_delay) - -/* Called when gateway activates with this destination. */ -/datum/gateway_destination/proc/activate(obj/machinery/gateway/activated) - return - -/* Called when gateway targeting this destination deactivates. */ -/datum/gateway_destination/proc/deactivate(obj/machinery/gateway/deactivated) - return - -/* Returns data used by gateway controller ui */ -/datum/gateway_destination/proc/get_ui_data() - . = list() - .["ref"] = REF(src) - .["name"] = name - .["availible"] = is_availible() - .["reason"] = get_availible_reason() - if(wait) - .["timeout"] = max(1 - (wait - (world.time - SSticker.round_start_time)) / wait, 0) - -/* Destination is another gateway */ -/datum/gateway_destination/gateway - /// The gateway this destination points at - var/obj/machinery/gateway/target_gateway - -/* We set the target gateway target to activator gateway */ -/datum/gateway_destination/gateway/activate(obj/machinery/gateway/activated) - if(!target_gateway.target) - target_gateway.activate(activated) - -/* We turn off the target gateway if it's linked with us */ -/datum/gateway_destination/gateway/deactivate(obj/machinery/gateway/deactivated) - if(target_gateway.target == deactivated.destination) - target_gateway.deactivate() - -/datum/gateway_destination/gateway/is_availible() - return ..() && target_gateway.calibrated && !target_gateway.target && target_gateway.powered() - -/datum/gateway_destination/gateway/get_availible_reason() - . = ..() - if(!target_gateway.calibrated) - . = "Exit gateway malfunction. Manual recalibration required." - if(target_gateway.target) - . = "Exit gateway in use." - if(!target_gateway.powered()) - . = "Exit gateway unpowered." - -/datum/gateway_destination/gateway/get_target_turf() - return get_step(target_gateway.portal,SOUTH) - -/datum/gateway_destination/gateway/post_transfer(atom/movable/AM) - . = ..() - addtimer(CALLBACK(AM,/atom/movable.proc/setDir,SOUTH),0) - -/* Special home destination, so we can check exile implants */ -/datum/gateway_destination/gateway/home - -/datum/gateway_destination/gateway/home/incoming_pass_check(atom/movable/AM) - if(isliving(AM)) - if(check_exile_implant(AM)) - return FALSE - else - for(var/mob/living/L in AM.contents) - if(check_exile_implant(L)) - target_gateway.say("Rejecting [AM]: Exile implant detected in contained lifeform.") - return FALSE - if(AM.has_buckled_mobs()) - for(var/mob/living/L in AM.buckled_mobs) - if(check_exile_implant(L)) - target_gateway.say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") - return FALSE - return TRUE - -/datum/gateway_destination/gateway/home/proc/check_exile_implant(mob/living/L) - for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant - to_chat(L, "The station gate has detected your exile implant and is blocking your entry.") - return TRUE - return FALSE - - -/* Destination is one ore more turfs - created by landmarks */ -/datum/gateway_destination/point - var/list/target_turfs = list() - /// Used by away landmarks - var/id - -/datum/gateway_destination/point/get_target_turf() - return pick(target_turfs) - -/* Dense invisible object starting the teleportation. Created by gateways on activation. */ -/obj/effect/gateway_portal_bumper - var/obj/machinery/gateway/gateway - density = TRUE - invisibility = INVISIBILITY_ABSTRACT - -/obj/effect/gateway_portal_bumper/Bumped(atom/movable/AM) - if(get_dir(src,AM) == SOUTH) - gateway.Transfer(AM) - -/obj/effect/gateway_portal_bumper/Destroy(force) - . = ..() - gateway = null - -/obj/machinery/gateway - name = "gateway" - desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations." - icon = 'icons/obj/machines/gateway.dmi' - icon_state = "off" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - - // 3x2 offset by one row - pixel_x = -32 - pixel_y = -32 - bound_height = 64 - bound_width = 96 - bound_x = -32 - bound_y = 0 - density = TRUE - - use_power = IDLE_POWER_USE - idle_power_usage = 100 - active_power_usage = 5000 - - var/calibrated = TRUE - /// Type of instanced gateway destination, needs to be subtype of /datum/gateway_destination/gateway - var/destination_type = /datum/gateway_destination/gateway - /// Name of the generated destination - var/destination_name = "Unknown Gateway" - /// This is our own destination, pointing at this gateway - var/datum/gateway_destination/gateway/destination - /// This is current active destination - var/datum/gateway_destination/target - /// bumper object, the thing that starts actual teleport - var/obj/effect/gateway_portal_bumper/portal - -/obj/machinery/gateway/Initialize() - generate_destination() - update_icon() - return ..() - -/obj/machinery/gateway/proc/generate_destination() - destination = new destination_type - destination.name = destination_name - destination.target_gateway = src - GLOB.gateway_destinations += destination - -/obj/machinery/gateway/proc/deactivate() - var/datum/gateway_destination/dest = target - target = null - dest.deactivate(src) - QDEL_NULL(portal) - use_power = IDLE_POWER_USE - update_icon() - -/obj/machinery/gateway/process() - if((machine_stat & (NOPOWER)) && use_power) - if(target) - deactivate() - return - -/obj/machinery/gateway/update_icon_state() - if(target) - icon_state = "on" - else - icon_state = "off" - -/obj/machinery/gateway/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) - return - -/obj/machinery/gateway/proc/generate_bumper() - portal = new(get_turf(src)) - portal.gateway = src - -/obj/machinery/gateway/proc/activate(datum/gateway_destination/D) - if(!powered() || target) - return - target = D - target.activate(destination) - generate_bumper() - use_power = ACTIVE_POWER_USE - update_icon() - -/obj/machinery/gateway/proc/Transfer(atom/movable/AM) - if(!target || !target.incoming_pass_check(AM)) - return - AM.forceMove(target.get_target_turf()) - target.post_transfer(AM) - -/* Station's primary gateway */ -/obj/machinery/gateway/centerstation - destination_type = /datum/gateway_destination/gateway/home - destination_name = "Home Gateway" - -/obj/machinery/gateway/centerstation/Initialize() - . = ..() - if(!GLOB.the_gateway) - GLOB.the_gateway = src - -/obj/machinery/gateway/centerstation/Destroy() - if(GLOB.the_gateway == src) - GLOB.the_gateway = null - return ..() - -/obj/machinery/gateway/multitool_act(mob/living/user, obj/item/I) - if(calibrated) - to_chat(user, "The gate is already calibrated, there is no work for you to do here.") - else - to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") - calibrated = TRUE - return TRUE - -/* Doesn't need control console or power, always links to home when interacting. */ -/obj/machinery/gateway/away - density = TRUE - use_power = NO_POWER_USE - -/obj/machinery/gateway/away/interact(mob/user, special_state) - . = ..() - if(!target) - if(!GLOB.the_gateway) - to_chat(user,"Home gateway is not responding!") - if(GLOB.the_gateway.target) - to_chat(user,"Home gateway already in use!") - return - activate(GLOB.the_gateway.destination) - else - deactivate() - -/* Gateway control computer */ -/obj/machinery/computer/gateway_control - name = "Gateway Control" - desc = "Human friendly interface to the mysterious gate next to it." - var/obj/machinery/gateway/G - -/obj/machinery/computer/gateway_control/Initialize(mapload, obj/item/circuitboard/C) - . = ..() - try_to_linkup() - -/obj/machinery/computer/gateway_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state = GLOB.default_state) - . = ..() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Gateway", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/gateway_control/ui_data(mob/user) - . = ..() - .["gateway_present"] = G - .["gateway_status"] = G ? G.powered() : FALSE - .["current_target"] = G?.target?.get_ui_data() - var/list/destinations = list() - if(G) - for(var/datum/gateway_destination/D in GLOB.gateway_destinations) - if(D == G.destination) - continue - destinations += list(D.get_ui_data()) - .["destinations"] = destinations - -/obj/machinery/computer/gateway_control/ui_act(action, list/params) - . = ..() - if(.) - return - switch(action) - if("linkup") - try_to_linkup() - return TRUE - if("activate") - var/datum/gateway_destination/D = locate(params["destination"]) in GLOB.gateway_destinations - try_to_connect(D) - return TRUE - if("deactivate") - if(G && G.target) - G.deactivate() - return TRUE - -/obj/machinery/computer/gateway_control/proc/try_to_linkup() - G = locate(/obj/machinery/gateway) in view(7,get_turf(src)) - -/obj/machinery/computer/gateway_control/proc/try_to_connect(datum/gateway_destination/D) - if(!D || !G) - return - if(!D.is_availible() || G.target) - return - G.activate(D) - -/obj/item/paper/fluff/gateway - info = "Congratulations,

                Your station has been selected to carry out the Gateway Project.

                The equipment will be shipped to you at the start of the next quarter.
                You are to prepare a secure location to house the equipment as outlined in the attached documents.

                --Nanotrasen Bluespace Research" - name = "Confidential Correspondence, Pg 1" +/// Station home gateway +GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation) +/// List of possible gateway destinations. +GLOBAL_LIST_EMPTY(gateway_destinations) + +/** + * Corresponds to single entry in gateway control. + * + * Will NOT be added automatically to GLOB.gateway_destinations list. + */ +/datum/gateway_destination + var/name = "Unknown Destination" + var/wait = 0 /// How long after roundstart this destination becomes active + var/enabled = TRUE /// If disabled, the destination won't be availible + var/hidden = FALSE /// Will not show on gateway controls at all. + +/* Can a gateway link to this destination right now. */ +/datum/gateway_destination/proc/is_availible() + return enabled && (world.time - SSticker.round_start_time >= wait) + +/* Returns user-friendly description why you can't connect to this destination, displayed in UI */ +/datum/gateway_destination/proc/get_availible_reason() + . = "Unreachable" + if(world.time - SSticker.round_start_time < wait) + . = "Connection desynchronized. Recalibration in progress." + +/* Check if the movable is allowed to arrive at this destination (exile implants mostly) */ +/datum/gateway_destination/proc/incoming_pass_check(atom/movable/AM) + return TRUE + +/* Get the actual turf we'll arrive at */ +/datum/gateway_destination/proc/get_target_turf() + CRASH("get target turf not implemented for this destination type") + +/* Called after moving the movable to target turf */ +/datum/gateway_destination/proc/post_transfer(atom/movable/AM) + if (ismob(AM)) + var/mob/M = AM + if (M.client) + M.client.move_delay = max(world.time + 5, M.client.move_delay) + +/* Called when gateway activates with this destination. */ +/datum/gateway_destination/proc/activate(obj/machinery/gateway/activated) + return + +/* Called when gateway targeting this destination deactivates. */ +/datum/gateway_destination/proc/deactivate(obj/machinery/gateway/deactivated) + return + +/* Returns data used by gateway controller ui */ +/datum/gateway_destination/proc/get_ui_data() + . = list() + .["ref"] = REF(src) + .["name"] = name + .["availible"] = is_availible() + .["reason"] = get_availible_reason() + if(wait) + .["timeout"] = max(1 - (wait - (world.time - SSticker.round_start_time)) / wait, 0) + +/* Destination is another gateway */ +/datum/gateway_destination/gateway + /// The gateway this destination points at + var/obj/machinery/gateway/target_gateway + +/* We set the target gateway target to activator gateway */ +/datum/gateway_destination/gateway/activate(obj/machinery/gateway/activated) + if(!target_gateway.target) + target_gateway.activate(activated) + +/* We turn off the target gateway if it's linked with us */ +/datum/gateway_destination/gateway/deactivate(obj/machinery/gateway/deactivated) + if(target_gateway.target == deactivated.destination) + target_gateway.deactivate() + +/datum/gateway_destination/gateway/is_availible() + return ..() && target_gateway.calibrated && !target_gateway.target && target_gateway.powered() + +/datum/gateway_destination/gateway/get_availible_reason() + . = ..() + if(!target_gateway.calibrated) + . = "Exit gateway malfunction. Manual recalibration required." + if(target_gateway.target) + . = "Exit gateway in use." + if(!target_gateway.powered()) + . = "Exit gateway unpowered." + +/datum/gateway_destination/gateway/get_target_turf() + return get_step(target_gateway.portal,SOUTH) + +/datum/gateway_destination/gateway/post_transfer(atom/movable/AM) + . = ..() + addtimer(CALLBACK(AM,/atom/movable.proc/setDir,SOUTH),0) + +/* Special home destination, so we can check exile implants */ +/datum/gateway_destination/gateway/home + +/datum/gateway_destination/gateway/home/incoming_pass_check(atom/movable/AM) + if(isliving(AM)) + if(check_exile_implant(AM)) + return FALSE + else + for(var/mob/living/L in AM.contents) + if(check_exile_implant(L)) + target_gateway.say("Rejecting [AM]: Exile implant detected in contained lifeform.") + return FALSE + if(AM.has_buckled_mobs()) + for(var/mob/living/L in AM.buckled_mobs) + if(check_exile_implant(L)) + target_gateway.say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") + return FALSE + return TRUE + +/datum/gateway_destination/gateway/home/proc/check_exile_implant(mob/living/L) + for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant + to_chat(L, "The station gate has detected your exile implant and is blocking your entry.") + return TRUE + return FALSE + + +/* Destination is one ore more turfs - created by landmarks */ +/datum/gateway_destination/point + var/list/target_turfs = list() + /// Used by away landmarks + var/id + +/datum/gateway_destination/point/get_target_turf() + return pick(target_turfs) + +/* Dense invisible object starting the teleportation. Created by gateways on activation. */ +/obj/effect/gateway_portal_bumper + var/obj/machinery/gateway/gateway + density = TRUE + invisibility = INVISIBILITY_ABSTRACT + +/obj/effect/gateway_portal_bumper/Bumped(atom/movable/AM) + if(get_dir(src,AM) == SOUTH) + gateway.Transfer(AM) + +/obj/effect/gateway_portal_bumper/Destroy(force) + . = ..() + gateway = null + +/obj/machinery/gateway + name = "gateway" + desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations." + icon = 'icons/obj/machines/gateway.dmi' + icon_state = "off" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + // 3x2 offset by one row + pixel_x = -32 + pixel_y = -32 + bound_height = 64 + bound_width = 96 + bound_x = -32 + bound_y = 0 + density = TRUE + + use_power = IDLE_POWER_USE + idle_power_usage = 100 + active_power_usage = 5000 + + var/calibrated = TRUE + /// Type of instanced gateway destination, needs to be subtype of /datum/gateway_destination/gateway + var/destination_type = /datum/gateway_destination/gateway + /// Name of the generated destination + var/destination_name = "Unknown Gateway" + /// This is our own destination, pointing at this gateway + var/datum/gateway_destination/gateway/destination + /// This is current active destination + var/datum/gateway_destination/target + /// bumper object, the thing that starts actual teleport + var/obj/effect/gateway_portal_bumper/portal + +/obj/machinery/gateway/Initialize() + generate_destination() + update_icon() + return ..() + +/obj/machinery/gateway/proc/generate_destination() + destination = new destination_type + destination.name = destination_name + destination.target_gateway = src + GLOB.gateway_destinations += destination + +/obj/machinery/gateway/proc/deactivate() + var/datum/gateway_destination/dest = target + target = null + dest.deactivate(src) + QDEL_NULL(portal) + use_power = IDLE_POWER_USE + update_icon() + +/obj/machinery/gateway/process() + if((machine_stat & (NOPOWER)) && use_power) + if(target) + deactivate() + return + +/obj/machinery/gateway/update_icon_state() + if(target) + icon_state = "on" + else + icon_state = "off" + +/obj/machinery/gateway/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) + return + +/obj/machinery/gateway/proc/generate_bumper() + portal = new(get_turf(src)) + portal.gateway = src + +/obj/machinery/gateway/proc/activate(datum/gateway_destination/D) + if(!powered() || target) + return + target = D + target.activate(destination) + generate_bumper() + use_power = ACTIVE_POWER_USE + update_icon() + +/obj/machinery/gateway/proc/Transfer(atom/movable/AM) + if(!target || !target.incoming_pass_check(AM)) + return + AM.forceMove(target.get_target_turf()) + target.post_transfer(AM) + +/* Station's primary gateway */ +/obj/machinery/gateway/centerstation + destination_type = /datum/gateway_destination/gateway/home + destination_name = "Home Gateway" + +/obj/machinery/gateway/centerstation/Initialize() + . = ..() + if(!GLOB.the_gateway) + GLOB.the_gateway = src + +/obj/machinery/gateway/centerstation/Destroy() + if(GLOB.the_gateway == src) + GLOB.the_gateway = null + return ..() + +/obj/machinery/gateway/multitool_act(mob/living/user, obj/item/I) + if(calibrated) + to_chat(user, "The gate is already calibrated, there is no work for you to do here.") + else + to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") + calibrated = TRUE + return TRUE + +/* Doesn't need control console or power, always links to home when interacting. */ +/obj/machinery/gateway/away + density = TRUE + use_power = NO_POWER_USE + +/obj/machinery/gateway/away/interact(mob/user, special_state) + . = ..() + if(!target) + if(!GLOB.the_gateway) + to_chat(user,"Home gateway is not responding!") + if(GLOB.the_gateway.target) + to_chat(user,"Home gateway already in use!") + return + activate(GLOB.the_gateway.destination) + else + deactivate() + +/* Gateway control computer */ +/obj/machinery/computer/gateway_control + name = "Gateway Control" + desc = "Human friendly interface to the mysterious gate next to it." + var/obj/machinery/gateway/G + +/obj/machinery/computer/gateway_control/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + try_to_linkup() + +/obj/machinery/computer/gateway_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state = GLOB.default_state) + . = ..() + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Gateway", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/gateway_control/ui_data(mob/user) + . = ..() + .["gateway_present"] = G + .["gateway_status"] = G ? G.powered() : FALSE + .["current_target"] = G?.target?.get_ui_data() + var/list/destinations = list() + if(G) + for(var/datum/gateway_destination/D in GLOB.gateway_destinations) + if(D == G.destination) + continue + destinations += list(D.get_ui_data()) + .["destinations"] = destinations + +/obj/machinery/computer/gateway_control/ui_act(action, list/params) + . = ..() + if(.) + return + switch(action) + if("linkup") + try_to_linkup() + return TRUE + if("activate") + var/datum/gateway_destination/D = locate(params["destination"]) in GLOB.gateway_destinations + try_to_connect(D) + return TRUE + if("deactivate") + if(G && G.target) + G.deactivate() + return TRUE + +/obj/machinery/computer/gateway_control/proc/try_to_linkup() + G = locate(/obj/machinery/gateway) in view(7,get_turf(src)) + +/obj/machinery/computer/gateway_control/proc/try_to_connect(datum/gateway_destination/D) + if(!D || !G) + return + if(!D.is_availible() || G.target) + return + G.activate(D) + +/obj/item/paper/fluff/gateway + info = "Congratulations,

                Your station has been selected to carry out the Gateway Project.

                The equipment will be shipped to you at the start of the next quarter.
                You are to prepare a secure location to house the equipment as outlined in the attached documents.

                --Nanotrasen Bluespace Research" + name = "Confidential Correspondence, Pg 1" diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm index 60afed5bb0d..5a35f3d5756 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -1,401 +1,401 @@ - -//Academy Areas - -/area/awaymission/academy - name = "Academy Asteroids" - icon_state = "away" - -/area/awaymission/academy/headmaster - name = "Academy Fore Block" - icon_state = "away1" - -/area/awaymission/academy/classrooms - name = "Academy Classroom Block" - icon_state = "away2" - -/area/awaymission/academy/academyaft - name = "Academy Ship Aft Block" - icon_state = "away3" - -/area/awaymission/academy/academygate - name = "Academy Gateway" - icon_state = "away4" - -/area/awaymission/academy/academycellar - name = "Academy Cellar" - icon_state = "away4" - -/area/awaymission/academy/academyengine - name = "Academy Engine" - icon_state = "away4" - -//Academy Items - -/obj/item/paper/fluff/awaymissions/academy/console_maint - name = "Console Maintenance" - info = "We're upgrading to the latest mainframes for our consoles, the shipment should be in before spring break is over!" - -/obj/item/paper/fluff/awaymissions/academy/class/automotive - name = "Automotive Repair 101" - -/obj/item/paper/fluff/awaymissions/academy/class/pyromancy - name = "Pyromancy 250" - -/obj/item/paper/fluff/awaymissions/academy/class/biology - name = "Biology Lab" - -/obj/item/paper/fluff/awaymissions/academy/grade/aplus - name = "Summoning Midterm Exam" - info = "Grade: A+ Educator's Notes: Excellent form." - -/obj/item/paper/fluff/awaymissions/academy/grade/bminus - name = "Summoning Midterm Exam" - info = "Grade: B- Educator's Notes: Keep applying yourself, you're showing improvement." - -/obj/item/paper/fluff/awaymissions/academy/grade/dminus - name = "Summoning Midterm Exam" - info = "Grade: D- Educator's Notes: SEE ME AFTER CLASS." - -/obj/item/paper/fluff/awaymissions/academy/grade/failure - name = "Pyromancy Evaluation" - info = "Current Grade: F. Educator's Notes: No improvement shown despite multiple private lessons. Suggest additional tutelage." - - -/obj/singularity/academy - dissipate = 0 - move_self = 0 - grav_pull = 1 - -/obj/singularity/academy/admin_investigate_setup() - return - -/obj/singularity/academy/process() - eat() - if(prob(1)) - mezzer() - - -/obj/item/clothing/glasses/meson/truesight - name = "The Lens of Truesight" - desc = "I can see forever!" - icon_state = "monocle" - inhand_icon_state = "headset" - - -/obj/structure/academy_wizard_spawner - name = "Academy Defensive System" - desc = "Made by Abjuration, Inc." - icon = 'icons/obj/cult.dmi' - icon_state = "forge" - anchored = TRUE - max_integrity = 200 - var/mob/living/current_wizard = null - var/next_check = 0 - var/cooldown = 600 - var/faction = ROLE_WIZARD - var/braindead_check = 0 - -/obj/structure/academy_wizard_spawner/New() - START_PROCESSING(SSobj, src) - -/obj/structure/academy_wizard_spawner/Destroy() - if(!broken) - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/structure/academy_wizard_spawner/process() - if(next_check < world.time) - if(!current_wizard) - for(var/mob/living/L in GLOB.player_list) - if(L.z == src.z && L.stat != DEAD && !(faction in L.faction)) - summon_wizard() - break - else - if(current_wizard.stat == DEAD) - current_wizard = null - summon_wizard() - if(!current_wizard.client) - if(!braindead_check) - braindead_check = 1 - else - braindead_check = 0 - give_control() - next_check = world.time + cooldown - -/obj/structure/academy_wizard_spawner/proc/give_control() - set waitfor = FALSE - - if(!current_wizard) - return - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as Wizard Academy Defender?", ROLE_WIZARD, null, ROLE_WIZARD, 50, current_wizard, POLL_IGNORE_ACADEMY_WIZARD) - - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender") - current_wizard.ghostize() // on the off chance braindead defender gets back in - current_wizard.key = C.key - -/obj/structure/academy_wizard_spawner/proc/summon_wizard() - var/turf/T = src.loc - var/mob/living/carbon/human/wizbody = new(T) - wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher") - wizbody.mind_initialize() - var/datum/mind/wizmind = wizbody.mind - wizmind.special_role = "Academy Defender" - wizmind.add_antag_datum(/datum/antagonist/wizard/academy) - current_wizard = wizbody - - give_control() - -/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE) - if(!broken) - broken = 1 - visible_message("[src] breaks down!") - icon_state = "forge_off" - STOP_PROCESSING(SSobj, src) - -/datum/outfit/wizard/academy - name = "Academy Wizard" - r_pocket = null - r_hand = null - suit = /obj/item/clothing/suit/wizrobe/red - head = /obj/item/clothing/head/wizard/red - backpack_contents = list(/obj/item/storage/box/survival = 1) - -/obj/item/dice/d20/fate - name = "\improper Die of Fate" - desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky." - icon_state = "d20" - sides = 20 - microwave_riggable = FALSE - var/reusable = TRUE - var/used = FALSE - -/obj/item/dice/d20/fate/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/cursed - name = "cursed Die of Fate" - desc = "A die with twenty sides. You feel that rolling this is a REALLY bad idea." - color = "#00BB00" - - rigged = DICE_TOTALLY_RIGGED - rigged_value = 1 - -/obj/item/dice/d20/fate/cursed/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/stealth - name = "d20" - desc = "A die with twenty sides. The preferred die to throw at the GM." - -/obj/item/dice/d20/fate/stealth/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/stealth/cursed - rigged = DICE_TOTALLY_RIGGED - rigged_value = 1 - -/obj/item/dice/d20/fate/stealth/cursed/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/diceroll(mob/user) - . = ..() - if(!used) - if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) - to_chat(user, "You feel the magic of the dice is restricted to ordinary humans!") - return - - if(!reusable) - used = TRUE - - var/turf/T = get_turf(src) - T.visible_message("[src] flares briefly.") - - addtimer(CALLBACK(src, .proc/effect, user, .), 1 SECONDS) - -/obj/item/dice/d20/fate/equipped(mob/user, slot) - . = ..() - if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) - to_chat(user, "You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.") - user.dropItemToGround(src) - - -/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll) - var/turf/T = get_turf(src) - switch(roll) - if(1) - //Dust - T.visible_message("[user] turns to dust!") - user.hellbound = TRUE - user.dust() - if(2) - //Death - T.visible_message("[user] suddenly dies!") - user.death() - if(3) - //Swarm of creatures - T.visible_message("A swarm of creatures surrounds [user]!") - for(var/direction in GLOB.alldirs) - new /mob/living/simple_animal/hostile/netherworld(get_step(get_turf(user),direction)) - if(4) - //Destroy Equipment - T.visible_message("Everything [user] is holding and wearing disappears!") - for(var/obj/item/I in user) - if(istype(I, /obj/item/implant)) - continue - qdel(I) - if(5) - //Monkeying - T.visible_message("[user] transforms into a monkey!") - user.monkeyize() - if(6) - //Cut speed - T.visible_message("[user] starts moving slower!") - user.add_movespeed_modifier(/datum/movespeed_modifier/die_of_fate) - if(7) - //Throw - T.visible_message("Unseen forces throw [user]!") - user.Stun(60) - user.adjustBruteLoss(50) - var/throw_dir = pick(GLOB.cardinals) - var/atom/throw_target = get_edge_target_turf(user, throw_dir) - user.throw_at(throw_target, 200, 4) - if(8) - //Fuel tank Explosion - T.visible_message("An explosion bursts into existence around [user]!") - explosion(get_turf(user),-1,0,2, flame_range = 2) - if(9) - //Cold - var/datum/disease/D = new /datum/disease/cold() - T.visible_message("[user] looks a little under the weather!") - user.ForceContractDisease(D, FALSE, TRUE) - if(10) - //Nothing - T.visible_message("Nothing seems to happen.") - if(11) - //Cookie - T.visible_message("A cookie appears out of thin air!") - var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) - do_smoke(0, drop_location()) - C.name = "Cookie of Fate" - if(12) - //Healing - T.visible_message("[user] looks very healthy!") - user.revive(full_heal = TRUE, admin_revive = TRUE) - if(13) - //Mad Dosh - T.visible_message("Mad dosh shoots out of [src]!") - var/turf/Start = get_turf(src) - for(var/direction in GLOB.alldirs) - var/turf/dirturf = get_step(Start,direction) - if(rand(0,1)) - new /obj/item/stack/spacecash/c1000(dirturf) - else - var/obj/item/storage/bag/money/M = new(dirturf) - for(var/i in 1 to rand(5,50)) - new /obj/item/coin/gold(M) - if(14) - //Free Gun - T.visible_message("An impressive gun appears!") - do_smoke(0, drop_location()) - new /obj/item/gun/ballistic/revolver/mateba(drop_location()) - if(15) - //Random One-use spellbook - T.visible_message("A magical looking book drops to the floor!") - do_smoke(0, drop_location()) - new /obj/item/book/granter/spell/random(drop_location()) - if(16) - //Servant & Servant Summon - T.visible_message("A Dice Servant appears in a cloud of smoke!") - var/mob/living/carbon/human/H = new(drop_location()) - do_smoke(0, drop_location()) - - H.equipOutfit(/datum/outfit/butler) - var/datum/mind/servant_mind = new /datum/mind() - var/datum/antagonist/magic_servant/A = new - servant_mind.add_antag_datum(A) - A.setup_master(user) - servant_mind.transfer_to(H) - - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") - H.key = C.key - - var/obj/effect/proc_holder/spell/targeted/summonmob/S = new - S.target_mob = H - user.mind.AddSpell(S) - - if(17) - //Tator Kit - T.visible_message("A suspicious box appears!") - new /obj/item/storage/box/syndicate/bundle_a(drop_location()) - do_smoke(0, drop_location()) - if(18) - //Captain ID - T.visible_message("A golden identification card appears!") - new /obj/item/card/id/captains_spare(drop_location()) - do_smoke(0, drop_location()) - if(19) - //Instrinct Resistance - T.visible_message("[user] looks very robust!") - user.physiology.brute_mod *= 0.5 - user.physiology.burn_mod *= 0.5 - - if(20) - //Free wizard! - T.visible_message("Magic flows out of [src] and into [user]!") - user.mind.make_Wizard() - -/datum/outfit/butler - name = "Butler" - uniform = /obj/item/clothing/under/suit/black_really - shoes = /obj/item/clothing/shoes/laceup - head = /obj/item/clothing/head/bowler - glasses = /obj/item/clothing/glasses/monocle - gloves = /obj/item/clothing/gloves/color/white - -/obj/effect/proc_holder/spell/targeted/summonmob - name = "Summon Servant" - desc = "This spell can be used to call your servant, whenever you need it." - charge_max = 100 - clothes_req = 0 - invocation = "JE VES" - invocation_type = INVOCATION_WHISPER - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = 1 - - var/mob/living/target_mob - - action_icon_state = "summons" - -/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr) - if(!target_mob) - return - var/turf/Start = get_turf(user) - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(Start,direction) - if(!T.density) - target_mob.Move(T) - -/obj/structure/ladder/unbreakable/rune - name = "\improper Teleportation Rune" - desc = "Could lead anywhere." - icon = 'icons/obj/rune.dmi' - icon_state = "1" - color = rgb(0,0,255) - -/obj/structure/ladder/unbreakable/rune/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user) - user.visible_message("[user] activates \the [src].", "You activate \the [src].") - -/obj/structure/ladder/unbreakable/rune/use(mob/user, is_ghost=FALSE) - if(is_ghost || !(user.mind in SSticker.mode.wizards)) - ..() + +//Academy Areas + +/area/awaymission/academy + name = "Academy Asteroids" + icon_state = "away" + +/area/awaymission/academy/headmaster + name = "Academy Fore Block" + icon_state = "away1" + +/area/awaymission/academy/classrooms + name = "Academy Classroom Block" + icon_state = "away2" + +/area/awaymission/academy/academyaft + name = "Academy Ship Aft Block" + icon_state = "away3" + +/area/awaymission/academy/academygate + name = "Academy Gateway" + icon_state = "away4" + +/area/awaymission/academy/academycellar + name = "Academy Cellar" + icon_state = "away4" + +/area/awaymission/academy/academyengine + name = "Academy Engine" + icon_state = "away4" + +//Academy Items + +/obj/item/paper/fluff/awaymissions/academy/console_maint + name = "Console Maintenance" + info = "We're upgrading to the latest mainframes for our consoles, the shipment should be in before spring break is over!" + +/obj/item/paper/fluff/awaymissions/academy/class/automotive + name = "Automotive Repair 101" + +/obj/item/paper/fluff/awaymissions/academy/class/pyromancy + name = "Pyromancy 250" + +/obj/item/paper/fluff/awaymissions/academy/class/biology + name = "Biology Lab" + +/obj/item/paper/fluff/awaymissions/academy/grade/aplus + name = "Summoning Midterm Exam" + info = "Grade: A+ Educator's Notes: Excellent form." + +/obj/item/paper/fluff/awaymissions/academy/grade/bminus + name = "Summoning Midterm Exam" + info = "Grade: B- Educator's Notes: Keep applying yourself, you're showing improvement." + +/obj/item/paper/fluff/awaymissions/academy/grade/dminus + name = "Summoning Midterm Exam" + info = "Grade: D- Educator's Notes: SEE ME AFTER CLASS." + +/obj/item/paper/fluff/awaymissions/academy/grade/failure + name = "Pyromancy Evaluation" + info = "Current Grade: F. Educator's Notes: No improvement shown despite multiple private lessons. Suggest additional tutelage." + + +/obj/singularity/academy + dissipate = 0 + move_self = 0 + grav_pull = 1 + +/obj/singularity/academy/admin_investigate_setup() + return + +/obj/singularity/academy/process() + eat() + if(prob(1)) + mezzer() + + +/obj/item/clothing/glasses/meson/truesight + name = "The Lens of Truesight" + desc = "I can see forever!" + icon_state = "monocle" + inhand_icon_state = "headset" + + +/obj/structure/academy_wizard_spawner + name = "Academy Defensive System" + desc = "Made by Abjuration, Inc." + icon = 'icons/obj/cult.dmi' + icon_state = "forge" + anchored = TRUE + max_integrity = 200 + var/mob/living/current_wizard = null + var/next_check = 0 + var/cooldown = 600 + var/faction = ROLE_WIZARD + var/braindead_check = 0 + +/obj/structure/academy_wizard_spawner/New() + START_PROCESSING(SSobj, src) + +/obj/structure/academy_wizard_spawner/Destroy() + if(!broken) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/structure/academy_wizard_spawner/process() + if(next_check < world.time) + if(!current_wizard) + for(var/mob/living/L in GLOB.player_list) + if(L.z == src.z && L.stat != DEAD && !(faction in L.faction)) + summon_wizard() + break + else + if(current_wizard.stat == DEAD) + current_wizard = null + summon_wizard() + if(!current_wizard.client) + if(!braindead_check) + braindead_check = 1 + else + braindead_check = 0 + give_control() + next_check = world.time + cooldown + +/obj/structure/academy_wizard_spawner/proc/give_control() + set waitfor = FALSE + + if(!current_wizard) + return + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as Wizard Academy Defender?", ROLE_WIZARD, null, ROLE_WIZARD, 50, current_wizard, POLL_IGNORE_ACADEMY_WIZARD) + + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender") + current_wizard.ghostize() // on the off chance braindead defender gets back in + current_wizard.key = C.key + +/obj/structure/academy_wizard_spawner/proc/summon_wizard() + var/turf/T = src.loc + var/mob/living/carbon/human/wizbody = new(T) + wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher") + wizbody.mind_initialize() + var/datum/mind/wizmind = wizbody.mind + wizmind.special_role = "Academy Defender" + wizmind.add_antag_datum(/datum/antagonist/wizard/academy) + current_wizard = wizbody + + give_control() + +/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE) + if(!broken) + broken = 1 + visible_message("[src] breaks down!") + icon_state = "forge_off" + STOP_PROCESSING(SSobj, src) + +/datum/outfit/wizard/academy + name = "Academy Wizard" + r_pocket = null + r_hand = null + suit = /obj/item/clothing/suit/wizrobe/red + head = /obj/item/clothing/head/wizard/red + backpack_contents = list(/obj/item/storage/box/survival = 1) + +/obj/item/dice/d20/fate + name = "\improper Die of Fate" + desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky." + icon_state = "d20" + sides = 20 + microwave_riggable = FALSE + var/reusable = TRUE + var/used = FALSE + +/obj/item/dice/d20/fate/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/cursed + name = "cursed Die of Fate" + desc = "A die with twenty sides. You feel that rolling this is a REALLY bad idea." + color = "#00BB00" + + rigged = DICE_TOTALLY_RIGGED + rigged_value = 1 + +/obj/item/dice/d20/fate/cursed/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/stealth + name = "d20" + desc = "A die with twenty sides. The preferred die to throw at the GM." + +/obj/item/dice/d20/fate/stealth/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/stealth/cursed + rigged = DICE_TOTALLY_RIGGED + rigged_value = 1 + +/obj/item/dice/d20/fate/stealth/cursed/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/diceroll(mob/user) + . = ..() + if(!used) + if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) + to_chat(user, "You feel the magic of the dice is restricted to ordinary humans!") + return + + if(!reusable) + used = TRUE + + var/turf/T = get_turf(src) + T.visible_message("[src] flares briefly.") + + addtimer(CALLBACK(src, .proc/effect, user, .), 1 SECONDS) + +/obj/item/dice/d20/fate/equipped(mob/user, slot) + . = ..() + if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) + to_chat(user, "You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.") + user.dropItemToGround(src) + + +/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll) + var/turf/T = get_turf(src) + switch(roll) + if(1) + //Dust + T.visible_message("[user] turns to dust!") + user.hellbound = TRUE + user.dust() + if(2) + //Death + T.visible_message("[user] suddenly dies!") + user.death() + if(3) + //Swarm of creatures + T.visible_message("A swarm of creatures surrounds [user]!") + for(var/direction in GLOB.alldirs) + new /mob/living/simple_animal/hostile/netherworld(get_step(get_turf(user),direction)) + if(4) + //Destroy Equipment + T.visible_message("Everything [user] is holding and wearing disappears!") + for(var/obj/item/I in user) + if(istype(I, /obj/item/implant)) + continue + qdel(I) + if(5) + //Monkeying + T.visible_message("[user] transforms into a monkey!") + user.monkeyize() + if(6) + //Cut speed + T.visible_message("[user] starts moving slower!") + user.add_movespeed_modifier(/datum/movespeed_modifier/die_of_fate) + if(7) + //Throw + T.visible_message("Unseen forces throw [user]!") + user.Stun(60) + user.adjustBruteLoss(50) + var/throw_dir = pick(GLOB.cardinals) + var/atom/throw_target = get_edge_target_turf(user, throw_dir) + user.throw_at(throw_target, 200, 4) + if(8) + //Fuel tank Explosion + T.visible_message("An explosion bursts into existence around [user]!") + explosion(get_turf(user),-1,0,2, flame_range = 2) + if(9) + //Cold + var/datum/disease/D = new /datum/disease/cold() + T.visible_message("[user] looks a little under the weather!") + user.ForceContractDisease(D, FALSE, TRUE) + if(10) + //Nothing + T.visible_message("Nothing seems to happen.") + if(11) + //Cookie + T.visible_message("A cookie appears out of thin air!") + var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) + do_smoke(0, drop_location()) + C.name = "Cookie of Fate" + if(12) + //Healing + T.visible_message("[user] looks very healthy!") + user.revive(full_heal = TRUE, admin_revive = TRUE) + if(13) + //Mad Dosh + T.visible_message("Mad dosh shoots out of [src]!") + var/turf/Start = get_turf(src) + for(var/direction in GLOB.alldirs) + var/turf/dirturf = get_step(Start,direction) + if(rand(0,1)) + new /obj/item/stack/spacecash/c1000(dirturf) + else + var/obj/item/storage/bag/money/M = new(dirturf) + for(var/i in 1 to rand(5,50)) + new /obj/item/coin/gold(M) + if(14) + //Free Gun + T.visible_message("An impressive gun appears!") + do_smoke(0, drop_location()) + new /obj/item/gun/ballistic/revolver/mateba(drop_location()) + if(15) + //Random One-use spellbook + T.visible_message("A magical looking book drops to the floor!") + do_smoke(0, drop_location()) + new /obj/item/book/granter/spell/random(drop_location()) + if(16) + //Servant & Servant Summon + T.visible_message("A Dice Servant appears in a cloud of smoke!") + var/mob/living/carbon/human/H = new(drop_location()) + do_smoke(0, drop_location()) + + H.equipOutfit(/datum/outfit/butler) + var/datum/mind/servant_mind = new /datum/mind() + var/datum/antagonist/magic_servant/A = new + servant_mind.add_antag_datum(A) + A.setup_master(user) + servant_mind.transfer_to(H) + + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H) + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") + H.key = C.key + + var/obj/effect/proc_holder/spell/targeted/summonmob/S = new + S.target_mob = H + user.mind.AddSpell(S) + + if(17) + //Tator Kit + T.visible_message("A suspicious box appears!") + new /obj/item/storage/box/syndicate/bundle_a(drop_location()) + do_smoke(0, drop_location()) + if(18) + //Captain ID + T.visible_message("A golden identification card appears!") + new /obj/item/card/id/captains_spare(drop_location()) + do_smoke(0, drop_location()) + if(19) + //Instrinct Resistance + T.visible_message("[user] looks very robust!") + user.physiology.brute_mod *= 0.5 + user.physiology.burn_mod *= 0.5 + + if(20) + //Free wizard! + T.visible_message("Magic flows out of [src] and into [user]!") + user.mind.make_Wizard() + +/datum/outfit/butler + name = "Butler" + uniform = /obj/item/clothing/under/suit/black_really + shoes = /obj/item/clothing/shoes/laceup + head = /obj/item/clothing/head/bowler + glasses = /obj/item/clothing/glasses/monocle + gloves = /obj/item/clothing/gloves/color/white + +/obj/effect/proc_holder/spell/targeted/summonmob + name = "Summon Servant" + desc = "This spell can be used to call your servant, whenever you need it." + charge_max = 100 + clothes_req = 0 + invocation = "JE VES" + invocation_type = INVOCATION_WHISPER + range = -1 + level_max = 0 //cannot be improved + cooldown_min = 100 + include_user = 1 + + var/mob/living/target_mob + + action_icon_state = "summons" + +/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr) + if(!target_mob) + return + var/turf/Start = get_turf(user) + for(var/direction in GLOB.alldirs) + var/turf/T = get_step(Start,direction) + if(!T.density) + target_mob.Move(T) + +/obj/structure/ladder/unbreakable/rune + name = "\improper Teleportation Rune" + desc = "Could lead anywhere." + icon = 'icons/obj/rune.dmi' + icon_state = "1" + color = rgb(0,0,255) + +/obj/structure/ladder/unbreakable/rune/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user) + user.visible_message("[user] activates \the [src].", "You activate \the [src].") + +/obj/structure/ladder/unbreakable/rune/use(mob/user, is_ghost=FALSE) + if(is_ghost || !(user.mind in SSticker.mode.wizards)) + ..() diff --git a/code/modules/awaymissions/mission_code/centcomAway.dm b/code/modules/awaymissions/mission_code/centcomAway.dm index a6d1d7c9a6b..fb104144efd 100644 --- a/code/modules/awaymissions/mission_code/centcomAway.dm +++ b/code/modules/awaymissions/mission_code/centcomAway.dm @@ -1,63 +1,63 @@ -//centcomAway areas - -/area/awaymission/centcom_away - name = "XCC-P5831" - icon_state = "away" - requires_power = FALSE - -/area/awaymission/centcom_away/general - name = "XCC-P5831" - ambientsounds = list('sound/ambience/ambigen3.ogg') - -/area/awaymission/centcom_away/maint - name = "XCC-P5831 Maintenance" - icon_state = "away1" - ambientsounds = list('sound/ambience/ambisin1.ogg') - -/area/awaymission/centcom_away/thunderdome - name = "XCC-P5831 Thunderdome" - icon_state = "away2" - ambientsounds = list('sound/ambience/ambisin2.ogg') - -/area/awaymission/centcom_away/cafe - name = "XCC-P5831 Kitchen Arena" - icon_state = "away3" - ambientsounds = list('sound/ambience/ambisin3.ogg') - -/area/awaymission/centcom_away/courtroom - name = "XCC-P5831 Courtroom" - icon_state = "away4" - ambientsounds = list('sound/ambience/ambisin4.ogg') - -/area/awaymission/centcom_away/hangar - name = "XCC-P5831 Hangars" - icon_state = "away4" - ambientsounds = list('sound/ambience/ambigen5.ogg') - -//centcomAway items - -/obj/item/paper/pamphlet/centcom/visitor_info - name = "Visitor Info Pamphlet" - info = " XCC-P5831 Visitor Information
                \ - Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \ - used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \ - projects across the vastness of space.
                \ - Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \ - acts as NT's base of operations but still plays a very important role its corporate affairs; \ - serving as a supply and repair depot, as well as being host to its most important legal proceedings\ - and the thrilling pay-per-view broadcasts of PLASTEEL CHEF and THUNDERDOME LIVE.
                \ - We hope you enjoy your stay!" - -/obj/item/paper/fluff/awaymissions/centcom/gateway_memo - name = "Memo to XCC-P5831 QM" - info = "From: XCC-P5831 Management Office
                \ - To: Rolf Ingram, XCC-P5831 Quartermaster
                \ - Hey, Rolf, once you pack that gateway into the ferry hangar, make absolutely sure \ - to deactivate it! As you may know, SS13 has recently got its network up and running, \ - which means that until we get this gate shipped off to the next colonization staging \ - area, they'll be able to hop straight in here if its hooked up on our end.
                \ - Obviously, that's something I'd very much rather avoid. Our forensics and medical \ - teams never did figure out what happened that last time... and I can't wrap my head \ - around it myself. Why would a shuttle full of evacuees all snap and beat each other \ - to death the moment they reached safety?
                \ - - D. Cereza" +//centcomAway areas + +/area/awaymission/centcom_away + name = "XCC-P5831" + icon_state = "away" + requires_power = FALSE + +/area/awaymission/centcom_away/general + name = "XCC-P5831" + ambientsounds = list('sound/ambience/ambigen3.ogg') + +/area/awaymission/centcom_away/maint + name = "XCC-P5831 Maintenance" + icon_state = "away1" + ambientsounds = list('sound/ambience/ambisin1.ogg') + +/area/awaymission/centcom_away/thunderdome + name = "XCC-P5831 Thunderdome" + icon_state = "away2" + ambientsounds = list('sound/ambience/ambisin2.ogg') + +/area/awaymission/centcom_away/cafe + name = "XCC-P5831 Kitchen Arena" + icon_state = "away3" + ambientsounds = list('sound/ambience/ambisin3.ogg') + +/area/awaymission/centcom_away/courtroom + name = "XCC-P5831 Courtroom" + icon_state = "away4" + ambientsounds = list('sound/ambience/ambisin4.ogg') + +/area/awaymission/centcom_away/hangar + name = "XCC-P5831 Hangars" + icon_state = "away4" + ambientsounds = list('sound/ambience/ambigen5.ogg') + +//centcomAway items + +/obj/item/paper/pamphlet/centcom/visitor_info + name = "Visitor Info Pamphlet" + info = " XCC-P5831 Visitor Information
                \ + Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \ + used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \ + projects across the vastness of space.
                \ + Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \ + acts as NT's base of operations but still plays a very important role its corporate affairs; \ + serving as a supply and repair depot, as well as being host to its most important legal proceedings\ + and the thrilling pay-per-view broadcasts of PLASTEEL CHEF and THUNDERDOME LIVE.
                \ + We hope you enjoy your stay!" + +/obj/item/paper/fluff/awaymissions/centcom/gateway_memo + name = "Memo to XCC-P5831 QM" + info = "From: XCC-P5831 Management Office
                \ + To: Rolf Ingram, XCC-P5831 Quartermaster
                \ + Hey, Rolf, once you pack that gateway into the ferry hangar, make absolutely sure \ + to deactivate it! As you may know, SS13 has recently got its network up and running, \ + which means that until we get this gate shipped off to the next colonization staging \ + area, they'll be able to hop straight in here if its hooked up on our end.
                \ + Obviously, that's something I'd very much rather avoid. Our forensics and medical \ + teams never did figure out what happened that last time... and I can't wrap my head \ + around it myself. Why would a shuttle full of evacuees all snap and beat each other \ + to death the moment they reached safety?
                \ + - D. Cereza" diff --git a/code/modules/awaymissions/mission_code/wildwest.dm b/code/modules/awaymissions/mission_code/wildwest.dm index 6679125ad6e..9f11f149961 100644 --- a/code/modules/awaymissions/mission_code/wildwest.dm +++ b/code/modules/awaymissions/mission_code/wildwest.dm @@ -1,166 +1,166 @@ -/* Code for the Wild West map by Brotemis - * Contains: - * Wish Granter - * Meat Grinder - */ - -//Areas - -/area/awaymission/wildwest/mines - name = "Wild West Mines" - icon_state = "away1" - requires_power = FALSE - -/area/awaymission/wildwest/gov - name = "Wild West Mansion" - icon_state = "away2" - requires_power = FALSE - -/area/awaymission/wildwest/refine - name = "Wild West Refinery" - icon_state = "away3" - requires_power = FALSE - -/area/awaymission/wildwest/vault - name = "Wild West Vault" - icon_state = "away3" - -/area/awaymission/wildwest/vaultdoors - name = "Wild West Vault Doors" // this is to keep the vault area being entirely lit because of requires_power - icon_state = "away2" - requires_power = FALSE - - - ////////// wildwest papers - -/obj/item/paper/fluff/awaymissions/wildwest/grinder - info = "meat grinder requires sacri" - - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page1 - name = "Planer Saul's Journal: Page 1" - info = "We've discovered something floating in space. We can't really tell how old it is, but it is scraped and bent to hell. There object is the size of about a room with double doors that we have yet to break into. It is a lot sturdier than we could have imagined. We have decided to call it 'The Vault' " - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page4 - name = "Planer Saul's Journal: Page 4" - info = " The miners in the town have become sick and almost all production has stopped. They, in a fit of delusion, tossed all of their mining equipment into the furnaces. They all claimed the same thing. A voice beckoning them to lay down their arms. Stupid miners." - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page7 - name = "Planer Sauls' Journal: Page 7" - info = "The Vault...it just keeps growing and growing. I went on my daily walk through the garden and now it's just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...it's calling to me." - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page8 - name = "Planer Saul's Journal: Page 8" - info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside." - - -/* - * Wish Granter - */ -/obj/machinery/wish_granter_dark - name = "Wish Granter" - desc = "You're not so sure about this, anymore..." - icon = 'icons/obj/device.dmi' - icon_state = "syndbeacon" - - density = TRUE - use_power = NO_POWER_USE - - var/chargesa = 1 - var/insistinga = 0 - -/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user) - if(chargesa <= 0) - to_chat(user, "The Wish Granter lies silent.") - return - - else if(!ishuman(user)) - to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") - return - - else if(is_special_character(user)) - to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") - - else if (!insistinga) - to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") - insistinga++ - - else - chargesa-- - insistinga = 0 - var/wish = input("You want...","Wish") as null|anything in sortList(list("Power","Wealth","Immortality","Peace")) - switch(wish) - if("Power") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - user.dna.add_mutation(LASEREYES) - user.dna.add_mutation(SPACEMUT) - user.dna.add_mutation(XRAY) - user.set_species(/datum/species/shadow) - if("Wealth") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - new /obj/structure/closet/syndicate/resources/everything(loc) - user.set_species(/datum/species/shadow) - if("Immortality") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - user.verbs += /mob/living/carbon/proc/immortality - user.set_species(/datum/species/shadow) - if("Peace") - to_chat(user, "Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.") - to_chat(user, "You feel as if you just narrowly avoided a terrible fate...") - for(var/mob/living/simple_animal/hostile/faithless/F in GLOB.mob_living_list) - F.death() - - -///////////////Meatgrinder////////////// - - -/obj/effect/meatgrinder - name = "Meat Grinder" - desc = "What is that thing?" - density = TRUE - anchored = TRUE - icon = 'icons/mob/blob.dmi' - icon_state = "blobpod" - var/triggered = 0 - -/obj/effect/meatgrinder/Crossed(atom/movable/AM) - . = ..() - Bumped(AM) - -/obj/effect/meatgrinder/Bumped(atom/movable/AM) - - if(triggered) - return - if(!ishuman(AM)) - return - - var/mob/living/carbon/human/M = AM - - if(M.stat != DEAD && M.ckey) - visible_message("[M] triggered [src]!") - triggered = 1 - - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - explosion(M, 1, 0, 0, 0) - qdel(src) - -/////For the Wishgranter/////////// - -/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness. - set category = "Immortality" - set name = "Resurrection" - - var/mob/living/carbon/C = usr - if(!C.stat) - to_chat(C, "You're not dead yet!") - return - if(C.has_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT)) - to_chat(C, "You're already resurrecting!") - return - C.apply_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT) - return 1 +/* Code for the Wild West map by Brotemis + * Contains: + * Wish Granter + * Meat Grinder + */ + +//Areas + +/area/awaymission/wildwest/mines + name = "Wild West Mines" + icon_state = "away1" + requires_power = FALSE + +/area/awaymission/wildwest/gov + name = "Wild West Mansion" + icon_state = "away2" + requires_power = FALSE + +/area/awaymission/wildwest/refine + name = "Wild West Refinery" + icon_state = "away3" + requires_power = FALSE + +/area/awaymission/wildwest/vault + name = "Wild West Vault" + icon_state = "away3" + +/area/awaymission/wildwest/vaultdoors + name = "Wild West Vault Doors" // this is to keep the vault area being entirely lit because of requires_power + icon_state = "away2" + requires_power = FALSE + + + ////////// wildwest papers + +/obj/item/paper/fluff/awaymissions/wildwest/grinder + info = "meat grinder requires sacri" + + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page1 + name = "Planer Saul's Journal: Page 1" + info = "We've discovered something floating in space. We can't really tell how old it is, but it is scraped and bent to hell. There object is the size of about a room with double doors that we have yet to break into. It is a lot sturdier than we could have imagined. We have decided to call it 'The Vault' " + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page4 + name = "Planer Saul's Journal: Page 4" + info = " The miners in the town have become sick and almost all production has stopped. They, in a fit of delusion, tossed all of their mining equipment into the furnaces. They all claimed the same thing. A voice beckoning them to lay down their arms. Stupid miners." + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page7 + name = "Planer Sauls' Journal: Page 7" + info = "The Vault...it just keeps growing and growing. I went on my daily walk through the garden and now it's just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...it's calling to me." + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page8 + name = "Planer Saul's Journal: Page 8" + info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside." + + +/* + * Wish Granter + */ +/obj/machinery/wish_granter_dark + name = "Wish Granter" + desc = "You're not so sure about this, anymore..." + icon = 'icons/obj/device.dmi' + icon_state = "syndbeacon" + + density = TRUE + use_power = NO_POWER_USE + + var/chargesa = 1 + var/insistinga = 0 + +/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user) + if(chargesa <= 0) + to_chat(user, "The Wish Granter lies silent.") + return + + else if(!ishuman(user)) + to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") + return + + else if(is_special_character(user)) + to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") + + else if (!insistinga) + to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") + insistinga++ + + else + chargesa-- + insistinga = 0 + var/wish = input("You want...","Wish") as null|anything in sortList(list("Power","Wealth","Immortality","Peace")) + switch(wish) + if("Power") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + user.dna.add_mutation(LASEREYES) + user.dna.add_mutation(SPACEMUT) + user.dna.add_mutation(XRAY) + user.set_species(/datum/species/shadow) + if("Wealth") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + new /obj/structure/closet/syndicate/resources/everything(loc) + user.set_species(/datum/species/shadow) + if("Immortality") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + user.verbs += /mob/living/carbon/proc/immortality + user.set_species(/datum/species/shadow) + if("Peace") + to_chat(user, "Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.") + to_chat(user, "You feel as if you just narrowly avoided a terrible fate...") + for(var/mob/living/simple_animal/hostile/faithless/F in GLOB.mob_living_list) + F.death() + + +///////////////Meatgrinder////////////// + + +/obj/effect/meatgrinder + name = "Meat Grinder" + desc = "What is that thing?" + density = TRUE + anchored = TRUE + icon = 'icons/mob/blob.dmi' + icon_state = "blobpod" + var/triggered = 0 + +/obj/effect/meatgrinder/Crossed(atom/movable/AM) + . = ..() + Bumped(AM) + +/obj/effect/meatgrinder/Bumped(atom/movable/AM) + + if(triggered) + return + if(!ishuman(AM)) + return + + var/mob/living/carbon/human/M = AM + + if(M.stat != DEAD && M.ckey) + visible_message("[M] triggered [src]!") + triggered = 1 + + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, src) + s.start() + explosion(M, 1, 0, 0, 0) + qdel(src) + +/////For the Wishgranter/////////// + +/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness. + set category = "Immortality" + set name = "Resurrection" + + var/mob/living/carbon/C = usr + if(!C.stat) + to_chat(C, "You're not dead yet!") + return + if(C.has_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT)) + to_chat(C, "You're already resurrecting!") + return + C.apply_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT) + return 1 diff --git a/code/modules/awaymissions/pamphlet.dm b/code/modules/awaymissions/pamphlet.dm index 8912a66aa93..3bbdfbe9b47 100644 --- a/code/modules/awaymissions/pamphlet.dm +++ b/code/modules/awaymissions/pamphlet.dm @@ -1,42 +1,42 @@ -/obj/item/paper/pamphlet - name = "pamphlet" - icon_state = "pamphlet" - show_written_words = FALSE - - -/obj/item/paper/pamphlet/violent_video_games - name = "pamphlet - \'Violent Video Games and You\'" - desc = "A pamphlet encouraging the reader to maintain a balanced lifestyle and take care of their mental health, while still enjoying video games in a healthy way. You probably don't need this..." - info = "They don't make you kill people. There, we said it. Now get back to work!" - -/obj/item/paper/pamphlet/gateway - info = "Welcome to the Nanotrasen Gateway project...
                \ - Congratulations! If you're reading this, you and your superiors have decided that you're \ - ready to commit to a life spent colonising the rolling hills of far away worlds. You \ - must be ready for a lifetime of adventure, a little bit of hard work, and an award \ - winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.
                \ -
                Because we care about you, we feel it is only fair to make sure you know the risks \ - before you commit to joining the Nanotrasen Gateway project. All away destinations have \ - been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \ - We've even left a case of space beer along with the basic materials you'll need to expand \ - Nanotrasen's operational area and start your new life.

                \ - Gateway Operation Basics
                \ - All Nanotrasen approved Gateways operate on the same basic principals. They operate off \ - area equipment power as you would expect, and without this supply, it cannot safely function, \ - causinng it to reject all attempts at operation.

                \ - Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \ - searching for an output location. The amount of time this takes is variable, but the Gateway \ - interface will give you an estimate accurate to the minute. Power loss will not interrupt the \ - searching process. Influenza will not interrupt the searching process. Temporal anomalies \ - may cause the estimate to be inaccurate, but will not interrupt the searching process.

                \ - Life On The Other Side
                \ - Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \ - This is a normal side effect of travelling vast distances in a short period of time. You should \ - survey the immediate area, and attempt to locate your complimentary case of space beer. Our \ - expeditionary teams have ensured the complete safety of all away locations, but in a small \ - number of cases, the Gateway they have established may not be immediately obvious. \ - Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \ -

                A New World
                \ - As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \ - Though complete safety is assured, participants are advised to prepare for inhospitable \ - environs." +/obj/item/paper/pamphlet + name = "pamphlet" + icon_state = "pamphlet" + show_written_words = FALSE + + +/obj/item/paper/pamphlet/violent_video_games + name = "pamphlet - \'Violent Video Games and You\'" + desc = "A pamphlet encouraging the reader to maintain a balanced lifestyle and take care of their mental health, while still enjoying video games in a healthy way. You probably don't need this..." + info = "They don't make you kill people. There, we said it. Now get back to work!" + +/obj/item/paper/pamphlet/gateway + info = "Welcome to the Nanotrasen Gateway project...
                \ + Congratulations! If you're reading this, you and your superiors have decided that you're \ + ready to commit to a life spent colonising the rolling hills of far away worlds. You \ + must be ready for a lifetime of adventure, a little bit of hard work, and an award \ + winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.
                \ +
                Because we care about you, we feel it is only fair to make sure you know the risks \ + before you commit to joining the Nanotrasen Gateway project. All away destinations have \ + been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \ + We've even left a case of space beer along with the basic materials you'll need to expand \ + Nanotrasen's operational area and start your new life.

                \ + Gateway Operation Basics
                \ + All Nanotrasen approved Gateways operate on the same basic principals. They operate off \ + area equipment power as you would expect, and without this supply, it cannot safely function, \ + causinng it to reject all attempts at operation.

                \ + Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \ + searching for an output location. The amount of time this takes is variable, but the Gateway \ + interface will give you an estimate accurate to the minute. Power loss will not interrupt the \ + searching process. Influenza will not interrupt the searching process. Temporal anomalies \ + may cause the estimate to be inaccurate, but will not interrupt the searching process.

                \ + Life On The Other Side
                \ + Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \ + This is a normal side effect of travelling vast distances in a short period of time. You should \ + survey the immediate area, and attempt to locate your complimentary case of space beer. Our \ + expeditionary teams have ensured the complete safety of all away locations, but in a small \ + number of cases, the Gateway they have established may not be immediately obvious. \ + Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \ +

                A New World
                \ + As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \ + Though complete safety is assured, participants are advised to prepare for inhospitable \ + environs." diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm index 3d6f85f96c4..dae36500093 100644 --- a/code/modules/awaymissions/zlevel.dm +++ b/code/modules/awaymissions/zlevel.dm @@ -1,62 +1,62 @@ -// How much "space" we give the edge of the map -GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt")) - -/proc/createRandomZlevel() - if(GLOB.potentialRandomZlevels && GLOB.potentialRandomZlevels.len) - to_chat(world, "Loading away mission...") - var/map = pick(GLOB.potentialRandomZlevels) - load_new_z_level(map, "Away Mission") - to_chat(world, "Away mission loaded.") - -/obj/effect/landmark/awaystart - name = "away mission spawn" - desc = "Randomly picked away mission spawn points." - var/id - var/delay = TRUE // If the generated destination should be delayed by configured gateway delay - -/obj/effect/landmark/awaystart/Initialize() - . = ..() - var/datum/gateway_destination/point/current - for(var/datum/gateway_destination/point/D in GLOB.gateway_destinations) - if(D.id == id) - current = D - if(!current) - current = new - current.id = id - if(delay) - current.wait = CONFIG_GET(number/gateway_delay) - GLOB.gateway_destinations += current - current.target_turfs += get_turf(src) - -/obj/effect/landmark/awaystart/nodelay - delay = FALSE - -/proc/generateMapList(filename) - . = list() - var/list/Lines = world.file2list(filename) - - if(!Lines.len) - return - for (var/t in Lines) - if (!t) - continue - - t = trim(t) - if (length(t) == 0) - continue - else if (t[1] == "#") - continue - - var/pos = findtext(t, " ") - var/name = null - - if (pos) - name = lowertext(copytext(t, 1, pos)) - - else - name = lowertext(t) - - if (!name) - continue - - . += t +// How much "space" we give the edge of the map +GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt")) + +/proc/createRandomZlevel() + if(GLOB.potentialRandomZlevels && GLOB.potentialRandomZlevels.len) + to_chat(world, "Loading away mission...") + var/map = pick(GLOB.potentialRandomZlevels) + load_new_z_level(map, "Away Mission") + to_chat(world, "Away mission loaded.") + +/obj/effect/landmark/awaystart + name = "away mission spawn" + desc = "Randomly picked away mission spawn points." + var/id + var/delay = TRUE // If the generated destination should be delayed by configured gateway delay + +/obj/effect/landmark/awaystart/Initialize() + . = ..() + var/datum/gateway_destination/point/current + for(var/datum/gateway_destination/point/D in GLOB.gateway_destinations) + if(D.id == id) + current = D + if(!current) + current = new + current.id = id + if(delay) + current.wait = CONFIG_GET(number/gateway_delay) + GLOB.gateway_destinations += current + current.target_turfs += get_turf(src) + +/obj/effect/landmark/awaystart/nodelay + delay = FALSE + +/proc/generateMapList(filename) + . = list() + var/list/Lines = world.file2list(filename) + + if(!Lines.len) + return + for (var/t in Lines) + if (!t) + continue + + t = trim(t) + if (length(t) == 0) + continue + else if (t[1] == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + + else + name = lowertext(t) + + if (!name) + continue + + . += t diff --git a/code/modules/cargo/bounty_console.dm b/code/modules/cargo/bounty_console.dm index 7424413c8e7..88e5aba2223 100644 --- a/code/modules/cargo/bounty_console.dm +++ b/code/modules/cargo/bounty_console.dm @@ -1,65 +1,65 @@ -#define PRINTER_TIMEOUT 10 - -/obj/machinery/computer/bounty - name = "\improper Nanotrasen bounty console" - desc = "Used to check and claim bounties offered by Nanotrasen" - icon_screen = "bounty" - circuit = /obj/item/circuitboard/computer/bounty - light_color = "#E2853D"//orange - var/printer_ready = 0 //cooldown var - var/static/datum/bank_account/cargocash - -/obj/machinery/computer/bounty/Initialize() - . = ..() - printer_ready = world.time + PRINTER_TIMEOUT - cargocash = SSeconomy.get_dep_account(ACCOUNT_CAR) - -/obj/machinery/computer/bounty/proc/print_paper() - new /obj/item/paper/bounty_printout(loc) - -/obj/item/paper/bounty_printout - name = "paper - Bounties" - -/obj/item/paper/bounty_printout/Initialize() - . = ..() - info = "

                Nanotrasen Cargo Bounties


                " - update_icon() - - for(var/datum/bounty/B in GLOB.bounties_list) - if(B.claimed) - continue - info += {"

                [B.name]

                -
                • Reward: [B.reward_string()]
                • -
                • Completed: [B.completion_string()]
                "} - -/obj/machinery/computer/bounty/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - if(!GLOB.bounties_list.len) - setup_bounties() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "CargoBountyConsole", name, 750, 600, master_ui, state) - ui.open() - -/obj/machinery/computer/bounty/ui_data(mob/user) - var/list/data = list() - var/list/bountyinfo = list() - for(var/datum/bounty/B in GLOB.bounties_list) - bountyinfo += list(list("name" = B.name, "description" = B.description, "reward_string" = B.reward_string(), "completion_string" = B.completion_string() , "claimed" = B.claimed, "can_claim" = B.can_claim(), "priority" = B.high_priority, "bounty_ref" = REF(B))) - data["stored_cash"] = cargocash.account_balance - data["bountydata"] = bountyinfo - return data - -/obj/machinery/computer/bounty/ui_act(action,params) - if(..()) - return - switch(action) - if("ClaimBounty") - var/datum/bounty/cashmoney = locate(params["bounty"]) in GLOB.bounties_list - if(cashmoney) - cashmoney.claim() - return TRUE - if("Print") - if(printer_ready < world.time) - printer_ready = world.time + PRINTER_TIMEOUT - print_paper() - return +#define PRINTER_TIMEOUT 10 + +/obj/machinery/computer/bounty + name = "\improper Nanotrasen bounty console" + desc = "Used to check and claim bounties offered by Nanotrasen" + icon_screen = "bounty" + circuit = /obj/item/circuitboard/computer/bounty + light_color = "#E2853D"//orange + var/printer_ready = 0 //cooldown var + var/static/datum/bank_account/cargocash + +/obj/machinery/computer/bounty/Initialize() + . = ..() + printer_ready = world.time + PRINTER_TIMEOUT + cargocash = SSeconomy.get_dep_account(ACCOUNT_CAR) + +/obj/machinery/computer/bounty/proc/print_paper() + new /obj/item/paper/bounty_printout(loc) + +/obj/item/paper/bounty_printout + name = "paper - Bounties" + +/obj/item/paper/bounty_printout/Initialize() + . = ..() + info = "

                Nanotrasen Cargo Bounties


                " + update_icon() + + for(var/datum/bounty/B in GLOB.bounties_list) + if(B.claimed) + continue + info += {"

                [B.name]

                +
                • Reward: [B.reward_string()]
                • +
                • Completed: [B.completion_string()]
                "} + +/obj/machinery/computer/bounty/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + if(!GLOB.bounties_list.len) + setup_bounties() + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "CargoBountyConsole", name, 750, 600, master_ui, state) + ui.open() + +/obj/machinery/computer/bounty/ui_data(mob/user) + var/list/data = list() + var/list/bountyinfo = list() + for(var/datum/bounty/B in GLOB.bounties_list) + bountyinfo += list(list("name" = B.name, "description" = B.description, "reward_string" = B.reward_string(), "completion_string" = B.completion_string() , "claimed" = B.claimed, "can_claim" = B.can_claim(), "priority" = B.high_priority, "bounty_ref" = REF(B))) + data["stored_cash"] = cargocash.account_balance + data["bountydata"] = bountyinfo + return data + +/obj/machinery/computer/bounty/ui_act(action,params) + if(..()) + return + switch(action) + if("ClaimBounty") + var/datum/bounty/cashmoney = locate(params["bounty"]) in GLOB.bounties_list + if(cashmoney) + cashmoney.claim() + return TRUE + if("Print") + if(printer_ready < world.time) + printer_ready = world.time + PRINTER_TIMEOUT + print_paper() + return diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm index 4e73508024f..6a68da4d80e 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -1,286 +1,286 @@ -/obj/machinery/computer/cargo - name = "supply console" - desc = "Used to order supplies, approve requests, and control the shuttle." - icon_screen = "supply" - circuit = /obj/item/circuitboard/computer/cargo - ui_x = 780 - ui_y = 750 - - var/requestonly = FALSE - var/contraband = FALSE - var/self_paid = FALSE - var/safety_warning = "For safety reasons, the automated supply shuttle \ - cannot transport live organisms, human remains, classified nuclear weaponry, \ - homing beacons or machinery housing any form of artificial intelligence." - var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." - /// radio used by the console to send messages on supply channel - var/obj/item/radio/headset/radio - /// var that tracks message cooldown - var/message_cooldown - var/list/loaded_coupons - - light_color = "#E2853D"//orange - -/obj/machinery/computer/cargo/request - name = "supply request console" - desc = "Used to request supplies from cargo." - icon_screen = "request" - circuit = /obj/item/circuitboard/computer/cargo/request - requestonly = TRUE - -/obj/machinery/computer/cargo/Initialize() - . = ..() - radio = new /obj/item/radio/headset/headset_cargo(src) - var/obj/item/circuitboard/computer/cargo/board = circuit - contraband = board.contraband - if (board.obj_flags & EMAGGED) - obj_flags |= EMAGGED - else - obj_flags &= ~EMAGGED - -/obj/machinery/computer/cargo/Destroy() - QDEL_NULL(radio) - ..() - -/obj/machinery/computer/cargo/proc/get_export_categories() - . = EXPORT_CARGO - if(contraband) - . |= EXPORT_CONTRABAND - if(obj_flags & EMAGGED) - . |= EXPORT_EMAG - -/obj/machinery/computer/cargo/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - if(user) - user.visible_message("[user] swipes a suspicious card through [src]!", - "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.") - - obj_flags |= EMAGGED - contraband = TRUE - - // This also permamently sets this on the circuit board - var/obj/item/circuitboard/computer/cargo/board = circuit - board.contraband = TRUE - board.obj_flags |= EMAGGED - update_static_data(user) - -/obj/machinery/computer/cargo/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Cargo", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/cargo/ui_data() - var/list/data = list() - data["location"] = SSshuttle.supply.getStatusText() - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - data["points"] = D.account_balance - data["away"] = SSshuttle.supply.getDockedId() == "supply_away" - data["self_paid"] = self_paid - data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE - data["loan"] = !!SSshuttle.shuttle_loan - data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched - var/message = "Remember to stamp and send back the supply manifests." - if(SSshuttle.centcom_message) - message = SSshuttle.centcom_message - if(SSshuttle.supplyBlocked) - message = blockade_warning - data["message"] = message - data["cart"] = list() - for(var/datum/supply_order/SO in SSshuttle.shoppinglist) - data["cart"] += list(list( - "object" = SO.pack.name, - "cost" = SO.pack.cost, - "id" = SO.id, - "orderer" = SO.orderer, - "paid" = !isnull(SO.paying_account) //paid by requester - )) - - data["requests"] = list() - for(var/datum/supply_order/SO in SSshuttle.requestlist) - data["requests"] += list(list( - "object" = SO.pack.name, - "cost" = SO.pack.cost, - "orderer" = SO.orderer, - "reason" = SO.reason, - "id" = SO.id - )) - - return data - -/obj/machinery/computer/cargo/ui_static_data(mob/user) - var/list/data = list() - data["requestonly"] = requestonly - data["supplies"] = list() - for(var/pack in SSshuttle.supply_packs) - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] - if(!data["supplies"][P.group]) - data["supplies"][P.group] = list( - "name" = P.group, - "packs" = list() - ) - if((P.hidden && !(obj_flags & EMAGGED)) || (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.DropPodOnly) - continue - data["supplies"][P.group]["packs"] += list(list( - "name" = P.name, - "cost" = P.cost, - "id" = pack, - "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. - "goody" = P.goody, - "access" = P.access - )) - return data - -/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui) - if(..()) - return - switch(action) - if("send") - if(!SSshuttle.supply.canMove()) - say(safety_warning) - return - if(SSshuttle.supplyBlocked) - say(blockade_warning) - return - if(SSshuttle.supply.getDockedId() == "supply_home") - SSshuttle.supply.export_categories = get_export_categories() - SSshuttle.moveShuttle("supply", "supply_away", TRUE) - say("The supply shuttle is departing.") - investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO) - else - investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO) - say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.") - SSshuttle.moveShuttle("supply", "supply_home", TRUE) - . = TRUE - if("loan") - if(!SSshuttle.shuttle_loan) - return - if(SSshuttle.supplyBlocked) - say(blockade_warning) - return - else if(SSshuttle.supply.mode != SHUTTLE_IDLE) - return - else if(SSshuttle.supply.getDockedId() != "supply_away") - return - else - SSshuttle.shuttle_loan.loan_shuttle() - say("The supply shuttle has been loaned to CentCom.") - investigate_log("[key_name(usr)] accepted a shuttle loan event.", INVESTIGATE_CARGO) - log_game("[key_name(usr)] accepted a shuttle loan event.") - . = TRUE - if("add") - var/id = text2path(params["id"]) - var/datum/supply_pack/pack = SSshuttle.supply_packs[id] - if(!istype(pack)) - return - if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly) - return - - var/name = "*None Provided*" - var/rank = "*None Provided*" - var/ckey = usr.ckey - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - name = H.get_authentification_name() - rank = H.get_assignment(hand_first = TRUE) - else if(issilicon(usr)) - name = usr.real_name - rank = "Silicon" - - var/datum/bank_account/account - if(self_paid && ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/obj/item/card/id/id_card = H.get_idcard(TRUE) - if(!istype(id_card)) - say("No ID card detected.") - return - if(istype(id_card, /obj/item/card/id/departmental_budget)) - say("The [src] rejects [id_card].") - return - account = id_card.registered_account - if(!istype(account)) - say("Invalid bank account.") - return - - var/reason = "" - if(requestonly && !self_paid) - reason = stripped_input("Reason:", name, "") - if(isnull(reason) || ..()) - return - - if(pack.goody && !self_paid) - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) - say("ERROR: Small crates may only be purchased by private accounts.") - return - - var/obj/item/coupon/applied_coupon - for(var/i in loaded_coupons) - var/obj/item/coupon/coupon_check = i - if(pack.type == coupon_check.discounted_pack) - say("Coupon found! [round(coupon_check.discount_pct_off * 100)]% off applied!") - coupon_check.moveToNullspace() - applied_coupon = coupon_check - break - - var/turf/T = get_turf(src) - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account, applied_coupon) - SO.generateRequisition(T) - if(requestonly && !self_paid) - SSshuttle.requestlist += SO - else - SSshuttle.shoppinglist += SO - if(self_paid) - say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") - if(requestonly && message_cooldown < world.time) - radio.talk_into(src, "A new order has been requested.", RADIO_CHANNEL_SUPPLY) - message_cooldown = world.time + 30 SECONDS - . = TRUE - if("remove") - var/id = text2num(params["id"]) - for(var/datum/supply_order/SO in SSshuttle.shoppinglist) - if(SO.id == id) - if(SO.applied_coupon) - say("Coupon refunded.") - SO.applied_coupon.forceMove(get_turf(src)) - SSshuttle.shoppinglist -= SO - . = TRUE - break - if("clear") - SSshuttle.shoppinglist.Cut() - . = TRUE - if("approve") - var/id = text2num(params["id"]) - for(var/datum/supply_order/SO in SSshuttle.requestlist) - if(SO.id == id) - SSshuttle.requestlist -= SO - SSshuttle.shoppinglist += SO - . = TRUE - break - if("deny") - var/id = text2num(params["id"]) - for(var/datum/supply_order/SO in SSshuttle.requestlist) - if(SO.id == id) - SSshuttle.requestlist -= SO - . = TRUE - break - if("denyall") - SSshuttle.requestlist.Cut() - . = TRUE - if("toggleprivate") - self_paid = !self_paid - . = TRUE - if(.) - post_signal("supply") - -/obj/machinery/computer/cargo/proc/post_signal(command) - - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = command)) - frequency.post_signal(src, status_signal) +/obj/machinery/computer/cargo + name = "supply console" + desc = "Used to order supplies, approve requests, and control the shuttle." + icon_screen = "supply" + circuit = /obj/item/circuitboard/computer/cargo + ui_x = 780 + ui_y = 750 + + var/requestonly = FALSE + var/contraband = FALSE + var/self_paid = FALSE + var/safety_warning = "For safety reasons, the automated supply shuttle \ + cannot transport live organisms, human remains, classified nuclear weaponry, \ + homing beacons or machinery housing any form of artificial intelligence." + var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." + /// radio used by the console to send messages on supply channel + var/obj/item/radio/headset/radio + /// var that tracks message cooldown + var/message_cooldown + var/list/loaded_coupons + + light_color = "#E2853D"//orange + +/obj/machinery/computer/cargo/request + name = "supply request console" + desc = "Used to request supplies from cargo." + icon_screen = "request" + circuit = /obj/item/circuitboard/computer/cargo/request + requestonly = TRUE + +/obj/machinery/computer/cargo/Initialize() + . = ..() + radio = new /obj/item/radio/headset/headset_cargo(src) + var/obj/item/circuitboard/computer/cargo/board = circuit + contraband = board.contraband + if (board.obj_flags & EMAGGED) + obj_flags |= EMAGGED + else + obj_flags &= ~EMAGGED + +/obj/machinery/computer/cargo/Destroy() + QDEL_NULL(radio) + ..() + +/obj/machinery/computer/cargo/proc/get_export_categories() + . = EXPORT_CARGO + if(contraband) + . |= EXPORT_CONTRABAND + if(obj_flags & EMAGGED) + . |= EXPORT_EMAG + +/obj/machinery/computer/cargo/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + if(user) + user.visible_message("[user] swipes a suspicious card through [src]!", + "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.") + + obj_flags |= EMAGGED + contraband = TRUE + + // This also permamently sets this on the circuit board + var/obj/item/circuitboard/computer/cargo/board = circuit + board.contraband = TRUE + board.obj_flags |= EMAGGED + update_static_data(user) + +/obj/machinery/computer/cargo/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Cargo", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/cargo/ui_data() + var/list/data = list() + data["location"] = SSshuttle.supply.getStatusText() + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance + data["away"] = SSshuttle.supply.getDockedId() == "supply_away" + data["self_paid"] = self_paid + data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE + data["loan"] = !!SSshuttle.shuttle_loan + data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched + var/message = "Remember to stamp and send back the supply manifests." + if(SSshuttle.centcom_message) + message = SSshuttle.centcom_message + if(SSshuttle.supplyBlocked) + message = blockade_warning + data["message"] = message + data["cart"] = list() + for(var/datum/supply_order/SO in SSshuttle.shoppinglist) + data["cart"] += list(list( + "object" = SO.pack.name, + "cost" = SO.pack.cost, + "id" = SO.id, + "orderer" = SO.orderer, + "paid" = !isnull(SO.paying_account) //paid by requester + )) + + data["requests"] = list() + for(var/datum/supply_order/SO in SSshuttle.requestlist) + data["requests"] += list(list( + "object" = SO.pack.name, + "cost" = SO.pack.cost, + "orderer" = SO.orderer, + "reason" = SO.reason, + "id" = SO.id + )) + + return data + +/obj/machinery/computer/cargo/ui_static_data(mob/user) + var/list/data = list() + data["requestonly"] = requestonly + data["supplies"] = list() + for(var/pack in SSshuttle.supply_packs) + var/datum/supply_pack/P = SSshuttle.supply_packs[pack] + if(!data["supplies"][P.group]) + data["supplies"][P.group] = list( + "name" = P.group, + "packs" = list() + ) + if((P.hidden && !(obj_flags & EMAGGED)) || (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.DropPodOnly) + continue + data["supplies"][P.group]["packs"] += list(list( + "name" = P.name, + "cost" = P.cost, + "id" = pack, + "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. + "goody" = P.goody, + "access" = P.access + )) + return data + +/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui) + if(..()) + return + switch(action) + if("send") + if(!SSshuttle.supply.canMove()) + say(safety_warning) + return + if(SSshuttle.supplyBlocked) + say(blockade_warning) + return + if(SSshuttle.supply.getDockedId() == "supply_home") + SSshuttle.supply.export_categories = get_export_categories() + SSshuttle.moveShuttle("supply", "supply_away", TRUE) + say("The supply shuttle is departing.") + investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO) + else + investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO) + say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.") + SSshuttle.moveShuttle("supply", "supply_home", TRUE) + . = TRUE + if("loan") + if(!SSshuttle.shuttle_loan) + return + if(SSshuttle.supplyBlocked) + say(blockade_warning) + return + else if(SSshuttle.supply.mode != SHUTTLE_IDLE) + return + else if(SSshuttle.supply.getDockedId() != "supply_away") + return + else + SSshuttle.shuttle_loan.loan_shuttle() + say("The supply shuttle has been loaned to CentCom.") + investigate_log("[key_name(usr)] accepted a shuttle loan event.", INVESTIGATE_CARGO) + log_game("[key_name(usr)] accepted a shuttle loan event.") + . = TRUE + if("add") + var/id = text2path(params["id"]) + var/datum/supply_pack/pack = SSshuttle.supply_packs[id] + if(!istype(pack)) + return + if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly) + return + + var/name = "*None Provided*" + var/rank = "*None Provided*" + var/ckey = usr.ckey + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + name = H.get_authentification_name() + rank = H.get_assignment(hand_first = TRUE) + else if(issilicon(usr)) + name = usr.real_name + rank = "Silicon" + + var/datum/bank_account/account + if(self_paid && ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/obj/item/card/id/id_card = H.get_idcard(TRUE) + if(!istype(id_card)) + say("No ID card detected.") + return + if(istype(id_card, /obj/item/card/id/departmental_budget)) + say("The [src] rejects [id_card].") + return + account = id_card.registered_account + if(!istype(account)) + say("Invalid bank account.") + return + + var/reason = "" + if(requestonly && !self_paid) + reason = stripped_input("Reason:", name, "") + if(isnull(reason) || ..()) + return + + if(pack.goody && !self_paid) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + say("ERROR: Small crates may only be purchased by private accounts.") + return + + var/obj/item/coupon/applied_coupon + for(var/i in loaded_coupons) + var/obj/item/coupon/coupon_check = i + if(pack.type == coupon_check.discounted_pack) + say("Coupon found! [round(coupon_check.discount_pct_off * 100)]% off applied!") + coupon_check.moveToNullspace() + applied_coupon = coupon_check + break + + var/turf/T = get_turf(src) + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account, applied_coupon) + SO.generateRequisition(T) + if(requestonly && !self_paid) + SSshuttle.requestlist += SO + else + SSshuttle.shoppinglist += SO + if(self_paid) + say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") + if(requestonly && message_cooldown < world.time) + radio.talk_into(src, "A new order has been requested.", RADIO_CHANNEL_SUPPLY) + message_cooldown = world.time + 30 SECONDS + . = TRUE + if("remove") + var/id = text2num(params["id"]) + for(var/datum/supply_order/SO in SSshuttle.shoppinglist) + if(SO.id == id) + if(SO.applied_coupon) + say("Coupon refunded.") + SO.applied_coupon.forceMove(get_turf(src)) + SSshuttle.shoppinglist -= SO + . = TRUE + break + if("clear") + SSshuttle.shoppinglist.Cut() + . = TRUE + if("approve") + var/id = text2num(params["id"]) + for(var/datum/supply_order/SO in SSshuttle.requestlist) + if(SO.id == id) + SSshuttle.requestlist -= SO + SSshuttle.shoppinglist += SO + . = TRUE + break + if("deny") + var/id = text2num(params["id"]) + for(var/datum/supply_order/SO in SSshuttle.requestlist) + if(SO.id == id) + SSshuttle.requestlist -= SO + . = TRUE + break + if("denyall") + SSshuttle.requestlist.Cut() + . = TRUE + if("toggleprivate") + self_paid = !self_paid + . = TRUE + if(.) + post_signal("supply") + +/obj/machinery/computer/cargo/proc/post_signal(command) + + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = command)) + frequency.post_signal(src, status_signal) diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 549b61dfe19..2bed44e4117 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -1,216 +1,216 @@ -#define MAX_EMAG_ROCKETS 8 -#define BEACON_COST 500 -#define SP_LINKED 1 -#define SP_READY 2 -#define SP_LAUNCH 3 -#define SP_UNLINK 4 -#define SP_UNREADY 5 - -/obj/machinery/computer/cargo/express - name = "express supply console" - desc = "This console allows the user to purchase a package \ - with 1/40th of the delivery time: made possible by NanoTrasen's new \"1500mm Orbital Railgun\".\ - All sales are near instantaneous - please choose carefully" - icon_screen = "supply_express" - circuit = /obj/item/circuitboard/computer/cargo/express - ui_x = 600 - ui_y = 700 - blockade_warning = "Bluespace instability detected. Delivery impossible." - req_access = list(ACCESS_QM) - - var/message - var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names. - var/list/meme_pack_data - var/obj/item/supplypod_beacon/beacon //the linked supplypod beacon - var/area/landingzone = /area/quartermaster/storage //where we droppin boys - var/podType = /obj/structure/closet/supplypod - var/cooldown = 0 //cooldown to prevent printing supplypod beacon spam - var/locked = TRUE //is the console locked? unlock with ID - var/usingBeacon = FALSE //is the console in beacon mode? exists to let beacon know when a pod may come in - -/obj/machinery/computer/cargo/express/Initialize() - . = ..() - packin_up() - -/obj/machinery/computer/cargo/express/Destroy() - if(beacon) - beacon.unlink_console() - return ..() - -/obj/machinery/computer/cargo/express/attackby(obj/item/W, mob/living/user, params) - if((istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) && allowed(user)) - locked = !locked - to_chat(user, "You [locked ? "lock" : "unlock"] the interface.") - return - else if(istype(W, /obj/item/disk/cargo/bluespace_pod)) - podType = /obj/structure/closet/supplypod/bluespacepod//doesnt effect circuit board, making reversal possible - to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.") - qdel(W) - return TRUE - else if(istype(W, /obj/item/supplypod_beacon)) - var/obj/item/supplypod_beacon/sb = W - if (sb.express_console != src) - sb.link_console(src, user) - return TRUE - else - to_chat(user, "[src] is already linked to [sb].") - ..() - -/obj/machinery/computer/cargo/express/emag_act(mob/living/user) - if(obj_flags & EMAGGED) - return - if(user) - user.visible_message("[user] swipes a suspicious card through [src]!", - "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") - obj_flags |= EMAGGED - // This also sets this on the circuit board - var/obj/item/circuitboard/computer/cargo/board = circuit - board.obj_flags |= EMAGGED - packin_up() - -/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry - meme_pack_data = list() // sorry for what? - for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all - if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice - meme_pack_data[P.group] = list( // it gets cheaper when I return it - "name" = P.group, // mmhm - "packs" = list() // sometimes, I return it so much, I rip the manifest - ) // see, my quartermaster taught me a few things too - if((P.hidden) || (P.special)) // like, how not to rip the manifest - continue// by using someone else's crate - if(!(obj_flags & EMAGGED) && P.contraband) // will you show me? - continue // i'd be right happy to - meme_pack_data[P.group]["packs"] += list(list( - "name" = P.name, - "cost" = P.cost, - "id" = pack, - "desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name. - )) - -/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "CargoExpress", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/cargo/express/ui_data(mob/user) - var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location? - var/list/data = list() - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - data["points"] = D.account_balance - data["locked"] = locked//swipe an ID to unlock - data["siliconUser"] = user.has_unlimited_silicon_privilege - data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui - data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? - data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? - data["canBuyBeacon"] = cooldown <= 0 && D.account_balance >= BEACON_COST - data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary - data["hasBeacon"] = beacon != null//is there a linked beacon? - data["beaconName"] = beacon ? beacon.name : "No Beacon Found" - data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons - data["supplies"] = list() - message = "Sales are near-instantaneous - please choose carefully." - if(SSshuttle.supplyBlocked) - message = blockade_warning - if(usingBeacon && !beacon) - message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed - else if (usingBeacon && !canBeacon) - message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf - if(obj_flags & EMAGGED) - message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD." - data["message"] = message - if(!meme_pack_data) - packin_up() - stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]") - data["supplies"] = meme_pack_data - if (cooldown > 0)//cooldown used for printing beacons - cooldown-- - return data - -/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) - switch(action) - if("LZCargo") - usingBeacon = FALSE - if (beacon) - beacon.update_status(SP_UNREADY) //ready light on beacon will turn off - if("LZBeacon") - usingBeacon = TRUE - if (beacon) - beacon.update_status(SP_READY) //turns on the beacon's ready light - if("printBeacon") - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - if(D.adjust_money(-BEACON_COST)) - cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam - var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) - C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) - printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 - beacon.name = "Supply Pod Beacon #[printed_beacons]" - - - if("add")//Generate Supply Order first - var/id = text2path(params["id"]) - var/datum/supply_pack/pack = SSshuttle.supply_packs[id] - if(!istype(pack)) - return - var/name = "*None Provided*" - var/rank = "*None Provided*" - var/ckey = usr.ckey - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - name = H.get_authentification_name() - rank = H.get_assignment(hand_first = TRUE) - else if(issilicon(usr)) - name = usr.real_name - rank = "Silicon" - var/reason = "" - var/list/empty_turfs - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) - var/points_to_check - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - points_to_check = D.account_balance - if(!(obj_flags & EMAGGED)) - if(SO.pack.cost <= points_to_check) - var/LZ - if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay - LZ = get_turf(beacon) - beacon.update_status(SP_LAUNCH) - else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay - landingzone = GLOB.areas_by_type[/area/quartermaster/storage] - if (!landingzone) - WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.") - landingzone = get_area(src) - for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone - if(is_blocked_turf(T)) - continue - LAZYADD(empty_turfs, T) - CHECK_TICK - if(empty_turfs && empty_turfs.len) - LZ = pick(empty_turfs) - if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call - D.adjust_money(-SO.pack.cost) - new /obj/effect/pod_landingzone(LZ, podType, SO) - . = TRUE - update_icon() - else - if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) - landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone - for(var/turf/open/floor/T in landingzone.contents) - if(is_blocked_turf(T)) - continue - LAZYADD(empty_turfs, T) - CHECK_TICK - if(empty_turfs && empty_turfs.len) - D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS))) - - SO.generateRequisition(get_turf(src)) - for(var/i in 1 to MAX_EMAG_ROCKETS) - var/LZ = pick(empty_turfs) - LAZYREMOVE(empty_turfs, LZ) - new /obj/effect/pod_landingzone(LZ, podType, SO) - . = TRUE - update_icon() - CHECK_TICK +#define MAX_EMAG_ROCKETS 8 +#define BEACON_COST 500 +#define SP_LINKED 1 +#define SP_READY 2 +#define SP_LAUNCH 3 +#define SP_UNLINK 4 +#define SP_UNREADY 5 + +/obj/machinery/computer/cargo/express + name = "express supply console" + desc = "This console allows the user to purchase a package \ + with 1/40th of the delivery time: made possible by NanoTrasen's new \"1500mm Orbital Railgun\".\ + All sales are near instantaneous - please choose carefully" + icon_screen = "supply_express" + circuit = /obj/item/circuitboard/computer/cargo/express + ui_x = 600 + ui_y = 700 + blockade_warning = "Bluespace instability detected. Delivery impossible." + req_access = list(ACCESS_QM) + + var/message + var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names. + var/list/meme_pack_data + var/obj/item/supplypod_beacon/beacon //the linked supplypod beacon + var/area/landingzone = /area/quartermaster/storage //where we droppin boys + var/podType = /obj/structure/closet/supplypod + var/cooldown = 0 //cooldown to prevent printing supplypod beacon spam + var/locked = TRUE //is the console locked? unlock with ID + var/usingBeacon = FALSE //is the console in beacon mode? exists to let beacon know when a pod may come in + +/obj/machinery/computer/cargo/express/Initialize() + . = ..() + packin_up() + +/obj/machinery/computer/cargo/express/Destroy() + if(beacon) + beacon.unlink_console() + return ..() + +/obj/machinery/computer/cargo/express/attackby(obj/item/W, mob/living/user, params) + if((istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) && allowed(user)) + locked = !locked + to_chat(user, "You [locked ? "lock" : "unlock"] the interface.") + return + else if(istype(W, /obj/item/disk/cargo/bluespace_pod)) + podType = /obj/structure/closet/supplypod/bluespacepod//doesnt effect circuit board, making reversal possible + to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.") + qdel(W) + return TRUE + else if(istype(W, /obj/item/supplypod_beacon)) + var/obj/item/supplypod_beacon/sb = W + if (sb.express_console != src) + sb.link_console(src, user) + return TRUE + else + to_chat(user, "[src] is already linked to [sb].") + ..() + +/obj/machinery/computer/cargo/express/emag_act(mob/living/user) + if(obj_flags & EMAGGED) + return + if(user) + user.visible_message("[user] swipes a suspicious card through [src]!", + "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") + obj_flags |= EMAGGED + // This also sets this on the circuit board + var/obj/item/circuitboard/computer/cargo/board = circuit + board.obj_flags |= EMAGGED + packin_up() + +/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry + meme_pack_data = list() // sorry for what? + for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs + var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all + if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice + meme_pack_data[P.group] = list( // it gets cheaper when I return it + "name" = P.group, // mmhm + "packs" = list() // sometimes, I return it so much, I rip the manifest + ) // see, my quartermaster taught me a few things too + if((P.hidden) || (P.special)) // like, how not to rip the manifest + continue// by using someone else's crate + if(!(obj_flags & EMAGGED) && P.contraband) // will you show me? + continue // i'd be right happy to + meme_pack_data[P.group]["packs"] += list(list( + "name" = P.name, + "cost" = P.cost, + "id" = pack, + "desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name. + )) + +/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "CargoExpress", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/cargo/express/ui_data(mob/user) + var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location? + var/list/data = list() + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance + data["locked"] = locked//swipe an ID to unlock + data["siliconUser"] = user.has_unlimited_silicon_privilege + data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui + data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? + data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? + data["canBuyBeacon"] = cooldown <= 0 && D.account_balance >= BEACON_COST + data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary + data["hasBeacon"] = beacon != null//is there a linked beacon? + data["beaconName"] = beacon ? beacon.name : "No Beacon Found" + data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons + data["supplies"] = list() + message = "Sales are near-instantaneous - please choose carefully." + if(SSshuttle.supplyBlocked) + message = blockade_warning + if(usingBeacon && !beacon) + message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed + else if (usingBeacon && !canBeacon) + message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf + if(obj_flags & EMAGGED) + message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD." + data["message"] = message + if(!meme_pack_data) + packin_up() + stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]") + data["supplies"] = meme_pack_data + if (cooldown > 0)//cooldown used for printing beacons + cooldown-- + return data + +/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) + switch(action) + if("LZCargo") + usingBeacon = FALSE + if (beacon) + beacon.update_status(SP_UNREADY) //ready light on beacon will turn off + if("LZBeacon") + usingBeacon = TRUE + if (beacon) + beacon.update_status(SP_READY) //turns on the beacon's ready light + if("printBeacon") + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + if(D.adjust_money(-BEACON_COST)) + cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam + var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) + C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) + printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 + beacon.name = "Supply Pod Beacon #[printed_beacons]" + + + if("add")//Generate Supply Order first + var/id = text2path(params["id"]) + var/datum/supply_pack/pack = SSshuttle.supply_packs[id] + if(!istype(pack)) + return + var/name = "*None Provided*" + var/rank = "*None Provided*" + var/ckey = usr.ckey + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + name = H.get_authentification_name() + rank = H.get_assignment(hand_first = TRUE) + else if(issilicon(usr)) + name = usr.real_name + rank = "Silicon" + var/reason = "" + var/list/empty_turfs + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) + var/points_to_check + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + points_to_check = D.account_balance + if(!(obj_flags & EMAGGED)) + if(SO.pack.cost <= points_to_check) + var/LZ + if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay + LZ = get_turf(beacon) + beacon.update_status(SP_LAUNCH) + else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay + landingzone = GLOB.areas_by_type[/area/quartermaster/storage] + if (!landingzone) + WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.") + landingzone = get_area(src) + for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone + if(is_blocked_turf(T)) + continue + LAZYADD(empty_turfs, T) + CHECK_TICK + if(empty_turfs && empty_turfs.len) + LZ = pick(empty_turfs) + if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call + D.adjust_money(-SO.pack.cost) + new /obj/effect/pod_landingzone(LZ, podType, SO) + . = TRUE + update_icon() + else + if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) + landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone + for(var/turf/open/floor/T in landingzone.contents) + if(is_blocked_turf(T)) + continue + LAZYADD(empty_turfs, T) + CHECK_TICK + if(empty_turfs && empty_turfs.len) + D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS))) + + SO.generateRequisition(get_turf(src)) + for(var/i in 1 to MAX_EMAG_ROCKETS) + var/LZ = pick(empty_turfs) + LAZYREMOVE(empty_turfs, LZ) + new /obj/effect/pod_landingzone(LZ, podType, SO) + . = TRUE + update_icon() + CHECK_TICK diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm index 7fc5f720e00..2f8028021c5 100644 --- a/code/modules/cargo/gondolapod.dm +++ b/code/modules/cargo/gondolapod.dm @@ -1,76 +1,76 @@ -/mob/living/simple_animal/pet/gondola/gondolapod - name = "gondola" - real_name = "gondola" - desc = "The silent walker. This one seems to be part of a delivery agency." - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "bops" - response_disarm_simple = "bop" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - faction = list("gondola") - turns_per_move = 10 - icon = 'icons/obj/supplypods.dmi' - icon_state = "gondola" - icon_living = "gondola" - pixel_x = -16//2x2 sprite - pixel_y = -5 - layer = TABLE_LAYER//so that deliveries dont appear underneath it - loot = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/stack/sheet/animalhide/gondola = 2, /obj/item/reagent_containers/food/snacks/meat/slab/gondola = 2) - //Gondolas aren't affected by cold. - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - maxHealth = 200 - health = 200 - del_on_death = TRUE - var/opened = FALSE - var/obj/structure/closet/supplypod/centcompod/linked_pod - -/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod) - linked_pod = pod - name = linked_pod.name - desc = linked_pod.desc - . = ..() - -/mob/living/simple_animal/pet/gondola/gondolapod/update_overlays() - . = ..() - if(opened) - . += "[icon_state]_open" - -/mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() - set name = "Release Contents" - set category = "Gondola" - set desc = "Release any contents stored within your vast belly." - linked_pod.open_pod(src, forced = TRUE) - -/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user) - . = ..() - if (contents.len) - . += "It looks like it hasn't made its delivery yet.
                " - else - . += "It looks like it has already made its delivery.
                " - -/mob/living/simple_animal/pet/gondola/gondolapod/verb/check() - set name = "Count Contents" - set category = "Gondola" - set desc = "Take a deep look inside youself, and count up what's inside" - var/total = contents.len - if (total) - to_chat(src, "You detect [total] object\s within your incredibly vast belly.") - else - to_chat(src, "A closer look inside yourself reveals... nothing.") - -/mob/living/simple_animal/pet/gondola/gondolapod/setOpened() - opened = TRUE - update_icon() - addtimer(CALLBACK(src, /atom/.proc/setClosed), 50) - -/mob/living/simple_animal/pet/gondola/gondolapod/setClosed() - opened = FALSE - update_icon() - -/mob/living/simple_animal/pet/gondola/gondolapod/death() - qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death - qdel(src) - ..() +/mob/living/simple_animal/pet/gondola/gondolapod + name = "gondola" + real_name = "gondola" + desc = "The silent walker. This one seems to be part of a delivery agency." + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "bops" + response_disarm_simple = "bop" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + faction = list("gondola") + turns_per_move = 10 + icon = 'icons/obj/supplypods.dmi' + icon_state = "gondola" + icon_living = "gondola" + pixel_x = -16//2x2 sprite + pixel_y = -5 + layer = TABLE_LAYER//so that deliveries dont appear underneath it + loot = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/stack/sheet/animalhide/gondola = 2, /obj/item/reagent_containers/food/snacks/meat/slab/gondola = 2) + //Gondolas aren't affected by cold. + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + maxbodytemp = 1500 + maxHealth = 200 + health = 200 + del_on_death = TRUE + var/opened = FALSE + var/obj/structure/closet/supplypod/centcompod/linked_pod + +/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod) + linked_pod = pod + name = linked_pod.name + desc = linked_pod.desc + . = ..() + +/mob/living/simple_animal/pet/gondola/gondolapod/update_overlays() + . = ..() + if(opened) + . += "[icon_state]_open" + +/mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() + set name = "Release Contents" + set category = "Gondola" + set desc = "Release any contents stored within your vast belly." + linked_pod.open_pod(src, forced = TRUE) + +/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user) + . = ..() + if (contents.len) + . += "It looks like it hasn't made its delivery yet." + else + . += "It looks like it has already made its delivery." + +/mob/living/simple_animal/pet/gondola/gondolapod/verb/check() + set name = "Count Contents" + set category = "Gondola" + set desc = "Take a deep look inside youself, and count up what's inside" + var/total = contents.len + if (total) + to_chat(src, "You detect [total] object\s within your incredibly vast belly.") + else + to_chat(src, "A closer look inside yourself reveals... nothing.") + +/mob/living/simple_animal/pet/gondola/gondolapod/setOpened() + opened = TRUE + update_icon() + addtimer(CALLBACK(src, /atom/.proc/setClosed), 50) + +/mob/living/simple_animal/pet/gondola/gondolapod/setClosed() + opened = FALSE + update_icon() + +/mob/living/simple_animal/pet/gondola/gondolapod/death() + qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death + qdel(src) + ..() diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm index 2009e404232..f6823cf847e 100644 --- a/code/modules/cargo/order.dm +++ b/code/modules/cargo/order.dm @@ -1,125 +1,125 @@ -/obj/item/paper/fluff/jobs/cargo/manifest - var/order_cost = 0 - var/order_id = 0 - var/errors = 0 - -/obj/item/paper/fluff/jobs/cargo/manifest/New(atom/A, id, cost) - ..() - order_id = id - order_cost = cost - - if(prob(MANIFEST_ERROR_CHANCE)) - errors |= MANIFEST_ERROR_NAME - if(prob(MANIFEST_ERROR_CHANCE)) - errors |= MANIFEST_ERROR_CONTENTS - if(prob(MANIFEST_ERROR_CHANCE)) - errors |= MANIFEST_ERROR_ITEM - -/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved() - return stamped && stamped.len && !is_denied() - -/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied() - return stamped && ("stamp-deny" in stamped) - -/datum/supply_order - var/id - var/orderer - var/orderer_rank - var/orderer_ckey - var/reason - var/discounted_pct - var/datum/supply_pack/pack - var/datum/bank_account/paying_account - var/obj/item/coupon/applied_coupon - -/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason, paying_account, coupon) - id = SSshuttle.ordernum++ - src.pack = pack - src.orderer = orderer - src.orderer_rank = orderer_rank - src.orderer_ckey = orderer_ckey - src.reason = reason - src.paying_account = paying_account - src.applied_coupon = coupon - -/datum/supply_order/proc/generateRequisition(turf/T) - var/obj/item/paper/P = new(T) - - P.name = "requisition form - #[id] ([pack.name])" - P.info += "

                [station_name()] Supply Requisition

                " - P.info += "
                " - P.info += "Order #[id]
                " - P.info += "Time of Order: [station_time_timestamp()]
                " - P.info += "Item: [pack.name]
                " - P.info += "Access Restrictions: [get_access_desc(pack.access)]
                " - P.info += "Requested by: [orderer]
                " - if(paying_account) - P.info += "Paid by: [paying_account.account_holder]
                " - P.info += "Rank: [orderer_rank]
                " - P.info += "Comment: [reason]
                " - - P.update_icon() - return P - -/datum/supply_order/proc/generateManifest(obj/container, owner, packname) //generates-the-manifests. - var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(container, id, 0) - - var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name() - - P.name = "shipping manifest - [packname?"#[id] ([pack.name])":"(Grouped Item Crate)"]" - P.info += "

                [command_name()] Shipping Manifest

                " - P.info += "
                " - if(owner && !(owner == "Cargo")) - P.info += "Direct purchase from [owner]
                " - P.name += " - Purchased by [owner]" - P.info += "Order[packname?"":"s"]: [id]
                " - P.info += "Destination: [station_name]
                " - if(packname) - P.info += "Item: [packname]
                " - P.info += "Contents:
                " - P.info += "
                  " - for(var/atom/movable/AM in container.contents - P) - if((P.errors & MANIFEST_ERROR_CONTENTS)) - if(prob(50)) - P.info += "
                • [AM.name]
                • " - else - continue - P.info += "
                • [AM.name]
                • " - P.info += "
                " - P.info += "

                Stamp below to confirm receipt of goods:

                " - - if(P.errors & MANIFEST_ERROR_ITEM) - if(istype(container, /obj/structure/closet/crate/secure) || istype(container, /obj/structure/closet/crate/large)) - P.errors &= ~MANIFEST_ERROR_ITEM - else - var/lost = max(round(container.contents.len / 10), 1) - while(--lost >= 0) - qdel(pick(container.contents)) - - P.update_icon() - P.forceMove(container) - - if(istype(container, /obj/structure/closet/crate)) - var/obj/structure/closet/crate/C = container - C.manifest = P - C.update_icon() - else - container.contents += P - - return P - -/datum/supply_order/proc/generate(atom/A) - var/account_holder - if(paying_account) - account_holder = paying_account.account_holder - else - account_holder = "Cargo" - var/obj/structure/closet/crate/C = pack.generate(A, paying_account) - generateManifest(C, account_holder, pack) - return C - -/datum/supply_order/proc/generateCombo(miscbox, misc_own, misc_contents) - for (var/I in misc_contents) - new I(miscbox) - generateManifest(miscbox, misc_own, "") - return +/obj/item/paper/fluff/jobs/cargo/manifest + var/order_cost = 0 + var/order_id = 0 + var/errors = 0 + +/obj/item/paper/fluff/jobs/cargo/manifest/New(atom/A, id, cost) + ..() + order_id = id + order_cost = cost + + if(prob(MANIFEST_ERROR_CHANCE)) + errors |= MANIFEST_ERROR_NAME + if(prob(MANIFEST_ERROR_CHANCE)) + errors |= MANIFEST_ERROR_CONTENTS + if(prob(MANIFEST_ERROR_CHANCE)) + errors |= MANIFEST_ERROR_ITEM + +/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved() + return stamped && stamped.len && !is_denied() + +/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied() + return stamped && ("stamp-deny" in stamped) + +/datum/supply_order + var/id + var/orderer + var/orderer_rank + var/orderer_ckey + var/reason + var/discounted_pct + var/datum/supply_pack/pack + var/datum/bank_account/paying_account + var/obj/item/coupon/applied_coupon + +/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason, paying_account, coupon) + id = SSshuttle.ordernum++ + src.pack = pack + src.orderer = orderer + src.orderer_rank = orderer_rank + src.orderer_ckey = orderer_ckey + src.reason = reason + src.paying_account = paying_account + src.applied_coupon = coupon + +/datum/supply_order/proc/generateRequisition(turf/T) + var/obj/item/paper/P = new(T) + + P.name = "requisition form - #[id] ([pack.name])" + P.info += "

                [station_name()] Supply Requisition

                " + P.info += "
                " + P.info += "Order #[id]
                " + P.info += "Time of Order: [station_time_timestamp()]
                " + P.info += "Item: [pack.name]
                " + P.info += "Access Restrictions: [get_access_desc(pack.access)]
                " + P.info += "Requested by: [orderer]
                " + if(paying_account) + P.info += "Paid by: [paying_account.account_holder]
                " + P.info += "Rank: [orderer_rank]
                " + P.info += "Comment: [reason]
                " + + P.update_icon() + return P + +/datum/supply_order/proc/generateManifest(obj/container, owner, packname) //generates-the-manifests. + var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(container, id, 0) + + var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name() + + P.name = "shipping manifest - [packname?"#[id] ([pack.name])":"(Grouped Item Crate)"]" + P.info += "

                [command_name()] Shipping Manifest

                " + P.info += "
                " + if(owner && !(owner == "Cargo")) + P.info += "Direct purchase from [owner]
                " + P.name += " - Purchased by [owner]" + P.info += "Order[packname?"":"s"]: [id]
                " + P.info += "Destination: [station_name]
                " + if(packname) + P.info += "Item: [packname]
                " + P.info += "Contents:
                " + P.info += "
                  " + for(var/atom/movable/AM in container.contents - P) + if((P.errors & MANIFEST_ERROR_CONTENTS)) + if(prob(50)) + P.info += "
                • [AM.name]
                • " + else + continue + P.info += "
                • [AM.name]
                • " + P.info += "
                " + P.info += "

                Stamp below to confirm receipt of goods:

                " + + if(P.errors & MANIFEST_ERROR_ITEM) + if(istype(container, /obj/structure/closet/crate/secure) || istype(container, /obj/structure/closet/crate/large)) + P.errors &= ~MANIFEST_ERROR_ITEM + else + var/lost = max(round(container.contents.len / 10), 1) + while(--lost >= 0) + qdel(pick(container.contents)) + + P.update_icon() + P.forceMove(container) + + if(istype(container, /obj/structure/closet/crate)) + var/obj/structure/closet/crate/C = container + C.manifest = P + C.update_icon() + else + container.contents += P + + return P + +/datum/supply_order/proc/generate(atom/A) + var/account_holder + if(paying_account) + account_holder = paying_account.account_holder + else + account_holder = "Cargo" + var/obj/structure/closet/crate/C = pack.generate(A, paying_account) + generateManifest(C, account_holder, pack) + return C + +/datum/supply_order/proc/generateCombo(miscbox, misc_own, misc_contents) + for (var/I in misc_contents) + new I(miscbox) + generateManifest(miscbox, misc_own, "") + return diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 51ab94402e2..d36ebc94988 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -1,585 +1,585 @@ -#define SUPPLYPOD_X_OFFSET -16 - -//The "pod_landingzone" temp visual is created by anything that "launches" a supplypod. This is what animates the pod and makes the pod forcemove to the station. -//------------------------------------SUPPLY POD-------------------------------------// -/obj/structure/closet/supplypod - name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe - desc = "A Nanotrasen supply drop pod." - icon = 'icons/obj/supplypods.dmi' - icon_state = "pod" //This is a common base sprite shared by a number of pods - pixel_x = SUPPLYPOD_X_OFFSET //2x2 sprite - layer = BELOW_OBJ_LAYER //So that the crate inside doesn't appear underneath - allow_objects = TRUE - allow_dense = TRUE - delivery_icon = null - can_weld_shut = FALSE - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) - anchored = TRUE //So it cant slide around after landing - anchorable = FALSE - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - appearance_flags = KEEP_TOGETHER | PIXEL_SCALE - density = FALSE - - //*****NOTE*****: Many of these comments are similarly described in centcom_podlauncher.dm. If you change them here, please consider doing so in the centcom podlauncher code as well! - var/adminNamed = FALSE //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) - var/bluespace = FALSE //If true, the pod deletes (in a shower of sparks) after landing - var/landingDelay = 30 //How long the pod takes to land after launching - var/openingDelay = 30 //How long the pod takes to open after landing - var/departureDelay = 30 //How long the pod takes to leave after opening. If bluespace = TRUE, it deletes. If reversing = TRUE, it flies back to centcom. - var/damage = 0 //Damage that occurs to any mob under the pod when it lands. - var/effectStun = FALSE //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! - var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands - var/effectOrgans = FALSE //If true, yeets out every limb and organ from anyone caught under the pod when it lands - var/effectGib = FALSE //If true, anyone under the pod will be gibbed when it lands - var/effectStealth = FALSE //If true, a target icon isn't displayed on the turf where the pod will land - var/effectQuiet = FALSE //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) - var/effectMissile = FALSE //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground - var/effectCircle = FALSE //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here - var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. - var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom - var/turf/reverse_dropoff_turf //Turf that the reverse pod will drop off it's newly-acquired cargo to - var/fallDuration = 4 - var/fallingSoundLength = 11 - var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands - var/landingSound //Admin sound to play when the pod lands - var/openingSound //Admin sound to play when the pod opens - var/leavingSound //Admin sound to play when the pod leaves - var/soundVolume = 80 //Volume to play sounds at. Ignores the cap - var/list/explosionSize = list(0,0,2,3) - var/stay_after_drop = FALSE - var/specialised = FALSE // It's not a general use pod for cargo/admin use - var/rubble_type //Rubble effect associated with this supplypod - var/decal = "default" //What kind of extra decals we add to the pod to make it look nice - var/door = "pod_door" - var/fin_mask = "topfin" - var/obj/effect/supplypod_rubble/rubble - var/obj/effect/engineglow/glow_effect - var/effectShrapnel = FALSE - var/shrapnel_type = /obj/projectile/bullet/shrapnel - var/shrapnel_magnitude = 3 - -/obj/structure/closet/supplypod/bluespacepod - style = STYLE_BLUESPACE - bluespace = TRUE - explosionSize = list(0,0,1,2) - landingDelay = 15 //Slightly quicker than the supplypod - -/obj/structure/closet/supplypod/extractionpod - name = "Syndicate Extraction Pod" - desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas. Targets must be manually stuffed inside the pod for proper delivery." - specialised = TRUE - style = STYLE_SYNDICATE - bluespace = TRUE - explosionSize = list(0,0,1,2) - landingDelay = 25 //Longer than others - -/obj/structure/closet/supplypod/centcompod - style = STYLE_CENTCOM - bluespace = TRUE - explosionSize = list(0,0,0,0) - landingDelay = 20 //Very speedy! - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/structure/closet/supplypod/Initialize() - . = ..() - setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly - -/obj/structure/closet/supplypod/extractionpod/Initialize() - . = ..() - reverse_dropoff_turf = pick(GLOB.holdingfacility) - -/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, duringInit = FALSE) //Used to give the sprite an icon state, name, and description. Should only be called once - if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM. - setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly. - return - style = chosenStyle - var/base = POD_STYLES[chosenStyle][POD_BASE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. - icon_state = base - decal = POD_STYLES[chosenStyle][POD_DECAL] - rubble_type = POD_STYLES[chosenStyle][POD_RUBBLE_TYPE] - if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum - name = POD_STYLES[chosenStyle][POD_NAME] - desc = POD_STYLES[chosenStyle][POD_DESC] - door = "[base]_door" - update_icon() - -/obj/structure/closet/supplypod/proc/SetReverseIcon() - fin_mask = "bottomfin" - if (POD_STYLES[style][POD_SHAPE] == POD_SHAPE_NORML) - icon_state = POD_STYLES[style][POD_BASE] + "_reverse" - pixel_x = initial(pixel_x) - transform = matrix() - update_icon() - -/obj/structure/closet/supplypod/proc/backToNonReverseIcon() - fin_mask = initial(fin_mask) - if (POD_STYLES[style][POD_SHAPE] == POD_SHAPE_NORML) - icon_state = POD_STYLES[style][POD_BASE] - pixel_x = initial(pixel_x) - transform = matrix() - update_icon() - -/obj/structure/closet/supplypod/closet_update_overlays(list/new_overlays) - return - -/obj/structure/closet/supplypod/update_overlays() - . = ..() - if (style == STYLE_INVISIBLE) - return - if (rubble) - . += rubble.getForeground(src) - if (style == STYLE_SEETHROUGH) - for (var/atom/A in contents) - var/mutable_appearance/itemIcon = new(A) - itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) - . += itemIcon - return - - if (opened) //We're opened means all we have to worry about is masking a decal if we have one - if (!decal) //We don't have a decal to mask - return - var/icon/masked_decal = new(icon, decal) //The decal we want to apply - var/icon/door_masker = new(icon, door) //The door shape we want to 'cut out' of the decal - door_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) - door_masker.SwapColor("#ffffffff", null) - door_masker.Blend("#000000", ICON_SUBTRACT) - masked_decal.Blend(door_masker, ICON_ADD) - . += masked_decal - else //If we're closed - if (POD_STYLES[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking - . += door - else - var/icon/masked_door = new(icon, door) //The door we want to apply - var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door - fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) - fin_masker.SwapColor("#ffffffff", null) - fin_masker.Blend("#000000", ICON_SUBTRACT) - masked_door.Blend(fin_masker, ICON_ADD) - . += masked_door - if (decal) - . += decal - -/obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) - if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. - return FALSE - else - ..() - -/obj/structure/closet/supplypod/ex_act() //Explosions dont do SHIT TO US! This is because supplypods create explosions when they land. - return - -/obj/structure/closet/supplypod/contents_explosion() //Supplypods also protect their contents from the harmful effects of fucking exploding. - return - -/obj/structure/closet/supplypod/toggle(mob/living/user) - return - -/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) - return - -/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting(atom/movable/holder = src) - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever - audible_message("The pod hisses, closing and launching itself away from the station.", "The ground vibrates, and you hear the sound of engines firing.") - stay_after_drop = FALSE - holder.pixel_z = initial(holder.pixel_z) - holder.alpha = initial(holder.alpha) - var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/fly_me_to_the_moon] - forceMove(shippingLane) //Move to the centcom-z-level until the pod_landingzone says we can drop back down again - if (!reverse_dropoff_turf) //If we're centcom-launched, the reverse dropoff turf will be a centcom loading bay. If we're an extraction pod, it should be the ninja jail. - reverse_dropoff_turf = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas - landingDelay = initial(landingDelay) //Reset the landing timers so we land on whatever turf we're aiming at normally. Will be changed to be editable later (tm) - fallDuration = initial(fallDuration) //This is so if someone adds a really long dramatic landing time they don't have to sit through it twice on the pod's return trip - openingDelay = initial(openingDelay) - backToNonReverseIcon() - new /obj/effect/pod_landingzone(reverse_dropoff_turf, src) - -/obj/structure/closet/supplypod/proc/preOpen() //Called before the open_pod() proc. Handles anything that occurs right as the pod lands. - var/turf/T = get_turf(src) - var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p - density = TRUE //Density is originally false so the pod doesn't block anything while it's still falling through the air - if (landingSound) - playsound(get_turf(src), landingSound, soundVolume, FALSE, FALSE) - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) - if(effectShrapnel) - SEND_SIGNAL(src, COMSIG_SUPPLYPOD_LANDED) - for (var/mob/living/M in T) - if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people): - var/mob/living/carbon/CM = M - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! - break - if (effectOrgans && iscarbon(M)) //effectOrgans means remove every organ in our mob - var/mob/living/carbon/CM = M - for(var/X in CM.internal_organs) - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) //Pick a random direction to toss them in - var/obj/item/organ/O = X - O.Remove(CM) //Note that this isn't the same proc as for lists - O.forceMove(T) //Move the organ outta the body - O.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away - sleep(1) - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! - bodypart.throw_at(destination, 2, 3) - sleep(1) - - if (effectGib) //effectGib is on, that means whatever's underneath us better be fucking oof'd on - M.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) - M.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs - M.adjustBruteLoss(damage) - var/explosion_sum = B[1] + B[2] + B[3] + B[4] - if (explosion_sum != 0) //If the explosion list isn't all zeroes, call an explosion - explosion(get_turf(src), B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing - else if (!effectQuiet) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) - playsound(src, "explosion", landingSound ? 15 : 80, TRUE) - if (effectMissile) //If we are acting like a missile, then right after we land and finish fucking shit up w explosions, we should delete - opened = TRUE //We set opened to TRUE to avoid spending time trying to open (due to being deleted) during the Destroy() proc - qdel(src) - return - if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges - var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src) - benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. - moveToNullspace() - addtimer(CALLBACK(src, .proc/open_pod, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob - else if (style == STYLE_SEETHROUGH) - open_pod(src) - else - addtimer(CALLBACK(src, .proc/open_pod, src), openingDelay) //After the openingDelay passes, we use the open proc from this supplypod, while referencing this supplypod's contents - -/obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with - if (!holder) - return - if (opened) //This is to ensure we don't open something that has already been opened - return - holder.setOpened() - var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about - var/mob/M - if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents - M = holder - if (M.key && !forced && !broken) //If we are player controlled, then we shouldn't open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) - return - if (openingSound) - playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play - for (var/atom/movable/O in holder.contents) //Go through the contents of the holder - O.forceMove(T) //move everything from the contents of the holder to the turf of the holder - if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound - playsound(get_turf(holder), open_sound, 15, TRUE, -3) - if (broken) //If the pod is opening because it's been destroyed, we end here - return - if (style == STYLE_SEETHROUGH) - startExitSequence(src) - else - if (reversing) - addtimer(CALLBACK(src, .proc/SetReverseIcon), departureDelay/2) //Finish up the pod's duties after a certain amount of time - if(!stay_after_drop) // Departing should be handled manually - addtimer(CALLBACK(src, .proc/startExitSequence, holder), departureDelay) //Finish up the pod's duties after a certain amount of time - -/obj/structure/closet/supplypod/proc/startExitSequence(atom/movable/holder) - if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom - close(holder) - else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) - deleteRubble() - if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH) - do_sparks(5, TRUE, holder) //Create some sparks right before closing - qdel(src) //Delete ourselves and the holder - if (holder != src) - qdel(holder) - -/obj/structure/closet/supplypod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true - if (!holder) - return - take_contents(holder) - playsound(holder, close_sound, close_sound_volume, TRUE, -3) - holder.setClosed() - addtimer(CALLBACK(src, .proc/preReturn, holder), 10) //Start to leave a second after closing for cinematic effect - -/obj/structure/closet/supplypod/take_contents(atom/movable/holder) - var/atom/L = holder.drop_location() - for(var/atom/movable/AM in L) - if(AM != src && !insert(AM, holder)) // Can't insert that - continue - -/obj/structure/closet/supplypod/insert(atom/movable/AM, atom/movable/holder) - if(insertion_allowed(AM)) - AM.forceMove(holder) - return TRUE - else - return FALSE - -/obj/structure/closet/supplypod/insertion_allowed(atom/movable/AM) - if(ismob(AM)) - if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... - return FALSE - var/mob/living/L = AM - if(L.anchored || L.incorporeal_move) - return FALSE - L.stop_pulling() - - else if(isobj(AM)) - if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) - return FALSE - else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) - return TRUE - else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) - return FALSE - else - return FALSE - - return TRUE - -/obj/structure/closet/supplypod/proc/preReturn(atom/movable/holder) - if (leavingSound) - playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) - deleteRubble() - animate(holder, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) - animate(holder, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising pod - - addtimer(CALLBACK(src, .proc/handleReturnAfterDeparting, holder), 15) //Finish up the pod's duties after a certain amount of time - -/obj/structure/closet/supplypod/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open_pod() proc for more details - opened = TRUE - density = FALSE - update_icon() - -/obj/structure/closet/supplypod/extractionpod/setOpened() - opened = TRUE - density = TRUE - update_icon() - -/obj/structure/closet/supplypod/setClosed() //Ditto - opened = FALSE - density = TRUE - update_icon() - -/obj/structure/closet/supplypod/proc/tryMakeRubble(turf/T) //Ditto - if (rubble_type == RUBBLE_NONE) - return - if (rubble) - return - if (effectMissile) - return - if (isspaceturf(T) || isclosedturf(T)) - return - rubble = new /obj/effect/supplypod_rubble(T) - rubble.setStyle(rubble_type, src) - update_icon() - -/obj/structure/closet/supplypod/Moved() - deleteRubble() - return ..() - -/obj/structure/closet/supplypod/proc/deleteRubble() - rubble?.fadeAway() - rubble = null - update_icon() - -/obj/structure/closet/supplypod/proc/addGlow() - if (POD_STYLES[style][POD_SHAPE] != POD_SHAPE_NORML) - return - glow_effect = new(src) - glow_effect.icon_state = "pod_glow_" + POD_STYLES[style][POD_GLOW] - vis_contents += glow_effect - glow_effect.layer = GASFIRE_LAYER - -/obj/structure/closet/supplypod/proc/endGlow() - if(!glow_effect) - return - glow_effect.layer = LOW_ITEM_LAYER - glow_effect.fadeAway(openingDelay) - -/obj/structure/closet/supplypod/Destroy() - deleteRubble() - open_pod(src, broken = TRUE) //Lets dump our contents by opening up - return ..() - -//------------------------------------TEMPORARY_VISUAL-------------------------------------// -/obj/effect/supplypod_smoke //Falling pod smoke - name = "" - icon = 'icons/obj/supplypods_32x32.dmi' - icon_state = "smoke" - desc = "" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - alpha = 0 - -/obj/effect/engineglow //Falling pod smoke - name = "" - icon = 'icons/obj/supplypods.dmi' - icon_state = "pod_engineglow" - desc = "" - layer = GASFIRE_LAYER - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - alpha = 255 - -/obj/effect/engineglow/proc/fadeAway(leaveTime) - var/duration = min(leaveTime, 25) - animate(src, alpha=0, time = duration) - QDEL_IN(src, duration + 5) - -/obj/effect/supplypod_smoke/proc/drawSelf(amount) - alpha = max(0, 255-(amount*20)) - -/obj/effect/supplypod_rubble //This is the object that forceMoves the supplypod to it's location - name = "Debris" - desc = "A small crater of rubble. Closer inspection reveals the debris to be made primarily of space-grade metal fragments. You're pretty sure that this will disperse before too long." - icon = 'icons/obj/supplypods.dmi' - layer = PROJECTILE_HIT_THRESHHOLD_LAYER // We want this to go right below the layer of supplypods and supplypod_rubble's forground. - icon_state = "rubble_bg" - anchored = TRUE - pixel_x = SUPPLYPOD_X_OFFSET - var/foreground = "rubble_fg" - var/verticle_offset = 0 - -/obj/effect/supplypod_rubble/proc/getForeground(obj/structure/closet/supplypod/pod) - var/mutable_appearance/rubble_overlay = mutable_appearance('icons/obj/supplypods.dmi', foreground) - rubble_overlay.appearance_flags = KEEP_APART|RESET_TRANSFORM - rubble_overlay.transform = matrix().Translate(SUPPLYPOD_X_OFFSET - pod.pixel_x, verticle_offset) - return rubble_overlay - -/obj/effect/supplypod_rubble/proc/fadeAway() - animate(src, alpha=0, time = 30) - QDEL_IN(src, 35) - -/obj/effect/supplypod_rubble/proc/setStyle(type, obj/structure/closet/supplypod/pod) - if (type == RUBBLE_WIDE) - icon_state += "_wide" - foreground += "_wide" - if (type == RUBBLE_THIN) - icon_state += "_thin" - foreground += "_thin" - if (pod.style == STYLE_BOX) - verticle_offset = -2 - else - verticle_offset = initial(verticle_offset) - - pixel_y = verticle_offset - -/obj/effect/pod_landingzone_effect - name = "" - desc = "" - icon = 'icons/obj/supplypods_32x32.dmi' - icon_state = "LZ_Slider" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - -/obj/effect/pod_landingzone_effect/Initialize(mapload, obj/structure/closet/supplypod/pod) - transform = matrix() * 1.5 - animate(src, transform = matrix()*0.01, time = pod.landingDelay+pod.fallDuration) - ..() - -/obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to it's location - name = "Landing Zone Indicator" - desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." - icon = 'icons/obj/supplypods_32x32.dmi' - icon_state = "LZ" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - light_range = 2 - anchored = TRUE - alpha = 0 - var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this pod_landingzone - var/obj/effect/pod_landingzone_effect/helper - var/list/smoke_effects = new /list(13) - -/obj/effect/ex_act() - return - -/obj/effect/pod_landingzone/Initialize(mapload, podParam, single_order = null, clientman) - . = ..() - if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) - podParam = new podParam() //If its just a path, instantiate it - pod = podParam - if (!pod.effectStealth) - helper = new (drop_location(), pod) - alpha = 255 - animate(src, transform = matrix().Turn(90), time = pod.landingDelay+pod.fallDuration) - if (single_order) - if (istype(single_order, /datum/supply_order)) - var/datum/supply_order/SO = single_order - SO.generate(pod) - else if (istype(single_order, /atom/movable)) - var/atom/movable/O = single_order - O.forceMove(pod) - for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to set their view to the pod_landingzone. This is so that they can see where they are about to land - M.reset_perspective(src) - if(pod.effectStun) //If effectStun is true, stun any mobs caught on this pod_landingzone until the pod gets a chance to hit them - for (var/mob/living/M in get_turf(src)) - M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. - if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) - pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound - pod.fallingSound = 'sound/weapons/mortar_whistle.ogg' - var/soundStartTime = pod.landingDelay - pod.fallingSoundLength + pod.fallDuration - if (soundStartTime < 0) - soundStartTime = 1 - if (!pod.effectQuiet) - addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) - addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) - -/obj/effect/pod_landingzone/proc/playFallingSound() - playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) - -/obj/effect/pod_landingzone/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle - pod.addGlow() - pod.update_icon() - if (pod.style != STYLE_INVISIBLE) - pod.add_filter("motionblur",1,list("type"="motion_blur", "x"=0, "y"=3)) - pod.forceMove(drop_location()) - for (var/mob/living/M in pod) //Remember earlier (initialization) when we moved mobs into the pod_landingzone so they wouldnt get lost in nullspace? Time to get them out - M.reset_perspective(null) - var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from - pod.pixel_x = cos(angle)*32*length(smoke_effects) //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the pod_landingzone - pod.pixel_z = sin(angle)*32*length(smoke_effects) - var/rotation = Get_Pixel_Angle(pod.pixel_z, pod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps - setupSmoke(rotation) - pod.transform = matrix().Turn(rotation) - pod.layer = FLY_LAYER - if (pod.style != STYLE_INVISIBLE) - animate(pod.get_filter("motionblur"), y = 0, time = pod.fallDuration, flags = ANIMATION_PARALLEL) - animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.fallDuration, easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle! - addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation - -/obj/effect/pod_landingzone/proc/setupSmoke(rotation) - if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) - return - for ( var/i in 1 to length(smoke_effects)) - var/obj/effect/supplypod_smoke/S = new (drop_location()) - if (i == 1) - S.layer = FLY_LAYER - S.icon_state = "smoke_start" - S.transform = matrix().Turn(rotation) - smoke_effects[i] = S - S.pixel_x = sin(rotation)*32 * i - S.pixel_y = abs(cos(rotation))*32 * i - S.filters += filter(type = "blur", size = 4) - var/time = (pod.fallDuration / length(smoke_effects))*(length(smoke_effects)-i) - addtimer(CALLBACK(S, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation - QDEL_IN(S, pod.fallDuration + 35) - -/obj/effect/pod_landingzone/proc/drawSmoke() - if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) - return - for (var/obj/effect/supplypod_smoke/S in smoke_effects) - animate(S, alpha = 0, time = 20, flags = ANIMATION_PARALLEL) - animate(S.filters[1], size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) - -/obj/effect/pod_landingzone/proc/endLaunch() - pod.tryMakeRubble(drop_location()) - pod.layer = initial(pod.layer) - pod.endGlow() - QDEL_NULL(helper) - pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place - drawSmoke() - qdel(src) //The pod_landingzone's purpose is complete. It can rest easy now - -//------------------------------------UPGRADES-------------------------------------// -/obj/item/disk/cargo/bluespace_pod //Disk that can be inserted into the Express Console to allow for Advanced Bluespace Pods - name = "Bluespace Drop Pod Upgrade" - desc = "This disk provides a firmware update to the Express Supply Console, granting the use of Nanotrasen's Bluespace Drop Pods to the supply department." - icon = 'icons/obj/module.dmi' - icon_state = "cargodisk" - inhand_icon_state = "card-id" - w_class = WEIGHT_CLASS_SMALL - -#undef SUPPLYPOD_X_OFFSET +#define SUPPLYPOD_X_OFFSET -16 + +//The "pod_landingzone" temp visual is created by anything that "launches" a supplypod. This is what animates the pod and makes the pod forcemove to the station. +//------------------------------------SUPPLY POD-------------------------------------// +/obj/structure/closet/supplypod + name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe + desc = "A Nanotrasen supply drop pod." + icon = 'icons/obj/supplypods.dmi' + icon_state = "pod" //This is a common base sprite shared by a number of pods + pixel_x = SUPPLYPOD_X_OFFSET //2x2 sprite + layer = BELOW_OBJ_LAYER //So that the crate inside doesn't appear underneath + allow_objects = TRUE + allow_dense = TRUE + delivery_icon = null + can_weld_shut = FALSE + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) + anchored = TRUE //So it cant slide around after landing + anchorable = FALSE + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + appearance_flags = KEEP_TOGETHER | PIXEL_SCALE + density = FALSE + + //*****NOTE*****: Many of these comments are similarly described in centcom_podlauncher.dm. If you change them here, please consider doing so in the centcom podlauncher code as well! + var/adminNamed = FALSE //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) + var/bluespace = FALSE //If true, the pod deletes (in a shower of sparks) after landing + var/landingDelay = 30 //How long the pod takes to land after launching + var/openingDelay = 30 //How long the pod takes to open after landing + var/departureDelay = 30 //How long the pod takes to leave after opening. If bluespace = TRUE, it deletes. If reversing = TRUE, it flies back to centcom. + var/damage = 0 //Damage that occurs to any mob under the pod when it lands. + var/effectStun = FALSE //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! + var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands + var/effectOrgans = FALSE //If true, yeets out every limb and organ from anyone caught under the pod when it lands + var/effectGib = FALSE //If true, anyone under the pod will be gibbed when it lands + var/effectStealth = FALSE //If true, a target icon isn't displayed on the turf where the pod will land + var/effectQuiet = FALSE //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) + var/effectMissile = FALSE //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground + var/effectCircle = FALSE //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here + var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. + var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom + var/turf/reverse_dropoff_turf //Turf that the reverse pod will drop off it's newly-acquired cargo to + var/fallDuration = 4 + var/fallingSoundLength = 11 + var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands + var/landingSound //Admin sound to play when the pod lands + var/openingSound //Admin sound to play when the pod opens + var/leavingSound //Admin sound to play when the pod leaves + var/soundVolume = 80 //Volume to play sounds at. Ignores the cap + var/list/explosionSize = list(0,0,2,3) + var/stay_after_drop = FALSE + var/specialised = FALSE // It's not a general use pod for cargo/admin use + var/rubble_type //Rubble effect associated with this supplypod + var/decal = "default" //What kind of extra decals we add to the pod to make it look nice + var/door = "pod_door" + var/fin_mask = "topfin" + var/obj/effect/supplypod_rubble/rubble + var/obj/effect/engineglow/glow_effect + var/effectShrapnel = FALSE + var/shrapnel_type = /obj/projectile/bullet/shrapnel + var/shrapnel_magnitude = 3 + +/obj/structure/closet/supplypod/bluespacepod + style = STYLE_BLUESPACE + bluespace = TRUE + explosionSize = list(0,0,1,2) + landingDelay = 15 //Slightly quicker than the supplypod + +/obj/structure/closet/supplypod/extractionpod + name = "Syndicate Extraction Pod" + desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas. Targets must be manually stuffed inside the pod for proper delivery." + specialised = TRUE + style = STYLE_SYNDICATE + bluespace = TRUE + explosionSize = list(0,0,1,2) + landingDelay = 25 //Longer than others + +/obj/structure/closet/supplypod/centcompod + style = STYLE_CENTCOM + bluespace = TRUE + explosionSize = list(0,0,0,0) + landingDelay = 20 //Very speedy! + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/structure/closet/supplypod/Initialize() + . = ..() + setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly + +/obj/structure/closet/supplypod/extractionpod/Initialize() + . = ..() + reverse_dropoff_turf = pick(GLOB.holdingfacility) + +/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, duringInit = FALSE) //Used to give the sprite an icon state, name, and description. Should only be called once + if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM. + setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly. + return + style = chosenStyle + var/base = POD_STYLES[chosenStyle][POD_BASE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. + icon_state = base + decal = POD_STYLES[chosenStyle][POD_DECAL] + rubble_type = POD_STYLES[chosenStyle][POD_RUBBLE_TYPE] + if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum + name = POD_STYLES[chosenStyle][POD_NAME] + desc = POD_STYLES[chosenStyle][POD_DESC] + door = "[base]_door" + update_icon() + +/obj/structure/closet/supplypod/proc/SetReverseIcon() + fin_mask = "bottomfin" + if (POD_STYLES[style][POD_SHAPE] == POD_SHAPE_NORML) + icon_state = POD_STYLES[style][POD_BASE] + "_reverse" + pixel_x = initial(pixel_x) + transform = matrix() + update_icon() + +/obj/structure/closet/supplypod/proc/backToNonReverseIcon() + fin_mask = initial(fin_mask) + if (POD_STYLES[style][POD_SHAPE] == POD_SHAPE_NORML) + icon_state = POD_STYLES[style][POD_BASE] + pixel_x = initial(pixel_x) + transform = matrix() + update_icon() + +/obj/structure/closet/supplypod/closet_update_overlays(list/new_overlays) + return + +/obj/structure/closet/supplypod/update_overlays() + . = ..() + if (style == STYLE_INVISIBLE) + return + if (rubble) + . += rubble.getForeground(src) + if (style == STYLE_SEETHROUGH) + for (var/atom/A in contents) + var/mutable_appearance/itemIcon = new(A) + itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) + . += itemIcon + return + + if (opened) //We're opened means all we have to worry about is masking a decal if we have one + if (!decal) //We don't have a decal to mask + return + var/icon/masked_decal = new(icon, decal) //The decal we want to apply + var/icon/door_masker = new(icon, door) //The door shape we want to 'cut out' of the decal + door_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + door_masker.SwapColor("#ffffffff", null) + door_masker.Blend("#000000", ICON_SUBTRACT) + masked_decal.Blend(door_masker, ICON_ADD) + . += masked_decal + else //If we're closed + if (POD_STYLES[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking + . += door + else + var/icon/masked_door = new(icon, door) //The door we want to apply + var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door + fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + fin_masker.SwapColor("#ffffffff", null) + fin_masker.Blend("#000000", ICON_SUBTRACT) + masked_door.Blend(fin_masker, ICON_ADD) + . += masked_door + if (decal) + . += decal + +/obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) + if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. + return FALSE + else + ..() + +/obj/structure/closet/supplypod/ex_act() //Explosions dont do SHIT TO US! This is because supplypods create explosions when they land. + return + +/obj/structure/closet/supplypod/contents_explosion() //Supplypods also protect their contents from the harmful effects of fucking exploding. + return + +/obj/structure/closet/supplypod/toggle(mob/living/user) + return + +/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) + return + +/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting(atom/movable/holder = src) + reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() ) + bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever + audible_message("The pod hisses, closing and launching itself away from the station.", "The ground vibrates, and you hear the sound of engines firing.") + stay_after_drop = FALSE + holder.pixel_z = initial(holder.pixel_z) + holder.alpha = initial(holder.alpha) + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/fly_me_to_the_moon] + forceMove(shippingLane) //Move to the centcom-z-level until the pod_landingzone says we can drop back down again + if (!reverse_dropoff_turf) //If we're centcom-launched, the reverse dropoff turf will be a centcom loading bay. If we're an extraction pod, it should be the ninja jail. + reverse_dropoff_turf = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas + landingDelay = initial(landingDelay) //Reset the landing timers so we land on whatever turf we're aiming at normally. Will be changed to be editable later (tm) + fallDuration = initial(fallDuration) //This is so if someone adds a really long dramatic landing time they don't have to sit through it twice on the pod's return trip + openingDelay = initial(openingDelay) + backToNonReverseIcon() + new /obj/effect/pod_landingzone(reverse_dropoff_turf, src) + +/obj/structure/closet/supplypod/proc/preOpen() //Called before the open_pod() proc. Handles anything that occurs right as the pod lands. + var/turf/T = get_turf(src) + var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p + density = TRUE //Density is originally false so the pod doesn't block anything while it's still falling through the air + if (landingSound) + playsound(get_turf(src), landingSound, soundVolume, FALSE, FALSE) + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) + if(effectShrapnel) + SEND_SIGNAL(src, COMSIG_SUPPLYPOD_LANDED) + for (var/mob/living/M in T) + if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people): + var/mob/living/carbon/CM = M + for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! + break + if (effectOrgans && iscarbon(M)) //effectOrgans means remove every organ in our mob + var/mob/living/carbon/CM = M + for(var/X in CM.internal_organs) + var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) //Pick a random direction to toss them in + var/obj/item/organ/O = X + O.Remove(CM) //Note that this isn't the same proc as for lists + O.forceMove(T) //Move the organ outta the body + O.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away + sleep(1) + for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! + bodypart.throw_at(destination, 2, 3) + sleep(1) + + if (effectGib) //effectGib is on, that means whatever's underneath us better be fucking oof'd on + M.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) + M.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs + M.adjustBruteLoss(damage) + var/explosion_sum = B[1] + B[2] + B[3] + B[4] + if (explosion_sum != 0) //If the explosion list isn't all zeroes, call an explosion + explosion(get_turf(src), B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing + else if (!effectQuiet) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) + playsound(src, "explosion", landingSound ? 15 : 80, TRUE) + if (effectMissile) //If we are acting like a missile, then right after we land and finish fucking shit up w explosions, we should delete + opened = TRUE //We set opened to TRUE to avoid spending time trying to open (due to being deleted) during the Destroy() proc + qdel(src) + return + if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges + var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src) + benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. + moveToNullspace() + addtimer(CALLBACK(src, .proc/open_pod, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob + else if (style == STYLE_SEETHROUGH) + open_pod(src) + else + addtimer(CALLBACK(src, .proc/open_pod, src), openingDelay) //After the openingDelay passes, we use the open proc from this supplypod, while referencing this supplypod's contents + +/obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with + if (!holder) + return + if (opened) //This is to ensure we don't open something that has already been opened + return + holder.setOpened() + var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about + var/mob/M + if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents + M = holder + if (M.key && !forced && !broken) //If we are player controlled, then we shouldn't open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) + return + if (openingSound) + playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play + for (var/atom/movable/O in holder.contents) //Go through the contents of the holder + O.forceMove(T) //move everything from the contents of the holder to the turf of the holder + if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound + playsound(get_turf(holder), open_sound, 15, TRUE, -3) + if (broken) //If the pod is opening because it's been destroyed, we end here + return + if (style == STYLE_SEETHROUGH) + startExitSequence(src) + else + if (reversing) + addtimer(CALLBACK(src, .proc/SetReverseIcon), departureDelay/2) //Finish up the pod's duties after a certain amount of time + if(!stay_after_drop) // Departing should be handled manually + addtimer(CALLBACK(src, .proc/startExitSequence, holder), departureDelay) //Finish up the pod's duties after a certain amount of time + +/obj/structure/closet/supplypod/proc/startExitSequence(atom/movable/holder) + if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom + close(holder) + else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) + deleteRubble() + if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH) + do_sparks(5, TRUE, holder) //Create some sparks right before closing + qdel(src) //Delete ourselves and the holder + if (holder != src) + qdel(holder) + +/obj/structure/closet/supplypod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true + if (!holder) + return + take_contents(holder) + playsound(holder, close_sound, close_sound_volume, TRUE, -3) + holder.setClosed() + addtimer(CALLBACK(src, .proc/preReturn, holder), 10) //Start to leave a second after closing for cinematic effect + +/obj/structure/closet/supplypod/take_contents(atom/movable/holder) + var/atom/L = holder.drop_location() + for(var/atom/movable/AM in L) + if(AM != src && !insert(AM, holder)) // Can't insert that + continue + +/obj/structure/closet/supplypod/insert(atom/movable/AM, atom/movable/holder) + if(insertion_allowed(AM)) + AM.forceMove(holder) + return TRUE + else + return FALSE + +/obj/structure/closet/supplypod/insertion_allowed(atom/movable/AM) + if(ismob(AM)) + if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... + return FALSE + var/mob/living/L = AM + if(L.anchored || L.incorporeal_move) + return FALSE + L.stop_pulling() + + else if(isobj(AM)) + if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) + return FALSE + else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) + return TRUE + else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) + return FALSE + else + return FALSE + + return TRUE + +/obj/structure/closet/supplypod/proc/preReturn(atom/movable/holder) + if (leavingSound) + playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) + deleteRubble() + animate(holder, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) + animate(holder, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising pod + + addtimer(CALLBACK(src, .proc/handleReturnAfterDeparting, holder), 15) //Finish up the pod's duties after a certain amount of time + +/obj/structure/closet/supplypod/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open_pod() proc for more details + opened = TRUE + density = FALSE + update_icon() + +/obj/structure/closet/supplypod/extractionpod/setOpened() + opened = TRUE + density = TRUE + update_icon() + +/obj/structure/closet/supplypod/setClosed() //Ditto + opened = FALSE + density = TRUE + update_icon() + +/obj/structure/closet/supplypod/proc/tryMakeRubble(turf/T) //Ditto + if (rubble_type == RUBBLE_NONE) + return + if (rubble) + return + if (effectMissile) + return + if (isspaceturf(T) || isclosedturf(T)) + return + rubble = new /obj/effect/supplypod_rubble(T) + rubble.setStyle(rubble_type, src) + update_icon() + +/obj/structure/closet/supplypod/Moved() + deleteRubble() + return ..() + +/obj/structure/closet/supplypod/proc/deleteRubble() + rubble?.fadeAway() + rubble = null + update_icon() + +/obj/structure/closet/supplypod/proc/addGlow() + if (POD_STYLES[style][POD_SHAPE] != POD_SHAPE_NORML) + return + glow_effect = new(src) + glow_effect.icon_state = "pod_glow_" + POD_STYLES[style][POD_GLOW] + vis_contents += glow_effect + glow_effect.layer = GASFIRE_LAYER + +/obj/structure/closet/supplypod/proc/endGlow() + if(!glow_effect) + return + glow_effect.layer = LOW_ITEM_LAYER + glow_effect.fadeAway(openingDelay) + +/obj/structure/closet/supplypod/Destroy() + deleteRubble() + open_pod(src, broken = TRUE) //Lets dump our contents by opening up + return ..() + +//------------------------------------TEMPORARY_VISUAL-------------------------------------// +/obj/effect/supplypod_smoke //Falling pod smoke + name = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "smoke" + desc = "" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 0 + +/obj/effect/engineglow //Falling pod smoke + name = "" + icon = 'icons/obj/supplypods.dmi' + icon_state = "pod_engineglow" + desc = "" + layer = GASFIRE_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 255 + +/obj/effect/engineglow/proc/fadeAway(leaveTime) + var/duration = min(leaveTime, 25) + animate(src, alpha=0, time = duration) + QDEL_IN(src, duration + 5) + +/obj/effect/supplypod_smoke/proc/drawSelf(amount) + alpha = max(0, 255-(amount*20)) + +/obj/effect/supplypod_rubble //This is the object that forceMoves the supplypod to it's location + name = "Debris" + desc = "A small crater of rubble. Closer inspection reveals the debris to be made primarily of space-grade metal fragments. You're pretty sure that this will disperse before too long." + icon = 'icons/obj/supplypods.dmi' + layer = PROJECTILE_HIT_THRESHHOLD_LAYER // We want this to go right below the layer of supplypods and supplypod_rubble's forground. + icon_state = "rubble_bg" + anchored = TRUE + pixel_x = SUPPLYPOD_X_OFFSET + var/foreground = "rubble_fg" + var/verticle_offset = 0 + +/obj/effect/supplypod_rubble/proc/getForeground(obj/structure/closet/supplypod/pod) + var/mutable_appearance/rubble_overlay = mutable_appearance('icons/obj/supplypods.dmi', foreground) + rubble_overlay.appearance_flags = KEEP_APART|RESET_TRANSFORM + rubble_overlay.transform = matrix().Translate(SUPPLYPOD_X_OFFSET - pod.pixel_x, verticle_offset) + return rubble_overlay + +/obj/effect/supplypod_rubble/proc/fadeAway() + animate(src, alpha=0, time = 30) + QDEL_IN(src, 35) + +/obj/effect/supplypod_rubble/proc/setStyle(type, obj/structure/closet/supplypod/pod) + if (type == RUBBLE_WIDE) + icon_state += "_wide" + foreground += "_wide" + if (type == RUBBLE_THIN) + icon_state += "_thin" + foreground += "_thin" + if (pod.style == STYLE_BOX) + verticle_offset = -2 + else + verticle_offset = initial(verticle_offset) + + pixel_y = verticle_offset + +/obj/effect/pod_landingzone_effect + name = "" + desc = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "LZ_Slider" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + +/obj/effect/pod_landingzone_effect/Initialize(mapload, obj/structure/closet/supplypod/pod) + transform = matrix() * 1.5 + animate(src, transform = matrix()*0.01, time = pod.landingDelay+pod.fallDuration) + ..() + +/obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to it's location + name = "Landing Zone Indicator" + desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "LZ" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + light_range = 2 + anchored = TRUE + alpha = 0 + var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this pod_landingzone + var/obj/effect/pod_landingzone_effect/helper + var/list/smoke_effects = new /list(13) + +/obj/effect/ex_act() + return + +/obj/effect/pod_landingzone/Initialize(mapload, podParam, single_order = null, clientman) + . = ..() + if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) + podParam = new podParam() //If its just a path, instantiate it + pod = podParam + if (!pod.effectStealth) + helper = new (drop_location(), pod) + alpha = 255 + animate(src, transform = matrix().Turn(90), time = pod.landingDelay+pod.fallDuration) + if (single_order) + if (istype(single_order, /datum/supply_order)) + var/datum/supply_order/SO = single_order + SO.generate(pod) + else if (istype(single_order, /atom/movable)) + var/atom/movable/O = single_order + O.forceMove(pod) + for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to set their view to the pod_landingzone. This is so that they can see where they are about to land + M.reset_perspective(src) + if(pod.effectStun) //If effectStun is true, stun any mobs caught on this pod_landingzone until the pod gets a chance to hit them + for (var/mob/living/M in get_turf(src)) + M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. + if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) + pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound + pod.fallingSound = 'sound/weapons/mortar_whistle.ogg' + var/soundStartTime = pod.landingDelay - pod.fallingSoundLength + pod.fallDuration + if (soundStartTime < 0) + soundStartTime = 1 + if (!pod.effectQuiet) + addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) + addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) + +/obj/effect/pod_landingzone/proc/playFallingSound() + playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) + +/obj/effect/pod_landingzone/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle + pod.addGlow() + pod.update_icon() + if (pod.style != STYLE_INVISIBLE) + pod.add_filter("motionblur",1,list("type"="motion_blur", "x"=0, "y"=3)) + pod.forceMove(drop_location()) + for (var/mob/living/M in pod) //Remember earlier (initialization) when we moved mobs into the pod_landingzone so they wouldnt get lost in nullspace? Time to get them out + M.reset_perspective(null) + var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from + pod.pixel_x = cos(angle)*32*length(smoke_effects) //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the pod_landingzone + pod.pixel_z = sin(angle)*32*length(smoke_effects) + var/rotation = Get_Pixel_Angle(pod.pixel_z, pod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps + setupSmoke(rotation) + pod.transform = matrix().Turn(rotation) + pod.layer = FLY_LAYER + if (pod.style != STYLE_INVISIBLE) + animate(pod.get_filter("motionblur"), y = 0, time = pod.fallDuration, flags = ANIMATION_PARALLEL) + animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.fallDuration, easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle! + addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + +/obj/effect/pod_landingzone/proc/setupSmoke(rotation) + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for ( var/i in 1 to length(smoke_effects)) + var/obj/effect/supplypod_smoke/S = new (drop_location()) + if (i == 1) + S.layer = FLY_LAYER + S.icon_state = "smoke_start" + S.transform = matrix().Turn(rotation) + smoke_effects[i] = S + S.pixel_x = sin(rotation)*32 * i + S.pixel_y = abs(cos(rotation))*32 * i + S.filters += filter(type = "blur", size = 4) + var/time = (pod.fallDuration / length(smoke_effects))*(length(smoke_effects)-i) + addtimer(CALLBACK(S, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + QDEL_IN(S, pod.fallDuration + 35) + +/obj/effect/pod_landingzone/proc/drawSmoke() + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for (var/obj/effect/supplypod_smoke/S in smoke_effects) + animate(S, alpha = 0, time = 20, flags = ANIMATION_PARALLEL) + animate(S.filters[1], size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) + +/obj/effect/pod_landingzone/proc/endLaunch() + pod.tryMakeRubble(drop_location()) + pod.layer = initial(pod.layer) + pod.endGlow() + QDEL_NULL(helper) + pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place + drawSmoke() + qdel(src) //The pod_landingzone's purpose is complete. It can rest easy now + +//------------------------------------UPGRADES-------------------------------------// +/obj/item/disk/cargo/bluespace_pod //Disk that can be inserted into the Express Console to allow for Advanced Bluespace Pods + name = "Bluespace Drop Pod Upgrade" + desc = "This disk provides a firmware update to the Express Supply Console, granting the use of Nanotrasen's Bluespace Drop Pods to the supply department." + icon = 'icons/obj/module.dmi' + icon_state = "cargodisk" + inhand_icon_state = "card-id" + w_class = WEIGHT_CLASS_SMALL + +#undef SUPPLYPOD_X_OFFSET diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index b857b9d3515..5c4ead8e189 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -1,179 +1,179 @@ - -/client - ////////////////////// - //BLACK MAGIC THINGS// - ////////////////////// - parent_type = /datum - //////////////// - //ADMIN THINGS// - //////////////// - ///Contains admin info. Null if client is not an admin. - var/datum/admins/holder = null - ///Needs to implement InterceptClickOn(user,params,atom) proc - var/datum/click_intercept = null - ///Used for admin AI interaction - var/AI_Interact = FALSE - - ///Used to cache this client's bans to save on DB queries - var/ban_cache = null - ///Contains the last message sent by this client - used to protect against copy-paste spamming. - var/last_message = "" - ///contins a number of how many times a message identical to last_message was sent. - var/last_message_count = 0 - ///How many messages sent in the last 10 seconds - var/total_message_count = 0 - ///Next tick to reset the total message counter - var/total_count_reset = 0 - ///Internal counter for clients sending external (IRC/Discord) relay messages via ahelp to prevent spamming. Set to a number every time an admin reply is sent, decremented for every client send. - var/externalreplyamount = 0 - - ///////// - //OTHER// - ///////// - ///Player preferences datum for the client - var/datum/preferences/prefs = null - ///last turn of the controlled mob, I think this is only used by mechs? - var/last_turn = 0 - ///Move delay of controlled mob, related to input handling - var/move_delay = 0 - ///Current area of the controlled mob - var/area = null - - /////////////// - //SOUND STUFF// - /////////////// - ///Currently playing ambience sound - var/ambience_playing = null - ///Whether an ambience sound has been played and one shouldn't be played again, unset by a callback - var/played = FALSE - //////////// - //SECURITY// - //////////// - // comment out the line below when debugging locally to enable the options & messages menu - control_freak = 1 - - //////////////////////////////////// - //things that require the database// - //////////////////////////////////// - ///Used to determine how old the account is - in days. - var/player_age = -1 - ///Date that this account was first seen in the server - var/player_join_date = null - ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip - var/related_accounts_ip = "Requires database" - ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id - var/related_accounts_cid = "Requires database" - ///Date of byond account creation in ISO 8601 format - var/account_join_date = null - ///Age of byond account in days - var/account_age = -1 - - preload_rsc = PRELOAD_RSC - - var/obj/screen/click_catcher/void - - ///used to make a special mouse cursor, this one for mouse up icon - var/mouse_up_icon = null - ///used to make a special mouse cursor, this one for mouse up icon - var/mouse_down_icon = null - - ///Used for ip intel checking to identify evaders, disabled because of issues with traffic - var/ip_intel = "Disabled" - - ///datum that controls the displaying and hiding of tooltips - var/datum/tooltip/tooltips - - ///Last ping of the client - var/lastping = 0 - ///Average ping of the client - var/avgping = 0 - ///world.time they connected - var/connection_time - ///world.realtime they connected - var/connection_realtime - ///world.timeofday they connected - var/connection_timeofday - - ///If the client is currently in player preferences - var/inprefs = FALSE - ///Used for limiting the rate of topic sends by the client to avoid abuse - var/list/topiclimiter - ///Used for limiting the rate of clicks sends by the client to avoid abuse - var/list/clicklimiter - - ///goonchat chatoutput of the client - var/datum/chat_output/chatOutput - - ///lazy list of all credit object bound to this client - var/list/credits - - ///these persist between logins/logouts during the same round. - var/datum/player_details/player_details - - ///Should only be a key-value list of north/south/east/west = obj/screen. - var/list/char_render_holders - - ///Amount of keydowns in the last keysend checking interval - var/client_keysend_amount = 0 - ///World tick time where client_keysend_amount will reset - var/next_keysend_reset = 0 - ///World tick time where keysend_tripped will reset back to false - var/next_keysend_trip_reset = 0 - ///When set to true, user will be autokicked if they trip the keysends in a second limit again - var/keysend_tripped = FALSE - ///custom movement keys for this client - var/list/movement_keys = list() - - ///Autoclick list of two elements, first being the clicked thing, second being the parameters. - var/list/atom/selected_target[2] - ///Autoclick variable referencing the associated item. - var/obj/item/active_mousedown_item = null - ///Used in MouseDrag to preserve the original mouse click parameters - var/mouseParams = "" - ///Used in MouseDrag to preserve the last mouse-entered location. - var/mouseLocation = null - ///Used in MouseDrag to preserve the last mouse-entered object. - var/mouseObject = null - //Middle-mouse-button click dragtime control for aimbot exploit detection. - var/middragtime = 0 - //Middle-mouse-button clicked object control for aimbot exploit detection. - var/atom/middragatom - - /// Messages currently seen by this client - var/list/seen_messages - var/datum/view_data/view_size - ///A lazy list of atoms we've examined in the last EXAMINE_MORE_TIME (default 1.5) seconds, so that we will call [atom/proc/examine_more()] instead of [atom/proc/examine()] on them when examining - var/list/recent_examines - - var/list/parallax_layers - var/list/parallax_layers_cached - var/atom/movable/movingmob - var/turf/previous_turf - ///world.time of when we can state animate()ing parallax again - var/dont_animate_parallax - ///world.time of last parallax update - var/last_parallax_shift - ///ds between parallax updates - var/parallax_throttle = 0 - var/parallax_movedir = 0 - var/parallax_layers_max = 4 - var/parallax_animate_timer - - /** - * Assoc list with all the active maps - when a screen obj is added to - * a map, it's put in here as well. - * - * Format: list( = list(/obj/screen)) - */ - var/list/screen_maps = list() - - // List of all asset filenames sent to this client by the asset cache, along with their assoicated md5s - var/list/sent_assets = list() - /// List of all completed blocking send jobs awaiting acknowledgement by send_asset - var/list/completed_asset_jobs = list() - /// Last asset send job id. - var/last_asset_job = 0 - var/last_completed_asset_job = 0 - - /// rate limiting for the crew manifest - var/crew_manifest_delay + +/client + ////////////////////// + //BLACK MAGIC THINGS// + ////////////////////// + parent_type = /datum + //////////////// + //ADMIN THINGS// + //////////////// + ///Contains admin info. Null if client is not an admin. + var/datum/admins/holder = null + ///Needs to implement InterceptClickOn(user,params,atom) proc + var/datum/click_intercept = null + ///Used for admin AI interaction + var/AI_Interact = FALSE + + ///Used to cache this client's bans to save on DB queries + var/ban_cache = null + ///Contains the last message sent by this client - used to protect against copy-paste spamming. + var/last_message = "" + ///contins a number of how many times a message identical to last_message was sent. + var/last_message_count = 0 + ///How many messages sent in the last 10 seconds + var/total_message_count = 0 + ///Next tick to reset the total message counter + var/total_count_reset = 0 + ///Internal counter for clients sending external (IRC/Discord) relay messages via ahelp to prevent spamming. Set to a number every time an admin reply is sent, decremented for every client send. + var/externalreplyamount = 0 + + ///////// + //OTHER// + ///////// + ///Player preferences datum for the client + var/datum/preferences/prefs = null + ///last turn of the controlled mob, I think this is only used by mechs? + var/last_turn = 0 + ///Move delay of controlled mob, related to input handling + var/move_delay = 0 + ///Current area of the controlled mob + var/area = null + + /////////////// + //SOUND STUFF// + /////////////// + ///Currently playing ambience sound + var/ambience_playing = null + ///Whether an ambience sound has been played and one shouldn't be played again, unset by a callback + var/played = FALSE + //////////// + //SECURITY// + //////////// + // comment out the line below when debugging locally to enable the options & messages menu + control_freak = 1 + + //////////////////////////////////// + //things that require the database// + //////////////////////////////////// + ///Used to determine how old the account is - in days. + var/player_age = -1 + ///Date that this account was first seen in the server + var/player_join_date = null + ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip + var/related_accounts_ip = "Requires database" + ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id + var/related_accounts_cid = "Requires database" + ///Date of byond account creation in ISO 8601 format + var/account_join_date = null + ///Age of byond account in days + var/account_age = -1 + + preload_rsc = PRELOAD_RSC + + var/obj/screen/click_catcher/void + + ///used to make a special mouse cursor, this one for mouse up icon + var/mouse_up_icon = null + ///used to make a special mouse cursor, this one for mouse up icon + var/mouse_down_icon = null + + ///Used for ip intel checking to identify evaders, disabled because of issues with traffic + var/ip_intel = "Disabled" + + ///datum that controls the displaying and hiding of tooltips + var/datum/tooltip/tooltips + + ///Last ping of the client + var/lastping = 0 + ///Average ping of the client + var/avgping = 0 + ///world.time they connected + var/connection_time + ///world.realtime they connected + var/connection_realtime + ///world.timeofday they connected + var/connection_timeofday + + ///If the client is currently in player preferences + var/inprefs = FALSE + ///Used for limiting the rate of topic sends by the client to avoid abuse + var/list/topiclimiter + ///Used for limiting the rate of clicks sends by the client to avoid abuse + var/list/clicklimiter + + ///goonchat chatoutput of the client + var/datum/chat_output/chatOutput + + ///lazy list of all credit object bound to this client + var/list/credits + + ///these persist between logins/logouts during the same round. + var/datum/player_details/player_details + + ///Should only be a key-value list of north/south/east/west = obj/screen. + var/list/char_render_holders + + ///Amount of keydowns in the last keysend checking interval + var/client_keysend_amount = 0 + ///World tick time where client_keysend_amount will reset + var/next_keysend_reset = 0 + ///World tick time where keysend_tripped will reset back to false + var/next_keysend_trip_reset = 0 + ///When set to true, user will be autokicked if they trip the keysends in a second limit again + var/keysend_tripped = FALSE + ///custom movement keys for this client + var/list/movement_keys = list() + + ///Autoclick list of two elements, first being the clicked thing, second being the parameters. + var/list/atom/selected_target[2] + ///Autoclick variable referencing the associated item. + var/obj/item/active_mousedown_item = null + ///Used in MouseDrag to preserve the original mouse click parameters + var/mouseParams = "" + ///Used in MouseDrag to preserve the last mouse-entered location. + var/mouseLocation = null + ///Used in MouseDrag to preserve the last mouse-entered object. + var/mouseObject = null + //Middle-mouse-button click dragtime control for aimbot exploit detection. + var/middragtime = 0 + //Middle-mouse-button clicked object control for aimbot exploit detection. + var/atom/middragatom + + /// Messages currently seen by this client + var/list/seen_messages + var/datum/view_data/view_size + ///A lazy list of atoms we've examined in the last EXAMINE_MORE_TIME (default 1.5) seconds, so that we will call [atom/proc/examine_more()] instead of [atom/proc/examine()] on them when examining + var/list/recent_examines + + var/list/parallax_layers + var/list/parallax_layers_cached + var/atom/movable/movingmob + var/turf/previous_turf + ///world.time of when we can state animate()ing parallax again + var/dont_animate_parallax + ///world.time of last parallax update + var/last_parallax_shift + ///ds between parallax updates + var/parallax_throttle = 0 + var/parallax_movedir = 0 + var/parallax_layers_max = 4 + var/parallax_animate_timer + + /** + * Assoc list with all the active maps - when a screen obj is added to + * a map, it's put in here as well. + * + * Format: list( = list(/obj/screen)) + */ + var/list/screen_maps = list() + + // List of all asset filenames sent to this client by the asset cache, along with their assoicated md5s + var/list/sent_assets = list() + /// List of all completed blocking send jobs awaiting acknowledgement by send_asset + var/list/completed_asset_jobs = list() + /// Last asset send job id. + var/last_asset_job = 0 + var/last_completed_asset_job = 0 + + /// rate limiting for the crew manifest + var/crew_manifest_delay diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 01b44231bbc..1e4d221d3b1 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -1,1973 +1,1973 @@ -GLOBAL_LIST_EMPTY(preferences_datums) - -/datum/preferences - var/client/parent - //doohickeys for savefiles - var/path - var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used - var/max_save_slots = 3 - - //non-preference stuff - var/muted = 0 - var/last_ip - var/last_id - - //game-preferences - var/lastchangelog = "" //Saved changlog filesize to detect if there was a change - var/ooccolor = "#c43b23" - var/asaycolor = "#ff4500" //This won't change the color for current admins, only incoming ones. - var/enable_tips = TRUE - var/tip_delay = 500 //tip delay in milliseconds - - //Antag preferences - var/list/be_special = list() //Special role selection - var/tmp/old_be_special = 0 //Bitflag version of be_special, used to update old savefiles and nothing more - //If it's 0, that's good, if it's anything but 0, the owner of this prefs file's antag choices were, - //autocorrected this round, not that you'd need to check that. - - var/UI_style = null - var/buttons_locked = FALSE - var/hotkeys = TRUE - var/chat_on_map = TRUE - var/max_chat_length = CHAT_MESSAGE_MAX_LENGTH - var/see_chat_non_mob = TRUE - - // Custom Keybindings - var/list/key_bindings = list() - - var/tgui_fancy = TRUE - var/tgui_lock = TRUE - var/windowflashing = TRUE - var/toggles = TOGGLES_DEFAULT - var/db_flags - var/chat_toggles = TOGGLES_DEFAULT_CHAT - var/ghost_form = "ghost" - var/ghost_orbit = GHOST_ORBIT_CIRCLE - var/ghost_accs = GHOST_ACCS_DEFAULT_OPTION - var/ghost_others = GHOST_OTHERS_DEFAULT_OPTION - var/ghost_hud = 1 - var/inquisitive_ghost = 1 - var/allow_midround_antag = 1 - var/preferred_map = null - var/pda_style = MONO - var/pda_color = "#808000" - - var/uses_glasses_colour = 0 - - //character preferences - var/slot_randomized //keeps track of round-to-round randomization of the character slot, prevents overwriting - var/real_name //our character's name - var/gender = MALE //gender of character (well duh) - var/age = 30 //age of character - var/underwear = "Nude" //underwear type - var/underwear_color = "000" //underwear color - var/undershirt = "Nude" //undershirt type - var/socks = "Nude" //socks type - var/backpack = DBACKPACK //backpack type - var/jumpsuit_style = PREF_SUIT //suit/skirt - var/hairstyle = "Bald" //Hair type - var/hair_color = "000" //Hair color - var/facial_hairstyle = "Shaved" //Face hair type - var/facial_hair_color = "000" //Facial hair color - var/skin_tone = "caucasian1" //Skin color - var/eye_color = "000" //Eye color - var/datum/species/pref_species = new /datum/species/human() //Mutant race - var/list/features = list("mcolor" = "FFF", "ethcolor" = "9c3030", "tail_lizard" = "Smooth", "tail_human" = "None", "snout" = "Round", "horns" = "None", "ears" = "None", "wings" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs", "moth_wings" = "Plain", "moth_markings" = "None") - var/list/randomise = list(RANDOM_UNDERWEAR = TRUE, RANDOM_UNDERWEAR_COLOR = TRUE, RANDOM_UNDERSHIRT = TRUE, RANDOM_SOCKS = TRUE, RANDOM_BACKPACK = TRUE, RANDOM_JUMPSUIT_STYLE = TRUE, RANDOM_HAIRSTYLE = TRUE, RANDOM_HAIR_COLOR = TRUE, RANDOM_FACIAL_HAIRSTYLE = TRUE, RANDOM_FACIAL_HAIR_COLOR = TRUE, RANDOM_SKIN_TONE = TRUE, RANDOM_EYE_COLOR = TRUE) - var/phobia = "spiders" - - var/list/custom_names = list() - var/preferred_ai_core_display = "Blue" - var/prefered_security_department = SEC_DEPT_RANDOM - - //Quirk list - var/list/all_quirks = list() - - //Job preferences 2.0 - indexed by job title , no key or value implies never - var/list/job_preferences = list() - - // Want randomjob if preferences already filled - Donkie - var/joblessrole = BERANDOMJOB //defaults to 1 for fewer assistants - - // 0 = character settings, 1 = game preferences - var/current_tab = 0 - - var/unlock_content = 0 - - var/list/ignoring = list() - - var/clientfps = 0 - - var/parallax - - var/ambientocclusion = TRUE - ///Should we automatically fit the viewport? - var/auto_fit_viewport = FALSE - ///Should we be in the widescreen mode set by the config? - var/widescreenpref = TRUE - ///What size should pixels be displayed as? 0 is strech to fit - var/pixel_size = 0 - ///What scaling method should we use? Distort means nearest neighbor - var/scaling_method = SCALING_METHOD_DISTORT - var/uplink_spawn_loc = UPLINK_PDA - ///The playtime_reward_cloak variable can be set to TRUE from the prefs menu only once the user has gained over 5K playtime hours. If true, it allows the user to get a cool looking roundstart cloak. - var/playtime_reward_cloak = FALSE - - var/list/exp = list() - var/list/menuoptions - - var/action_buttons_screen_locs = list() - - ///This var stores the amount of points the owner will get for making it out alive. - var/hardcore_survival_score = 0 - - ///Someone thought we were nice! We get a little heart in OOC until we join the server past the below time (we can keep it until the end of the round otherwise) - var/hearted - ///If we have a hearted commendations, we honor it every time the player loads preferences until this time has been passed - var/hearted_until - /// Agendered spessmen can choose whether to have a male or female bodytype - var/body_type - - /// If we have persistent scars enabled - var/persistent_scars = TRUE - /// We have 5 slots for persistent scars, if enabled we pick a random one to load (empty by default) and scars at the end of the shift if we survived as our original person - var/list/scars_list = list("1" = "", "2" = "", "3" = "", "4" = "", "5" = "") - /// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)] - var/scars_index = 1 - -/datum/preferences/New(client/C) - parent = C - - for(var/custom_name_id in GLOB.preferences_custom_names) - custom_names[custom_name_id] = get_default_name(custom_name_id) - - UI_style = GLOB.available_ui_styles[1] - if(istype(C)) - if(!IsGuestKey(C.key)) - load_path(C.ckey) - unlock_content = C.IsByondMember() - if(unlock_content) - max_save_slots = 8 - var/loaded_preferences_successfully = load_preferences() - if(loaded_preferences_successfully) - if(load_character()) - return - //we couldn't load character data so just randomize the character appearance + name - random_character() //let's create a random character then - rather than a fat, bald and naked man. - key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys - C?.update_movement_keys(src) - real_name = pref_species.random_name(gender,1) - if(!loaded_preferences_successfully) - save_preferences() - save_character() //let's save this new random character so it doesn't keep generating new ones. - menuoptions = list() - return - -#define APPEARANCE_CATEGORY_COLUMN "" -#define MAX_MUTANT_ROWS 4 - -/datum/preferences/proc/ShowChoices(mob/user) - if(!user || !user.client) - return - if(slot_randomized) - load_character(default_slot) // Reloads the character slot. Prevents random features from overwriting the slot if saved. - slot_randomized = FALSE - update_preview_icon() - var/list/dat = list("
                ") - - dat += "Character Settings" - dat += "Game Preferences" - dat += "OOC Preferences" - dat += "Custom Keybindings" - - if(!path) - dat += "
                Please create an account to save your preferences
                " - - dat += "
                " - - dat += "
                " - - switch(current_tab) - if (0) // Character Settings# - if(path) - var/savefile/S = new /savefile(path) - if(S) - dat += "
                " - var/name - var/unspaced_slots = 0 - for(var/i=1, i<=max_save_slots, i++) - unspaced_slots++ - if(unspaced_slots > 4) - dat += "
                " - unspaced_slots = 0 - S.cd = "/character[i]" - S["real_name"] >> name - if(!name) - name = "Character[i]" - dat += "[name] " - dat += "
                " - - dat += "

                Occupation Choices

                " - dat += "Set Occupation Preferences
                " - if(CONFIG_GET(flag/roundstart_traits)) - dat += "

                Quirk Setup

                " - dat += "Configure Quirks
                " - dat += "
                Current Quirks: [all_quirks.len ? all_quirks.Join(", ") : "None"]
                " - dat += "

                Identity

                " - dat += "" - - dat += "
                " - if(is_banned_from(user.ckey, "Appearance")) - dat += "You are banned from using custom names and appearances. You can continue to adjust your characters, but you will be randomised once you join the game.
                " - dat += "Random Name " - dat += "Always Random Name: [(randomise[RANDOM_NAME]) ? "Yes" : "No"]" - dat += "When Antagonist: [(randomise[RANDOM_NAME_ANTAG]) ? "Yes" : "No"]" - if(user.client.get_exp_living(TRUE) >= PLAYTIME_HARDCORE_RANDOM) - dat += "Hardcore Random: [(randomise[RANDOM_HARDCORE]) ? "Yes" : "No"]" - dat += "
                Name: " - dat += "[real_name]
                " - - if(!(AGENDER in pref_species.species_traits)) - var/dispGender - if(gender == MALE) - dispGender = "Male" - else if(gender == FEMALE) - dispGender = "Female" - else - dispGender = "Other" - dat += "Gender: [dispGender]" - if(gender == PLURAL || gender == NEUTER) - dat += "
                Body Type: [body_type == MALE ? "Male" : "Female"]" - - if(randomise[RANDOM_BODY] || randomise[RANDOM_BODY_ANTAG]) //doesn't work unless random body - dat += "Always Random Gender: [(randomise[RANDOM_GENDER]) ? "Yes" : "No"]" - dat += "When Antagonist: [(randomise[RANDOM_GENDER_ANTAG]) ? "Yes" : "No"]" - - dat += "
                Age: [age]" - if(randomise[RANDOM_BODY] || randomise[RANDOM_BODY_ANTAG]) //doesn't work unless random body - dat += "Always Random Age: [(randomise[RANDOM_AGE]) ? "Yes" : "No"]" - dat += "When Antagonist: [(randomise[RANDOM_AGE_ANTAG]) ? "Yes" : "No"]" - - dat += "

                Special Names:
                " - var/old_group - for(var/custom_name_id in GLOB.preferences_custom_names) - var/namedata = GLOB.preferences_custom_names[custom_name_id] - if(!old_group) - old_group = namedata["group"] - else if(old_group != namedata["group"]) - old_group = namedata["group"] - dat += "
                " - dat += "[namedata["pref_name"]]: [custom_names[custom_name_id]] " - dat += "

                " - - dat += "Custom Job Preferences:
                " - dat += "Preferred AI Core Display: [preferred_ai_core_display]
                " - dat += "Preferred Security Department: [prefered_security_department]
                " - - dat += "

                Body

                " - dat += "Random Body " - dat += "Always Random Body: [(randomise[RANDOM_BODY]) ? "Yes" : "No"]" - dat += "When Antagonist: [(randomise[RANDOM_BODY_ANTAG]) ? "Yes" : "No"]
                " - - dat += "" - if (user.client.get_exp_living(TRUE) >= PLAYTIME_VETERAN) - dat += "
                Don The Ultimate Gamer Cloak?:
                [(playtime_reward_cloak) ? "Enabled" : "Disabled"]
                " - var/use_skintones = pref_species.use_skintones - if(use_skintones) - - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Skin Tone

                " - - dat += "[skin_tone]" - dat += "[(randomise[RANDOM_SKIN_TONE]) ? "Lock" : "Unlock"]" - dat += "
                " - - var/mutant_colors - if((MUTCOLORS in pref_species.species_traits) || (MUTCOLORS_PARTSONLY in pref_species.species_traits)) - - if(!use_skintones) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Mutant Color

                " - - dat += "   Change
                " - - mutant_colors = TRUE - - if(istype(pref_species, /datum/species/ethereal)) //not the best thing to do tbf but I dont know whats better. - - if(!use_skintones) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Ethereal Color

                " - - dat += "   Change
                " - - - if((EYECOLOR in pref_species.species_traits) && !(NOEYESPRITES in pref_species.species_traits)) - - if(!use_skintones && !mutant_colors) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Eye Color

                " - dat += "   Change" - dat += "[(randomise[RANDOM_EYE_COLOR]) ? "Lock" : "Unlock"]" - - dat += "
                " - else if(use_skintones || mutant_colors) - dat += "" - - if(HAIR in pref_species.species_traits) - - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Hairstyle

                " - - dat += "[hairstyle]" - dat += "<>" - dat += "[(randomise[RANDOM_HAIRSTYLE]) ? "Lock" : "Unlock"]" - - dat += "
                   Change" - dat += "[(randomise[RANDOM_HAIR_COLOR]) ? "Lock" : "Unlock"]" - - dat += "

                Facial Hairstyle

                " - - dat += "[facial_hairstyle]" - dat += "<>" - dat += "[(randomise[RANDOM_FACIAL_HAIRSTYLE]) ? "Lock" : "Unlock"]" - - dat += "
                   Change" - dat += "[(randomise[RANDOM_FACIAL_HAIR_COLOR]) ? "Lock" : "Unlock"]" - dat += "
                " - - //Mutant stuff - var/mutant_category = 0 - - if("tail_lizard" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Tail

                " - - dat += "[features["tail_lizard"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("snout" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Snout

                " - - dat += "[features["snout"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("horns" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Horns

                " - - dat += "[features["horns"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("frills" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Frills

                " - - dat += "[features["frills"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("spines" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Spines

                " - - dat += "[features["spines"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("body_markings" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Body Markings

                " - - dat += "[features["body_markings"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("legs" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Legs

                " - - dat += "[features["legs"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("moth_wings" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Moth wings

                " - - dat += "[features["moth_wings"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("moth_markings" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Moth markings

                " - - dat += "[features["moth_markings"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("tail_human" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Tail

                " - - dat += "[features["tail_human"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if("ears" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Ears

                " - - dat += "[features["ears"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - //Adds a thing to select which phobia because I can't be assed to put that in the quirks window - if("Phobia" in all_quirks) - dat += "

                Phobia

                " - - dat += "[phobia]
                " - - if(CONFIG_GET(flag/join_with_mutant_humans)) - - if("wings" in pref_species.default_features && GLOB.r_wings_list.len >1) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Wings

                " - - dat += "[features["wings"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if(mutant_category) - dat += "" - mutant_category = 0 - dat += "
                " - - dat += "Species:
                [pref_species.name]
                " - dat += "Random Species " - dat += "Always Random Species: [(randomise[RANDOM_SPECIES]) ? "Yes" : "No"]
                " - - dat += "Underwear:
                [underwear]" - dat += "[(randomise[RANDOM_UNDERWEAR]) ? "Lock" : "Unlock"]" - - dat += "
                Underwear Color:
                    Change" - dat += "[(randomise[RANDOM_UNDERWEAR_COLOR]) ? "Lock" : "Unlock"]" - - dat += "
                Undershirt:
                [undershirt]" - dat += "[(randomise[RANDOM_UNDERSHIRT]) ? "Lock" : "Unlock"]" - - - dat += "
                Socks:
                [socks]" - dat += "[(randomise[RANDOM_SOCKS]) ? "Lock" : "Unlock"]" - - - dat += "
                Jumpsuit Style:
                [jumpsuit_style]" - dat += "[(randomise[RANDOM_JUMPSUIT_STYLE]) ? "Lock" : "Unlock"]" - - dat += "
                Backpack:
                [backpack]" - dat += "[(randomise[RANDOM_BACKPACK]) ? "Lock" : "Unlock"]" - - if(CAN_SCAR in pref_species.species_traits) - dat += "
                Temporal Scarring:
                [(persistent_scars) ? "Enabled" : "Disabled"]" - dat += "Clear scar slots" - - dat += "
                Uplink Spawn Location:
                [uplink_spawn_loc]
                " - - - if (1) // Game Preferences - dat += "
                " - dat += "

                General Settings

                " - dat += "UI Style: [UI_style]
                " - dat += "tgui Monitors: [(tgui_lock) ? "Primary" : "All"]
                " - dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"]
                " - dat += "Show Runechat Chat Bubbles: [chat_on_map ? "Enabled" : "Disabled"]
                " - dat += "Runechat message char limit: [max_chat_length]
                " - dat += "See Runechat for non-mobs: [see_chat_non_mob ? "Enabled" : "Disabled"]
                " - dat += "
                " - dat += "Action Buttons: [(buttons_locked) ? "Locked In Place" : "Unlocked"]
                " - dat += "Hotkey mode: [(hotkeys) ? "Hotkeys" : "Default"]
                " - dat += "
                " - dat += "PDA Color:     Change
                " - dat += "PDA Style: [pda_style]
                " - dat += "
                " - dat += "Ghost Ears: [(chat_toggles & CHAT_GHOSTEARS) ? "All Speech" : "Nearest Creatures"]
                " - dat += "Ghost Radio: [(chat_toggles & CHAT_GHOSTRADIO) ? "All Messages":"No Messages"]
                " - dat += "Ghost Sight: [(chat_toggles & CHAT_GHOSTSIGHT) ? "All Emotes" : "Nearest Creatures"]
                " - dat += "Ghost Whispers: [(chat_toggles & CHAT_GHOSTWHISPER) ? "All Speech" : "Nearest Creatures"]
                " - dat += "Ghost PDA: [(chat_toggles & CHAT_GHOSTPDA) ? "All Messages" : "Nearest Creatures"]
                " - dat += "Ghost Law Changes: [(chat_toggles & CHAT_GHOSTLAWS) ? "All Law Changes" : "No Law Changes"]
                " - - if(unlock_content) - dat += "Ghost Form: [ghost_form]
                " - dat += "Ghost Orbit: [ghost_orbit]
                " - - var/button_name = "If you see this something went wrong." - switch(ghost_accs) - if(GHOST_ACCS_FULL) - button_name = GHOST_ACCS_FULL_NAME - if(GHOST_ACCS_DIR) - button_name = GHOST_ACCS_DIR_NAME - if(GHOST_ACCS_NONE) - button_name = GHOST_ACCS_NONE_NAME - - dat += "Ghost Accessories: [button_name]
                " - - switch(ghost_others) - if(GHOST_OTHERS_THEIR_SETTING) - button_name = GHOST_OTHERS_THEIR_SETTING_NAME - if(GHOST_OTHERS_DEFAULT_SPRITE) - button_name = GHOST_OTHERS_DEFAULT_SPRITE_NAME - if(GHOST_OTHERS_SIMPLE) - button_name = GHOST_OTHERS_SIMPLE_NAME - - dat += "Ghosts of Others: [button_name]
                " - dat += "
                " - - dat += "Income Updates: [(chat_toggles & CHAT_BANKCARD) ? "Allowed" : "Muted"]
                " - dat += "
                " - - dat += "FPS: [clientfps]
                " - - dat += "Parallax (Fancy Space): " - switch (parallax) - if (PARALLAX_LOW) - dat += "Low" - if (PARALLAX_MED) - dat += "Medium" - if (PARALLAX_INSANE) - dat += "Insane" - if (PARALLAX_DISABLE) - dat += "Disabled" - else - dat += "High" - dat += "
                " - - dat += "Ambient Occlusion: [ambientocclusion ? "Enabled" : "Disabled"]
                " - dat += "Fit Viewport: [auto_fit_viewport ? "Auto" : "Manual"]
                " - if (CONFIG_GET(string/default_view) != CONFIG_GET(string/default_view_square)) - dat += "Widescreen: [widescreenpref ? "Enabled ([CONFIG_GET(string/default_view)])" : "Disabled ([CONFIG_GET(string/default_view_square)])"]
                " - - button_name = pixel_size - dat += "Pixel Scaling: [(button_name) ? "Pixel Perfect [button_name]x" : "Stretch to fit"]
                " - - switch(scaling_method) - if(SCALING_METHOD_DISTORT) - button_name = "Nearest Neighbor" - if(SCALING_METHOD_NORMAL) - button_name = "Point Sampling" - if(SCALING_METHOD_BLUR) - button_name = "Bilinear" - dat += "Scaling Method: [button_name]
                " - - if (CONFIG_GET(flag/maprotation)) - var/p_map = preferred_map - if (!p_map) - p_map = "Default" - if (config.defaultmap) - p_map += " ([config.defaultmap.map_name])" - else - if (p_map in config.maplist) - var/datum/map_config/VM = config.maplist[p_map] - if (!VM) - p_map += " (No longer exists)" - else - p_map = VM.map_name - else - p_map += " (No longer exists)" - if(CONFIG_GET(flag/preference_map_voting)) - dat += "Preferred Map: [p_map]
                " - - dat += "
                " - - dat += "

                Special Role Settings

                " - - if(is_banned_from(user.ckey, ROLE_SYNDICATE)) - dat += "You are banned from antagonist roles.
                " - src.be_special = list() - - - for (var/i in GLOB.special_roles) - if(is_banned_from(user.ckey, i)) - dat += "Be [capitalize(i)]: BANNED
                " - else - var/days_remaining = null - if(ispath(GLOB.special_roles[i]) && CONFIG_GET(flag/use_age_restriction_for_jobs)) //If it's a game mode antag, check if the player meets the minimum age - var/mode_path = GLOB.special_roles[i] - var/datum/game_mode/temp_mode = new mode_path - days_remaining = temp_mode.get_remaining_days(user.client) - - if(days_remaining) - dat += "Be [capitalize(i)]: \[IN [days_remaining] DAYS]
                " - else - dat += "Be [capitalize(i)]: [(i in be_special) ? "Enabled" : "Disabled"]
                " - dat += "
                " - dat += "Midround Antagonist: [(toggles & MIDROUND_ANTAG) ? "Enabled" : "Disabled"]
                " - dat += "
                " - if(2) //OOC Preferences - dat += "" - - if(user.client.holder) - dat +="" - dat += "
                " - dat += "

                OOC Settings

                " - dat += "Window Flashing: [(windowflashing) ? "Enabled":"Disabled"]
                " - dat += "
                " - dat += "Play Admin MIDIs: [(toggles & SOUND_MIDI) ? "Enabled":"Disabled"]
                " - dat += "Play Lobby Music: [(toggles & SOUND_LOBBY) ? "Enabled":"Disabled"]
                " - dat += "Play End of Round Sounds: [(toggles & SOUND_ENDOFROUND) ? "Enabled":"Disabled"]
                " - dat += "See Pull Requests: [(chat_toggles & CHAT_PULLR) ? "Enabled":"Disabled"]
                " - dat += "
                " - - - if(user.client) - if(unlock_content) - dat += "BYOND Membership Publicity: [(toggles & MEMBER_PUBLIC) ? "Public" : "Hidden"]
                " - - if(unlock_content || check_rights_for(user.client, R_ADMIN)) - dat += "OOC Color:     Change
                " - if(hearted_until) - dat += "Clear OOC Commend Heart
                " - - dat += "
                " - - dat += "

                Admin Settings

                " - - dat += "Adminhelp Sounds: [(toggles & SOUND_ADMINHELP)?"Enabled":"Disabled"]
                " - dat += "Prayer Sounds: [(toggles & SOUND_PRAYERS)?"Enabled":"Disabled"]
                " - dat += "Announce Login: [(toggles & ANNOUNCE_LOGIN)?"Enabled":"Disabled"]
                " - dat += "
                " - dat += "Combo HUD Lighting: [(toggles & COMBOHUD_LIGHTING)?"Full-bright":"No Change"]
                " - dat += "
                " - dat += "Hide Dead Chat: [(chat_toggles & CHAT_DEAD)?"Shown":"Hidden"]
                " - dat += "Hide Radio Messages: [(chat_toggles & CHAT_RADIO)?"Shown":"Hidden"]
                " - dat += "Hide Prayers: [(chat_toggles & CHAT_PRAYER)?"Shown":"Hidden"]
                " - dat += "Ignore Being Summoned as Cult Ghost: [(toggles & ADMIN_IGNORE_CULT_GHOST)?"Don't Allow Being Summoned":"Allow Being Summoned"]
                " - if(CONFIG_GET(flag/allow_admin_asaycolor)) - dat += "
                " - dat += "ASAY Color:     Change
                " - - //deadmin - dat += "

                Deadmin While Playing

                " - var/timegate = CONFIG_GET(number/auto_deadmin_timegate) - if(timegate) - dat += "Noted roles will automatically deadmin during the first [FLOOR(timegate / 600, 1)] minutes of the round, and will defer to individual preferences after.
                " - - if(CONFIG_GET(flag/auto_deadmin_players) && !timegate) - dat += "Always Deadmin: FORCED
                " - else - dat += "Always Deadmin: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_ALWAYS)?"Enabled":"Disabled"]
                " - if(!(toggles & DEADMIN_ALWAYS)) - dat += "
                " - if(!CONFIG_GET(flag/auto_deadmin_antagonists) || (CONFIG_GET(flag/auto_deadmin_antagonists) && !timegate)) - dat += "As Antag: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_ANTAGONIST)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Antag: FORCED
                " - - if(!CONFIG_GET(flag/auto_deadmin_heads) || (CONFIG_GET(flag/auto_deadmin_heads) && !timegate)) - dat += "As Command: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_HEAD)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Command: FORCED
                " - - if(!CONFIG_GET(flag/auto_deadmin_security) || (CONFIG_GET(flag/auto_deadmin_security) && !timegate)) - dat += "As Security: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_SECURITY)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Security: FORCED
                " - - if(!CONFIG_GET(flag/auto_deadmin_silicons) || (CONFIG_GET(flag/auto_deadmin_silicons) && !timegate)) - dat += "As Silicon: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_SILICON)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Silicon: FORCED
                " - - dat += "
                " - if(3) // Custom keybindings - // Create an inverted list of keybindings -> key - var/list/user_binds = list() - for (var/key in key_bindings) - for(var/kb_name in key_bindings[key]) - user_binds[kb_name] += list(key) - - var/list/kb_categories = list() - // Group keybinds by category - for (var/name in GLOB.keybindings_by_name) - var/datum/keybinding/kb = GLOB.keybindings_by_name[name] - kb_categories[kb.category] += list(kb) - - dat += "" - - for (var/category in kb_categories) - dat += "

                [category]

                " - for (var/i in kb_categories[category]) - var/datum/keybinding/kb = i - if(!length(user_binds[kb.name])) - dat += " Unbound" - var/list/default_keys = hotkeys ? kb.hotkey_keys : kb.classic_keys - if(LAZYLEN(default_keys)) - dat += "| Default: [default_keys.Join(", ")]" - dat += "
                " - else - var/bound_key = user_binds[kb.name][1] - dat += " [bound_key]" - for(var/bound_key_index in 2 to length(user_binds[kb.name])) - bound_key = user_binds[kb.name][bound_key_index] - dat += " | [bound_key]" - if(length(user_binds[kb.name]) < MAX_KEYS_PER_KEYBIND) - dat += "| Add Secondary" - var/list/default_keys = hotkeys ? kb.classic_keys : kb.hotkey_keys - if(LAZYLEN(default_keys)) - dat += "| Default: [default_keys.Join(", ")]" - dat += "
                " - - dat += "

                " - dat += "\[Reset to default\]" - dat += "" - dat += "
                " - - if(!IsGuestKey(user.key)) - dat += "Undo " - dat += "Save Setup " - - dat += "Reset Setup" - dat += "
                " - - winshow(user, "preferences_window", TRUE) - var/datum/browser/popup = new(user, "preferences_browser", "
                Character Setup
                ", 640, 770) - popup.set_content(dat.Join()) - popup.open(FALSE) - onclose(user, "preferences_window", src) - -#undef APPEARANCE_CATEGORY_COLUMN -#undef MAX_MUTANT_ROWS - -/datum/preferences/proc/CaptureKeybinding(mob/user, datum/keybinding/kb, var/old_key) - var/HTML = {" -
                Keybinding: [kb.full_name]
                [kb.description]

                Press any key to change
                Press ESC to clear
                - - "} - winshow(user, "capturekeypress", TRUE) - var/datum/browser/popup = new(user, "capturekeypress", "
                Keybindings
                ", 350, 300) - popup.set_content(HTML) - popup.open(FALSE) - onclose(user, "capturekeypress", src) - -/datum/preferences/proc/SetChoices(mob/user, limit = 17, list/splitJobs = list("Chief Engineer"), widthPerColumn = 295, height = 620) - if(!SSjob) - return - - //limit - The amount of jobs allowed per column. Defaults to 17 to make it look nice. - //splitJobs - Allows you split the table by job. You can make different tables for each department by including their heads. Defaults to CE to make it look nice. - //widthPerColumn - Screen's width for every column. - //height - Screen's height. - - var/width = widthPerColumn - - var/HTML = "
                " - if(SSjob.occupations.len <= 0) - HTML += "The job SSticker is not yet finished creating jobs, please try again later" - HTML += "
                Done

                " // Easier to press up here. - - else - HTML += "Choose occupation chances
                " - HTML += "
                Left-click to raise an occupation preference, right-click to lower it.
                " - HTML += "
                Done

                " // Easier to press up here. - HTML += "" - HTML += "
                " // Table within a table for alignment, also allows you to easily add more colomns. - HTML += "" - var/index = -1 - - //The job before the current job. I only use this to get the previous jobs color when I'm filling in blank rows. - var/datum/job/lastJob - - for(var/datum/job/job in sortList(SSjob.occupations, /proc/cmp_job_display_asc)) - - index += 1 - if((index >= limit) || (job.title in splitJobs)) - width += widthPerColumn - if((index < limit) && (lastJob != null)) - //If the cells were broken up by a job in the splitJob list then it will fill in the rest of the cells with - //the last job's selection color. Creating a rather nice effect. - for(var/i = 0, i < (limit - index), i += 1) - HTML += "" - HTML += "
                  
                " - index = 0 - - HTML += "" - continue - var/required_playtime_remaining = job.required_playtime_remaining(user.client) - if(required_playtime_remaining) - HTML += "[rank]" - continue - if(!job.player_old_enough(user.client)) - var/available_in_days = job.available_in_days(user.client) - HTML += "[rank]" - continue - if((job_preferences[SSjob.overflow_role] == JP_LOW) && (rank != SSjob.overflow_role) && !is_banned_from(user.ckey, SSjob.overflow_role)) - HTML += "[rank]" - continue - if((rank in GLOB.command_positions) || (rank == "AI"))//Bold head jobs - HTML += "[rank]" - else - HTML += "[rank]" - - HTML += "" - continue - - HTML += "[prefLevelLabel]" - HTML += "" - - for(var/i = 1, i < (limit - index), i += 1) // Finish the column so it is even - HTML += "" - - HTML += "
                " - var/rank = job.title - lastJob = job - if(is_banned_from(user.ckey, rank)) - HTML += "[rank] BANNED
                \[ [get_exp_format(required_playtime_remaining)] as [job.get_exp_req_type()] \]
                \[IN [(available_in_days)] DAYS\]
                " - - var/prefLevelLabel = "ERROR" - var/prefLevelColor = "pink" - var/prefUpperLevel = -1 // level to assign on left click - var/prefLowerLevel = -1 // level to assign on right click - - switch(job_preferences[job.title]) - if(JP_HIGH) - prefLevelLabel = "High" - prefLevelColor = "slateblue" - prefUpperLevel = 4 - prefLowerLevel = 2 - if(JP_MEDIUM) - prefLevelLabel = "Medium" - prefLevelColor = "green" - prefUpperLevel = 1 - prefLowerLevel = 3 - if(JP_LOW) - prefLevelLabel = "Low" - prefLevelColor = "orange" - prefUpperLevel = 2 - prefLowerLevel = 4 - else - prefLevelLabel = "NEVER" - prefLevelColor = "red" - prefUpperLevel = 3 - prefLowerLevel = 1 - - HTML += "" - - if(rank == SSjob.overflow_role)//Overflow is special - if(job_preferences[SSjob.overflow_role] == JP_LOW) - HTML += "Yes" - else - HTML += "No" - HTML += "
                  
                " - HTML += "
                " - - var/message = "Be an [SSjob.overflow_role] if preferences unavailable" - if(joblessrole == BERANDOMJOB) - message = "Get random job if preferences unavailable" - else if(joblessrole == RETURNTOLOBBY) - message = "Return to lobby if preferences unavailable" - HTML += "

                [message]
                " - HTML += "
                Reset Preferences
                " - - var/datum/browser/popup = new(user, "mob_occupation", "
                Occupation Preferences
                ", width, height) - popup.set_window_options("can_close=0") - popup.set_content(HTML) - popup.open(FALSE) - -/datum/preferences/proc/SetJobPreferenceLevel(datum/job/job, level) - if (!job) - return FALSE - - if (level == JP_HIGH) // to high - //Set all other high to medium - for(var/j in job_preferences) - if(job_preferences[j] == JP_HIGH) - job_preferences[j] = JP_MEDIUM - //technically break here - - job_preferences[job.title] = level - return TRUE - -/datum/preferences/proc/UpdateJobPreference(mob/user, role, desiredLvl) - if(!SSjob || SSjob.occupations.len <= 0) - return - var/datum/job/job = SSjob.GetJob(role) - - if(!job) - user << browse(null, "window=mob_occupation") - ShowChoices(user) - return - - if (!isnum(desiredLvl)) - to_chat(user, "UpdateJobPreference - desired level was not a number. Please notify coders!") - ShowChoices(user) - return - - var/jpval = null - switch(desiredLvl) - if(3) - jpval = JP_LOW - if(2) - jpval = JP_MEDIUM - if(1) - jpval = JP_HIGH - - if(role == SSjob.overflow_role) - if(job_preferences[job.title] == JP_LOW) - jpval = null - else - jpval = JP_LOW - - SetJobPreferenceLevel(job, jpval) - SetChoices(user) - - return 1 - - -/datum/preferences/proc/ResetJobs() - job_preferences = list() - -/datum/preferences/proc/SetQuirks(mob/user) - if(!SSquirks) - to_chat(user, "The quirk subsystem is still initializing! Try again in a minute.") - return - - var/list/dat = list() - if(!SSquirks.quirks.len) - dat += "The quirk subsystem hasn't finished initializing, please hold..." - dat += "
                Done

                " - else - dat += "
                Choose quirk setup

                " - dat += "
                Left-click to add or remove quirks. You need negative quirks to have positive ones.
                \ - Quirks are applied at roundstart and cannot normally be removed.
                " - dat += "
                Done
                " - dat += "
                " - dat += "
                Current quirks: [all_quirks.len ? all_quirks.Join(", ") : "None"]
                " - dat += "
                [GetPositiveQuirkCount()] / [MAX_QUIRKS] max positive quirks
                \ - Quirk balance remaining: [GetQuirkBalance()]

                " - for(var/V in SSquirks.quirks) - var/datum/quirk/T = SSquirks.quirks[V] - var/quirk_name = initial(T.name) - var/has_quirk - var/quirk_cost = initial(T.value) * -1 - var/lock_reason = "This trait is unavailable." - var/quirk_conflict = FALSE - for(var/_V in all_quirks) - if(_V == quirk_name) - has_quirk = TRUE - if(initial(T.mood_quirk) && CONFIG_GET(flag/disable_human_mood)) - lock_reason = "Mood is disabled." - quirk_conflict = TRUE - if(has_quirk) - if(quirk_conflict) - all_quirks -= quirk_name - has_quirk = FALSE - else - quirk_cost *= -1 //invert it back, since we'd be regaining this amount - if(quirk_cost > 0) - quirk_cost = "+[quirk_cost]" - var/font_color = "#AAAAFF" - if(initial(T.value) != 0) - font_color = initial(T.value) > 0 ? "#AAFFAA" : "#FFAAAA" - if(quirk_conflict) - dat += "[quirk_name] - [initial(T.desc)] \ - LOCKED: [lock_reason]
                " - else - if(has_quirk) - dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ - [quirk_name] - [initial(T.desc)]
                " - else - dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ - [quirk_name] - [initial(T.desc)]
                " - dat += "
                Reset Quirks
                " - - var/datum/browser/popup = new(user, "mob_occupation", "
                Quirk Preferences
                ", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way - popup.set_window_options("can_close=0") - popup.set_content(dat.Join()) - popup.open(FALSE) - -/datum/preferences/proc/GetQuirkBalance() - var/bal = 0 - for(var/V in all_quirks) - var/datum/quirk/T = SSquirks.quirks[V] - bal -= initial(T.value) - return bal - -/datum/preferences/proc/GetPositiveQuirkCount() - . = 0 - for(var/q in all_quirks) - if(SSquirks.quirk_points[q] > 0) - .++ - -/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess.. - . = ..() - if(href_list["close"]) - var/client/C = usr.client - if(C) - C.clear_character_previews() - -/datum/preferences/proc/process_link(mob/user, list/href_list) - if(href_list["bancheck"]) - var/list/ban_details = is_banned_from_with_details(user.ckey, user.client.address, user.client.computer_id, href_list["bancheck"]) - var/admin = FALSE - if(GLOB.admin_datums[user.ckey] || GLOB.deadmins[user.ckey]) - admin = TRUE - for(var/i in ban_details) - if(admin && !text2num(i["applies_to_admins"])) - continue - ban_details = i - break //we only want to get the most recent ban's details - if(ban_details && ban_details.len) - var/expires = "This is a permanent ban." - if(ban_details["expiration_time"]) - expires = " The ban is for [DisplayTimeText(text2num(ban_details["duration"]) MINUTES)] and expires on [ban_details["expiration_time"]] (server time)." - to_chat(user, "You, or another user of this computer or connection ([ban_details["key"]]) is banned from playing [href_list["bancheck"]].
                The ban reason is: [ban_details["reason"]]
                This ban (BanID #[ban_details["id"]]) was applied by [ban_details["admin_key"]] on [ban_details["bantime"]] during round ID [ban_details["round_id"]].
                [expires]
                ") - return - if(href_list["preference"] == "job") - switch(href_list["task"]) - if("close") - user << browse(null, "window=mob_occupation") - ShowChoices(user) - if("reset") - ResetJobs() - SetChoices(user) - if("random") - switch(joblessrole) - if(RETURNTOLOBBY) - if(is_banned_from(user.ckey, SSjob.overflow_role)) - joblessrole = BERANDOMJOB - else - joblessrole = BEOVERFLOW - if(BEOVERFLOW) - joblessrole = BERANDOMJOB - if(BERANDOMJOB) - joblessrole = RETURNTOLOBBY - SetChoices(user) - if("setJobLevel") - UpdateJobPreference(user, href_list["text"], text2num(href_list["level"])) - else - SetChoices(user) - return 1 - - else if(href_list["preference"] == "trait") - switch(href_list["task"]) - if("close") - user << browse(null, "window=mob_occupation") - ShowChoices(user) - if("update") - var/quirk = href_list["trait"] - if(!SSquirks.quirks[quirk]) - return - for(var/V in SSquirks.quirk_blacklist) //V is a list - var/list/L = V - if(!(quirk in L)) - continue - for(var/Q in all_quirks) - if((Q in L) && !(Q == quirk)) //two quirks have lined up in the list of the list of quirks that conflict with each other, so return (see quirks.dm for more details) - to_chat(user, "[quirk] is incompatible with [Q].") - return - var/value = SSquirks.quirk_points[quirk] - var/balance = GetQuirkBalance() - if(quirk in all_quirks) - if(balance + value < 0) - to_chat(user, "Refunding this would cause you to go below your balance!") - return - all_quirks -= quirk - else - var/is_positive_quirk = SSquirks.quirk_points[quirk] > 0 - if(is_positive_quirk && GetPositiveQuirkCount() >= MAX_QUIRKS) - to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!") - return - if(balance - value < 0) - to_chat(user, "You don't have enough balance to gain this quirk!") - return - all_quirks += quirk - SetQuirks(user) - if("reset") - all_quirks = list() - SetQuirks(user) - else - SetQuirks(user) - return TRUE - - switch(href_list["task"]) - if("random") - switch(href_list["preference"]) - if("name") - real_name = pref_species.random_name(gender,1) - if("age") - age = rand(AGE_MIN, AGE_MAX) - if("hair") - hair_color = random_short_color() - if("hairstyle") - hairstyle = random_hairstyle(gender) - if("facial") - facial_hair_color = random_short_color() - if("facial_hairstyle") - facial_hairstyle = random_facial_hairstyle(gender) - if("underwear") - underwear = random_underwear(gender) - if("underwear_color") - underwear_color = random_short_color() - if("undershirt") - undershirt = random_undershirt(gender) - if("socks") - socks = random_socks() - if(BODY_ZONE_PRECISE_EYES) - eye_color = random_eye_color() - if("s_tone") - skin_tone = random_skin_tone() - if("species") - random_species() - if("bag") - backpack = pick(GLOB.backpacklist) - if("suit") - jumpsuit_style = pick(GLOB.jumpsuitlist) - if("all") - random_character(gender) - - if("input") - - if(href_list["preference"] in GLOB.preferences_custom_names) - ask_for_custom_name(user,href_list["preference"]) - - - switch(href_list["preference"]) - if("ghostform") - if(unlock_content) - var/new_form = input(user, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms - if(new_form) - ghost_form = new_form - if("ghostorbit") - if(unlock_content) - var/new_orbit = input(user, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND", null) as null|anything in GLOB.ghost_orbits - if(new_orbit) - ghost_orbit = new_orbit - - if("ghostaccs") - var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,GHOST_ACCS_FULL_NAME, GHOST_ACCS_DIR_NAME, GHOST_ACCS_NONE_NAME) - switch(new_ghost_accs) - if(GHOST_ACCS_FULL_NAME) - ghost_accs = GHOST_ACCS_FULL - if(GHOST_ACCS_DIR_NAME) - ghost_accs = GHOST_ACCS_DIR - if(GHOST_ACCS_NONE_NAME) - ghost_accs = GHOST_ACCS_NONE - - if("ghostothers") - var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,GHOST_OTHERS_THEIR_SETTING_NAME, GHOST_OTHERS_DEFAULT_SPRITE_NAME, GHOST_OTHERS_SIMPLE_NAME) - switch(new_ghost_others) - if(GHOST_OTHERS_THEIR_SETTING_NAME) - ghost_others = GHOST_OTHERS_THEIR_SETTING - if(GHOST_OTHERS_DEFAULT_SPRITE_NAME) - ghost_others = GHOST_OTHERS_DEFAULT_SPRITE - if(GHOST_OTHERS_SIMPLE_NAME) - ghost_others = GHOST_OTHERS_SIMPLE - - if("name") - var/new_name = input(user, "Choose your character's name:", "Character Preference") as text|null - if(new_name) - new_name = reject_bad_name(new_name) - if(new_name) - real_name = new_name - else - to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .") - - if("age") - var/new_age = input(user, "Choose your character's age:\n([AGE_MIN]-[AGE_MAX])", "Character Preference") as num|null - if(new_age) - age = max(min( round(text2num(new_age)), AGE_MAX),AGE_MIN) - - if("hair") - var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null - if(new_hair) - hair_color = sanitize_hexcolor(new_hair) - - if("hairstyle") - var/new_hairstyle - if(gender == MALE) - new_hairstyle = input(user, "Choose your character's hairstyle:", "Character Preference") as null|anything in GLOB.hairstyles_male_list - else if(gender == FEMALE) - new_hairstyle = input(user, "Choose your character's hairstyle:", "Character Preference") as null|anything in GLOB.hairstyles_female_list - else - new_hairstyle = input(user, "Choose your character's hairstyle:", "Character Preference") as null|anything in GLOB.hairstyles_list - if(new_hairstyle) - hairstyle = new_hairstyle - - if("next_hairstyle") - if (gender == MALE) - hairstyle = next_list_item(hairstyle, GLOB.hairstyles_male_list) - else if(gender == FEMALE) - hairstyle = next_list_item(hairstyle, GLOB.hairstyles_female_list) - else - hairstyle = next_list_item(hairstyle, GLOB.hairstyles_list) - - if("previous_hairstyle") - if (gender == MALE) - hairstyle = previous_list_item(hairstyle, GLOB.hairstyles_male_list) - else if(gender == FEMALE) - hairstyle = previous_list_item(hairstyle, GLOB.hairstyles_female_list) - else - hairstyle = previous_list_item(hairstyle, GLOB.hairstyles_list) - - if("facial") - var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference","#"+facial_hair_color) as color|null - if(new_facial) - facial_hair_color = sanitize_hexcolor(new_facial) - - if("facial_hairstyle") - var/new_facial_hairstyle - if(gender == MALE) - new_facial_hairstyle = input(user, "Choose your character's facial-hairstyle:", "Character Preference") as null|anything in GLOB.facial_hairstyles_male_list - else if(gender == FEMALE) - new_facial_hairstyle = input(user, "Choose your character's facial-hairstyle:", "Character Preference") as null|anything in GLOB.facial_hairstyles_female_list - else - new_facial_hairstyle = input(user, "Choose your character's facial-hairstyle:", "Character Preference") as null|anything in GLOB.facial_hairstyles_list - if(new_facial_hairstyle) - facial_hairstyle = new_facial_hairstyle - - if("next_facehairstyle") - if (gender == MALE) - facial_hairstyle = next_list_item(facial_hairstyle, GLOB.facial_hairstyles_male_list) - else if(gender == FEMALE) - facial_hairstyle = next_list_item(facial_hairstyle, GLOB.facial_hairstyles_female_list) - else - facial_hairstyle = next_list_item(facial_hairstyle, GLOB.facial_hairstyles_list) - - if("previous_facehairstyle") - if (gender == MALE) - facial_hairstyle = previous_list_item(facial_hairstyle, GLOB.facial_hairstyles_male_list) - else if (gender == FEMALE) - facial_hairstyle = previous_list_item(facial_hairstyle, GLOB.facial_hairstyles_female_list) - else - facial_hairstyle = previous_list_item(facial_hairstyle, GLOB.facial_hairstyles_list) - - if("underwear") - var/new_underwear - if(gender == MALE) - new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_m - else if(gender == FEMALE) - new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_f - else - new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_list - if(new_underwear) - underwear = new_underwear - - if("underwear_color") - var/new_underwear_color = input(user, "Choose your character's underwear color:", "Character Preference","#"+underwear_color) as color|null - if(new_underwear_color) - underwear_color = sanitize_hexcolor(new_underwear_color) - - if("undershirt") - var/new_undershirt - if(gender == MALE) - new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_m - else if(gender == FEMALE) - new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_f - else - new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_list - if(new_undershirt) - undershirt = new_undershirt - - if("socks") - var/new_socks - new_socks = input(user, "Choose your character's socks:", "Character Preference") as null|anything in GLOB.socks_list - if(new_socks) - socks = new_socks - - if("eyes") - var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference","#"+eye_color) as color|null - if(new_eyes) - eye_color = sanitize_hexcolor(new_eyes) - - if("species") - - var/result = input(user, "Select a species", "Species Selection") as null|anything in GLOB.roundstart_races - - if(result) - var/newtype = GLOB.species_list[result] - pref_species = new newtype() - //Now that we changed our species, we must verify that the mutant colour is still allowed. - var/temp_hsv = RGBtoHSV(features["mcolor"]) - if(features["mcolor"] == "#000" || (!(MUTCOLORS_PARTSONLY in pref_species.species_traits) && ReadHSV(temp_hsv)[3] < ReadHSV("#7F7F7F")[3])) - features["mcolor"] = pref_species.default_color - if(randomise[RANDOM_NAME]) - real_name = pref_species.random_name(gender) - - if("mutant_color") - var/new_mutantcolor = input(user, "Choose your character's alien/mutant color:", "Character Preference","#"+features["mcolor"]) as color|null - if(new_mutantcolor) - var/temp_hsv = RGBtoHSV(new_mutantcolor) - if(new_mutantcolor == "#000000") - features["mcolor"] = pref_species.default_color - else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright, but only if they affect the skin - features["mcolor"] = sanitize_hexcolor(new_mutantcolor) - else - to_chat(user, "Invalid color. Your color is not bright enough.") - - if("color_ethereal") - var/new_etherealcolor = input(user, "Choose your ethereal color", "Character Preference") as null|anything in GLOB.color_list_ethereal - if(new_etherealcolor) - features["ethcolor"] = GLOB.color_list_ethereal[new_etherealcolor] - - - if("tail_lizard") - var/new_tail - new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_lizard - if(new_tail) - features["tail_lizard"] = new_tail - - if("tail_human") - var/new_tail - new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_human - if(new_tail) - features["tail_human"] = new_tail - - if("snout") - var/new_snout - new_snout = input(user, "Choose your character's snout:", "Character Preference") as null|anything in GLOB.snouts_list - if(new_snout) - features["snout"] = new_snout - - if("horns") - var/new_horns - new_horns = input(user, "Choose your character's horns:", "Character Preference") as null|anything in GLOB.horns_list - if(new_horns) - features["horns"] = new_horns - - if("ears") - var/new_ears - new_ears = input(user, "Choose your character's ears:", "Character Preference") as null|anything in GLOB.ears_list - if(new_ears) - features["ears"] = new_ears - - if("wings") - var/new_wings - new_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.r_wings_list - if(new_wings) - features["wings"] = new_wings - - if("frills") - var/new_frills - new_frills = input(user, "Choose your character's frills:", "Character Preference") as null|anything in GLOB.frills_list - if(new_frills) - features["frills"] = new_frills - - if("spines") - var/new_spines - new_spines = input(user, "Choose your character's spines:", "Character Preference") as null|anything in GLOB.spines_list - if(new_spines) - features["spines"] = new_spines - - if("body_markings") - var/new_body_markings - new_body_markings = input(user, "Choose your character's body markings:", "Character Preference") as null|anything in GLOB.body_markings_list - if(new_body_markings) - features["body_markings"] = new_body_markings - - if("legs") - var/new_legs - new_legs = input(user, "Choose your character's legs:", "Character Preference") as null|anything in GLOB.legs_list - if(new_legs) - features["legs"] = new_legs - - if("moth_wings") - var/new_moth_wings - new_moth_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.moth_wings_list - if(new_moth_wings) - features["moth_wings"] = new_moth_wings - - if("moth_markings") - var/new_moth_markings - new_moth_markings = input(user, "Choose your character's markings:", "Character Preference") as null|anything in GLOB.moth_markings_list - if(new_moth_markings) - features["moth_markings"] = new_moth_markings - - if("s_tone") - var/new_s_tone = input(user, "Choose your character's skin-tone:", "Character Preference") as null|anything in GLOB.skin_tones - if(new_s_tone) - skin_tone = new_s_tone - - if("ooccolor") - var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference",ooccolor) as color|null - if(new_ooccolor) - ooccolor = new_ooccolor - - if("asaycolor") - var/new_asaycolor = input(user, "Choose your ASAY color:", "Game Preference",asaycolor) as color|null - if(new_asaycolor) - asaycolor = new_asaycolor - - if("bag") - var/new_backpack = input(user, "Choose your character's style of bag:", "Character Preference") as null|anything in GLOB.backpacklist - if(new_backpack) - backpack = new_backpack - - if("suit") - if(jumpsuit_style == PREF_SUIT) - jumpsuit_style = PREF_SKIRT - else - jumpsuit_style = PREF_SUIT - - if("uplink_loc") - var/new_loc = input(user, "Choose your character's traitor uplink spawn location:", "Character Preference") as null|anything in GLOB.uplink_spawn_loc_list - if(new_loc) - uplink_spawn_loc = new_loc - - if("playtime_reward_cloak") - if (user.client.get_exp_living(TRUE) >= PLAYTIME_VETERAN) - playtime_reward_cloak = !playtime_reward_cloak - - if("ai_core_icon") - var/ai_core_icon = input(user, "Choose your preferred AI core display screen:", "AI Core Display Screen Selection") as null|anything in GLOB.ai_core_display_screens - if(ai_core_icon) - preferred_ai_core_display = ai_core_icon - - if("sec_dept") - var/department = input(user, "Choose your preferred security department:", "Security Departments") as null|anything in GLOB.security_depts_prefs - if(department) - prefered_security_department = department - - if ("preferred_map") - var/maplist = list() - var/default = "Default" - if (config.defaultmap) - default += " ([config.defaultmap.map_name])" - for (var/M in config.maplist) - var/datum/map_config/VM = config.maplist[M] - if(!VM.votable) - continue - var/friendlyname = "[VM.map_name] " - if (VM.voteweight <= 0) - friendlyname += " (disabled)" - maplist[friendlyname] = VM.map_name - maplist[default] = null - var/pickedmap = input(user, "Choose your preferred map. This will be used to help weight random map selection.", "Character Preference") as null|anything in sortList(maplist) - if (pickedmap) - preferred_map = maplist[pickedmap] - - if ("clientfps") - var/desiredfps = input(user, "Choose your desired fps. (0 = synced with server tick rate (currently:[world.fps]))", "Character Preference", clientfps) as null|num - if (!isnull(desiredfps)) - clientfps = desiredfps - parent.fps = desiredfps - if("ui") - var/pickedui = input(user, "Choose your UI style.", "Character Preference", UI_style) as null|anything in sortList(GLOB.available_ui_styles) - if(pickedui) - UI_style = pickedui - if (parent && parent.mob && parent.mob.hud_used) - parent.mob.hud_used.update_ui_style(ui_style2icon(UI_style)) - if("pda_style") - var/pickedPDAStyle = input(user, "Choose your PDA style.", "Character Preference", pda_style) as null|anything in GLOB.pda_styles - if(pickedPDAStyle) - pda_style = pickedPDAStyle - if("pda_color") - var/pickedPDAColor = input(user, "Choose your PDA Interface color.", "Character Preference", pda_color) as color|null - if(pickedPDAColor) - pda_color = pickedPDAColor - - if("phobia") - var/phobiaType = input(user, "What are you scared of?", "Character Preference", phobia) as null|anything in SStraumas.phobia_types - if(phobiaType) - phobia = phobiaType - - if ("max_chat_length") - var/desiredlength = input(user, "Choose the max character length of shown Runechat messages. Valid range is 1 to [CHAT_MESSAGE_MAX_LENGTH] (default: [initial(max_chat_length)]))", "Character Preference", max_chat_length) as null|num - if (!isnull(desiredlength)) - max_chat_length = clamp(desiredlength, 1, CHAT_MESSAGE_MAX_LENGTH) - - else - switch(href_list["preference"]) - if("publicity") - if(unlock_content) - toggles ^= MEMBER_PUBLIC - if("gender") - var/list/friendlyGenders = list("Male" = "male", "Female" = "female", "Other" = "plural") - var/pickedGender = input(user, "Choose your gender.", "Character Preference", gender) as null|anything in friendlyGenders - if(pickedGender && friendlyGenders[pickedGender] != gender) - gender = friendlyGenders[pickedGender] - underwear = random_underwear(gender) - undershirt = random_undershirt(gender) - socks = random_socks() - facial_hairstyle = random_facial_hairstyle(gender) - hairstyle = random_hairstyle(gender) - if("body_type") - if(body_type == MALE) - body_type = FEMALE - else - body_type = MALE - if("hotkeys") - hotkeys = !hotkeys - if(hotkeys) - winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED]") - else - winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_DISABLED]") - - if("keybindings_capture") - var/datum/keybinding/kb = GLOB.keybindings_by_name[href_list["keybinding"]] - var/old_key = href_list["old_key"] - CaptureKeybinding(user, kb, old_key) - return - - if("keybindings_set") - var/kb_name = href_list["keybinding"] - if(!kb_name) - user << browse(null, "window=capturekeypress") - ShowChoices(user) - return - - var/clear_key = text2num(href_list["clear_key"]) - var/old_key = href_list["old_key"] - if(clear_key) - if(key_bindings[old_key]) - key_bindings[old_key] -= kb_name - if(!length(key_bindings[old_key])) - key_bindings -= old_key - user << browse(null, "window=capturekeypress") - save_preferences() - ShowChoices(user) - return - - var/new_key = uppertext(href_list["key"]) - var/AltMod = text2num(href_list["alt"]) ? "Alt" : "" - var/CtrlMod = text2num(href_list["ctrl"]) ? "Ctrl" : "" - var/ShiftMod = text2num(href_list["shift"]) ? "Shift" : "" - var/numpad = text2num(href_list["numpad"]) ? "Numpad" : "" - // var/key_code = text2num(href_list["key_code"]) - - if(GLOB._kbMap[new_key]) - new_key = GLOB._kbMap[new_key] - - var/full_key - switch(new_key) - if("Alt") - full_key = "[new_key][CtrlMod][ShiftMod]" - if("Ctrl") - full_key = "[AltMod][new_key][ShiftMod]" - if("Shift") - full_key = "[AltMod][CtrlMod][new_key]" - else - full_key = "[AltMod][CtrlMod][ShiftMod][numpad][new_key]" - if(key_bindings[old_key]) - key_bindings[old_key] -= kb_name - if(!length(key_bindings[old_key])) - key_bindings -= old_key - key_bindings[full_key] += list(kb_name) - key_bindings[full_key] = sortList(key_bindings[full_key]) - - user << browse(null, "window=capturekeypress") - user.client.update_movement_keys() - save_preferences() - - if("keybindings_reset") - var/choice = tgalert(user, "Would you prefer 'hotkey' or 'classic' defaults?", "Setup keybindings", "Hotkey", "Classic", "Cancel") - if(choice == "Cancel") - ShowChoices(user) - return - hotkeys = (choice == "Hotkey") - key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key) - user.client.update_movement_keys() - - if("chat_on_map") - chat_on_map = !chat_on_map - if("see_chat_non_mob") - see_chat_non_mob = !see_chat_non_mob - - if("action_buttons") - buttons_locked = !buttons_locked - if("tgui_fancy") - tgui_fancy = !tgui_fancy - if("tgui_lock") - tgui_lock = !tgui_lock - if("winflash") - windowflashing = !windowflashing - - //here lies the badmins - if("hear_adminhelps") - user.client.toggleadminhelpsound() - if("hear_prayers") - user.client.toggle_prayer_sound() - if("announce_login") - user.client.toggleannouncelogin() - if("combohud_lighting") - toggles ^= COMBOHUD_LIGHTING - if("toggle_dead_chat") - user.client.deadchat() - if("toggle_radio_chatter") - user.client.toggle_hear_radio() - if("toggle_prayers") - user.client.toggleprayers() - if("toggle_deadmin_always") - toggles ^= DEADMIN_ALWAYS - if("toggle_deadmin_antag") - toggles ^= DEADMIN_ANTAGONIST - if("toggle_deadmin_head") - toggles ^= DEADMIN_POSITION_HEAD - if("toggle_deadmin_security") - toggles ^= DEADMIN_POSITION_SECURITY - if("toggle_deadmin_silicon") - toggles ^= DEADMIN_POSITION_SILICON - if("toggle_ignore_cult_ghost") - toggles ^= ADMIN_IGNORE_CULT_GHOST - - - if("be_special") - var/be_special_type = href_list["be_special_type"] - if(be_special_type in be_special) - be_special -= be_special_type - else - be_special += be_special_type - - if("toggle_random") - var/random_type = href_list["random_type"] - if(randomise[random_type]) - randomise -= random_type - else - randomise[random_type] = TRUE - - if("persistent_scars") - persistent_scars = !persistent_scars - - if("clear_scars") - to_chat(user, "All scar slots cleared. Please save character to confirm.") - scars_list["1"] = "" - scars_list["2"] = "" - scars_list["3"] = "" - scars_list["4"] = "" - scars_list["5"] = "" - - if("hear_midis") - toggles ^= SOUND_MIDI - - if("lobby_music") - toggles ^= SOUND_LOBBY - if((toggles & SOUND_LOBBY) && user.client && isnewplayer(user)) - user.client.playtitlemusic() - else - user.stop_sound_channel(CHANNEL_LOBBYMUSIC) - - if("endofround_sounds") - toggles ^= SOUND_ENDOFROUND - - if("ghost_ears") - chat_toggles ^= CHAT_GHOSTEARS - - if("ghost_sight") - chat_toggles ^= CHAT_GHOSTSIGHT - - if("ghost_whispers") - chat_toggles ^= CHAT_GHOSTWHISPER - - if("ghost_radio") - chat_toggles ^= CHAT_GHOSTRADIO - - if("ghost_pda") - chat_toggles ^= CHAT_GHOSTPDA - - if("ghost_laws") - chat_toggles ^= CHAT_GHOSTLAWS - - if("income_pings") - chat_toggles ^= CHAT_BANKCARD - - if("pull_requests") - chat_toggles ^= CHAT_PULLR - - if("allow_midround_antag") - toggles ^= MIDROUND_ANTAG - - if("parallaxup") - parallax = WRAP(parallax + 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) - if (parent && parent.mob && parent.mob.hud_used) - parent.mob.hud_used.update_parallax_pref(parent.mob) - - if("parallaxdown") - parallax = WRAP(parallax - 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) - if (parent && parent.mob && parent.mob.hud_used) - parent.mob.hud_used.update_parallax_pref(parent.mob) - - if("ambientocclusion") - ambientocclusion = !ambientocclusion - if(parent && parent.screen && parent.screen.len) - var/obj/screen/plane_master/game_world/PM = locate(/obj/screen/plane_master/game_world) in parent.screen - PM.backdrop(parent.mob) - - if("auto_fit_viewport") - auto_fit_viewport = !auto_fit_viewport - if(auto_fit_viewport && parent) - parent.fit_viewport() - - if("widescreenpref") - widescreenpref = !widescreenpref - user.client.view_size.setDefault(getScreenSize(widescreenpref)) - - if("pixel_size") - switch(pixel_size) - if(PIXEL_SCALING_AUTO) - pixel_size = PIXEL_SCALING_1X - if(PIXEL_SCALING_1X) - pixel_size = PIXEL_SCALING_1_2X - if(PIXEL_SCALING_1_2X) - pixel_size = PIXEL_SCALING_2X - if(PIXEL_SCALING_2X) - pixel_size = PIXEL_SCALING_3X - if(PIXEL_SCALING_3X) - pixel_size = PIXEL_SCALING_AUTO - user.client.view_size.apply() //Let's winset() it so it actually works - - if("scaling_method") - switch(scaling_method) - if(SCALING_METHOD_NORMAL) - scaling_method = SCALING_METHOD_DISTORT - if(SCALING_METHOD_DISTORT) - scaling_method = SCALING_METHOD_BLUR - if(SCALING_METHOD_BLUR) - scaling_method = SCALING_METHOD_NORMAL - user.client.view_size.setZoomMode() - - if("save") - save_preferences() - save_character() - - if("load") - load_preferences() - load_character() - - if("changeslot") - if(!load_character(text2num(href_list["num"]))) - random_character() - real_name = random_unique_name(gender) - save_character() - - if("tab") - if (href_list["tab"]) - current_tab = text2num(href_list["tab"]) - - if("clear_heart") - hearted = FALSE - hearted_until = null - to_chat(user, "OOC Commendation Heart disabled") - save_preferences() - - ShowChoices(user) - return 1 - -/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = 1, roundstart_checks = TRUE, character_setup = FALSE, antagonist = FALSE, is_latejoiner = TRUE) - - hardcore_survival_score = 0 //Set to 0 to prevent you getting points from last another time. - - if((randomise[RANDOM_SPECIES] || randomise[RANDOM_HARDCORE]) && !character_setup) - - random_species() - - if((randomise[RANDOM_BODY] || (randomise[RANDOM_BODY_ANTAG] && antagonist) || randomise[RANDOM_HARDCORE]) && !character_setup) - slot_randomized = TRUE - random_character(gender, antagonist) - - if((randomise[RANDOM_NAME] || (randomise[RANDOM_NAME_ANTAG] && antagonist) || randomise[RANDOM_HARDCORE]) && !character_setup) - slot_randomized = TRUE - real_name = pref_species.random_name(gender) - - if(randomise[RANDOM_HARDCORE] && parent.mob.mind && !character_setup) - if(can_be_random_hardcore()) - hardcore_random_setup(character, antagonist, is_latejoiner) - - if(roundstart_checks) - if(CONFIG_GET(flag/humans_need_surnames) && (pref_species.id == "human")) - var/firstspace = findtext(real_name, " ") - var/name_length = length(real_name) - if(!firstspace) //we need a surname - real_name += " [pick(GLOB.last_names)]" - else if(firstspace == name_length) - real_name += "[pick(GLOB.last_names)]" - - character.real_name = real_name - character.name = character.real_name - - character.gender = gender - character.age = age - if(gender == MALE || gender == FEMALE) - character.body_type = gender - else - character.body_type = body_type - - character.eye_color = eye_color - var/obj/item/organ/eyes/organ_eyes = character.getorgan(/obj/item/organ/eyes) - if(organ_eyes) - if(!initial(organ_eyes.eye_color)) - organ_eyes.eye_color = eye_color - organ_eyes.old_eye_color = eye_color - character.hair_color = hair_color - character.facial_hair_color = facial_hair_color - - character.skin_tone = skin_tone - character.hairstyle = hairstyle - character.facial_hairstyle = facial_hairstyle - character.underwear = underwear - character.underwear_color = underwear_color - character.undershirt = undershirt - character.socks = socks - - character.backpack = backpack - - character.jumpsuit_style = jumpsuit_style - - var/datum/species/chosen_species - chosen_species = pref_species.type - if(roundstart_checks && !(pref_species.id in GLOB.roundstart_races) && !(pref_species.id in (CONFIG_GET(keyed_list/roundstart_no_hard_check)))) - chosen_species = /datum/species/human - pref_species = new /datum/species/human - save_character() - - character.dna.features = features.Copy() - character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE) - character.dna.real_name = character.real_name - - if("tail_lizard" in pref_species.default_features) - character.dna.species.mutant_bodyparts |= "tail_lizard" - - if(icon_updates) - character.update_body() - character.update_hair() - character.update_body_parts() - -/datum/preferences/proc/can_be_random_hardcore() - if(parent.mob.mind.assigned_role in GLOB.command_positions) //No command staff - return FALSE - for(var/A in parent.mob.mind.antag_datums) - var/datum/antagonist/antag - if(antag.get_team()) //No team antags - return FALSE - return TRUE - -/datum/preferences/proc/get_default_name(name_id) - switch(name_id) - if("human") - return random_unique_name() - if("ai") - return pick(GLOB.ai_names) - if("cyborg") - return DEFAULT_CYBORG_NAME - if("clown") - return pick(GLOB.clown_names) - if("mime") - return pick(GLOB.mime_names) - if("religion") - return DEFAULT_RELIGION - if("deity") - return DEFAULT_DEITY - return random_unique_name() - -/datum/preferences/proc/ask_for_custom_name(mob/user,name_id) - var/namedata = GLOB.preferences_custom_names[name_id] - if(!namedata) - return - - var/raw_name = input(user, "Choose your character's [namedata["qdesc"]]:","Character Preference") as text|null - if(!raw_name) - if(namedata["allow_null"]) - custom_names[name_id] = get_default_name(name_id) - else - return - else - var/sanitized_name = reject_bad_name(raw_name,namedata["allow_numbers"]) - if(!sanitized_name) - to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z,[namedata["allow_numbers"] ? ",0-9," : ""] -, ' and .") - return - else - custom_names[name_id] = sanitized_name - -//Used in savefile update 32, can be removed once that is no longer relevant. -/datum/preferences/proc/force_reset_keybindings() - var/choice = tgalert(parent.mob, "Your basic keybindings need to be reset, emotes will remain as before. Would you prefer 'hotkey' or 'classic' mode?", "Reset keybindings", "Hotkey", "Classic") - hotkeys = (choice != "Classic") - var/list/oldkeys = key_bindings - key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key) - - for(var/key in oldkeys) - if(!key_bindings[key]) - key_bindings[key] = oldkeys[key] - parent.update_movement_keys() +GLOBAL_LIST_EMPTY(preferences_datums) + +/datum/preferences + var/client/parent + //doohickeys for savefiles + var/path + var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used + var/max_save_slots = 3 + + //non-preference stuff + var/muted = 0 + var/last_ip + var/last_id + + //game-preferences + var/lastchangelog = "" //Saved changlog filesize to detect if there was a change + var/ooccolor = "#c43b23" + var/asaycolor = "#ff4500" //This won't change the color for current admins, only incoming ones. + var/enable_tips = TRUE + var/tip_delay = 500 //tip delay in milliseconds + + //Antag preferences + var/list/be_special = list() //Special role selection + var/tmp/old_be_special = 0 //Bitflag version of be_special, used to update old savefiles and nothing more + //If it's 0, that's good, if it's anything but 0, the owner of this prefs file's antag choices were, + //autocorrected this round, not that you'd need to check that. + + var/UI_style = null + var/buttons_locked = FALSE + var/hotkeys = TRUE + var/chat_on_map = TRUE + var/max_chat_length = CHAT_MESSAGE_MAX_LENGTH + var/see_chat_non_mob = TRUE + + // Custom Keybindings + var/list/key_bindings = list() + + var/tgui_fancy = TRUE + var/tgui_lock = TRUE + var/windowflashing = TRUE + var/toggles = TOGGLES_DEFAULT + var/db_flags + var/chat_toggles = TOGGLES_DEFAULT_CHAT + var/ghost_form = "ghost" + var/ghost_orbit = GHOST_ORBIT_CIRCLE + var/ghost_accs = GHOST_ACCS_DEFAULT_OPTION + var/ghost_others = GHOST_OTHERS_DEFAULT_OPTION + var/ghost_hud = 1 + var/inquisitive_ghost = 1 + var/allow_midround_antag = 1 + var/preferred_map = null + var/pda_style = MONO + var/pda_color = "#808000" + + var/uses_glasses_colour = 0 + + //character preferences + var/slot_randomized //keeps track of round-to-round randomization of the character slot, prevents overwriting + var/real_name //our character's name + var/gender = MALE //gender of character (well duh) + var/age = 30 //age of character + var/underwear = "Nude" //underwear type + var/underwear_color = "000" //underwear color + var/undershirt = "Nude" //undershirt type + var/socks = "Nude" //socks type + var/backpack = DBACKPACK //backpack type + var/jumpsuit_style = PREF_SUIT //suit/skirt + var/hairstyle = "Bald" //Hair type + var/hair_color = "000" //Hair color + var/facial_hairstyle = "Shaved" //Face hair type + var/facial_hair_color = "000" //Facial hair color + var/skin_tone = "caucasian1" //Skin color + var/eye_color = "000" //Eye color + var/datum/species/pref_species = new /datum/species/human() //Mutant race + var/list/features = list("mcolor" = "FFF", "ethcolor" = "9c3030", "tail_lizard" = "Smooth", "tail_human" = "None", "snout" = "Round", "horns" = "None", "ears" = "None", "wings" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs", "moth_wings" = "Plain", "moth_markings" = "None") + var/list/randomise = list(RANDOM_UNDERWEAR = TRUE, RANDOM_UNDERWEAR_COLOR = TRUE, RANDOM_UNDERSHIRT = TRUE, RANDOM_SOCKS = TRUE, RANDOM_BACKPACK = TRUE, RANDOM_JUMPSUIT_STYLE = TRUE, RANDOM_HAIRSTYLE = TRUE, RANDOM_HAIR_COLOR = TRUE, RANDOM_FACIAL_HAIRSTYLE = TRUE, RANDOM_FACIAL_HAIR_COLOR = TRUE, RANDOM_SKIN_TONE = TRUE, RANDOM_EYE_COLOR = TRUE) + var/phobia = "spiders" + + var/list/custom_names = list() + var/preferred_ai_core_display = "Blue" + var/prefered_security_department = SEC_DEPT_RANDOM + + //Quirk list + var/list/all_quirks = list() + + //Job preferences 2.0 - indexed by job title , no key or value implies never + var/list/job_preferences = list() + + // Want randomjob if preferences already filled - Donkie + var/joblessrole = BERANDOMJOB //defaults to 1 for fewer assistants + + // 0 = character settings, 1 = game preferences + var/current_tab = 0 + + var/unlock_content = 0 + + var/list/ignoring = list() + + var/clientfps = 0 + + var/parallax + + var/ambientocclusion = TRUE + ///Should we automatically fit the viewport? + var/auto_fit_viewport = FALSE + ///Should we be in the widescreen mode set by the config? + var/widescreenpref = TRUE + ///What size should pixels be displayed as? 0 is strech to fit + var/pixel_size = 0 + ///What scaling method should we use? Distort means nearest neighbor + var/scaling_method = SCALING_METHOD_DISTORT + var/uplink_spawn_loc = UPLINK_PDA + ///The playtime_reward_cloak variable can be set to TRUE from the prefs menu only once the user has gained over 5K playtime hours. If true, it allows the user to get a cool looking roundstart cloak. + var/playtime_reward_cloak = FALSE + + var/list/exp = list() + var/list/menuoptions + + var/action_buttons_screen_locs = list() + + ///This var stores the amount of points the owner will get for making it out alive. + var/hardcore_survival_score = 0 + + ///Someone thought we were nice! We get a little heart in OOC until we join the server past the below time (we can keep it until the end of the round otherwise) + var/hearted + ///If we have a hearted commendations, we honor it every time the player loads preferences until this time has been passed + var/hearted_until + /// Agendered spessmen can choose whether to have a male or female bodytype + var/body_type + + /// If we have persistent scars enabled + var/persistent_scars = TRUE + /// We have 5 slots for persistent scars, if enabled we pick a random one to load (empty by default) and scars at the end of the shift if we survived as our original person + var/list/scars_list = list("1" = "", "2" = "", "3" = "", "4" = "", "5" = "") + /// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)] + var/scars_index = 1 + +/datum/preferences/New(client/C) + parent = C + + for(var/custom_name_id in GLOB.preferences_custom_names) + custom_names[custom_name_id] = get_default_name(custom_name_id) + + UI_style = GLOB.available_ui_styles[1] + if(istype(C)) + if(!IsGuestKey(C.key)) + load_path(C.ckey) + unlock_content = C.IsByondMember() + if(unlock_content) + max_save_slots = 8 + var/loaded_preferences_successfully = load_preferences() + if(loaded_preferences_successfully) + if(load_character()) + return + //we couldn't load character data so just randomize the character appearance + name + random_character() //let's create a random character then - rather than a fat, bald and naked man. + key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys + C?.update_movement_keys(src) + real_name = pref_species.random_name(gender,1) + if(!loaded_preferences_successfully) + save_preferences() + save_character() //let's save this new random character so it doesn't keep generating new ones. + menuoptions = list() + return + +#define APPEARANCE_CATEGORY_COLUMN "" +#define MAX_MUTANT_ROWS 4 + +/datum/preferences/proc/ShowChoices(mob/user) + if(!user || !user.client) + return + if(slot_randomized) + load_character(default_slot) // Reloads the character slot. Prevents random features from overwriting the slot if saved. + slot_randomized = FALSE + update_preview_icon() + var/list/dat = list("
                ") + + dat += "Character Settings" + dat += "Game Preferences" + dat += "OOC Preferences" + dat += "Custom Keybindings" + + if(!path) + dat += "
                Please create an account to save your preferences
                " + + dat += "
                " + + dat += "
                " + + switch(current_tab) + if (0) // Character Settings# + if(path) + var/savefile/S = new /savefile(path) + if(S) + dat += "
                " + var/name + var/unspaced_slots = 0 + for(var/i=1, i<=max_save_slots, i++) + unspaced_slots++ + if(unspaced_slots > 4) + dat += "
                " + unspaced_slots = 0 + S.cd = "/character[i]" + S["real_name"] >> name + if(!name) + name = "Character[i]" + dat += "[name] " + dat += "
                " + + dat += "

                Occupation Choices

                " + dat += "Set Occupation Preferences
                " + if(CONFIG_GET(flag/roundstart_traits)) + dat += "

                Quirk Setup

                " + dat += "Configure Quirks
                " + dat += "
                Current Quirks: [all_quirks.len ? all_quirks.Join(", ") : "None"]
                " + dat += "

                Identity

                " + dat += "" + + dat += "
                " + if(is_banned_from(user.ckey, "Appearance")) + dat += "You are banned from using custom names and appearances. You can continue to adjust your characters, but you will be randomised once you join the game.
                " + dat += "Random Name " + dat += "Always Random Name: [(randomise[RANDOM_NAME]) ? "Yes" : "No"]" + dat += "When Antagonist: [(randomise[RANDOM_NAME_ANTAG]) ? "Yes" : "No"]" + if(user.client.get_exp_living(TRUE) >= PLAYTIME_HARDCORE_RANDOM) + dat += "Hardcore Random: [(randomise[RANDOM_HARDCORE]) ? "Yes" : "No"]" + dat += "
                Name: " + dat += "[real_name]
                " + + if(!(AGENDER in pref_species.species_traits)) + var/dispGender + if(gender == MALE) + dispGender = "Male" + else if(gender == FEMALE) + dispGender = "Female" + else + dispGender = "Other" + dat += "Gender: [dispGender]" + if(gender == PLURAL || gender == NEUTER) + dat += "
                Body Type: [body_type == MALE ? "Male" : "Female"]" + + if(randomise[RANDOM_BODY] || randomise[RANDOM_BODY_ANTAG]) //doesn't work unless random body + dat += "Always Random Gender: [(randomise[RANDOM_GENDER]) ? "Yes" : "No"]" + dat += "When Antagonist: [(randomise[RANDOM_GENDER_ANTAG]) ? "Yes" : "No"]" + + dat += "
                Age: [age]" + if(randomise[RANDOM_BODY] || randomise[RANDOM_BODY_ANTAG]) //doesn't work unless random body + dat += "Always Random Age: [(randomise[RANDOM_AGE]) ? "Yes" : "No"]" + dat += "When Antagonist: [(randomise[RANDOM_AGE_ANTAG]) ? "Yes" : "No"]" + + dat += "

                Special Names:
                " + var/old_group + for(var/custom_name_id in GLOB.preferences_custom_names) + var/namedata = GLOB.preferences_custom_names[custom_name_id] + if(!old_group) + old_group = namedata["group"] + else if(old_group != namedata["group"]) + old_group = namedata["group"] + dat += "
                " + dat += "[namedata["pref_name"]]: [custom_names[custom_name_id]] " + dat += "

                " + + dat += "Custom Job Preferences:
                " + dat += "Preferred AI Core Display: [preferred_ai_core_display]
                " + dat += "Preferred Security Department: [prefered_security_department]
                " + + dat += "

                Body

                " + dat += "Random Body " + dat += "Always Random Body: [(randomise[RANDOM_BODY]) ? "Yes" : "No"]" + dat += "When Antagonist: [(randomise[RANDOM_BODY_ANTAG]) ? "Yes" : "No"]
                " + + dat += "" + if (user.client.get_exp_living(TRUE) >= PLAYTIME_VETERAN) + dat += "
                Don The Ultimate Gamer Cloak?:
                [(playtime_reward_cloak) ? "Enabled" : "Disabled"]
                " + var/use_skintones = pref_species.use_skintones + if(use_skintones) + + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Skin Tone

                " + + dat += "[skin_tone]" + dat += "[(randomise[RANDOM_SKIN_TONE]) ? "Lock" : "Unlock"]" + dat += "
                " + + var/mutant_colors + if((MUTCOLORS in pref_species.species_traits) || (MUTCOLORS_PARTSONLY in pref_species.species_traits)) + + if(!use_skintones) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Mutant Color

                " + + dat += "   Change
                " + + mutant_colors = TRUE + + if(istype(pref_species, /datum/species/ethereal)) //not the best thing to do tbf but I dont know whats better. + + if(!use_skintones) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Ethereal Color

                " + + dat += "   Change
                " + + + if((EYECOLOR in pref_species.species_traits) && !(NOEYESPRITES in pref_species.species_traits)) + + if(!use_skintones && !mutant_colors) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Eye Color

                " + dat += "   Change" + dat += "[(randomise[RANDOM_EYE_COLOR]) ? "Lock" : "Unlock"]" + + dat += "
                " + else if(use_skintones || mutant_colors) + dat += "" + + if(HAIR in pref_species.species_traits) + + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Hairstyle

                " + + dat += "[hairstyle]" + dat += "<>" + dat += "[(randomise[RANDOM_HAIRSTYLE]) ? "Lock" : "Unlock"]" + + dat += "
                   Change" + dat += "[(randomise[RANDOM_HAIR_COLOR]) ? "Lock" : "Unlock"]" + + dat += "

                Facial Hairstyle

                " + + dat += "[facial_hairstyle]" + dat += "<>" + dat += "[(randomise[RANDOM_FACIAL_HAIRSTYLE]) ? "Lock" : "Unlock"]" + + dat += "
                   Change" + dat += "[(randomise[RANDOM_FACIAL_HAIR_COLOR]) ? "Lock" : "Unlock"]" + dat += "
                " + + //Mutant stuff + var/mutant_category = 0 + + if("tail_lizard" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Tail

                " + + dat += "[features["tail_lizard"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("snout" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Snout

                " + + dat += "[features["snout"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("horns" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Horns

                " + + dat += "[features["horns"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("frills" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Frills

                " + + dat += "[features["frills"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("spines" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Spines

                " + + dat += "[features["spines"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("body_markings" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Body Markings

                " + + dat += "[features["body_markings"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("legs" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Legs

                " + + dat += "[features["legs"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("moth_wings" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Moth wings

                " + + dat += "[features["moth_wings"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("moth_markings" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Moth markings

                " + + dat += "[features["moth_markings"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("tail_human" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Tail

                " + + dat += "[features["tail_human"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if("ears" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Ears

                " + + dat += "[features["ears"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + //Adds a thing to select which phobia because I can't be assed to put that in the quirks window + if("Phobia" in all_quirks) + dat += "

                Phobia

                " + + dat += "[phobia]
                " + + if(CONFIG_GET(flag/join_with_mutant_humans)) + + if("wings" in pref_species.default_features && GLOB.r_wings_list.len >1) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Wings

                " + + dat += "[features["wings"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if(mutant_category) + dat += "" + mutant_category = 0 + dat += "
                " + + dat += "Species:
                [pref_species.name]
                " + dat += "Random Species " + dat += "Always Random Species: [(randomise[RANDOM_SPECIES]) ? "Yes" : "No"]
                " + + dat += "Underwear:
                [underwear]" + dat += "[(randomise[RANDOM_UNDERWEAR]) ? "Lock" : "Unlock"]" + + dat += "
                Underwear Color:
                    Change" + dat += "[(randomise[RANDOM_UNDERWEAR_COLOR]) ? "Lock" : "Unlock"]" + + dat += "
                Undershirt:
                [undershirt]" + dat += "[(randomise[RANDOM_UNDERSHIRT]) ? "Lock" : "Unlock"]" + + + dat += "
                Socks:
                [socks]" + dat += "[(randomise[RANDOM_SOCKS]) ? "Lock" : "Unlock"]" + + + dat += "
                Jumpsuit Style:
                [jumpsuit_style]" + dat += "[(randomise[RANDOM_JUMPSUIT_STYLE]) ? "Lock" : "Unlock"]" + + dat += "
                Backpack:
                [backpack]" + dat += "[(randomise[RANDOM_BACKPACK]) ? "Lock" : "Unlock"]" + + if(CAN_SCAR in pref_species.species_traits) + dat += "
                Temporal Scarring:
                [(persistent_scars) ? "Enabled" : "Disabled"]" + dat += "Clear scar slots" + + dat += "
                Uplink Spawn Location:
                [uplink_spawn_loc]
                " + + + if (1) // Game Preferences + dat += "
                " + dat += "

                General Settings

                " + dat += "UI Style: [UI_style]
                " + dat += "tgui Monitors: [(tgui_lock) ? "Primary" : "All"]
                " + dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"]
                " + dat += "Show Runechat Chat Bubbles: [chat_on_map ? "Enabled" : "Disabled"]
                " + dat += "Runechat message char limit: [max_chat_length]
                " + dat += "See Runechat for non-mobs: [see_chat_non_mob ? "Enabled" : "Disabled"]
                " + dat += "
                " + dat += "Action Buttons: [(buttons_locked) ? "Locked In Place" : "Unlocked"]
                " + dat += "Hotkey mode: [(hotkeys) ? "Hotkeys" : "Default"]
                " + dat += "
                " + dat += "PDA Color:     Change
                " + dat += "PDA Style: [pda_style]
                " + dat += "
                " + dat += "Ghost Ears: [(chat_toggles & CHAT_GHOSTEARS) ? "All Speech" : "Nearest Creatures"]
                " + dat += "Ghost Radio: [(chat_toggles & CHAT_GHOSTRADIO) ? "All Messages":"No Messages"]
                " + dat += "Ghost Sight: [(chat_toggles & CHAT_GHOSTSIGHT) ? "All Emotes" : "Nearest Creatures"]
                " + dat += "Ghost Whispers: [(chat_toggles & CHAT_GHOSTWHISPER) ? "All Speech" : "Nearest Creatures"]
                " + dat += "Ghost PDA: [(chat_toggles & CHAT_GHOSTPDA) ? "All Messages" : "Nearest Creatures"]
                " + dat += "Ghost Law Changes: [(chat_toggles & CHAT_GHOSTLAWS) ? "All Law Changes" : "No Law Changes"]
                " + + if(unlock_content) + dat += "Ghost Form: [ghost_form]
                " + dat += "Ghost Orbit: [ghost_orbit]
                " + + var/button_name = "If you see this something went wrong." + switch(ghost_accs) + if(GHOST_ACCS_FULL) + button_name = GHOST_ACCS_FULL_NAME + if(GHOST_ACCS_DIR) + button_name = GHOST_ACCS_DIR_NAME + if(GHOST_ACCS_NONE) + button_name = GHOST_ACCS_NONE_NAME + + dat += "Ghost Accessories: [button_name]
                " + + switch(ghost_others) + if(GHOST_OTHERS_THEIR_SETTING) + button_name = GHOST_OTHERS_THEIR_SETTING_NAME + if(GHOST_OTHERS_DEFAULT_SPRITE) + button_name = GHOST_OTHERS_DEFAULT_SPRITE_NAME + if(GHOST_OTHERS_SIMPLE) + button_name = GHOST_OTHERS_SIMPLE_NAME + + dat += "Ghosts of Others: [button_name]
                " + dat += "
                " + + dat += "Income Updates: [(chat_toggles & CHAT_BANKCARD) ? "Allowed" : "Muted"]
                " + dat += "
                " + + dat += "FPS: [clientfps]
                " + + dat += "Parallax (Fancy Space): " + switch (parallax) + if (PARALLAX_LOW) + dat += "Low" + if (PARALLAX_MED) + dat += "Medium" + if (PARALLAX_INSANE) + dat += "Insane" + if (PARALLAX_DISABLE) + dat += "Disabled" + else + dat += "High" + dat += "
                " + + dat += "Ambient Occlusion: [ambientocclusion ? "Enabled" : "Disabled"]
                " + dat += "Fit Viewport: [auto_fit_viewport ? "Auto" : "Manual"]
                " + if (CONFIG_GET(string/default_view) != CONFIG_GET(string/default_view_square)) + dat += "Widescreen: [widescreenpref ? "Enabled ([CONFIG_GET(string/default_view)])" : "Disabled ([CONFIG_GET(string/default_view_square)])"]
                " + + button_name = pixel_size + dat += "Pixel Scaling: [(button_name) ? "Pixel Perfect [button_name]x" : "Stretch to fit"]
                " + + switch(scaling_method) + if(SCALING_METHOD_DISTORT) + button_name = "Nearest Neighbor" + if(SCALING_METHOD_NORMAL) + button_name = "Point Sampling" + if(SCALING_METHOD_BLUR) + button_name = "Bilinear" + dat += "Scaling Method: [button_name]
                " + + if (CONFIG_GET(flag/maprotation)) + var/p_map = preferred_map + if (!p_map) + p_map = "Default" + if (config.defaultmap) + p_map += " ([config.defaultmap.map_name])" + else + if (p_map in config.maplist) + var/datum/map_config/VM = config.maplist[p_map] + if (!VM) + p_map += " (No longer exists)" + else + p_map = VM.map_name + else + p_map += " (No longer exists)" + if(CONFIG_GET(flag/preference_map_voting)) + dat += "Preferred Map: [p_map]
                " + + dat += "
                " + + dat += "

                Special Role Settings

                " + + if(is_banned_from(user.ckey, ROLE_SYNDICATE)) + dat += "You are banned from antagonist roles.
                " + src.be_special = list() + + + for (var/i in GLOB.special_roles) + if(is_banned_from(user.ckey, i)) + dat += "Be [capitalize(i)]: BANNED
                " + else + var/days_remaining = null + if(ispath(GLOB.special_roles[i]) && CONFIG_GET(flag/use_age_restriction_for_jobs)) //If it's a game mode antag, check if the player meets the minimum age + var/mode_path = GLOB.special_roles[i] + var/datum/game_mode/temp_mode = new mode_path + days_remaining = temp_mode.get_remaining_days(user.client) + + if(days_remaining) + dat += "Be [capitalize(i)]: \[IN [days_remaining] DAYS]
                " + else + dat += "Be [capitalize(i)]: [(i in be_special) ? "Enabled" : "Disabled"]
                " + dat += "
                " + dat += "Midround Antagonist: [(toggles & MIDROUND_ANTAG) ? "Enabled" : "Disabled"]
                " + dat += "
                " + if(2) //OOC Preferences + dat += "" + + if(user.client.holder) + dat +="" + dat += "
                " + dat += "

                OOC Settings

                " + dat += "Window Flashing: [(windowflashing) ? "Enabled":"Disabled"]
                " + dat += "
                " + dat += "Play Admin MIDIs: [(toggles & SOUND_MIDI) ? "Enabled":"Disabled"]
                " + dat += "Play Lobby Music: [(toggles & SOUND_LOBBY) ? "Enabled":"Disabled"]
                " + dat += "Play End of Round Sounds: [(toggles & SOUND_ENDOFROUND) ? "Enabled":"Disabled"]
                " + dat += "See Pull Requests: [(chat_toggles & CHAT_PULLR) ? "Enabled":"Disabled"]
                " + dat += "
                " + + + if(user.client) + if(unlock_content) + dat += "BYOND Membership Publicity: [(toggles & MEMBER_PUBLIC) ? "Public" : "Hidden"]
                " + + if(unlock_content || check_rights_for(user.client, R_ADMIN)) + dat += "OOC Color:     Change
                " + if(hearted_until) + dat += "Clear OOC Commend Heart
                " + + dat += "
                " + + dat += "

                Admin Settings

                " + + dat += "Adminhelp Sounds: [(toggles & SOUND_ADMINHELP)?"Enabled":"Disabled"]
                " + dat += "Prayer Sounds: [(toggles & SOUND_PRAYERS)?"Enabled":"Disabled"]
                " + dat += "Announce Login: [(toggles & ANNOUNCE_LOGIN)?"Enabled":"Disabled"]
                " + dat += "
                " + dat += "Combo HUD Lighting: [(toggles & COMBOHUD_LIGHTING)?"Full-bright":"No Change"]
                " + dat += "
                " + dat += "Hide Dead Chat: [(chat_toggles & CHAT_DEAD)?"Shown":"Hidden"]
                " + dat += "Hide Radio Messages: [(chat_toggles & CHAT_RADIO)?"Shown":"Hidden"]
                " + dat += "Hide Prayers: [(chat_toggles & CHAT_PRAYER)?"Shown":"Hidden"]
                " + dat += "Ignore Being Summoned as Cult Ghost: [(toggles & ADMIN_IGNORE_CULT_GHOST)?"Don't Allow Being Summoned":"Allow Being Summoned"]
                " + if(CONFIG_GET(flag/allow_admin_asaycolor)) + dat += "
                " + dat += "ASAY Color:     Change
                " + + //deadmin + dat += "

                Deadmin While Playing

                " + var/timegate = CONFIG_GET(number/auto_deadmin_timegate) + if(timegate) + dat += "Noted roles will automatically deadmin during the first [FLOOR(timegate / 600, 1)] minutes of the round, and will defer to individual preferences after.
                " + + if(CONFIG_GET(flag/auto_deadmin_players) && !timegate) + dat += "Always Deadmin: FORCED
                " + else + dat += "Always Deadmin: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_ALWAYS)?"Enabled":"Disabled"]
                " + if(!(toggles & DEADMIN_ALWAYS)) + dat += "
                " + if(!CONFIG_GET(flag/auto_deadmin_antagonists) || (CONFIG_GET(flag/auto_deadmin_antagonists) && !timegate)) + dat += "As Antag: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_ANTAGONIST)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Antag: FORCED
                " + + if(!CONFIG_GET(flag/auto_deadmin_heads) || (CONFIG_GET(flag/auto_deadmin_heads) && !timegate)) + dat += "As Command: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_HEAD)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Command: FORCED
                " + + if(!CONFIG_GET(flag/auto_deadmin_security) || (CONFIG_GET(flag/auto_deadmin_security) && !timegate)) + dat += "As Security: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_SECURITY)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Security: FORCED
                " + + if(!CONFIG_GET(flag/auto_deadmin_silicons) || (CONFIG_GET(flag/auto_deadmin_silicons) && !timegate)) + dat += "As Silicon: [timegate ? "(Time Locked) " : ""][(toggles & DEADMIN_POSITION_SILICON)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Silicon: FORCED
                " + + dat += "
                " + if(3) // Custom keybindings + // Create an inverted list of keybindings -> key + var/list/user_binds = list() + for (var/key in key_bindings) + for(var/kb_name in key_bindings[key]) + user_binds[kb_name] += list(key) + + var/list/kb_categories = list() + // Group keybinds by category + for (var/name in GLOB.keybindings_by_name) + var/datum/keybinding/kb = GLOB.keybindings_by_name[name] + kb_categories[kb.category] += list(kb) + + dat += "" + + for (var/category in kb_categories) + dat += "

                [category]

                " + for (var/i in kb_categories[category]) + var/datum/keybinding/kb = i + if(!length(user_binds[kb.name])) + dat += " Unbound" + var/list/default_keys = hotkeys ? kb.hotkey_keys : kb.classic_keys + if(LAZYLEN(default_keys)) + dat += "| Default: [default_keys.Join(", ")]" + dat += "
                " + else + var/bound_key = user_binds[kb.name][1] + dat += " [bound_key]" + for(var/bound_key_index in 2 to length(user_binds[kb.name])) + bound_key = user_binds[kb.name][bound_key_index] + dat += " | [bound_key]" + if(length(user_binds[kb.name]) < MAX_KEYS_PER_KEYBIND) + dat += "| Add Secondary" + var/list/default_keys = hotkeys ? kb.classic_keys : kb.hotkey_keys + if(LAZYLEN(default_keys)) + dat += "| Default: [default_keys.Join(", ")]" + dat += "
                " + + dat += "

                " + dat += "\[Reset to default\]" + dat += "" + dat += "
                " + + if(!IsGuestKey(user.key)) + dat += "Undo " + dat += "Save Setup " + + dat += "Reset Setup" + dat += "
                " + + winshow(user, "preferences_window", TRUE) + var/datum/browser/popup = new(user, "preferences_browser", "
                Character Setup
                ", 640, 770) + popup.set_content(dat.Join()) + popup.open(FALSE) + onclose(user, "preferences_window", src) + +#undef APPEARANCE_CATEGORY_COLUMN +#undef MAX_MUTANT_ROWS + +/datum/preferences/proc/CaptureKeybinding(mob/user, datum/keybinding/kb, var/old_key) + var/HTML = {" +
                Keybinding: [kb.full_name]
                [kb.description]

                Press any key to change
                Press ESC to clear
                + + "} + winshow(user, "capturekeypress", TRUE) + var/datum/browser/popup = new(user, "capturekeypress", "
                Keybindings
                ", 350, 300) + popup.set_content(HTML) + popup.open(FALSE) + onclose(user, "capturekeypress", src) + +/datum/preferences/proc/SetChoices(mob/user, limit = 17, list/splitJobs = list("Chief Engineer"), widthPerColumn = 295, height = 620) + if(!SSjob) + return + + //limit - The amount of jobs allowed per column. Defaults to 17 to make it look nice. + //splitJobs - Allows you split the table by job. You can make different tables for each department by including their heads. Defaults to CE to make it look nice. + //widthPerColumn - Screen's width for every column. + //height - Screen's height. + + var/width = widthPerColumn + + var/HTML = "
                " + if(SSjob.occupations.len <= 0) + HTML += "The job SSticker is not yet finished creating jobs, please try again later" + HTML += "
                Done

                " // Easier to press up here. + + else + HTML += "Choose occupation chances
                " + HTML += "
                Left-click to raise an occupation preference, right-click to lower it.
                " + HTML += "
                Done

                " // Easier to press up here. + HTML += "" + HTML += "
                " // Table within a table for alignment, also allows you to easily add more colomns. + HTML += "" + var/index = -1 + + //The job before the current job. I only use this to get the previous jobs color when I'm filling in blank rows. + var/datum/job/lastJob + + for(var/datum/job/job in sortList(SSjob.occupations, /proc/cmp_job_display_asc)) + + index += 1 + if((index >= limit) || (job.title in splitJobs)) + width += widthPerColumn + if((index < limit) && (lastJob != null)) + //If the cells were broken up by a job in the splitJob list then it will fill in the rest of the cells with + //the last job's selection color. Creating a rather nice effect. + for(var/i = 0, i < (limit - index), i += 1) + HTML += "" + HTML += "
                  
                " + index = 0 + + HTML += "" + continue + var/required_playtime_remaining = job.required_playtime_remaining(user.client) + if(required_playtime_remaining) + HTML += "[rank]" + continue + if(!job.player_old_enough(user.client)) + var/available_in_days = job.available_in_days(user.client) + HTML += "[rank]" + continue + if((job_preferences[SSjob.overflow_role] == JP_LOW) && (rank != SSjob.overflow_role) && !is_banned_from(user.ckey, SSjob.overflow_role)) + HTML += "[rank]" + continue + if((rank in GLOB.command_positions) || (rank == "AI"))//Bold head jobs + HTML += "[rank]" + else + HTML += "[rank]" + + HTML += "" + continue + + HTML += "[prefLevelLabel]" + HTML += "" + + for(var/i = 1, i < (limit - index), i += 1) // Finish the column so it is even + HTML += "" + + HTML += "
                " + var/rank = job.title + lastJob = job + if(is_banned_from(user.ckey, rank)) + HTML += "[rank] BANNED
                \[ [get_exp_format(required_playtime_remaining)] as [job.get_exp_req_type()] \]
                \[IN [(available_in_days)] DAYS\]
                " + + var/prefLevelLabel = "ERROR" + var/prefLevelColor = "pink" + var/prefUpperLevel = -1 // level to assign on left click + var/prefLowerLevel = -1 // level to assign on right click + + switch(job_preferences[job.title]) + if(JP_HIGH) + prefLevelLabel = "High" + prefLevelColor = "slateblue" + prefUpperLevel = 4 + prefLowerLevel = 2 + if(JP_MEDIUM) + prefLevelLabel = "Medium" + prefLevelColor = "green" + prefUpperLevel = 1 + prefLowerLevel = 3 + if(JP_LOW) + prefLevelLabel = "Low" + prefLevelColor = "orange" + prefUpperLevel = 2 + prefLowerLevel = 4 + else + prefLevelLabel = "NEVER" + prefLevelColor = "red" + prefUpperLevel = 3 + prefLowerLevel = 1 + + HTML += "" + + if(rank == SSjob.overflow_role)//Overflow is special + if(job_preferences[SSjob.overflow_role] == JP_LOW) + HTML += "Yes" + else + HTML += "No" + HTML += "
                  
                " + HTML += "
                " + + var/message = "Be an [SSjob.overflow_role] if preferences unavailable" + if(joblessrole == BERANDOMJOB) + message = "Get random job if preferences unavailable" + else if(joblessrole == RETURNTOLOBBY) + message = "Return to lobby if preferences unavailable" + HTML += "

                [message]
                " + HTML += "
                Reset Preferences
                " + + var/datum/browser/popup = new(user, "mob_occupation", "
                Occupation Preferences
                ", width, height) + popup.set_window_options("can_close=0") + popup.set_content(HTML) + popup.open(FALSE) + +/datum/preferences/proc/SetJobPreferenceLevel(datum/job/job, level) + if (!job) + return FALSE + + if (level == JP_HIGH) // to high + //Set all other high to medium + for(var/j in job_preferences) + if(job_preferences[j] == JP_HIGH) + job_preferences[j] = JP_MEDIUM + //technically break here + + job_preferences[job.title] = level + return TRUE + +/datum/preferences/proc/UpdateJobPreference(mob/user, role, desiredLvl) + if(!SSjob || SSjob.occupations.len <= 0) + return + var/datum/job/job = SSjob.GetJob(role) + + if(!job) + user << browse(null, "window=mob_occupation") + ShowChoices(user) + return + + if (!isnum(desiredLvl)) + to_chat(user, "UpdateJobPreference - desired level was not a number. Please notify coders!") + ShowChoices(user) + return + + var/jpval = null + switch(desiredLvl) + if(3) + jpval = JP_LOW + if(2) + jpval = JP_MEDIUM + if(1) + jpval = JP_HIGH + + if(role == SSjob.overflow_role) + if(job_preferences[job.title] == JP_LOW) + jpval = null + else + jpval = JP_LOW + + SetJobPreferenceLevel(job, jpval) + SetChoices(user) + + return 1 + + +/datum/preferences/proc/ResetJobs() + job_preferences = list() + +/datum/preferences/proc/SetQuirks(mob/user) + if(!SSquirks) + to_chat(user, "The quirk subsystem is still initializing! Try again in a minute.") + return + + var/list/dat = list() + if(!SSquirks.quirks.len) + dat += "The quirk subsystem hasn't finished initializing, please hold..." + dat += "
                Done

                " + else + dat += "
                Choose quirk setup

                " + dat += "
                Left-click to add or remove quirks. You need negative quirks to have positive ones.
                \ + Quirks are applied at roundstart and cannot normally be removed.
                " + dat += "
                Done
                " + dat += "
                " + dat += "
                Current quirks: [all_quirks.len ? all_quirks.Join(", ") : "None"]
                " + dat += "
                [GetPositiveQuirkCount()] / [MAX_QUIRKS] max positive quirks
                \ + Quirk balance remaining: [GetQuirkBalance()]

                " + for(var/V in SSquirks.quirks) + var/datum/quirk/T = SSquirks.quirks[V] + var/quirk_name = initial(T.name) + var/has_quirk + var/quirk_cost = initial(T.value) * -1 + var/lock_reason = "This trait is unavailable." + var/quirk_conflict = FALSE + for(var/_V in all_quirks) + if(_V == quirk_name) + has_quirk = TRUE + if(initial(T.mood_quirk) && CONFIG_GET(flag/disable_human_mood)) + lock_reason = "Mood is disabled." + quirk_conflict = TRUE + if(has_quirk) + if(quirk_conflict) + all_quirks -= quirk_name + has_quirk = FALSE + else + quirk_cost *= -1 //invert it back, since we'd be regaining this amount + if(quirk_cost > 0) + quirk_cost = "+[quirk_cost]" + var/font_color = "#AAAAFF" + if(initial(T.value) != 0) + font_color = initial(T.value) > 0 ? "#AAFFAA" : "#FFAAAA" + if(quirk_conflict) + dat += "[quirk_name] - [initial(T.desc)] \ + LOCKED: [lock_reason]
                " + else + if(has_quirk) + dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ + [quirk_name] - [initial(T.desc)]
                " + else + dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ + [quirk_name] - [initial(T.desc)]
                " + dat += "
                Reset Quirks
                " + + var/datum/browser/popup = new(user, "mob_occupation", "
                Quirk Preferences
                ", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way + popup.set_window_options("can_close=0") + popup.set_content(dat.Join()) + popup.open(FALSE) + +/datum/preferences/proc/GetQuirkBalance() + var/bal = 0 + for(var/V in all_quirks) + var/datum/quirk/T = SSquirks.quirks[V] + bal -= initial(T.value) + return bal + +/datum/preferences/proc/GetPositiveQuirkCount() + . = 0 + for(var/q in all_quirks) + if(SSquirks.quirk_points[q] > 0) + .++ + +/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess.. + . = ..() + if(href_list["close"]) + var/client/C = usr.client + if(C) + C.clear_character_previews() + +/datum/preferences/proc/process_link(mob/user, list/href_list) + if(href_list["bancheck"]) + var/list/ban_details = is_banned_from_with_details(user.ckey, user.client.address, user.client.computer_id, href_list["bancheck"]) + var/admin = FALSE + if(GLOB.admin_datums[user.ckey] || GLOB.deadmins[user.ckey]) + admin = TRUE + for(var/i in ban_details) + if(admin && !text2num(i["applies_to_admins"])) + continue + ban_details = i + break //we only want to get the most recent ban's details + if(ban_details && ban_details.len) + var/expires = "This is a permanent ban." + if(ban_details["expiration_time"]) + expires = " The ban is for [DisplayTimeText(text2num(ban_details["duration"]) MINUTES)] and expires on [ban_details["expiration_time"]] (server time)." + to_chat(user, "You, or another user of this computer or connection ([ban_details["key"]]) is banned from playing [href_list["bancheck"]].
                The ban reason is: [ban_details["reason"]]
                This ban (BanID #[ban_details["id"]]) was applied by [ban_details["admin_key"]] on [ban_details["bantime"]] during round ID [ban_details["round_id"]].
                [expires]
                ") + return + if(href_list["preference"] == "job") + switch(href_list["task"]) + if("close") + user << browse(null, "window=mob_occupation") + ShowChoices(user) + if("reset") + ResetJobs() + SetChoices(user) + if("random") + switch(joblessrole) + if(RETURNTOLOBBY) + if(is_banned_from(user.ckey, SSjob.overflow_role)) + joblessrole = BERANDOMJOB + else + joblessrole = BEOVERFLOW + if(BEOVERFLOW) + joblessrole = BERANDOMJOB + if(BERANDOMJOB) + joblessrole = RETURNTOLOBBY + SetChoices(user) + if("setJobLevel") + UpdateJobPreference(user, href_list["text"], text2num(href_list["level"])) + else + SetChoices(user) + return 1 + + else if(href_list["preference"] == "trait") + switch(href_list["task"]) + if("close") + user << browse(null, "window=mob_occupation") + ShowChoices(user) + if("update") + var/quirk = href_list["trait"] + if(!SSquirks.quirks[quirk]) + return + for(var/V in SSquirks.quirk_blacklist) //V is a list + var/list/L = V + if(!(quirk in L)) + continue + for(var/Q in all_quirks) + if((Q in L) && !(Q == quirk)) //two quirks have lined up in the list of the list of quirks that conflict with each other, so return (see quirks.dm for more details) + to_chat(user, "[quirk] is incompatible with [Q].") + return + var/value = SSquirks.quirk_points[quirk] + var/balance = GetQuirkBalance() + if(quirk in all_quirks) + if(balance + value < 0) + to_chat(user, "Refunding this would cause you to go below your balance!") + return + all_quirks -= quirk + else + var/is_positive_quirk = SSquirks.quirk_points[quirk] > 0 + if(is_positive_quirk && GetPositiveQuirkCount() >= MAX_QUIRKS) + to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!") + return + if(balance - value < 0) + to_chat(user, "You don't have enough balance to gain this quirk!") + return + all_quirks += quirk + SetQuirks(user) + if("reset") + all_quirks = list() + SetQuirks(user) + else + SetQuirks(user) + return TRUE + + switch(href_list["task"]) + if("random") + switch(href_list["preference"]) + if("name") + real_name = pref_species.random_name(gender,1) + if("age") + age = rand(AGE_MIN, AGE_MAX) + if("hair") + hair_color = random_short_color() + if("hairstyle") + hairstyle = random_hairstyle(gender) + if("facial") + facial_hair_color = random_short_color() + if("facial_hairstyle") + facial_hairstyle = random_facial_hairstyle(gender) + if("underwear") + underwear = random_underwear(gender) + if("underwear_color") + underwear_color = random_short_color() + if("undershirt") + undershirt = random_undershirt(gender) + if("socks") + socks = random_socks() + if(BODY_ZONE_PRECISE_EYES) + eye_color = random_eye_color() + if("s_tone") + skin_tone = random_skin_tone() + if("species") + random_species() + if("bag") + backpack = pick(GLOB.backpacklist) + if("suit") + jumpsuit_style = pick(GLOB.jumpsuitlist) + if("all") + random_character(gender) + + if("input") + + if(href_list["preference"] in GLOB.preferences_custom_names) + ask_for_custom_name(user,href_list["preference"]) + + + switch(href_list["preference"]) + if("ghostform") + if(unlock_content) + var/new_form = input(user, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms + if(new_form) + ghost_form = new_form + if("ghostorbit") + if(unlock_content) + var/new_orbit = input(user, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND", null) as null|anything in GLOB.ghost_orbits + if(new_orbit) + ghost_orbit = new_orbit + + if("ghostaccs") + var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,GHOST_ACCS_FULL_NAME, GHOST_ACCS_DIR_NAME, GHOST_ACCS_NONE_NAME) + switch(new_ghost_accs) + if(GHOST_ACCS_FULL_NAME) + ghost_accs = GHOST_ACCS_FULL + if(GHOST_ACCS_DIR_NAME) + ghost_accs = GHOST_ACCS_DIR + if(GHOST_ACCS_NONE_NAME) + ghost_accs = GHOST_ACCS_NONE + + if("ghostothers") + var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,GHOST_OTHERS_THEIR_SETTING_NAME, GHOST_OTHERS_DEFAULT_SPRITE_NAME, GHOST_OTHERS_SIMPLE_NAME) + switch(new_ghost_others) + if(GHOST_OTHERS_THEIR_SETTING_NAME) + ghost_others = GHOST_OTHERS_THEIR_SETTING + if(GHOST_OTHERS_DEFAULT_SPRITE_NAME) + ghost_others = GHOST_OTHERS_DEFAULT_SPRITE + if(GHOST_OTHERS_SIMPLE_NAME) + ghost_others = GHOST_OTHERS_SIMPLE + + if("name") + var/new_name = input(user, "Choose your character's name:", "Character Preference") as text|null + if(new_name) + new_name = reject_bad_name(new_name) + if(new_name) + real_name = new_name + else + to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .") + + if("age") + var/new_age = input(user, "Choose your character's age:\n([AGE_MIN]-[AGE_MAX])", "Character Preference") as num|null + if(new_age) + age = max(min( round(text2num(new_age)), AGE_MAX),AGE_MIN) + + if("hair") + var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null + if(new_hair) + hair_color = sanitize_hexcolor(new_hair) + + if("hairstyle") + var/new_hairstyle + if(gender == MALE) + new_hairstyle = input(user, "Choose your character's hairstyle:", "Character Preference") as null|anything in GLOB.hairstyles_male_list + else if(gender == FEMALE) + new_hairstyle = input(user, "Choose your character's hairstyle:", "Character Preference") as null|anything in GLOB.hairstyles_female_list + else + new_hairstyle = input(user, "Choose your character's hairstyle:", "Character Preference") as null|anything in GLOB.hairstyles_list + if(new_hairstyle) + hairstyle = new_hairstyle + + if("next_hairstyle") + if (gender == MALE) + hairstyle = next_list_item(hairstyle, GLOB.hairstyles_male_list) + else if(gender == FEMALE) + hairstyle = next_list_item(hairstyle, GLOB.hairstyles_female_list) + else + hairstyle = next_list_item(hairstyle, GLOB.hairstyles_list) + + if("previous_hairstyle") + if (gender == MALE) + hairstyle = previous_list_item(hairstyle, GLOB.hairstyles_male_list) + else if(gender == FEMALE) + hairstyle = previous_list_item(hairstyle, GLOB.hairstyles_female_list) + else + hairstyle = previous_list_item(hairstyle, GLOB.hairstyles_list) + + if("facial") + var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference","#"+facial_hair_color) as color|null + if(new_facial) + facial_hair_color = sanitize_hexcolor(new_facial) + + if("facial_hairstyle") + var/new_facial_hairstyle + if(gender == MALE) + new_facial_hairstyle = input(user, "Choose your character's facial-hairstyle:", "Character Preference") as null|anything in GLOB.facial_hairstyles_male_list + else if(gender == FEMALE) + new_facial_hairstyle = input(user, "Choose your character's facial-hairstyle:", "Character Preference") as null|anything in GLOB.facial_hairstyles_female_list + else + new_facial_hairstyle = input(user, "Choose your character's facial-hairstyle:", "Character Preference") as null|anything in GLOB.facial_hairstyles_list + if(new_facial_hairstyle) + facial_hairstyle = new_facial_hairstyle + + if("next_facehairstyle") + if (gender == MALE) + facial_hairstyle = next_list_item(facial_hairstyle, GLOB.facial_hairstyles_male_list) + else if(gender == FEMALE) + facial_hairstyle = next_list_item(facial_hairstyle, GLOB.facial_hairstyles_female_list) + else + facial_hairstyle = next_list_item(facial_hairstyle, GLOB.facial_hairstyles_list) + + if("previous_facehairstyle") + if (gender == MALE) + facial_hairstyle = previous_list_item(facial_hairstyle, GLOB.facial_hairstyles_male_list) + else if (gender == FEMALE) + facial_hairstyle = previous_list_item(facial_hairstyle, GLOB.facial_hairstyles_female_list) + else + facial_hairstyle = previous_list_item(facial_hairstyle, GLOB.facial_hairstyles_list) + + if("underwear") + var/new_underwear + if(gender == MALE) + new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_m + else if(gender == FEMALE) + new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_f + else + new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_list + if(new_underwear) + underwear = new_underwear + + if("underwear_color") + var/new_underwear_color = input(user, "Choose your character's underwear color:", "Character Preference","#"+underwear_color) as color|null + if(new_underwear_color) + underwear_color = sanitize_hexcolor(new_underwear_color) + + if("undershirt") + var/new_undershirt + if(gender == MALE) + new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_m + else if(gender == FEMALE) + new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_f + else + new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_list + if(new_undershirt) + undershirt = new_undershirt + + if("socks") + var/new_socks + new_socks = input(user, "Choose your character's socks:", "Character Preference") as null|anything in GLOB.socks_list + if(new_socks) + socks = new_socks + + if("eyes") + var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference","#"+eye_color) as color|null + if(new_eyes) + eye_color = sanitize_hexcolor(new_eyes) + + if("species") + + var/result = input(user, "Select a species", "Species Selection") as null|anything in GLOB.roundstart_races + + if(result) + var/newtype = GLOB.species_list[result] + pref_species = new newtype() + //Now that we changed our species, we must verify that the mutant colour is still allowed. + var/temp_hsv = RGBtoHSV(features["mcolor"]) + if(features["mcolor"] == "#000" || (!(MUTCOLORS_PARTSONLY in pref_species.species_traits) && ReadHSV(temp_hsv)[3] < ReadHSV("#7F7F7F")[3])) + features["mcolor"] = pref_species.default_color + if(randomise[RANDOM_NAME]) + real_name = pref_species.random_name(gender) + + if("mutant_color") + var/new_mutantcolor = input(user, "Choose your character's alien/mutant color:", "Character Preference","#"+features["mcolor"]) as color|null + if(new_mutantcolor) + var/temp_hsv = RGBtoHSV(new_mutantcolor) + if(new_mutantcolor == "#000000") + features["mcolor"] = pref_species.default_color + else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright, but only if they affect the skin + features["mcolor"] = sanitize_hexcolor(new_mutantcolor) + else + to_chat(user, "Invalid color. Your color is not bright enough.") + + if("color_ethereal") + var/new_etherealcolor = input(user, "Choose your ethereal color", "Character Preference") as null|anything in GLOB.color_list_ethereal + if(new_etherealcolor) + features["ethcolor"] = GLOB.color_list_ethereal[new_etherealcolor] + + + if("tail_lizard") + var/new_tail + new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_lizard + if(new_tail) + features["tail_lizard"] = new_tail + + if("tail_human") + var/new_tail + new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_human + if(new_tail) + features["tail_human"] = new_tail + + if("snout") + var/new_snout + new_snout = input(user, "Choose your character's snout:", "Character Preference") as null|anything in GLOB.snouts_list + if(new_snout) + features["snout"] = new_snout + + if("horns") + var/new_horns + new_horns = input(user, "Choose your character's horns:", "Character Preference") as null|anything in GLOB.horns_list + if(new_horns) + features["horns"] = new_horns + + if("ears") + var/new_ears + new_ears = input(user, "Choose your character's ears:", "Character Preference") as null|anything in GLOB.ears_list + if(new_ears) + features["ears"] = new_ears + + if("wings") + var/new_wings + new_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.r_wings_list + if(new_wings) + features["wings"] = new_wings + + if("frills") + var/new_frills + new_frills = input(user, "Choose your character's frills:", "Character Preference") as null|anything in GLOB.frills_list + if(new_frills) + features["frills"] = new_frills + + if("spines") + var/new_spines + new_spines = input(user, "Choose your character's spines:", "Character Preference") as null|anything in GLOB.spines_list + if(new_spines) + features["spines"] = new_spines + + if("body_markings") + var/new_body_markings + new_body_markings = input(user, "Choose your character's body markings:", "Character Preference") as null|anything in GLOB.body_markings_list + if(new_body_markings) + features["body_markings"] = new_body_markings + + if("legs") + var/new_legs + new_legs = input(user, "Choose your character's legs:", "Character Preference") as null|anything in GLOB.legs_list + if(new_legs) + features["legs"] = new_legs + + if("moth_wings") + var/new_moth_wings + new_moth_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.moth_wings_list + if(new_moth_wings) + features["moth_wings"] = new_moth_wings + + if("moth_markings") + var/new_moth_markings + new_moth_markings = input(user, "Choose your character's markings:", "Character Preference") as null|anything in GLOB.moth_markings_list + if(new_moth_markings) + features["moth_markings"] = new_moth_markings + + if("s_tone") + var/new_s_tone = input(user, "Choose your character's skin-tone:", "Character Preference") as null|anything in GLOB.skin_tones + if(new_s_tone) + skin_tone = new_s_tone + + if("ooccolor") + var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference",ooccolor) as color|null + if(new_ooccolor) + ooccolor = new_ooccolor + + if("asaycolor") + var/new_asaycolor = input(user, "Choose your ASAY color:", "Game Preference",asaycolor) as color|null + if(new_asaycolor) + asaycolor = new_asaycolor + + if("bag") + var/new_backpack = input(user, "Choose your character's style of bag:", "Character Preference") as null|anything in GLOB.backpacklist + if(new_backpack) + backpack = new_backpack + + if("suit") + if(jumpsuit_style == PREF_SUIT) + jumpsuit_style = PREF_SKIRT + else + jumpsuit_style = PREF_SUIT + + if("uplink_loc") + var/new_loc = input(user, "Choose your character's traitor uplink spawn location:", "Character Preference") as null|anything in GLOB.uplink_spawn_loc_list + if(new_loc) + uplink_spawn_loc = new_loc + + if("playtime_reward_cloak") + if (user.client.get_exp_living(TRUE) >= PLAYTIME_VETERAN) + playtime_reward_cloak = !playtime_reward_cloak + + if("ai_core_icon") + var/ai_core_icon = input(user, "Choose your preferred AI core display screen:", "AI Core Display Screen Selection") as null|anything in GLOB.ai_core_display_screens + if(ai_core_icon) + preferred_ai_core_display = ai_core_icon + + if("sec_dept") + var/department = input(user, "Choose your preferred security department:", "Security Departments") as null|anything in GLOB.security_depts_prefs + if(department) + prefered_security_department = department + + if ("preferred_map") + var/maplist = list() + var/default = "Default" + if (config.defaultmap) + default += " ([config.defaultmap.map_name])" + for (var/M in config.maplist) + var/datum/map_config/VM = config.maplist[M] + if(!VM.votable) + continue + var/friendlyname = "[VM.map_name] " + if (VM.voteweight <= 0) + friendlyname += " (disabled)" + maplist[friendlyname] = VM.map_name + maplist[default] = null + var/pickedmap = input(user, "Choose your preferred map. This will be used to help weight random map selection.", "Character Preference") as null|anything in sortList(maplist) + if (pickedmap) + preferred_map = maplist[pickedmap] + + if ("clientfps") + var/desiredfps = input(user, "Choose your desired fps. (0 = synced with server tick rate (currently:[world.fps]))", "Character Preference", clientfps) as null|num + if (!isnull(desiredfps)) + clientfps = desiredfps + parent.fps = desiredfps + if("ui") + var/pickedui = input(user, "Choose your UI style.", "Character Preference", UI_style) as null|anything in sortList(GLOB.available_ui_styles) + if(pickedui) + UI_style = pickedui + if (parent && parent.mob && parent.mob.hud_used) + parent.mob.hud_used.update_ui_style(ui_style2icon(UI_style)) + if("pda_style") + var/pickedPDAStyle = input(user, "Choose your PDA style.", "Character Preference", pda_style) as null|anything in GLOB.pda_styles + if(pickedPDAStyle) + pda_style = pickedPDAStyle + if("pda_color") + var/pickedPDAColor = input(user, "Choose your PDA Interface color.", "Character Preference", pda_color) as color|null + if(pickedPDAColor) + pda_color = pickedPDAColor + + if("phobia") + var/phobiaType = input(user, "What are you scared of?", "Character Preference", phobia) as null|anything in SStraumas.phobia_types + if(phobiaType) + phobia = phobiaType + + if ("max_chat_length") + var/desiredlength = input(user, "Choose the max character length of shown Runechat messages. Valid range is 1 to [CHAT_MESSAGE_MAX_LENGTH] (default: [initial(max_chat_length)]))", "Character Preference", max_chat_length) as null|num + if (!isnull(desiredlength)) + max_chat_length = clamp(desiredlength, 1, CHAT_MESSAGE_MAX_LENGTH) + + else + switch(href_list["preference"]) + if("publicity") + if(unlock_content) + toggles ^= MEMBER_PUBLIC + if("gender") + var/list/friendlyGenders = list("Male" = "male", "Female" = "female", "Other" = "plural") + var/pickedGender = input(user, "Choose your gender.", "Character Preference", gender) as null|anything in friendlyGenders + if(pickedGender && friendlyGenders[pickedGender] != gender) + gender = friendlyGenders[pickedGender] + underwear = random_underwear(gender) + undershirt = random_undershirt(gender) + socks = random_socks() + facial_hairstyle = random_facial_hairstyle(gender) + hairstyle = random_hairstyle(gender) + if("body_type") + if(body_type == MALE) + body_type = FEMALE + else + body_type = MALE + if("hotkeys") + hotkeys = !hotkeys + if(hotkeys) + winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED]") + else + winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_DISABLED]") + + if("keybindings_capture") + var/datum/keybinding/kb = GLOB.keybindings_by_name[href_list["keybinding"]] + var/old_key = href_list["old_key"] + CaptureKeybinding(user, kb, old_key) + return + + if("keybindings_set") + var/kb_name = href_list["keybinding"] + if(!kb_name) + user << browse(null, "window=capturekeypress") + ShowChoices(user) + return + + var/clear_key = text2num(href_list["clear_key"]) + var/old_key = href_list["old_key"] + if(clear_key) + if(key_bindings[old_key]) + key_bindings[old_key] -= kb_name + if(!length(key_bindings[old_key])) + key_bindings -= old_key + user << browse(null, "window=capturekeypress") + save_preferences() + ShowChoices(user) + return + + var/new_key = uppertext(href_list["key"]) + var/AltMod = text2num(href_list["alt"]) ? "Alt" : "" + var/CtrlMod = text2num(href_list["ctrl"]) ? "Ctrl" : "" + var/ShiftMod = text2num(href_list["shift"]) ? "Shift" : "" + var/numpad = text2num(href_list["numpad"]) ? "Numpad" : "" + // var/key_code = text2num(href_list["key_code"]) + + if(GLOB._kbMap[new_key]) + new_key = GLOB._kbMap[new_key] + + var/full_key + switch(new_key) + if("Alt") + full_key = "[new_key][CtrlMod][ShiftMod]" + if("Ctrl") + full_key = "[AltMod][new_key][ShiftMod]" + if("Shift") + full_key = "[AltMod][CtrlMod][new_key]" + else + full_key = "[AltMod][CtrlMod][ShiftMod][numpad][new_key]" + if(key_bindings[old_key]) + key_bindings[old_key] -= kb_name + if(!length(key_bindings[old_key])) + key_bindings -= old_key + key_bindings[full_key] += list(kb_name) + key_bindings[full_key] = sortList(key_bindings[full_key]) + + user << browse(null, "window=capturekeypress") + user.client.update_movement_keys() + save_preferences() + + if("keybindings_reset") + var/choice = tgalert(user, "Would you prefer 'hotkey' or 'classic' defaults?", "Setup keybindings", "Hotkey", "Classic", "Cancel") + if(choice == "Cancel") + ShowChoices(user) + return + hotkeys = (choice == "Hotkey") + key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key) + user.client.update_movement_keys() + + if("chat_on_map") + chat_on_map = !chat_on_map + if("see_chat_non_mob") + see_chat_non_mob = !see_chat_non_mob + + if("action_buttons") + buttons_locked = !buttons_locked + if("tgui_fancy") + tgui_fancy = !tgui_fancy + if("tgui_lock") + tgui_lock = !tgui_lock + if("winflash") + windowflashing = !windowflashing + + //here lies the badmins + if("hear_adminhelps") + user.client.toggleadminhelpsound() + if("hear_prayers") + user.client.toggle_prayer_sound() + if("announce_login") + user.client.toggleannouncelogin() + if("combohud_lighting") + toggles ^= COMBOHUD_LIGHTING + if("toggle_dead_chat") + user.client.deadchat() + if("toggle_radio_chatter") + user.client.toggle_hear_radio() + if("toggle_prayers") + user.client.toggleprayers() + if("toggle_deadmin_always") + toggles ^= DEADMIN_ALWAYS + if("toggle_deadmin_antag") + toggles ^= DEADMIN_ANTAGONIST + if("toggle_deadmin_head") + toggles ^= DEADMIN_POSITION_HEAD + if("toggle_deadmin_security") + toggles ^= DEADMIN_POSITION_SECURITY + if("toggle_deadmin_silicon") + toggles ^= DEADMIN_POSITION_SILICON + if("toggle_ignore_cult_ghost") + toggles ^= ADMIN_IGNORE_CULT_GHOST + + + if("be_special") + var/be_special_type = href_list["be_special_type"] + if(be_special_type in be_special) + be_special -= be_special_type + else + be_special += be_special_type + + if("toggle_random") + var/random_type = href_list["random_type"] + if(randomise[random_type]) + randomise -= random_type + else + randomise[random_type] = TRUE + + if("persistent_scars") + persistent_scars = !persistent_scars + + if("clear_scars") + to_chat(user, "All scar slots cleared. Please save character to confirm.") + scars_list["1"] = "" + scars_list["2"] = "" + scars_list["3"] = "" + scars_list["4"] = "" + scars_list["5"] = "" + + if("hear_midis") + toggles ^= SOUND_MIDI + + if("lobby_music") + toggles ^= SOUND_LOBBY + if((toggles & SOUND_LOBBY) && user.client && isnewplayer(user)) + user.client.playtitlemusic() + else + user.stop_sound_channel(CHANNEL_LOBBYMUSIC) + + if("endofround_sounds") + toggles ^= SOUND_ENDOFROUND + + if("ghost_ears") + chat_toggles ^= CHAT_GHOSTEARS + + if("ghost_sight") + chat_toggles ^= CHAT_GHOSTSIGHT + + if("ghost_whispers") + chat_toggles ^= CHAT_GHOSTWHISPER + + if("ghost_radio") + chat_toggles ^= CHAT_GHOSTRADIO + + if("ghost_pda") + chat_toggles ^= CHAT_GHOSTPDA + + if("ghost_laws") + chat_toggles ^= CHAT_GHOSTLAWS + + if("income_pings") + chat_toggles ^= CHAT_BANKCARD + + if("pull_requests") + chat_toggles ^= CHAT_PULLR + + if("allow_midround_antag") + toggles ^= MIDROUND_ANTAG + + if("parallaxup") + parallax = WRAP(parallax + 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) + if (parent && parent.mob && parent.mob.hud_used) + parent.mob.hud_used.update_parallax_pref(parent.mob) + + if("parallaxdown") + parallax = WRAP(parallax - 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) + if (parent && parent.mob && parent.mob.hud_used) + parent.mob.hud_used.update_parallax_pref(parent.mob) + + if("ambientocclusion") + ambientocclusion = !ambientocclusion + if(parent && parent.screen && parent.screen.len) + var/obj/screen/plane_master/game_world/PM = locate(/obj/screen/plane_master/game_world) in parent.screen + PM.backdrop(parent.mob) + + if("auto_fit_viewport") + auto_fit_viewport = !auto_fit_viewport + if(auto_fit_viewport && parent) + parent.fit_viewport() + + if("widescreenpref") + widescreenpref = !widescreenpref + user.client.view_size.setDefault(getScreenSize(widescreenpref)) + + if("pixel_size") + switch(pixel_size) + if(PIXEL_SCALING_AUTO) + pixel_size = PIXEL_SCALING_1X + if(PIXEL_SCALING_1X) + pixel_size = PIXEL_SCALING_1_2X + if(PIXEL_SCALING_1_2X) + pixel_size = PIXEL_SCALING_2X + if(PIXEL_SCALING_2X) + pixel_size = PIXEL_SCALING_3X + if(PIXEL_SCALING_3X) + pixel_size = PIXEL_SCALING_AUTO + user.client.view_size.apply() //Let's winset() it so it actually works + + if("scaling_method") + switch(scaling_method) + if(SCALING_METHOD_NORMAL) + scaling_method = SCALING_METHOD_DISTORT + if(SCALING_METHOD_DISTORT) + scaling_method = SCALING_METHOD_BLUR + if(SCALING_METHOD_BLUR) + scaling_method = SCALING_METHOD_NORMAL + user.client.view_size.setZoomMode() + + if("save") + save_preferences() + save_character() + + if("load") + load_preferences() + load_character() + + if("changeslot") + if(!load_character(text2num(href_list["num"]))) + random_character() + real_name = random_unique_name(gender) + save_character() + + if("tab") + if (href_list["tab"]) + current_tab = text2num(href_list["tab"]) + + if("clear_heart") + hearted = FALSE + hearted_until = null + to_chat(user, "OOC Commendation Heart disabled") + save_preferences() + + ShowChoices(user) + return 1 + +/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = 1, roundstart_checks = TRUE, character_setup = FALSE, antagonist = FALSE, is_latejoiner = TRUE) + + hardcore_survival_score = 0 //Set to 0 to prevent you getting points from last another time. + + if((randomise[RANDOM_SPECIES] || randomise[RANDOM_HARDCORE]) && !character_setup) + + random_species() + + if((randomise[RANDOM_BODY] || (randomise[RANDOM_BODY_ANTAG] && antagonist) || randomise[RANDOM_HARDCORE]) && !character_setup) + slot_randomized = TRUE + random_character(gender, antagonist) + + if((randomise[RANDOM_NAME] || (randomise[RANDOM_NAME_ANTAG] && antagonist) || randomise[RANDOM_HARDCORE]) && !character_setup) + slot_randomized = TRUE + real_name = pref_species.random_name(gender) + + if(randomise[RANDOM_HARDCORE] && parent.mob.mind && !character_setup) + if(can_be_random_hardcore()) + hardcore_random_setup(character, antagonist, is_latejoiner) + + if(roundstart_checks) + if(CONFIG_GET(flag/humans_need_surnames) && (pref_species.id == "human")) + var/firstspace = findtext(real_name, " ") + var/name_length = length(real_name) + if(!firstspace) //we need a surname + real_name += " [pick(GLOB.last_names)]" + else if(firstspace == name_length) + real_name += "[pick(GLOB.last_names)]" + + character.real_name = real_name + character.name = character.real_name + + character.gender = gender + character.age = age + if(gender == MALE || gender == FEMALE) + character.body_type = gender + else + character.body_type = body_type + + character.eye_color = eye_color + var/obj/item/organ/eyes/organ_eyes = character.getorgan(/obj/item/organ/eyes) + if(organ_eyes) + if(!initial(organ_eyes.eye_color)) + organ_eyes.eye_color = eye_color + organ_eyes.old_eye_color = eye_color + character.hair_color = hair_color + character.facial_hair_color = facial_hair_color + + character.skin_tone = skin_tone + character.hairstyle = hairstyle + character.facial_hairstyle = facial_hairstyle + character.underwear = underwear + character.underwear_color = underwear_color + character.undershirt = undershirt + character.socks = socks + + character.backpack = backpack + + character.jumpsuit_style = jumpsuit_style + + var/datum/species/chosen_species + chosen_species = pref_species.type + if(roundstart_checks && !(pref_species.id in GLOB.roundstart_races) && !(pref_species.id in (CONFIG_GET(keyed_list/roundstart_no_hard_check)))) + chosen_species = /datum/species/human + pref_species = new /datum/species/human + save_character() + + character.dna.features = features.Copy() + character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE) + character.dna.real_name = character.real_name + + if("tail_lizard" in pref_species.default_features) + character.dna.species.mutant_bodyparts |= "tail_lizard" + + if(icon_updates) + character.update_body() + character.update_hair() + character.update_body_parts() + +/datum/preferences/proc/can_be_random_hardcore() + if(parent.mob.mind.assigned_role in GLOB.command_positions) //No command staff + return FALSE + for(var/A in parent.mob.mind.antag_datums) + var/datum/antagonist/antag + if(antag.get_team()) //No team antags + return FALSE + return TRUE + +/datum/preferences/proc/get_default_name(name_id) + switch(name_id) + if("human") + return random_unique_name() + if("ai") + return pick(GLOB.ai_names) + if("cyborg") + return DEFAULT_CYBORG_NAME + if("clown") + return pick(GLOB.clown_names) + if("mime") + return pick(GLOB.mime_names) + if("religion") + return DEFAULT_RELIGION + if("deity") + return DEFAULT_DEITY + return random_unique_name() + +/datum/preferences/proc/ask_for_custom_name(mob/user,name_id) + var/namedata = GLOB.preferences_custom_names[name_id] + if(!namedata) + return + + var/raw_name = input(user, "Choose your character's [namedata["qdesc"]]:","Character Preference") as text|null + if(!raw_name) + if(namedata["allow_null"]) + custom_names[name_id] = get_default_name(name_id) + else + return + else + var/sanitized_name = reject_bad_name(raw_name,namedata["allow_numbers"]) + if(!sanitized_name) + to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z,[namedata["allow_numbers"] ? ",0-9," : ""] -, ' and .") + return + else + custom_names[name_id] = sanitized_name + +//Used in savefile update 32, can be removed once that is no longer relevant. +/datum/preferences/proc/force_reset_keybindings() + var/choice = tgalert(parent.mob, "Your basic keybindings need to be reset, emotes will remain as before. Would you prefer 'hotkey' or 'classic' mode?", "Reset keybindings", "Hotkey", "Classic") + hotkeys = (choice != "Classic") + var/list/oldkeys = key_bindings + key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key) + + for(var/key in oldkeys) + if(!key_bindings[key]) + key_bindings[key] = oldkeys[key] + parent.update_movement_keys() diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm index 135d3a28952..d766adb394e 100644 --- a/code/modules/client/preferences_toggles.dm +++ b/code/modules/client/preferences_toggles.dm @@ -1,482 +1,482 @@ -//this works as is to create a single checked item, but has no back end code for toggleing the check yet -#define TOGGLE_CHECKBOX(PARENT, CHILD) PARENT/CHILD/abstract = TRUE;PARENT/CHILD/checkbox = CHECKBOX_TOGGLE;PARENT/CHILD/verb/CHILD - -//Example usage TOGGLE_CHECKBOX(datum/verbs/menu/settings/Ghost/chatterbox, toggle_ghost_ears)() - -//override because we don't want to save preferences twice. -/datum/verbs/menu/settings/Set_checked(client/C, verbpath) - if (checkbox == CHECKBOX_GROUP) - C.prefs.menuoptions[type] = verbpath - else if (checkbox == CHECKBOX_TOGGLE) - var/checked = Get_checked(C) - C.prefs.menuoptions[type] = !checked - winset(C, "[verbpath]", "is-checked = [!checked]") - -/datum/verbs/menu/settings/verb/setup_character() - set name = "Game Preferences" - set category = "Preferences" - set desc = "Open Game Preferences Window" - usr.client.prefs.current_tab = 1 - usr.client.prefs.ShowChoices(usr) - -//toggles -/datum/verbs/menu/settings/ghost/chatterbox - name = "Chat Box Spam" - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_ears)() - set name = "Show/Hide GhostEars" - set category = "Preferences" - set desc = "See All Speech" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTEARS - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Ears", "[usr.client.prefs.chat_toggles & CHAT_GHOSTEARS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_ears/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTEARS - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_sight)() - set name = "Show/Hide GhostSight" - set category = "Preferences" - set desc = "See All Emotes" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTSIGHT - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Sight", "[usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_sight/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTSIGHT - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_whispers)() - set name = "Show/Hide GhostWhispers" - set category = "Preferences" - set desc = "See All Whispers" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTWHISPER - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Whispers", "[usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_whispers/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTWHISPER - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_radio)() - set name = "Show/Hide GhostRadio" - set category = "Preferences" - set desc = "See All Radio Chatter" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTRADIO - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Radio", "[usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1 -/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_radio/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTRADIO - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_pda)() - set name = "Show/Hide GhostPDA" - set category = "Preferences" - set desc = "See All PDA Messages" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTPDA - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost PDA", "[usr.client.prefs.chat_toggles & CHAT_GHOSTPDA ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_pda/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTPDA - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_laws)() - set name = "Show/Hide GhostLaws" - set category = "Preferences" - set desc = "See All Law Changes" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTLAWS - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS) ? "be notified of all law changes" : "no longer be notified of law changes"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Laws", "[usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_laws/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTLAWS - -/datum/verbs/menu/settings/ghost/chatterbox/events - name = "Events" - -//please be aware that the following two verbs have inverted stat output, so that "Toggle Deathrattle|1" still means you activated it -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox/events, toggle_deathrattle)() - set name = "Toggle Deathrattle" - set category = "Preferences" - set desc = "Death" - usr.client.prefs.toggles ^= DISABLE_DEATHRATTLE - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get messages when a sentient mob dies.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Deathrattle", "[!(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should spend some time reading the comments. -/datum/verbs/menu/settings/ghost/chatterbox/events/toggle_deathrattle/Get_checked(client/C) - return !(C.prefs.toggles & DISABLE_DEATHRATTLE) - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox/events, toggle_arrivalrattle)() - set name = "Toggle Arrivalrattle" - set category = "Preferences" - set desc = "New Player Arrival" - usr.client.prefs.toggles ^= DISABLE_ARRIVALRATTLE - to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get messages when someone joins the station.") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Arrivalrattle", "[!(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should rethink where your life went so wrong. -/datum/verbs/menu/settings/ghost/chatterbox/events/toggle_arrivalrattle/Get_checked(client/C) - return !(C.prefs.toggles & DISABLE_ARRIVALRATTLE) - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost, togglemidroundantag)() - set name = "Toggle Midround Antagonist" - set category = "Preferences" - set desc = "Midround Antagonist" - usr.client.prefs.toggles ^= MIDROUND_ANTAG - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Midround Antag", "[usr.client.prefs.toggles & MIDROUND_ANTAG ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/ghost/togglemidroundantag/Get_checked(client/C) - return C.prefs.toggles & MIDROUND_ANTAG - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggletitlemusic)() - set name = "Hear/Silence Lobby Music" - set category = "Preferences" - set desc = "Hear Music In Lobby" - usr.client.prefs.toggles ^= SOUND_LOBBY - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_LOBBY) - to_chat(usr, "You will now hear music in the game lobby.") - if(isnewplayer(usr)) - usr.client.playtitlemusic() - else - to_chat(usr, "You will no longer hear music in the game lobby.") - usr.stop_sound_channel(CHANNEL_LOBBYMUSIC) - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Lobby Music", "[usr.client.prefs.toggles & SOUND_LOBBY ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/sound/toggletitlemusic/Get_checked(client/C) - return C.prefs.toggles & SOUND_LOBBY - - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggleendofroundsounds)() - set name = "Hear/Silence End of Round Sounds" - set category = "Preferences" - set desc = "Hear Round End Sounds" - usr.client.prefs.toggles ^= SOUND_ENDOFROUND - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_ENDOFROUND) - to_chat(usr, "You will now hear sounds played before the server restarts.") - else - to_chat(usr, "You will no longer hear sounds played before the server restarts.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle End of Round Sounds", "[usr.client.prefs.toggles & SOUND_ENDOFROUND ? "Enabled" : "Disabled"]")) -/datum/verbs/menu/settings/sound/toggleendofroundsounds/Get_checked(client/C) - return C.prefs.toggles & SOUND_ENDOFROUND - - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, togglemidis)() - set name = "Hear/Silence Midis" - set category = "Preferences" - set desc = "Hear Admin Triggered Sounds (Midis)" - usr.client.prefs.toggles ^= SOUND_MIDI - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_MIDI) - to_chat(usr, "You will now hear any sounds uploaded by admins.") - else - to_chat(usr, "You will no longer hear sounds uploaded by admins") - usr.stop_sound_channel(CHANNEL_ADMIN) - var/client/C = usr.client - if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - C.chatOutput.stopMusic() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Midis", "[usr.client.prefs.toggles & SOUND_MIDI ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/sound/togglemidis/Get_checked(client/C) - return C.prefs.toggles & SOUND_MIDI - - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_instruments)() - set name = "Hear/Silence Instruments" - set category = "Preferences" - set desc = "Hear In-game Instruments" - usr.client.prefs.toggles ^= SOUND_INSTRUMENTS - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_INSTRUMENTS) - to_chat(usr, "You will now hear people playing musical instruments.") - else - to_chat(usr, "You will no longer hear musical instruments.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Instruments", "[usr.client.prefs.toggles & SOUND_INSTRUMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/sound/toggle_instruments/Get_checked(client/C) - return C.prefs.toggles & SOUND_INSTRUMENTS - - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, Toggle_Soundscape)() - set name = "Hear/Silence Ambience" - set category = "Preferences" - set desc = "Hear Ambient Sound Effects" - usr.client.prefs.toggles ^= SOUND_AMBIENCE - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_AMBIENCE) - to_chat(usr, "You will now hear ambient sounds.") - else - to_chat(usr, "You will no longer hear ambient sounds.") - usr.stop_sound_channel(CHANNEL_AMBIENCE) - usr.stop_sound_channel(CHANNEL_BUZZ) - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ambience", "[usr.client.prefs.toggles & SOUND_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/sound/Toggle_Soundscape/Get_checked(client/C) - return C.prefs.toggles & SOUND_AMBIENCE - - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_ship_ambience)() - set name = "Hear/Silence Ship Ambience" - set category = "Preferences" - set desc = "Hear Ship Ambience Roar" - usr.client.prefs.toggles ^= SOUND_SHIP_AMBIENCE - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE) - to_chat(usr, "You will now hear ship ambience.") - else - to_chat(usr, "You will no longer hear ship ambience.") - usr.stop_sound_channel(CHANNEL_BUZZ) - usr.client.ambience_playing = 0 - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ship Ambience", "[usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^) -/datum/verbs/menu/settings/sound/toggle_ship_ambience/Get_checked(client/C) - return C.prefs.toggles & SOUND_SHIP_AMBIENCE - - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_announcement_sound)() - set name = "Hear/Silence Announcements" - set category = "Preferences" - set desc = "Hear Announcement Sound" - usr.client.prefs.toggles ^= SOUND_ANNOUNCEMENTS - to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Announcement Sound", "[usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/sound/toggle_announcement_sound/Get_checked(client/C) - return C.prefs.toggles & SOUND_ANNOUNCEMENTS - - -/datum/verbs/menu/settings/sound/verb/stop_client_sounds() - set name = "Stop Sounds" - set category = "Preferences" - set desc = "Stop Current Sounds" - SEND_SOUND(usr, sound(null)) - var/client/C = usr.client - if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - C.chatOutput.stopMusic() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings, listen_ooc)() - set name = "Show/Hide OOC" - set category = "Preferences" - set desc = "Show OOC Chat" - usr.client.prefs.chat_toggles ^= CHAT_OOC - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/settings/listen_ooc/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_OOC - -TOGGLE_CHECKBOX(/datum/verbs/menu/settings, listen_bank_card)() - set name = "Show/Hide Income Updates" - set category = "Preferences" - set desc = "Show or hide updates to your income" - usr.client.prefs.chat_toggles ^= CHAT_BANKCARD - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "now" : "no longer"] be notified when you get paid.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Income Notifications", "[(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "Enabled" : "Disabled"]")) -/datum/verbs/menu/settings/listen_bank_card/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_BANKCARD - - -GLOBAL_LIST_INIT(ghost_forms, sortList(list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \ - "ghost_blue","ghost_yellow","ghost_green","ghost_pink", \ - "ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \ - "ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\ - "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost"))) -/client/proc/pick_form() - if(!is_content_unlocked()) - alert("This setting is for accounts with BYOND premium only.") - return - var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms - if(new_form) - prefs.ghost_form = new_form - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_icon(new_form) - -GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON)) - -/client/proc/pick_ghost_orbit() - if(!is_content_unlocked()) - alert("This setting is for accounts with BYOND premium only.") - return - var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits - if(new_orbit) - prefs.ghost_orbit = new_orbit - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.ghost_orbit = new_orbit - -/client/proc/pick_ghost_accs() - var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites") - if(new_ghost_accs) - switch(new_ghost_accs) - if("full accessories") - prefs.ghost_accs = GHOST_ACCS_FULL - if("only directional sprites") - prefs.ghost_accs = GHOST_ACCS_DIR - if("default sprites") - prefs.ghost_accs = GHOST_ACCS_NONE - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_icon() - -/client/verb/pick_ghost_customization() - set name = "Ghost Customization" - set category = "Preferences" - set desc = "Customize your ghastly appearance." - if(is_content_unlocked()) - switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories")) - if("Ghost Form") - pick_form() - if("Ghost Orbit") - pick_ghost_orbit() - if("Ghost Accessories") - pick_ghost_accs() - else - pick_ghost_accs() - -/client/verb/pick_ghost_others() - set name = "Ghosts of Others" - set category = "Preferences" - set desc = "Change display settings for the ghosts of other players." - var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost") - if(new_ghost_others) - switch(new_ghost_others) - if("Their Setting") - prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING - if("Default Sprites") - prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE - if("White Ghost") - prefs.ghost_others = GHOST_OTHERS_SIMPLE - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_sight() - -/client/verb/toggle_intent_style() - set name = "Toggle Intent Selection Style" - set category = "Preferences" - set desc = "Toggle between directly clicking the desired intent or clicking to rotate through." - prefs.toggles ^= INTENT_STYLE - to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]") - prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ghost_hud_pref() - set name = "Toggle Ghost HUD" - set category = "Preferences" - set desc = "Hide/Show Ghost HUD" - - prefs.ghost_hud = !prefs.ghost_hud - to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].") - prefs.save_preferences() - if(isobserver(mob)) - mob.hud_used.show_hud() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]")) - -/client/verb/toggle_inquisition() // warning: unexpected inquisition - set name = "Toggle Inquisitiveness" - set desc = "Sets whether your ghost examines everything on click by default" - set category = "Preferences" - - prefs.inquisitive_ghost = !prefs.inquisitive_ghost - prefs.save_preferences() - if(prefs.inquisitive_ghost) - to_chat(src, "You will now examine everything you click on.") - else - to_chat(src, "You will no longer examine things you click on.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]")) - -//Admin Preferences -/client/proc/toggleadminhelpsound() - set name = "Hear/Silence Adminhelps" - set category = "Prefs - Admin" - set desc = "Toggle hearing a notification when admin PMs are received" - if(!holder) - return - prefs.toggles ^= SOUND_ADMINHELP - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggleannouncelogin() - set name = "Do/Don't Announce Login" - set category = "Prefs - Admin" - set desc = "Toggle if you want an announcement to admins when you login during a round" - if(!holder) - return - prefs.toggles ^= ANNOUNCE_LOGIN - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_hear_radio() - set name = "Show/Hide Radio Chatter" - set category = "Prefs - Admin" - set desc = "Toggle seeing radiochatter from nearby radios and speakers" - if(!holder) - return - prefs.chat_toggles ^= CHAT_RADIO - prefs.save_preferences() - to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/deadchat() - set name = "Show/Hide Deadchat" - set category = "Prefs - Admin" - set desc ="Toggles seeing deadchat" - if(!holder) - return - prefs.chat_toggles ^= CHAT_DEAD - prefs.save_preferences() - to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggleprayers() - set name = "Show/Hide Prayers" - set category = "Prefs - Admin" - set desc = "Toggles seeing prayers" - if(!holder) - return - prefs.chat_toggles ^= CHAT_PRAYER - prefs.save_preferences() - to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_prayer_sound() - set name = "Hear/Silence Prayer Sounds" - set category = "Prefs - Admin" - set desc = "Hear Prayer Sounds" - if(!holder) - return - prefs.toggles ^= SOUND_PRAYERS - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & SOUND_PRAYERS) ? "now" : "no longer"] hear a sound when prayers arrive.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) - -/client/proc/colorasay() - set name = "Set Admin Say Color" - set category = "Prefs - Admin" - set desc = "Set the color of your ASAY messages" - if(!holder) - return - if(!CONFIG_GET(flag/allow_admin_asaycolor)) - to_chat(src, "Custom Asay color is currently disabled by the server.") - return - var/new_asaycolor = input(src, "Please select your ASAY color.", "ASAY color", prefs.asaycolor) as color|null - if(new_asaycolor) - prefs.asaycolor = sanitize_ooccolor(new_asaycolor) - prefs.save_preferences() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set ASAY Color") - return - -/client/proc/resetasaycolor() - set name = "Reset your Admin Say Color" - set desc = "Returns your ASAY Color to default" - set category = "Prefs - Admin" - if(!holder) - return - if(!CONFIG_GET(flag/allow_admin_asaycolor)) - to_chat(src, "Custom Asay color is currently disabled by the server.") - return - prefs.asaycolor = initial(prefs.asaycolor) - prefs.save_preferences() +//this works as is to create a single checked item, but has no back end code for toggleing the check yet +#define TOGGLE_CHECKBOX(PARENT, CHILD) PARENT/CHILD/abstract = TRUE;PARENT/CHILD/checkbox = CHECKBOX_TOGGLE;PARENT/CHILD/verb/CHILD + +//Example usage TOGGLE_CHECKBOX(datum/verbs/menu/settings/Ghost/chatterbox, toggle_ghost_ears)() + +//override because we don't want to save preferences twice. +/datum/verbs/menu/settings/Set_checked(client/C, verbpath) + if (checkbox == CHECKBOX_GROUP) + C.prefs.menuoptions[type] = verbpath + else if (checkbox == CHECKBOX_TOGGLE) + var/checked = Get_checked(C) + C.prefs.menuoptions[type] = !checked + winset(C, "[verbpath]", "is-checked = [!checked]") + +/datum/verbs/menu/settings/verb/setup_character() + set name = "Game Preferences" + set category = "Preferences" + set desc = "Open Game Preferences Window" + usr.client.prefs.current_tab = 1 + usr.client.prefs.ShowChoices(usr) + +//toggles +/datum/verbs/menu/settings/ghost/chatterbox + name = "Chat Box Spam" + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_ears)() + set name = "Show/Hide GhostEars" + set category = "Preferences" + set desc = "See All Speech" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTEARS + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Ears", "[usr.client.prefs.chat_toggles & CHAT_GHOSTEARS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_ears/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTEARS + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_sight)() + set name = "Show/Hide GhostSight" + set category = "Preferences" + set desc = "See All Emotes" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTSIGHT + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Sight", "[usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_sight/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTSIGHT + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_whispers)() + set name = "Show/Hide GhostWhispers" + set category = "Preferences" + set desc = "See All Whispers" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTWHISPER + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Whispers", "[usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_whispers/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTWHISPER + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_radio)() + set name = "Show/Hide GhostRadio" + set category = "Preferences" + set desc = "See All Radio Chatter" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTRADIO + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Radio", "[usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1 +/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_radio/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTRADIO + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_pda)() + set name = "Show/Hide GhostPDA" + set category = "Preferences" + set desc = "See All PDA Messages" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTPDA + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost PDA", "[usr.client.prefs.chat_toggles & CHAT_GHOSTPDA ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_pda/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTPDA + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox, toggle_ghost_laws)() + set name = "Show/Hide GhostLaws" + set category = "Preferences" + set desc = "See All Law Changes" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTLAWS + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS) ? "be notified of all law changes" : "no longer be notified of law changes"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Laws", "[usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/ghost/chatterbox/toggle_ghost_laws/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTLAWS + +/datum/verbs/menu/settings/ghost/chatterbox/events + name = "Events" + +//please be aware that the following two verbs have inverted stat output, so that "Toggle Deathrattle|1" still means you activated it +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox/events, toggle_deathrattle)() + set name = "Toggle Deathrattle" + set category = "Preferences" + set desc = "Death" + usr.client.prefs.toggles ^= DISABLE_DEATHRATTLE + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get messages when a sentient mob dies.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Deathrattle", "[!(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should spend some time reading the comments. +/datum/verbs/menu/settings/ghost/chatterbox/events/toggle_deathrattle/Get_checked(client/C) + return !(C.prefs.toggles & DISABLE_DEATHRATTLE) + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost/chatterbox/events, toggle_arrivalrattle)() + set name = "Toggle Arrivalrattle" + set category = "Preferences" + set desc = "New Player Arrival" + usr.client.prefs.toggles ^= DISABLE_ARRIVALRATTLE + to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get messages when someone joins the station.") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Arrivalrattle", "[!(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should rethink where your life went so wrong. +/datum/verbs/menu/settings/ghost/chatterbox/events/toggle_arrivalrattle/Get_checked(client/C) + return !(C.prefs.toggles & DISABLE_ARRIVALRATTLE) + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/ghost, togglemidroundantag)() + set name = "Toggle Midround Antagonist" + set category = "Preferences" + set desc = "Midround Antagonist" + usr.client.prefs.toggles ^= MIDROUND_ANTAG + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Midround Antag", "[usr.client.prefs.toggles & MIDROUND_ANTAG ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/ghost/togglemidroundantag/Get_checked(client/C) + return C.prefs.toggles & MIDROUND_ANTAG + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggletitlemusic)() + set name = "Hear/Silence Lobby Music" + set category = "Preferences" + set desc = "Hear Music In Lobby" + usr.client.prefs.toggles ^= SOUND_LOBBY + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_LOBBY) + to_chat(usr, "You will now hear music in the game lobby.") + if(isnewplayer(usr)) + usr.client.playtitlemusic() + else + to_chat(usr, "You will no longer hear music in the game lobby.") + usr.stop_sound_channel(CHANNEL_LOBBYMUSIC) + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Lobby Music", "[usr.client.prefs.toggles & SOUND_LOBBY ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/sound/toggletitlemusic/Get_checked(client/C) + return C.prefs.toggles & SOUND_LOBBY + + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggleendofroundsounds)() + set name = "Hear/Silence End of Round Sounds" + set category = "Preferences" + set desc = "Hear Round End Sounds" + usr.client.prefs.toggles ^= SOUND_ENDOFROUND + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_ENDOFROUND) + to_chat(usr, "You will now hear sounds played before the server restarts.") + else + to_chat(usr, "You will no longer hear sounds played before the server restarts.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle End of Round Sounds", "[usr.client.prefs.toggles & SOUND_ENDOFROUND ? "Enabled" : "Disabled"]")) +/datum/verbs/menu/settings/sound/toggleendofroundsounds/Get_checked(client/C) + return C.prefs.toggles & SOUND_ENDOFROUND + + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, togglemidis)() + set name = "Hear/Silence Midis" + set category = "Preferences" + set desc = "Hear Admin Triggered Sounds (Midis)" + usr.client.prefs.toggles ^= SOUND_MIDI + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_MIDI) + to_chat(usr, "You will now hear any sounds uploaded by admins.") + else + to_chat(usr, "You will no longer hear sounds uploaded by admins") + usr.stop_sound_channel(CHANNEL_ADMIN) + var/client/C = usr.client + if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.stopMusic() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Midis", "[usr.client.prefs.toggles & SOUND_MIDI ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/sound/togglemidis/Get_checked(client/C) + return C.prefs.toggles & SOUND_MIDI + + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_instruments)() + set name = "Hear/Silence Instruments" + set category = "Preferences" + set desc = "Hear In-game Instruments" + usr.client.prefs.toggles ^= SOUND_INSTRUMENTS + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_INSTRUMENTS) + to_chat(usr, "You will now hear people playing musical instruments.") + else + to_chat(usr, "You will no longer hear musical instruments.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Instruments", "[usr.client.prefs.toggles & SOUND_INSTRUMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/sound/toggle_instruments/Get_checked(client/C) + return C.prefs.toggles & SOUND_INSTRUMENTS + + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, Toggle_Soundscape)() + set name = "Hear/Silence Ambience" + set category = "Preferences" + set desc = "Hear Ambient Sound Effects" + usr.client.prefs.toggles ^= SOUND_AMBIENCE + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_AMBIENCE) + to_chat(usr, "You will now hear ambient sounds.") + else + to_chat(usr, "You will no longer hear ambient sounds.") + usr.stop_sound_channel(CHANNEL_AMBIENCE) + usr.stop_sound_channel(CHANNEL_BUZZ) + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ambience", "[usr.client.prefs.toggles & SOUND_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/sound/Toggle_Soundscape/Get_checked(client/C) + return C.prefs.toggles & SOUND_AMBIENCE + + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_ship_ambience)() + set name = "Hear/Silence Ship Ambience" + set category = "Preferences" + set desc = "Hear Ship Ambience Roar" + usr.client.prefs.toggles ^= SOUND_SHIP_AMBIENCE + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE) + to_chat(usr, "You will now hear ship ambience.") + else + to_chat(usr, "You will no longer hear ship ambience.") + usr.stop_sound_channel(CHANNEL_BUZZ) + usr.client.ambience_playing = 0 + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ship Ambience", "[usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^) +/datum/verbs/menu/settings/sound/toggle_ship_ambience/Get_checked(client/C) + return C.prefs.toggles & SOUND_SHIP_AMBIENCE + + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings/sound, toggle_announcement_sound)() + set name = "Hear/Silence Announcements" + set category = "Preferences" + set desc = "Hear Announcement Sound" + usr.client.prefs.toggles ^= SOUND_ANNOUNCEMENTS + to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Announcement Sound", "[usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/sound/toggle_announcement_sound/Get_checked(client/C) + return C.prefs.toggles & SOUND_ANNOUNCEMENTS + + +/datum/verbs/menu/settings/sound/verb/stop_client_sounds() + set name = "Stop Sounds" + set category = "Preferences" + set desc = "Stop Current Sounds" + SEND_SOUND(usr, sound(null)) + var/client/C = usr.client + if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.stopMusic() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings, listen_ooc)() + set name = "Show/Hide OOC" + set category = "Preferences" + set desc = "Show OOC Chat" + usr.client.prefs.chat_toggles ^= CHAT_OOC + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/settings/listen_ooc/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_OOC + +TOGGLE_CHECKBOX(/datum/verbs/menu/settings, listen_bank_card)() + set name = "Show/Hide Income Updates" + set category = "Preferences" + set desc = "Show or hide updates to your income" + usr.client.prefs.chat_toggles ^= CHAT_BANKCARD + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "now" : "no longer"] be notified when you get paid.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Income Notifications", "[(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "Enabled" : "Disabled"]")) +/datum/verbs/menu/settings/listen_bank_card/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_BANKCARD + + +GLOBAL_LIST_INIT(ghost_forms, sortList(list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \ + "ghost_blue","ghost_yellow","ghost_green","ghost_pink", \ + "ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \ + "ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\ + "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost"))) +/client/proc/pick_form() + if(!is_content_unlocked()) + alert("This setting is for accounts with BYOND premium only.") + return + var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms + if(new_form) + prefs.ghost_form = new_form + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_icon(new_form) + +GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON)) + +/client/proc/pick_ghost_orbit() + if(!is_content_unlocked()) + alert("This setting is for accounts with BYOND premium only.") + return + var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits + if(new_orbit) + prefs.ghost_orbit = new_orbit + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.ghost_orbit = new_orbit + +/client/proc/pick_ghost_accs() + var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites") + if(new_ghost_accs) + switch(new_ghost_accs) + if("full accessories") + prefs.ghost_accs = GHOST_ACCS_FULL + if("only directional sprites") + prefs.ghost_accs = GHOST_ACCS_DIR + if("default sprites") + prefs.ghost_accs = GHOST_ACCS_NONE + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_icon() + +/client/verb/pick_ghost_customization() + set name = "Ghost Customization" + set category = "Preferences" + set desc = "Customize your ghastly appearance." + if(is_content_unlocked()) + switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories")) + if("Ghost Form") + pick_form() + if("Ghost Orbit") + pick_ghost_orbit() + if("Ghost Accessories") + pick_ghost_accs() + else + pick_ghost_accs() + +/client/verb/pick_ghost_others() + set name = "Ghosts of Others" + set category = "Preferences" + set desc = "Change display settings for the ghosts of other players." + var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost") + if(new_ghost_others) + switch(new_ghost_others) + if("Their Setting") + prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING + if("Default Sprites") + prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE + if("White Ghost") + prefs.ghost_others = GHOST_OTHERS_SIMPLE + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_sight() + +/client/verb/toggle_intent_style() + set name = "Toggle Intent Selection Style" + set category = "Preferences" + set desc = "Toggle between directly clicking the desired intent or clicking to rotate through." + prefs.toggles ^= INTENT_STYLE + to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]") + prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/verb/toggle_ghost_hud_pref() + set name = "Toggle Ghost HUD" + set category = "Preferences" + set desc = "Hide/Show Ghost HUD" + + prefs.ghost_hud = !prefs.ghost_hud + to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].") + prefs.save_preferences() + if(isobserver(mob)) + mob.hud_used.show_hud() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]")) + +/client/verb/toggle_inquisition() // warning: unexpected inquisition + set name = "Toggle Inquisitiveness" + set desc = "Sets whether your ghost examines everything on click by default" + set category = "Preferences" + + prefs.inquisitive_ghost = !prefs.inquisitive_ghost + prefs.save_preferences() + if(prefs.inquisitive_ghost) + to_chat(src, "You will now examine everything you click on.") + else + to_chat(src, "You will no longer examine things you click on.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]")) + +//Admin Preferences +/client/proc/toggleadminhelpsound() + set name = "Hear/Silence Adminhelps" + set category = "Prefs - Admin" + set desc = "Toggle hearing a notification when admin PMs are received" + if(!holder) + return + prefs.toggles ^= SOUND_ADMINHELP + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggleannouncelogin() + set name = "Do/Don't Announce Login" + set category = "Prefs - Admin" + set desc = "Toggle if you want an announcement to admins when you login during a round" + if(!holder) + return + prefs.toggles ^= ANNOUNCE_LOGIN + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_hear_radio() + set name = "Show/Hide Radio Chatter" + set category = "Prefs - Admin" + set desc = "Toggle seeing radiochatter from nearby radios and speakers" + if(!holder) + return + prefs.chat_toggles ^= CHAT_RADIO + prefs.save_preferences() + to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/deadchat() + set name = "Show/Hide Deadchat" + set category = "Prefs - Admin" + set desc ="Toggles seeing deadchat" + if(!holder) + return + prefs.chat_toggles ^= CHAT_DEAD + prefs.save_preferences() + to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggleprayers() + set name = "Show/Hide Prayers" + set category = "Prefs - Admin" + set desc = "Toggles seeing prayers" + if(!holder) + return + prefs.chat_toggles ^= CHAT_PRAYER + prefs.save_preferences() + to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_prayer_sound() + set name = "Hear/Silence Prayer Sounds" + set category = "Prefs - Admin" + set desc = "Hear Prayer Sounds" + if(!holder) + return + prefs.toggles ^= SOUND_PRAYERS + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & SOUND_PRAYERS) ? "now" : "no longer"] hear a sound when prayers arrive.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) + +/client/proc/colorasay() + set name = "Set Admin Say Color" + set category = "Prefs - Admin" + set desc = "Set the color of your ASAY messages" + if(!holder) + return + if(!CONFIG_GET(flag/allow_admin_asaycolor)) + to_chat(src, "Custom Asay color is currently disabled by the server.") + return + var/new_asaycolor = input(src, "Please select your ASAY color.", "ASAY color", prefs.asaycolor) as color|null + if(new_asaycolor) + prefs.asaycolor = sanitize_ooccolor(new_asaycolor) + prefs.save_preferences() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set ASAY Color") + return + +/client/proc/resetasaycolor() + set name = "Reset your Admin Say Color" + set desc = "Returns your ASAY Color to default" + set category = "Prefs - Admin" + if(!holder) + return + if(!CONFIG_GET(flag/allow_admin_asaycolor)) + to_chat(src, "Custom Asay color is currently disabled by the server.") + return + prefs.asaycolor = initial(prefs.asaycolor) + prefs.save_preferences() diff --git a/code/modules/client/verbs/etips.dm b/code/modules/client/verbs/etips.dm index 2455f5d01b0..c85e5113560 100644 --- a/code/modules/client/verbs/etips.dm +++ b/code/modules/client/verbs/etips.dm @@ -1,20 +1,20 @@ -/client/verb/toggle_tips() - set name = "Toggle Examine Tooltips" - set desc = "Toggles examine hover-over tooltips" - set category = "Preferences" - - prefs.enable_tips = !prefs.enable_tips - prefs.save_preferences() - to_chat(usr, "Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled.") - -/client/verb/change_tip_delay() - set name = "Set Examine Tooltip Delay" - set desc = "Sets the delay in milliseconds before examine tooltips appear" - set category = "Preferences" - - var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10) - indelay = text2num(indelay) - if(usr)//is this what you mean? - prefs.tip_delay = indelay - prefs.save_preferences() - to_chat(usr, "Tooltip delay set to [indelay] milliseconds.") +/client/verb/toggle_tips() + set name = "Toggle Examine Tooltips" + set desc = "Toggles examine hover-over tooltips" + set category = "Preferences" + + prefs.enable_tips = !prefs.enable_tips + prefs.save_preferences() + to_chat(usr, "Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled.") + +/client/verb/change_tip_delay() + set name = "Set Examine Tooltip Delay" + set desc = "Sets the delay in milliseconds before examine tooltips appear" + set category = "Preferences" + + var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10) + indelay = text2num(indelay) + if(usr)//is this what you mean? + prefs.tip_delay = indelay + prefs.save_preferences() + to_chat(usr, "Tooltip delay set to [indelay] milliseconds.") diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index a0c4697fda4..6b8f78f823c 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -1,460 +1,460 @@ -/obj/item/clothing - name = "clothing" - resistance_flags = FLAMMABLE - max_integrity = 200 - integrity_failure = 0.4 - var/damaged_clothes = CLOTHING_PRISTINE //similar to machine's BROKEN stat and structure's broken var - - ///What level of bright light protection item has. - var/flash_protect = FLASH_PROTECTION_NONE - var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect - var/up = 0 //but separated to allow items to protect but not impair vision, like space helmets - var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down - var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv - var/visor_flags_cover = 0 //same as above, but for flags_cover -//what to toggle when toggled with weldingvisortoggle() - var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - var/alt_desc = null - var/toggle_message = null - var/alt_toggle_message = null - var/active_sound = null - var/toggle_cooldown = null - var/cooldown = 0 - - var/clothing_flags = NONE - - /// What items can be consumed to repair this clothing (must by an /obj/item/stack) - var/repairable_by = /obj/item/stack/sheet/cloth - - //Var modification - PLEASE be careful with this I know who you are and where you live - var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts" - var/list/user_vars_remembered //Auto built by the above + dropped() + equipped() - - var/pocket_storage_component_path - - //These allow head/mask items to dynamically alter the user's hair - // and facial hair, checking hair_extensions.dmi and facialhair_extensions.dmi - // for a state matching hair_state+dynamic_hair_suffix - // THESE OVERRIDE THE HIDEHAIR FLAGS - var/dynamic_hair_suffix = ""//head > mask for head hair - var/dynamic_fhair_suffix = ""//mask > head for facial hair - - ///These are armor values that protect the wearer, taken from the clothing's armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). - var/list/armor_list = list() - ///These are armor values that protect the clothing, taken from its armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). - var/list/durability_list = list() - - /// How much clothing damage has been dealt to each of the limbs of the clothing, assuming it covers more than one limb - var/list/damage_by_parts - /// How much integrity is in a specific limb before that limb is disabled (for use in [/obj/item/clothing/proc/take_damage_zone], and only if we cover multiple zones.) Set to 0 to disable shredding. - var/limb_integrity = 0 - /// How many zones (body parts, not precise) we have disabled so far, for naming purposes - var/zones_disabled - -/obj/item/clothing/Initialize() - if((clothing_flags & VOICEBOX_TOGGLABLE)) - actions_types += /datum/action/item_action/toggle_voice_box - . = ..() - if(ispath(pocket_storage_component_path)) - LoadComponent(pocket_storage_component_path) - -/obj/item/clothing/MouseDrop(atom/over_object) - . = ..() - var/mob/M = usr - - if(ismecha(M.loc)) // stops inventory actions in a mech - return - - if(!M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - if(M.putItemFromInventoryInHandIfPossible(src, H.held_index)) - add_fingerprint(usr) - -/obj/item/reagent_containers/food/snacks/clothing - name = "temporary moth clothing snack item" - desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object." - list_reagents = list(/datum/reagent/consumable/nutriment = 1) - tastes = list("dust" = 1, "lint" = 1) - foodtype = CLOTH - -/obj/item/clothing/attack(mob/M, mob/user, def_zone) - if(user.a_intent != INTENT_HARM && ismoth(M)) - if(damaged_clothes == CLOTHING_SHREDDED) - to_chat(user, "[src] seem[p_s()] pretty torn apart... [p_they(TRUE)] probably wouldn't be too tasty.") - return - var/obj/item/reagent_containers/food/snacks/clothing/clothing_as_food = new - clothing_as_food.name = name - if(clothing_as_food.attack(M, user, def_zone)) - take_damage(15, sound_effect=FALSE) - qdel(clothing_as_food) - else - return ..() - -/obj/item/clothing/attackby(obj/item/W, mob/user, params) - if(damaged_clothes && istype(W, repairable_by)) - var/obj/item/stack/S = W - switch(damaged_clothes) - if(CLOTHING_DAMAGED) - S.use(1) - repair(user, params) - if(CLOTHING_SHREDDED) - if(S.amount < 3) - to_chat(user, "You require 3 [S.name] to repair [src].") - return - to_chat(user, "You begin fixing the damage to [src] with [S]...") - if(do_after(user, 6 SECONDS, TRUE, src)) - if(S.use(3)) - repair(user, params) - return 1 - return ..() - -/// Set the clothing's integrity back to 100%, remove all damage to bodyparts, and generally fix it up -/obj/item/clothing/proc/repair(mob/user, params) - update_clothes_damaged_state(CLOTHING_PRISTINE) - obj_integrity = max_integrity - name = initial(name) // remove "tattered" or "shredded" if there's a prefix - body_parts_covered = initial(body_parts_covered) - slot_flags = initial(slot_flags) - damage_by_parts = null - if(user) - UnregisterSignal(user, COMSIG_MOVABLE_MOVED) - to_chat(user, "You fix the damage on [src].") - -/** - * take_damage_zone() is used for dealing damage to specific bodyparts on a worn piece of clothing, meant to be called from [/obj/item/bodypart/proc/check_woundings_mods()] - * - * This proc only matters when a bodypart that this clothing is covering is harmed by a direct attack (being on fire or in space need not apply), and only if this clothing covers - * more than one bodypart to begin with. No point in tracking damage by zone for a hat, and I'm not cruel enough to let you fully break them in a few shots. - * Also if limb_integrity is 0, then this clothing doesn't have bodypart damage enabled so skip it. - * - * Arguments: - * * def_zone: The bodypart zone in question - * * damage_amount: Incoming damage - * * damage_type: BRUTE or BURN - * * armour_penetration: If the attack had armour_penetration - */ -/obj/item/clothing/proc/take_damage_zone(def_zone, damage_amount, damage_type, armour_penetration) - if(!def_zone || !limb_integrity || (initial(body_parts_covered) in GLOB.bitflags)) // the second check sees if we only cover one bodypart anyway and don't need to bother with this - return - var/list/covered_limbs = body_parts_covered2organ_names(body_parts_covered) // what do we actually cover? - if(!(def_zone in covered_limbs)) - return - - var/damage_dealt = take_damage(damage_amount * 0.1, damage_type, armour_penetration, FALSE) * 10 // only deal 10% of the damage to the general integrity damage, then multiply it by 10 so we know how much to deal to limb - LAZYINITLIST(damage_by_parts) - damage_by_parts[def_zone] += damage_dealt - if(damage_by_parts[def_zone] > limb_integrity) - disable_zone(def_zone, damage_type) - -/** - * disable_zone() is used to disable a given bodypart's protection on our clothing item, mainly from [/obj/item/clothing/proc/take_damage_zone()] - * - * This proc disables all protection on the specified bodypart for this piece of clothing: it'll be as if it doesn't cover it at all anymore (because it won't!) - * If every possible bodypart has been disabled on the clothing, we put it out of commission entirely and mark it as shredded, whereby it will have to be repaired in - * order to equip it again. Also note we only consider it damaged if there's more than one bodypart disabled. - * - * Arguments: - * * def_zone: The bodypart zone we're disabling - * * damage_type: Only really relevant for the verb for describing the breaking, and maybe obj_destruction() - */ -/obj/item/clothing/proc/disable_zone(def_zone, damage_type) - var/list/covered_limbs = body_parts_covered2organ_names(body_parts_covered) - if(!(def_zone in covered_limbs)) - return - - var/zone_name = parse_zone(def_zone) - var/break_verb = ((damage_type == BRUTE) ? "torn" : "burned") - - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - C.visible_message("The [zone_name] on [C]'s [src.name] is [break_verb] away!", "The [zone_name] on your [src.name] is [break_verb] away!", vision_distance = COMBAT_MESSAGE_RANGE) - RegisterSignal(C, COMSIG_MOVABLE_MOVED, .proc/bristle, override = TRUE) - - zones_disabled++ - for(var/i in zone2body_parts_covered(def_zone)) - body_parts_covered &= ~i - - if(body_parts_covered == NONE) // if there are no more parts to break then the whole thing is kaput - obj_destruction((damage_type == BRUTE ? "melee" : "laser")) // melee/laser is good enough since this only procs from direct attacks anyway and not from fire/bombs - return - - switch(zones_disabled) - if(1) - name = "damaged [initial(name)]" - if(2) - name = "mangy [initial(name)]" - if(3 to INFINITY) // take better care of your shit, dude - name = "tattered [initial(name)]" - - update_clothes_damaged_state(CLOTHING_DAMAGED) - -/obj/item/clothing/Destroy() - user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up - return ..() - -/obj/item/clothing/dropped(mob/user) - ..() - if(!istype(user)) - return - UnregisterSignal(user, COMSIG_MOVABLE_MOVED) - if(LAZYLEN(user_vars_remembered)) - for(var/variable in user_vars_remembered) - if(variable in user.vars) - if(user.vars[variable] == user_vars_to_edit[variable]) //Is it still what we set it to? (if not we best not change it) - user.vars[variable] = user_vars_remembered[variable] - user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null. - -/obj/item/clothing/equipped(mob/user, slot) - ..() - if (!istype(user)) - return - if(slot_flags & slot) //Was equipped to a valid slot for this item? - if(iscarbon(user) && LAZYLEN(zones_disabled)) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/bristle, override = TRUE) - if (LAZYLEN(user_vars_to_edit)) - for(var/variable in user_vars_to_edit) - if(variable in user.vars) - LAZYSET(user_vars_remembered, variable, user.vars[variable]) - user.vv_edit_var(variable, user_vars_to_edit[variable]) - -/obj/item/clothing/examine(mob/user) - . = ..() - if(damaged_clothes == CLOTHING_SHREDDED) - . += "[p_theyre(TRUE)] completely shredded and require[p_s()] mending before [p_they()] can be worn again!" - return - - switch (max_heat_protection_temperature) - if (400 to 1000) - . += "[src] offers the wearer limited protection from fire." - if (1001 to 1600) - . += "[src] offers the wearer some protection from fire." - if (1601 to 35000) - . += "[src] offers the wearer robust protection from fire." - - for(var/zone in damage_by_parts) - var/pct_damage_part = damage_by_parts[zone] / limb_integrity * 100 - var/zone_name = parse_zone(zone) - switch(pct_damage_part) - if(100 to INFINITY) - . += "The [zone_name] is useless and requires mending!" - if(60 to 99) - . += "The [zone_name] is heavily shredded!" - if(30 to 59) - . += "The [zone_name] is partially shredded." - - var/datum/component/storage/pockets = GetComponent(/datum/component/storage) - if(pockets) - var/list/how_cool_are_your_threads = list("") - if(pockets.attack_hand_interact) - how_cool_are_your_threads += "[src]'s storage opens when clicked.\n" - else - how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n" - if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" - else - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n" - if(pockets.quickdraw) - how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n" - if(pockets.silent) - how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n" - how_cool_are_your_threads += "" - . += how_cool_are_your_threads.Join() - - if(LAZYLEN(armor_list)) - armor_list.Cut() - if(armor.bio) - armor_list += list("TOXIN" = armor.bio) - if(armor.bomb) - armor_list += list("EXPLOSIVE" = armor.bomb) - if(armor.bullet) - armor_list += list("BULLET" = armor.bullet) - if(armor.energy) - armor_list += list("ENERGY" = armor.energy) - if(armor.laser) - armor_list += list("LASER" = armor.laser) - if(armor.magic) - armor_list += list("MAGIC" = armor.magic) - if(armor.melee) - armor_list += list("MELEE" = armor.melee) - if(armor.rad) - armor_list += list("RADIATION" = armor.rad) - - if(LAZYLEN(durability_list)) - durability_list.Cut() - if(armor.fire) - durability_list += list("FIRE" = armor.fire) - if(armor.acid) - durability_list += list("ACID" = armor.acid) - - if(LAZYLEN(armor_list) || LAZYLEN(durability_list)) - . += "It has a tag listing its protection classes." - -/obj/item/clothing/Topic(href, href_list) - . = ..() - - if(href_list["list_armor"]) - var/list/readout = list("PROTECTION CLASSES (I-X)") - if(LAZYLEN(armor_list)) - readout += "\nARMOR" - for(var/dam_type in armor_list) - var/armor_amount = armor_list[dam_type] - readout += "\n[dam_type] [armor_to_protection_class(armor_amount)]" //e.g. BOMB IV - if(LAZYLEN(durability_list)) - readout += "\nDURABILITY" - for(var/dam_type in durability_list) - var/durability_amount = durability_list[dam_type] - readout += "\n[dam_type] [armor_to_protection_class(durability_amount)]" //e.g. FIRE II - readout += "" - - to_chat(usr, "[readout.Join()]") - -/** - * Rounds armor_value to nearest 10, divides it by 10 and then expresses it in roman numerals up to 10 - * - * Rounds armor_value to nearest 10, divides it by 10 - * and then expresses it in roman numerals up to 10 - * Arguments: - * * armor_value - Number we're converting - */ -/obj/item/clothing/proc/armor_to_protection_class(armor_value) - armor_value = round(armor_value,10) / 10 - switch (armor_value) - if (1) - . = "I" - if (2) - . = "II" - if (3) - . = "III" - if (4) - . = "IV" - if (5) - . = "V" - if (6) - . = "VI" - if (7) - . = "VII" - if (8) - . = "VIII" - if (9) - . = "IX" - if (10 to INFINITY) - . = "X" - return . - -/obj/item/clothing/obj_break(damage_flag) - update_clothes_damaged_state(CLOTHING_DAMAGED) - - if(isliving(loc)) //It's not important enough to warrant a message if it's not on someone - var/mob/living/M = loc - if(src in M.get_equipped_items(FALSE)) - to_chat(M, "Your [name] start[p_s()] to fall apart!") - else - to_chat(M, "[src] start[p_s()] to fall apart!") - -//This mostly exists so subtypes can call appriopriate update icon calls on the wearer. -/obj/item/clothing/proc/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) - damaged_clothes = damaged_state - -/obj/item/clothing/update_overlays() - . = ..() - if(damaged_clothes) - var/index = "[REF(initial(icon))]-[initial(icon_state)]" - var/static/list/damaged_clothes_icons = list() - var/icon/damaged_clothes_icon = damaged_clothes_icons[index] - if(!damaged_clothes_icon) - damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object - damaged_clothes_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) - damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant - damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon) - damaged_clothes_icons[index] = damaged_clothes_icon - . += damaged_clothes_icon - -/* -SEE_SELF // can see self, no matter what -SEE_MOBS // can see all mobs, no matter what -SEE_OBJS // can see all objs, no matter what -SEE_TURFS // can see all turfs (and areas), no matter what -SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are - // in a lit area (via pixel_x,y or smooth movement), can see those pixels -BLIND // can't see anything -*/ - -/proc/generate_female_clothing(index,t_color,icon,type) - var/icon/female_clothing_icon = icon("icon"=icon, "icon_state"=t_color) - var/icon/female_s = icon("icon"='icons/mob/clothing/under/masking_helpers.dmi', "icon_state"="[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]") - female_clothing_icon.Blend(female_s, ICON_MULTIPLY) - female_clothing_icon = fcopy_rsc(female_clothing_icon) - GLOB.female_clothing_icons[index] = female_clothing_icon - -/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc. - if(!can_use(user)) - return FALSE - - visor_toggling() - - to_chat(user, "You adjust \the [src] [up ? "up" : "down"].") - - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.head_update(src, forced = 1) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - return TRUE - -/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags - up = !up - clothing_flags ^= visor_flags - flags_inv ^= visor_flags_inv - flags_cover ^= initial(flags_cover) - icon_state = "[initial(icon_state)][up ? "up" : ""]" - if(visor_vars_to_toggle & VISOR_FLASHPROTECT) - flash_protect ^= initial(flash_protect) - if(visor_vars_to_toggle & VISOR_TINT) - tint ^= initial(tint) - -/obj/item/clothing/head/helmet/space/plasmaman/visor_toggling() //handles all the actual toggling of flags - up = !up - clothing_flags ^= visor_flags - flags_inv ^= visor_flags_inv - icon_state = "[initial(icon_state)]" - if(visor_vars_to_toggle & VISOR_FLASHPROTECT) - flash_protect ^= initial(flash_protect) - if(visor_vars_to_toggle & VISOR_TINT) - tint ^= initial(tint) - -/obj/item/clothing/proc/can_use(mob/user) - if(user && ismob(user)) - if(!user.incapacitated()) - return 1 - return 0 - -/obj/item/clothing/obj_destruction(damage_flag) - if(damage_flag == "bomb") - var/turf/T = get_turf(src) - //so the shred survives potential turf change from the explosion. - addtimer(CALLBACK_NEW(/obj/effect/decal/cleanable/shreds, list(T, name)), 1) - deconstruct(FALSE) - else if(!(damage_flag in list("acid", "fire"))) - body_parts_covered = NONE - slot_flags = NONE - update_clothes_damaged_state(CLOTHING_SHREDDED) - if(isliving(loc)) - var/mob/living/M = loc - if(src in M.get_equipped_items(FALSE)) //make sure they were wearing it and not attacking the item in their hands / eating it if they were a moth. - M.visible_message("[M]'s [src.name] fall[p_s()] off, [p_theyre()] completely shredded!", "Your [src.name] fall[p_s()] off, [p_theyre()] completely shredded!", vision_distance = COMBAT_MESSAGE_RANGE) - M.dropItemToGround(src) - else - M.visible_message("[src] fall[p_s()] apart, completely shredded!", vision_distance = COMBAT_MESSAGE_RANGE) - name = "shredded [initial(name)]" // change the name -after- the message, not before. - else - ..() - -/// If we're a clothing with at least 1 shredded/disabled zone, give the wearer a periodic heads up letting them know their clothes are damaged -/obj/item/clothing/proc/bristle(mob/living/L) - if(!istype(L)) - return - if(prob(0.2)) - to_chat(L, "The damaged threads on your [src.name] chafe!") +/obj/item/clothing + name = "clothing" + resistance_flags = FLAMMABLE + max_integrity = 200 + integrity_failure = 0.4 + var/damaged_clothes = CLOTHING_PRISTINE //similar to machine's BROKEN stat and structure's broken var + + ///What level of bright light protection item has. + var/flash_protect = FLASH_PROTECTION_NONE + var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect + var/up = 0 //but separated to allow items to protect but not impair vision, like space helmets + var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down + var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv + var/visor_flags_cover = 0 //same as above, but for flags_cover +//what to toggle when toggled with weldingvisortoggle() + var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + var/alt_desc = null + var/toggle_message = null + var/alt_toggle_message = null + var/active_sound = null + var/toggle_cooldown = null + var/cooldown = 0 + + var/clothing_flags = NONE + + /// What items can be consumed to repair this clothing (must by an /obj/item/stack) + var/repairable_by = /obj/item/stack/sheet/cloth + + //Var modification - PLEASE be careful with this I know who you are and where you live + var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts" + var/list/user_vars_remembered //Auto built by the above + dropped() + equipped() + + var/pocket_storage_component_path + + //These allow head/mask items to dynamically alter the user's hair + // and facial hair, checking hair_extensions.dmi and facialhair_extensions.dmi + // for a state matching hair_state+dynamic_hair_suffix + // THESE OVERRIDE THE HIDEHAIR FLAGS + var/dynamic_hair_suffix = ""//head > mask for head hair + var/dynamic_fhair_suffix = ""//mask > head for facial hair + + ///These are armor values that protect the wearer, taken from the clothing's armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). + var/list/armor_list = list() + ///These are armor values that protect the clothing, taken from its armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). + var/list/durability_list = list() + + /// How much clothing damage has been dealt to each of the limbs of the clothing, assuming it covers more than one limb + var/list/damage_by_parts + /// How much integrity is in a specific limb before that limb is disabled (for use in [/obj/item/clothing/proc/take_damage_zone], and only if we cover multiple zones.) Set to 0 to disable shredding. + var/limb_integrity = 0 + /// How many zones (body parts, not precise) we have disabled so far, for naming purposes + var/zones_disabled + +/obj/item/clothing/Initialize() + if((clothing_flags & VOICEBOX_TOGGLABLE)) + actions_types += /datum/action/item_action/toggle_voice_box + . = ..() + if(ispath(pocket_storage_component_path)) + LoadComponent(pocket_storage_component_path) + +/obj/item/clothing/MouseDrop(atom/over_object) + . = ..() + var/mob/M = usr + + if(ismecha(M.loc)) // stops inventory actions in a mech + return + + if(!M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + if(M.putItemFromInventoryInHandIfPossible(src, H.held_index)) + add_fingerprint(usr) + +/obj/item/reagent_containers/food/snacks/clothing + name = "temporary moth clothing snack item" + desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object." + list_reagents = list(/datum/reagent/consumable/nutriment = 1) + tastes = list("dust" = 1, "lint" = 1) + foodtype = CLOTH + +/obj/item/clothing/attack(mob/M, mob/user, def_zone) + if(user.a_intent != INTENT_HARM && ismoth(M)) + if(damaged_clothes == CLOTHING_SHREDDED) + to_chat(user, "[src] seem[p_s()] pretty torn apart... [p_they(TRUE)] probably wouldn't be too tasty.") + return + var/obj/item/reagent_containers/food/snacks/clothing/clothing_as_food = new + clothing_as_food.name = name + if(clothing_as_food.attack(M, user, def_zone)) + take_damage(15, sound_effect=FALSE) + qdel(clothing_as_food) + else + return ..() + +/obj/item/clothing/attackby(obj/item/W, mob/user, params) + if(damaged_clothes && istype(W, repairable_by)) + var/obj/item/stack/S = W + switch(damaged_clothes) + if(CLOTHING_DAMAGED) + S.use(1) + repair(user, params) + if(CLOTHING_SHREDDED) + if(S.amount < 3) + to_chat(user, "You require 3 [S.name] to repair [src].") + return + to_chat(user, "You begin fixing the damage to [src] with [S]...") + if(do_after(user, 6 SECONDS, TRUE, src)) + if(S.use(3)) + repair(user, params) + return 1 + return ..() + +/// Set the clothing's integrity back to 100%, remove all damage to bodyparts, and generally fix it up +/obj/item/clothing/proc/repair(mob/user, params) + update_clothes_damaged_state(CLOTHING_PRISTINE) + obj_integrity = max_integrity + name = initial(name) // remove "tattered" or "shredded" if there's a prefix + body_parts_covered = initial(body_parts_covered) + slot_flags = initial(slot_flags) + damage_by_parts = null + if(user) + UnregisterSignal(user, COMSIG_MOVABLE_MOVED) + to_chat(user, "You fix the damage on [src].") + +/** + * take_damage_zone() is used for dealing damage to specific bodyparts on a worn piece of clothing, meant to be called from [/obj/item/bodypart/proc/check_woundings_mods()] + * + * This proc only matters when a bodypart that this clothing is covering is harmed by a direct attack (being on fire or in space need not apply), and only if this clothing covers + * more than one bodypart to begin with. No point in tracking damage by zone for a hat, and I'm not cruel enough to let you fully break them in a few shots. + * Also if limb_integrity is 0, then this clothing doesn't have bodypart damage enabled so skip it. + * + * Arguments: + * * def_zone: The bodypart zone in question + * * damage_amount: Incoming damage + * * damage_type: BRUTE or BURN + * * armour_penetration: If the attack had armour_penetration + */ +/obj/item/clothing/proc/take_damage_zone(def_zone, damage_amount, damage_type, armour_penetration) + if(!def_zone || !limb_integrity || (initial(body_parts_covered) in GLOB.bitflags)) // the second check sees if we only cover one bodypart anyway and don't need to bother with this + return + var/list/covered_limbs = body_parts_covered2organ_names(body_parts_covered) // what do we actually cover? + if(!(def_zone in covered_limbs)) + return + + var/damage_dealt = take_damage(damage_amount * 0.1, damage_type, armour_penetration, FALSE) * 10 // only deal 10% of the damage to the general integrity damage, then multiply it by 10 so we know how much to deal to limb + LAZYINITLIST(damage_by_parts) + damage_by_parts[def_zone] += damage_dealt + if(damage_by_parts[def_zone] > limb_integrity) + disable_zone(def_zone, damage_type) + +/** + * disable_zone() is used to disable a given bodypart's protection on our clothing item, mainly from [/obj/item/clothing/proc/take_damage_zone()] + * + * This proc disables all protection on the specified bodypart for this piece of clothing: it'll be as if it doesn't cover it at all anymore (because it won't!) + * If every possible bodypart has been disabled on the clothing, we put it out of commission entirely and mark it as shredded, whereby it will have to be repaired in + * order to equip it again. Also note we only consider it damaged if there's more than one bodypart disabled. + * + * Arguments: + * * def_zone: The bodypart zone we're disabling + * * damage_type: Only really relevant for the verb for describing the breaking, and maybe obj_destruction() + */ +/obj/item/clothing/proc/disable_zone(def_zone, damage_type) + var/list/covered_limbs = body_parts_covered2organ_names(body_parts_covered) + if(!(def_zone in covered_limbs)) + return + + var/zone_name = parse_zone(def_zone) + var/break_verb = ((damage_type == BRUTE) ? "torn" : "burned") + + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + C.visible_message("The [zone_name] on [C]'s [src.name] is [break_verb] away!", "The [zone_name] on your [src.name] is [break_verb] away!", vision_distance = COMBAT_MESSAGE_RANGE) + RegisterSignal(C, COMSIG_MOVABLE_MOVED, .proc/bristle, override = TRUE) + + zones_disabled++ + for(var/i in zone2body_parts_covered(def_zone)) + body_parts_covered &= ~i + + if(body_parts_covered == NONE) // if there are no more parts to break then the whole thing is kaput + obj_destruction((damage_type == BRUTE ? "melee" : "laser")) // melee/laser is good enough since this only procs from direct attacks anyway and not from fire/bombs + return + + switch(zones_disabled) + if(1) + name = "damaged [initial(name)]" + if(2) + name = "mangy [initial(name)]" + if(3 to INFINITY) // take better care of your shit, dude + name = "tattered [initial(name)]" + + update_clothes_damaged_state(CLOTHING_DAMAGED) + +/obj/item/clothing/Destroy() + user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up + return ..() + +/obj/item/clothing/dropped(mob/user) + ..() + if(!istype(user)) + return + UnregisterSignal(user, COMSIG_MOVABLE_MOVED) + if(LAZYLEN(user_vars_remembered)) + for(var/variable in user_vars_remembered) + if(variable in user.vars) + if(user.vars[variable] == user_vars_to_edit[variable]) //Is it still what we set it to? (if not we best not change it) + user.vars[variable] = user_vars_remembered[variable] + user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null. + +/obj/item/clothing/equipped(mob/user, slot) + ..() + if (!istype(user)) + return + if(slot_flags & slot) //Was equipped to a valid slot for this item? + if(iscarbon(user) && LAZYLEN(zones_disabled)) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/bristle, override = TRUE) + if (LAZYLEN(user_vars_to_edit)) + for(var/variable in user_vars_to_edit) + if(variable in user.vars) + LAZYSET(user_vars_remembered, variable, user.vars[variable]) + user.vv_edit_var(variable, user_vars_to_edit[variable]) + +/obj/item/clothing/examine(mob/user) + . = ..() + if(damaged_clothes == CLOTHING_SHREDDED) + . += "[p_theyre(TRUE)] completely shredded and require[p_s()] mending before [p_they()] can be worn again!" + return + + switch (max_heat_protection_temperature) + if (400 to 1000) + . += "[src] offers the wearer limited protection from fire." + if (1001 to 1600) + . += "[src] offers the wearer some protection from fire." + if (1601 to 35000) + . += "[src] offers the wearer robust protection from fire." + + for(var/zone in damage_by_parts) + var/pct_damage_part = damage_by_parts[zone] / limb_integrity * 100 + var/zone_name = parse_zone(zone) + switch(pct_damage_part) + if(100 to INFINITY) + . += "The [zone_name] is useless and requires mending!" + if(60 to 99) + . += "The [zone_name] is heavily shredded!" + if(30 to 59) + . += "The [zone_name] is partially shredded." + + var/datum/component/storage/pockets = GetComponent(/datum/component/storage) + if(pockets) + var/list/how_cool_are_your_threads = list("") + if(pockets.attack_hand_interact) + how_cool_are_your_threads += "[src]'s storage opens when clicked.\n" + else + how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n" + if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" + else + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n" + if(pockets.quickdraw) + how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n" + if(pockets.silent) + how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n" + how_cool_are_your_threads += "" + . += how_cool_are_your_threads.Join() + + if(LAZYLEN(armor_list)) + armor_list.Cut() + if(armor.bio) + armor_list += list("TOXIN" = armor.bio) + if(armor.bomb) + armor_list += list("EXPLOSIVE" = armor.bomb) + if(armor.bullet) + armor_list += list("BULLET" = armor.bullet) + if(armor.energy) + armor_list += list("ENERGY" = armor.energy) + if(armor.laser) + armor_list += list("LASER" = armor.laser) + if(armor.magic) + armor_list += list("MAGIC" = armor.magic) + if(armor.melee) + armor_list += list("MELEE" = armor.melee) + if(armor.rad) + armor_list += list("RADIATION" = armor.rad) + + if(LAZYLEN(durability_list)) + durability_list.Cut() + if(armor.fire) + durability_list += list("FIRE" = armor.fire) + if(armor.acid) + durability_list += list("ACID" = armor.acid) + + if(LAZYLEN(armor_list) || LAZYLEN(durability_list)) + . += "It has a tag listing its protection classes." + +/obj/item/clothing/Topic(href, href_list) + . = ..() + + if(href_list["list_armor"]) + var/list/readout = list("PROTECTION CLASSES (I-X)") + if(LAZYLEN(armor_list)) + readout += "\nARMOR" + for(var/dam_type in armor_list) + var/armor_amount = armor_list[dam_type] + readout += "\n[dam_type] [armor_to_protection_class(armor_amount)]" //e.g. BOMB IV + if(LAZYLEN(durability_list)) + readout += "\nDURABILITY" + for(var/dam_type in durability_list) + var/durability_amount = durability_list[dam_type] + readout += "\n[dam_type] [armor_to_protection_class(durability_amount)]" //e.g. FIRE II + readout += "" + + to_chat(usr, "[readout.Join()]") + +/** + * Rounds armor_value to nearest 10, divides it by 10 and then expresses it in roman numerals up to 10 + * + * Rounds armor_value to nearest 10, divides it by 10 + * and then expresses it in roman numerals up to 10 + * Arguments: + * * armor_value - Number we're converting + */ +/obj/item/clothing/proc/armor_to_protection_class(armor_value) + armor_value = round(armor_value,10) / 10 + switch (armor_value) + if (1) + . = "I" + if (2) + . = "II" + if (3) + . = "III" + if (4) + . = "IV" + if (5) + . = "V" + if (6) + . = "VI" + if (7) + . = "VII" + if (8) + . = "VIII" + if (9) + . = "IX" + if (10 to INFINITY) + . = "X" + return . + +/obj/item/clothing/obj_break(damage_flag) + update_clothes_damaged_state(CLOTHING_DAMAGED) + + if(isliving(loc)) //It's not important enough to warrant a message if it's not on someone + var/mob/living/M = loc + if(src in M.get_equipped_items(FALSE)) + to_chat(M, "Your [name] start[p_s()] to fall apart!") + else + to_chat(M, "[src] start[p_s()] to fall apart!") + +//This mostly exists so subtypes can call appriopriate update icon calls on the wearer. +/obj/item/clothing/proc/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) + damaged_clothes = damaged_state + +/obj/item/clothing/update_overlays() + . = ..() + if(damaged_clothes) + var/index = "[REF(initial(icon))]-[initial(icon_state)]" + var/static/list/damaged_clothes_icons = list() + var/icon/damaged_clothes_icon = damaged_clothes_icons[index] + if(!damaged_clothes_icon) + damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object + damaged_clothes_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) + damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant + damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon) + damaged_clothes_icons[index] = damaged_clothes_icon + . += damaged_clothes_icon + +/* +SEE_SELF // can see self, no matter what +SEE_MOBS // can see all mobs, no matter what +SEE_OBJS // can see all objs, no matter what +SEE_TURFS // can see all turfs (and areas), no matter what +SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are + // in a lit area (via pixel_x,y or smooth movement), can see those pixels +BLIND // can't see anything +*/ + +/proc/generate_female_clothing(index,t_color,icon,type) + var/icon/female_clothing_icon = icon("icon"=icon, "icon_state"=t_color) + var/icon/female_s = icon("icon"='icons/mob/clothing/under/masking_helpers.dmi', "icon_state"="[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]") + female_clothing_icon.Blend(female_s, ICON_MULTIPLY) + female_clothing_icon = fcopy_rsc(female_clothing_icon) + GLOB.female_clothing_icons[index] = female_clothing_icon + +/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc. + if(!can_use(user)) + return FALSE + + visor_toggling() + + to_chat(user, "You adjust \the [src] [up ? "up" : "down"].") + + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.head_update(src, forced = 1) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + return TRUE + +/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags + up = !up + clothing_flags ^= visor_flags + flags_inv ^= visor_flags_inv + flags_cover ^= initial(flags_cover) + icon_state = "[initial(icon_state)][up ? "up" : ""]" + if(visor_vars_to_toggle & VISOR_FLASHPROTECT) + flash_protect ^= initial(flash_protect) + if(visor_vars_to_toggle & VISOR_TINT) + tint ^= initial(tint) + +/obj/item/clothing/head/helmet/space/plasmaman/visor_toggling() //handles all the actual toggling of flags + up = !up + clothing_flags ^= visor_flags + flags_inv ^= visor_flags_inv + icon_state = "[initial(icon_state)]" + if(visor_vars_to_toggle & VISOR_FLASHPROTECT) + flash_protect ^= initial(flash_protect) + if(visor_vars_to_toggle & VISOR_TINT) + tint ^= initial(tint) + +/obj/item/clothing/proc/can_use(mob/user) + if(user && ismob(user)) + if(!user.incapacitated()) + return 1 + return 0 + +/obj/item/clothing/obj_destruction(damage_flag) + if(damage_flag == "bomb") + var/turf/T = get_turf(src) + //so the shred survives potential turf change from the explosion. + addtimer(CALLBACK_NEW(/obj/effect/decal/cleanable/shreds, list(T, name)), 1) + deconstruct(FALSE) + else if(!(damage_flag in list("acid", "fire"))) + body_parts_covered = NONE + slot_flags = NONE + update_clothes_damaged_state(CLOTHING_SHREDDED) + if(isliving(loc)) + var/mob/living/M = loc + if(src in M.get_equipped_items(FALSE)) //make sure they were wearing it and not attacking the item in their hands / eating it if they were a moth. + M.visible_message("[M]'s [src.name] fall[p_s()] off, [p_theyre()] completely shredded!", "Your [src.name] fall[p_s()] off, [p_theyre()] completely shredded!", vision_distance = COMBAT_MESSAGE_RANGE) + M.dropItemToGround(src) + else + M.visible_message("[src] fall[p_s()] apart, completely shredded!", vision_distance = COMBAT_MESSAGE_RANGE) + name = "shredded [initial(name)]" // change the name -after- the message, not before. + else + ..() + +/// If we're a clothing with at least 1 shredded/disabled zone, give the wearer a periodic heads up letting them know their clothes are damaged +/obj/item/clothing/proc/bristle(mob/living/L) + if(!istype(L)) + return + if(prob(0.2)) + to_chat(L, "The damaged threads on your [src.name] chafe!") diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index 14cc569886b..32a8d00c748 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -1,529 +1,529 @@ -//Glasses -/obj/item/clothing/glasses - name = "glasses" - icon = 'icons/obj/clothing/glasses.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_cover = GLASSESCOVERSEYES - slot_flags = ITEM_SLOT_EYES - strip_delay = 20 - equip_delay_other = 25 - resistance_flags = NONE - custom_materials = list(/datum/material/glass = 250) - var/vision_flags = 0 - var/darkness_view = 2//Base human is 2 - var/invis_view = SEE_INVISIBLE_LIVING //admin only for now - var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis - var/lighting_alpha - var/list/icon/current = list() //the current hud icons - var/vision_correction = 0 //does wearing these glasses correct some of our vision defects? - var/glass_colour_type //colors your vision when worn - -/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/clothing/glasses/examine(mob/user) - . = ..() - if(glass_colour_type && ishuman(user)) - . += "Alt-click to toggle [p_their()] colors." - -/obj/item/clothing/glasses/visor_toggling() - ..() - if(visor_vars_to_toggle & VISOR_VISIONFLAGS) - vision_flags ^= initial(vision_flags) - if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) - darkness_view ^= initial(darkness_view) - if(visor_vars_to_toggle & VISOR_INVISVIEW) - invis_view ^= initial(invis_view) - -/obj/item/clothing/glasses/weldingvisortoggle(mob/user) - . = ..() - if(. && user) - user.update_sight() - -//called when thermal glasses are emped. -/obj/item/clothing/glasses/proc/thermal_overload() - if(ishuman(src.loc)) - var/mob/living/carbon/human/H = src.loc - var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) - if(!H.is_blind()) - if(H.glasses == src) - to_chat(H, "[src] overloads and blinds you!") - H.flash_act(visual = 1) - H.blind_eyes(3) - H.blur_eyes(5) - eyes.applyOrganDamage(5) - -/obj/item/clothing/glasses/meson - name = "optical meson scanner" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." - icon_state = "meson" - inhand_icon_state = "meson" - darkness_view = 2 - vision_flags = SEE_TURFS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/lightgreen - -/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/clothing/glasses/meson/night - name = "night vision meson scanner" - desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." - icon_state = "nvgmeson" - inhand_icon_state = "nvgmeson" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/meson/gar - name = "gar mesons" - icon_state = "garm" - inhand_icon_state = "garm" - desc = "Do the impossible, see the invisible!" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/science - name = "science goggles" - desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." - icon_state = "purple" - inhand_icon_state = "glasses" - clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles - actions_types = list(/datum/action/item_action/toggle_research_scanner) - glass_colour_type = /datum/client_colour/glass_colour/purple - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - -/obj/item/clothing/glasses/science/item_action_slot_check(slot) - if(slot == ITEM_SLOT_EYES) - return 1 - -/obj/item/clothing/glasses/night - name = "night vision goggles" - desc = "You can totally see in the dark now!" - icon_state = "night" - inhand_icon_state = "glasses" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/glasses/eyepatch - name = "eyepatch" - desc = "Yarr." - icon_state = "eyepatch" - inhand_icon_state = "eyepatch" - -/obj/item/clothing/glasses/monocle - name = "monocle" - desc = "Such a dapper eyepiece!" - icon_state = "monocle" - inhand_icon_state = "headset" // lol - -/obj/item/clothing/glasses/material - name = "optical material scanner" - desc = "Very confusing glasses." - icon_state = "material" - inhand_icon_state = "glasses" - vision_flags = SEE_OBJS - glass_colour_type = /datum/client_colour/glass_colour/lightblue - -/obj/item/clothing/glasses/material/mining - name = "optical material scanner" - desc = "Used by miners to detect ores deep within the rock." - icon_state = "material" - inhand_icon_state = "glasses" - darkness_view = 0 - -/obj/item/clothing/glasses/material/mining/gar - name = "gar material scanner" - icon_state = "garm" - inhand_icon_state = "garm" - desc = "Do the impossible, see the invisible!" - force = 10 - throwforce = 20 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - glass_colour_type = /datum/client_colour/glass_colour/lightgreen - -/obj/item/clothing/glasses/regular - name = "prescription glasses" - desc = "Made by Nerd. Co." - icon_state = "glasses" - inhand_icon_state = "glasses" - vision_correction = 1 //corrects nearsightedness - -/obj/item/clothing/glasses/regular/jamjar - name = "jamjar glasses" - desc = "Also known as Virginity Protectors." - icon_state = "jamjar_glasses" - inhand_icon_state = "jamjar_glasses" - -/obj/item/clothing/glasses/regular/hipster - name = "prescription glasses" - desc = "Made by Uncool. Co." - icon_state = "hipster_glasses" - inhand_icon_state = "hipster_glasses" - -/obj/item/clothing/glasses/regular/circle - name = "circle glasses" - desc = "Why would you wear something so controversial yet so brave?" - icon_state = "circle_glasses" - inhand_icon_state = "circle_glasses" - -//Here lies green glasses, so ugly they died. RIP - -/obj/item/clothing/glasses/sunglasses - name = "sunglasses" - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." - icon_state = "sun" - inhand_icon_state = "sunglasses" - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/gray - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/glasses/sunglasses/reagent - name = "beer goggles" - icon_state = "sunhudbeer" - desc = "A pair of sunglasses outfitted with apparatus to scan reagents, as well as providing an innate understanding of liquid viscosity while in motion." - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/sunglasses/reagent/equipped(mob/user, slot) - . = ..() - if(ishuman(user) && slot == ITEM_SLOT_EYES) - ADD_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) - -/obj/item/clothing/glasses/sunglasses/reagent/dropped(mob/user) - . = ..() - REMOVE_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) - -/obj/item/clothing/glasses/sunglasses/chemical - name = "science glasses" - icon_state = "sunhudsci" - desc = "A pair of tacky purple sunglasses that allow the wearer to recognize various chemical compounds with only a glance." - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/sunglasses/garb - name = "black gar glasses" - desc = "Go beyond impossible and kick reason to the curb!" - icon_state = "garb" - inhand_icon_state = "garb" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/sunglasses/garb/supergarb - name = "black giga gar glasses" - desc = "Believe in us humans." - icon_state = "supergarb" - inhand_icon_state = "garb" - force = 12 - throwforce = 12 - -/obj/item/clothing/glasses/sunglasses/gar - name = "gar glasses" - desc = "Just who the hell do you think I am?!" - icon_state = "gar" - inhand_icon_state = "gar" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - glass_colour_type = /datum/client_colour/glass_colour/orange - -/obj/item/clothing/glasses/sunglasses/gar/supergar - name = "giga gar glasses" - desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!" - icon_state = "supergar" - inhand_icon_state = "gar" - force = 12 - throwforce = 12 - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/welding - name = "welding goggles" - desc = "Protects the eyes from bright flashes; approved by the mad scientist association." - icon_state = "welding-g" - inhand_icon_state = "welding-g" - actions_types = list(/datum/action/item_action/toggle) - flash_protect = FLASH_PROTECTION_WELDER - custom_materials = list(/datum/material/iron = 250) - tint = 2 - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - flags_cover = GLASSESCOVERSEYES - glass_colour_type = /datum/client_colour/glass_colour/gray - -/obj/item/clothing/glasses/welding/attack_self(mob/user) - weldingvisortoggle(user) - - -/obj/item/clothing/glasses/blindfold - name = "blindfold" - desc = "Covers the eyes, preventing sight." - icon_state = "blindfold" - inhand_icon_state = "blindfold" - flash_protect = FLASH_PROTECTION_WELDER - tint = 3 - darkness_view = 1 - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/glasses/blindfold/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot == ITEM_SLOT_EYES) - user.become_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/blindfold/dropped(mob/living/carbon/human/user) - ..() - user.cure_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/trickblindfold - name = "blindfold" - desc = "A see-through blindfold perfect for cheating at games like pin the stun baton on the clown." - icon_state = "trickblindfold" - inhand_icon_state = "blindfold" - -/obj/item/clothing/glasses/blindfold/white - name = "blind personnel blindfold" - desc = "Indicates that the wearer suffers from blindness." - icon_state = "blindfoldwhite" - inhand_icon_state = "blindfoldwhite" - var/colored_before = FALSE - -/obj/item/clothing/glasses/blindfold/white/equipped(mob/living/carbon/human/user, slot) - if(ishuman(user) && slot == ITEM_SLOT_EYES) - update_icon(user) - user.update_inv_glasses() //Color might have been changed by update_icon. - ..() - -/obj/item/clothing/glasses/blindfold/white/update_icon(mob/living/carbon/human/user) - if(ishuman(user) && !colored_before) - add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY) - colored_before = TRUE - -/obj/item/clothing/glasses/blindfold/white/worn_overlays(isinhands = FALSE, file2use) - . = list() - if(!isinhands && ishuman(loc) && !colored_before) - var/mob/living/carbon/human/H = loc - var/mutable_appearance/M = mutable_appearance('icons/mob/clothing/eyes.dmi', "blindfoldwhite") - M.appearance_flags |= RESET_COLOR - M.color = "#[H.eye_color]" - . += M - -/obj/item/clothing/glasses/sunglasses/big - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes." - icon_state = "bigsunglasses" - inhand_icon_state = "bigsunglasses" - -/obj/item/clothing/glasses/thermal - name = "optical thermal scanner" - desc = "Thermals in the shape of glasses." - icon_state = "thermal" - inhand_icon_state = "glasses" - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - flash_protect = FLASH_PROTECTION_SENSITIVE - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/thermal/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - thermal_overload() - -/obj/item/clothing/glasses/thermal/xray - name = "syndicate xray goggles" - desc = "A pair of xray goggles manufactured by the Syndicate." - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - -/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete - name = "chameleon thermals" - desc = "A pair of thermal optic goggles with an onboard chameleon generator." - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/thermal/syndi/Initialize() - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - -/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/glasses/thermal/monocle - name = "thermoncle" - desc = "Never before has seeing through walls felt so gentlepersonly." - icon_state = "thermoncle" - flags_1 = null //doesn't protect eyes because it's a monocle, duh - -/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! - if(user.gender == MALE) - desc = replacetext(desc, "person", "man") - else if(user.gender == FEMALE) - desc = replacetext(desc, "person", "woman") - . = ..() - desc = initial(desc) - -/obj/item/clothing/glasses/thermal/eyepatch - name = "optical thermal eyepatch" - desc = "An eyepatch with built-in thermal optics." - icon_state = "eyepatch" - inhand_icon_state = "eyepatch" - -/obj/item/clothing/glasses/cold - name = "cold goggles" - desc = "A pair of goggles meant for low temperatures." - icon_state = "cold" - inhand_icon_state = "cold" - -/obj/item/clothing/glasses/heat - name = "heat goggles" - desc = "A pair of goggles meant for high temperatures." - icon_state = "heat" - inhand_icon_state = "heat" - -/obj/item/clothing/glasses/orange - name = "orange glasses" - desc = "A sweet pair of orange shades." - icon_state = "orangeglasses" - inhand_icon_state = "orangeglasses" - glass_colour_type = /datum/client_colour/glass_colour/lightorange - -/obj/item/clothing/glasses/red - name = "red glasses" - desc = "Hey, you're looking good, senpai!" - icon_state = "redglasses" - inhand_icon_state = "redglasses" - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/godeye - name = "eye of god" - desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." - icon_state = "godeye" - inhand_icon_state = "godeye" - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - darkness_view = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - resistance_flags = LAVA_PROOF | FIRE_PROOF - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/godeye/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) - -/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, src) && W != src && W.loc == user) - if(W.icon_state == "godeye") - W.icon_state = "doublegodeye" - W.inhand_icon_state = "doublegodeye" - W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.update_inv_wear_mask() - else - to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") - qdel(src) - ..() - -/obj/item/clothing/glasses/AltClick(mob/user) - if(glass_colour_type && ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.client) - if(H.client.prefs) - if(src == H.glasses) - H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour - if(H.client.prefs.uses_glasses_colour) - to_chat(H, "You will now see glasses colors.") - else - to_chat(H, "You will no longer see glasses colors.") - H.update_glasses_color(src, 1) - else - return ..() - -/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) - var/old_colour_type = glass_colour_type - if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. - glass_colour_type = new_color_type - if(H && H.glasses == src) - if(old_colour_type) - H.remove_client_colour(old_colour_type) - if(glass_colour_type) - H.update_glasses_color(src, 1) - - -/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) - if(client && client.prefs.uses_glasses_colour && glasses_equipped) - add_client_colour(G.glass_colour_type) - else - remove_client_colour(G.glass_colour_type) - -/obj/item/clothing/glasses/debug - name = "debug glasses" - desc = "Medical, security and diagnostic hud. Alt click to toggle xray." - icon_state = "nvgmeson" - inhand_icon_state = "nvgmeson" - flags_cover = GLASSESCOVERSEYES - darkness_view = 8 - flash_protect = FLASH_PROTECTION_WELDER - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = FALSE - clothing_flags = SCAN_REAGENTS - vision_flags = SEE_TURFS - var/list/hudlist = list(DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED, DATA_HUD_SECURITY_ADVANCED) - var/xray = FALSE - -/obj/item/clothing/glasses/debug/equipped(mob/user, slot) - . = ..() - if(slot != ITEM_SLOT_EYES) - return - if(ishuman(user)) - for(var/hud in hudlist) - var/datum/atom_hud/H = GLOB.huds[hud] - H.add_hud_to(user) - ADD_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) - ADD_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) - -/obj/item/clothing/glasses/debug/dropped(mob/user) - . = ..() - REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) - REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) - if(ishuman(user)) - for(var/hud in hudlist) - var/datum/atom_hud/H = GLOB.huds[hud] - H.remove_hud_from(user) - -/obj/item/clothing/glasses/debug/AltClick(mob/user) - . = ..() - if(ishuman(user)) - if(xray) - vision_flags -= SEE_MOBS|SEE_OBJS - else - vision_flags += SEE_MOBS|SEE_OBJS - xray = !xray - var/mob/living/carbon/human/H = user - H.update_sight() +//Glasses +/obj/item/clothing/glasses + name = "glasses" + icon = 'icons/obj/clothing/glasses.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_cover = GLASSESCOVERSEYES + slot_flags = ITEM_SLOT_EYES + strip_delay = 20 + equip_delay_other = 25 + resistance_flags = NONE + custom_materials = list(/datum/material/glass = 250) + var/vision_flags = 0 + var/darkness_view = 2//Base human is 2 + var/invis_view = SEE_INVISIBLE_LIVING //admin only for now + var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis + var/lighting_alpha + var/list/icon/current = list() //the current hud icons + var/vision_correction = 0 //does wearing these glasses correct some of our vision defects? + var/glass_colour_type //colors your vision when worn + +/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/clothing/glasses/examine(mob/user) + . = ..() + if(glass_colour_type && ishuman(user)) + . += "Alt-click to toggle [p_their()] colors." + +/obj/item/clothing/glasses/visor_toggling() + ..() + if(visor_vars_to_toggle & VISOR_VISIONFLAGS) + vision_flags ^= initial(vision_flags) + if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) + darkness_view ^= initial(darkness_view) + if(visor_vars_to_toggle & VISOR_INVISVIEW) + invis_view ^= initial(invis_view) + +/obj/item/clothing/glasses/weldingvisortoggle(mob/user) + . = ..() + if(. && user) + user.update_sight() + +//called when thermal glasses are emped. +/obj/item/clothing/glasses/proc/thermal_overload() + if(ishuman(src.loc)) + var/mob/living/carbon/human/H = src.loc + var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) + if(!H.is_blind()) + if(H.glasses == src) + to_chat(H, "[src] overloads and blinds you!") + H.flash_act(visual = 1) + H.blind_eyes(3) + H.blur_eyes(5) + eyes.applyOrganDamage(5) + +/obj/item/clothing/glasses/meson + name = "optical meson scanner" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." + icon_state = "meson" + inhand_icon_state = "meson" + darkness_view = 2 + vision_flags = SEE_TURFS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/lightgreen + +/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/clothing/glasses/meson/night + name = "night vision meson scanner" + desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." + icon_state = "nvgmeson" + inhand_icon_state = "nvgmeson" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/meson/gar + name = "gar mesons" + icon_state = "garm" + inhand_icon_state = "garm" + desc = "Do the impossible, see the invisible!" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/science + name = "science goggles" + desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." + icon_state = "purple" + inhand_icon_state = "glasses" + clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles + actions_types = list(/datum/action/item_action/toggle_research_scanner) + glass_colour_type = /datum/client_colour/glass_colour/purple + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + +/obj/item/clothing/glasses/science/item_action_slot_check(slot) + if(slot == ITEM_SLOT_EYES) + return 1 + +/obj/item/clothing/glasses/night + name = "night vision goggles" + desc = "You can totally see in the dark now!" + icon_state = "night" + inhand_icon_state = "glasses" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/glasses/eyepatch + name = "eyepatch" + desc = "Yarr." + icon_state = "eyepatch" + inhand_icon_state = "eyepatch" + +/obj/item/clothing/glasses/monocle + name = "monocle" + desc = "Such a dapper eyepiece!" + icon_state = "monocle" + inhand_icon_state = "headset" // lol + +/obj/item/clothing/glasses/material + name = "optical material scanner" + desc = "Very confusing glasses." + icon_state = "material" + inhand_icon_state = "glasses" + vision_flags = SEE_OBJS + glass_colour_type = /datum/client_colour/glass_colour/lightblue + +/obj/item/clothing/glasses/material/mining + name = "optical material scanner" + desc = "Used by miners to detect ores deep within the rock." + icon_state = "material" + inhand_icon_state = "glasses" + darkness_view = 0 + +/obj/item/clothing/glasses/material/mining/gar + name = "gar material scanner" + icon_state = "garm" + inhand_icon_state = "garm" + desc = "Do the impossible, see the invisible!" + force = 10 + throwforce = 20 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + glass_colour_type = /datum/client_colour/glass_colour/lightgreen + +/obj/item/clothing/glasses/regular + name = "prescription glasses" + desc = "Made by Nerd. Co." + icon_state = "glasses" + inhand_icon_state = "glasses" + vision_correction = 1 //corrects nearsightedness + +/obj/item/clothing/glasses/regular/jamjar + name = "jamjar glasses" + desc = "Also known as Virginity Protectors." + icon_state = "jamjar_glasses" + inhand_icon_state = "jamjar_glasses" + +/obj/item/clothing/glasses/regular/hipster + name = "prescription glasses" + desc = "Made by Uncool. Co." + icon_state = "hipster_glasses" + inhand_icon_state = "hipster_glasses" + +/obj/item/clothing/glasses/regular/circle + name = "circle glasses" + desc = "Why would you wear something so controversial yet so brave?" + icon_state = "circle_glasses" + inhand_icon_state = "circle_glasses" + +//Here lies green glasses, so ugly they died. RIP + +/obj/item/clothing/glasses/sunglasses + name = "sunglasses" + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." + icon_state = "sun" + inhand_icon_state = "sunglasses" + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/gray + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/glasses/sunglasses/reagent + name = "beer goggles" + icon_state = "sunhudbeer" + desc = "A pair of sunglasses outfitted with apparatus to scan reagents, as well as providing an innate understanding of liquid viscosity while in motion." + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/sunglasses/reagent/equipped(mob/user, slot) + . = ..() + if(ishuman(user) && slot == ITEM_SLOT_EYES) + ADD_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) + +/obj/item/clothing/glasses/sunglasses/reagent/dropped(mob/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) + +/obj/item/clothing/glasses/sunglasses/chemical + name = "science glasses" + icon_state = "sunhudsci" + desc = "A pair of tacky purple sunglasses that allow the wearer to recognize various chemical compounds with only a glance." + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/sunglasses/garb + name = "black gar glasses" + desc = "Go beyond impossible and kick reason to the curb!" + icon_state = "garb" + inhand_icon_state = "garb" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/sunglasses/garb/supergarb + name = "black giga gar glasses" + desc = "Believe in us humans." + icon_state = "supergarb" + inhand_icon_state = "garb" + force = 12 + throwforce = 12 + +/obj/item/clothing/glasses/sunglasses/gar + name = "gar glasses" + desc = "Just who the hell do you think I am?!" + icon_state = "gar" + inhand_icon_state = "gar" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + glass_colour_type = /datum/client_colour/glass_colour/orange + +/obj/item/clothing/glasses/sunglasses/gar/supergar + name = "giga gar glasses" + desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!" + icon_state = "supergar" + inhand_icon_state = "gar" + force = 12 + throwforce = 12 + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/welding + name = "welding goggles" + desc = "Protects the eyes from bright flashes; approved by the mad scientist association." + icon_state = "welding-g" + inhand_icon_state = "welding-g" + actions_types = list(/datum/action/item_action/toggle) + flash_protect = FLASH_PROTECTION_WELDER + custom_materials = list(/datum/material/iron = 250) + tint = 2 + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + flags_cover = GLASSESCOVERSEYES + glass_colour_type = /datum/client_colour/glass_colour/gray + +/obj/item/clothing/glasses/welding/attack_self(mob/user) + weldingvisortoggle(user) + + +/obj/item/clothing/glasses/blindfold + name = "blindfold" + desc = "Covers the eyes, preventing sight." + icon_state = "blindfold" + inhand_icon_state = "blindfold" + flash_protect = FLASH_PROTECTION_WELDER + tint = 3 + darkness_view = 1 + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/glasses/blindfold/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot == ITEM_SLOT_EYES) + user.become_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/blindfold/dropped(mob/living/carbon/human/user) + ..() + user.cure_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/trickblindfold + name = "blindfold" + desc = "A see-through blindfold perfect for cheating at games like pin the stun baton on the clown." + icon_state = "trickblindfold" + inhand_icon_state = "blindfold" + +/obj/item/clothing/glasses/blindfold/white + name = "blind personnel blindfold" + desc = "Indicates that the wearer suffers from blindness." + icon_state = "blindfoldwhite" + inhand_icon_state = "blindfoldwhite" + var/colored_before = FALSE + +/obj/item/clothing/glasses/blindfold/white/equipped(mob/living/carbon/human/user, slot) + if(ishuman(user) && slot == ITEM_SLOT_EYES) + update_icon(user) + user.update_inv_glasses() //Color might have been changed by update_icon. + ..() + +/obj/item/clothing/glasses/blindfold/white/update_icon(mob/living/carbon/human/user) + if(ishuman(user) && !colored_before) + add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY) + colored_before = TRUE + +/obj/item/clothing/glasses/blindfold/white/worn_overlays(isinhands = FALSE, file2use) + . = list() + if(!isinhands && ishuman(loc) && !colored_before) + var/mob/living/carbon/human/H = loc + var/mutable_appearance/M = mutable_appearance('icons/mob/clothing/eyes.dmi', "blindfoldwhite") + M.appearance_flags |= RESET_COLOR + M.color = "#[H.eye_color]" + . += M + +/obj/item/clothing/glasses/sunglasses/big + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes." + icon_state = "bigsunglasses" + inhand_icon_state = "bigsunglasses" + +/obj/item/clothing/glasses/thermal + name = "optical thermal scanner" + desc = "Thermals in the shape of glasses." + icon_state = "thermal" + inhand_icon_state = "glasses" + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + flash_protect = FLASH_PROTECTION_SENSITIVE + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/thermal/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + thermal_overload() + +/obj/item/clothing/glasses/thermal/xray + name = "syndicate xray goggles" + desc = "A pair of xray goggles manufactured by the Syndicate." + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + +/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete + name = "chameleon thermals" + desc = "A pair of thermal optic goggles with an onboard chameleon generator." + + var/datum/action/item_action/chameleon/change/chameleon_action + +/obj/item/clothing/glasses/thermal/syndi/Initialize() + . = ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "Glasses" + chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chameleon_action.emp_randomise() + +/obj/item/clothing/glasses/thermal/monocle + name = "thermoncle" + desc = "Never before has seeing through walls felt so gentlepersonly." + icon_state = "thermoncle" + flags_1 = null //doesn't protect eyes because it's a monocle, duh + +/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! + if(user.gender == MALE) + desc = replacetext(desc, "person", "man") + else if(user.gender == FEMALE) + desc = replacetext(desc, "person", "woman") + . = ..() + desc = initial(desc) + +/obj/item/clothing/glasses/thermal/eyepatch + name = "optical thermal eyepatch" + desc = "An eyepatch with built-in thermal optics." + icon_state = "eyepatch" + inhand_icon_state = "eyepatch" + +/obj/item/clothing/glasses/cold + name = "cold goggles" + desc = "A pair of goggles meant for low temperatures." + icon_state = "cold" + inhand_icon_state = "cold" + +/obj/item/clothing/glasses/heat + name = "heat goggles" + desc = "A pair of goggles meant for high temperatures." + icon_state = "heat" + inhand_icon_state = "heat" + +/obj/item/clothing/glasses/orange + name = "orange glasses" + desc = "A sweet pair of orange shades." + icon_state = "orangeglasses" + inhand_icon_state = "orangeglasses" + glass_colour_type = /datum/client_colour/glass_colour/lightorange + +/obj/item/clothing/glasses/red + name = "red glasses" + desc = "Hey, you're looking good, senpai!" + icon_state = "redglasses" + inhand_icon_state = "redglasses" + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/godeye + name = "eye of god" + desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." + icon_state = "godeye" + inhand_icon_state = "godeye" + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + darkness_view = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + resistance_flags = LAVA_PROOF | FIRE_PROOF + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/godeye/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) + +/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, src) && W != src && W.loc == user) + if(W.icon_state == "godeye") + W.icon_state = "doublegodeye" + W.inhand_icon_state = "doublegodeye" + W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.update_inv_wear_mask() + else + to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") + qdel(src) + ..() + +/obj/item/clothing/glasses/AltClick(mob/user) + if(glass_colour_type && ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.client) + if(H.client.prefs) + if(src == H.glasses) + H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour + if(H.client.prefs.uses_glasses_colour) + to_chat(H, "You will now see glasses colors.") + else + to_chat(H, "You will no longer see glasses colors.") + H.update_glasses_color(src, 1) + else + return ..() + +/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) + var/old_colour_type = glass_colour_type + if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. + glass_colour_type = new_color_type + if(H && H.glasses == src) + if(old_colour_type) + H.remove_client_colour(old_colour_type) + if(glass_colour_type) + H.update_glasses_color(src, 1) + + +/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) + if(client && client.prefs.uses_glasses_colour && glasses_equipped) + add_client_colour(G.glass_colour_type) + else + remove_client_colour(G.glass_colour_type) + +/obj/item/clothing/glasses/debug + name = "debug glasses" + desc = "Medical, security and diagnostic hud. Alt click to toggle xray." + icon_state = "nvgmeson" + inhand_icon_state = "nvgmeson" + flags_cover = GLASSESCOVERSEYES + darkness_view = 8 + flash_protect = FLASH_PROTECTION_WELDER + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = FALSE + clothing_flags = SCAN_REAGENTS + vision_flags = SEE_TURFS + var/list/hudlist = list(DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED, DATA_HUD_SECURITY_ADVANCED) + var/xray = FALSE + +/obj/item/clothing/glasses/debug/equipped(mob/user, slot) + . = ..() + if(slot != ITEM_SLOT_EYES) + return + if(ishuman(user)) + for(var/hud in hudlist) + var/datum/atom_hud/H = GLOB.huds[hud] + H.add_hud_to(user) + ADD_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) + ADD_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) + +/obj/item/clothing/glasses/debug/dropped(mob/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) + REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) + if(ishuman(user)) + for(var/hud in hudlist) + var/datum/atom_hud/H = GLOB.huds[hud] + H.remove_hud_from(user) + +/obj/item/clothing/glasses/debug/AltClick(mob/user) + . = ..() + if(ishuman(user)) + if(xray) + vision_flags -= SEE_MOBS|SEE_OBJS + else + vision_flags += SEE_MOBS|SEE_OBJS + xray = !xray + var/mob/living/carbon/human/H = user + H.update_sight() diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 6f4ef1381af..6a86dd4aafe 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -1,244 +1,244 @@ -/obj/item/clothing/glasses/hud - name = "HUD" - desc = "A heads-up display that provides important info in (almost) real time." - flags_1 = null //doesn't protect eyes because it's a monocle, duh - var/hud_type = null - ///Used for topic calls. Just because you have a HUD display doesn't mean you should be able to interact with stuff. - var/hud_trait = null - - -/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) - ..() - if(slot != ITEM_SLOT_EYES) - return - if(hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) - if(hud_trait) - ADD_TRAIT(user, hud_trait, GLASSES_TRAIT) - -/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) - ..() - if(!istype(user) || user.glasses != src) - return - if(hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) - if(hud_trait) - REMOVE_TRAIT(user, hud_trait, GLASSES_TRAIT) - -/obj/item/clothing/glasses/hud/emp_act(severity) - . = ..() - if(obj_flags & EMAGGED || . & EMP_PROTECT_SELF) - return - obj_flags |= EMAGGED - desc = "[desc] The display is flickering slightly." - -/obj/item/clothing/glasses/hud/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") - desc = "[desc] The display is flickering slightly." - -/obj/item/clothing/glasses/hud/health - name = "health scanner HUD" - desc = "A heads-up display that scans the humanoids in view and provides accurate data about their health status." - icon_state = "healthhud" - hud_type = DATA_HUD_MEDICAL_ADVANCED - hud_trait = TRAIT_MEDICAL_HUD - glass_colour_type = /datum/client_colour/glass_colour/lightblue - -/obj/item/clothing/glasses/hud/health/night - name = "night vision health scanner HUD" - desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." - icon_state = "healthhudnight" - inhand_icon_state = "glasses" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/health/sunglasses - name = "medical HUDSunglasses" - desc = "Sunglasses with a medical HUD." - icon_state = "sunhudmed" - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/blue - -/obj/item/clothing/glasses/hud/diagnostic - name = "diagnostic HUD" - desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits." - icon_state = "diagnostichud" - hud_type = DATA_HUD_DIAGNOSTIC_BASIC - hud_trait = TRAIT_DIAGNOSTIC_HUD - glass_colour_type = /datum/client_colour/glass_colour/lightorange - -/obj/item/clothing/glasses/hud/diagnostic/night - name = "night vision diagnostic HUD" - desc = "A robotics diagnostic HUD fitted with a light amplifier." - icon_state = "diagnostichudnight" - inhand_icon_state = "glasses" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/diagnostic/sunglasses - name = "diagnostic sunglasses" - desc = "Sunglasses with a diagnostic HUD." - icon_state = "sunhuddiag" - inhand_icon_state = "glasses" - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - -/obj/item/clothing/glasses/hud/security - name = "security HUD" - desc = "A heads-up display that scans the humanoids in view and provides accurate data about their ID status and security records." - icon_state = "securityhud" - hud_type = DATA_HUD_SECURITY_ADVANCED - hud_trait = TRAIT_SECURITY_HUD - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/hud/security/chameleon - name = "chameleon security HUD" - desc = "A stolen security HUD integrated with Syndicate chameleon technology. Provides flash protection." - flash_protect = FLASH_PROTECTION_FLASH - - // Yes this code is the same as normal chameleon glasses, but we don't - // have multiple inheritance, okay? - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/hud/security/chameleon/Initialize() - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - -/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - - -/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch - name = "eyepatch HUD" - desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball." - icon_state = "hudpatch" - -/obj/item/clothing/glasses/hud/security/sunglasses - name = "security HUDSunglasses" - desc = "Sunglasses with a security HUD." - icon_state = "sunhudsec" - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/darkred - -/obj/item/clothing/glasses/hud/security/night - name = "night vision security HUD" - desc = "An advanced heads-up display that provides ID data and vision in complete darkness." - icon_state = "securityhudnight" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/security/sunglasses/gars - name = "\improper HUD gar glasses" - desc = "GAR glasses with a HUD." - icon_state = "gars" - inhand_icon_state = "garb" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars - name = "giga HUD gar glasses" - desc = "GIGA GAR glasses with a HUD." - icon_state = "supergars" - inhand_icon_state = "garb" - force = 12 - throwforce = 12 - -/obj/item/clothing/glasses/hud/toggle - name = "Toggle HUD" - desc = "A hud with multiple functions." - actions_types = list(/datum/action/item_action/switch_hud) - -/obj/item/clothing/glasses/hud/toggle/attack_self(mob/user) - if(!ishuman(user)) - return - var/mob/living/carbon/human/wearer = user - if (wearer.glasses != src) - return - - if (hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) - - if (hud_type == DATA_HUD_MEDICAL_ADVANCED) - hud_type = null - else if (hud_type == DATA_HUD_SECURITY_ADVANCED) - hud_type = DATA_HUD_MEDICAL_ADVANCED - else - hud_type = DATA_HUD_SECURITY_ADVANCED - - if (hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) - -/obj/item/clothing/glasses/hud/toggle/thermal - name = "thermal HUD scanner" - desc = "Thermal imaging HUD in the shape of glasses." - icon_state = "thermal" - hud_type = DATA_HUD_SECURITY_ADVANCED - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/hud/toggle/thermal/attack_self(mob/user) - ..() - switch (hud_type) - if (DATA_HUD_MEDICAL_ADVANCED) - icon_state = "meson" - change_glass_color(user, /datum/client_colour/glass_colour/green) - if (DATA_HUD_SECURITY_ADVANCED) - icon_state = "thermal" - change_glass_color(user, /datum/client_colour/glass_colour/red) - else - icon_state = "purple" - change_glass_color(user, /datum/client_colour/glass_colour/purple) - user.update_inv_glasses() - -/obj/item/clothing/glasses/hud/toggle/thermal/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - thermal_overload() - -/obj/item/clothing/glasses/hud/spacecop - name = "police aviators" - desc = "For thinking you look cool while brutalizing protestors and minorities." - icon_state = "bigsunglasses" - hud_type = ANTAG_HUD_GANGSTER - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/gray - - -/obj/item/clothing/glasses/hud/spacecop/hidden // for the undercover cop - name = "sunglasses" - desc = "These sunglasses are special, and let you view potential criminals." - icon_state = "sun" - inhand_icon_state = "sunglasses" - +/obj/item/clothing/glasses/hud + name = "HUD" + desc = "A heads-up display that provides important info in (almost) real time." + flags_1 = null //doesn't protect eyes because it's a monocle, duh + var/hud_type = null + ///Used for topic calls. Just because you have a HUD display doesn't mean you should be able to interact with stuff. + var/hud_trait = null + + +/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) + ..() + if(slot != ITEM_SLOT_EYES) + return + if(hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.add_hud_to(user) + if(hud_trait) + ADD_TRAIT(user, hud_trait, GLASSES_TRAIT) + +/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) + ..() + if(!istype(user) || user.glasses != src) + return + if(hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + if(hud_trait) + REMOVE_TRAIT(user, hud_trait, GLASSES_TRAIT) + +/obj/item/clothing/glasses/hud/emp_act(severity) + . = ..() + if(obj_flags & EMAGGED || . & EMP_PROTECT_SELF) + return + obj_flags |= EMAGGED + desc = "[desc] The display is flickering slightly." + +/obj/item/clothing/glasses/hud/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") + desc = "[desc] The display is flickering slightly." + +/obj/item/clothing/glasses/hud/health + name = "health scanner HUD" + desc = "A heads-up display that scans the humanoids in view and provides accurate data about their health status." + icon_state = "healthhud" + hud_type = DATA_HUD_MEDICAL_ADVANCED + hud_trait = TRAIT_MEDICAL_HUD + glass_colour_type = /datum/client_colour/glass_colour/lightblue + +/obj/item/clothing/glasses/hud/health/night + name = "night vision health scanner HUD" + desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." + icon_state = "healthhudnight" + inhand_icon_state = "glasses" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/health/sunglasses + name = "medical HUDSunglasses" + desc = "Sunglasses with a medical HUD." + icon_state = "sunhudmed" + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/blue + +/obj/item/clothing/glasses/hud/diagnostic + name = "diagnostic HUD" + desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits." + icon_state = "diagnostichud" + hud_type = DATA_HUD_DIAGNOSTIC_BASIC + hud_trait = TRAIT_DIAGNOSTIC_HUD + glass_colour_type = /datum/client_colour/glass_colour/lightorange + +/obj/item/clothing/glasses/hud/diagnostic/night + name = "night vision diagnostic HUD" + desc = "A robotics diagnostic HUD fitted with a light amplifier." + icon_state = "diagnostichudnight" + inhand_icon_state = "glasses" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/diagnostic/sunglasses + name = "diagnostic sunglasses" + desc = "Sunglasses with a diagnostic HUD." + icon_state = "sunhuddiag" + inhand_icon_state = "glasses" + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + +/obj/item/clothing/glasses/hud/security + name = "security HUD" + desc = "A heads-up display that scans the humanoids in view and provides accurate data about their ID status and security records." + icon_state = "securityhud" + hud_type = DATA_HUD_SECURITY_ADVANCED + hud_trait = TRAIT_SECURITY_HUD + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/hud/security/chameleon + name = "chameleon security HUD" + desc = "A stolen security HUD integrated with Syndicate chameleon technology. Provides flash protection." + flash_protect = FLASH_PROTECTION_FLASH + + // Yes this code is the same as normal chameleon glasses, but we don't + // have multiple inheritance, okay? + var/datum/action/item_action/chameleon/change/chameleon_action + +/obj/item/clothing/glasses/hud/security/chameleon/Initialize() + . = ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "Glasses" + chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chameleon_action.emp_randomise() + + +/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch + name = "eyepatch HUD" + desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball." + icon_state = "hudpatch" + +/obj/item/clothing/glasses/hud/security/sunglasses + name = "security HUDSunglasses" + desc = "Sunglasses with a security HUD." + icon_state = "sunhudsec" + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/darkred + +/obj/item/clothing/glasses/hud/security/night + name = "night vision security HUD" + desc = "An advanced heads-up display that provides ID data and vision in complete darkness." + icon_state = "securityhudnight" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/security/sunglasses/gars + name = "\improper HUD gar glasses" + desc = "GAR glasses with a HUD." + icon_state = "gars" + inhand_icon_state = "garb" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars + name = "giga HUD gar glasses" + desc = "GIGA GAR glasses with a HUD." + icon_state = "supergars" + inhand_icon_state = "garb" + force = 12 + throwforce = 12 + +/obj/item/clothing/glasses/hud/toggle + name = "Toggle HUD" + desc = "A hud with multiple functions." + actions_types = list(/datum/action/item_action/switch_hud) + +/obj/item/clothing/glasses/hud/toggle/attack_self(mob/user) + if(!ishuman(user)) + return + var/mob/living/carbon/human/wearer = user + if (wearer.glasses != src) + return + + if (hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + + if (hud_type == DATA_HUD_MEDICAL_ADVANCED) + hud_type = null + else if (hud_type == DATA_HUD_SECURITY_ADVANCED) + hud_type = DATA_HUD_MEDICAL_ADVANCED + else + hud_type = DATA_HUD_SECURITY_ADVANCED + + if (hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.add_hud_to(user) + +/obj/item/clothing/glasses/hud/toggle/thermal + name = "thermal HUD scanner" + desc = "Thermal imaging HUD in the shape of glasses." + icon_state = "thermal" + hud_type = DATA_HUD_SECURITY_ADVANCED + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/hud/toggle/thermal/attack_self(mob/user) + ..() + switch (hud_type) + if (DATA_HUD_MEDICAL_ADVANCED) + icon_state = "meson" + change_glass_color(user, /datum/client_colour/glass_colour/green) + if (DATA_HUD_SECURITY_ADVANCED) + icon_state = "thermal" + change_glass_color(user, /datum/client_colour/glass_colour/red) + else + icon_state = "purple" + change_glass_color(user, /datum/client_colour/glass_colour/purple) + user.update_inv_glasses() + +/obj/item/clothing/glasses/hud/toggle/thermal/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + thermal_overload() + +/obj/item/clothing/glasses/hud/spacecop + name = "police aviators" + desc = "For thinking you look cool while brutalizing protestors and minorities." + icon_state = "bigsunglasses" + hud_type = ANTAG_HUD_GANGSTER + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/gray + + +/obj/item/clothing/glasses/hud/spacecop/hidden // for the undercover cop + name = "sunglasses" + desc = "These sunglasses are special, and let you view potential criminals." + icon_state = "sun" + inhand_icon_state = "sunglasses" + diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm index 7fe62737085..104a0172d1a 100644 --- a/code/modules/clothing/gloves/_gloves.dm +++ b/code/modules/clothing/gloves/_gloves.dm @@ -1,43 +1,43 @@ -/obj/item/clothing/gloves - name = "gloves" - gender = PLURAL //Carn: for grammarically correct text-parsing - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/clothing/gloves.dmi' - siemens_coefficient = 0.5 - body_parts_covered = HANDS - slot_flags = ITEM_SLOT_GLOVES - attack_verb = list("challenged") - var/transfer_prints = FALSE - strip_delay = 20 - equip_delay_other = 40 - -/obj/item/clothing/gloves/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/item/clothing/gloves/proc/clean_blood(datum/source, strength) - if(strength < CLEAN_STRENGTH_BLOOD) - return - transfer_blood = 0 - -/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user) - user.visible_message("\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!") - return OXYLOSS - -/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands") - -/obj/item/clothing/gloves/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_gloves() - -// Called just before an attack_hand(), in mob/UnarmedAttack() -/obj/item/clothing/gloves/proc/Touch(atom/A, proximity) - return 0 // return 1 to cancel attack_hand() +/obj/item/clothing/gloves + name = "gloves" + gender = PLURAL //Carn: for grammarically correct text-parsing + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/clothing/gloves.dmi' + siemens_coefficient = 0.5 + body_parts_covered = HANDS + slot_flags = ITEM_SLOT_GLOVES + attack_verb = list("challenged") + var/transfer_prints = FALSE + strip_delay = 20 + equip_delay_other = 40 + +/obj/item/clothing/gloves/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/item/clothing/gloves/proc/clean_blood(datum/source, strength) + if(strength < CLEAN_STRENGTH_BLOOD) + return + transfer_blood = 0 + +/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user) + user.visible_message("\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!") + return OXYLOSS + +/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands") + +/obj/item/clothing/gloves/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_gloves() + +// Called just before an attack_hand(), in mob/UnarmedAttack() +/obj/item/clothing/gloves/proc/Touch(atom/A, proximity) + return 0 // return 1 to cancel attack_hand() diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm index ea060c0295f..29fade37474 100644 --- a/code/modules/clothing/gloves/boxing.dm +++ b/code/modules/clothing/gloves/boxing.dm @@ -1,19 +1,19 @@ -/obj/item/clothing/gloves/boxing - name = "boxing gloves" - desc = "Because you really needed another excuse to punch your crewmates." - icon_state = "boxing" - inhand_icon_state = "boxing" - equip_delay_other = 60 - species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion - -/obj/item/clothing/gloves/boxing/green - icon_state = "boxinggreen" - inhand_icon_state = "boxinggreen" - -/obj/item/clothing/gloves/boxing/blue - icon_state = "boxingblue" - inhand_icon_state = "boxingblue" - -/obj/item/clothing/gloves/boxing/yellow - icon_state = "boxingyellow" - inhand_icon_state = "boxingyellow" +/obj/item/clothing/gloves/boxing + name = "boxing gloves" + desc = "Because you really needed another excuse to punch your crewmates." + icon_state = "boxing" + inhand_icon_state = "boxing" + equip_delay_other = 60 + species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion + +/obj/item/clothing/gloves/boxing/green + icon_state = "boxinggreen" + inhand_icon_state = "boxinggreen" + +/obj/item/clothing/gloves/boxing/blue + icon_state = "boxingblue" + inhand_icon_state = "boxingblue" + +/obj/item/clothing/gloves/boxing/yellow + icon_state = "boxingyellow" + inhand_icon_state = "boxingyellow" diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm index 6901a0d12f4..49957b6b3e8 100644 --- a/code/modules/clothing/gloves/color.dm +++ b/code/modules/clothing/gloves/color.dm @@ -1,250 +1,250 @@ -/obj/item/clothing/gloves/color - dying_key = DYE_REGISTRY_GLOVES - -/obj/item/clothing/gloves/color/yellow - desc = "These gloves provide protection against electric shock." - name = "insulated gloves" - icon_state = "yellow" - inhand_icon_state = "ygloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - resistance_flags = NONE - custom_price = 1200 - custom_premium_price = 1200 - -/obj/item/toy/sprayoncan - name = "spray-on insulation applicator" - desc = "What is the number one problem facing our station today?" - icon = 'icons/obj/clothing/gloves.dmi' - icon_state = "sprayoncan" - -/obj/item/toy/sprayoncan/afterattack(atom/target, mob/living/carbon/user, proximity) - if(iscarbon(target) && proximity) - var/mob/living/carbon/C = target - var/mob/living/carbon/U = user - var/success = C.equip_to_slot_if_possible(new /obj/item/clothing/gloves/color/yellow/sprayon, ITEM_SLOT_GLOVES, TRUE, TRUE) - if(success) - if(C == user) - C.visible_message("[U] sprays their hands with glittery rubber!") - else - C.visible_message("[U] sprays glittery rubber on the hands of [C]!") - else - C.visible_message("The rubber fails to stick to [C]'s hands!") - - qdel(src) - -/obj/item/clothing/gloves/color/yellow/sprayon - desc = "How're you gonna get 'em off, nerd?" - name = "spray-on insulated gloves" - icon_state = "sprayon" - inhand_icon_state = "sprayon" - permeability_coefficient = 0 - resistance_flags = ACID_PROOF - var/shocks_remaining = 10 - -/obj/item/clothing/gloves/color/yellow/sprayon/Initialize() - .=..() - ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) - -/obj/item/clothing/gloves/color/yellow/sprayon/equipped(mob/user, slot) - . = ..() - RegisterSignal(user, COMSIG_LIVING_SHOCK_PREVENTED, .proc/Shocked) - -/obj/item/clothing/gloves/color/yellow/sprayon/proc/Shocked() - shocks_remaining-- - if(shocks_remaining < 0) - qdel(src) //if we run out of uses, the gloves crumble away into nothing, just like my dreams after working with .dm - -/obj/item/clothing/gloves/color/yellow/sprayon/dropped() - .=..() - qdel(src) //loose nodrop items bad - -/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap - desc = "These gloves are cheap knockoffs of the coveted ones - no way this can end badly." - name = "budget insulated gloves" - icon_state = "yellow" - inhand_icon_state = "ygloves" - siemens_coefficient = 1 //Set to a default of 1, gets overridden in Initialize() - permeability_coefficient = 0.05 - resistance_flags = NONE - -/obj/item/clothing/gloves/color/fyellow/Initialize() - . = ..() - siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) - -/obj/item/clothing/gloves/color/fyellow/old - desc = "Old and worn out insulated gloves, hopefully they still work." - name = "worn out insulated gloves" - -/obj/item/clothing/gloves/color/fyellow/old/Initialize() - . = ..() - siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) - -/obj/item/clothing/gloves/color/black - desc = "These gloves are fire-resistant." - name = "black gloves" - icon_state = "black" - inhand_icon_state = "blackgloves" - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - var/can_be_cut = TRUE - -/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WIRECUTTER) - if(can_be_cut && icon_state == initial(icon_state))//only if not dyed - to_chat(user, "You snip the fingertips off of [src].") - I.play_tool_sound(src) - new /obj/item/clothing/gloves/fingerless(drop_location()) - qdel(src) - ..() - -/obj/item/clothing/gloves/color/orange - name = "orange gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "orange" - inhand_icon_state = "orangegloves" - -/obj/item/clothing/gloves/color/red - name = "red gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "red" - inhand_icon_state = "redgloves" - - -/obj/item/clothing/gloves/color/red/insulated - name = "insulated gloves" - desc = "These gloves provide protection against electric shock." - siemens_coefficient = 0 - permeability_coefficient = 0.05 - resistance_flags = NONE - -/obj/item/clothing/gloves/color/rainbow - name = "rainbow gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "rainbow" - inhand_icon_state = "rainbowgloves" - -/obj/item/clothing/gloves/color/blue - name = "blue gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "blue" - inhand_icon_state = "bluegloves" - -/obj/item/clothing/gloves/color/purple - name = "purple gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "purple" - inhand_icon_state = "purplegloves" - -/obj/item/clothing/gloves/color/green - name = "green gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "green" - inhand_icon_state = "greengloves" - -/obj/item/clothing/gloves/color/grey - name = "grey gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "gray" - inhand_icon_state = "graygloves" - -/obj/item/clothing/gloves/color/light_brown - name = "light brown gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "lightbrown" - inhand_icon_state = "lightbrowngloves" - -/obj/item/clothing/gloves/color/brown - name = "brown gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "brown" - inhand_icon_state = "browngloves" - -/obj/item/clothing/gloves/color/captain - desc = "Regal blue gloves, with a nice gold trim, a diamond anti-shock coating, and an integrated thermal barrier. Swanky." - name = "captain's gloves" - icon_state = "captain" - inhand_icon_state = "egloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - strip_delay = 60 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50) - -/obj/item/clothing/gloves/color/latex - name = "latex gloves" - desc = "Cheap sterile gloves made from latex. Transfers minor paramedic knowledge to the user via budget nanochips." - icon_state = "latex" - inhand_icon_state = "latex" - siemens_coefficient = 0.3 - permeability_coefficient = 0.01 - transfer_prints = TRUE - resistance_flags = NONE - var/carrytrait = TRAIT_QUICK_CARRY - -/obj/item/clothing/gloves/color/latex/equipped(mob/user, slot) - ..() - if(slot == ITEM_SLOT_GLOVES) - ADD_TRAIT(user, carrytrait, CLOTHING_TRAIT) - -/obj/item/clothing/gloves/color/latex/dropped(mob/user) - ..() - REMOVE_TRAIT(user, carrytrait, CLOTHING_TRAIT) - -/obj/item/clothing/gloves/color/latex/nitrile - name = "nitrile gloves" - desc = "Pricy sterile gloves that are thicker than latex. Transfers intimate paramedic knowledge into the user via nanochips." - icon_state = "nitrile" - inhand_icon_state = "nitrilegloves" - transfer_prints = FALSE - carrytrait = TRAIT_QUICKER_CARRY - -/obj/item/clothing/gloves/color/latex/nitrile/infiltrator - name = "infiltrator gloves" - desc = "Specialized combat gloves for carrying people around. Transfers tactical kidnapping knowledge into the user via nanochips." - icon_state = "infiltrator" - inhand_icon_state = "infiltrator" - siemens_coefficient = 0 - permeability_coefficient = 0.3 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/gloves/color/latex/engineering - name = "tinker's gloves" - desc = "Overdesigned engineering gloves that have automated construction subrutines dialed in, allowing for faster construction while worn." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_gauntlets" - inhand_icon_state = "clockwork_gauntlets" - siemens_coefficient = 0.8 - permeability_coefficient = 0.3 - carrytrait = TRAIT_QUICK_BUILD - custom_materials = list(/datum/material/iron=2000, /datum/material/silver=1500, /datum/material/gold = 1000) - -/obj/item/clothing/gloves/color/white - name = "white gloves" - desc = "These look pretty fancy." - icon_state = "white" - inhand_icon_state = "wgloves" - custom_price = 200 - -/obj/effect/spawner/lootdrop/gloves - name = "random gloves" - desc = "These gloves are supposed to be a random color..." - icon = 'icons/obj/clothing/gloves.dmi' - icon_state = "random_gloves" - loot = list( - /obj/item/clothing/gloves/color/orange = 1, - /obj/item/clothing/gloves/color/red = 1, - /obj/item/clothing/gloves/color/blue = 1, - /obj/item/clothing/gloves/color/purple = 1, - /obj/item/clothing/gloves/color/green = 1, - /obj/item/clothing/gloves/color/grey = 1, - /obj/item/clothing/gloves/color/light_brown = 1, - /obj/item/clothing/gloves/color/brown = 1, - /obj/item/clothing/gloves/color/white = 1, - /obj/item/clothing/gloves/color/rainbow = 1) +/obj/item/clothing/gloves/color + dying_key = DYE_REGISTRY_GLOVES + +/obj/item/clothing/gloves/color/yellow + desc = "These gloves provide protection against electric shock." + name = "insulated gloves" + icon_state = "yellow" + inhand_icon_state = "ygloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + resistance_flags = NONE + custom_price = 1200 + custom_premium_price = 1200 + +/obj/item/toy/sprayoncan + name = "spray-on insulation applicator" + desc = "What is the number one problem facing our station today?" + icon = 'icons/obj/clothing/gloves.dmi' + icon_state = "sprayoncan" + +/obj/item/toy/sprayoncan/afterattack(atom/target, mob/living/carbon/user, proximity) + if(iscarbon(target) && proximity) + var/mob/living/carbon/C = target + var/mob/living/carbon/U = user + var/success = C.equip_to_slot_if_possible(new /obj/item/clothing/gloves/color/yellow/sprayon, ITEM_SLOT_GLOVES, TRUE, TRUE) + if(success) + if(C == user) + C.visible_message("[U] sprays their hands with glittery rubber!") + else + C.visible_message("[U] sprays glittery rubber on the hands of [C]!") + else + C.visible_message("The rubber fails to stick to [C]'s hands!") + + qdel(src) + +/obj/item/clothing/gloves/color/yellow/sprayon + desc = "How're you gonna get 'em off, nerd?" + name = "spray-on insulated gloves" + icon_state = "sprayon" + inhand_icon_state = "sprayon" + permeability_coefficient = 0 + resistance_flags = ACID_PROOF + var/shocks_remaining = 10 + +/obj/item/clothing/gloves/color/yellow/sprayon/Initialize() + .=..() + ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) + +/obj/item/clothing/gloves/color/yellow/sprayon/equipped(mob/user, slot) + . = ..() + RegisterSignal(user, COMSIG_LIVING_SHOCK_PREVENTED, .proc/Shocked) + +/obj/item/clothing/gloves/color/yellow/sprayon/proc/Shocked() + shocks_remaining-- + if(shocks_remaining < 0) + qdel(src) //if we run out of uses, the gloves crumble away into nothing, just like my dreams after working with .dm + +/obj/item/clothing/gloves/color/yellow/sprayon/dropped() + .=..() + qdel(src) //loose nodrop items bad + +/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap + desc = "These gloves are cheap knockoffs of the coveted ones - no way this can end badly." + name = "budget insulated gloves" + icon_state = "yellow" + inhand_icon_state = "ygloves" + siemens_coefficient = 1 //Set to a default of 1, gets overridden in Initialize() + permeability_coefficient = 0.05 + resistance_flags = NONE + +/obj/item/clothing/gloves/color/fyellow/Initialize() + . = ..() + siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) + +/obj/item/clothing/gloves/color/fyellow/old + desc = "Old and worn out insulated gloves, hopefully they still work." + name = "worn out insulated gloves" + +/obj/item/clothing/gloves/color/fyellow/old/Initialize() + . = ..() + siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) + +/obj/item/clothing/gloves/color/black + desc = "These gloves are fire-resistant." + name = "black gloves" + icon_state = "black" + inhand_icon_state = "blackgloves" + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + var/can_be_cut = TRUE + +/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WIRECUTTER) + if(can_be_cut && icon_state == initial(icon_state))//only if not dyed + to_chat(user, "You snip the fingertips off of [src].") + I.play_tool_sound(src) + new /obj/item/clothing/gloves/fingerless(drop_location()) + qdel(src) + ..() + +/obj/item/clothing/gloves/color/orange + name = "orange gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "orange" + inhand_icon_state = "orangegloves" + +/obj/item/clothing/gloves/color/red + name = "red gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "red" + inhand_icon_state = "redgloves" + + +/obj/item/clothing/gloves/color/red/insulated + name = "insulated gloves" + desc = "These gloves provide protection against electric shock." + siemens_coefficient = 0 + permeability_coefficient = 0.05 + resistance_flags = NONE + +/obj/item/clothing/gloves/color/rainbow + name = "rainbow gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "rainbow" + inhand_icon_state = "rainbowgloves" + +/obj/item/clothing/gloves/color/blue + name = "blue gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "blue" + inhand_icon_state = "bluegloves" + +/obj/item/clothing/gloves/color/purple + name = "purple gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "purple" + inhand_icon_state = "purplegloves" + +/obj/item/clothing/gloves/color/green + name = "green gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "green" + inhand_icon_state = "greengloves" + +/obj/item/clothing/gloves/color/grey + name = "grey gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "gray" + inhand_icon_state = "graygloves" + +/obj/item/clothing/gloves/color/light_brown + name = "light brown gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "lightbrown" + inhand_icon_state = "lightbrowngloves" + +/obj/item/clothing/gloves/color/brown + name = "brown gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "brown" + inhand_icon_state = "browngloves" + +/obj/item/clothing/gloves/color/captain + desc = "Regal blue gloves, with a nice gold trim, a diamond anti-shock coating, and an integrated thermal barrier. Swanky." + name = "captain's gloves" + icon_state = "captain" + inhand_icon_state = "egloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + strip_delay = 60 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50) + +/obj/item/clothing/gloves/color/latex + name = "latex gloves" + desc = "Cheap sterile gloves made from latex. Transfers minor paramedic knowledge to the user via budget nanochips." + icon_state = "latex" + inhand_icon_state = "latex" + siemens_coefficient = 0.3 + permeability_coefficient = 0.01 + transfer_prints = TRUE + resistance_flags = NONE + var/carrytrait = TRAIT_QUICK_CARRY + +/obj/item/clothing/gloves/color/latex/equipped(mob/user, slot) + ..() + if(slot == ITEM_SLOT_GLOVES) + ADD_TRAIT(user, carrytrait, CLOTHING_TRAIT) + +/obj/item/clothing/gloves/color/latex/dropped(mob/user) + ..() + REMOVE_TRAIT(user, carrytrait, CLOTHING_TRAIT) + +/obj/item/clothing/gloves/color/latex/nitrile + name = "nitrile gloves" + desc = "Pricy sterile gloves that are thicker than latex. Transfers intimate paramedic knowledge into the user via nanochips." + icon_state = "nitrile" + inhand_icon_state = "nitrilegloves" + transfer_prints = FALSE + carrytrait = TRAIT_QUICKER_CARRY + +/obj/item/clothing/gloves/color/latex/nitrile/infiltrator + name = "infiltrator gloves" + desc = "Specialized combat gloves for carrying people around. Transfers tactical kidnapping knowledge into the user via nanochips." + icon_state = "infiltrator" + inhand_icon_state = "infiltrator" + siemens_coefficient = 0 + permeability_coefficient = 0.3 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/gloves/color/latex/engineering + name = "tinker's gloves" + desc = "Overdesigned engineering gloves that have automated construction subrutines dialed in, allowing for faster construction while worn." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_gauntlets" + inhand_icon_state = "clockwork_gauntlets" + siemens_coefficient = 0.8 + permeability_coefficient = 0.3 + carrytrait = TRAIT_QUICK_BUILD + custom_materials = list(/datum/material/iron=2000, /datum/material/silver=1500, /datum/material/gold = 1000) + +/obj/item/clothing/gloves/color/white + name = "white gloves" + desc = "These look pretty fancy." + icon_state = "white" + inhand_icon_state = "wgloves" + custom_price = 200 + +/obj/effect/spawner/lootdrop/gloves + name = "random gloves" + desc = "These gloves are supposed to be a random color..." + icon = 'icons/obj/clothing/gloves.dmi' + icon_state = "random_gloves" + loot = list( + /obj/item/clothing/gloves/color/orange = 1, + /obj/item/clothing/gloves/color/red = 1, + /obj/item/clothing/gloves/color/blue = 1, + /obj/item/clothing/gloves/color/purple = 1, + /obj/item/clothing/gloves/color/green = 1, + /obj/item/clothing/gloves/color/grey = 1, + /obj/item/clothing/gloves/color/light_brown = 1, + /obj/item/clothing/gloves/color/brown = 1, + /obj/item/clothing/gloves/color/white = 1, + /obj/item/clothing/gloves/color/rainbow = 1) diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index 3536687c864..36d04a288e1 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -1,147 +1,147 @@ - -/obj/item/clothing/gloves/fingerless - name = "fingerless gloves" - desc = "Plain black gloves without fingertips for the hard working." - icon_state = "fingerless" - inhand_icon_state = "fingerless" - transfer_prints = TRUE - strip_delay = 40 - equip_delay_other = 20 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - custom_price = 75 - undyeable = TRUE - -/obj/item/clothing/gloves/botanic_leather - name = "botanist's leather gloves" - desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm." - icon_state = "leather" - inhand_icon_state = "ggloves" - permeability_coefficient = 0.9 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) - -/obj/item/clothing/gloves/combat - name = "combat gloves" - desc = "These tactical gloves are fireproof and electrically insulated." - icon_state = "black" - inhand_icon_state = "blackgloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - strip_delay = 80 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - -/obj/item/clothing/gloves/bracer - name = "bone bracers" - desc = "For when you're expecting to get slapped on the wrist. Offers modest protection to your arms." - icon_state = "bracers" - inhand_icon_state = "bracers" - transfer_prints = TRUE - strip_delay = 40 - equip_delay_other = 20 - body_parts_covered = ARMS - cold_protection = ARMS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - armor = list("melee" = 15, "bullet" = 25, "laser" = 15, "energy" = 15, "bomb" = 20, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/gloves/rapid - name = "Gloves of the North Star" - desc = "Just looking at these fills you with an urge to beat the shit out of people." - icon_state = "rapid" - inhand_icon_state = "rapid" - transfer_prints = TRUE - -/obj/item/clothing/gloves/rapid/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/punchcooldown) - - -/obj/item/clothing/gloves/color/plasmaman - desc = "Covers up those scandalous boney hands." - name = "plasma envirogloves" - icon_state = "plasmaman" - inhand_icon_state = "plasmaman" - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - permeability_coefficient = 0.05 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) - -/obj/item/clothing/gloves/color/plasmaman/black - name = "black envirogloves" - icon_state = "blackplasma" - inhand_icon_state = "blackplasma" - -/obj/item/clothing/gloves/color/plasmaman/white - name = "white envirogloves" - icon_state = "whiteplasma" - inhand_icon_state = "whiteplasma" - -/obj/item/clothing/gloves/color/plasmaman/robot - name = "roboticist envirogloves" - icon_state = "robotplasma" - inhand_icon_state = "robotplasma" - -/obj/item/clothing/gloves/color/plasmaman/janny - name = "janitor envirogloves" - icon_state = "jannyplasma" - inhand_icon_state = "jannyplasma" - -/obj/item/clothing/gloves/color/plasmaman/cargo - name = "cargo envirogloves" - icon_state = "cargoplasma" - inhand_icon_state = "cargoplasma" - -/obj/item/clothing/gloves/color/plasmaman/engineer - name = "engineering envirogloves" - icon_state = "engieplasma" - inhand_icon_state = "engieplasma" - siemens_coefficient = 0 - -/obj/item/clothing/gloves/color/plasmaman/atmos - name = "atmos envirogloves" - icon_state = "atmosplasma" - inhand_icon_state = "atmosplasma" - siemens_coefficient = 0 - -/obj/item/clothing/gloves/color/plasmaman/explorer - name = "explorer envirogloves" - icon_state = "explorerplasma" - inhand_icon_state = "explorerplasma" - -/obj/item/clothing/gloves/color/botanic_leather/plasmaman - name = "botany envirogloves" - desc = "Covers up those scandalous boney hands." - icon_state = "botanyplasma" - inhand_icon_state = "botanyplasma" - permeability_coefficient = 0.05 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) - -/obj/item/clothing/gloves/color/plasmaman/prototype - name = "prototype envirogloves" - icon_state = "protoplasma" - inhand_icon_state = "protoplasma" - -/obj/item/clothing/gloves/color/plasmaman/clown - name = "clown envirogloves" - icon_state = "clownplasma" - inhand_icon_state = "clownplasma" - -/obj/item/clothing/gloves/combat/wizard - name = "enchanted gloves" - desc = "These gloves have been enchanted with a spell that makes them electrically insulated and fireproof." - icon_state = "wizard" - inhand_icon_state = "purplegloves" + +/obj/item/clothing/gloves/fingerless + name = "fingerless gloves" + desc = "Plain black gloves without fingertips for the hard working." + icon_state = "fingerless" + inhand_icon_state = "fingerless" + transfer_prints = TRUE + strip_delay = 40 + equip_delay_other = 20 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + custom_price = 75 + undyeable = TRUE + +/obj/item/clothing/gloves/botanic_leather + name = "botanist's leather gloves" + desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm." + icon_state = "leather" + inhand_icon_state = "ggloves" + permeability_coefficient = 0.9 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) + +/obj/item/clothing/gloves/combat + name = "combat gloves" + desc = "These tactical gloves are fireproof and electrically insulated." + icon_state = "black" + inhand_icon_state = "blackgloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + strip_delay = 80 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + +/obj/item/clothing/gloves/bracer + name = "bone bracers" + desc = "For when you're expecting to get slapped on the wrist. Offers modest protection to your arms." + icon_state = "bracers" + inhand_icon_state = "bracers" + transfer_prints = TRUE + strip_delay = 40 + equip_delay_other = 20 + body_parts_covered = ARMS + cold_protection = ARMS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + armor = list("melee" = 15, "bullet" = 25, "laser" = 15, "energy" = 15, "bomb" = 20, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/gloves/rapid + name = "Gloves of the North Star" + desc = "Just looking at these fills you with an urge to beat the shit out of people." + icon_state = "rapid" + inhand_icon_state = "rapid" + transfer_prints = TRUE + +/obj/item/clothing/gloves/rapid/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/punchcooldown) + + +/obj/item/clothing/gloves/color/plasmaman + desc = "Covers up those scandalous boney hands." + name = "plasma envirogloves" + icon_state = "plasmaman" + inhand_icon_state = "plasmaman" + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + permeability_coefficient = 0.05 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) + +/obj/item/clothing/gloves/color/plasmaman/black + name = "black envirogloves" + icon_state = "blackplasma" + inhand_icon_state = "blackplasma" + +/obj/item/clothing/gloves/color/plasmaman/white + name = "white envirogloves" + icon_state = "whiteplasma" + inhand_icon_state = "whiteplasma" + +/obj/item/clothing/gloves/color/plasmaman/robot + name = "roboticist envirogloves" + icon_state = "robotplasma" + inhand_icon_state = "robotplasma" + +/obj/item/clothing/gloves/color/plasmaman/janny + name = "janitor envirogloves" + icon_state = "jannyplasma" + inhand_icon_state = "jannyplasma" + +/obj/item/clothing/gloves/color/plasmaman/cargo + name = "cargo envirogloves" + icon_state = "cargoplasma" + inhand_icon_state = "cargoplasma" + +/obj/item/clothing/gloves/color/plasmaman/engineer + name = "engineering envirogloves" + icon_state = "engieplasma" + inhand_icon_state = "engieplasma" + siemens_coefficient = 0 + +/obj/item/clothing/gloves/color/plasmaman/atmos + name = "atmos envirogloves" + icon_state = "atmosplasma" + inhand_icon_state = "atmosplasma" + siemens_coefficient = 0 + +/obj/item/clothing/gloves/color/plasmaman/explorer + name = "explorer envirogloves" + icon_state = "explorerplasma" + inhand_icon_state = "explorerplasma" + +/obj/item/clothing/gloves/color/botanic_leather/plasmaman + name = "botany envirogloves" + desc = "Covers up those scandalous boney hands." + icon_state = "botanyplasma" + inhand_icon_state = "botanyplasma" + permeability_coefficient = 0.05 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) + +/obj/item/clothing/gloves/color/plasmaman/prototype + name = "prototype envirogloves" + icon_state = "protoplasma" + inhand_icon_state = "protoplasma" + +/obj/item/clothing/gloves/color/plasmaman/clown + name = "clown envirogloves" + icon_state = "clownplasma" + inhand_icon_state = "clownplasma" + +/obj/item/clothing/gloves/combat/wizard + name = "enchanted gloves" + desc = "These gloves have been enchanted with a spell that makes them electrically insulated and fireproof." + icon_state = "wizard" + inhand_icon_state = "purplegloves" diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index 726ecea4677..d57007de246 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -1,76 +1,76 @@ -/obj/item/clothing/head - name = BODY_ZONE_HEAD - icon = 'icons/obj/clothing/hats.dmi' - icon_state = "top_hat" - inhand_icon_state = "that" - body_parts_covered = HEAD - slot_flags = ITEM_SLOT_HEAD - var/blockTracking = 0 //For AI tracking - var/can_toggle = null - dynamic_hair_suffix = "+generic" - -/obj/item/clothing/head/Initialize() - . = ..() - if(ishuman(loc) && dynamic_hair_suffix) - var/mob/living/carbon/human/H = loc - H.update_hair() - -///Special throw_impact for hats to frisbee hats at people to place them on their heads/attempt to de-hat them. -/obj/item/clothing/head/throw_impact(atom/hit_atom, datum/thrownthing/thrownthing) - . = ..() - ///if the thrown object's target zone isn't the head - if(thrownthing.target_zone != BODY_ZONE_HEAD) - return - ///ignore any hats with the tinfoil counter-measure enabled - if(clothing_flags & ANTI_TINFOIL_MANEUVER) - return - ///if the hat happens to be capable of holding contents and has something in it. mostly to prevent super cheesy stuff like stuffing a mini-bomb in a hat and throwing it - if(LAZYLEN(contents)) - return - if(iscarbon(hit_atom)) - var/mob/living/carbon/H = hit_atom - if(istype(H.head, /obj/item)) - var/obj/item/WH = H.head - ///check if the item has NODROP - if(HAS_TRAIT(WH, TRAIT_NODROP)) - H.visible_message("[src] bounces off [H]'s [WH.name]!", "[src] bounces off your [WH.name], falling to the floor.") - return - ///check if the item is an actual clothing head item, since some non-clothing items can be worn - if(istype(WH, /obj/item/clothing/head)) - var/obj/item/clothing/head/WHH = WH - ///SNUG_FIT hats are immune to being knocked off - if(WHH.clothing_flags & SNUG_FIT) - H.visible_message("[src] bounces off [H]'s [WHH.name]!", "[src] bounces off your [WHH.name], falling to the floor.") - return - ///if the hat manages to knock something off - if(H.dropItemToGround(WH)) - H.visible_message("[src] knocks [WH] off [H]'s head!", "[WH] is suddenly knocked off your head by [src]!") - if(H.equip_to_slot_if_possible(src, ITEM_SLOT_HEAD, 0, 1, 1)) - H.visible_message("[src] lands neatly on [H]'s head!", "[src] lands perfectly onto your head!") - return - if(iscyborg(hit_atom)) - var/mob/living/silicon/robot/R = hit_atom - ///hats in the borg's blacklist bounce off - if(is_type_in_typecache(src, GLOB.blacklisted_borg_hats)) - R.visible_message("[src] bounces off [R]!", "[src] bounces off you, falling to the floor.") - return - else - R.visible_message("[src] lands neatly on top of [R]!", "[src] lands perfectly on top of you.") - R.place_on_head(src) //hats aren't designed to snugly fit borg heads or w/e so they'll always manage to knock eachother off - - - - -/obj/item/clothing/head/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "helmetblood") - -/obj/item/clothing/head/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_head() +/obj/item/clothing/head + name = BODY_ZONE_HEAD + icon = 'icons/obj/clothing/hats.dmi' + icon_state = "top_hat" + inhand_icon_state = "that" + body_parts_covered = HEAD + slot_flags = ITEM_SLOT_HEAD + var/blockTracking = 0 //For AI tracking + var/can_toggle = null + dynamic_hair_suffix = "+generic" + +/obj/item/clothing/head/Initialize() + . = ..() + if(ishuman(loc) && dynamic_hair_suffix) + var/mob/living/carbon/human/H = loc + H.update_hair() + +///Special throw_impact for hats to frisbee hats at people to place them on their heads/attempt to de-hat them. +/obj/item/clothing/head/throw_impact(atom/hit_atom, datum/thrownthing/thrownthing) + . = ..() + ///if the thrown object's target zone isn't the head + if(thrownthing.target_zone != BODY_ZONE_HEAD) + return + ///ignore any hats with the tinfoil counter-measure enabled + if(clothing_flags & ANTI_TINFOIL_MANEUVER) + return + ///if the hat happens to be capable of holding contents and has something in it. mostly to prevent super cheesy stuff like stuffing a mini-bomb in a hat and throwing it + if(LAZYLEN(contents)) + return + if(iscarbon(hit_atom)) + var/mob/living/carbon/H = hit_atom + if(istype(H.head, /obj/item)) + var/obj/item/WH = H.head + ///check if the item has NODROP + if(HAS_TRAIT(WH, TRAIT_NODROP)) + H.visible_message("[src] bounces off [H]'s [WH.name]!", "[src] bounces off your [WH.name], falling to the floor.") + return + ///check if the item is an actual clothing head item, since some non-clothing items can be worn + if(istype(WH, /obj/item/clothing/head)) + var/obj/item/clothing/head/WHH = WH + ///SNUG_FIT hats are immune to being knocked off + if(WHH.clothing_flags & SNUG_FIT) + H.visible_message("[src] bounces off [H]'s [WHH.name]!", "[src] bounces off your [WHH.name], falling to the floor.") + return + ///if the hat manages to knock something off + if(H.dropItemToGround(WH)) + H.visible_message("[src] knocks [WH] off [H]'s head!", "[WH] is suddenly knocked off your head by [src]!") + if(H.equip_to_slot_if_possible(src, ITEM_SLOT_HEAD, 0, 1, 1)) + H.visible_message("[src] lands neatly on [H]'s head!", "[src] lands perfectly onto your head!") + return + if(iscyborg(hit_atom)) + var/mob/living/silicon/robot/R = hit_atom + ///hats in the borg's blacklist bounce off + if(is_type_in_typecache(src, GLOB.blacklisted_borg_hats)) + R.visible_message("[src] bounces off [R]!", "[src] bounces off you, falling to the floor.") + return + else + R.visible_message("[src] lands neatly on top of [R]!", "[src] lands perfectly on top of you.") + R.place_on_head(src) //hats aren't designed to snugly fit borg heads or w/e so they'll always manage to knock eachother off + + + + +/obj/item/clothing/head/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "helmetblood") + +/obj/item/clothing/head/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_head() diff --git a/code/modules/clothing/head/collectable.dm b/code/modules/clothing/head/collectable.dm index cdaba046b94..7f9c6510b9e 100644 --- a/code/modules/clothing/head/collectable.dm +++ b/code/modules/clothing/head/collectable.dm @@ -1,153 +1,153 @@ - -//Hat Station 13 - -/obj/item/clothing/head/collectable - name = "collectable hat" - desc = "A rare collectable hat." - -/obj/item/clothing/head/collectable/petehat - name = "ultra rare Pete's hat!" - desc = "It smells faintly of plasma." - icon_state = "petehat" - -/obj/item/clothing/head/collectable/xenom - name = "collectable xenomorph helmet!" - desc = "Hiss hiss hiss!" - clothing_flags = SNUG_FIT - icon_state = "xenom" - -/obj/item/clothing/head/collectable/chef - name = "collectable chef's hat" - desc = "A rare chef's hat meant for hat collectors!" - icon_state = "chef" - inhand_icon_state = "chef" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/chef - -/obj/item/clothing/head/collectable/paper - name = "collectable paper hat" - desc = "What looks like an ordinary paper hat is actually a rare and valuable collector's edition paper hat. Keep away from water, fire, and Curators." - icon_state = "paper" - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/collectable/tophat - name = "collectable top hat" - desc = "A top hat worn by only the most prestigious hat collectors." - icon_state = "tophat" - inhand_icon_state = "that" - -/obj/item/clothing/head/collectable/captain - name = "collectable captain's hat" - desc = "A collectable hat that'll make you look just like a real comdom!" - icon_state = "captain" - inhand_icon_state = "caphat" - - dog_fashion = /datum/dog_fashion/head/captain - -/obj/item/clothing/head/collectable/police - name = "collectable police officer's hat" - desc = "A collectable police officer's Hat. This hat emphasizes that you are THE LAW." - icon_state = "policehelm" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/warden - -/obj/item/clothing/head/collectable/beret - name = "collectable beret" - desc = "A collectable red beret. It smells faintly of garlic." - icon_state = "beret" - - dog_fashion = /datum/dog_fashion/head/beret - -/obj/item/clothing/head/collectable/welding - name = "collectable welding helmet" - desc = "A collectable welding helmet. Now with 80% less lead! Not for actual welding. Any welding done while wearing this helmet is done so at the owner's own risk!" - icon_state = "welding" - inhand_icon_state = "welding" - clothing_flags = SNUG_FIT - -/obj/item/clothing/head/collectable/slime - name = "collectable slime hat" - desc = "Just like a real brain slug!" - icon_state = "headslime" - inhand_icon_state = "headslime" - clothing_flags = SNUG_FIT - dynamic_hair_suffix = "" - -/obj/item/clothing/head/collectable/flatcap - name = "collectable flat cap" - desc = "A collectible farmer's flat cap!" - icon_state = "flat_cap" - inhand_icon_state = "detective" - -/obj/item/clothing/head/collectable/pirate - name = "collectable pirate hat" - desc = "You'd make a great Dread Syndie Roberts!" - icon_state = "pirate" - inhand_icon_state = "pirate" - - dog_fashion = /datum/dog_fashion/head/pirate - -/obj/item/clothing/head/collectable/kitty - name = "collectable kitty ears" - desc = "The fur feels... a bit too realistic." - icon_state = "kitty" - inhand_icon_state = "kitty" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/kitty - -/obj/item/clothing/head/collectable/rabbitears - name = "collectable rabbit ears" - desc = "Not as lucky as the feet!" - icon_state = "bunny" - inhand_icon_state = "bunny" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/rabbit - -/obj/item/clothing/head/collectable/wizard - name = "collectable wizard's hat" - desc = "NOTE: Any magical powers gained from wearing this hat are purely coincidental." - icon_state = "wizard" - - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/collectable/hardhat - name = "collectable hard hat" - desc = "WARNING! Offers no real protection, or luminosity, but damn, is it fancy!" - clothing_flags = SNUG_FIT - icon_state = "hardhat0_yellow" - inhand_icon_state = "hardhat0_yellow" - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/collectable/hos - name = "collectable HoS hat" - desc = "Now you too can beat prisoners, set silly sentences, and arrest for no reason!" - icon_state = "hoscap" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/collectable/hop - name = "collectable HoP hat" - desc = "It's your turn to demand excessive paperwork, signatures, stamps, and hire more clowns! Papers, please!" - icon_state = "hopcap" - dog_fashion = /datum/dog_fashion/head/hop - -/obj/item/clothing/head/collectable/thunderdome - name = "collectable Thunderdome helmet" - desc = "Go Red! I mean Green! I mean Red! No Green!" - icon_state = "thunderdome" - inhand_icon_state = "thunderdome" - clothing_flags = SNUG_FIT - flags_inv = HIDEHAIR - -/obj/item/clothing/head/collectable/swat - name = "collectable SWAT helmet" - desc = "That's not real blood. That's red paint." //Reference to the actual description - icon_state = "swat" - inhand_icon_state = "swat" - clothing_flags = SNUG_FIT - flags_inv = HIDEHAIR + +//Hat Station 13 + +/obj/item/clothing/head/collectable + name = "collectable hat" + desc = "A rare collectable hat." + +/obj/item/clothing/head/collectable/petehat + name = "ultra rare Pete's hat!" + desc = "It smells faintly of plasma." + icon_state = "petehat" + +/obj/item/clothing/head/collectable/xenom + name = "collectable xenomorph helmet!" + desc = "Hiss hiss hiss!" + clothing_flags = SNUG_FIT + icon_state = "xenom" + +/obj/item/clothing/head/collectable/chef + name = "collectable chef's hat" + desc = "A rare chef's hat meant for hat collectors!" + icon_state = "chef" + inhand_icon_state = "chef" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/chef + +/obj/item/clothing/head/collectable/paper + name = "collectable paper hat" + desc = "What looks like an ordinary paper hat is actually a rare and valuable collector's edition paper hat. Keep away from water, fire, and Curators." + icon_state = "paper" + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/collectable/tophat + name = "collectable top hat" + desc = "A top hat worn by only the most prestigious hat collectors." + icon_state = "tophat" + inhand_icon_state = "that" + +/obj/item/clothing/head/collectable/captain + name = "collectable captain's hat" + desc = "A collectable hat that'll make you look just like a real comdom!" + icon_state = "captain" + inhand_icon_state = "caphat" + + dog_fashion = /datum/dog_fashion/head/captain + +/obj/item/clothing/head/collectable/police + name = "collectable police officer's hat" + desc = "A collectable police officer's Hat. This hat emphasizes that you are THE LAW." + icon_state = "policehelm" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/warden + +/obj/item/clothing/head/collectable/beret + name = "collectable beret" + desc = "A collectable red beret. It smells faintly of garlic." + icon_state = "beret" + + dog_fashion = /datum/dog_fashion/head/beret + +/obj/item/clothing/head/collectable/welding + name = "collectable welding helmet" + desc = "A collectable welding helmet. Now with 80% less lead! Not for actual welding. Any welding done while wearing this helmet is done so at the owner's own risk!" + icon_state = "welding" + inhand_icon_state = "welding" + clothing_flags = SNUG_FIT + +/obj/item/clothing/head/collectable/slime + name = "collectable slime hat" + desc = "Just like a real brain slug!" + icon_state = "headslime" + inhand_icon_state = "headslime" + clothing_flags = SNUG_FIT + dynamic_hair_suffix = "" + +/obj/item/clothing/head/collectable/flatcap + name = "collectable flat cap" + desc = "A collectible farmer's flat cap!" + icon_state = "flat_cap" + inhand_icon_state = "detective" + +/obj/item/clothing/head/collectable/pirate + name = "collectable pirate hat" + desc = "You'd make a great Dread Syndie Roberts!" + icon_state = "pirate" + inhand_icon_state = "pirate" + + dog_fashion = /datum/dog_fashion/head/pirate + +/obj/item/clothing/head/collectable/kitty + name = "collectable kitty ears" + desc = "The fur feels... a bit too realistic." + icon_state = "kitty" + inhand_icon_state = "kitty" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/kitty + +/obj/item/clothing/head/collectable/rabbitears + name = "collectable rabbit ears" + desc = "Not as lucky as the feet!" + icon_state = "bunny" + inhand_icon_state = "bunny" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/rabbit + +/obj/item/clothing/head/collectable/wizard + name = "collectable wizard's hat" + desc = "NOTE: Any magical powers gained from wearing this hat are purely coincidental." + icon_state = "wizard" + + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/collectable/hardhat + name = "collectable hard hat" + desc = "WARNING! Offers no real protection, or luminosity, but damn, is it fancy!" + clothing_flags = SNUG_FIT + icon_state = "hardhat0_yellow" + inhand_icon_state = "hardhat0_yellow" + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/collectable/hos + name = "collectable HoS hat" + desc = "Now you too can beat prisoners, set silly sentences, and arrest for no reason!" + icon_state = "hoscap" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/collectable/hop + name = "collectable HoP hat" + desc = "It's your turn to demand excessive paperwork, signatures, stamps, and hire more clowns! Papers, please!" + icon_state = "hopcap" + dog_fashion = /datum/dog_fashion/head/hop + +/obj/item/clothing/head/collectable/thunderdome + name = "collectable Thunderdome helmet" + desc = "Go Red! I mean Green! I mean Red! No Green!" + icon_state = "thunderdome" + inhand_icon_state = "thunderdome" + clothing_flags = SNUG_FIT + flags_inv = HIDEHAIR + +/obj/item/clothing/head/collectable/swat + name = "collectable SWAT helmet" + desc = "That's not real blood. That's red paint." //Reference to the actual description + icon_state = "swat" + inhand_icon_state = "swat" + clothing_flags = SNUG_FIT + flags_inv = HIDEHAIR diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 223525d2f54..e077cfff14d 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -1,164 +1,164 @@ -/obj/item/clothing/head/hardhat - name = "hard hat" - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight." - icon_state = "hardhat0_yellow" - inhand_icon_state = "hardhat0_yellow" - var/brightness_on = 4 //luminosity when on - var/on = FALSE - var/hat_type = "yellow" //Determines used sprites: hardhat[on]_[hat_type] and hardhat[on]_[hat_type]2 (lying down sprite) - armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) - flags_inv = 0 - actions_types = list(/datum/action/item_action/toggle_helmet_light) - clothing_flags = SNUG_FIT - resistance_flags = FIRE_PROOF - dynamic_hair_suffix = "+generic" - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/clothing/head/hardhat/attack_self(mob/living/user) - toggle_helmet_light(user) - -/obj/item/clothing/head/hardhat/proc/toggle_helmet_light(mob/living/user) - on = !on - if(on) - turn_on(user) - else - turn_off(user) - update_icon() - -/obj/item/clothing/head/hardhat/update_icon_state() - icon_state = inhand_icon_state = "hardhat[on]_[hat_type]" - -/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) - set_light(brightness_on) - -/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) - set_light(0) - -/obj/item/clothing/head/hardhat/orange - icon_state = "hardhat0_orange" - inhand_icon_state = "hardhat0_orange" - hat_type = "orange" - dog_fashion = null - -/obj/item/clothing/head/hardhat/red - icon_state = "hardhat0_red" - inhand_icon_state = "hardhat0_red" - hat_type = "red" - dog_fashion = null - name = "firefighter helmet" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - -/obj/item/clothing/head/hardhat/red/upgraded - name = "workplace-ready firefighter helmet" - desc = "By applying state of the art lighting technology to a fire helmet, and using photo-chemical hardening methods, this hardhat will protect you from robust workplace hazards." - icon_state = "hardhat0_purple" - inhand_icon_state = "hardhat0_purple" - brightness_on = 5 - resistance_flags = FIRE_PROOF | ACID_PROOF - custom_materials = list(/datum/material/iron = 4000, /datum/material/glass = 1000, /datum/material/plastic = 3000, /datum/material/silver = 500) - hat_type = "purple" - -/obj/item/clothing/head/hardhat/white - icon_state = "hardhat0_white" - inhand_icon_state = "hardhat0_white" - hat_type = "white" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/dblue - icon_state = "hardhat0_dblue" - inhand_icon_state = "hardhat0_dblue" - hat_type = "dblue" - dog_fashion = null - -/obj/item/clothing/head/hardhat/atmos - icon_state = "hardhat0_atmos" - inhand_icon_state = "hardhat0_atmos" - hat_type = "atmos" - dog_fashion = null - name = "atmospheric technician's firefighting helmet" - desc = "A firefighter's helmet, able to keep the user cool in any situation." - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - heat_protection = HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - -/obj/item/clothing/head/hardhat/weldhat - name = "welding hard hat" - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield! The bulb seems a little smaller though." - brightness_on = 3 //Needs a little bit of tradeoff - dog_fashion = null - actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) - flash_protect = FLASH_PROTECTION_WELDER - tint = 2 - flags_inv = HIDEEYES | HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - visor_flags_inv = HIDEEYES | HIDEFACE - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - -/obj/item/clothing/head/hardhat/weldhat/Initialize() - . = ..() - update_icon() - -/obj/item/clothing/head/hardhat/weldhat/attack_self(mob/living/user) - toggle_helmet_light(user) - -/obj/item/clothing/head/hardhat/weldhat/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE)) - toggle_welding_screen(user) - -/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) - if(weldingvisortoggle(user)) - playsound(src, 'sound/mecha/mechmove03.ogg', 50, TRUE) //Visors don't just come from nothing - update_icon() - -/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) - . = ..() - if(!isinhands) - . += mutable_appearance('icons/mob/clothing/head.dmi', "weldhelmet") - if(!up) - . += mutable_appearance('icons/mob/clothing/head.dmi', "weldvisor") - -/obj/item/clothing/head/hardhat/weldhat/update_overlays() - . = ..() - if(!up) - . += "weldvisor" - -/obj/item/clothing/head/hardhat/weldhat/orange - icon_state = "hardhat0_orange" - inhand_icon_state = "hardhat0_orange" - hat_type = "orange" - -/obj/item/clothing/head/hardhat/weldhat/white - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield!" //This bulb is not smaller - icon_state = "hardhat0_white" - inhand_icon_state = "hardhat0_white" - brightness_on = 4 //Boss always takes the best stuff - hat_type = "white" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - -/obj/item/clothing/head/hardhat/weldhat/dblue - icon_state = "hardhat0_dblue" - inhand_icon_state = "hardhat0_dblue" - hat_type = "dblue" +/obj/item/clothing/head/hardhat + name = "hard hat" + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight." + icon_state = "hardhat0_yellow" + inhand_icon_state = "hardhat0_yellow" + var/brightness_on = 4 //luminosity when on + var/on = FALSE + var/hat_type = "yellow" //Determines used sprites: hardhat[on]_[hat_type] and hardhat[on]_[hat_type]2 (lying down sprite) + armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) + flags_inv = 0 + actions_types = list(/datum/action/item_action/toggle_helmet_light) + clothing_flags = SNUG_FIT + resistance_flags = FIRE_PROOF + dynamic_hair_suffix = "+generic" + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/clothing/head/hardhat/attack_self(mob/living/user) + toggle_helmet_light(user) + +/obj/item/clothing/head/hardhat/proc/toggle_helmet_light(mob/living/user) + on = !on + if(on) + turn_on(user) + else + turn_off(user) + update_icon() + +/obj/item/clothing/head/hardhat/update_icon_state() + icon_state = inhand_icon_state = "hardhat[on]_[hat_type]" + +/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) + set_light(brightness_on) + +/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) + set_light(0) + +/obj/item/clothing/head/hardhat/orange + icon_state = "hardhat0_orange" + inhand_icon_state = "hardhat0_orange" + hat_type = "orange" + dog_fashion = null + +/obj/item/clothing/head/hardhat/red + icon_state = "hardhat0_red" + inhand_icon_state = "hardhat0_red" + hat_type = "red" + dog_fashion = null + name = "firefighter helmet" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + +/obj/item/clothing/head/hardhat/red/upgraded + name = "workplace-ready firefighter helmet" + desc = "By applying state of the art lighting technology to a fire helmet, and using photo-chemical hardening methods, this hardhat will protect you from robust workplace hazards." + icon_state = "hardhat0_purple" + inhand_icon_state = "hardhat0_purple" + brightness_on = 5 + resistance_flags = FIRE_PROOF | ACID_PROOF + custom_materials = list(/datum/material/iron = 4000, /datum/material/glass = 1000, /datum/material/plastic = 3000, /datum/material/silver = 500) + hat_type = "purple" + +/obj/item/clothing/head/hardhat/white + icon_state = "hardhat0_white" + inhand_icon_state = "hardhat0_white" + hat_type = "white" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/dblue + icon_state = "hardhat0_dblue" + inhand_icon_state = "hardhat0_dblue" + hat_type = "dblue" + dog_fashion = null + +/obj/item/clothing/head/hardhat/atmos + icon_state = "hardhat0_atmos" + inhand_icon_state = "hardhat0_atmos" + hat_type = "atmos" + dog_fashion = null + name = "atmospheric technician's firefighting helmet" + desc = "A firefighter's helmet, able to keep the user cool in any situation." + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + heat_protection = HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + +/obj/item/clothing/head/hardhat/weldhat + name = "welding hard hat" + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield! The bulb seems a little smaller though." + brightness_on = 3 //Needs a little bit of tradeoff + dog_fashion = null + actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) + flash_protect = FLASH_PROTECTION_WELDER + tint = 2 + flags_inv = HIDEEYES | HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + visor_flags_inv = HIDEEYES | HIDEFACE + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + +/obj/item/clothing/head/hardhat/weldhat/Initialize() + . = ..() + update_icon() + +/obj/item/clothing/head/hardhat/weldhat/attack_self(mob/living/user) + toggle_helmet_light(user) + +/obj/item/clothing/head/hardhat/weldhat/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE)) + toggle_welding_screen(user) + +/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) + if(weldingvisortoggle(user)) + playsound(src, 'sound/mecha/mechmove03.ogg', 50, TRUE) //Visors don't just come from nothing + update_icon() + +/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) + . = ..() + if(!isinhands) + . += mutable_appearance('icons/mob/clothing/head.dmi', "weldhelmet") + if(!up) + . += mutable_appearance('icons/mob/clothing/head.dmi', "weldvisor") + +/obj/item/clothing/head/hardhat/weldhat/update_overlays() + . = ..() + if(!up) + . += "weldvisor" + +/obj/item/clothing/head/hardhat/weldhat/orange + icon_state = "hardhat0_orange" + inhand_icon_state = "hardhat0_orange" + hat_type = "orange" + +/obj/item/clothing/head/hardhat/weldhat/white + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield!" //This bulb is not smaller + icon_state = "hardhat0_white" + inhand_icon_state = "hardhat0_white" + brightness_on = 4 //Boss always takes the best stuff + hat_type = "white" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + +/obj/item/clothing/head/hardhat/weldhat/dblue + icon_state = "hardhat0_dblue" + inhand_icon_state = "hardhat0_dblue" + hat_type = "dblue" diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index 8bd7eed71cf..403415466dd 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -1,519 +1,519 @@ -/obj/item/clothing/head/helmet - name = "helmet" - desc = "Standard Security gear. Protects the head from impacts." - icon_state = "helmet" - inhand_icon_state = "helmet" - armor = list("melee" = 35, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - cold_protection = HEAD - min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT - strip_delay = 60 - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES - flags_inv = HIDEHAIR - - dog_fashion = /datum/dog_fashion/head/helmet - - var/can_flashlight = FALSE //if a flashlight can be mounted. if it has a flashlight and this is false, it is permanently attached. - var/obj/item/flashlight/seclite/attached_light - var/datum/action/item_action/toggle_helmet_flashlight/alight - -/obj/item/clothing/head/helmet/Initialize() - . = ..() - if(attached_light) - alight = new(src) - -/obj/item/clothing/head/helmet/examine(mob/user) - . = ..() - if(attached_light) - . += "It has \a [attached_light] [can_flashlight ? "" : "permanently "]mounted on it." - if(can_flashlight) - . += "[attached_light] looks like it can be unscrewed from [src]." - else if(can_flashlight) - . += "It has a mounting point for a seclite." - -/obj/item/clothing/head/helmet/Destroy() - QDEL_NULL(attached_light) - return ..() - -/obj/item/clothing/head/helmet/handle_atom_del(atom/A) - if(A == attached_light) - attached_light = null - update_helmlight() - update_icon() - QDEL_NULL(alight) - return ..() - -/obj/item/clothing/head/helmet/sec - can_flashlight = TRUE - -/obj/item/clothing/head/helmet/sec/attackby(obj/item/I, mob/user, params) - if(issignaler(I)) - var/obj/item/assembly/signaler/S = I - if(attached_light) //Has a flashlight. Player must remove it, else it will be lost forever. - to_chat(user, "The mounted flashlight is in the way, remove it first!") - return - - if(S.secured) - qdel(S) - var/obj/item/bot_assembly/secbot/A = new - user.put_in_hands(A) - to_chat(user, "You add the signaler to the helmet.") - qdel(src) - return - return ..() - -/obj/item/clothing/head/helmet/alt - name = "bulletproof helmet" - desc = "A bulletproof combat helmet that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." - icon_state = "helmetalt" - inhand_icon_state = "helmetalt" - armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - can_flashlight = TRUE - dog_fashion = null - -/obj/item/clothing/head/helmet/old - name = "degrading helmet" - desc = "Standard issue security helmet. Due to degradation the helmet's visor obstructs the users ability to see long distances." - tint = 2 - -/obj/item/clothing/head/helmet/blueshirt - name = "blue helmet" - desc = "A reliable, blue tinted helmet reminding you that you still owe that engineer a beer." - icon_state = "blueshift" - inhand_icon_state = "blueshift" - custom_premium_price = 750 - -/obj/item/clothing/head/helmet/riot - name = "riot helmet" - desc = "It's a helmet specifically designed to protect against close range attacks." - icon_state = "riot" - inhand_icon_state = "helmet" - toggle_message = "You pull the visor down on" - alt_toggle_message = "You push the visor up on" - can_toggle = 1 - armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - flags_inv = HIDEEARS|HIDEFACE - strip_delay = 80 - actions_types = list(/datum/action/item_action/toggle) - visor_flags_inv = HIDEFACE - toggle_cooldown = 0 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - dog_fashion = null - -/obj/item/clothing/head/helmet/attack_self(mob/user) - if(can_toggle && !user.incapacitated()) - if(world.time > cooldown + toggle_cooldown) - cooldown = world.time - up = !up - flags_1 ^= visor_flags - flags_inv ^= visor_flags_inv - flags_cover ^= visor_flags_cover - icon_state = "[initial(icon_state)][up ? "up" : ""]" - to_chat(user, "[up ? alt_toggle_message : toggle_message] \the [src].") - - user.update_inv_head() - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.head_update(src, forced = 1) - - if(active_sound) - while(up) - playsound(src, "[active_sound]", 100, FALSE, 4) - sleep(15) - -/obj/item/clothing/head/helmet/justice - name = "helmet of justice" - desc = "WEEEEOOO. WEEEEEOOO. WEEEEOOOO." - icon_state = "justice" - toggle_message = "You turn off the lights on" - alt_toggle_message = "You turn on the lights on" - actions_types = list(/datum/action/item_action/toggle_helmet_light) - can_toggle = 1 - toggle_cooldown = 20 - active_sound = 'sound/items/weeoo1.ogg' - dog_fashion = null - -/obj/item/clothing/head/helmet/justice/escape - name = "alarm helmet" - desc = "WEEEEOOO. WEEEEEOOO. STOP THAT MONKEY. WEEEOOOO." - icon_state = "justice2" - toggle_message = "You turn off the light on" - alt_toggle_message = "You turn on the light on" - -/obj/item/clothing/head/helmet/swat - name = "\improper SWAT helmet" - desc = "An extremely robust, space-worthy helmet in a nefarious red and black stripe pattern." - icon_state = "swatsyndie" - inhand_icon_state = "swatsyndie" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - clothing_flags = STOPSPRESSUREDAMAGE - strip_delay = 80 - resistance_flags = FIRE_PROOF | ACID_PROOF - dog_fashion = null - -/obj/item/clothing/head/helmet/police - name = "police officer's hat" - desc = "A police officer's Hat. This hat emphasizes that you are THE LAW." - icon_state = "policehelm" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/helmet/constable - name = "constable helmet" - desc = "A british looking helmet." - worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' - icon_state = "constable" - inhand_icon_state = "constable" - worn_x_dimension = 64 - worn_y_dimension = 64 - custom_price = 350 - -/obj/item/clothing/head/helmet/swat/nanotrasen - name = "\improper SWAT helmet" - desc = "An extremely robust, space-worthy helmet with the Nanotrasen logo emblazoned on the top." - icon_state = "swat" - inhand_icon_state = "swat" - -/obj/item/clothing/head/helmet/thunderdome - name = "\improper Thunderdome helmet" - desc = "'Let the battle commence!'" - flags_inv = HIDEEARS|HIDEHAIR - icon_state = "thunderdome" - inhand_icon_state = "thunderdome" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - strip_delay = 80 - dog_fashion = null - -/obj/item/clothing/head/helmet/roman - name = "\improper Roman helmet" - desc = "An ancient helmet made of bronze and leather." - flags_inv = HIDEEARS|HIDEHAIR - flags_cover = HEADCOVERSEYES - armor = list("melee" = 25, "bullet" = 0, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - icon_state = "roman" - inhand_icon_state = "roman" - strip_delay = 100 - dog_fashion = null - -/obj/item/clothing/head/helmet/roman/fake - desc = "An ancient helmet made of plastic and leather." - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/head/helmet/roman/legionnaire - name = "\improper Roman legionnaire helmet" - desc = "An ancient helmet made of bronze and leather. Has a red crest on top of it." - icon_state = "roman_c" - inhand_icon_state = "roman_c" - -/obj/item/clothing/head/helmet/roman/legionnaire/fake - desc = "An ancient helmet made of plastic and leather. Has a red crest on top of it." - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/head/helmet/gladiator - name = "gladiator helmet" - desc = "Ave, Imperator, morituri te salutant." - icon_state = "gladiator" - inhand_icon_state = "gladiator" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR - flags_cover = HEADCOVERSEYES - dog_fashion = null - -/obj/item/clothing/head/helmet/redtaghelm - name = "red laser tag helmet" - desc = "They have chosen their own end." - icon_state = "redtaghelm" - flags_cover = HEADCOVERSEYES - inhand_icon_state = "redtaghelm" - armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - // Offer about the same protection as a hardhat. - dog_fashion = null - -/obj/item/clothing/head/helmet/bluetaghelm - name = "blue laser tag helmet" - desc = "They'll need more men." - icon_state = "bluetaghelm" - flags_cover = HEADCOVERSEYES - inhand_icon_state = "bluetaghelm" - armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - // Offer about the same protection as a hardhat. - dog_fashion = null - -/obj/item/clothing/head/helmet/knight - name = "medieval helmet" - desc = "A classic metal helmet." - icon_state = "knight_green" - inhand_icon_state = "knight_green" - armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - strip_delay = 80 - dog_fashion = null - - -/obj/item/clothing/head/helmet/knight/Initialize(mapload) - . = ..() - var/datum/component = GetComponent(/datum/component/wearertargeting/earprotection) - qdel(component) - -/obj/item/clothing/head/helmet/knight/blue - icon_state = "knight_blue" - inhand_icon_state = "knight_blue" - -/obj/item/clothing/head/helmet/knight/yellow - icon_state = "knight_yellow" - inhand_icon_state = "knight_yellow" - -/obj/item/clothing/head/helmet/knight/red - icon_state = "knight_red" - inhand_icon_state = "knight_red" - -/obj/item/clothing/head/helmet/knight/greyscale - name = "knight helmet" - desc = "A classic medieval helmet, if you hold it upside down you could see that it's actually a bucket." - icon_state = "knight_greyscale" - inhand_icon_state = "knight_greyscale" - armor = list("melee" = 35, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 10, "rad" = 10, "fire" = 40, "acid" = 40) - material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS //Can change color and add prefix - -/obj/item/clothing/head/helmet/skull - name = "skull helmet" - desc = "An intimidating tribal helmet, it doesn't look very comfortable." - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES - armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - icon_state = "skull" - inhand_icon_state = "skull" - strip_delay = 100 - -/obj/item/clothing/head/helmet/durathread - name = "durathread helmet" - desc = "A helmet made from durathread and leather." - icon_state = "durathread" - inhand_icon_state = "durathread" - resistance_flags = FLAMMABLE - armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 40, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/helmet/rus_helmet - name = "russian helmet" - desc = "It can hold a bottle of vodka." - icon_state = "rus_helmet" - inhand_icon_state = "rus_helmet" - armor = list("melee" = 25, "bullet" = 30, "laser" = 0, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 20, "fire" = 20, "acid" = 50) - pocket_storage_component_path = /datum/component/storage/concrete/pockets/helmet - -/obj/item/clothing/head/helmet/rus_ushanka - name = "battle ushanka" - desc = "100% bear." - icon_state = "rus_ushanka" - inhand_icon_state = "rus_ushanka" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - armor = list("melee" = 25, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 50) - -/obj/item/clothing/head/helmet/infiltrator - name = "infiltrator helmet" - desc = "The galaxy isn't big enough for the two of us." - icon_state = "infiltrator" - inhand_icon_state = "infiltrator" - armor = list("melee" = 40, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - flash_protect = FLASH_PROTECTION_WELDER - flags_inv = HIDEHAIR|HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - strip_delay = 80 - -//monkey sentience caps - -/obj/item/clothing/head/helmet/monkey_sentience - name = "monkey mind magnification helmet" - desc = "A fragile, circuitry embedded helmet for boosting the intelligence of a monkey to a higher level. You see several warning labels..." - - icon_state = "monkeymind" - inhand_icon_state = "monkeymind" - strip_delay = 100 - var/mob/living/carbon/monkey/magnification = null ///if the helmet is on a valid target (just works like a normal helmet if not (cargo please stop)) - var/polling = FALSE///if the helmet is currently polling for targets (special code for removal) - var/light_colors = 1 ///which icon state color this is (red, blue, yellow) - -/obj/item/clothing/head/helmet/monkey_sentience/Initialize() - . = ..() - light_colors = rand(1,3) - update_icon_state() - -/obj/item/clothing/head/helmet/monkey_sentience/examine(mob/user) - . = ..() - . += "---WARNING: REMOVAL OF HELMET ON SUBJECT MAY LEAD TO:---" - . += "BLOOD RAGE" - . += "BRAIN DEATH" - . += "PRIMAL GENE ACTIVATION" - . += "GENETIC MAKEUP MASS SUSCEPTIBILITY" - . += "Ask your CMO if mind magnification is right for you." - -/obj/item/clothing/head/helmet/monkey_sentience/update_icon_state() - icon_state = "[initial(icon_state)][light_colors][magnification ? "up" : ""]" - -/obj/item/clothing/head/helmet/monkey_sentience/equipped(mob/user, slot) - . = ..() - if(slot != ITEM_SLOT_HEAD) - return - if(!ismonkey(user) || user.ckey) - var/mob/living/something = user - to_chat(something, "You feel a stabbing pain in the back of your head for a moment.") - something.apply_damage(5,BRUTE,BODY_ZONE_HEAD,FALSE,FALSE,FALSE) //notably: no damage resist (it's in your helmet), no damage spread (it's in your helmet) - playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - return - magnification = user //this polls ghosts - visible_message("[src] powers up!") - playsound(src, 'sound/machines/ping.ogg', 30, TRUE) - polling = TRUE - var/list/candidates = pollCandidatesForMob("Do you want to play as a mind magnified monkey?", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, magnification, POLL_IGNORE_SENTIENCE_POTION) - polling = FALSE - if(!candidates.len) - magnification = null - visible_message("[src] falls silent and drops on the floor. Maybe you should try again later?") - playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - user.dropItemToGround(src) - var/mob/picked = pick(candidates) - magnification.key = picked.key - playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE) - to_chat(magnification, "You're a mind magnified monkey! Protect your helmet with your life- if you lose it, your sentience goes with it!") - var/policy = get_policy(ROLE_MONKEY_HELMET) - if(policy) - to_chat(magnification, policy) - icon_state = "[icon_state]up" - -/obj/item/clothing/head/helmet/monkey_sentience/Destroy() - disconnect() - return ..() - -/obj/item/clothing/head/helmet/monkey_sentience/proc/disconnect() - if(!magnification) //not put on a viable head - return - if(!polling)//put on a viable head, but taken off after polling finished. - if(magnification.client) - to_chat(magnification, "You feel your flicker of sentience ripped away from you, as everything becomes dim...") - magnification.ghostize(FALSE) - if(prob(10)) - switch(rand(1,4)) - if(1) //blood rage - magnification.aggressive = TRUE - if(2) //brain death - magnification.apply_damage(500,BRAIN,BODY_ZONE_HEAD,FALSE,FALSE,FALSE) - if(3) //primal gene (gorilla) - magnification.gorillize() - if(4) //genetic mass susceptibility (gib) - magnification.gib() - //either used up correctly or taken off before polling finished (punish this by destroying the helmet) - playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - playsound(src, "sparks", 100, TRUE) - visible_message("[src] fizzles and breaks apart!") - magnification = null - new /obj/effect/decal/cleanable/ash/crematorium(drop_location()) //just in case they're in a locker or other containers it needs to use crematorium ash, see the path itself for an explanation - -/obj/item/clothing/head/helmet/monkey_sentience/dropped(mob/user) - . = ..() - if(magnification || polling) - qdel(src)//runs disconnect code - - -//LightToggle - -/obj/item/clothing/head/helmet/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/clothing/head/helmet/update_icon_state() - var/state = "[initial(icon_state)]" - if(attached_light) - if(attached_light.on) - state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on" - else - state += "-flight" //etc. - - icon_state = state - -/obj/item/clothing/head/helmet/ui_action_click(mob/user, action) - if(istype(action, alight)) - toggle_helmlight() - else - ..() - -/obj/item/clothing/head/helmet/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/flashlight/seclite)) - var/obj/item/flashlight/seclite/S = I - if(can_flashlight && !attached_light) - if(!user.transferItemToLoc(S, src)) - return - to_chat(user, "You click [S] into place on [src].") - if(S.on) - set_light(0) - attached_light = S - update_icon() - update_helmlight() - alight = new(src) - if(loc == user) - alight.Grant(user) - return - return ..() - -/obj/item/clothing/head/helmet/screwdriver_act(mob/living/user, obj/item/I) - . = ..() - if(can_flashlight && attached_light) //if it has a light but can_flashlight is false, the light is permanently attached. - I.play_tool_sound(src) - to_chat(user, "You unscrew [attached_light] from [src].") - attached_light.forceMove(drop_location()) - if(Adjacent(user) && !issilicon(user)) - user.put_in_hands(attached_light) - - var/obj/item/flashlight/removed_light = attached_light - attached_light = null - update_helmlight() - removed_light.update_brightness(user) - update_icon() - user.update_inv_head() - QDEL_NULL(alight) - return TRUE - -/obj/item/clothing/head/helmet/proc/toggle_helmlight() - set name = "Toggle Helmetlight" - set category = "Object" - set desc = "Click to toggle your helmet's attached flashlight." - - if(!attached_light) - return - - var/mob/user = usr - if(user.incapacitated()) - return - attached_light.on = !attached_light.on - to_chat(user, "You toggle the helmet-light [attached_light.on ? "on":"off"].") - - playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) - update_helmlight() - -/obj/item/clothing/head/helmet/proc/update_helmlight() - if(attached_light) - if(attached_light.on) - set_light(attached_light.brightness_on) - else - set_light(0) - update_icon() - - else - set_light(0) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() +/obj/item/clothing/head/helmet + name = "helmet" + desc = "Standard Security gear. Protects the head from impacts." + icon_state = "helmet" + inhand_icon_state = "helmet" + armor = list("melee" = 35, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + cold_protection = HEAD + min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT + strip_delay = 60 + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES + flags_inv = HIDEHAIR + + dog_fashion = /datum/dog_fashion/head/helmet + + var/can_flashlight = FALSE //if a flashlight can be mounted. if it has a flashlight and this is false, it is permanently attached. + var/obj/item/flashlight/seclite/attached_light + var/datum/action/item_action/toggle_helmet_flashlight/alight + +/obj/item/clothing/head/helmet/Initialize() + . = ..() + if(attached_light) + alight = new(src) + +/obj/item/clothing/head/helmet/examine(mob/user) + . = ..() + if(attached_light) + . += "It has \a [attached_light] [can_flashlight ? "" : "permanently "]mounted on it." + if(can_flashlight) + . += "[attached_light] looks like it can be unscrewed from [src]." + else if(can_flashlight) + . += "It has a mounting point for a seclite." + +/obj/item/clothing/head/helmet/Destroy() + QDEL_NULL(attached_light) + return ..() + +/obj/item/clothing/head/helmet/handle_atom_del(atom/A) + if(A == attached_light) + attached_light = null + update_helmlight() + update_icon() + QDEL_NULL(alight) + return ..() + +/obj/item/clothing/head/helmet/sec + can_flashlight = TRUE + +/obj/item/clothing/head/helmet/sec/attackby(obj/item/I, mob/user, params) + if(issignaler(I)) + var/obj/item/assembly/signaler/S = I + if(attached_light) //Has a flashlight. Player must remove it, else it will be lost forever. + to_chat(user, "The mounted flashlight is in the way, remove it first!") + return + + if(S.secured) + qdel(S) + var/obj/item/bot_assembly/secbot/A = new + user.put_in_hands(A) + to_chat(user, "You add the signaler to the helmet.") + qdel(src) + return + return ..() + +/obj/item/clothing/head/helmet/alt + name = "bulletproof helmet" + desc = "A bulletproof combat helmet that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." + icon_state = "helmetalt" + inhand_icon_state = "helmetalt" + armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + can_flashlight = TRUE + dog_fashion = null + +/obj/item/clothing/head/helmet/old + name = "degrading helmet" + desc = "Standard issue security helmet. Due to degradation the helmet's visor obstructs the users ability to see long distances." + tint = 2 + +/obj/item/clothing/head/helmet/blueshirt + name = "blue helmet" + desc = "A reliable, blue tinted helmet reminding you that you still owe that engineer a beer." + icon_state = "blueshift" + inhand_icon_state = "blueshift" + custom_premium_price = 750 + +/obj/item/clothing/head/helmet/riot + name = "riot helmet" + desc = "It's a helmet specifically designed to protect against close range attacks." + icon_state = "riot" + inhand_icon_state = "helmet" + toggle_message = "You pull the visor down on" + alt_toggle_message = "You push the visor up on" + can_toggle = 1 + armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + flags_inv = HIDEEARS|HIDEFACE + strip_delay = 80 + actions_types = list(/datum/action/item_action/toggle) + visor_flags_inv = HIDEFACE + toggle_cooldown = 0 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + dog_fashion = null + +/obj/item/clothing/head/helmet/attack_self(mob/user) + if(can_toggle && !user.incapacitated()) + if(world.time > cooldown + toggle_cooldown) + cooldown = world.time + up = !up + flags_1 ^= visor_flags + flags_inv ^= visor_flags_inv + flags_cover ^= visor_flags_cover + icon_state = "[initial(icon_state)][up ? "up" : ""]" + to_chat(user, "[up ? alt_toggle_message : toggle_message] \the [src].") + + user.update_inv_head() + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.head_update(src, forced = 1) + + if(active_sound) + while(up) + playsound(src, "[active_sound]", 100, FALSE, 4) + sleep(15) + +/obj/item/clothing/head/helmet/justice + name = "helmet of justice" + desc = "WEEEEOOO. WEEEEEOOO. WEEEEOOOO." + icon_state = "justice" + toggle_message = "You turn off the lights on" + alt_toggle_message = "You turn on the lights on" + actions_types = list(/datum/action/item_action/toggle_helmet_light) + can_toggle = 1 + toggle_cooldown = 20 + active_sound = 'sound/items/weeoo1.ogg' + dog_fashion = null + +/obj/item/clothing/head/helmet/justice/escape + name = "alarm helmet" + desc = "WEEEEOOO. WEEEEEOOO. STOP THAT MONKEY. WEEEOOOO." + icon_state = "justice2" + toggle_message = "You turn off the light on" + alt_toggle_message = "You turn on the light on" + +/obj/item/clothing/head/helmet/swat + name = "\improper SWAT helmet" + desc = "An extremely robust, space-worthy helmet in a nefarious red and black stripe pattern." + icon_state = "swatsyndie" + inhand_icon_state = "swatsyndie" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + clothing_flags = STOPSPRESSUREDAMAGE + strip_delay = 80 + resistance_flags = FIRE_PROOF | ACID_PROOF + dog_fashion = null + +/obj/item/clothing/head/helmet/police + name = "police officer's hat" + desc = "A police officer's Hat. This hat emphasizes that you are THE LAW." + icon_state = "policehelm" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/helmet/constable + name = "constable helmet" + desc = "A british looking helmet." + worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' + icon_state = "constable" + inhand_icon_state = "constable" + worn_x_dimension = 64 + worn_y_dimension = 64 + custom_price = 350 + +/obj/item/clothing/head/helmet/swat/nanotrasen + name = "\improper SWAT helmet" + desc = "An extremely robust, space-worthy helmet with the Nanotrasen logo emblazoned on the top." + icon_state = "swat" + inhand_icon_state = "swat" + +/obj/item/clothing/head/helmet/thunderdome + name = "\improper Thunderdome helmet" + desc = "'Let the battle commence!'" + flags_inv = HIDEEARS|HIDEHAIR + icon_state = "thunderdome" + inhand_icon_state = "thunderdome" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + strip_delay = 80 + dog_fashion = null + +/obj/item/clothing/head/helmet/roman + name = "\improper Roman helmet" + desc = "An ancient helmet made of bronze and leather." + flags_inv = HIDEEARS|HIDEHAIR + flags_cover = HEADCOVERSEYES + armor = list("melee" = 25, "bullet" = 0, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + icon_state = "roman" + inhand_icon_state = "roman" + strip_delay = 100 + dog_fashion = null + +/obj/item/clothing/head/helmet/roman/fake + desc = "An ancient helmet made of plastic and leather." + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/head/helmet/roman/legionnaire + name = "\improper Roman legionnaire helmet" + desc = "An ancient helmet made of bronze and leather. Has a red crest on top of it." + icon_state = "roman_c" + inhand_icon_state = "roman_c" + +/obj/item/clothing/head/helmet/roman/legionnaire/fake + desc = "An ancient helmet made of plastic and leather. Has a red crest on top of it." + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/head/helmet/gladiator + name = "gladiator helmet" + desc = "Ave, Imperator, morituri te salutant." + icon_state = "gladiator" + inhand_icon_state = "gladiator" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR + flags_cover = HEADCOVERSEYES + dog_fashion = null + +/obj/item/clothing/head/helmet/redtaghelm + name = "red laser tag helmet" + desc = "They have chosen their own end." + icon_state = "redtaghelm" + flags_cover = HEADCOVERSEYES + inhand_icon_state = "redtaghelm" + armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + // Offer about the same protection as a hardhat. + dog_fashion = null + +/obj/item/clothing/head/helmet/bluetaghelm + name = "blue laser tag helmet" + desc = "They'll need more men." + icon_state = "bluetaghelm" + flags_cover = HEADCOVERSEYES + inhand_icon_state = "bluetaghelm" + armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + // Offer about the same protection as a hardhat. + dog_fashion = null + +/obj/item/clothing/head/helmet/knight + name = "medieval helmet" + desc = "A classic metal helmet." + icon_state = "knight_green" + inhand_icon_state = "knight_green" + armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + strip_delay = 80 + dog_fashion = null + + +/obj/item/clothing/head/helmet/knight/Initialize(mapload) + . = ..() + var/datum/component = GetComponent(/datum/component/wearertargeting/earprotection) + qdel(component) + +/obj/item/clothing/head/helmet/knight/blue + icon_state = "knight_blue" + inhand_icon_state = "knight_blue" + +/obj/item/clothing/head/helmet/knight/yellow + icon_state = "knight_yellow" + inhand_icon_state = "knight_yellow" + +/obj/item/clothing/head/helmet/knight/red + icon_state = "knight_red" + inhand_icon_state = "knight_red" + +/obj/item/clothing/head/helmet/knight/greyscale + name = "knight helmet" + desc = "A classic medieval helmet, if you hold it upside down you could see that it's actually a bucket." + icon_state = "knight_greyscale" + inhand_icon_state = "knight_greyscale" + armor = list("melee" = 35, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 10, "rad" = 10, "fire" = 40, "acid" = 40) + material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS //Can change color and add prefix + +/obj/item/clothing/head/helmet/skull + name = "skull helmet" + desc = "An intimidating tribal helmet, it doesn't look very comfortable." + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES + armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + icon_state = "skull" + inhand_icon_state = "skull" + strip_delay = 100 + +/obj/item/clothing/head/helmet/durathread + name = "durathread helmet" + desc = "A helmet made from durathread and leather." + icon_state = "durathread" + inhand_icon_state = "durathread" + resistance_flags = FLAMMABLE + armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 40, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/helmet/rus_helmet + name = "russian helmet" + desc = "It can hold a bottle of vodka." + icon_state = "rus_helmet" + inhand_icon_state = "rus_helmet" + armor = list("melee" = 25, "bullet" = 30, "laser" = 0, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 20, "fire" = 20, "acid" = 50) + pocket_storage_component_path = /datum/component/storage/concrete/pockets/helmet + +/obj/item/clothing/head/helmet/rus_ushanka + name = "battle ushanka" + desc = "100% bear." + icon_state = "rus_ushanka" + inhand_icon_state = "rus_ushanka" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + armor = list("melee" = 25, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 50) + +/obj/item/clothing/head/helmet/infiltrator + name = "infiltrator helmet" + desc = "The galaxy isn't big enough for the two of us." + icon_state = "infiltrator" + inhand_icon_state = "infiltrator" + armor = list("melee" = 40, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + flash_protect = FLASH_PROTECTION_WELDER + flags_inv = HIDEHAIR|HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + strip_delay = 80 + +//monkey sentience caps + +/obj/item/clothing/head/helmet/monkey_sentience + name = "monkey mind magnification helmet" + desc = "A fragile, circuitry embedded helmet for boosting the intelligence of a monkey to a higher level. You see several warning labels..." + + icon_state = "monkeymind" + inhand_icon_state = "monkeymind" + strip_delay = 100 + var/mob/living/carbon/monkey/magnification = null ///if the helmet is on a valid target (just works like a normal helmet if not (cargo please stop)) + var/polling = FALSE///if the helmet is currently polling for targets (special code for removal) + var/light_colors = 1 ///which icon state color this is (red, blue, yellow) + +/obj/item/clothing/head/helmet/monkey_sentience/Initialize() + . = ..() + light_colors = rand(1,3) + update_icon_state() + +/obj/item/clothing/head/helmet/monkey_sentience/examine(mob/user) + . = ..() + . += "---WARNING: REMOVAL OF HELMET ON SUBJECT MAY LEAD TO:---" + . += "BLOOD RAGE" + . += "BRAIN DEATH" + . += "PRIMAL GENE ACTIVATION" + . += "GENETIC MAKEUP MASS SUSCEPTIBILITY" + . += "Ask your CMO if mind magnification is right for you." + +/obj/item/clothing/head/helmet/monkey_sentience/update_icon_state() + icon_state = "[initial(icon_state)][light_colors][magnification ? "up" : ""]" + +/obj/item/clothing/head/helmet/monkey_sentience/equipped(mob/user, slot) + . = ..() + if(slot != ITEM_SLOT_HEAD) + return + if(!ismonkey(user) || user.ckey) + var/mob/living/something = user + to_chat(something, "You feel a stabbing pain in the back of your head for a moment.") + something.apply_damage(5,BRUTE,BODY_ZONE_HEAD,FALSE,FALSE,FALSE) //notably: no damage resist (it's in your helmet), no damage spread (it's in your helmet) + playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + return + magnification = user //this polls ghosts + visible_message("[src] powers up!") + playsound(src, 'sound/machines/ping.ogg', 30, TRUE) + polling = TRUE + var/list/candidates = pollCandidatesForMob("Do you want to play as a mind magnified monkey?", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, magnification, POLL_IGNORE_SENTIENCE_POTION) + polling = FALSE + if(!candidates.len) + magnification = null + visible_message("[src] falls silent and drops on the floor. Maybe you should try again later?") + playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + user.dropItemToGround(src) + var/mob/picked = pick(candidates) + magnification.key = picked.key + playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE) + to_chat(magnification, "You're a mind magnified monkey! Protect your helmet with your life- if you lose it, your sentience goes with it!") + var/policy = get_policy(ROLE_MONKEY_HELMET) + if(policy) + to_chat(magnification, policy) + icon_state = "[icon_state]up" + +/obj/item/clothing/head/helmet/monkey_sentience/Destroy() + disconnect() + return ..() + +/obj/item/clothing/head/helmet/monkey_sentience/proc/disconnect() + if(!magnification) //not put on a viable head + return + if(!polling)//put on a viable head, but taken off after polling finished. + if(magnification.client) + to_chat(magnification, "You feel your flicker of sentience ripped away from you, as everything becomes dim...") + magnification.ghostize(FALSE) + if(prob(10)) + switch(rand(1,4)) + if(1) //blood rage + magnification.aggressive = TRUE + if(2) //brain death + magnification.apply_damage(500,BRAIN,BODY_ZONE_HEAD,FALSE,FALSE,FALSE) + if(3) //primal gene (gorilla) + magnification.gorillize() + if(4) //genetic mass susceptibility (gib) + magnification.gib() + //either used up correctly or taken off before polling finished (punish this by destroying the helmet) + playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + playsound(src, "sparks", 100, TRUE) + visible_message("[src] fizzles and breaks apart!") + magnification = null + new /obj/effect/decal/cleanable/ash/crematorium(drop_location()) //just in case they're in a locker or other containers it needs to use crematorium ash, see the path itself for an explanation + +/obj/item/clothing/head/helmet/monkey_sentience/dropped(mob/user) + . = ..() + if(magnification || polling) + qdel(src)//runs disconnect code + + +//LightToggle + +/obj/item/clothing/head/helmet/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/clothing/head/helmet/update_icon_state() + var/state = "[initial(icon_state)]" + if(attached_light) + if(attached_light.on) + state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on" + else + state += "-flight" //etc. + + icon_state = state + +/obj/item/clothing/head/helmet/ui_action_click(mob/user, action) + if(istype(action, alight)) + toggle_helmlight() + else + ..() + +/obj/item/clothing/head/helmet/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/flashlight/seclite)) + var/obj/item/flashlight/seclite/S = I + if(can_flashlight && !attached_light) + if(!user.transferItemToLoc(S, src)) + return + to_chat(user, "You click [S] into place on [src].") + if(S.on) + set_light(0) + attached_light = S + update_icon() + update_helmlight() + alight = new(src) + if(loc == user) + alight.Grant(user) + return + return ..() + +/obj/item/clothing/head/helmet/screwdriver_act(mob/living/user, obj/item/I) + . = ..() + if(can_flashlight && attached_light) //if it has a light but can_flashlight is false, the light is permanently attached. + I.play_tool_sound(src) + to_chat(user, "You unscrew [attached_light] from [src].") + attached_light.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(attached_light) + + var/obj/item/flashlight/removed_light = attached_light + attached_light = null + update_helmlight() + removed_light.update_brightness(user) + update_icon() + user.update_inv_head() + QDEL_NULL(alight) + return TRUE + +/obj/item/clothing/head/helmet/proc/toggle_helmlight() + set name = "Toggle Helmetlight" + set category = "Object" + set desc = "Click to toggle your helmet's attached flashlight." + + if(!attached_light) + return + + var/mob/user = usr + if(user.incapacitated()) + return + attached_light.on = !attached_light.on + to_chat(user, "You toggle the helmet-light [attached_light.on ? "on":"off"].") + + playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) + update_helmlight() + +/obj/item/clothing/head/helmet/proc/update_helmlight() + if(attached_light) + if(attached_light.on) + set_light(attached_light.brightness_on) + else + set_light(0) + update_icon() + + else + set_light(0) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index 772e40d3e9b..c6083203853 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -1,270 +1,270 @@ -//defines the drill hat's yelling setting -#define DRILL_DEFAULT "default" -#define DRILL_SHOUTING "shouting" -#define DRILL_YELLING "yelling" -#define DRILL_CANADIAN "canadian" - -//Chef -/obj/item/clothing/head/chefhat - name = "chef's hat" - inhand_icon_state = "chef" - icon_state = "chef" - desc = "The commander in chef's head wear." - strip_delay = 10 - equip_delay_other = 10 - dynamic_hair_suffix = "" - dog_fashion = /datum/dog_fashion/head/chef - -/obj/item/clothing/head/chefhat/suicide_act(mob/user) - user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to become a chef.") - user.say("Bork Bork Bork!", forced = "chef hat suicide") - sleep(20) - user.visible_message("[user] climbs into an imaginary oven!") - user.say("BOOORK!", forced = "chef hat suicide") - playsound(user, 'sound/machines/ding.ogg', 50, TRUE) - return(FIRELOSS) - -//Captain -/obj/item/clothing/head/caphat - name = "captain's hat" - desc = "It's good being the king." - icon_state = "captain" - inhand_icon_state = "that" - flags_inv = 0 - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/captain - -//Captain: This is no longer space-worthy -/obj/item/clothing/head/caphat/parade - name = "captain's parade cap" - desc = "Worn only by Captains with an abundance of class." - icon_state = "capcap" - - dog_fashion = null - - -//Head of Personnel -/obj/item/clothing/head/hopcap - name = "head of personnel's cap" - icon_state = "hopcap" - desc = "The symbol of true bureaucratic micromanagement." - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - dog_fashion = /datum/dog_fashion/head/hop - -//Chaplain -/obj/item/clothing/head/nun_hood - name = "nun hood" - desc = "Maximum piety in this star system." - icon_state = "nun_hood" - flags_inv = HIDEHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/bishopmitre - name = "bishop mitre" - desc = "An opulent hat that functions as a radio to God. Or as a lightning rod, depending on who you ask." - icon_state = "bishopmitre" - -//Detective -/obj/item/clothing/head/fedora/det_hat - name = "detective's fedora" - desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." - armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) - icon_state = "detective" - var/candy_cooldown = 0 - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora/detective - dog_fashion = /datum/dog_fashion/head/detective - -/obj/item/clothing/head/fedora/det_hat/Initialize() - . = ..() - new /obj/item/reagent_containers/food/drinks/flask/det(src) - -/obj/item/clothing/head/fedora/det_hat/examine(mob/user) - . = ..() - . += "Alt-click to take a candy corn." - -/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - ..() - if(loc == user) - if(candy_cooldown < world.time) - var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) - user.put_in_hands(CC) - to_chat(user, "You slip a candy corn from your hat.") - candy_cooldown = world.time+1200 - else - to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") - - -//Mime -/obj/item/clothing/head/beret - name = "beret" - desc = "A beret, a mime's favorite headwear." - icon_state = "beret" - dog_fashion = /datum/dog_fashion/head/beret - dynamic_hair_suffix = "" - -/obj/item/clothing/head/beret/vintage - name = "vintage beret" - desc = "A well-worn beret." - icon_state = "vintageberet" - dog_fashion = null - -/obj/item/clothing/head/beret/archaic - name = "archaic beret" - desc = "An absolutely ancient beret, allegedly worn by the first mime to ever step foot on a NanoTrasen station." - icon_state = "archaicberet" - dog_fashion = null - -/obj/item/clothing/head/beret/black - name = "black beret" - desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." - icon_state = "beretblack" - -/obj/item/clothing/head/beret/highlander - desc = "That was white fabric. Was." - dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES - -/obj/item/clothing/head/beret/highlander/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) - -/obj/item/clothing/head/beret/durathread - name = "durathread beret" - desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "beretdurathread" - armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 25, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) - -//Security - -/obj/item/clothing/head/hos - name = "head of security cap" - desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." - icon_state = "hoscap" - armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) - strip_delay = 80 - dynamic_hair_suffix = "" - -/obj/item/clothing/head/hos/syndicate - name = "syndicate cap" - desc = "A black cap fit for a high ranking syndicate officer." - -/obj/item/clothing/head/hos/beret - name = "head of security beret" - desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." - icon_state = "hosberetblack" - -/obj/item/clothing/head/hos/beret/syndicate - name = "syndicate beret" - desc = "A black beret with thick armor padding inside. Stylish and robust." - -/obj/item/clothing/head/warden - name = "warden's police hat" - desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." - icon_state = "policehelm" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/warden - -/obj/item/clothing/head/warden/drill - name = "warden's campaign hat" - desc = "A special armored campaign hat with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." - icon_state = "wardendrill" - inhand_icon_state = "wardendrill" - dog_fashion = null - var/mode = DRILL_DEFAULT - -/obj/item/clothing/head/warden/drill/screwdriver_act(mob/living/carbon/human/user, obj/item/I) - if(..()) - return TRUE - switch(mode) - if(DRILL_DEFAULT) - to_chat(user, "You set the voice circuit to the middle position.") - mode = DRILL_SHOUTING - if(DRILL_SHOUTING) - to_chat(user, "You set the voice circuit to the last position.") - mode = DRILL_YELLING - if(DRILL_YELLING) - to_chat(user, "You set the voice circuit to the first position.") - mode = DRILL_DEFAULT - if(DRILL_CANADIAN) - to_chat(user, "You adjust voice circuit but nothing happens, probably because it's broken.") - return TRUE - -/obj/item/clothing/head/warden/drill/wirecutter_act(mob/living/user, obj/item/I) - ..() - if(mode != DRILL_CANADIAN) - to_chat(user, "You broke the voice circuit!") - mode = DRILL_CANADIAN - return TRUE - -/obj/item/clothing/head/warden/drill/equipped(mob/M, slot) - . = ..() - if (slot == ITEM_SLOT_HEAD) - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) - else - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/warden/drill/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/warden/drill/proc/handle_speech(datum/source, mob/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - switch (mode) - if(DRILL_SHOUTING) - message += "!" - if(DRILL_YELLING) - message += "!!" - if(DRILL_CANADIAN) - message = " [message]" - var/list/canadian_words = strings("canadian_replacement.json", "canadian") - - for(var/key in canadian_words) - var/value = canadian_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(30)) - message += pick(", eh?", ", EH?") - speech_args[SPEECH_MESSAGE] = message - -/obj/item/clothing/head/beret/sec - name = "security beret" - desc = "A robust beret with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." - icon_state = "beret_badge" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/beret/sec/navyhos - name = "head of security's beret" - desc = "A special beret with the Head of Security's insignia emblazoned on it. A symbol of excellence, a badge of courage, a mark of distinction." - icon_state = "hosberet" - -/obj/item/clothing/head/beret/sec/navywarden - name = "warden's beret" - desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." - icon_state = "wardenberet" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/beret/sec/navyofficer - desc = "A special beret with the security insignia emblazoned on it. For officers with class." - icon_state = "officerberet" - -//Curator -/obj/item/clothing/head/fedora/curator - name = "treasure hunter's fedora" - desc = "You got red text today kid, but it doesn't mean you have to like it." - icon_state = "curator" - -#undef DRILL_DEFAULT -#undef DRILL_SHOUTING -#undef DRILL_YELLING -#undef DRILL_CANADIAN +//defines the drill hat's yelling setting +#define DRILL_DEFAULT "default" +#define DRILL_SHOUTING "shouting" +#define DRILL_YELLING "yelling" +#define DRILL_CANADIAN "canadian" + +//Chef +/obj/item/clothing/head/chefhat + name = "chef's hat" + inhand_icon_state = "chef" + icon_state = "chef" + desc = "The commander in chef's head wear." + strip_delay = 10 + equip_delay_other = 10 + dynamic_hair_suffix = "" + dog_fashion = /datum/dog_fashion/head/chef + +/obj/item/clothing/head/chefhat/suicide_act(mob/user) + user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to become a chef.") + user.say("Bork Bork Bork!", forced = "chef hat suicide") + sleep(20) + user.visible_message("[user] climbs into an imaginary oven!") + user.say("BOOORK!", forced = "chef hat suicide") + playsound(user, 'sound/machines/ding.ogg', 50, TRUE) + return(FIRELOSS) + +//Captain +/obj/item/clothing/head/caphat + name = "captain's hat" + desc = "It's good being the king." + icon_state = "captain" + inhand_icon_state = "that" + flags_inv = 0 + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/captain + +//Captain: This is no longer space-worthy +/obj/item/clothing/head/caphat/parade + name = "captain's parade cap" + desc = "Worn only by Captains with an abundance of class." + icon_state = "capcap" + + dog_fashion = null + + +//Head of Personnel +/obj/item/clothing/head/hopcap + name = "head of personnel's cap" + icon_state = "hopcap" + desc = "The symbol of true bureaucratic micromanagement." + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + dog_fashion = /datum/dog_fashion/head/hop + +//Chaplain +/obj/item/clothing/head/nun_hood + name = "nun hood" + desc = "Maximum piety in this star system." + icon_state = "nun_hood" + flags_inv = HIDEHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/bishopmitre + name = "bishop mitre" + desc = "An opulent hat that functions as a radio to God. Or as a lightning rod, depending on who you ask." + icon_state = "bishopmitre" + +//Detective +/obj/item/clothing/head/fedora/det_hat + name = "detective's fedora" + desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." + armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) + icon_state = "detective" + var/candy_cooldown = 0 + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora/detective + dog_fashion = /datum/dog_fashion/head/detective + +/obj/item/clothing/head/fedora/det_hat/Initialize() + . = ..() + new /obj/item/reagent_containers/food/drinks/flask/det(src) + +/obj/item/clothing/head/fedora/det_hat/examine(mob/user) + . = ..() + . += "Alt-click to take a candy corn." + +/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + ..() + if(loc == user) + if(candy_cooldown < world.time) + var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) + user.put_in_hands(CC) + to_chat(user, "You slip a candy corn from your hat.") + candy_cooldown = world.time+1200 + else + to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") + + +//Mime +/obj/item/clothing/head/beret + name = "beret" + desc = "A beret, a mime's favorite headwear." + icon_state = "beret" + dog_fashion = /datum/dog_fashion/head/beret + dynamic_hair_suffix = "" + +/obj/item/clothing/head/beret/vintage + name = "vintage beret" + desc = "A well-worn beret." + icon_state = "vintageberet" + dog_fashion = null + +/obj/item/clothing/head/beret/archaic + name = "archaic beret" + desc = "An absolutely ancient beret, allegedly worn by the first mime to ever step foot on a NanoTrasen station." + icon_state = "archaicberet" + dog_fashion = null + +/obj/item/clothing/head/beret/black + name = "black beret" + desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." + icon_state = "beretblack" + +/obj/item/clothing/head/beret/highlander + desc = "That was white fabric. Was." + dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES + +/obj/item/clothing/head/beret/highlander/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) + +/obj/item/clothing/head/beret/durathread + name = "durathread beret" + desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "beretdurathread" + armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 25, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) + +//Security + +/obj/item/clothing/head/hos + name = "head of security cap" + desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." + icon_state = "hoscap" + armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) + strip_delay = 80 + dynamic_hair_suffix = "" + +/obj/item/clothing/head/hos/syndicate + name = "syndicate cap" + desc = "A black cap fit for a high ranking syndicate officer." + +/obj/item/clothing/head/hos/beret + name = "head of security beret" + desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." + icon_state = "hosberetblack" + +/obj/item/clothing/head/hos/beret/syndicate + name = "syndicate beret" + desc = "A black beret with thick armor padding inside. Stylish and robust." + +/obj/item/clothing/head/warden + name = "warden's police hat" + desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." + icon_state = "policehelm" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/warden + +/obj/item/clothing/head/warden/drill + name = "warden's campaign hat" + desc = "A special armored campaign hat with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." + icon_state = "wardendrill" + inhand_icon_state = "wardendrill" + dog_fashion = null + var/mode = DRILL_DEFAULT + +/obj/item/clothing/head/warden/drill/screwdriver_act(mob/living/carbon/human/user, obj/item/I) + if(..()) + return TRUE + switch(mode) + if(DRILL_DEFAULT) + to_chat(user, "You set the voice circuit to the middle position.") + mode = DRILL_SHOUTING + if(DRILL_SHOUTING) + to_chat(user, "You set the voice circuit to the last position.") + mode = DRILL_YELLING + if(DRILL_YELLING) + to_chat(user, "You set the voice circuit to the first position.") + mode = DRILL_DEFAULT + if(DRILL_CANADIAN) + to_chat(user, "You adjust voice circuit but nothing happens, probably because it's broken.") + return TRUE + +/obj/item/clothing/head/warden/drill/wirecutter_act(mob/living/user, obj/item/I) + ..() + if(mode != DRILL_CANADIAN) + to_chat(user, "You broke the voice circuit!") + mode = DRILL_CANADIAN + return TRUE + +/obj/item/clothing/head/warden/drill/equipped(mob/M, slot) + . = ..() + if (slot == ITEM_SLOT_HEAD) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/warden/drill/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/warden/drill/proc/handle_speech(datum/source, mob/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + switch (mode) + if(DRILL_SHOUTING) + message += "!" + if(DRILL_YELLING) + message += "!!" + if(DRILL_CANADIAN) + message = " [message]" + var/list/canadian_words = strings("canadian_replacement.json", "canadian") + + for(var/key in canadian_words) + var/value = canadian_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(30)) + message += pick(", eh?", ", EH?") + speech_args[SPEECH_MESSAGE] = message + +/obj/item/clothing/head/beret/sec + name = "security beret" + desc = "A robust beret with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." + icon_state = "beret_badge" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/beret/sec/navyhos + name = "head of security's beret" + desc = "A special beret with the Head of Security's insignia emblazoned on it. A symbol of excellence, a badge of courage, a mark of distinction." + icon_state = "hosberet" + +/obj/item/clothing/head/beret/sec/navywarden + name = "warden's beret" + desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." + icon_state = "wardenberet" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/beret/sec/navyofficer + desc = "A special beret with the security insignia emblazoned on it. For officers with class." + icon_state = "officerberet" + +//Curator +/obj/item/clothing/head/fedora/curator + name = "treasure hunter's fedora" + desc = "You got red text today kid, but it doesn't mean you have to like it." + icon_state = "curator" + +#undef DRILL_DEFAULT +#undef DRILL_SHOUTING +#undef DRILL_YELLING +#undef DRILL_CANADIAN diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index 0f2a4c86eb6..e6e69317619 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -1,475 +1,475 @@ - - -/obj/item/clothing/head/centhat - name = "\improper CentCom hat" - icon_state = "centcom" - desc = "It's good to be emperor." - inhand_icon_state = "that" - flags_inv = 0 - armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 80 - -/obj/item/clothing/head/spacepolice - name = "space police cap" - desc = "A blue cap for patrolling the daily beat." - icon_state = "policecap_families" - inhand_icon_state = "policecap_families" - -/obj/item/clothing/head/powdered_wig - name = "powdered wig" - desc = "A powdered wig." - icon_state = "pwig" - inhand_icon_state = "pwig" - -/obj/item/clothing/head/that - name = "top-hat" - desc = "It's an amish looking hat." - icon_state = "tophat" - inhand_icon_state = "that" - dog_fashion = /datum/dog_fashion/head - throwforce = 1 - -/obj/item/clothing/head/canada - name = "striped red tophat" - desc = "It smells like fresh donut holes. / Il sent comme des trous de beignets frais." - icon_state = "canada" - inhand_icon_state = "canada" - -/obj/item/clothing/head/redcoat - name = "redcoat's hat" - icon_state = "redcoat" - desc = "'I guess it's a redhead.'" - -/obj/item/clothing/head/mailman - name = "mailman's hat" - icon_state = "mailman" - desc = "'Right-on-time' mail service head wear." - -/obj/item/clothing/head/plaguedoctorhat - name = "plague doctor's hat" - desc = "These were once used by plague doctors. They're pretty much useless." - icon_state = "plaguedoctor" - permeability_coefficient = 0.01 - -/obj/item/clothing/head/hasturhood - name = "hastur's hood" - desc = "It's unspeakably stylish." - icon_state = "hasturhood" - flags_inv = HIDEHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/nursehat - name = "nurse's hat" - desc = "It allows quick identification of trained medical personnel." - icon_state = "nursehat" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/nurse - -/obj/item/clothing/head/syndicatefake - name = "black space-helmet replica" - icon_state = "syndicate-helm-black-red" - inhand_icon_state = "syndicate-helm-black-red" - desc = "A plastic replica of a Syndicate agent's space helmet. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/cueball - name = "cueball helmet" - desc = "A large, featureless white orb meant to be worn on your head. How do you even see out of this thing?" - icon_state = "cueball" - inhand_icon_state="cueball" - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES|HEADCOVERSMOUTH - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/snowman - name = "Snowman Head" - desc = "A ball of white styrofoam. So festive." - icon_state = "snowman_h" - inhand_icon_state = "snowman_h" - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/justice - name = "justice hat" - desc = "Fight for what's righteous!" - icon_state = "justicered" - inhand_icon_state = "justicered" - clothing_flags = SNUG_FIT - flags_inv = HIDEHAIR|HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/justice/blue - icon_state = "justiceblue" - inhand_icon_state = "justiceblue" - -/obj/item/clothing/head/justice/yellow - icon_state = "justiceyellow" - inhand_icon_state = "justiceyellow" - -/obj/item/clothing/head/justice/green - icon_state = "justicegreen" - inhand_icon_state = "justicegreen" - -/obj/item/clothing/head/justice/pink - icon_state = "justicepink" - inhand_icon_state = "justicepink" - -/obj/item/clothing/head/rabbitears - name = "rabbit ears" - desc = "Wearing these makes you look useless, and only good for your sex appeal." - icon_state = "bunny" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/rabbit - -/obj/item/clothing/head/pirate - name = "pirate hat" - desc = "Yarr." - icon_state = "pirate" - inhand_icon_state = "pirate" - dog_fashion = /datum/dog_fashion/head/pirate - -/obj/item/clothing/head/pirate - var/datum/language/piratespeak/L = new - -/obj/item/clothing/head/pirate/equipped(mob/user, slot) - . = ..() - if(!ishuman(user)) - return - if(slot == ITEM_SLOT_HEAD) - user.grant_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) - to_chat(user, "You suddenly know how to speak like a pirate!") - -/obj/item/clothing/head/pirate/dropped(mob/user) - . = ..() - if(!ishuman(user)) - return - var/mob/living/carbon/human/H = user - if(H.get_item_by_slot(ITEM_SLOT_HEAD) == src) - user.remove_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) - to_chat(user, "You can no longer speak like a pirate.") - -/obj/item/clothing/head/pirate/captain - name = "pirate captain hat" - icon_state = "hgpiratecap" - inhand_icon_state = "hgpiratecap" - -/obj/item/clothing/head/bandana - name = "pirate bandana" - desc = "Yarr." - icon_state = "bandana" - inhand_icon_state = "bandana" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/bowler - name = "bowler-hat" - desc = "Gentleman, elite aboard!" - icon_state = "bowler" - inhand_icon_state = "bowler" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/witchwig - name = "witch costume wig" - desc = "Eeeee~heheheheheheh!" - icon_state = "witch" - inhand_icon_state = "witch" - flags_inv = HIDEHAIR - -/obj/item/clothing/head/chicken - name = "chicken suit head" - desc = "Bkaw!" - icon_state = "chickenhead" - inhand_icon_state = "chickensuit" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/griffin - name = "griffon head" - desc = "Why not 'eagle head'? Who knows." - icon_state = "griffinhat" - inhand_icon_state = "griffinhat" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/bearpelt - name = "bear pelt hat" - desc = "Fuzzy." - icon_state = "bearpelt" - inhand_icon_state = "bearpelt" - -/obj/item/clothing/head/xenos - name = "xenos helmet" - icon_state = "xenos" - inhand_icon_state = "xenos_helm" - desc = "A helmet made out of chitinous alien hide." - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - -/obj/item/clothing/head/fedora - name = "fedora" - icon_state = "fedora" - inhand_icon_state = "fedora" - desc = "A really cool hat if you're a mobster. A really lame hat if you're not." - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora - -/obj/item/clothing/head/fedora/white - name = "white fedora" - icon_state = "fedora_white" - inhand_icon_state = "fedora_white" - -/obj/item/clothing/head/fedora/beige - name = "beige fedora" - icon_state = "fedora_beige" - inhand_icon_state = "fedora_beige" - -/obj/item/clothing/head/fedora/suicide_act(mob/user) - if(user.gender == FEMALE) - return 0 - var/mob/living/carbon/human/H = user - user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to be nice to girls.") - user.say("M'lady.", forced = "fedora suicide") - sleep(10) - H.facial_hairstyle = "Neckbeard" - return(BRUTELOSS) - -/obj/item/clothing/head/sombrero - name = "sombrero" - icon_state = "sombrero" - inhand_icon_state = "sombrero" - desc = "You can practically taste the fiesta." - flags_inv = HIDEHAIR - - dog_fashion = /datum/dog_fashion/head/sombrero - -/obj/item/clothing/head/sombrero/green - name = "green sombrero" - icon_state = "greensombrero" - inhand_icon_state = "greensombrero" - desc = "As elegant as a dancing cactus." - flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS - dog_fashion = null - -/obj/item/clothing/head/sombrero/shamebrero - name = "shamebrero" - icon_state = "shamebrero" - inhand_icon_state = "shamebrero" - desc = "Once it's on, it never comes off." - dog_fashion = null - -/obj/item/clothing/head/sombrero/shamebrero/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) - -/obj/item/clothing/head/flatcap - name = "flat cap" - desc = "A working man's cap." - icon_state = "flat_cap" - inhand_icon_state = "detective" - -/obj/item/clothing/head/hunter - name = "bounty hunting hat" - desc = "Ain't nobody gonna cheat the hangman in my town." - icon_state = "cowboy" - worn_icon_state = "hunter" - inhand_icon_state = "hunter" - armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/cone - desc = "This cone is trying to warn you of something!" - name = "warning cone" - icon = 'icons/obj/janitor.dmi' - icon_state = "cone" - inhand_icon_state = "cone" - force = 1 - throwforce = 3 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("warned", "cautioned", "smashed") - resistance_flags = NONE - dynamic_hair_suffix = "" - -/obj/item/clothing/head/santa - name = "santa hat" - desc = "On the first day of christmas my employer gave to me!" - icon_state = "santahatnorm" - inhand_icon_state = "that" - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - dog_fashion = /datum/dog_fashion/head/santa - -/obj/item/clothing/head/jester - name = "jester hat" - desc = "A hat with bells, to add some merriness to the suit." - icon_state = "jester_hat" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/jester/alt - icon_state = "jester2" - -/obj/item/clothing/head/rice_hat - name = "rice hat" - desc = "Welcome to the rice fields, motherfucker." - icon_state = "rice_hat" - -/obj/item/clothing/head/lizard - name = "lizardskin cloche hat" - desc = "How many lizards died to make this hat? Not enough." - icon_state = "lizard" - -/obj/item/clothing/head/papersack - name = "paper sack hat" - desc = "A paper sack with crude holes cut out for eyes. Useful for hiding one's identity or ugliness." - icon_state = "papersack" - flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS - -/obj/item/clothing/head/papersack/smiley - name = "paper sack hat" - desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." - icon_state = "papersack_smile" - flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS - -/obj/item/clothing/head/crown - name = "crown" - desc = "A crown fit for a king, a petty king maybe." - icon_state = "crown" - armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - dynamic_hair_suffix = "" - -/obj/item/clothing/head/crown/fancy - name = "magnificent crown" - desc = "A crown worn by only the highest emperors of the land space." - icon_state = "fancycrown" - -/obj/item/clothing/head/scarecrow_hat - name = "scarecrow hat" - desc = "A simple straw hat." - icon_state = "scarecrow_hat" - -/obj/item/clothing/head/lobsterhat - name = "foam lobster head" - desc = "When everything's going to crab, protecting your head is the best choice." - icon_state = "lobster_hat" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/drfreezehat - name = "doctor freeze's wig" - desc = "A cool wig for cool people." - icon_state = "drfreeze_hat" - flags_inv = HIDEHAIR - -/obj/item/clothing/head/pharaoh - name = "pharaoh hat" - desc = "Walk like an Egyptian." - icon_state = "pharoah_hat" - inhand_icon_state = "pharoah_hat" - -/obj/item/clothing/head/nemes - name = "headdress of Nemes" - desc = "Lavish space tomb not included." - icon_state = "nemes_headdress" - -/obj/item/clothing/head/delinquent - name = "delinquent hat" - desc = "Good grief." - icon_state = "delinquent" - -/obj/item/clothing/head/frenchberet - name = "french beret" - desc = "A quality beret, infused with the aroma of chain-smoking, wine-swilling Parisians. You feel less inclined to engage military conflict, for some reason." - icon_state = "beret" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/frenchberet/equipped(mob/M, slot) - . = ..() - if (slot == ITEM_SLOT_HEAD) - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) - else - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/frenchberet/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/frenchberet/proc/handle_speech(datum/source, mob/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = " [message]" - var/list/french_words = strings("french_replacement.json", "french") - - for(var/key in french_words) - var/value = french_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(3)) - message += pick(" Honh honh honh!"," Honh!"," Zut Alors!") - speech_args[SPEECH_MESSAGE] = trim(message) - -/obj/item/clothing/head/clownmitre - name = "Hat of the Honkmother" - desc = "It's hard for parishoners to see a banana peel on the floor when they're looking up at your glorious chapeau." - icon_state = "clownmitre" - -/obj/item/clothing/head/kippah - name = "kippah" - desc = "Signals that you follow the Jewish Halakha. Keeps the head covered and the soul extra-Orthodox." - icon_state = "kippah" - -/obj/item/clothing/head/medievaljewhat - name = "medieval Jewish hat" - desc = "A silly looking hat, intended to be placed on the heads of the station's oppressed religious minorities." - icon_state = "medievaljewhat" - -/obj/item/clothing/head/taqiyahwhite - name = "white taqiyah" - desc = "An extra-mustahabb way of showing your devotion to Allah." - icon_state = "taqiyahwhite" - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small - -/obj/item/clothing/head/taqiyahred - name = "red taqiyah" - desc = "An extra-mustahabb way of showing your devotion to Allah." - icon_state = "taqiyahred" - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small - -/obj/item/clothing/head/shrine_wig - name = "shrine maiden's wig" - desc = "Purify in style!" - flags_inv = HIDEHAIR //bald - worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' - icon_state = "shrine_wig" - inhand_icon_state = "shrine_wig" - worn_x_dimension = 64 - worn_y_dimension = 64 - dynamic_hair_suffix = "" - -/obj/item/clothing/head/intern - name = "\improper CentCom Head Intern beancap" - desc = "A horrifying mix of beanie and softcap in CentCom green. You'd have to be pretty desperate for power over your peers to agree to wear this." - icon_state = "intern_hat" - inhand_icon_state = "intern_hat" - -/obj/item/clothing/head/coordinator - name = "coordinator cap" - desc = "A cap for a party ooordinator, stylish!." - icon_state = "capcap" - inhand_icon_state = "that" - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - -/obj/item/clothing/head/jackbros - name = "frosty hat" - desc = "Hee-ho!" - icon_state = "JackFrostHat" - inhand_icon_state = "JackFrostHat" + + +/obj/item/clothing/head/centhat + name = "\improper CentCom hat" + icon_state = "centcom" + desc = "It's good to be emperor." + inhand_icon_state = "that" + flags_inv = 0 + armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 80 + +/obj/item/clothing/head/spacepolice + name = "space police cap" + desc = "A blue cap for patrolling the daily beat." + icon_state = "policecap_families" + inhand_icon_state = "policecap_families" + +/obj/item/clothing/head/powdered_wig + name = "powdered wig" + desc = "A powdered wig." + icon_state = "pwig" + inhand_icon_state = "pwig" + +/obj/item/clothing/head/that + name = "top-hat" + desc = "It's an amish looking hat." + icon_state = "tophat" + inhand_icon_state = "that" + dog_fashion = /datum/dog_fashion/head + throwforce = 1 + +/obj/item/clothing/head/canada + name = "striped red tophat" + desc = "It smells like fresh donut holes. / Il sent comme des trous de beignets frais." + icon_state = "canada" + inhand_icon_state = "canada" + +/obj/item/clothing/head/redcoat + name = "redcoat's hat" + icon_state = "redcoat" + desc = "'I guess it's a redhead.'" + +/obj/item/clothing/head/mailman + name = "mailman's hat" + icon_state = "mailman" + desc = "'Right-on-time' mail service head wear." + +/obj/item/clothing/head/plaguedoctorhat + name = "plague doctor's hat" + desc = "These were once used by plague doctors. They're pretty much useless." + icon_state = "plaguedoctor" + permeability_coefficient = 0.01 + +/obj/item/clothing/head/hasturhood + name = "hastur's hood" + desc = "It's unspeakably stylish." + icon_state = "hasturhood" + flags_inv = HIDEHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/nursehat + name = "nurse's hat" + desc = "It allows quick identification of trained medical personnel." + icon_state = "nursehat" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/nurse + +/obj/item/clothing/head/syndicatefake + name = "black space-helmet replica" + icon_state = "syndicate-helm-black-red" + inhand_icon_state = "syndicate-helm-black-red" + desc = "A plastic replica of a Syndicate agent's space helmet. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/cueball + name = "cueball helmet" + desc = "A large, featureless white orb meant to be worn on your head. How do you even see out of this thing?" + icon_state = "cueball" + inhand_icon_state="cueball" + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES|HEADCOVERSMOUTH + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/snowman + name = "Snowman Head" + desc = "A ball of white styrofoam. So festive." + icon_state = "snowman_h" + inhand_icon_state = "snowman_h" + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/justice + name = "justice hat" + desc = "Fight for what's righteous!" + icon_state = "justicered" + inhand_icon_state = "justicered" + clothing_flags = SNUG_FIT + flags_inv = HIDEHAIR|HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/justice/blue + icon_state = "justiceblue" + inhand_icon_state = "justiceblue" + +/obj/item/clothing/head/justice/yellow + icon_state = "justiceyellow" + inhand_icon_state = "justiceyellow" + +/obj/item/clothing/head/justice/green + icon_state = "justicegreen" + inhand_icon_state = "justicegreen" + +/obj/item/clothing/head/justice/pink + icon_state = "justicepink" + inhand_icon_state = "justicepink" + +/obj/item/clothing/head/rabbitears + name = "rabbit ears" + desc = "Wearing these makes you look useless, and only good for your sex appeal." + icon_state = "bunny" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/rabbit + +/obj/item/clothing/head/pirate + name = "pirate hat" + desc = "Yarr." + icon_state = "pirate" + inhand_icon_state = "pirate" + dog_fashion = /datum/dog_fashion/head/pirate + +/obj/item/clothing/head/pirate + var/datum/language/piratespeak/L = new + +/obj/item/clothing/head/pirate/equipped(mob/user, slot) + . = ..() + if(!ishuman(user)) + return + if(slot == ITEM_SLOT_HEAD) + user.grant_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + to_chat(user, "You suddenly know how to speak like a pirate!") + +/obj/item/clothing/head/pirate/dropped(mob/user) + . = ..() + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(H.get_item_by_slot(ITEM_SLOT_HEAD) == src) + user.remove_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + to_chat(user, "You can no longer speak like a pirate.") + +/obj/item/clothing/head/pirate/captain + name = "pirate captain hat" + icon_state = "hgpiratecap" + inhand_icon_state = "hgpiratecap" + +/obj/item/clothing/head/bandana + name = "pirate bandana" + desc = "Yarr." + icon_state = "bandana" + inhand_icon_state = "bandana" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/bowler + name = "bowler-hat" + desc = "Gentleman, elite aboard!" + icon_state = "bowler" + inhand_icon_state = "bowler" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/witchwig + name = "witch costume wig" + desc = "Eeeee~heheheheheheh!" + icon_state = "witch" + inhand_icon_state = "witch" + flags_inv = HIDEHAIR + +/obj/item/clothing/head/chicken + name = "chicken suit head" + desc = "Bkaw!" + icon_state = "chickenhead" + inhand_icon_state = "chickensuit" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/griffin + name = "griffon head" + desc = "Why not 'eagle head'? Who knows." + icon_state = "griffinhat" + inhand_icon_state = "griffinhat" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/bearpelt + name = "bear pelt hat" + desc = "Fuzzy." + icon_state = "bearpelt" + inhand_icon_state = "bearpelt" + +/obj/item/clothing/head/xenos + name = "xenos helmet" + icon_state = "xenos" + inhand_icon_state = "xenos_helm" + desc = "A helmet made out of chitinous alien hide." + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + +/obj/item/clothing/head/fedora + name = "fedora" + icon_state = "fedora" + inhand_icon_state = "fedora" + desc = "A really cool hat if you're a mobster. A really lame hat if you're not." + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora + +/obj/item/clothing/head/fedora/white + name = "white fedora" + icon_state = "fedora_white" + inhand_icon_state = "fedora_white" + +/obj/item/clothing/head/fedora/beige + name = "beige fedora" + icon_state = "fedora_beige" + inhand_icon_state = "fedora_beige" + +/obj/item/clothing/head/fedora/suicide_act(mob/user) + if(user.gender == FEMALE) + return 0 + var/mob/living/carbon/human/H = user + user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to be nice to girls.") + user.say("M'lady.", forced = "fedora suicide") + sleep(10) + H.facial_hairstyle = "Neckbeard" + return(BRUTELOSS) + +/obj/item/clothing/head/sombrero + name = "sombrero" + icon_state = "sombrero" + inhand_icon_state = "sombrero" + desc = "You can practically taste the fiesta." + flags_inv = HIDEHAIR + + dog_fashion = /datum/dog_fashion/head/sombrero + +/obj/item/clothing/head/sombrero/green + name = "green sombrero" + icon_state = "greensombrero" + inhand_icon_state = "greensombrero" + desc = "As elegant as a dancing cactus." + flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS + dog_fashion = null + +/obj/item/clothing/head/sombrero/shamebrero + name = "shamebrero" + icon_state = "shamebrero" + inhand_icon_state = "shamebrero" + desc = "Once it's on, it never comes off." + dog_fashion = null + +/obj/item/clothing/head/sombrero/shamebrero/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) + +/obj/item/clothing/head/flatcap + name = "flat cap" + desc = "A working man's cap." + icon_state = "flat_cap" + inhand_icon_state = "detective" + +/obj/item/clothing/head/hunter + name = "bounty hunting hat" + desc = "Ain't nobody gonna cheat the hangman in my town." + icon_state = "cowboy" + worn_icon_state = "hunter" + inhand_icon_state = "hunter" + armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/cone + desc = "This cone is trying to warn you of something!" + name = "warning cone" + icon = 'icons/obj/janitor.dmi' + icon_state = "cone" + inhand_icon_state = "cone" + force = 1 + throwforce = 3 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("warned", "cautioned", "smashed") + resistance_flags = NONE + dynamic_hair_suffix = "" + +/obj/item/clothing/head/santa + name = "santa hat" + desc = "On the first day of christmas my employer gave to me!" + icon_state = "santahatnorm" + inhand_icon_state = "that" + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + dog_fashion = /datum/dog_fashion/head/santa + +/obj/item/clothing/head/jester + name = "jester hat" + desc = "A hat with bells, to add some merriness to the suit." + icon_state = "jester_hat" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/jester/alt + icon_state = "jester2" + +/obj/item/clothing/head/rice_hat + name = "rice hat" + desc = "Welcome to the rice fields, motherfucker." + icon_state = "rice_hat" + +/obj/item/clothing/head/lizard + name = "lizardskin cloche hat" + desc = "How many lizards died to make this hat? Not enough." + icon_state = "lizard" + +/obj/item/clothing/head/papersack + name = "paper sack hat" + desc = "A paper sack with crude holes cut out for eyes. Useful for hiding one's identity or ugliness." + icon_state = "papersack" + flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS + +/obj/item/clothing/head/papersack/smiley + name = "paper sack hat" + desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." + icon_state = "papersack_smile" + flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS + +/obj/item/clothing/head/crown + name = "crown" + desc = "A crown fit for a king, a petty king maybe." + icon_state = "crown" + armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + dynamic_hair_suffix = "" + +/obj/item/clothing/head/crown/fancy + name = "magnificent crown" + desc = "A crown worn by only the highest emperors of the land space." + icon_state = "fancycrown" + +/obj/item/clothing/head/scarecrow_hat + name = "scarecrow hat" + desc = "A simple straw hat." + icon_state = "scarecrow_hat" + +/obj/item/clothing/head/lobsterhat + name = "foam lobster head" + desc = "When everything's going to crab, protecting your head is the best choice." + icon_state = "lobster_hat" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/drfreezehat + name = "doctor freeze's wig" + desc = "A cool wig for cool people." + icon_state = "drfreeze_hat" + flags_inv = HIDEHAIR + +/obj/item/clothing/head/pharaoh + name = "pharaoh hat" + desc = "Walk like an Egyptian." + icon_state = "pharoah_hat" + inhand_icon_state = "pharoah_hat" + +/obj/item/clothing/head/nemes + name = "headdress of Nemes" + desc = "Lavish space tomb not included." + icon_state = "nemes_headdress" + +/obj/item/clothing/head/delinquent + name = "delinquent hat" + desc = "Good grief." + icon_state = "delinquent" + +/obj/item/clothing/head/frenchberet + name = "french beret" + desc = "A quality beret, infused with the aroma of chain-smoking, wine-swilling Parisians. You feel less inclined to engage military conflict, for some reason." + icon_state = "beret" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/frenchberet/equipped(mob/M, slot) + . = ..() + if (slot == ITEM_SLOT_HEAD) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/frenchberet/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/frenchberet/proc/handle_speech(datum/source, mob/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = " [message]" + var/list/french_words = strings("french_replacement.json", "french") + + for(var/key in french_words) + var/value = french_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(3)) + message += pick(" Honh honh honh!"," Honh!"," Zut Alors!") + speech_args[SPEECH_MESSAGE] = trim(message) + +/obj/item/clothing/head/clownmitre + name = "Hat of the Honkmother" + desc = "It's hard for parishoners to see a banana peel on the floor when they're looking up at your glorious chapeau." + icon_state = "clownmitre" + +/obj/item/clothing/head/kippah + name = "kippah" + desc = "Signals that you follow the Jewish Halakha. Keeps the head covered and the soul extra-Orthodox." + icon_state = "kippah" + +/obj/item/clothing/head/medievaljewhat + name = "medieval Jewish hat" + desc = "A silly looking hat, intended to be placed on the heads of the station's oppressed religious minorities." + icon_state = "medievaljewhat" + +/obj/item/clothing/head/taqiyahwhite + name = "white taqiyah" + desc = "An extra-mustahabb way of showing your devotion to Allah." + icon_state = "taqiyahwhite" + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small + +/obj/item/clothing/head/taqiyahred + name = "red taqiyah" + desc = "An extra-mustahabb way of showing your devotion to Allah." + icon_state = "taqiyahred" + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small + +/obj/item/clothing/head/shrine_wig + name = "shrine maiden's wig" + desc = "Purify in style!" + flags_inv = HIDEHAIR //bald + worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' + icon_state = "shrine_wig" + inhand_icon_state = "shrine_wig" + worn_x_dimension = 64 + worn_y_dimension = 64 + dynamic_hair_suffix = "" + +/obj/item/clothing/head/intern + name = "\improper CentCom Head Intern beancap" + desc = "A horrifying mix of beanie and softcap in CentCom green. You'd have to be pretty desperate for power over your peers to agree to wear this." + icon_state = "intern_hat" + inhand_icon_state = "intern_hat" + +/obj/item/clothing/head/coordinator + name = "coordinator cap" + desc = "A cap for a party ooordinator, stylish!." + icon_state = "capcap" + inhand_icon_state = "that" + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + +/obj/item/clothing/head/jackbros + name = "frosty hat" + desc = "Hee-ho!" + icon_state = "JackFrostHat" + inhand_icon_state = "JackFrostHat" diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 057eee0f9a2..18bbbb86580 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -1,373 +1,373 @@ -/* - * Contents: - * Welding mask - * Cakehat - * Ushanka - * Pumpkin head - * Kitty ears - * Cardborg disguise - * Wig - * Bronze hat - */ - -/* - * Welding mask - */ -/obj/item/clothing/head/welding - name = "welding helmet" - desc = "A head-mounted face cover designed to protect the wearer completely from space-arc eye." - icon_state = "welding" - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - inhand_icon_state = "welding" - custom_materials = list(/datum/material/iron=1750, /datum/material/glass=400) - flash_protect = FLASH_PROTECTION_WELDER - tint = 2 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 60) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - actions_types = list(/datum/action/item_action/toggle) - visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = FIRE_PROOF - clothing_flags = SNUG_FIT - -/obj/item/clothing/head/welding/attack_self(mob/user) - weldingvisortoggle(user) - -/* - * Cakehat - */ -/obj/item/clothing/head/hardhat/cakehat - name = "cakehat" - desc = "You put the cake on your head. Brilliant." - icon_state = "hardhat0_cakehat" - inhand_icon_state = "hardhat0_cakehat" - hat_type = "cakehat" - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - hitsound = 'sound/weapons/tap.ogg' - var/hitsound_on = 'sound/weapons/sear.ogg' //so we can differentiate between cakehat and energyhat - var/hitsound_off = 'sound/weapons/tap.ogg' - var/force_on = 15 - var/throwforce_on = 15 - var/damtype_on = BURN - flags_inv = HIDEEARS|HIDEHAIR - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 2 //luminosity when on - flags_cover = HEADCOVERSEYES - heat = 999 - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/cakehat/process() - var/turf/location = src.loc - if(ishuman(location)) - var/mob/living/carbon/human/M = location - if(M.is_holding(src) || M.head == src) - location = M.loc - - if(isturf(location)) - location.hotspot_expose(700, 1) - -/obj/item/clothing/head/hardhat/cakehat/turn_on(mob/living/user) - ..() - force = force_on - throwforce = throwforce_on - damtype = damtype_on - hitsound = hitsound_on - START_PROCESSING(SSobj, src) - -/obj/item/clothing/head/hardhat/cakehat/turn_off(mob/living/user) - ..() - force = 0 - throwforce = 0 - damtype = BRUTE - hitsound = hitsound_off - STOP_PROCESSING(SSobj, src) - -/obj/item/clothing/head/hardhat/cakehat/get_temperature() - return on * heat - -/obj/item/clothing/head/hardhat/cakehat/energycake - name = "energy cake" - desc = "You put the energy sword on your cake. Brilliant." - icon_state = "hardhat0_energycake" - inhand_icon_state = "hardhat0_energycake" - hat_type = "energycake" - hitsound = 'sound/weapons/tap.ogg' - hitsound_on = 'sound/weapons/blade1.ogg' - hitsound_off = 'sound/weapons/tap.ogg' - damtype_on = BRUTE - force_on = 18 //same as epen (but much more obvious) - brightness_on = 3 //ditto - heat = 0 - -/obj/item/clothing/head/hardhat/cakehat/energycake/turn_on(mob/living/user) - playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) - to_chat(user, "You turn on \the [src].") - ..() - -/obj/item/clothing/head/hardhat/cakehat/energycake/turn_off(mob/living/user) - playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) - to_chat(user, "You turn off \the [src].") - ..() - -/* - * Ushanka - */ -/obj/item/clothing/head/ushanka - name = "ushanka" - desc = "Perfect for winter in Siberia, da?" - icon_state = "ushankadown" - inhand_icon_state = "ushankadown" - flags_inv = HIDEEARS|HIDEHAIR - var/earflaps = 1 - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - - dog_fashion = /datum/dog_fashion/head/ushanka - -/obj/item/clothing/head/ushanka/attack_self(mob/user) - if(earflaps) - src.icon_state = "ushankaup" - src.inhand_icon_state = "ushankaup" - earflaps = 0 - to_chat(user, "You raise the ear flaps on the ushanka.") - else - src.icon_state = "ushankadown" - src.inhand_icon_state = "ushankadown" - earflaps = 1 - to_chat(user, "You lower the ear flaps on the ushanka.") - -/* - * Pumpkin head - */ -/obj/item/clothing/head/hardhat/pumpkinhead - name = "carved pumpkin" - desc = "A jack o' lantern! Believed to ward off evil spirits." - icon_state = "hardhat0_pumpkin" - inhand_icon_state = "hardhat0_pumpkin" - hat_type = "pumpkin" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 2 //luminosity when on - flags_cover = HEADCOVERSEYES - -/* - * Kitty ears - */ -/obj/item/clothing/head/kitty - name = "kitty ears" - desc = "A pair of kitty ears. Meow!" - icon_state = "kitty" - color = "#999999" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/kitty - -/obj/item/clothing/head/kitty/equipped(mob/living/carbon/human/user, slot) - if(ishuman(user) && slot == ITEM_SLOT_HEAD) - update_icon(user) - user.update_inv_head() //Color might have been changed by update_icon. - ..() - -/obj/item/clothing/head/kitty/update_icon(mob/living/carbon/human/user) - if(ishuman(user)) - add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) - -/obj/item/clothing/head/kitty/genuine - desc = "A pair of kitty ears. A tag on the inside says \"Hand made from real cats.\"" - -/obj/item/clothing/head/hardhat/reindeer - name = "novelty reindeer hat" - desc = "Some fake antlers and a very fake red nose." - icon_state = "hardhat0_reindeer" - inhand_icon_state = "hardhat0_reindeer" - hat_type = "reindeer" - flags_inv = 0 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 1 //luminosity when on - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/reindeer - -/obj/item/clothing/head/cardborg - name = "cardborg helmet" - desc = "A helmet made out of a box." - icon_state = "cardborg_h" - inhand_icon_state = "cardborg_h" - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - - dog_fashion = /datum/dog_fashion/head/cardborg - -/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) - ..() - if(ishuman(user) && slot == ITEM_SLOT_HEAD) - var/mob/living/carbon/human/H = user - if(istype(H.wear_suit, /obj/item/clothing/suit/cardborg)) - var/obj/item/clothing/suit/cardborg/CB = H.wear_suit - CB.disguise(user, src) - -/obj/item/clothing/head/cardborg/dropped(mob/living/user) - ..() - user.remove_alt_appearance("standard_borg_disguise") - -/obj/item/clothing/head/wig - name = "wig" - desc = "A bunch of hair without a head attached." - icon = 'icons/mob/human_face.dmi' // default icon for all hairs - icon_state = "hair_vlong" - inhand_icon_state = "pwig" - worn_icon_state = "wig" - flags_inv = HIDEHAIR - color = "#000" - var/hairstyle = "Very Long Hair" - var/adjustablecolor = TRUE //can color be changed manually? - -/obj/item/clothing/head/wig/Initialize(mapload) - . = ..() - update_icon() - -/obj/item/clothing/head/wig/update_icon_state() - var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] - if(!S) - return - else - icon_state = S.icon_state - - -/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, file2use) - . = list() - if(!isinhands) - var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] - if(!S) - return - var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER) - M.appearance_flags |= RESET_COLOR - M.color = color - . += M - -/obj/item/clothing/head/wig/attack_self(mob/user) - var/new_style = input(user, "Select a hairstyle", "Wig Styling") as null|anything in (GLOB.hairstyles_list - "Bald") - var/newcolor = adjustablecolor ? input(usr,"","Choose Color",color) as color|null : null - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(new_style && new_style != hairstyle) - hairstyle = new_style - user.visible_message("[user] changes \the [src]'s hairstyle to [new_style].", "You change \the [src]'s hairstyle to [new_style].") - if(newcolor && newcolor != color) // only update if necessary - add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) - update_icon() - -/obj/item/clothing/head/wig/afterattack(mob/living/carbon/human/target, mob/user) - . = ..() - if (istype(target) && (HAIR in target.dna.species.species_traits) && target.hairstyle != "Bald") - to_chat(user, "You adjust the [src] to look just like [target.name]'s [target.hairstyle].") - add_atom_colour("#[target.hair_color]", FIXED_COLOUR_PRIORITY) - hairstyle = target.hairstyle - update_icon() - -/obj/item/clothing/head/wig/random/Initialize(mapload) - hairstyle = pick(GLOB.hairstyles_list - "Bald") //Don't want invisible wig - add_atom_colour("#[random_short_color()]", FIXED_COLOUR_PRIORITY) - . = ..() - -/obj/item/clothing/head/wig/natural - name = "natural wig" - desc = "A bunch of hair without a head attached. This one changes color to match the hair of the wearer. Nothing natural about that." - color = "#FFF" - adjustablecolor = FALSE - custom_price = 100 - -/obj/item/clothing/head/wig/natural/Initialize(mapload) - hairstyle = pick(GLOB.hairstyles_list - "Bald") - . = ..() - -/obj/item/clothing/head/wig/natural/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(ishuman(user) && slot == ITEM_SLOT_HEAD) - if (color != "#[user.hair_color]") // only update if necessary - add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) - update_icon() - user.update_inv_head() - -/obj/item/clothing/head/bronze - name = "bronze hat" - desc = "A crude helmet made out of bronze plates. It offers very little in the way of protection." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_helmet_old" - clothing_flags = SNUG_FIT - flags_inv = HIDEEARS|HIDEHAIR - armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) - -/obj/item/clothing/head/foilhat - name = "tinfoil hat" - desc = "Thought control rays, psychotronic scanning. Don't mind that, I'm protected cause I made this hat." - icon_state = "foilhat" - inhand_icon_state = "foilhat" - armor = list("melee" = 0, "bullet" = 0, "laser" = -5,"energy" = -15, "bomb" = 0, "bio" = 0, "rad" = -5, "fire" = 0, "acid" = 0) - equip_delay_other = 140 - clothing_flags = ANTI_TINFOIL_MANEUVER - var/datum/brain_trauma/mild/phobia/conspiracies/paranoia - var/warped = FALSE - -/obj/item/clothing/head/foilhat/Initialize(mapload) - . = ..() - if(!warped) - AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD, 6, TRUE, null, CALLBACK(src, .proc/warp_up)) - else - warp_up() - -/obj/item/clothing/head/foilhat/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot != ITEM_SLOT_HEAD || warped) - return - if(paranoia) - QDEL_NULL(paranoia) - paranoia = new() - - user.gain_trauma(paranoia, TRAUMA_RESILIENCE_MAGIC) - to_chat(user, "As you don the foiled hat, an entire world of conspiracy theories and seemingly insane ideas suddenly rush into your mind. What you once thought unbelievable suddenly seems.. undeniable. Everything is connected and nothing happens just by accident. You know too much and now they're out to get you. ") - -/obj/item/clothing/head/foilhat/MouseDrop(atom/over_object) - //God Im sorry - if(!warped && iscarbon(usr)) - var/mob/living/carbon/C = usr - if(src == C.head) - to_chat(C, "Why would you want to take this off? Do you want them to get into your mind?!") - return - return ..() - -/obj/item/clothing/head/foilhat/dropped(mob/user) - . = ..() - if(paranoia) - QDEL_NULL(paranoia) - -/obj/item/clothing/head/foilhat/proc/warp_up() - name = "scorched tinfoil hat" - desc = "A badly warped up hat. Quite unprobable this will still work against any of fictional and contemporary dangers it used to." - warped = TRUE - clothing_flags &= ~ANTI_TINFOIL_MANEUVER - if(!isliving(loc) || !paranoia) - return - var/mob/living/target = loc - if(target.get_item_by_slot(ITEM_SLOT_HEAD) != src) - return - QDEL_NULL(paranoia) - if(target.stat < UNCONSCIOUS) - to_chat(target, "Your zealous conspirationism rapidly dissipates as the donned hat warps up into a ruined mess. All those theories starting to sound like nothing but a ridicolous fanfare.") - -/obj/item/clothing/head/foilhat/attack_hand(mob/user) - if(!warped && iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.head) - to_chat(user, "Why would you want to take this off? Do you want them to get into your mind?!") - return - return ..() - -/obj/item/clothing/head/foilhat/microwave_act(obj/machinery/microwave/M) - . = ..() - if(!warped) - warp_up() +/* + * Contents: + * Welding mask + * Cakehat + * Ushanka + * Pumpkin head + * Kitty ears + * Cardborg disguise + * Wig + * Bronze hat + */ + +/* + * Welding mask + */ +/obj/item/clothing/head/welding + name = "welding helmet" + desc = "A head-mounted face cover designed to protect the wearer completely from space-arc eye." + icon_state = "welding" + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + inhand_icon_state = "welding" + custom_materials = list(/datum/material/iron=1750, /datum/material/glass=400) + flash_protect = FLASH_PROTECTION_WELDER + tint = 2 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 60) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + actions_types = list(/datum/action/item_action/toggle) + visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = FIRE_PROOF + clothing_flags = SNUG_FIT + +/obj/item/clothing/head/welding/attack_self(mob/user) + weldingvisortoggle(user) + +/* + * Cakehat + */ +/obj/item/clothing/head/hardhat/cakehat + name = "cakehat" + desc = "You put the cake on your head. Brilliant." + icon_state = "hardhat0_cakehat" + inhand_icon_state = "hardhat0_cakehat" + hat_type = "cakehat" + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + hitsound = 'sound/weapons/tap.ogg' + var/hitsound_on = 'sound/weapons/sear.ogg' //so we can differentiate between cakehat and energyhat + var/hitsound_off = 'sound/weapons/tap.ogg' + var/force_on = 15 + var/throwforce_on = 15 + var/damtype_on = BURN + flags_inv = HIDEEARS|HIDEHAIR + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 2 //luminosity when on + flags_cover = HEADCOVERSEYES + heat = 999 + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/cakehat/process() + var/turf/location = src.loc + if(ishuman(location)) + var/mob/living/carbon/human/M = location + if(M.is_holding(src) || M.head == src) + location = M.loc + + if(isturf(location)) + location.hotspot_expose(700, 1) + +/obj/item/clothing/head/hardhat/cakehat/turn_on(mob/living/user) + ..() + force = force_on + throwforce = throwforce_on + damtype = damtype_on + hitsound = hitsound_on + START_PROCESSING(SSobj, src) + +/obj/item/clothing/head/hardhat/cakehat/turn_off(mob/living/user) + ..() + force = 0 + throwforce = 0 + damtype = BRUTE + hitsound = hitsound_off + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/head/hardhat/cakehat/get_temperature() + return on * heat + +/obj/item/clothing/head/hardhat/cakehat/energycake + name = "energy cake" + desc = "You put the energy sword on your cake. Brilliant." + icon_state = "hardhat0_energycake" + inhand_icon_state = "hardhat0_energycake" + hat_type = "energycake" + hitsound = 'sound/weapons/tap.ogg' + hitsound_on = 'sound/weapons/blade1.ogg' + hitsound_off = 'sound/weapons/tap.ogg' + damtype_on = BRUTE + force_on = 18 //same as epen (but much more obvious) + brightness_on = 3 //ditto + heat = 0 + +/obj/item/clothing/head/hardhat/cakehat/energycake/turn_on(mob/living/user) + playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) + to_chat(user, "You turn on \the [src].") + ..() + +/obj/item/clothing/head/hardhat/cakehat/energycake/turn_off(mob/living/user) + playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) + to_chat(user, "You turn off \the [src].") + ..() + +/* + * Ushanka + */ +/obj/item/clothing/head/ushanka + name = "ushanka" + desc = "Perfect for winter in Siberia, da?" + icon_state = "ushankadown" + inhand_icon_state = "ushankadown" + flags_inv = HIDEEARS|HIDEHAIR + var/earflaps = 1 + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + + dog_fashion = /datum/dog_fashion/head/ushanka + +/obj/item/clothing/head/ushanka/attack_self(mob/user) + if(earflaps) + src.icon_state = "ushankaup" + src.inhand_icon_state = "ushankaup" + earflaps = 0 + to_chat(user, "You raise the ear flaps on the ushanka.") + else + src.icon_state = "ushankadown" + src.inhand_icon_state = "ushankadown" + earflaps = 1 + to_chat(user, "You lower the ear flaps on the ushanka.") + +/* + * Pumpkin head + */ +/obj/item/clothing/head/hardhat/pumpkinhead + name = "carved pumpkin" + desc = "A jack o' lantern! Believed to ward off evil spirits." + icon_state = "hardhat0_pumpkin" + inhand_icon_state = "hardhat0_pumpkin" + hat_type = "pumpkin" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 2 //luminosity when on + flags_cover = HEADCOVERSEYES + +/* + * Kitty ears + */ +/obj/item/clothing/head/kitty + name = "kitty ears" + desc = "A pair of kitty ears. Meow!" + icon_state = "kitty" + color = "#999999" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/kitty + +/obj/item/clothing/head/kitty/equipped(mob/living/carbon/human/user, slot) + if(ishuman(user) && slot == ITEM_SLOT_HEAD) + update_icon(user) + user.update_inv_head() //Color might have been changed by update_icon. + ..() + +/obj/item/clothing/head/kitty/update_icon(mob/living/carbon/human/user) + if(ishuman(user)) + add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) + +/obj/item/clothing/head/kitty/genuine + desc = "A pair of kitty ears. A tag on the inside says \"Hand made from real cats.\"" + +/obj/item/clothing/head/hardhat/reindeer + name = "novelty reindeer hat" + desc = "Some fake antlers and a very fake red nose." + icon_state = "hardhat0_reindeer" + inhand_icon_state = "hardhat0_reindeer" + hat_type = "reindeer" + flags_inv = 0 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 1 //luminosity when on + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/reindeer + +/obj/item/clothing/head/cardborg + name = "cardborg helmet" + desc = "A helmet made out of a box." + icon_state = "cardborg_h" + inhand_icon_state = "cardborg_h" + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + + dog_fashion = /datum/dog_fashion/head/cardborg + +/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) + ..() + if(ishuman(user) && slot == ITEM_SLOT_HEAD) + var/mob/living/carbon/human/H = user + if(istype(H.wear_suit, /obj/item/clothing/suit/cardborg)) + var/obj/item/clothing/suit/cardborg/CB = H.wear_suit + CB.disguise(user, src) + +/obj/item/clothing/head/cardborg/dropped(mob/living/user) + ..() + user.remove_alt_appearance("standard_borg_disguise") + +/obj/item/clothing/head/wig + name = "wig" + desc = "A bunch of hair without a head attached." + icon = 'icons/mob/human_face.dmi' // default icon for all hairs + icon_state = "hair_vlong" + inhand_icon_state = "pwig" + worn_icon_state = "wig" + flags_inv = HIDEHAIR + color = "#000" + var/hairstyle = "Very Long Hair" + var/adjustablecolor = TRUE //can color be changed manually? + +/obj/item/clothing/head/wig/Initialize(mapload) + . = ..() + update_icon() + +/obj/item/clothing/head/wig/update_icon_state() + var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] + if(!S) + return + else + icon_state = S.icon_state + + +/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, file2use) + . = list() + if(!isinhands) + var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] + if(!S) + return + var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER) + M.appearance_flags |= RESET_COLOR + M.color = color + . += M + +/obj/item/clothing/head/wig/attack_self(mob/user) + var/new_style = input(user, "Select a hairstyle", "Wig Styling") as null|anything in (GLOB.hairstyles_list - "Bald") + var/newcolor = adjustablecolor ? input(usr,"","Choose Color",color) as color|null : null + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(new_style && new_style != hairstyle) + hairstyle = new_style + user.visible_message("[user] changes \the [src]'s hairstyle to [new_style].", "You change \the [src]'s hairstyle to [new_style].") + if(newcolor && newcolor != color) // only update if necessary + add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) + update_icon() + +/obj/item/clothing/head/wig/afterattack(mob/living/carbon/human/target, mob/user) + . = ..() + if (istype(target) && (HAIR in target.dna.species.species_traits) && target.hairstyle != "Bald") + to_chat(user, "You adjust the [src] to look just like [target.name]'s [target.hairstyle].") + add_atom_colour("#[target.hair_color]", FIXED_COLOUR_PRIORITY) + hairstyle = target.hairstyle + update_icon() + +/obj/item/clothing/head/wig/random/Initialize(mapload) + hairstyle = pick(GLOB.hairstyles_list - "Bald") //Don't want invisible wig + add_atom_colour("#[random_short_color()]", FIXED_COLOUR_PRIORITY) + . = ..() + +/obj/item/clothing/head/wig/natural + name = "natural wig" + desc = "A bunch of hair without a head attached. This one changes color to match the hair of the wearer. Nothing natural about that." + color = "#FFF" + adjustablecolor = FALSE + custom_price = 100 + +/obj/item/clothing/head/wig/natural/Initialize(mapload) + hairstyle = pick(GLOB.hairstyles_list - "Bald") + . = ..() + +/obj/item/clothing/head/wig/natural/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(ishuman(user) && slot == ITEM_SLOT_HEAD) + if (color != "#[user.hair_color]") // only update if necessary + add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) + update_icon() + user.update_inv_head() + +/obj/item/clothing/head/bronze + name = "bronze hat" + desc = "A crude helmet made out of bronze plates. It offers very little in the way of protection." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_helmet_old" + clothing_flags = SNUG_FIT + flags_inv = HIDEEARS|HIDEHAIR + armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) + +/obj/item/clothing/head/foilhat + name = "tinfoil hat" + desc = "Thought control rays, psychotronic scanning. Don't mind that, I'm protected cause I made this hat." + icon_state = "foilhat" + inhand_icon_state = "foilhat" + armor = list("melee" = 0, "bullet" = 0, "laser" = -5,"energy" = -15, "bomb" = 0, "bio" = 0, "rad" = -5, "fire" = 0, "acid" = 0) + equip_delay_other = 140 + clothing_flags = ANTI_TINFOIL_MANEUVER + var/datum/brain_trauma/mild/phobia/conspiracies/paranoia + var/warped = FALSE + +/obj/item/clothing/head/foilhat/Initialize(mapload) + . = ..() + if(!warped) + AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD, 6, TRUE, null, CALLBACK(src, .proc/warp_up)) + else + warp_up() + +/obj/item/clothing/head/foilhat/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot != ITEM_SLOT_HEAD || warped) + return + if(paranoia) + QDEL_NULL(paranoia) + paranoia = new() + + user.gain_trauma(paranoia, TRAUMA_RESILIENCE_MAGIC) + to_chat(user, "As you don the foiled hat, an entire world of conspiracy theories and seemingly insane ideas suddenly rush into your mind. What you once thought unbelievable suddenly seems.. undeniable. Everything is connected and nothing happens just by accident. You know too much and now they're out to get you. ") + +/obj/item/clothing/head/foilhat/MouseDrop(atom/over_object) + //God Im sorry + if(!warped && iscarbon(usr)) + var/mob/living/carbon/C = usr + if(src == C.head) + to_chat(C, "Why would you want to take this off? Do you want them to get into your mind?!") + return + return ..() + +/obj/item/clothing/head/foilhat/dropped(mob/user) + . = ..() + if(paranoia) + QDEL_NULL(paranoia) + +/obj/item/clothing/head/foilhat/proc/warp_up() + name = "scorched tinfoil hat" + desc = "A badly warped up hat. Quite unprobable this will still work against any of fictional and contemporary dangers it used to." + warped = TRUE + clothing_flags &= ~ANTI_TINFOIL_MANEUVER + if(!isliving(loc) || !paranoia) + return + var/mob/living/target = loc + if(target.get_item_by_slot(ITEM_SLOT_HEAD) != src) + return + QDEL_NULL(paranoia) + if(target.stat < UNCONSCIOUS) + to_chat(target, "Your zealous conspirationism rapidly dissipates as the donned hat warps up into a ruined mess. All those theories starting to sound like nothing but a ridicolous fanfare.") + +/obj/item/clothing/head/foilhat/attack_hand(mob/user) + if(!warped && iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.head) + to_chat(user, "Why would you want to take this off? Do you want them to get into your mind?!") + return + return ..() + +/obj/item/clothing/head/foilhat/microwave_act(obj/machinery/microwave/M) + . = ..() + if(!warped) + warp_up() diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm index 22e8a48f87d..9c5b993291d 100644 --- a/code/modules/clothing/head/soft_caps.dm +++ b/code/modules/clothing/head/soft_caps.dm @@ -1,131 +1,131 @@ -/obj/item/clothing/head/soft - name = "cargo cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "cargosoft" - inhand_icon_state = "helmet" - var/soft_type = "cargo" - - dog_fashion = /datum/dog_fashion/head/cargo_tech - - var/flipped = 0 - -/obj/item/clothing/head/soft/dropped() - icon_state = "[soft_type]soft" - flipped=0 - ..() - -/obj/item/clothing/head/soft/verb/flipcap() - set category = "Object" - set name = "Flip cap" - - flip(usr) - - -/obj/item/clothing/head/soft/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - flip(user) - - -/obj/item/clothing/head/soft/proc/flip(mob/user) - if(!user.incapacitated()) - flipped = !flipped - if(src.flipped) - icon_state = "[soft_type]soft_flipped" - to_chat(user, "You flip the hat backwards.") - else - icon_state = "[soft_type]soft" - to_chat(user, "You flip the hat back in normal position.") - usr.update_inv_head() //so our mob-overlays update - -/obj/item/clothing/head/soft/examine(mob/user) - . = ..() - . += "Alt-click the cap to flip it [flipped ? "forwards" : "backwards"]." - -/obj/item/clothing/head/soft/red - name = "red cap" - desc = "It's a baseball hat in a tasteless red colour." - icon_state = "redsoft" - soft_type = "red" - dog_fashion = null - -/obj/item/clothing/head/soft/blue - name = "blue cap" - desc = "It's a baseball hat in a tasteless blue colour." - icon_state = "bluesoft" - soft_type = "blue" - dog_fashion = null - -/obj/item/clothing/head/soft/green - name = "green cap" - desc = "It's a baseball hat in a tasteless green colour." - icon_state = "greensoft" - soft_type = "green" - dog_fashion = null - -/obj/item/clothing/head/soft/yellow - name = "yellow cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "yellowsoft" - soft_type = "yellow" - dog_fashion = null - -/obj/item/clothing/head/soft/grey - name = "grey cap" - desc = "It's a baseball hat in a tasteful grey colour." - icon_state = "greysoft" - soft_type = "grey" - dog_fashion = null - -/obj/item/clothing/head/soft/orange - name = "orange cap" - desc = "It's a baseball hat in a tasteless orange colour." - icon_state = "orangesoft" - soft_type = "orange" - dog_fashion = null - -/obj/item/clothing/head/soft/mime - name = "white cap" - desc = "It's a baseball hat in a tasteless white colour." - icon_state = "mimesoft" - soft_type = "mime" - dog_fashion = null - -/obj/item/clothing/head/soft/purple - name = "purple cap" - desc = "It's a baseball hat in a tasteless purple colour." - icon_state = "purplesoft" - soft_type = "purple" - dog_fashion = null - -/obj/item/clothing/head/soft/black - name = "black cap" - desc = "It's a baseball hat in a tasteless black colour." - icon_state = "blacksoft" - soft_type = "black" - dog_fashion = null - -/obj/item/clothing/head/soft/rainbow - name = "rainbow cap" - desc = "It's a baseball hat in a bright rainbow of colors." - icon_state = "rainbowsoft" - soft_type = "rainbow" - dog_fashion = null - -/obj/item/clothing/head/soft/sec - name = "security cap" - desc = "It's a robust baseball hat in tasteful red colour." - icon_state = "secsoft" - soft_type = "sec" - armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/soft/paramedic - name = "paramedic cap" - desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." - icon_state = "paramedicsoft" - soft_type = "paramedic" - dog_fashion = null +/obj/item/clothing/head/soft + name = "cargo cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "cargosoft" + inhand_icon_state = "helmet" + var/soft_type = "cargo" + + dog_fashion = /datum/dog_fashion/head/cargo_tech + + var/flipped = 0 + +/obj/item/clothing/head/soft/dropped() + icon_state = "[soft_type]soft" + flipped=0 + ..() + +/obj/item/clothing/head/soft/verb/flipcap() + set category = "Object" + set name = "Flip cap" + + flip(usr) + + +/obj/item/clothing/head/soft/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + flip(user) + + +/obj/item/clothing/head/soft/proc/flip(mob/user) + if(!user.incapacitated()) + flipped = !flipped + if(src.flipped) + icon_state = "[soft_type]soft_flipped" + to_chat(user, "You flip the hat backwards.") + else + icon_state = "[soft_type]soft" + to_chat(user, "You flip the hat back in normal position.") + usr.update_inv_head() //so our mob-overlays update + +/obj/item/clothing/head/soft/examine(mob/user) + . = ..() + . += "Alt-click the cap to flip it [flipped ? "forwards" : "backwards"]." + +/obj/item/clothing/head/soft/red + name = "red cap" + desc = "It's a baseball hat in a tasteless red colour." + icon_state = "redsoft" + soft_type = "red" + dog_fashion = null + +/obj/item/clothing/head/soft/blue + name = "blue cap" + desc = "It's a baseball hat in a tasteless blue colour." + icon_state = "bluesoft" + soft_type = "blue" + dog_fashion = null + +/obj/item/clothing/head/soft/green + name = "green cap" + desc = "It's a baseball hat in a tasteless green colour." + icon_state = "greensoft" + soft_type = "green" + dog_fashion = null + +/obj/item/clothing/head/soft/yellow + name = "yellow cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "yellowsoft" + soft_type = "yellow" + dog_fashion = null + +/obj/item/clothing/head/soft/grey + name = "grey cap" + desc = "It's a baseball hat in a tasteful grey colour." + icon_state = "greysoft" + soft_type = "grey" + dog_fashion = null + +/obj/item/clothing/head/soft/orange + name = "orange cap" + desc = "It's a baseball hat in a tasteless orange colour." + icon_state = "orangesoft" + soft_type = "orange" + dog_fashion = null + +/obj/item/clothing/head/soft/mime + name = "white cap" + desc = "It's a baseball hat in a tasteless white colour." + icon_state = "mimesoft" + soft_type = "mime" + dog_fashion = null + +/obj/item/clothing/head/soft/purple + name = "purple cap" + desc = "It's a baseball hat in a tasteless purple colour." + icon_state = "purplesoft" + soft_type = "purple" + dog_fashion = null + +/obj/item/clothing/head/soft/black + name = "black cap" + desc = "It's a baseball hat in a tasteless black colour." + icon_state = "blacksoft" + soft_type = "black" + dog_fashion = null + +/obj/item/clothing/head/soft/rainbow + name = "rainbow cap" + desc = "It's a baseball hat in a bright rainbow of colors." + icon_state = "rainbowsoft" + soft_type = "rainbow" + dog_fashion = null + +/obj/item/clothing/head/soft/sec + name = "security cap" + desc = "It's a robust baseball hat in tasteful red colour." + icon_state = "secsoft" + soft_type = "sec" + armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/soft/paramedic + name = "paramedic cap" + desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." + icon_state = "paramedicsoft" + soft_type = "paramedic" + dog_fashion = null diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index 6314cca34b1..4fc0dc41d93 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -1,72 +1,72 @@ -/obj/item/clothing/mask - name = "mask" - icon = 'icons/obj/clothing/masks.dmi' - body_parts_covered = HEAD - slot_flags = ITEM_SLOT_MASK - strip_delay = 40 - equip_delay_other = 40 - var/modifies_speech = FALSE - var/mask_adjusted = 0 - var/adjusted_flags = null - -/obj/item/clothing/mask/attack_self(mob/user) - if((clothing_flags & VOICEBOX_TOGGLABLE)) - clothing_flags ^= (VOICEBOX_DISABLED) - var/status = !(clothing_flags & VOICEBOX_DISABLED) - to_chat(user, "You turn the voice box in [src] [status ? "on" : "off"].") - -/obj/item/clothing/mask/equipped(mob/M, slot) - . = ..() - if (slot == ITEM_SLOT_MASK && modifies_speech) - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) - else - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/mask/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/mask/proc/handle_speech() - -/obj/item/clothing/mask/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(body_parts_covered & HEAD) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "maskblood") - -/obj/item/clothing/mask/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_wear_mask() - -//Proc that moves gas/breath masks out of the way, disabling them and allowing pill/food consumption -/obj/item/clothing/mask/proc/adjustmask(mob/living/user) - if(user && user.incapacitated()) - return - mask_adjusted = !mask_adjusted - if(!mask_adjusted) - src.icon_state = initial(icon_state) - gas_transfer_coefficient = initial(gas_transfer_coefficient) - permeability_coefficient = initial(permeability_coefficient) - clothing_flags |= visor_flags - flags_inv |= visor_flags_inv - flags_cover |= visor_flags_cover - to_chat(user, "You push \the [src] back into place.") - slot_flags = initial(slot_flags) - else - icon_state += "_up" - to_chat(user, "You push \the [src] out of the way.") - gas_transfer_coefficient = null - permeability_coefficient = null - clothing_flags &= ~visor_flags - flags_inv &= ~visor_flags_inv - flags_cover &= ~visor_flags_cover - if(adjusted_flags) - slot_flags = adjusted_flags - if(user) - user.wear_mask_update(src, toggle_off = mask_adjusted) - user.update_action_buttons_icon() //when mask is adjusted out, we update all buttons icon so the user's potential internal tank correctly shows as off. +/obj/item/clothing/mask + name = "mask" + icon = 'icons/obj/clothing/masks.dmi' + body_parts_covered = HEAD + slot_flags = ITEM_SLOT_MASK + strip_delay = 40 + equip_delay_other = 40 + var/modifies_speech = FALSE + var/mask_adjusted = 0 + var/adjusted_flags = null + +/obj/item/clothing/mask/attack_self(mob/user) + if((clothing_flags & VOICEBOX_TOGGLABLE)) + clothing_flags ^= (VOICEBOX_DISABLED) + var/status = !(clothing_flags & VOICEBOX_DISABLED) + to_chat(user, "You turn the voice box in [src] [status ? "on" : "off"].") + +/obj/item/clothing/mask/equipped(mob/M, slot) + . = ..() + if (slot == ITEM_SLOT_MASK && modifies_speech) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/mask/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/mask/proc/handle_speech() + +/obj/item/clothing/mask/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(body_parts_covered & HEAD) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "maskblood") + +/obj/item/clothing/mask/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_wear_mask() + +//Proc that moves gas/breath masks out of the way, disabling them and allowing pill/food consumption +/obj/item/clothing/mask/proc/adjustmask(mob/living/user) + if(user && user.incapacitated()) + return + mask_adjusted = !mask_adjusted + if(!mask_adjusted) + src.icon_state = initial(icon_state) + gas_transfer_coefficient = initial(gas_transfer_coefficient) + permeability_coefficient = initial(permeability_coefficient) + clothing_flags |= visor_flags + flags_inv |= visor_flags_inv + flags_cover |= visor_flags_cover + to_chat(user, "You push \the [src] back into place.") + slot_flags = initial(slot_flags) + else + icon_state += "_up" + to_chat(user, "You push \the [src] out of the way.") + gas_transfer_coefficient = null + permeability_coefficient = null + clothing_flags &= ~visor_flags + flags_inv &= ~visor_flags_inv + flags_cover &= ~visor_flags_cover + if(adjusted_flags) + slot_flags = adjusted_flags + if(user) + user.wear_mask_update(src, toggle_off = mask_adjusted) + user.update_action_buttons_icon() //when mask is adjusted out, we update all buttons icon so the user's potential internal tank correctly shows as off. diff --git a/code/modules/clothing/masks/boxing.dm b/code/modules/clothing/masks/boxing.dm index 695076ac21a..d4ffd989ed6 100644 --- a/code/modules/clothing/masks/boxing.dm +++ b/code/modules/clothing/masks/boxing.dm @@ -1,98 +1,98 @@ -/obj/item/clothing/mask/balaclava - name = "balaclava" - desc = "LOADSAMONEY" - icon_state = "balaclava" - inhand_icon_state = "balaclava" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - actions_types = list(/datum/action/item_action/adjust) - -/obj/item/clothing/mask/balaclava/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/infiltrator - name = "infiltrator balaclava" - desc = "It makes you feel safe in your anonymity, but for a stealth outfit you sure do look obvious that you're up to no good. It seems to have a built in heads-up display." - icon_state = "syndicate_balaclava" - inhand_icon_state = "syndicate_balaclava" - clothing_flags = MASKINTERNALS - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - armor = list("melee" = 10, "bullet" = 5, "laser" = 5,"energy" = 5, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 100, "acid" = 40) - resistance_flags = FIRE_PROOF | ACID_PROOF - - var/voice_unknown = FALSE ///This makes it so that your name shows up as unknown when wearing the mask. - -/obj/item/clothing/mask/infiltrator/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot != ITEM_SLOT_MASK) - return - to_chat(user, "You roll the balaclava over your face, and a data display appears before your eyes.") - ADD_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - H.add_hud_to(user) - voice_unknown = TRUE - -/obj/item/clothing/mask/infiltrator/dropped(mob/living/carbon/human/user) - to_chat(user, "You pull off the balaclava, and the mask's internal hud system switches off quietly.") - REMOVE_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - H.remove_hud_from(user) - voice_unknown = FALSE - return ..() - -/obj/item/clothing/mask/luchador - name = "Luchador Mask" - desc = "Worn by robust fighters, flying high to defeat their foes!" - icon_state = "luchag" - inhand_icon_state = "luchag" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/luchador/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = replacetext(message, "captain", "CAPITÁN") - message = replacetext(message, "station", "ESTACIÓN") - message = replacetext(message, "sir", "SEÑOR") - message = replacetext(message, "the ", "el ") - message = replacetext(message, "my ", "mi ") - message = replacetext(message, "is ", "es ") - message = replacetext(message, "it's", "es") - message = replacetext(message, "friend", "amigo") - message = replacetext(message, "buddy", "amigo") - message = replacetext(message, "hello", "hola") - message = replacetext(message, " hot", " caliente") - message = replacetext(message, " very ", " muy ") - message = replacetext(message, "sword", "espada") - message = replacetext(message, "library", "biblioteca") - message = replacetext(message, "traitor", "traidor") - message = replacetext(message, "wizard", "mago") - message = uppertext(message) //Things end up looking better this way (no mixed cases), and it fits the macho wrestler image. - if(prob(25)) - message += " OLE!" - speech_args[SPEECH_MESSAGE] = message - -/obj/item/clothing/mask/luchador/tecnicos - name = "Tecnicos Mask" - desc = "Worn by robust fighters who uphold justice and fight honorably." - icon_state = "luchador" - inhand_icon_state = "luchador" - -/obj/item/clothing/mask/luchador/rudos - name = "Rudos Mask" - desc = "Worn by robust fighters who are willing to do anything to win." - icon_state = "luchar" - inhand_icon_state = "luchar" - -/obj/item/clothing/mask/russian_balaclava - name = "russian balaclava" - desc = "Protects your face from snow." - icon_state = "rus_balaclava" - inhand_icon_state = "rus_balaclava" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL +/obj/item/clothing/mask/balaclava + name = "balaclava" + desc = "LOADSAMONEY" + icon_state = "balaclava" + inhand_icon_state = "balaclava" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/adjust) + +/obj/item/clothing/mask/balaclava/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/infiltrator + name = "infiltrator balaclava" + desc = "It makes you feel safe in your anonymity, but for a stealth outfit you sure do look obvious that you're up to no good. It seems to have a built in heads-up display." + icon_state = "syndicate_balaclava" + inhand_icon_state = "syndicate_balaclava" + clothing_flags = MASKINTERNALS + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + armor = list("melee" = 10, "bullet" = 5, "laser" = 5,"energy" = 5, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 100, "acid" = 40) + resistance_flags = FIRE_PROOF | ACID_PROOF + + var/voice_unknown = FALSE ///This makes it so that your name shows up as unknown when wearing the mask. + +/obj/item/clothing/mask/infiltrator/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot != ITEM_SLOT_MASK) + return + to_chat(user, "You roll the balaclava over your face, and a data display appears before your eyes.") + ADD_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] + H.add_hud_to(user) + voice_unknown = TRUE + +/obj/item/clothing/mask/infiltrator/dropped(mob/living/carbon/human/user) + to_chat(user, "You pull off the balaclava, and the mask's internal hud system switches off quietly.") + REMOVE_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] + H.remove_hud_from(user) + voice_unknown = FALSE + return ..() + +/obj/item/clothing/mask/luchador + name = "Luchador Mask" + desc = "Worn by robust fighters, flying high to defeat their foes!" + icon_state = "luchag" + inhand_icon_state = "luchag" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/luchador/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = replacetext(message, "captain", "CAPITÁN") + message = replacetext(message, "station", "ESTACIÓN") + message = replacetext(message, "sir", "SEÑOR") + message = replacetext(message, "the ", "el ") + message = replacetext(message, "my ", "mi ") + message = replacetext(message, "is ", "es ") + message = replacetext(message, "it's", "es") + message = replacetext(message, "friend", "amigo") + message = replacetext(message, "buddy", "amigo") + message = replacetext(message, "hello", "hola") + message = replacetext(message, " hot", " caliente") + message = replacetext(message, " very ", " muy ") + message = replacetext(message, "sword", "espada") + message = replacetext(message, "library", "biblioteca") + message = replacetext(message, "traitor", "traidor") + message = replacetext(message, "wizard", "mago") + message = uppertext(message) //Things end up looking better this way (no mixed cases), and it fits the macho wrestler image. + if(prob(25)) + message += " OLE!" + speech_args[SPEECH_MESSAGE] = message + +/obj/item/clothing/mask/luchador/tecnicos + name = "Tecnicos Mask" + desc = "Worn by robust fighters who uphold justice and fight honorably." + icon_state = "luchador" + inhand_icon_state = "luchador" + +/obj/item/clothing/mask/luchador/rudos + name = "Rudos Mask" + desc = "Worn by robust fighters who are willing to do anything to win." + icon_state = "luchar" + inhand_icon_state = "luchar" + +/obj/item/clothing/mask/russian_balaclava + name = "russian balaclava" + desc = "Protects your face from snow." + icon_state = "rus_balaclava" + inhand_icon_state = "rus_balaclava" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/clothing/masks/breath.dm b/code/modules/clothing/masks/breath.dm index 095316b6d0b..e6bf7711a43 100644 --- a/code/modules/clothing/masks/breath.dm +++ b/code/modules/clothing/masks/breath.dm @@ -1,41 +1,41 @@ -/obj/item/clothing/mask/breath - desc = "A close-fitting mask that can be connected to an air supply." - name = "breath mask" - icon_state = "breath" - inhand_icon_state = "m_mask" - body_parts_covered = 0 - clothing_flags = MASKINTERNALS - visor_flags = MASKINTERNALS - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.1 - permeability_coefficient = 0.5 - actions_types = list(/datum/action/item_action/adjust) - flags_cover = MASKCOVERSMOUTH - visor_flags_cover = MASKCOVERSMOUTH - resistance_flags = NONE - -/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/mask/breath/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/breath/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - adjustmask(user) - -/obj/item/clothing/mask/breath/examine(mob/user) - . = ..() - . += "Alt-click [src] to adjust it." - -/obj/item/clothing/mask/breath/medical - desc = "A close-fitting sterile mask that can be connected to an air supply." - name = "medical mask" - icon_state = "medical" - inhand_icon_state = "m_mask" - permeability_coefficient = 0.01 - equip_delay_other = 10 +/obj/item/clothing/mask/breath + desc = "A close-fitting mask that can be connected to an air supply." + name = "breath mask" + icon_state = "breath" + inhand_icon_state = "m_mask" + body_parts_covered = 0 + clothing_flags = MASKINTERNALS + visor_flags = MASKINTERNALS + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.1 + permeability_coefficient = 0.5 + actions_types = list(/datum/action/item_action/adjust) + flags_cover = MASKCOVERSMOUTH + visor_flags_cover = MASKCOVERSMOUTH + resistance_flags = NONE + +/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/mask/breath/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/breath/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + adjustmask(user) + +/obj/item/clothing/mask/breath/examine(mob/user) + . = ..() + . += "Alt-click [src] to adjust it." + +/obj/item/clothing/mask/breath/medical + desc = "A close-fitting sterile mask that can be connected to an air supply." + name = "medical mask" + icon_state = "medical" + inhand_icon_state = "m_mask" + permeability_coefficient = 0.01 + equip_delay_other = 10 diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 1166fc0f4be..deaf2d88c35 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -1,264 +1,264 @@ -/obj/item/clothing/mask/gas - name = "gas mask" - desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate - icon_state = "gas_alt" - clothing_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_NORMAL - inhand_icon_state = "gas_alt" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - -/obj/item/clothing/mask/gas/atmos - name = "atmospheric gas mask" - desc = "Improved gas mask utilized by atmospheric technicians. Still not very good at blocking gas flow, but it's flameproof!" - icon_state = "gas_atmos" - inhand_icon_state = "gas_atmos" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 20, "acid" = 10) - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.001 //cargo cult time, this var does nothing but just in case someone actually makes it do something - permeability_coefficient = 0.001 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/mask/gas/atmos/captain - name = "captain's gas mask" - desc = "Nanotrasen cut corners and repainted a spare atmospheric gas mask, but don't tell anyone." - icon_state = "gas_cap" - inhand_icon_state = "gas_cap" - resistance_flags = FIRE_PROOF | ACID_PROOF - -// **** Welding gas mask **** - -/obj/item/clothing/mask/gas/welding - name = "welding mask" - desc = "A gas mask with built-in welding goggles and a face shield. Looks like a skull - clearly designed by a nerd." - icon_state = "weldingmask" - flash_protect = FLASH_PROTECTION_WELDER - custom_materials = list(/datum/material/iron=4000, /datum/material/glass=2000) - tint = 2 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 55) - actions_types = list(/datum/action/item_action/toggle) - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = MASKCOVERSEYES - visor_flags_inv = HIDEEYES - visor_flags_cover = MASKCOVERSEYES - resistance_flags = FIRE_PROOF - -/obj/item/clothing/mask/gas/welding/attack_self(mob/user) - weldingvisortoggle(user) - -/obj/item/clothing/mask/gas/welding/up - -/obj/item/clothing/mask/gas/welding/up/Initialize() - ..() - visor_toggling() - -// ******************************************************************** - -//Plague Dr suit can be found in clothing/suits/bio.dm -/obj/item/clothing/mask/gas/plaguedoctor - name = "plague doctor mask" - desc = "A modernised version of the classic design, this mask will not only filter out toxins but it can also be connected to an air supply." - icon_state = "plaguedoctor" - inhand_icon_state = "gas_mask" - armor = list("melee" = 0, "bullet" = 0, "laser" = 2,"energy" = 2, "bomb" = 0, "bio" = 75, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/mask/gas/syndicate - name = "syndicate mask" - desc = "A close-fitting tactical mask that can be connected to an air supply." - icon_state = "syndicate" - strip_delay = 60 - -/obj/item/clothing/mask/gas/clown_hat - name = "clown wig and mask" - desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask." - clothing_flags = MASKINTERNALS - icon_state = "clown" - inhand_icon_state = "clown_hat" - dye_color = "clown" - w_class = WEIGHT_CLASS_SMALL - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - actions_types = list(/datum/action/item_action/adjust) - dog_fashion = /datum/dog_fashion/head/clown - var/list/clownmask_designs = list() - -/obj/item/clothing/mask/gas/clown_hat/Initialize(mapload) - .=..() - clownmask_designs = list( - "True Form" = image(icon = src.icon, icon_state = "clown"), - "The Feminist" = image(icon = src.icon, icon_state = "sexyclown"), - "The Jester" = image(icon = src.icon, icon_state = "chaos"), - "The Madman" = image(icon = src.icon, icon_state = "joker"), - "The Rainbow Color" = image(icon = src.icon, icon_state = "rainbow") - ) - -/obj/item/clothing/mask/gas/clown_hat/ui_action_click(mob/user) - if(!istype(user) || user.incapacitated()) - return - - var/list/options = list() - options["True Form"] = "clown" - options["The Feminist"] = "sexyclown" - options["The Madman"] = "joker" - options["The Rainbow Color"] ="rainbow" - options["The Jester"] ="chaos" //Nepeta33Leijon is holding me captive and forced me to help with this please send help - - var/choice = show_radial_menu(user,src, clownmask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) - if(!choice) - return FALSE - - if(src && choice && !user.incapacitated() && in_range(user,src)) - icon_state = options[choice] - user.update_inv_wear_mask() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - to_chat(user, "Your Clown Mask has now morphed into [choice], all praise the Honkmother!") - return TRUE - -/obj/item/clothing/mask/gas/sexyclown - name = "sexy-clown wig and mask" - desc = "A feminine clown mask for the dabbling crossdressers or female entertainers." - clothing_flags = MASKINTERNALS - icon_state = "sexyclown" - inhand_icon_state = "sexyclown" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/mime - name = "mime mask" - desc = "The traditional mime's mask. It has an eerie facial posture." - clothing_flags = MASKINTERNALS - icon_state = "mime" - inhand_icon_state = "mime" - w_class = WEIGHT_CLASS_SMALL - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - actions_types = list(/datum/action/item_action/adjust) - var/list/mimemask_designs = list() - -/obj/item/clothing/mask/gas/mime/Initialize(mapload) - .=..() - mimemask_designs = list( - "Blanc" = image(icon = src.icon, icon_state = "mime"), - "Excité" = image(icon = src.icon, icon_state = "sexymime"), - "Triste" = image(icon = src.icon, icon_state = "sadmime"), - "Effrayé" = image(icon = src.icon, icon_state = "scaredmime") - ) - -/obj/item/clothing/mask/gas/mime/ui_action_click(mob/user) - if(!istype(user) || user.incapacitated()) - return - - var/list/options = list() - options["Blanc"] = "mime" - options["Triste"] = "sadmime" - options["Effrayé"] = "scaredmime" - options["Excité"] ="sexymime" - - var/choice = show_radial_menu(user,src, mimemask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) - if(!choice) - return FALSE - - if(src && choice && !user.incapacitated() && in_range(user,src)) - icon_state = options[choice] - user.update_inv_wear_mask() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - to_chat(user, "Your Mime Mask has now morphed into [choice]!") - return TRUE - -/obj/item/clothing/mask/gas/monkeymask - name = "monkey mask" - desc = "A mask used when acting as a monkey." - clothing_flags = MASKINTERNALS - icon_state = "monkeymask" - inhand_icon_state = "monkeymask" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/sexymime - name = "sexy mime mask" - desc = "A traditional female mime's mask." - clothing_flags = MASKINTERNALS - icon_state = "sexymime" - inhand_icon_state = "sexymime" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/cyborg - name = "cyborg visor" - desc = "Beep boop." - icon_state = "death" - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/owl_mask - name = "owl mask" - desc = "Twoooo!" - icon_state = "owl" - clothing_flags = MASKINTERNALS - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/carp - name = "carp mask" - desc = "Gnash gnash." - icon_state = "carp_mask" - -/obj/item/clothing/mask/gas/tiki_mask - name = "tiki mask" - desc = "A creepy wooden mask. Surprisingly expressive for a poorly carved bit of wood." - icon_state = "tiki_eyebrow" - inhand_icon_state = "tiki_eyebrow" - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.25) - resistance_flags = FLAMMABLE - max_integrity = 100 - actions_types = list(/datum/action/item_action/adjust) - dog_fashion = null - var/list/tikimask_designs = list() - -/obj/item/clothing/mask/gas/tiki_mask/Initialize(mapload) - .=..() - tikimask_designs = list( - "Original Tiki" = image(icon = src.icon, icon_state = "tiki_eyebrow"), - "Happy Tiki" = image(icon = src.icon, icon_state = "tiki_happy"), - "Confused Tiki" = image(icon = src.icon, icon_state = "tiki_confused"), - "Angry Tiki" = image(icon = src.icon, icon_state = "tiki_angry") - ) - -/obj/item/clothing/mask/gas/tiki_mask/ui_action_click(mob/user) - var/mob/M = usr - var/list/options = list() - options["Original Tiki"] = "tiki_eyebrow" - options["Happy Tiki"] = "tiki_happy" - options["Confused Tiki"] = "tiki_confused" - options["Angry Tiki"] ="tiki_angry" - - var/choice = show_radial_menu(user,src, tikimask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) - if(!choice) - return FALSE - - if(src && choice && !M.stat && in_range(M,src)) - icon_state = options[choice] - user.update_inv_wear_mask() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - to_chat(M, "The Tiki Mask has now changed into the [choice] Mask!") - return 1 - -/obj/item/clothing/mask/gas/tiki_mask/yalp_elor - icon_state = "tiki_yalp" - actions_types = list() - -/obj/item/clothing/mask/gas/hunter - name = "bounty hunting mask" - desc = "A custom tactical mask with decals added." - icon_state = "hunter" - inhand_icon_state = "hunter" - resistance_flags = FIRE_PROOF | ACID_PROOF - flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR +/obj/item/clothing/mask/gas + name = "gas mask" + desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate + icon_state = "gas_alt" + clothing_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_NORMAL + inhand_icon_state = "gas_alt" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + +/obj/item/clothing/mask/gas/atmos + name = "atmospheric gas mask" + desc = "Improved gas mask utilized by atmospheric technicians. Still not very good at blocking gas flow, but it's flameproof!" + icon_state = "gas_atmos" + inhand_icon_state = "gas_atmos" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 20, "acid" = 10) + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.001 //cargo cult time, this var does nothing but just in case someone actually makes it do something + permeability_coefficient = 0.001 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/mask/gas/atmos/captain + name = "captain's gas mask" + desc = "Nanotrasen cut corners and repainted a spare atmospheric gas mask, but don't tell anyone." + icon_state = "gas_cap" + inhand_icon_state = "gas_cap" + resistance_flags = FIRE_PROOF | ACID_PROOF + +// **** Welding gas mask **** + +/obj/item/clothing/mask/gas/welding + name = "welding mask" + desc = "A gas mask with built-in welding goggles and a face shield. Looks like a skull - clearly designed by a nerd." + icon_state = "weldingmask" + flash_protect = FLASH_PROTECTION_WELDER + custom_materials = list(/datum/material/iron=4000, /datum/material/glass=2000) + tint = 2 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 55) + actions_types = list(/datum/action/item_action/toggle) + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = MASKCOVERSEYES + visor_flags_inv = HIDEEYES + visor_flags_cover = MASKCOVERSEYES + resistance_flags = FIRE_PROOF + +/obj/item/clothing/mask/gas/welding/attack_self(mob/user) + weldingvisortoggle(user) + +/obj/item/clothing/mask/gas/welding/up + +/obj/item/clothing/mask/gas/welding/up/Initialize() + ..() + visor_toggling() + +// ******************************************************************** + +//Plague Dr suit can be found in clothing/suits/bio.dm +/obj/item/clothing/mask/gas/plaguedoctor + name = "plague doctor mask" + desc = "A modernised version of the classic design, this mask will not only filter out toxins but it can also be connected to an air supply." + icon_state = "plaguedoctor" + inhand_icon_state = "gas_mask" + armor = list("melee" = 0, "bullet" = 0, "laser" = 2,"energy" = 2, "bomb" = 0, "bio" = 75, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/mask/gas/syndicate + name = "syndicate mask" + desc = "A close-fitting tactical mask that can be connected to an air supply." + icon_state = "syndicate" + strip_delay = 60 + +/obj/item/clothing/mask/gas/clown_hat + name = "clown wig and mask" + desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask." + clothing_flags = MASKINTERNALS + icon_state = "clown" + inhand_icon_state = "clown_hat" + dye_color = "clown" + w_class = WEIGHT_CLASS_SMALL + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + actions_types = list(/datum/action/item_action/adjust) + dog_fashion = /datum/dog_fashion/head/clown + var/list/clownmask_designs = list() + +/obj/item/clothing/mask/gas/clown_hat/Initialize(mapload) + .=..() + clownmask_designs = list( + "True Form" = image(icon = src.icon, icon_state = "clown"), + "The Feminist" = image(icon = src.icon, icon_state = "sexyclown"), + "The Jester" = image(icon = src.icon, icon_state = "chaos"), + "The Madman" = image(icon = src.icon, icon_state = "joker"), + "The Rainbow Color" = image(icon = src.icon, icon_state = "rainbow") + ) + +/obj/item/clothing/mask/gas/clown_hat/ui_action_click(mob/user) + if(!istype(user) || user.incapacitated()) + return + + var/list/options = list() + options["True Form"] = "clown" + options["The Feminist"] = "sexyclown" + options["The Madman"] = "joker" + options["The Rainbow Color"] ="rainbow" + options["The Jester"] ="chaos" //Nepeta33Leijon is holding me captive and forced me to help with this please send help + + var/choice = show_radial_menu(user,src, clownmask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) + if(!choice) + return FALSE + + if(src && choice && !user.incapacitated() && in_range(user,src)) + icon_state = options[choice] + user.update_inv_wear_mask() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + to_chat(user, "Your Clown Mask has now morphed into [choice], all praise the Honkmother!") + return TRUE + +/obj/item/clothing/mask/gas/sexyclown + name = "sexy-clown wig and mask" + desc = "A feminine clown mask for the dabbling crossdressers or female entertainers." + clothing_flags = MASKINTERNALS + icon_state = "sexyclown" + inhand_icon_state = "sexyclown" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/mime + name = "mime mask" + desc = "The traditional mime's mask. It has an eerie facial posture." + clothing_flags = MASKINTERNALS + icon_state = "mime" + inhand_icon_state = "mime" + w_class = WEIGHT_CLASS_SMALL + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + actions_types = list(/datum/action/item_action/adjust) + var/list/mimemask_designs = list() + +/obj/item/clothing/mask/gas/mime/Initialize(mapload) + .=..() + mimemask_designs = list( + "Blanc" = image(icon = src.icon, icon_state = "mime"), + "Excité" = image(icon = src.icon, icon_state = "sexymime"), + "Triste" = image(icon = src.icon, icon_state = "sadmime"), + "Effrayé" = image(icon = src.icon, icon_state = "scaredmime") + ) + +/obj/item/clothing/mask/gas/mime/ui_action_click(mob/user) + if(!istype(user) || user.incapacitated()) + return + + var/list/options = list() + options["Blanc"] = "mime" + options["Triste"] = "sadmime" + options["Effrayé"] = "scaredmime" + options["Excité"] ="sexymime" + + var/choice = show_radial_menu(user,src, mimemask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) + if(!choice) + return FALSE + + if(src && choice && !user.incapacitated() && in_range(user,src)) + icon_state = options[choice] + user.update_inv_wear_mask() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + to_chat(user, "Your Mime Mask has now morphed into [choice]!") + return TRUE + +/obj/item/clothing/mask/gas/monkeymask + name = "monkey mask" + desc = "A mask used when acting as a monkey." + clothing_flags = MASKINTERNALS + icon_state = "monkeymask" + inhand_icon_state = "monkeymask" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/sexymime + name = "sexy mime mask" + desc = "A traditional female mime's mask." + clothing_flags = MASKINTERNALS + icon_state = "sexymime" + inhand_icon_state = "sexymime" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/cyborg + name = "cyborg visor" + desc = "Beep boop." + icon_state = "death" + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/owl_mask + name = "owl mask" + desc = "Twoooo!" + icon_state = "owl" + clothing_flags = MASKINTERNALS + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/carp + name = "carp mask" + desc = "Gnash gnash." + icon_state = "carp_mask" + +/obj/item/clothing/mask/gas/tiki_mask + name = "tiki mask" + desc = "A creepy wooden mask. Surprisingly expressive for a poorly carved bit of wood." + icon_state = "tiki_eyebrow" + inhand_icon_state = "tiki_eyebrow" + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.25) + resistance_flags = FLAMMABLE + max_integrity = 100 + actions_types = list(/datum/action/item_action/adjust) + dog_fashion = null + var/list/tikimask_designs = list() + +/obj/item/clothing/mask/gas/tiki_mask/Initialize(mapload) + .=..() + tikimask_designs = list( + "Original Tiki" = image(icon = src.icon, icon_state = "tiki_eyebrow"), + "Happy Tiki" = image(icon = src.icon, icon_state = "tiki_happy"), + "Confused Tiki" = image(icon = src.icon, icon_state = "tiki_confused"), + "Angry Tiki" = image(icon = src.icon, icon_state = "tiki_angry") + ) + +/obj/item/clothing/mask/gas/tiki_mask/ui_action_click(mob/user) + var/mob/M = usr + var/list/options = list() + options["Original Tiki"] = "tiki_eyebrow" + options["Happy Tiki"] = "tiki_happy" + options["Confused Tiki"] = "tiki_confused" + options["Angry Tiki"] ="tiki_angry" + + var/choice = show_radial_menu(user,src, tikimask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) + if(!choice) + return FALSE + + if(src && choice && !M.stat && in_range(M,src)) + icon_state = options[choice] + user.update_inv_wear_mask() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + to_chat(M, "The Tiki Mask has now changed into the [choice] Mask!") + return 1 + +/obj/item/clothing/mask/gas/tiki_mask/yalp_elor + icon_state = "tiki_yalp" + actions_types = list() + +/obj/item/clothing/mask/gas/hunter + name = "bounty hunting mask" + desc = "A custom tactical mask with decals added." + icon_state = "hunter" + inhand_icon_state = "hunter" + resistance_flags = FIRE_PROOF | ACID_PROOF + flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 28174e2b2b2..8468c0e23f6 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -1,336 +1,336 @@ -/obj/item/clothing/mask/muzzle - name = "muzzle" - desc = "To stop that awful noise." - icon_state = "muzzle" - inhand_icon_state = "blindfold" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.9 - equip_delay_other = 20 - -/obj/item/clothing/mask/muzzle/attack_paw(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.wear_mask) - to_chat(user, "You need help taking this off!") - return - ..() - -/obj/item/clothing/mask/surgical - name = "sterile mask" - desc = "A sterile mask designed to help prevent the spread of diseases." - icon_state = "sterile" - inhand_icon_state = "sterile" - w_class = WEIGHT_CLASS_TINY - flags_inv = HIDEFACE - flags_cover = MASKCOVERSMOUTH - visor_flags_inv = HIDEFACE - visor_flags_cover = MASKCOVERSMOUTH - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.01 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 25, "rad" = 0, "fire" = 0, "acid" = 0) - actions_types = list(/datum/action/item_action/adjust) - -/obj/item/clothing/mask/surgical/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/fakemoustache - name = "fake moustache" - desc = "Warning: moustache is fake." - icon_state = "fake-moustache" - flags_inv = HIDEFACE - -/obj/item/clothing/mask/fakemoustache/italian - name = "italian moustache" - desc = "Made from authentic Italian moustache hairs. Gives the wearer an irresistable urge to gesticulate wildly." - modifies_speech = TRUE - -/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = " [message]" - var/list/italian_words = strings("italian_replacement.json", "italian") - - for(var/key in italian_words) - var/value = italian_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(3)) - message += pick(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!") - speech_args[SPEECH_MESSAGE] = trim(message) - -/obj/item/clothing/mask/joy - name = "joy mask" - desc = "Express your happiness or hide your sorrows with this laughing face with crying tears of joy cutout." - icon_state = "joy" - -/obj/item/clothing/mask/pig - name = "pig mask" - desc = "A rubber pig mask with a built-in voice modulator." - icon_state = "pig" - inhand_icon_state = "pig" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - clothing_flags = VOICEBOX_TOGGLABLE - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/pig/handle_speech(datum/source, list/speech_args) - if(!(clothing_flags & VOICEBOX_DISABLED)) - speech_args[SPEECH_MESSAGE] = pick("Oink!","Squeeeeeeee!","Oink Oink!") - -/obj/item/clothing/mask/pig/cursed - name = "pig face" - desc = "It looks like a mask, but closer inspection reveals it's melded onto this person's face!" - flags_inv = HIDEFACIALHAIR - clothing_flags = NONE - -/obj/item/clothing/mask/pig/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - playsound(get_turf(src), 'sound/magic/pighead_curse.ogg', 50, TRUE) - -///frog mask - reeee!! -/obj/item/clothing/mask/frog - name = "frog mask" - desc = "An ancient mask carved in the shape of a frog.
                Sanity is like gravity, all it needs is a push." - icon_state = "frog" - inhand_icon_state = "frog" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - clothing_flags = VOICEBOX_TOGGLABLE - modifies_speech = TRUE - -/obj/item/clothing/mask/frog/handle_speech(datum/source, list/speech_args) //whenever you speak - if(!(clothing_flags & VOICEBOX_DISABLED)) - if(prob(5)) //sometimes, the angry spirit finds others words to speak. - speech_args[SPEECH_MESSAGE] = pick("HUUUUU!!","SMOOOOOKIN'!!","Hello my baby, hello my honey, hello my rag-time gal.", "Feels bad, man.", "GIT DIS GUY OFF ME!!" ,"SOMEBODY STOP ME!!", "NORMIES, GET OUT!!") - else - speech_args[SPEECH_MESSAGE] = pick("Ree!!", "Reee!!","REEE!!","REEEEE!!") //but its usually just angry gibberish, - -/obj/item/clothing/mask/frog/cursed - clothing_flags = NONE - -/obj/item/clothing/mask/frog/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - -/obj/item/clothing/mask/frog/cursed/equipped(mob/user, slot) - var/mob/living/carbon/C = user - if(C.wear_mask == src && HAS_TRAIT_FROM(src, TRAIT_NODROP, CURSED_MASK_TRAIT)) - to_chat(user, "[src] was cursed! Ree!!") - return ..() - -/obj/item/clothing/mask/cowmask - name = "cow mask" - icon_state = "cowmask" - inhand_icon_state = "cowmask" - clothing_flags = VOICEBOX_TOGGLABLE - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/cowmask/handle_speech(datum/source, list/speech_args) - if(!(clothing_flags & VOICEBOX_DISABLED)) - speech_args[SPEECH_MESSAGE] = pick("Moooooooo!","Moo!","Moooo!") - -/obj/item/clothing/mask/cowmask/cursed - name = "cow face" - desc = "It looks like a cow mask, but closer inspection reveals it's melded onto this person's face!" - flags_inv = HIDEFACIALHAIR - clothing_flags = NONE - -/obj/item/clothing/mask/cowmask/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - playsound(get_turf(src), 'sound/magic/cowhead_curse.ogg', 50, TRUE) - -/obj/item/clothing/mask/horsehead - name = "horse head mask" - desc = "A mask made of soft vinyl and latex, representing the head of a horse." - icon_state = "horsehead" - inhand_icon_state = "horsehead" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDEEYES|HIDEEARS - w_class = WEIGHT_CLASS_SMALL - clothing_flags = VOICEBOX_TOGGLABLE - -/obj/item/clothing/mask/horsehead/handle_speech(datum/source, list/speech_args) - if(!(clothing_flags & VOICEBOX_DISABLED)) - speech_args[SPEECH_MESSAGE] = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!") - -/obj/item/clothing/mask/horsehead/cursed - name = "horse face" - desc = "It initially looks like a mask, but it's melded into the poor person's face." - clothing_flags = NONE - flags_inv = HIDEFACIALHAIR - -/obj/item/clothing/mask/horsehead/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - playsound(get_turf(src), 'sound/magic/horsehead_curse.ogg', 50, TRUE) - -/obj/item/clothing/mask/rat - name = "rat mask" - desc = "A mask made of soft vinyl and latex, representing the head of a rat." - icon_state = "rat" - inhand_icon_state = "rat" - flags_inv = HIDEFACE - flags_cover = MASKCOVERSMOUTH - -/obj/item/clothing/mask/rat/fox - name = "fox mask" - desc = "A mask made of soft vinyl and latex, representing the head of a fox." - icon_state = "fox" - inhand_icon_state = "fox" - -/obj/item/clothing/mask/rat/bee - name = "bee mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bee." - icon_state = "bee" - inhand_icon_state = "bee" - -/obj/item/clothing/mask/rat/bear - name = "bear mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bear." - icon_state = "bear" - inhand_icon_state = "bear" - -/obj/item/clothing/mask/rat/bat - name = "bat mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bat." - icon_state = "bat" - inhand_icon_state = "bat" - -/obj/item/clothing/mask/rat/raven - name = "raven mask" - desc = "A mask made of soft vinyl and latex, representing the head of a raven." - icon_state = "raven" - inhand_icon_state = "raven" - -/obj/item/clothing/mask/rat/jackal - name = "jackal mask" - desc = "A mask made of soft vinyl and latex, representing the head of a jackal." - icon_state = "jackal" - inhand_icon_state = "jackal" - -/obj/item/clothing/mask/rat/tribal - name = "tribal mask" - desc = "A mask carved out of wood, detailed carefully by hand." - icon_state = "bumba" - inhand_icon_state = "bumba" - -/obj/item/clothing/mask/bandana - name = "botany bandana" - desc = "A fine bandana with nanotech lining and a hydroponics pattern." - w_class = WEIGHT_CLASS_TINY - flags_cover = MASKCOVERSMOUTH - flags_inv = HIDEFACE|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - visor_flags_cover = MASKCOVERSMOUTH | PEPPERPROOF - slot_flags = ITEM_SLOT_MASK - adjusted_flags = ITEM_SLOT_HEAD - icon_state = "bandbotany" - -/obj/item/clothing/mask/bandana/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/bandana/AltClick(mob/user) - . = ..() - if(iscarbon(user)) - var/mob/living/carbon/C = user - if((C.get_item_by_slot(ITEM_SLOT_HEAD == src)) || (C.get_item_by_slot(ITEM_SLOT_MASK) == src)) - to_chat(user, "You can't tie [src] while wearing it!") - return - if(slot_flags & ITEM_SLOT_HEAD) - to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") - else - if(user.is_holding(src)) - var/obj/item/clothing/neck/neckerchief/nk = new(src) - nk.name = "[name] neckerchief" - nk.desc = "[desc] It's tied up like a neckerchief." - nk.icon_state = icon_state - nk.worn_icon = 'icons/misc/hidden.dmi' //hide underlying neckerchief object while it applies its own mutable appearance - nk.sourceBandanaType = src.type - var/currentHandIndex = user.get_held_index_of_item(src) - user.transferItemToLoc(src, null) - user.put_in_hand(nk, currentHandIndex) - user.visible_message("You tie [src] up like a neckerchief.", "[user] ties [src] up like a neckerchief.") - qdel(src) - else - to_chat(user, "You must be holding [src] in order to tie it!") - -/obj/item/clothing/mask/bandana/red - name = "red bandana" - desc = "A fine red bandana with nanotech lining." - icon_state = "bandred" - -/obj/item/clothing/mask/bandana/blue - name = "blue bandana" - desc = "A fine blue bandana with nanotech lining." - icon_state = "bandblue" - -/obj/item/clothing/mask/bandana/green - name = "green bandana" - desc = "A fine green bandana with nanotech lining." - icon_state = "bandgreen" - -/obj/item/clothing/mask/bandana/gold - name = "gold bandana" - desc = "A fine gold bandana with nanotech lining." - icon_state = "bandgold" - -/obj/item/clothing/mask/bandana/black - name = "black bandana" - desc = "A fine black bandana with nanotech lining." - icon_state = "bandblack" - -/obj/item/clothing/mask/bandana/skull - name = "skull bandana" - desc = "A fine black bandana with nanotech lining and a skull emblem." - icon_state = "bandskull" - -/obj/item/clothing/mask/bandana/durathread - name = "durathread bandana" - desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." - icon_state = "banddurathread" - -/obj/item/clothing/mask/mummy - name = "mummy mask" - desc = "Ancient bandages." - icon_state = "mummy_mask" - inhand_icon_state = "mummy_mask" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/mask/scarecrow - name = "sack mask" - desc = "A burlap sack with eyeholes." - icon_state = "scarecrow_sack" - inhand_icon_state = "scarecrow_sack" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/mask/gondola - name = "gondola mask" - desc = "Genuine gondola fur." - icon_state = "gondola" - inhand_icon_state = "gondola" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/gondola/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = " [message]" - var/list/spurdo_words = strings("spurdo_replacement.json", "spurdo") - for(var/key in spurdo_words) - var/value = spurdo_words[key] - if(islist(value)) - value = pick(value) - message = replacetextEx(message,regex(uppertext(key),"g"), "[uppertext(value)]") - message = replacetextEx(message,regex(capitalize(key),"g"), "[capitalize(value)]") - message = replacetextEx(message,regex(key,"g"), "[value]") - speech_args[SPEECH_MESSAGE] = trim(message) +/obj/item/clothing/mask/muzzle + name = "muzzle" + desc = "To stop that awful noise." + icon_state = "muzzle" + inhand_icon_state = "blindfold" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.9 + equip_delay_other = 20 + +/obj/item/clothing/mask/muzzle/attack_paw(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.wear_mask) + to_chat(user, "You need help taking this off!") + return + ..() + +/obj/item/clothing/mask/surgical + name = "sterile mask" + desc = "A sterile mask designed to help prevent the spread of diseases." + icon_state = "sterile" + inhand_icon_state = "sterile" + w_class = WEIGHT_CLASS_TINY + flags_inv = HIDEFACE + flags_cover = MASKCOVERSMOUTH + visor_flags_inv = HIDEFACE + visor_flags_cover = MASKCOVERSMOUTH + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.01 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 25, "rad" = 0, "fire" = 0, "acid" = 0) + actions_types = list(/datum/action/item_action/adjust) + +/obj/item/clothing/mask/surgical/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/fakemoustache + name = "fake moustache" + desc = "Warning: moustache is fake." + icon_state = "fake-moustache" + flags_inv = HIDEFACE + +/obj/item/clothing/mask/fakemoustache/italian + name = "italian moustache" + desc = "Made from authentic Italian moustache hairs. Gives the wearer an irresistable urge to gesticulate wildly." + modifies_speech = TRUE + +/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = " [message]" + var/list/italian_words = strings("italian_replacement.json", "italian") + + for(var/key in italian_words) + var/value = italian_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(3)) + message += pick(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!") + speech_args[SPEECH_MESSAGE] = trim(message) + +/obj/item/clothing/mask/joy + name = "joy mask" + desc = "Express your happiness or hide your sorrows with this laughing face with crying tears of joy cutout." + icon_state = "joy" + +/obj/item/clothing/mask/pig + name = "pig mask" + desc = "A rubber pig mask with a built-in voice modulator." + icon_state = "pig" + inhand_icon_state = "pig" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + clothing_flags = VOICEBOX_TOGGLABLE + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/pig/handle_speech(datum/source, list/speech_args) + if(!(clothing_flags & VOICEBOX_DISABLED)) + speech_args[SPEECH_MESSAGE] = pick("Oink!","Squeeeeeeee!","Oink Oink!") + +/obj/item/clothing/mask/pig/cursed + name = "pig face" + desc = "It looks like a mask, but closer inspection reveals it's melded onto this person's face!" + flags_inv = HIDEFACIALHAIR + clothing_flags = NONE + +/obj/item/clothing/mask/pig/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + playsound(get_turf(src), 'sound/magic/pighead_curse.ogg', 50, TRUE) + +///frog mask - reeee!! +/obj/item/clothing/mask/frog + name = "frog mask" + desc = "An ancient mask carved in the shape of a frog.
                Sanity is like gravity, all it needs is a push." + icon_state = "frog" + inhand_icon_state = "frog" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + clothing_flags = VOICEBOX_TOGGLABLE + modifies_speech = TRUE + +/obj/item/clothing/mask/frog/handle_speech(datum/source, list/speech_args) //whenever you speak + if(!(clothing_flags & VOICEBOX_DISABLED)) + if(prob(5)) //sometimes, the angry spirit finds others words to speak. + speech_args[SPEECH_MESSAGE] = pick("HUUUUU!!","SMOOOOOKIN'!!","Hello my baby, hello my honey, hello my rag-time gal.", "Feels bad, man.", "GIT DIS GUY OFF ME!!" ,"SOMEBODY STOP ME!!", "NORMIES, GET OUT!!") + else + speech_args[SPEECH_MESSAGE] = pick("Ree!!", "Reee!!","REEE!!","REEEEE!!") //but its usually just angry gibberish, + +/obj/item/clothing/mask/frog/cursed + clothing_flags = NONE + +/obj/item/clothing/mask/frog/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + +/obj/item/clothing/mask/frog/cursed/equipped(mob/user, slot) + var/mob/living/carbon/C = user + if(C.wear_mask == src && HAS_TRAIT_FROM(src, TRAIT_NODROP, CURSED_MASK_TRAIT)) + to_chat(user, "[src] was cursed! Ree!!") + return ..() + +/obj/item/clothing/mask/cowmask + name = "cow mask" + icon_state = "cowmask" + inhand_icon_state = "cowmask" + clothing_flags = VOICEBOX_TOGGLABLE + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/cowmask/handle_speech(datum/source, list/speech_args) + if(!(clothing_flags & VOICEBOX_DISABLED)) + speech_args[SPEECH_MESSAGE] = pick("Moooooooo!","Moo!","Moooo!") + +/obj/item/clothing/mask/cowmask/cursed + name = "cow face" + desc = "It looks like a cow mask, but closer inspection reveals it's melded onto this person's face!" + flags_inv = HIDEFACIALHAIR + clothing_flags = NONE + +/obj/item/clothing/mask/cowmask/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + playsound(get_turf(src), 'sound/magic/cowhead_curse.ogg', 50, TRUE) + +/obj/item/clothing/mask/horsehead + name = "horse head mask" + desc = "A mask made of soft vinyl and latex, representing the head of a horse." + icon_state = "horsehead" + inhand_icon_state = "horsehead" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDEEYES|HIDEEARS + w_class = WEIGHT_CLASS_SMALL + clothing_flags = VOICEBOX_TOGGLABLE + +/obj/item/clothing/mask/horsehead/handle_speech(datum/source, list/speech_args) + if(!(clothing_flags & VOICEBOX_DISABLED)) + speech_args[SPEECH_MESSAGE] = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!") + +/obj/item/clothing/mask/horsehead/cursed + name = "horse face" + desc = "It initially looks like a mask, but it's melded into the poor person's face." + clothing_flags = NONE + flags_inv = HIDEFACIALHAIR + +/obj/item/clothing/mask/horsehead/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + playsound(get_turf(src), 'sound/magic/horsehead_curse.ogg', 50, TRUE) + +/obj/item/clothing/mask/rat + name = "rat mask" + desc = "A mask made of soft vinyl and latex, representing the head of a rat." + icon_state = "rat" + inhand_icon_state = "rat" + flags_inv = HIDEFACE + flags_cover = MASKCOVERSMOUTH + +/obj/item/clothing/mask/rat/fox + name = "fox mask" + desc = "A mask made of soft vinyl and latex, representing the head of a fox." + icon_state = "fox" + inhand_icon_state = "fox" + +/obj/item/clothing/mask/rat/bee + name = "bee mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bee." + icon_state = "bee" + inhand_icon_state = "bee" + +/obj/item/clothing/mask/rat/bear + name = "bear mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bear." + icon_state = "bear" + inhand_icon_state = "bear" + +/obj/item/clothing/mask/rat/bat + name = "bat mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bat." + icon_state = "bat" + inhand_icon_state = "bat" + +/obj/item/clothing/mask/rat/raven + name = "raven mask" + desc = "A mask made of soft vinyl and latex, representing the head of a raven." + icon_state = "raven" + inhand_icon_state = "raven" + +/obj/item/clothing/mask/rat/jackal + name = "jackal mask" + desc = "A mask made of soft vinyl and latex, representing the head of a jackal." + icon_state = "jackal" + inhand_icon_state = "jackal" + +/obj/item/clothing/mask/rat/tribal + name = "tribal mask" + desc = "A mask carved out of wood, detailed carefully by hand." + icon_state = "bumba" + inhand_icon_state = "bumba" + +/obj/item/clothing/mask/bandana + name = "botany bandana" + desc = "A fine bandana with nanotech lining and a hydroponics pattern." + w_class = WEIGHT_CLASS_TINY + flags_cover = MASKCOVERSMOUTH + flags_inv = HIDEFACE|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + visor_flags_cover = MASKCOVERSMOUTH | PEPPERPROOF + slot_flags = ITEM_SLOT_MASK + adjusted_flags = ITEM_SLOT_HEAD + icon_state = "bandbotany" + +/obj/item/clothing/mask/bandana/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/bandana/AltClick(mob/user) + . = ..() + if(iscarbon(user)) + var/mob/living/carbon/C = user + if((C.get_item_by_slot(ITEM_SLOT_HEAD == src)) || (C.get_item_by_slot(ITEM_SLOT_MASK) == src)) + to_chat(user, "You can't tie [src] while wearing it!") + return + if(slot_flags & ITEM_SLOT_HEAD) + to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") + else + if(user.is_holding(src)) + var/obj/item/clothing/neck/neckerchief/nk = new(src) + nk.name = "[name] neckerchief" + nk.desc = "[desc] It's tied up like a neckerchief." + nk.icon_state = icon_state + nk.worn_icon = 'icons/misc/hidden.dmi' //hide underlying neckerchief object while it applies its own mutable appearance + nk.sourceBandanaType = src.type + var/currentHandIndex = user.get_held_index_of_item(src) + user.transferItemToLoc(src, null) + user.put_in_hand(nk, currentHandIndex) + user.visible_message("You tie [src] up like a neckerchief.", "[user] ties [src] up like a neckerchief.") + qdel(src) + else + to_chat(user, "You must be holding [src] in order to tie it!") + +/obj/item/clothing/mask/bandana/red + name = "red bandana" + desc = "A fine red bandana with nanotech lining." + icon_state = "bandred" + +/obj/item/clothing/mask/bandana/blue + name = "blue bandana" + desc = "A fine blue bandana with nanotech lining." + icon_state = "bandblue" + +/obj/item/clothing/mask/bandana/green + name = "green bandana" + desc = "A fine green bandana with nanotech lining." + icon_state = "bandgreen" + +/obj/item/clothing/mask/bandana/gold + name = "gold bandana" + desc = "A fine gold bandana with nanotech lining." + icon_state = "bandgold" + +/obj/item/clothing/mask/bandana/black + name = "black bandana" + desc = "A fine black bandana with nanotech lining." + icon_state = "bandblack" + +/obj/item/clothing/mask/bandana/skull + name = "skull bandana" + desc = "A fine black bandana with nanotech lining and a skull emblem." + icon_state = "bandskull" + +/obj/item/clothing/mask/bandana/durathread + name = "durathread bandana" + desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." + icon_state = "banddurathread" + +/obj/item/clothing/mask/mummy + name = "mummy mask" + desc = "Ancient bandages." + icon_state = "mummy_mask" + inhand_icon_state = "mummy_mask" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/mask/scarecrow + name = "sack mask" + desc = "A burlap sack with eyeholes." + icon_state = "scarecrow_sack" + inhand_icon_state = "scarecrow_sack" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/mask/gondola + name = "gondola mask" + desc = "Genuine gondola fur." + icon_state = "gondola" + inhand_icon_state = "gondola" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/gondola/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = " [message]" + var/list/spurdo_words = strings("spurdo_replacement.json", "spurdo") + for(var/key in spurdo_words) + var/value = spurdo_words[key] + if(islist(value)) + value = pick(value) + message = replacetextEx(message,regex(uppertext(key),"g"), "[uppertext(value)]") + message = replacetextEx(message,regex(capitalize(key),"g"), "[capitalize(value)]") + message = replacetextEx(message,regex(key,"g"), "[value]") + speech_args[SPEECH_MESSAGE] = trim(message) diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index 3c32fd4d58a..6ae00237f20 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -1,277 +1,277 @@ -/obj/item/clothing/shoes - name = "shoes" - icon = 'icons/obj/clothing/shoes.dmi' - desc = "Comfortable-looking shoes." - gender = PLURAL //Carn: for grammarically correct text-parsing - var/chained = 0 - - body_parts_covered = FEET - slot_flags = ITEM_SLOT_FEET - - permeability_coefficient = 0.5 - slowdown = SHOES_SLOWDOWN - strip_delay = 1 SECONDS - var/blood_state = BLOOD_STATE_NOT_BLOODY - var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - var/offset = 0 - var/equipped_before_drop = FALSE - var/can_be_bloody = TRUE - ///Whether these shoes have laces that can be tied/untied - var/can_be_tied = TRUE - ///Are we currently tied? Can either be SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED - var/tied = SHOES_TIED - ///How long it takes to lace/unlace these shoes - var/lace_time = 5 SECONDS - ///any alerts we have active - var/obj/screen/alert/our_alert - -/obj/item/clothing/shoes/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/item/clothing/shoes/suicide_act(mob/living/carbon/user) - if(rand(2)>1) - user.visible_message("[user] begins tying \the [src] up waaay too tightly! It looks like [user.p_theyre()] trying to commit suicide!") - var/obj/item/bodypart/l_leg = user.get_bodypart(BODY_ZONE_L_LEG) - var/obj/item/bodypart/r_leg = user.get_bodypart(BODY_ZONE_R_LEG) - if(l_leg) - l_leg.dismember() - if(r_leg) - r_leg.dismember() - playsound(user, "desecration", 50, TRUE, -1) - return BRUTELOSS - else//didnt realize this suicide act existed (was in miscellaneous.dm) and didnt want to remove it, so made it a 50/50 chance. Why not! - user.visible_message("[user] is bashing [user.p_their()] own head in with [src]! Ain't that a kick in the head?") - for(var/i = 0, i < 3, i++) - sleep(3) - playsound(user, 'sound/weapons/genhit2.ogg', 50, TRUE) - return(BRUTELOSS) - -/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - var/bloody = FALSE - if(HAS_BLOOD_DNA(src)) - bloody = TRUE - else - bloody = bloody_shoes[BLOOD_STATE_HUMAN] - - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") - if(bloody) - . += mutable_appearance('icons/effects/blood.dmi', "shoeblood") - -/obj/item/clothing/shoes/examine(mob/user) - . = ..() - - if(!ishuman(loc)) - return ..() - if(tied == SHOES_UNTIED) - . += "The shoelaces are untied." - else if(tied == SHOES_KNOTTED) - . += "The shoelaces are all knotted together." - -/obj/item/clothing/shoes/equipped(mob/user, slot) - . = ..() - if(offset && (slot_flags & slot)) - user.pixel_y += offset - worn_y_dimension -= (offset * 2) - user.update_inv_shoes() - equipped_before_drop = TRUE - if(can_be_tied && tied == SHOES_UNTIED) - our_alert = user.throw_alert("shoealert", /obj/screen/alert/shoes/untied) - RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) - -/obj/item/clothing/shoes/proc/restore_offsets(mob/user) - equipped_before_drop = FALSE - user.pixel_y -= offset - worn_y_dimension = world.icon_size - -/obj/item/clothing/shoes/dropped(mob/user) - if(our_alert && our_alert.owner == user) - user.clear_alert("shoealert") - if(offset && equipped_before_drop) - restore_offsets(user) - . = ..() - -/obj/item/clothing/shoes/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_shoes() - -/obj/item/clothing/shoes/proc/clean_blood(datum/source, strength) - if(strength < CLEAN_STRENGTH_BLOOD) - return - bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - blood_state = BLOOD_STATE_NOT_BLOODY - if(ismob(loc)) - var/mob/M = loc - M.update_inv_shoes() - -/obj/item/proc/negates_gravity() - return FALSE - -/** - * adjust_laces adjusts whether our shoes (assuming they can_be_tied) and tied, untied, or knotted - * - * In addition to setting the state, it will deal with getting rid of alerts if they exist, as well as registering and unregistering the stepping signals - * - * Arguments: - * * - * * state: SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED, depending on what you want them to become - * * user: used to check to see if we're the ones unknotting our own laces - */ -/obj/item/clothing/shoes/proc/adjust_laces(state, mob/user) - if(!can_be_tied) - return - - var/mob/living/carbon/human/our_guy - if(ishuman(loc)) - our_guy = loc - - tied = state - if(tied == SHOES_TIED) - if(our_guy) - our_guy.clear_alert("shoealert") - UnregisterSignal(src, COMSIG_SHOES_STEP_ACTION) - else - if(tied == SHOES_UNTIED && our_guy && user == our_guy) - our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) // if we're the ones unknotting our own laces, of course we know they're untied - RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) - -/** - * handle_tying deals with all the actual tying/untying/knotting, inferring your intent from who you are in relation to the state of the laces - * - * If you're the wearer, you want them to move towards tied-ness (knotted -> untied -> tied). If you're not, you're pranking them, so you're moving towards knotted-ness (tied -> untied -> knotted) - * - * Arguments: - * * - * * user: who is the person interacting with the shoes? - */ -/obj/item/clothing/shoes/proc/handle_tying(mob/user) - ///our_guy here is the wearer, if one exists (and he must exist, or we don't care) - var/mob/living/carbon/human/our_guy = loc - if(!istype(our_guy)) - return - - if(!in_range(user, our_guy)) - to_chat(user, "You aren't close enough to interact with [src]'s laces!") - return - - if(user == loc && tied != SHOES_TIED) // if they're our own shoes, go tie-wards - if(INTERACTING_WITH(user, our_guy)) - to_chat(user, "You're already interacting with [src]!") - return - user.visible_message("[user] begins [tied ? "unknotting" : "tying"] the laces of [user.p_their()] [src.name].", "You begin [tied ? "unknotting" : "tying"] the laces of your [src.name]...") - - if(do_after(user, lace_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) - to_chat(user, "You [tied ? "unknot" : "tie"] the laces of your [src.name].") - if(tied == SHOES_UNTIED) - adjust_laces(SHOES_TIED, user) - else - adjust_laces(SHOES_UNTIED, user) - - else // if they're someone else's shoes, go knot-wards - var/mob/living/L = user - if(istype(L) && (L.mobility_flags & MOBILITY_STAND)) - to_chat(user, "You must be on the floor to interact with [src]!") - return - if(tied == SHOES_KNOTTED) - to_chat(user, "The laces on [loc]'s [src.name] are already a hopelessly tangled mess!") - return - if(INTERACTING_WITH(user, our_guy)) - to_chat(user, "You're already interacting with [src]!") - return - - var/mod_time = lace_time - to_chat(user, "You quietly set to work [tied ? "untying" : "knotting"] [loc]'s [src.name]...") - if(HAS_TRAIT(user, TRAIT_CLUMSY)) // based clowns trained their whole lives for this - mod_time *= 0.75 - - if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) - to_chat(user, "You [tied ? "untie" : "knot"] the laces on [loc]'s [src.name].") - if(tied == SHOES_UNTIED) - adjust_laces(SHOES_KNOTTED, user) - else - adjust_laces(SHOES_UNTIED, user) - else // if one of us moved - user.visible_message("[our_guy] stamps on [user]'s hand, mid-shoelace [tied ? "knotting" : "untying"]!", "Ow! [our_guy] stamps on your hand!", list(our_guy)) - to_chat(our_guy, "You stamp on [user]'s hand! What the- [user.p_they()] [user.p_were()] [tied ? "knotting" : "untying"] your shoelaces!") - user.emote("scream") - if(istype(L)) - var/obj/item/bodypart/ouchie = L.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) - if(ouchie) - ouchie.receive_damage(brute = 10, stamina = 40) - L.Paralyze(10) - -///checking to make sure we're still on the person we're supposed to be, for lacing do_after's -/obj/item/clothing/shoes/proc/still_shoed(mob/living/carbon/our_guy) - return (loc == our_guy) - -///check_trip runs on each step to see if we fall over as a result of our lace status. Knotted laces are a guaranteed trip, while untied shoes are just a chance to stumble -/obj/item/clothing/shoes/proc/check_trip() - var/mob/living/carbon/human/our_guy = loc - if(!istype(our_guy)) // are they REALLY /our guy/? - return - - if(tied == SHOES_KNOTTED) - our_guy.Paralyze(5) - our_guy.Knockdown(10) - our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] knotted shoelaces and falls! What a klutz!", "You trip on your knotted shoelaces and fall over!") - SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! - our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/knotted) - - else if(tied == SHOES_UNTIED) - var/wiser = TRUE // did we stumble and realize our laces are undone? - switch(rand(1, 1000)) - if(1) // .1% chance to trip and fall over (note these are per step while our laces are undone) - our_guy.Paralyze(5) - our_guy.Knockdown(10) - SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! - our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] untied shoelaces and falls! What a klutz!", "You trip on your untied shoelaces and fall over!") - - if(2 to 5) // .4% chance to stumble and lurch forward - our_guy.throw_at(get_step(our_guy, our_guy.dir), 3, 2) - to_chat(our_guy, "You stumble on your untied shoelaces and lurch forward!") - - if(6 to 13) // .7% chance to stumble and fling what we're holding - var/have_anything = FALSE - for(var/obj/item/I in our_guy.held_items) - have_anything = TRUE - our_guy.accident(I) - to_chat(our_guy, "You trip on your shoelaces a bit[have_anything ? ", flinging what you were holding" : ""]!") - - if(14 to 25) // 1.3ish% chance to stumble and be a bit off balance (like being disarmed) - to_chat(our_guy, "You stumble a bit on your untied shoelaces!") - if(!our_guy.has_movespeed_modifier(/datum/movespeed_modifier/shove)) - our_guy.add_movespeed_modifier(/datum/movespeed_modifier/shove) - addtimer(CALLBACK(our_guy, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH) - - if(26 to 1000) - wiser = FALSE - if(wiser) - SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "untied", /datum/mood_event/untied) // well we realized they're untied now! - our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) - - -/obj/item/clothing/shoes/attack_hand(mob/living/carbon/human/user) - if(!istype(user)) - return ..() - if(loc == user && tied != SHOES_TIED && (user.mobility_flags & MOBILITY_USE)) - handle_tying(user) - return - ..() - -/obj/item/clothing/shoes/attack_self(mob/user) - . = ..() - - if(INTERACTING_WITH(user, src)) - to_chat(user, "You're already interacting with [src]!") - return - - to_chat(user, "You begin [tied ? "untying" : "tying"] the laces on [src]...") - - if(do_after(user, lace_time, needhand=TRUE, target=src,extra_checks=CALLBACK(src, .proc/still_shoed, user))) - to_chat(user, "You [tied ? "untie" : "tie"] the laces on [src].") - adjust_laces(tied ? SHOES_TIED : SHOES_UNTIED, user) +/obj/item/clothing/shoes + name = "shoes" + icon = 'icons/obj/clothing/shoes.dmi' + desc = "Comfortable-looking shoes." + gender = PLURAL //Carn: for grammarically correct text-parsing + var/chained = 0 + + body_parts_covered = FEET + slot_flags = ITEM_SLOT_FEET + + permeability_coefficient = 0.5 + slowdown = SHOES_SLOWDOWN + strip_delay = 1 SECONDS + var/blood_state = BLOOD_STATE_NOT_BLOODY + var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + var/offset = 0 + var/equipped_before_drop = FALSE + var/can_be_bloody = TRUE + ///Whether these shoes have laces that can be tied/untied + var/can_be_tied = TRUE + ///Are we currently tied? Can either be SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED + var/tied = SHOES_TIED + ///How long it takes to lace/unlace these shoes + var/lace_time = 5 SECONDS + ///any alerts we have active + var/obj/screen/alert/our_alert + +/obj/item/clothing/shoes/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/item/clothing/shoes/suicide_act(mob/living/carbon/user) + if(rand(2)>1) + user.visible_message("[user] begins tying \the [src] up waaay too tightly! It looks like [user.p_theyre()] trying to commit suicide!") + var/obj/item/bodypart/l_leg = user.get_bodypart(BODY_ZONE_L_LEG) + var/obj/item/bodypart/r_leg = user.get_bodypart(BODY_ZONE_R_LEG) + if(l_leg) + l_leg.dismember() + if(r_leg) + r_leg.dismember() + playsound(user, "desecration", 50, TRUE, -1) + return BRUTELOSS + else//didnt realize this suicide act existed (was in miscellaneous.dm) and didnt want to remove it, so made it a 50/50 chance. Why not! + user.visible_message("[user] is bashing [user.p_their()] own head in with [src]! Ain't that a kick in the head?") + for(var/i = 0, i < 3, i++) + sleep(3) + playsound(user, 'sound/weapons/genhit2.ogg', 50, TRUE) + return(BRUTELOSS) + +/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + var/bloody = FALSE + if(HAS_BLOOD_DNA(src)) + bloody = TRUE + else + bloody = bloody_shoes[BLOOD_STATE_HUMAN] + + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") + if(bloody) + . += mutable_appearance('icons/effects/blood.dmi', "shoeblood") + +/obj/item/clothing/shoes/examine(mob/user) + . = ..() + + if(!ishuman(loc)) + return ..() + if(tied == SHOES_UNTIED) + . += "The shoelaces are untied." + else if(tied == SHOES_KNOTTED) + . += "The shoelaces are all knotted together." + +/obj/item/clothing/shoes/equipped(mob/user, slot) + . = ..() + if(offset && (slot_flags & slot)) + user.pixel_y += offset + worn_y_dimension -= (offset * 2) + user.update_inv_shoes() + equipped_before_drop = TRUE + if(can_be_tied && tied == SHOES_UNTIED) + our_alert = user.throw_alert("shoealert", /obj/screen/alert/shoes/untied) + RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) + +/obj/item/clothing/shoes/proc/restore_offsets(mob/user) + equipped_before_drop = FALSE + user.pixel_y -= offset + worn_y_dimension = world.icon_size + +/obj/item/clothing/shoes/dropped(mob/user) + if(our_alert && our_alert.owner == user) + user.clear_alert("shoealert") + if(offset && equipped_before_drop) + restore_offsets(user) + . = ..() + +/obj/item/clothing/shoes/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_shoes() + +/obj/item/clothing/shoes/proc/clean_blood(datum/source, strength) + if(strength < CLEAN_STRENGTH_BLOOD) + return + bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + blood_state = BLOOD_STATE_NOT_BLOODY + if(ismob(loc)) + var/mob/M = loc + M.update_inv_shoes() + +/obj/item/proc/negates_gravity() + return FALSE + +/** + * adjust_laces adjusts whether our shoes (assuming they can_be_tied) and tied, untied, or knotted + * + * In addition to setting the state, it will deal with getting rid of alerts if they exist, as well as registering and unregistering the stepping signals + * + * Arguments: + * * + * * state: SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED, depending on what you want them to become + * * user: used to check to see if we're the ones unknotting our own laces + */ +/obj/item/clothing/shoes/proc/adjust_laces(state, mob/user) + if(!can_be_tied) + return + + var/mob/living/carbon/human/our_guy + if(ishuman(loc)) + our_guy = loc + + tied = state + if(tied == SHOES_TIED) + if(our_guy) + our_guy.clear_alert("shoealert") + UnregisterSignal(src, COMSIG_SHOES_STEP_ACTION) + else + if(tied == SHOES_UNTIED && our_guy && user == our_guy) + our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) // if we're the ones unknotting our own laces, of course we know they're untied + RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) + +/** + * handle_tying deals with all the actual tying/untying/knotting, inferring your intent from who you are in relation to the state of the laces + * + * If you're the wearer, you want them to move towards tied-ness (knotted -> untied -> tied). If you're not, you're pranking them, so you're moving towards knotted-ness (tied -> untied -> knotted) + * + * Arguments: + * * + * * user: who is the person interacting with the shoes? + */ +/obj/item/clothing/shoes/proc/handle_tying(mob/user) + ///our_guy here is the wearer, if one exists (and he must exist, or we don't care) + var/mob/living/carbon/human/our_guy = loc + if(!istype(our_guy)) + return + + if(!in_range(user, our_guy)) + to_chat(user, "You aren't close enough to interact with [src]'s laces!") + return + + if(user == loc && tied != SHOES_TIED) // if they're our own shoes, go tie-wards + if(INTERACTING_WITH(user, our_guy)) + to_chat(user, "You're already interacting with [src]!") + return + user.visible_message("[user] begins [tied ? "unknotting" : "tying"] the laces of [user.p_their()] [src.name].", "You begin [tied ? "unknotting" : "tying"] the laces of your [src.name]...") + + if(do_after(user, lace_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) + to_chat(user, "You [tied ? "unknot" : "tie"] the laces of your [src.name].") + if(tied == SHOES_UNTIED) + adjust_laces(SHOES_TIED, user) + else + adjust_laces(SHOES_UNTIED, user) + + else // if they're someone else's shoes, go knot-wards + var/mob/living/L = user + if(istype(L) && (L.mobility_flags & MOBILITY_STAND)) + to_chat(user, "You must be on the floor to interact with [src]!") + return + if(tied == SHOES_KNOTTED) + to_chat(user, "The laces on [loc]'s [src.name] are already a hopelessly tangled mess!") + return + if(INTERACTING_WITH(user, our_guy)) + to_chat(user, "You're already interacting with [src]!") + return + + var/mod_time = lace_time + to_chat(user, "You quietly set to work [tied ? "untying" : "knotting"] [loc]'s [src.name]...") + if(HAS_TRAIT(user, TRAIT_CLUMSY)) // based clowns trained their whole lives for this + mod_time *= 0.75 + + if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) + to_chat(user, "You [tied ? "untie" : "knot"] the laces on [loc]'s [src.name].") + if(tied == SHOES_UNTIED) + adjust_laces(SHOES_KNOTTED, user) + else + adjust_laces(SHOES_UNTIED, user) + else // if one of us moved + user.visible_message("[our_guy] stamps on [user]'s hand, mid-shoelace [tied ? "knotting" : "untying"]!", "Ow! [our_guy] stamps on your hand!", list(our_guy)) + to_chat(our_guy, "You stamp on [user]'s hand! What the- [user.p_they()] [user.p_were()] [tied ? "knotting" : "untying"] your shoelaces!") + user.emote("scream") + if(istype(L)) + var/obj/item/bodypart/ouchie = L.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + if(ouchie) + ouchie.receive_damage(brute = 10, stamina = 40) + L.Paralyze(10) + +///checking to make sure we're still on the person we're supposed to be, for lacing do_after's +/obj/item/clothing/shoes/proc/still_shoed(mob/living/carbon/our_guy) + return (loc == our_guy) + +///check_trip runs on each step to see if we fall over as a result of our lace status. Knotted laces are a guaranteed trip, while untied shoes are just a chance to stumble +/obj/item/clothing/shoes/proc/check_trip() + var/mob/living/carbon/human/our_guy = loc + if(!istype(our_guy)) // are they REALLY /our guy/? + return + + if(tied == SHOES_KNOTTED) + our_guy.Paralyze(5) + our_guy.Knockdown(10) + our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] knotted shoelaces and falls! What a klutz!", "You trip on your knotted shoelaces and fall over!") + SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! + our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/knotted) + + else if(tied == SHOES_UNTIED) + var/wiser = TRUE // did we stumble and realize our laces are undone? + switch(rand(1, 1000)) + if(1) // .1% chance to trip and fall over (note these are per step while our laces are undone) + our_guy.Paralyze(5) + our_guy.Knockdown(10) + SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! + our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] untied shoelaces and falls! What a klutz!", "You trip on your untied shoelaces and fall over!") + + if(2 to 5) // .4% chance to stumble and lurch forward + our_guy.throw_at(get_step(our_guy, our_guy.dir), 3, 2) + to_chat(our_guy, "You stumble on your untied shoelaces and lurch forward!") + + if(6 to 13) // .7% chance to stumble and fling what we're holding + var/have_anything = FALSE + for(var/obj/item/I in our_guy.held_items) + have_anything = TRUE + our_guy.accident(I) + to_chat(our_guy, "You trip on your shoelaces a bit[have_anything ? ", flinging what you were holding" : ""]!") + + if(14 to 25) // 1.3ish% chance to stumble and be a bit off balance (like being disarmed) + to_chat(our_guy, "You stumble a bit on your untied shoelaces!") + if(!our_guy.has_movespeed_modifier(/datum/movespeed_modifier/shove)) + our_guy.add_movespeed_modifier(/datum/movespeed_modifier/shove) + addtimer(CALLBACK(our_guy, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH) + + if(26 to 1000) + wiser = FALSE + if(wiser) + SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "untied", /datum/mood_event/untied) // well we realized they're untied now! + our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) + + +/obj/item/clothing/shoes/attack_hand(mob/living/carbon/human/user) + if(!istype(user)) + return ..() + if(loc == user && tied != SHOES_TIED && (user.mobility_flags & MOBILITY_USE)) + handle_tying(user) + return + ..() + +/obj/item/clothing/shoes/attack_self(mob/user) + . = ..() + + if(INTERACTING_WITH(user, src)) + to_chat(user, "You're already interacting with [src]!") + return + + to_chat(user, "You begin [tied ? "untying" : "tying"] the laces on [src]...") + + if(do_after(user, lace_time, needhand=TRUE, target=src,extra_checks=CALLBACK(src, .proc/still_shoed, user))) + to_chat(user, "You [tied ? "untie" : "tie"] the laces on [src].") + adjust_laces(tied ? SHOES_TIED : SHOES_UNTIED, user) diff --git a/code/modules/clothing/shoes/colour.dm b/code/modules/clothing/shoes/colour.dm index 3a610bc68f3..40092bf7d10 100644 --- a/code/modules/clothing/shoes/colour.dm +++ b/code/modules/clothing/shoes/colour.dm @@ -1,89 +1,89 @@ -/obj/item/clothing/shoes/sneakers - dying_key = DYE_REGISTRY_SNEAKERS - -/obj/item/clothing/shoes/sneakers/black - name = "black shoes" - icon_state = "black" - desc = "A pair of black shoes." - custom_price = 50 - - cold_protection = FEET - min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT - heat_protection = FEET - max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT - -/obj/item/clothing/shoes/sneakers/brown - name = "brown shoes" - desc = "A pair of brown shoes." - icon_state = "brown" - -/obj/item/clothing/shoes/sneakers/blue - name = "blue shoes" - icon_state = "blue" - -/obj/item/clothing/shoes/sneakers/green - name = "green shoes" - icon_state = "green" - -/obj/item/clothing/shoes/sneakers/yellow - name = "yellow shoes" - icon_state = "yellow" - -/obj/item/clothing/shoes/sneakers/purple - name = "purple shoes" - icon_state = "purple" - -/obj/item/clothing/shoes/sneakers/red - name = "red shoes" - desc = "Stylish red shoes." - icon_state = "red" - -/obj/item/clothing/shoes/sneakers/white - name = "white shoes" - icon_state = "white" - permeability_coefficient = 0.01 - -/obj/item/clothing/shoes/sneakers/rainbow - name = "rainbow shoes" - desc = "Very gay shoes." - icon_state = "rain_bow" - -/obj/item/clothing/shoes/sneakers/orange - name = "orange shoes" - icon_state = "orange" - -/obj/item/clothing/shoes/sneakers/orange/attack_self(mob/user) - if (src.chained) - src.chained = null - src.slowdown = SHOES_SLOWDOWN - new /obj/item/restraints/handcuffs( user.loc ) - src.icon_state = "orange" - return - -/obj/item/clothing/shoes/sneakers/orange/attackby(obj/H, loc, params) - ..() - // Note: not using istype here because we want to ignore all subtypes - if (H.type == /obj/item/restraints/handcuffs && !chained) - qdel(H) - src.chained = 1 - src.slowdown = 15 - src.icon_state = "orange1" - return - -/obj/item/clothing/shoes/sneakers/orange/allow_attack_hand_drop(mob/user) - if(ishuman(user)) - var/mob/living/carbon/human/C = user - if(C.shoes == src && chained == 1) - to_chat(user, "You need help taking these off!") - return FALSE - return ..() - -/obj/item/clothing/shoes/sneakers/orange/MouseDrop(atom/over) - var/mob/m = usr - if(ishuman(m)) - var/mob/living/carbon/human/c = m - if(c.shoes == src && chained == 1) - to_chat(c, "You need help taking these off!") - return - return ..() - +/obj/item/clothing/shoes/sneakers + dying_key = DYE_REGISTRY_SNEAKERS + +/obj/item/clothing/shoes/sneakers/black + name = "black shoes" + icon_state = "black" + desc = "A pair of black shoes." + custom_price = 50 + + cold_protection = FEET + min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT + heat_protection = FEET + max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT + +/obj/item/clothing/shoes/sneakers/brown + name = "brown shoes" + desc = "A pair of brown shoes." + icon_state = "brown" + +/obj/item/clothing/shoes/sneakers/blue + name = "blue shoes" + icon_state = "blue" + +/obj/item/clothing/shoes/sneakers/green + name = "green shoes" + icon_state = "green" + +/obj/item/clothing/shoes/sneakers/yellow + name = "yellow shoes" + icon_state = "yellow" + +/obj/item/clothing/shoes/sneakers/purple + name = "purple shoes" + icon_state = "purple" + +/obj/item/clothing/shoes/sneakers/red + name = "red shoes" + desc = "Stylish red shoes." + icon_state = "red" + +/obj/item/clothing/shoes/sneakers/white + name = "white shoes" + icon_state = "white" + permeability_coefficient = 0.01 + +/obj/item/clothing/shoes/sneakers/rainbow + name = "rainbow shoes" + desc = "Very gay shoes." + icon_state = "rain_bow" + +/obj/item/clothing/shoes/sneakers/orange + name = "orange shoes" + icon_state = "orange" + +/obj/item/clothing/shoes/sneakers/orange/attack_self(mob/user) + if (src.chained) + src.chained = null + src.slowdown = SHOES_SLOWDOWN + new /obj/item/restraints/handcuffs( user.loc ) + src.icon_state = "orange" + return + +/obj/item/clothing/shoes/sneakers/orange/attackby(obj/H, loc, params) + ..() + // Note: not using istype here because we want to ignore all subtypes + if (H.type == /obj/item/restraints/handcuffs && !chained) + qdel(H) + src.chained = 1 + src.slowdown = 15 + src.icon_state = "orange1" + return + +/obj/item/clothing/shoes/sneakers/orange/allow_attack_hand_drop(mob/user) + if(ishuman(user)) + var/mob/living/carbon/human/C = user + if(C.shoes == src && chained == 1) + to_chat(user, "You need help taking these off!") + return FALSE + return ..() + +/obj/item/clothing/shoes/sneakers/orange/MouseDrop(atom/over) + var/mob/m = usr + if(ishuman(m)) + var/mob/living/carbon/human/c = m + if(c.shoes == src && chained == 1) + to_chat(c, "You need help taking these off!") + return + return ..() + diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index 70dde1e2177..952dbe0ad80 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -1,59 +1,59 @@ -/obj/item/clothing/shoes/magboots - desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." - name = "magboots" - icon_state = "magboots0" - var/magboot_state = "magboots" - var/magpulse = 0 - var/slowdown_active = 2 - permeability_coefficient = 0.05 - actions_types = list(/datum/action/item_action/toggle) - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/shoes/magboots/verb/toggle() - set name = "Toggle Magboots" - set category = "Object" - set src in usr - if(!can_use(usr)) - return - attack_self(usr) - - -/obj/item/clothing/shoes/magboots/attack_self(mob/user) - if(magpulse) - clothing_flags &= ~NOSLIP - slowdown = SHOES_SLOWDOWN - else - clothing_flags |= NOSLIP - slowdown = slowdown_active - magpulse = !magpulse - icon_state = "[magboot_state][magpulse]" - to_chat(user, "You [magpulse ? "enable" : "disable"] the mag-pulse traction system.") - user.update_inv_shoes() //so our mob-overlays update - user.update_gravity(user.has_gravity()) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/shoes/magboots/negates_gravity() - return clothing_flags & NOSLIP - -/obj/item/clothing/shoes/magboots/examine(mob/user) - . = ..() - . += "Its mag-pulse traction system appears to be [magpulse ? "enabled" : "disabled"]." - - -/obj/item/clothing/shoes/magboots/advance - desc = "Advanced magnetic boots that have a lighter magnetic pull, placing less burden on the wearer." - name = "advanced magboots" - icon_state = "advmag0" - magboot_state = "advmag" - slowdown_active = SHOES_SLOWDOWN - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/shoes/magboots/syndie - desc = "Reverse-engineered magnetic boots that have a heavy magnetic pull. Property of Gorlex Marauders." - name = "blood-red magboots" - icon_state = "syndiemag0" - magboot_state = "syndiemag" +/obj/item/clothing/shoes/magboots + desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." + name = "magboots" + icon_state = "magboots0" + var/magboot_state = "magboots" + var/magpulse = 0 + var/slowdown_active = 2 + permeability_coefficient = 0.05 + actions_types = list(/datum/action/item_action/toggle) + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/shoes/magboots/verb/toggle() + set name = "Toggle Magboots" + set category = "Object" + set src in usr + if(!can_use(usr)) + return + attack_self(usr) + + +/obj/item/clothing/shoes/magboots/attack_self(mob/user) + if(magpulse) + clothing_flags &= ~NOSLIP + slowdown = SHOES_SLOWDOWN + else + clothing_flags |= NOSLIP + slowdown = slowdown_active + magpulse = !magpulse + icon_state = "[magboot_state][magpulse]" + to_chat(user, "You [magpulse ? "enable" : "disable"] the mag-pulse traction system.") + user.update_inv_shoes() //so our mob-overlays update + user.update_gravity(user.has_gravity()) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/shoes/magboots/negates_gravity() + return clothing_flags & NOSLIP + +/obj/item/clothing/shoes/magboots/examine(mob/user) + . = ..() + . += "Its mag-pulse traction system appears to be [magpulse ? "enabled" : "disabled"]." + + +/obj/item/clothing/shoes/magboots/advance + desc = "Advanced magnetic boots that have a lighter magnetic pull, placing less burden on the wearer." + name = "advanced magboots" + icon_state = "advmag0" + magboot_state = "advmag" + slowdown_active = SHOES_SLOWDOWN + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/shoes/magboots/syndie + desc = "Reverse-engineered magnetic boots that have a heavy magnetic pull. Property of Gorlex Marauders." + name = "blood-red magboots" + icon_state = "syndiemag0" + magboot_state = "syndiemag" diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm index e300ae09a90..7b32283cbbb 100644 --- a/code/modules/clothing/spacesuits/_spacesuits.dm +++ b/code/modules/clothing/spacesuits/_spacesuits.dm @@ -1,251 +1,251 @@ -#define THERMAL_REGULATOR_COST 18 // the cost per tick for the thermal regulator - -//Note: Everything in modules/clothing/spacesuits should have the entire suit grouped together. -// Meaning the the suit is defined directly after the corrisponding helmet. Just like below! -/obj/item/clothing/head/helmet/space - name = "space helmet" - icon_state = "spaceold" - desc = "A special helmet with solar UV shielding to protect your eyes from harmful rays." - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | SNUG_FIT - inhand_icon_state = "spaceold" - permeability_coefficient = 0.01 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - dynamic_hair_suffix = "" - dynamic_fhair_suffix = "" - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - flash_protect = FLASH_PROTECTION_WELDER - strip_delay = 50 - equip_delay_other = 50 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - dog_fashion = null - -/obj/item/clothing/suit/space - name = "space suit" - desc = "A suit that protects against low pressure environments. Has a big 13 on the back." - icon_state = "spaceold" - inhand_icon_state = "s_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.02 - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight, /obj/item/tank/internals) - slowdown = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT_OFF - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - strip_delay = 80 - equip_delay_other = 80 - resistance_flags = NONE - actions_types = list(/datum/action/item_action/toggle_spacesuit) - var/temperature_setting = BODYTEMP_NORMAL /// The default temperature setting - var/obj/item/stock_parts/cell/cell = /obj/item/stock_parts/cell/high /// If this is a path, this gets created as an object in Initialize. - var/cell_cover_open = FALSE /// Status of the cell cover on the suit - var/thermal_on = FALSE /// Status of the thermal regulator - -/obj/item/clothing/suit/space/Initialize(mapload) - . = ..() - if(ispath(cell)) - cell = new cell(src) - -/// Start Processing on the space suit when it is worn to heat the wearer -/obj/item/clothing/suit/space/equipped(mob/user, slot) - . = ..() - if(slot == ITEM_SLOT_OCLOTHING) // Check that the slot is valid - START_PROCESSING(SSobj, src) - update_hud_icon(user) // update the hud - -// On removal stop processing, save battery -/obj/item/clothing/suit/space/dropped(mob/user) - . = ..() - STOP_PROCESSING(SSobj, src) - var/mob/living/carbon/human/human = user - if(istype(human)) - human.update_spacesuit_hud_icon("0") - -// Space Suit temperature regulation and power usage -/obj/item/clothing/suit/space/process() - var/mob/living/carbon/human/user = src.loc - if(!user || !ishuman(user) || !(user.wear_suit == src)) - return - - // Do nothing if thermal regulators are off - if(!thermal_on) - return - - // If we got here, thermal regulators are on. If there's no cell, turn them - // off - if(!cell) - toggle_spacesuit() - update_hud_icon(user) - return - - // cell.use will return FALSE if charge is lower than THERMAL_REGULATOR_COST - if(!cell.use(THERMAL_REGULATOR_COST)) - toggle_spacesuit() - update_hud_icon(user) - return - - // If we got here, it means thermals are on, the cell is in and the cell has - // just had enough charge subtracted from it to power the thermal regulator - user.adjust_bodytemperature((temperature_setting - user.bodytemperature), use_steps=TRUE, capped=FALSE) - update_hud_icon(user) - -// Clean up the cell on destroy -/obj/item/clothing/suit/space/Destroy() - if(cell) - QDEL_NULL(cell) - var/mob/living/carbon/human/human = src.loc - if(istype(human)) - human.update_spacesuit_hud_icon("0") - STOP_PROCESSING(SSobj, src) - return ..() - -// Clean up the cell on destroy -/obj/item/clothing/suit/space/handle_atom_del(atom/A) - if(A == cell) - cell = null - thermal_on = FALSE - return ..() - -// support for items that interact with the cell -/obj/item/clothing/suit/space/get_cell() - return cell - -// Show the status of the suit and the cell -/obj/item/clothing/suit/space/examine(mob/user) - . = ..() - if(in_range(src, user) || isobserver(user)) - . += "The thermal regulator is [thermal_on ? "on" : "off"] and the temperature is set to \ - [round(temperature_setting-T0C,0.1)] °C ([round(temperature_setting*1.8-459.67,0.1)] °F)" - . += "The power meter shows [cell ? "[round(cell.percent(), 0.1)]%" : "!invalid!"] charge remaining." - if(cell_cover_open) - . += "The cell cover is open exposing the cell and setting knobs." - if(!cell) - . += "The slot for a cell is empty." - else - . += "\The [cell] is firmly in place." - -// object handling for accessing features of the suit -/obj/item/clothing/suit/space/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_CROWBAR) - toggle_spacesuit_cell(user) - return - else if(cell_cover_open && I.tool_behaviour == TOOL_SCREWDRIVER) - var/range_low = 20 // Default min temp c - var/range_high = 45 // default max temp c - if(obj_flags & EMAGGED) - range_low = -20 // emagged min temp c - range_high = 120 // emagged max temp c - - var/deg_c = input(user, "What temperature would you like to set the thermal regulator to? \ - ([range_low]-[range_high] degrees celcius)") as null|num - if(deg_c && deg_c >= range_low && deg_c <= range_high) - temperature_setting = round(T0C + deg_c, 0.1) - to_chat(user, "You see the readout change to [deg_c] c.") - return - else if(cell_cover_open && istype(I, /obj/item/stock_parts/cell)) - if(cell) - to_chat(user, "[src] already has a cell installed.") - return - if(user.transferItemToLoc(I, src)) - cell = I - to_chat(user, "You successfully install \the [cell] into [src].") - return - return ..() - -/// Open the cell cover when ALT+Click on the suit -/obj/item/clothing/suit/space/AltClick(mob/living/user) - if(!user || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return ..() - toggle_spacesuit_cell(user) - -/// Remove the cell whent he cover is open on CTRL+Click -/obj/item/clothing/suit/space/CtrlClick(mob/living/user) - if(user && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - if(cell_cover_open && cell) - remove_cell(user) - return - return ..() - -// Remove the cell when using the suit on its self -/obj/item/clothing/suit/space/attack_self(mob/user) - remove_cell(user) - -/// Remove the cell from the suit if the cell cover is open -/obj/item/clothing/suit/space/proc/remove_cell(mob/user) - if(cell_cover_open && cell) - user.visible_message("[user] removes \the [cell] from [src]!", \ - "You remove [cell].") - cell.add_fingerprint(user) - user.put_in_hands(cell) - cell = null - -/// Toggle the space suit's cell cover -/obj/item/clothing/suit/space/proc/toggle_spacesuit_cell(mob/user) - cell_cover_open = !cell_cover_open - to_chat(user, "You [cell_cover_open ? "open" : "close"] the cell cover on \the [src].") - -/// Toggle the space suit's thermal regulator status -/obj/item/clothing/suit/space/proc/toggle_spacesuit() - // If we're turning thermal protection on, check for valid cell and for enough - // charge that cell. If it's too low, we shouldn't bother with setting the - // thermal protection value and should just return out early. - if(!thermal_on && !(cell && cell.charge >= THERMAL_REGULATOR_COST)) - return - - thermal_on = !thermal_on - min_cold_protection_temperature = thermal_on ? SPACE_SUIT_MIN_TEMP_PROTECT : SPACE_SUIT_MIN_TEMP_PROTECT_OFF - SEND_SIGNAL(src, COMSIG_SUIT_SPACE_TOGGLE) - -// let emags override the temperature settings -/obj/item/clothing/suit/space/emag_act(mob/user) - if(!(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - user.visible_message("You emag [src], overwriting thermal regulator restrictions.") - log_game("[key_name(user)] emagged [src] at [AREACOORD(src)], overwriting thermal regulator restrictions.") - playsound(src, "sparks", 50, TRUE) - -// update the HUD icon -/obj/item/clothing/suit/space/proc/update_hud_icon(mob/user) - var/mob/living/carbon/human/human = user - - if(!cell) - human.update_spacesuit_hud_icon("missing") - return - - var/cell_percent = cell.percent() - - // Check if there's enough charge to trigger a thermal regulator tick and - // if there is, whethere the cell's capacity indicates high, medium or low - // charge based on it. - if(cell.charge >= THERMAL_REGULATOR_COST) - if(cell_percent > 60) - human.update_spacesuit_hud_icon("high") - return - if(cell_percent > 20) - human.update_spacesuit_hud_icon("mid") - return - human.update_spacesuit_hud_icon("low") - return - - human.update_spacesuit_hud_icon("empty") - return - -// zap the cell if we get hit with an emp -/obj/item/clothing/suit/space/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_CONTENTS) - return - if(cell) - cell.emp_act(severity) - -#undef THERMAL_REGULATOR_COST +#define THERMAL_REGULATOR_COST 18 // the cost per tick for the thermal regulator + +//Note: Everything in modules/clothing/spacesuits should have the entire suit grouped together. +// Meaning the the suit is defined directly after the corrisponding helmet. Just like below! +/obj/item/clothing/head/helmet/space + name = "space helmet" + icon_state = "spaceold" + desc = "A special helmet with solar UV shielding to protect your eyes from harmful rays." + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | SNUG_FIT + inhand_icon_state = "spaceold" + permeability_coefficient = 0.01 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + dynamic_hair_suffix = "" + dynamic_fhair_suffix = "" + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + flash_protect = FLASH_PROTECTION_WELDER + strip_delay = 50 + equip_delay_other = 50 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + dog_fashion = null + +/obj/item/clothing/suit/space + name = "space suit" + desc = "A suit that protects against low pressure environments. Has a big 13 on the back." + icon_state = "spaceold" + inhand_icon_state = "s_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.02 + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight, /obj/item/tank/internals) + slowdown = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT_OFF + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + strip_delay = 80 + equip_delay_other = 80 + resistance_flags = NONE + actions_types = list(/datum/action/item_action/toggle_spacesuit) + var/temperature_setting = BODYTEMP_NORMAL /// The default temperature setting + var/obj/item/stock_parts/cell/cell = /obj/item/stock_parts/cell/high /// If this is a path, this gets created as an object in Initialize. + var/cell_cover_open = FALSE /// Status of the cell cover on the suit + var/thermal_on = FALSE /// Status of the thermal regulator + +/obj/item/clothing/suit/space/Initialize(mapload) + . = ..() + if(ispath(cell)) + cell = new cell(src) + +/// Start Processing on the space suit when it is worn to heat the wearer +/obj/item/clothing/suit/space/equipped(mob/user, slot) + . = ..() + if(slot == ITEM_SLOT_OCLOTHING) // Check that the slot is valid + START_PROCESSING(SSobj, src) + update_hud_icon(user) // update the hud + +// On removal stop processing, save battery +/obj/item/clothing/suit/space/dropped(mob/user) + . = ..() + STOP_PROCESSING(SSobj, src) + var/mob/living/carbon/human/human = user + if(istype(human)) + human.update_spacesuit_hud_icon("0") + +// Space Suit temperature regulation and power usage +/obj/item/clothing/suit/space/process() + var/mob/living/carbon/human/user = src.loc + if(!user || !ishuman(user) || !(user.wear_suit == src)) + return + + // Do nothing if thermal regulators are off + if(!thermal_on) + return + + // If we got here, thermal regulators are on. If there's no cell, turn them + // off + if(!cell) + toggle_spacesuit() + update_hud_icon(user) + return + + // cell.use will return FALSE if charge is lower than THERMAL_REGULATOR_COST + if(!cell.use(THERMAL_REGULATOR_COST)) + toggle_spacesuit() + update_hud_icon(user) + return + + // If we got here, it means thermals are on, the cell is in and the cell has + // just had enough charge subtracted from it to power the thermal regulator + user.adjust_bodytemperature((temperature_setting - user.bodytemperature), use_steps=TRUE, capped=FALSE) + update_hud_icon(user) + +// Clean up the cell on destroy +/obj/item/clothing/suit/space/Destroy() + if(cell) + QDEL_NULL(cell) + var/mob/living/carbon/human/human = src.loc + if(istype(human)) + human.update_spacesuit_hud_icon("0") + STOP_PROCESSING(SSobj, src) + return ..() + +// Clean up the cell on destroy +/obj/item/clothing/suit/space/handle_atom_del(atom/A) + if(A == cell) + cell = null + thermal_on = FALSE + return ..() + +// support for items that interact with the cell +/obj/item/clothing/suit/space/get_cell() + return cell + +// Show the status of the suit and the cell +/obj/item/clothing/suit/space/examine(mob/user) + . = ..() + if(in_range(src, user) || isobserver(user)) + . += "The thermal regulator is [thermal_on ? "on" : "off"] and the temperature is set to \ + [round(temperature_setting-T0C,0.1)] °C ([round(temperature_setting*1.8-459.67,0.1)] °F)" + . += "The power meter shows [cell ? "[round(cell.percent(), 0.1)]%" : "!invalid!"] charge remaining." + if(cell_cover_open) + . += "The cell cover is open exposing the cell and setting knobs." + if(!cell) + . += "The slot for a cell is empty." + else + . += "\The [cell] is firmly in place." + +// object handling for accessing features of the suit +/obj/item/clothing/suit/space/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_CROWBAR) + toggle_spacesuit_cell(user) + return + else if(cell_cover_open && I.tool_behaviour == TOOL_SCREWDRIVER) + var/range_low = 20 // Default min temp c + var/range_high = 45 // default max temp c + if(obj_flags & EMAGGED) + range_low = -20 // emagged min temp c + range_high = 120 // emagged max temp c + + var/deg_c = input(user, "What temperature would you like to set the thermal regulator to? \ + ([range_low]-[range_high] degrees celcius)") as null|num + if(deg_c && deg_c >= range_low && deg_c <= range_high) + temperature_setting = round(T0C + deg_c, 0.1) + to_chat(user, "You see the readout change to [deg_c] c.") + return + else if(cell_cover_open && istype(I, /obj/item/stock_parts/cell)) + if(cell) + to_chat(user, "[src] already has a cell installed.") + return + if(user.transferItemToLoc(I, src)) + cell = I + to_chat(user, "You successfully install \the [cell] into [src].") + return + return ..() + +/// Open the cell cover when ALT+Click on the suit +/obj/item/clothing/suit/space/AltClick(mob/living/user) + if(!user || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return ..() + toggle_spacesuit_cell(user) + +/// Remove the cell whent he cover is open on CTRL+Click +/obj/item/clothing/suit/space/CtrlClick(mob/living/user) + if(user && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + if(cell_cover_open && cell) + remove_cell(user) + return + return ..() + +// Remove the cell when using the suit on its self +/obj/item/clothing/suit/space/attack_self(mob/user) + remove_cell(user) + +/// Remove the cell from the suit if the cell cover is open +/obj/item/clothing/suit/space/proc/remove_cell(mob/user) + if(cell_cover_open && cell) + user.visible_message("[user] removes \the [cell] from [src]!", \ + "You remove [cell].") + cell.add_fingerprint(user) + user.put_in_hands(cell) + cell = null + +/// Toggle the space suit's cell cover +/obj/item/clothing/suit/space/proc/toggle_spacesuit_cell(mob/user) + cell_cover_open = !cell_cover_open + to_chat(user, "You [cell_cover_open ? "open" : "close"] the cell cover on \the [src].") + +/// Toggle the space suit's thermal regulator status +/obj/item/clothing/suit/space/proc/toggle_spacesuit() + // If we're turning thermal protection on, check for valid cell and for enough + // charge that cell. If it's too low, we shouldn't bother with setting the + // thermal protection value and should just return out early. + if(!thermal_on && !(cell && cell.charge >= THERMAL_REGULATOR_COST)) + return + + thermal_on = !thermal_on + min_cold_protection_temperature = thermal_on ? SPACE_SUIT_MIN_TEMP_PROTECT : SPACE_SUIT_MIN_TEMP_PROTECT_OFF + SEND_SIGNAL(src, COMSIG_SUIT_SPACE_TOGGLE) + +// let emags override the temperature settings +/obj/item/clothing/suit/space/emag_act(mob/user) + if(!(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + user.visible_message("You emag [src], overwriting thermal regulator restrictions.") + log_game("[key_name(user)] emagged [src] at [AREACOORD(src)], overwriting thermal regulator restrictions.") + playsound(src, "sparks", 50, TRUE) + +// update the HUD icon +/obj/item/clothing/suit/space/proc/update_hud_icon(mob/user) + var/mob/living/carbon/human/human = user + + if(!cell) + human.update_spacesuit_hud_icon("missing") + return + + var/cell_percent = cell.percent() + + // Check if there's enough charge to trigger a thermal regulator tick and + // if there is, whethere the cell's capacity indicates high, medium or low + // charge based on it. + if(cell.charge >= THERMAL_REGULATOR_COST) + if(cell_percent > 60) + human.update_spacesuit_hud_icon("high") + return + if(cell_percent > 20) + human.update_spacesuit_hud_icon("mid") + return + human.update_spacesuit_hud_icon("low") + return + + human.update_spacesuit_hud_icon("empty") + return + +// zap the cell if we get hit with an emp +/obj/item/clothing/suit/space/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_CONTENTS) + return + if(cell) + cell.emp_act(severity) + +#undef THERMAL_REGULATOR_COST diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index d977692e96a..5c1bde4e357 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -1,461 +1,461 @@ -//miscellaneous spacesuits -/* -Contains: - - Captain's spacesuit - - Death squad's hardsuit - - SWAT suit - - Officer's beret/spacesuit - - NASA Voidsuit - - Father Christmas' magical clothes - - Pirate's spacesuit - - ERT hardsuit: command, sec, engi, med, janitor - - EVA spacesuit - - Freedom's spacesuit (freedom from vacuum's oppression) - - Carp hardsuit - - Bounty hunter hardsuit - - Blackmarket combat medic hardsuit -*/ - - //Death squad armored space suits, not hardsuits! -/obj/item/clothing/head/helmet/space/hardsuit/deathsquad - name = "MK.III SWAT Helmet" - desc = "An advanced tactical space helmet." - icon_state = "deathsquad" - inhand_icon_state = "deathsquad" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - actions_types = list() - -/obj/item/clothing/head/helmet/space/hardsuit/deathsquad/attack_self(mob/user) - return - -/obj/item/clothing/suit/space/hardsuit/deathsquad - name = "MK.III SWAT Suit" - desc = "A prototype designed to replace the ageing MK.II SWAT suit. Based on the streamlined MK.II model, the traditional ceramic and graphene plate construction was replaced with plasteel, allowing superior armor against most threats. There's room for some kind of energy projection device on the back." - icon_state = "deathsquad" - inhand_icon_state = "swat_suit" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/deathsquad - dog_fashion = /datum/dog_fashion/back/deathsquad - cell = /obj/item/stock_parts/cell/bluespace - - //NEW SWAT suit -/obj/item/clothing/suit/space/swat - name = "MK.I SWAT Suit" - desc = "A tactical space suit first developed in a joint effort by the defunct IS-ERI and Nanotrasen in 20XX for military space operations. A tried and true workhorse, it is very difficult to move in but offers robust protection against all threats!" - icon_state = "heavy" - inhand_icon_state = "swat_suit" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) - strip_delay = 120 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/helmet/space/beret - name = "officer's beret" - desc = "An armored beret commonly used by special operations officers. Uses advanced force field technology to protect the head from space." - icon_state = "beret_badge" - dynamic_hair_suffix = "+generic" - dynamic_fhair_suffix = "+generic" - flags_inv = 0 - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/suit/space/officer - name = "officer's jacket" - desc = "An armored, space-proof jacket used in special operations." - icon_state = "detective" - inhand_icon_state = "det_suit" - blood_overlay_type = "coat" - slowdown = 0 - flags_inv = 0 - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - - //NASA Voidsuit -/obj/item/clothing/head/helmet/space/nasavoid - name = "NASA Void Helmet" - desc = "An old, NASA CentCom branch designed, dark red space suit helmet." - icon_state = "void" - inhand_icon_state = "void" - -/obj/item/clothing/suit/space/nasavoid - name = "NASA Voidsuit" - icon_state = "void" - inhand_icon_state = "void" - desc = "An old, NASA CentCom branch designed, dark red space suit." - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) - -/obj/item/clothing/head/helmet/space/nasavoid/old - name = "Engineering Void Helmet" - desc = "A CentCom engineering dark red space suit helmet. While old and dusty, it still gets the job done." - icon_state = "void" - inhand_icon_state = "void" - -/obj/item/clothing/suit/space/nasavoid/old - name = "Engineering Voidsuit" - icon_state = "void" - inhand_icon_state = "void" - desc = "A CentCom engineering dark red space suit. Age has degraded the suit making is difficult to move around in." - slowdown = 4 - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) - - //Space santa outfit suit -/obj/item/clothing/head/helmet/space/santahat - name = "Santa's hat" - desc = "Ho ho ho. Merrry X-mas!" - icon_state = "santahat" - flags_cover = HEADCOVERSEYES - - dog_fashion = /datum/dog_fashion/head/santa - -/obj/item/clothing/suit/space/santa - name = "Santa's suit" - desc = "Festive!" - icon_state = "santa" - inhand_icon_state = "santa" - slowdown = 0 - allowed = list(/obj/item) //for stuffing exta special presents - - - //Space pirate outfit -/obj/item/clothing/head/helmet/space/pirate - name = "pirate hat" - desc = "Yarr." - icon_state = "pirate" - inhand_icon_state = "pirate" - armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - flags_inv = HIDEHAIR - strip_delay = 40 - equip_delay_other = 20 - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/helmet/space/pirate/bandana - name = "pirate bandana" - icon_state = "bandana" - inhand_icon_state = "bandana" - -/obj/item/clothing/suit/space/pirate - name = "pirate coat" - desc = "Yarr." - icon_state = "pirate" - inhand_icon_state = "pirate" - w_class = WEIGHT_CLASS_NORMAL - flags_inv = 0 - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) - slowdown = 0 - armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - strip_delay = 40 - equip_delay_other = 20 - - //Emergency Response Team suits -/obj/item/clothing/head/helmet/space/hardsuit/ert - name = "emergency response team commander helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has blue highlights." - icon_state = "hardsuit0-ert_commander" - inhand_icon_state = "hardsuit0-ert_commander" - hardsuit_type = "ert_commander" - armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - strip_delay = 130 - brightness_on = 7 - resistance_flags = FIRE_PROOF - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - -/obj/item/clothing/head/helmet/space/hardsuit/ert/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) - -/obj/item/clothing/suit/space/hardsuit/ert - name = "emergency response team commander hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has blue highlights. Offers superb protection against environmental hazards." - icon_state = "ert_command" - inhand_icon_state = "ert_command" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - slowdown = 0 - strip_delay = 130 - resistance_flags = FIRE_PROOF - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - cell = /obj/item/stock_parts/cell/bluespace - -// ERT suit's gets EMP Protection -/obj/item/clothing/suit/space/hardsuit/ert/Initialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_CONTENTS) - - //ERT Security -/obj/item/clothing/head/helmet/space/hardsuit/ert/sec - name = "emergency response team security helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has red highlights." - icon_state = "hardsuit0-ert_security" - inhand_icon_state = "hardsuit0-ert_security" - hardsuit_type = "ert_security" - -/obj/item/clothing/suit/space/hardsuit/ert/sec - name = "emergency response team security hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has red highlights. Offers superb protection against environmental hazards." - icon_state = "ert_security" - inhand_icon_state = "ert_security" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/sec - - //ERT Engineering -/obj/item/clothing/head/helmet/space/hardsuit/ert/engi - name = "emergency response team engineering helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has orange highlights." - icon_state = "hardsuit0-ert_engineer" - inhand_icon_state = "hardsuit0-ert_engineer" - hardsuit_type = "ert_engineer" - -/obj/item/clothing/suit/space/hardsuit/ert/engi - name = "emergency response team engineering hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has orange highlights. Offers superb protection against environmental hazards." - icon_state = "ert_engineer" - inhand_icon_state = "ert_engineer" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/engi - - //ERT Medical -/obj/item/clothing/head/helmet/space/hardsuit/ert/med - name = "emergency response team medical helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has white highlights." - icon_state = "hardsuit0-ert_medical" - inhand_icon_state = "hardsuit0-ert_medical" - hardsuit_type = "ert_medical" - -/obj/item/clothing/suit/space/hardsuit/ert/med - name = "emergency response team medical hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has white highlights. Offers superb protection against environmental hazards." - icon_state = "ert_medical" - inhand_icon_state = "ert_medical" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med - - //ERT Janitor -/obj/item/clothing/head/helmet/space/hardsuit/ert/jani - name = "emergency response team janitorial helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has purple highlights." - icon_state = "hardsuit0-ert_janitor" - inhand_icon_state = "hardsuit0-ert_janitor" - hardsuit_type = "ert_janitor" - -/obj/item/clothing/suit/space/hardsuit/ert/jani - name = "emergency response team janitorial hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has purple highlights. Offers superb protection against environmental hazards. This one has extra clips for holding various janitorial tools." - icon_state = "ert_janitor" - inhand_icon_state = "ert_janitor" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/jani - allowed = list(/obj/item/tank/internals, /obj/item/storage/bag/trash, /obj/item/melee/flyswatter, /obj/item/mop, /obj/item/holosign_creator, /obj/item/reagent_containers/glass/bucket, /obj/item/reagent_containers/spray/chemsprayer/janitor) - - //ERT Clown -/obj/item/clothing/head/helmet/space/hardsuit/ert/clown - name = "emergency response team clown helmet" - desc = "The integrated helmet of an ERT hardsuit, this one is colourful!" - icon_state = "hardsuit0-ert_clown" - inhand_icon_state = "hardsuit0-ert_clown" - hardsuit_type = "ert_clown" - -/obj/item/clothing/suit/space/hardsuit/ert/clown - name = "emergency response team clown hardsuit" - desc = "The non-standard issue hardsuit of the ERT, this one is colourful! Offers superb protection against environmental hazards. Does not offer superb protection against a ravaging crew." - icon_state = "ert_clown" - inhand_icon_state = "ert_clown" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/clown - allowed = list(/obj/item/tank/internals, /obj/item/bikehorn, /obj/item/instrument, /obj/item/reagent_containers/food/snacks/grown/banana, /obj/item/grown/bananapeel) - -/obj/item/clothing/suit/space/eva - name = "EVA suit" - icon_state = "space" - inhand_icon_state = "s_suit" - desc = "A lightweight space suit with the basic ability to protect the wearer from the vacuum of space during emergencies." - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) - -/obj/item/clothing/head/helmet/space/eva - name = "EVA helmet" - icon_state = "space" - inhand_icon_state = "space" - desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." - flash_protect = FLASH_PROTECTION_NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) - -/obj/item/clothing/head/helmet/space/freedom - name = "eagle helmet" - desc = "An advanced, space-proof helmet. It appears to be modeled after an old-world eagle." - icon_state = "griffinhat" - inhand_icon_state = "griffinhat" - armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF | FIRE_PROOF - -/obj/item/clothing/suit/space/freedom - name = "eagle suit" - desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit and the wings appear to be stuck in 'freedom' mode." - icon_state = "freedom" - inhand_icon_state = "freedom" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 20, "bullet" = 40, "laser" = 30,"energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF | FIRE_PROOF - slowdown = 0 - -//Carpsuit, bestsuit, lovesuit -/obj/item/clothing/head/helmet/space/hardsuit/carp - name = "carp helmet" - desc = "Spaceworthy and it looks like a space carp's head, smells like one too." - icon_state = "carp_helm" - inhand_icon_state = "syndicate" - armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy as a space carp - brightness_on = 0 //luminosity when on - actions_types = list() - flags_inv = HIDEEARS|HIDEHAIR|HIDEFACIALHAIR //facial hair will clip with the helm, this'll need a dynamic_fhair_suffix at some point. - -/obj/item/clothing/head/helmet/space/hardsuit/carp/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) - -/obj/item/clothing/suit/space/hardsuit/carp - name = "carp space suit" - desc = "A slimming piece of dubious space carp technology, you suspect it won't stand up to hand-to-hand blows." - icon_state = "carp_suit" - inhand_icon_state = "space_suit_syndicate" - slowdown = 0 //Space carp magic, never stop believing - armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy whimpy whoo - allowed = list(/obj/item/tank/internals, /obj/item/pneumatic_cannon/speargun) //I'm giving you a hint here - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp - -/obj/item/clothing/head/helmet/space/hardsuit/carp/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == ITEM_SLOT_HEAD) - user.faction |= "carp" - -/obj/item/clothing/head/helmet/space/hardsuit/carp/dropped(mob/living/carbon/human/user) - ..() - if (user.head == src) - user.faction -= "carp" - -/obj/item/clothing/suit/space/hardsuit/carp/old - name = "battered carp space suit" - desc = "It's covered in bite marks and scratches, yet seems to be still perfectly functional." - slowdown = 1 - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal - name = "paranormal response team helmet" - desc = "A helmet worn by those who deal with paranormal threats for a living." - icon_state = "hardsuit0-prt" - inhand_icon_state = "hardsuit0-prt" - hardsuit_type = "prt" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - actions_types = list() - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_OCLOTHING) - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal - name = "paranormal response team hardsuit" - desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." - icon_state = "knight_grey" - inhand_icon_state = "knight_grey" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, TRUE, TRUE, TRUE, ITEM_SLOT_OCLOTHING) - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor - name = "inquisitor's hardsuit" - icon_state = "hardsuit-inq" - inhand_icon_state = "hardsuit-inq" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor - name = "inquisitor's helmet" - icon_state = "hardsuit0-inq" - inhand_icon_state = "hardsuit0-inq" - hardsuit_type = "inq" - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/berserker - name = "champion's hardsuit" - desc = "Voices echo from the hardsuit, driving the user insane." - icon_state = "hardsuit-berserker" - inhand_icon_state = "hardsuit-berserker" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker - name = "champion's helmet" - desc = "Peering into the eyes of the helmet is enough to seal damnation." - icon_state = "hardsuit0-berserker" - inhand_icon_state = "hardsuit0-berserker" - hardsuit_type = "berserker" - -/obj/item/clothing/head/helmet/space/fragile - name = "emergency space helmet" - desc = "A bulky, air-tight helmet meant to protect the user during emergency situations. It doesn't look very durable." - icon_state = "syndicate-helm-orange" - inhand_icon_state = "syndicate-helm-orange" - armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) - strip_delay = 65 - -/obj/item/clothing/suit/space/fragile - name = "emergency space suit" - desc = "A bulky, air-tight suit meant to protect the user during emergency situations. It doesn't look very durable." - var/torn = FALSE - icon_state = "syndicate-orange" - inhand_icon_state = "syndicate-orange" - slowdown = 2 - armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) - strip_delay = 65 - -/obj/item/clothing/suit/space/fragile/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!torn && prob(50)) - to_chat(owner, "[src] tears from the damage, breaking the air-tight seal!") - clothing_flags &= ~STOPSPRESSUREDAMAGE - name = "torn [src]." - desc = "A bulky suit meant to protect the user during emergency situations, at least until someone tore a hole in the suit." - torn = TRUE - playsound(loc, 'sound/weapons/slashmiss.ogg', 50, TRUE) - playsound(loc, 'sound/effects/refill.ogg', 50, TRUE) - -/obj/item/clothing/suit/space/hunter - name = "bounty hunting suit" - desc = "A custom version of the MK.II SWAT suit, modified to look rugged and tough. Works as a space suit, if you can find a helmet." - icon_state = "hunter" - inhand_icon_state = "swat_suit" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) - armor = list("melee" = 60, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - resistance_flags = FIRE_PROOF | ACID_PROOF - cell = /obj/item/stock_parts/cell/hyper - -//We can either be alive monsters or dead monsters, you choose. -/obj/item/clothing/head/helmet/space/hardsuit/combatmedic - name = "endemic combat medic helmet" - desc = "The integrated helmet of the combat medic hardsuit, this has a bright, glowing facemask." - icon_state = "hardsuit0-combatmedic" - inhand_icon_state = "hardsuit0-combatmedic" - armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) - hardsuit_type = "combatmedic" - -/obj/item/clothing/suit/space/hardsuit/combatmedic - name = "endemic combat medic hardsuit" - desc = "The standard issue hardsuit of infectious disease officers, before the formation of ERT teams. This model is labeled 'Veradux'." - icon_state = "combatmedic" - inhand_icon_state = "combatmedic" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/combatmedic - armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) - allowed = list(/obj/item/gun, /obj/item/melee/baton, /obj/item/circular_saw, /obj/item/tank/internals, /obj/item/storage/box/pillbottles,\ - /obj/item/storage/firstaid, /obj/item/stack/medical/gauze, /obj/item/stack/medical/suture, /obj/item/stack/medical/mesh, /obj/item/storage/bag/chemistry) +//miscellaneous spacesuits +/* +Contains: + - Captain's spacesuit + - Death squad's hardsuit + - SWAT suit + - Officer's beret/spacesuit + - NASA Voidsuit + - Father Christmas' magical clothes + - Pirate's spacesuit + - ERT hardsuit: command, sec, engi, med, janitor + - EVA spacesuit + - Freedom's spacesuit (freedom from vacuum's oppression) + - Carp hardsuit + - Bounty hunter hardsuit + - Blackmarket combat medic hardsuit +*/ + + //Death squad armored space suits, not hardsuits! +/obj/item/clothing/head/helmet/space/hardsuit/deathsquad + name = "MK.III SWAT Helmet" + desc = "An advanced tactical space helmet." + icon_state = "deathsquad" + inhand_icon_state = "deathsquad" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + actions_types = list() + +/obj/item/clothing/head/helmet/space/hardsuit/deathsquad/attack_self(mob/user) + return + +/obj/item/clothing/suit/space/hardsuit/deathsquad + name = "MK.III SWAT Suit" + desc = "A prototype designed to replace the ageing MK.II SWAT suit. Based on the streamlined MK.II model, the traditional ceramic and graphene plate construction was replaced with plasteel, allowing superior armor against most threats. There's room for some kind of energy projection device on the back." + icon_state = "deathsquad" + inhand_icon_state = "swat_suit" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/deathsquad + dog_fashion = /datum/dog_fashion/back/deathsquad + cell = /obj/item/stock_parts/cell/bluespace + + //NEW SWAT suit +/obj/item/clothing/suit/space/swat + name = "MK.I SWAT Suit" + desc = "A tactical space suit first developed in a joint effort by the defunct IS-ERI and Nanotrasen in 20XX for military space operations. A tried and true workhorse, it is very difficult to move in but offers robust protection against all threats!" + icon_state = "heavy" + inhand_icon_state = "swat_suit" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) + strip_delay = 120 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/helmet/space/beret + name = "officer's beret" + desc = "An armored beret commonly used by special operations officers. Uses advanced force field technology to protect the head from space." + icon_state = "beret_badge" + dynamic_hair_suffix = "+generic" + dynamic_fhair_suffix = "+generic" + flags_inv = 0 + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/suit/space/officer + name = "officer's jacket" + desc = "An armored, space-proof jacket used in special operations." + icon_state = "detective" + inhand_icon_state = "det_suit" + blood_overlay_type = "coat" + slowdown = 0 + flags_inv = 0 + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + + //NASA Voidsuit +/obj/item/clothing/head/helmet/space/nasavoid + name = "NASA Void Helmet" + desc = "An old, NASA CentCom branch designed, dark red space suit helmet." + icon_state = "void" + inhand_icon_state = "void" + +/obj/item/clothing/suit/space/nasavoid + name = "NASA Voidsuit" + icon_state = "void" + inhand_icon_state = "void" + desc = "An old, NASA CentCom branch designed, dark red space suit." + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) + +/obj/item/clothing/head/helmet/space/nasavoid/old + name = "Engineering Void Helmet" + desc = "A CentCom engineering dark red space suit helmet. While old and dusty, it still gets the job done." + icon_state = "void" + inhand_icon_state = "void" + +/obj/item/clothing/suit/space/nasavoid/old + name = "Engineering Voidsuit" + icon_state = "void" + inhand_icon_state = "void" + desc = "A CentCom engineering dark red space suit. Age has degraded the suit making is difficult to move around in." + slowdown = 4 + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) + + //Space santa outfit suit +/obj/item/clothing/head/helmet/space/santahat + name = "Santa's hat" + desc = "Ho ho ho. Merrry X-mas!" + icon_state = "santahat" + flags_cover = HEADCOVERSEYES + + dog_fashion = /datum/dog_fashion/head/santa + +/obj/item/clothing/suit/space/santa + name = "Santa's suit" + desc = "Festive!" + icon_state = "santa" + inhand_icon_state = "santa" + slowdown = 0 + allowed = list(/obj/item) //for stuffing exta special presents + + + //Space pirate outfit +/obj/item/clothing/head/helmet/space/pirate + name = "pirate hat" + desc = "Yarr." + icon_state = "pirate" + inhand_icon_state = "pirate" + armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + flags_inv = HIDEHAIR + strip_delay = 40 + equip_delay_other = 20 + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/helmet/space/pirate/bandana + name = "pirate bandana" + icon_state = "bandana" + inhand_icon_state = "bandana" + +/obj/item/clothing/suit/space/pirate + name = "pirate coat" + desc = "Yarr." + icon_state = "pirate" + inhand_icon_state = "pirate" + w_class = WEIGHT_CLASS_NORMAL + flags_inv = 0 + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) + slowdown = 0 + armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + strip_delay = 40 + equip_delay_other = 20 + + //Emergency Response Team suits +/obj/item/clothing/head/helmet/space/hardsuit/ert + name = "emergency response team commander helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has blue highlights." + icon_state = "hardsuit0-ert_commander" + inhand_icon_state = "hardsuit0-ert_commander" + hardsuit_type = "ert_commander" + armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + strip_delay = 130 + brightness_on = 7 + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + +/obj/item/clothing/head/helmet/space/hardsuit/ert/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) + +/obj/item/clothing/suit/space/hardsuit/ert + name = "emergency response team commander hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has blue highlights. Offers superb protection against environmental hazards." + icon_state = "ert_command" + inhand_icon_state = "ert_command" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + slowdown = 0 + strip_delay = 130 + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + cell = /obj/item/stock_parts/cell/bluespace + +// ERT suit's gets EMP Protection +/obj/item/clothing/suit/space/hardsuit/ert/Initialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_CONTENTS) + + //ERT Security +/obj/item/clothing/head/helmet/space/hardsuit/ert/sec + name = "emergency response team security helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has red highlights." + icon_state = "hardsuit0-ert_security" + inhand_icon_state = "hardsuit0-ert_security" + hardsuit_type = "ert_security" + +/obj/item/clothing/suit/space/hardsuit/ert/sec + name = "emergency response team security hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has red highlights. Offers superb protection against environmental hazards." + icon_state = "ert_security" + inhand_icon_state = "ert_security" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/sec + + //ERT Engineering +/obj/item/clothing/head/helmet/space/hardsuit/ert/engi + name = "emergency response team engineering helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has orange highlights." + icon_state = "hardsuit0-ert_engineer" + inhand_icon_state = "hardsuit0-ert_engineer" + hardsuit_type = "ert_engineer" + +/obj/item/clothing/suit/space/hardsuit/ert/engi + name = "emergency response team engineering hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has orange highlights. Offers superb protection against environmental hazards." + icon_state = "ert_engineer" + inhand_icon_state = "ert_engineer" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/engi + + //ERT Medical +/obj/item/clothing/head/helmet/space/hardsuit/ert/med + name = "emergency response team medical helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has white highlights." + icon_state = "hardsuit0-ert_medical" + inhand_icon_state = "hardsuit0-ert_medical" + hardsuit_type = "ert_medical" + +/obj/item/clothing/suit/space/hardsuit/ert/med + name = "emergency response team medical hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has white highlights. Offers superb protection against environmental hazards." + icon_state = "ert_medical" + inhand_icon_state = "ert_medical" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med + + //ERT Janitor +/obj/item/clothing/head/helmet/space/hardsuit/ert/jani + name = "emergency response team janitorial helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has purple highlights." + icon_state = "hardsuit0-ert_janitor" + inhand_icon_state = "hardsuit0-ert_janitor" + hardsuit_type = "ert_janitor" + +/obj/item/clothing/suit/space/hardsuit/ert/jani + name = "emergency response team janitorial hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has purple highlights. Offers superb protection against environmental hazards. This one has extra clips for holding various janitorial tools." + icon_state = "ert_janitor" + inhand_icon_state = "ert_janitor" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/jani + allowed = list(/obj/item/tank/internals, /obj/item/storage/bag/trash, /obj/item/melee/flyswatter, /obj/item/mop, /obj/item/holosign_creator, /obj/item/reagent_containers/glass/bucket, /obj/item/reagent_containers/spray/chemsprayer/janitor) + + //ERT Clown +/obj/item/clothing/head/helmet/space/hardsuit/ert/clown + name = "emergency response team clown helmet" + desc = "The integrated helmet of an ERT hardsuit, this one is colourful!" + icon_state = "hardsuit0-ert_clown" + inhand_icon_state = "hardsuit0-ert_clown" + hardsuit_type = "ert_clown" + +/obj/item/clothing/suit/space/hardsuit/ert/clown + name = "emergency response team clown hardsuit" + desc = "The non-standard issue hardsuit of the ERT, this one is colourful! Offers superb protection against environmental hazards. Does not offer superb protection against a ravaging crew." + icon_state = "ert_clown" + inhand_icon_state = "ert_clown" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/clown + allowed = list(/obj/item/tank/internals, /obj/item/bikehorn, /obj/item/instrument, /obj/item/reagent_containers/food/snacks/grown/banana, /obj/item/grown/bananapeel) + +/obj/item/clothing/suit/space/eva + name = "EVA suit" + icon_state = "space" + inhand_icon_state = "s_suit" + desc = "A lightweight space suit with the basic ability to protect the wearer from the vacuum of space during emergencies." + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) + +/obj/item/clothing/head/helmet/space/eva + name = "EVA helmet" + icon_state = "space" + inhand_icon_state = "space" + desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." + flash_protect = FLASH_PROTECTION_NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) + +/obj/item/clothing/head/helmet/space/freedom + name = "eagle helmet" + desc = "An advanced, space-proof helmet. It appears to be modeled after an old-world eagle." + icon_state = "griffinhat" + inhand_icon_state = "griffinhat" + armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF | FIRE_PROOF + +/obj/item/clothing/suit/space/freedom + name = "eagle suit" + desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit and the wings appear to be stuck in 'freedom' mode." + icon_state = "freedom" + inhand_icon_state = "freedom" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 20, "bullet" = 40, "laser" = 30,"energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF | FIRE_PROOF + slowdown = 0 + +//Carpsuit, bestsuit, lovesuit +/obj/item/clothing/head/helmet/space/hardsuit/carp + name = "carp helmet" + desc = "Spaceworthy and it looks like a space carp's head, smells like one too." + icon_state = "carp_helm" + inhand_icon_state = "syndicate" + armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy as a space carp + brightness_on = 0 //luminosity when on + actions_types = list() + flags_inv = HIDEEARS|HIDEHAIR|HIDEFACIALHAIR //facial hair will clip with the helm, this'll need a dynamic_fhair_suffix at some point. + +/obj/item/clothing/head/helmet/space/hardsuit/carp/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) + +/obj/item/clothing/suit/space/hardsuit/carp + name = "carp space suit" + desc = "A slimming piece of dubious space carp technology, you suspect it won't stand up to hand-to-hand blows." + icon_state = "carp_suit" + inhand_icon_state = "space_suit_syndicate" + slowdown = 0 //Space carp magic, never stop believing + armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy whimpy whoo + allowed = list(/obj/item/tank/internals, /obj/item/pneumatic_cannon/speargun) //I'm giving you a hint here + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp + +/obj/item/clothing/head/helmet/space/hardsuit/carp/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == ITEM_SLOT_HEAD) + user.faction |= "carp" + +/obj/item/clothing/head/helmet/space/hardsuit/carp/dropped(mob/living/carbon/human/user) + ..() + if (user.head == src) + user.faction -= "carp" + +/obj/item/clothing/suit/space/hardsuit/carp/old + name = "battered carp space suit" + desc = "It's covered in bite marks and scratches, yet seems to be still perfectly functional." + slowdown = 1 + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal + name = "paranormal response team helmet" + desc = "A helmet worn by those who deal with paranormal threats for a living." + icon_state = "hardsuit0-prt" + inhand_icon_state = "hardsuit0-prt" + hardsuit_type = "prt" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + actions_types = list() + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_OCLOTHING) + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal + name = "paranormal response team hardsuit" + desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." + icon_state = "knight_grey" + inhand_icon_state = "knight_grey" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, TRUE, TRUE, TRUE, ITEM_SLOT_OCLOTHING) + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor + name = "inquisitor's hardsuit" + icon_state = "hardsuit-inq" + inhand_icon_state = "hardsuit-inq" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor + name = "inquisitor's helmet" + icon_state = "hardsuit0-inq" + inhand_icon_state = "hardsuit0-inq" + hardsuit_type = "inq" + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/berserker + name = "champion's hardsuit" + desc = "Voices echo from the hardsuit, driving the user insane." + icon_state = "hardsuit-berserker" + inhand_icon_state = "hardsuit-berserker" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker + name = "champion's helmet" + desc = "Peering into the eyes of the helmet is enough to seal damnation." + icon_state = "hardsuit0-berserker" + inhand_icon_state = "hardsuit0-berserker" + hardsuit_type = "berserker" + +/obj/item/clothing/head/helmet/space/fragile + name = "emergency space helmet" + desc = "A bulky, air-tight helmet meant to protect the user during emergency situations. It doesn't look very durable." + icon_state = "syndicate-helm-orange" + inhand_icon_state = "syndicate-helm-orange" + armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) + strip_delay = 65 + +/obj/item/clothing/suit/space/fragile + name = "emergency space suit" + desc = "A bulky, air-tight suit meant to protect the user during emergency situations. It doesn't look very durable." + var/torn = FALSE + icon_state = "syndicate-orange" + inhand_icon_state = "syndicate-orange" + slowdown = 2 + armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) + strip_delay = 65 + +/obj/item/clothing/suit/space/fragile/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(!torn && prob(50)) + to_chat(owner, "[src] tears from the damage, breaking the air-tight seal!") + clothing_flags &= ~STOPSPRESSUREDAMAGE + name = "torn [src]." + desc = "A bulky suit meant to protect the user during emergency situations, at least until someone tore a hole in the suit." + torn = TRUE + playsound(loc, 'sound/weapons/slashmiss.ogg', 50, TRUE) + playsound(loc, 'sound/effects/refill.ogg', 50, TRUE) + +/obj/item/clothing/suit/space/hunter + name = "bounty hunting suit" + desc = "A custom version of the MK.II SWAT suit, modified to look rugged and tough. Works as a space suit, if you can find a helmet." + icon_state = "hunter" + inhand_icon_state = "swat_suit" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) + armor = list("melee" = 60, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + resistance_flags = FIRE_PROOF | ACID_PROOF + cell = /obj/item/stock_parts/cell/hyper + +//We can either be alive monsters or dead monsters, you choose. +/obj/item/clothing/head/helmet/space/hardsuit/combatmedic + name = "endemic combat medic helmet" + desc = "The integrated helmet of the combat medic hardsuit, this has a bright, glowing facemask." + icon_state = "hardsuit0-combatmedic" + inhand_icon_state = "hardsuit0-combatmedic" + armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) + hardsuit_type = "combatmedic" + +/obj/item/clothing/suit/space/hardsuit/combatmedic + name = "endemic combat medic hardsuit" + desc = "The standard issue hardsuit of infectious disease officers, before the formation of ERT teams. This model is labeled 'Veradux'." + icon_state = "combatmedic" + inhand_icon_state = "combatmedic" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/combatmedic + armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) + allowed = list(/obj/item/gun, /obj/item/melee/baton, /obj/item/circular_saw, /obj/item/tank/internals, /obj/item/storage/box/pillbottles,\ + /obj/item/storage/firstaid, /obj/item/stack/medical/gauze, /obj/item/stack/medical/suture, /obj/item/stack/medical/mesh, /obj/item/storage/bag/chemistry) diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm index a72a4967ed8..72b4bd9d65b 100644 --- a/code/modules/clothing/spacesuits/syndi.dm +++ b/code/modules/clothing/spacesuits/syndi.dm @@ -1,162 +1,162 @@ -//Regular syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate - name = "red space helmet" - icon_state = "syndicate" - inhand_icon_state = "syndicate" - desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - -/obj/item/clothing/suit/space/syndicate - name = "red space suit" - icon_state = "syndicate" - inhand_icon_state = "space_suit_syndicate" - desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword/saber, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - cell = /obj/item/stock_parts/cell/hyper - -//Green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/green - name = "green space helmet" - icon_state = "syndicate-helm-green" - inhand_icon_state = "syndicate-helm-green" - -/obj/item/clothing/suit/space/syndicate/green - name = "green space suit" - icon_state = "syndicate-green" - inhand_icon_state = "syndicate-green" - - -//Dark green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/green/dark - name = "dark green space helmet" - icon_state = "syndicate-helm-green-dark" - inhand_icon_state = "syndicate-helm-green-dark" - -/obj/item/clothing/suit/space/syndicate/green/dark - name = "dark green space suit" - icon_state = "syndicate-green-dark" - inhand_icon_state = "syndicate-green-dark" - - -//Orange syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/orange - name = "orange space helmet" - icon_state = "syndicate-helm-orange" - inhand_icon_state = "syndicate-helm-orange" - -/obj/item/clothing/suit/space/syndicate/orange - name = "orange space suit" - icon_state = "syndicate-orange" - inhand_icon_state = "syndicate-orange" - -//Blue syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/blue - name = "blue space helmet" - icon_state = "syndicate-helm-blue" - inhand_icon_state = "syndicate-helm-blue" - -/obj/item/clothing/suit/space/syndicate/blue - name = "blue space suit" - icon_state = "syndicate-blue" - inhand_icon_state = "syndicate-blue" - - -//Black syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black - name = "black space helmet" - icon_state = "syndicate-helm-black" - inhand_icon_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black - name = "black space suit" - icon_state = "syndicate-black" - inhand_icon_state = "syndicate-black" - - -//Black-green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/green - name = "black space helmet" - icon_state = "syndicate-helm-black-green" - inhand_icon_state = "syndicate-helm-black-green" - -/obj/item/clothing/suit/space/syndicate/black/green - name = "black and green space suit" - icon_state = "syndicate-black-green" - inhand_icon_state = "syndicate-black-green" - - -//Black-blue syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/blue - name = "black space helmet" - icon_state = "syndicate-helm-black-blue" - inhand_icon_state = "syndicate-helm-black-blue" - -/obj/item/clothing/suit/space/syndicate/black/blue - name = "black and blue space suit" - icon_state = "syndicate-black-blue" - inhand_icon_state = "syndicate-black-blue" - - -//Black medical syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/med - name = "black space helmet" - icon_state = "syndicate-helm-black-med" - inhand_icon_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/med - name = "green space suit" - icon_state = "syndicate-black-med" - inhand_icon_state = "syndicate-black" - - -//Black-orange syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/orange - name = "black space helmet" - icon_state = "syndicate-helm-black-orange" - inhand_icon_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/orange - name = "black and orange space suit" - icon_state = "syndicate-black-orange" - inhand_icon_state = "syndicate-black" - - -//Black-red syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/red - name = "black space helmet" - icon_state = "syndicate-helm-black-red" - inhand_icon_state = "syndicate-helm-black-red" - -/obj/item/clothing/suit/space/syndicate/black/red - name = "black and red space suit" - icon_state = "syndicate-black-red" - inhand_icon_state = "syndicate-black-red" - -//Black-red syndicate contract varient -/obj/item/clothing/head/helmet/space/syndicate/contract - name = "contractor helmet" - desc = "A specialised black and gold helmet that's more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." - w_class = WEIGHT_CLASS_SMALL - icon_state = "syndicate-contract-helm" - inhand_icon_state = "syndicate-contract-helm" - -/obj/item/clothing/suit/space/syndicate/contract - name = "contractor space suit" - desc = "A specialised black and gold space suit that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." - slowdown = 1 - w_class = WEIGHT_CLASS_SMALL - icon_state = "syndicate-contract" - inhand_icon_state = "syndicate-contract" - -//Black with yellow/red engineering syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/engie - name = "black space helmet" - icon_state = "syndicate-helm-black-engie" - inhand_icon_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/engie - name = "black engineering space suit" - icon_state = "syndicate-black-engie" - inhand_icon_state = "syndicate-black" +//Regular syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate + name = "red space helmet" + icon_state = "syndicate" + inhand_icon_state = "syndicate" + desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + +/obj/item/clothing/suit/space/syndicate + name = "red space suit" + icon_state = "syndicate" + inhand_icon_state = "space_suit_syndicate" + desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword/saber, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + cell = /obj/item/stock_parts/cell/hyper + +//Green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/green + name = "green space helmet" + icon_state = "syndicate-helm-green" + inhand_icon_state = "syndicate-helm-green" + +/obj/item/clothing/suit/space/syndicate/green + name = "green space suit" + icon_state = "syndicate-green" + inhand_icon_state = "syndicate-green" + + +//Dark green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/green/dark + name = "dark green space helmet" + icon_state = "syndicate-helm-green-dark" + inhand_icon_state = "syndicate-helm-green-dark" + +/obj/item/clothing/suit/space/syndicate/green/dark + name = "dark green space suit" + icon_state = "syndicate-green-dark" + inhand_icon_state = "syndicate-green-dark" + + +//Orange syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/orange + name = "orange space helmet" + icon_state = "syndicate-helm-orange" + inhand_icon_state = "syndicate-helm-orange" + +/obj/item/clothing/suit/space/syndicate/orange + name = "orange space suit" + icon_state = "syndicate-orange" + inhand_icon_state = "syndicate-orange" + +//Blue syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/blue + name = "blue space helmet" + icon_state = "syndicate-helm-blue" + inhand_icon_state = "syndicate-helm-blue" + +/obj/item/clothing/suit/space/syndicate/blue + name = "blue space suit" + icon_state = "syndicate-blue" + inhand_icon_state = "syndicate-blue" + + +//Black syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black + name = "black space helmet" + icon_state = "syndicate-helm-black" + inhand_icon_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black + name = "black space suit" + icon_state = "syndicate-black" + inhand_icon_state = "syndicate-black" + + +//Black-green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/green + name = "black space helmet" + icon_state = "syndicate-helm-black-green" + inhand_icon_state = "syndicate-helm-black-green" + +/obj/item/clothing/suit/space/syndicate/black/green + name = "black and green space suit" + icon_state = "syndicate-black-green" + inhand_icon_state = "syndicate-black-green" + + +//Black-blue syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/blue + name = "black space helmet" + icon_state = "syndicate-helm-black-blue" + inhand_icon_state = "syndicate-helm-black-blue" + +/obj/item/clothing/suit/space/syndicate/black/blue + name = "black and blue space suit" + icon_state = "syndicate-black-blue" + inhand_icon_state = "syndicate-black-blue" + + +//Black medical syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/med + name = "black space helmet" + icon_state = "syndicate-helm-black-med" + inhand_icon_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/med + name = "green space suit" + icon_state = "syndicate-black-med" + inhand_icon_state = "syndicate-black" + + +//Black-orange syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/orange + name = "black space helmet" + icon_state = "syndicate-helm-black-orange" + inhand_icon_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/orange + name = "black and orange space suit" + icon_state = "syndicate-black-orange" + inhand_icon_state = "syndicate-black" + + +//Black-red syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/red + name = "black space helmet" + icon_state = "syndicate-helm-black-red" + inhand_icon_state = "syndicate-helm-black-red" + +/obj/item/clothing/suit/space/syndicate/black/red + name = "black and red space suit" + icon_state = "syndicate-black-red" + inhand_icon_state = "syndicate-black-red" + +//Black-red syndicate contract varient +/obj/item/clothing/head/helmet/space/syndicate/contract + name = "contractor helmet" + desc = "A specialised black and gold helmet that's more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." + w_class = WEIGHT_CLASS_SMALL + icon_state = "syndicate-contract-helm" + inhand_icon_state = "syndicate-contract-helm" + +/obj/item/clothing/suit/space/syndicate/contract + name = "contractor space suit" + desc = "A specialised black and gold space suit that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." + slowdown = 1 + w_class = WEIGHT_CLASS_SMALL + icon_state = "syndicate-contract" + inhand_icon_state = "syndicate-contract" + +//Black with yellow/red engineering syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/engie + name = "black space helmet" + icon_state = "syndicate-helm-black-engie" + inhand_icon_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/engie + name = "black engineering space suit" + icon_state = "syndicate-black-engie" + inhand_icon_state = "syndicate-black" diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm index f2e8cfa5d36..54bd223b809 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -1,35 +1,35 @@ -/obj/item/clothing/suit - icon = 'icons/obj/clothing/suits.dmi' - name = "suit" - var/fire_resist = T0C+100 - allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - drop_sound = 'sound/items/handling/cloth_drop.ogg' - pickup_sound = 'sound/items/handling/cloth_pickup.ogg' - slot_flags = ITEM_SLOT_OCLOTHING - var/blood_overlay_type = "suit" - var/togglename = null - var/suittoggled = FALSE - limb_integrity = 0 // disabled for most exo-suits - - -/obj/item/clothing/suit/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") - var/mob/living/carbon/human/M = loc - if(ishuman(M) && M.w_uniform) - var/obj/item/clothing/under/U = M.w_uniform - if(istype(U) && U.attached_accessory) - var/obj/item/clothing/accessory/A = U.attached_accessory - if(A.above_suit) - . += U.accessory_overlay - -/obj/item/clothing/suit/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_wear_suit() +/obj/item/clothing/suit + icon = 'icons/obj/clothing/suits.dmi' + name = "suit" + var/fire_resist = T0C+100 + allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + drop_sound = 'sound/items/handling/cloth_drop.ogg' + pickup_sound = 'sound/items/handling/cloth_pickup.ogg' + slot_flags = ITEM_SLOT_OCLOTHING + var/blood_overlay_type = "suit" + var/togglename = null + var/suittoggled = FALSE + limb_integrity = 0 // disabled for most exo-suits + + +/obj/item/clothing/suit/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") + var/mob/living/carbon/human/M = loc + if(ishuman(M) && M.w_uniform) + var/obj/item/clothing/under/U = M.w_uniform + if(istype(U) && U.attached_accessory) + var/obj/item/clothing/accessory/A = U.attached_accessory + if(A.above_suit) + . += U.accessory_overlay + +/obj/item/clothing/suit/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_wear_suit() diff --git a/code/modules/clothing/suits/bio.dm b/code/modules/clothing/suits/bio.dm index 2ba4d8250f7..e24c1c7fed4 100644 --- a/code/modules/clothing/suits/bio.dm +++ b/code/modules/clothing/suits/bio.dm @@ -1,100 +1,100 @@ -//Biosuit complete with shoes (in the item sprite) -/obj/item/clothing/head/bio_hood - name = "bio hood" - icon_state = "bio" - desc = "A hood that protects the head and face from biological contaminants." - permeability_coefficient = 0.01 - clothing_flags = THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT | SNUG_FIT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE - resistance_flags = ACID_PROOF - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - -/obj/item/clothing/suit/bio_suit - name = "bio suit" - desc = "A suit that protects against biological contamination." - icon_state = "bio" - inhand_icon_state = "bio_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - clothing_flags = THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - slowdown = 0.5 - allowed = list(/obj/item/tank/internals, /obj/item/reagent_containers/dropper, /obj/item/flashlight/pen, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/glass/beaker) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = ACID_PROOF - -//Standard biosuit, orange stripe -/obj/item/clothing/head/bio_hood/general - icon_state = "bio_general" - -/obj/item/clothing/suit/bio_suit/general - icon_state = "bio_general" - - -//Virology biosuit, green stripe -/obj/item/clothing/head/bio_hood/virology - icon_state = "bio_virology" - -/obj/item/clothing/suit/bio_suit/virology - icon_state = "bio_virology" - - -//Security biosuit, grey with red stripe across the chest -/obj/item/clothing/head/bio_hood/security - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - icon_state = "bio_security" - -/obj/item/clothing/suit/bio_suit/security - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - icon_state = "bio_security" - -/obj/item/clothing/suit/bio_suit/security/Initialize() - . = ..() - allowed += GLOB.security_vest_allowed - -//Janitor's biosuit, grey with purple arms -/obj/item/clothing/head/bio_hood/janitor - icon_state = "bio_janitor" - -/obj/item/clothing/suit/bio_suit/janitor - icon_state = "bio_janitor" - -/obj/item/clothing/suit/bio_suit/janitor/Initialize() - . = ..() - allowed += list(/obj/item/storage/bag/trash, /obj/item/reagent_containers/spray) - -//Scientist's biosuit, white with a pink-ish hue -/obj/item/clothing/head/bio_hood/scientist - icon_state = "bio_scientist" - -/obj/item/clothing/suit/bio_suit/scientist - icon_state = "bio_scientist" - -//CMO's biosuit, blue stripe -/obj/item/clothing/head/bio_hood/cmo - icon_state = "bio_cmo" - -/obj/item/clothing/suit/bio_suit/cmo - icon_state = "bio_cmo" - -/obj/item/clothing/suit/bio_suit/cmo/Initialize() - . = ..() - allowed += list(/obj/item/melee/classic_baton/telescopic) - -//Plague Dr mask can be found in clothing/masks/gasmask.dm -/obj/item/clothing/suit/bio_suit/plaguedoctorsuit - name = "plague doctor suit" - desc = "It protected doctors from the Black Death, back then. You bet your arse it's gonna help you against viruses." - icon_state = "plaguedoctor" - inhand_icon_state = "bio_suit" - strip_delay = 40 - equip_delay_other = 20 - -/obj/item/clothing/suit/bio_suit/plaguedoctorsuit/Initialize() - . = ..() - allowed += list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/cane) +//Biosuit complete with shoes (in the item sprite) +/obj/item/clothing/head/bio_hood + name = "bio hood" + icon_state = "bio" + desc = "A hood that protects the head and face from biological contaminants." + permeability_coefficient = 0.01 + clothing_flags = THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT | SNUG_FIT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE + resistance_flags = ACID_PROOF + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + +/obj/item/clothing/suit/bio_suit + name = "bio suit" + desc = "A suit that protects against biological contamination." + icon_state = "bio" + inhand_icon_state = "bio_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + clothing_flags = THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + slowdown = 0.5 + allowed = list(/obj/item/tank/internals, /obj/item/reagent_containers/dropper, /obj/item/flashlight/pen, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/glass/beaker) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = ACID_PROOF + +//Standard biosuit, orange stripe +/obj/item/clothing/head/bio_hood/general + icon_state = "bio_general" + +/obj/item/clothing/suit/bio_suit/general + icon_state = "bio_general" + + +//Virology biosuit, green stripe +/obj/item/clothing/head/bio_hood/virology + icon_state = "bio_virology" + +/obj/item/clothing/suit/bio_suit/virology + icon_state = "bio_virology" + + +//Security biosuit, grey with red stripe across the chest +/obj/item/clothing/head/bio_hood/security + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + icon_state = "bio_security" + +/obj/item/clothing/suit/bio_suit/security + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + icon_state = "bio_security" + +/obj/item/clothing/suit/bio_suit/security/Initialize() + . = ..() + allowed += GLOB.security_vest_allowed + +//Janitor's biosuit, grey with purple arms +/obj/item/clothing/head/bio_hood/janitor + icon_state = "bio_janitor" + +/obj/item/clothing/suit/bio_suit/janitor + icon_state = "bio_janitor" + +/obj/item/clothing/suit/bio_suit/janitor/Initialize() + . = ..() + allowed += list(/obj/item/storage/bag/trash, /obj/item/reagent_containers/spray) + +//Scientist's biosuit, white with a pink-ish hue +/obj/item/clothing/head/bio_hood/scientist + icon_state = "bio_scientist" + +/obj/item/clothing/suit/bio_suit/scientist + icon_state = "bio_scientist" + +//CMO's biosuit, blue stripe +/obj/item/clothing/head/bio_hood/cmo + icon_state = "bio_cmo" + +/obj/item/clothing/suit/bio_suit/cmo + icon_state = "bio_cmo" + +/obj/item/clothing/suit/bio_suit/cmo/Initialize() + . = ..() + allowed += list(/obj/item/melee/classic_baton/telescopic) + +//Plague Dr mask can be found in clothing/masks/gasmask.dm +/obj/item/clothing/suit/bio_suit/plaguedoctorsuit + name = "plague doctor suit" + desc = "It protected doctors from the Black Death, back then. You bet your arse it's gonna help you against viruses." + icon_state = "plaguedoctor" + inhand_icon_state = "bio_suit" + strip_delay = 40 + equip_delay_other = 20 + +/obj/item/clothing/suit/bio_suit/plaguedoctorsuit/Initialize() + . = ..() + allowed += list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/cane) diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm index b3f12194efa..8c15d650f5b 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -1,185 +1,185 @@ -/* - * Job related - */ - -//Botanist -/obj/item/clothing/suit/apron - name = "apron" - desc = "A basic blue apron." - icon_state = "apron" - inhand_icon_state = "apron" - blood_overlay_type = "armor" - body_parts_covered = CHEST|GROIN - allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/graft, /obj/item/secateurs, /obj/item/geneshears) - -/obj/item/clothing/suit/apron/waders - name = "horticultural waders" - desc = "A pair of heavy duty leather waders, perfect for insulating your soft flesh from spills, soil and thorns." - icon_state = "hort_waders" - inhand_icon_state = "hort_waders" - body_parts_covered = CHEST|GROIN|LEGS - permeability_coefficient = 0.5 - -//Captain -/obj/item/clothing/suit/captunic - name = "captain's parade tunic" - desc = "Worn by a Captain to show their class." - icon_state = "captunic" - inhand_icon_state = "bio_suit" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEJUMPSUIT - allowed = list(/obj/item/disk, /obj/item/stamp, /obj/item/reagent_containers/food/drinks/flask, /obj/item/melee, /obj/item/storage/lockbox/medal, /obj/item/assembly/flash/handheld, /obj/item/storage/box/matches, /obj/item/lighter, /obj/item/clothing/mask/cigarette, /obj/item/storage/fancy/cigarettes, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - -//Chef -/obj/item/clothing/suit/toggle/chef - name = "chef's apron" - desc = "An apron-jacket used by a high class chef." - icon_state = "chef" - inhand_icon_state = "chef" - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.5 - body_parts_covered = CHEST|GROIN|ARMS - allowed = list(/obj/item/kitchen) - togglename = "sleeves" - -//Cook -/obj/item/clothing/suit/apron/chef - name = "cook's apron" - desc = "A basic, dull, white chef's apron." - icon_state = "apronchef" - inhand_icon_state = "apronchef" - blood_overlay_type = "armor" - body_parts_covered = CHEST|GROIN - allowed = list(/obj/item/kitchen) - -//Detective -/obj/item/clothing/suit/det_suit - name = "trenchcoat" - desc = "A 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." - icon_state = "detective" - inhand_icon_state = "det_suit" - blood_overlay_type = "coat" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - cold_protection = CHEST|GROIN|LEGS|ARMS - heat_protection = CHEST|GROIN|LEGS|ARMS - -/obj/item/clothing/suit/det_suit/Initialize() - . = ..() - allowed = GLOB.detective_vest_allowed - -/obj/item/clothing/suit/det_suit/grey - name = "noir trenchcoat" - desc = "A hard-boiled private investigator's grey trenchcoat." - icon_state = "greydet" - inhand_icon_state = "greydet" - -/obj/item/clothing/suit/det_suit/noir - name = "noir suit coat" - desc = "A dapper private investigator's grey suit coat." - icon_state = "detsuit" - inhand_icon_state = "detsuit" - -//Engineering -/obj/item/clothing/suit/hazardvest - name = "hazard vest" - desc = "A high-visibility vest used in work zones." - icon_state = "hazard" - inhand_icon_state = "hazard" - blood_overlay_type = "armor" - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/radio) - resistance_flags = NONE - -//Lawyer -/obj/item/clothing/suit/toggle/lawyer - name = "blue suit jacket" - desc = "A snappy dress jacket." - icon_state = "suitjacket_blue" - inhand_icon_state = "suitjacket_blue" - blood_overlay_type = "coat" - body_parts_covered = CHEST|ARMS - togglename = "buttons" - -/obj/item/clothing/suit/toggle/lawyer/purple - name = "purple suit jacket" - desc = "A foppish dress jacket." - icon_state = "suitjacket_purp" - inhand_icon_state = "suitjacket_purp" - -/obj/item/clothing/suit/toggle/lawyer/black - name = "black suit jacket" - desc = "A professional suit jacket." - icon_state = "suitjacket_black" - inhand_icon_state = "ro_suit" - - -//Mime -/obj/item/clothing/suit/toggle/suspenders - name = "suspenders" - desc = "They suspend the illusion of the mime's play." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders" - worn_icon_state = "suspenders" - blood_overlay_type = "armor" //it's the less thing that I can put here - togglename = "straps" - -//Security -/obj/item/clothing/suit/security/officer - name = "security officer's jacket" - desc = "This jacket is for those special occasions when a security officer isn't required to wear their armor." - icon_state = "officerbluejacket" - inhand_icon_state = "officerbluejacket" - body_parts_covered = CHEST|ARMS - -/obj/item/clothing/suit/security/warden - name = "warden's jacket" - desc = "Perfectly suited for the warden that wants to leave an impression of style on those who visit the brig." - icon_state = "wardenbluejacket" - inhand_icon_state = "wardenbluejacket" - body_parts_covered = CHEST|ARMS - -/obj/item/clothing/suit/security/hos - name = "head of security's jacket" - desc = "This piece of clothing was specifically designed for asserting superior authority." - icon_state = "hosbluejacket" - inhand_icon_state = "hosbluejacket" - body_parts_covered = CHEST|ARMS - -//Surgeon -/obj/item/clothing/suit/apron/surgical - name = "surgical apron" - desc = "A sterile blue surgical apron." - icon_state = "surgical" - allowed = list(/obj/item/scalpel, /obj/item/surgical_drapes, /obj/item/cautery, /obj/item/hemostat, /obj/item/retractor) - -//Curator -/obj/item/clothing/suit/curator - name = "treasure hunter's coat" - desc = "Both fashionable and lightly armoured, this jacket is favoured by treasure hunters the galaxy over." - icon_state = "curator" - inhand_icon_state = "curator" - blood_overlay_type = "coat" - body_parts_covered = CHEST|ARMS - allowed = list(/obj/item/tank/internals, /obj/item/melee/curator_whip) - armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - cold_protection = CHEST|ARMS - heat_protection = CHEST|ARMS - - -//Robotocist - -/obj/item/clothing/suit/hooded/techpriest - name = "techpriest robes" - desc = "For those who REALLY love their toasters." - icon_state = "techpriest" - inhand_icon_state = "techpriest" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - hoodtype = /obj/item/clothing/head/hooded/techpriest - -/obj/item/clothing/head/hooded/techpriest - name = "techpriest's hood" - desc = "A hood for those who REALLY love their toasters." - icon_state = "techpriesthood" - inhand_icon_state = "techpriesthood" - body_parts_covered = HEAD - flags_inv = HIDEHAIR|HIDEEARS +/* + * Job related + */ + +//Botanist +/obj/item/clothing/suit/apron + name = "apron" + desc = "A basic blue apron." + icon_state = "apron" + inhand_icon_state = "apron" + blood_overlay_type = "armor" + body_parts_covered = CHEST|GROIN + allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/graft, /obj/item/secateurs, /obj/item/geneshears) + +/obj/item/clothing/suit/apron/waders + name = "horticultural waders" + desc = "A pair of heavy duty leather waders, perfect for insulating your soft flesh from spills, soil and thorns." + icon_state = "hort_waders" + inhand_icon_state = "hort_waders" + body_parts_covered = CHEST|GROIN|LEGS + permeability_coefficient = 0.5 + +//Captain +/obj/item/clothing/suit/captunic + name = "captain's parade tunic" + desc = "Worn by a Captain to show their class." + icon_state = "captunic" + inhand_icon_state = "bio_suit" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDEJUMPSUIT + allowed = list(/obj/item/disk, /obj/item/stamp, /obj/item/reagent_containers/food/drinks/flask, /obj/item/melee, /obj/item/storage/lockbox/medal, /obj/item/assembly/flash/handheld, /obj/item/storage/box/matches, /obj/item/lighter, /obj/item/clothing/mask/cigarette, /obj/item/storage/fancy/cigarettes, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + +//Chef +/obj/item/clothing/suit/toggle/chef + name = "chef's apron" + desc = "An apron-jacket used by a high class chef." + icon_state = "chef" + inhand_icon_state = "chef" + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.5 + body_parts_covered = CHEST|GROIN|ARMS + allowed = list(/obj/item/kitchen) + togglename = "sleeves" + +//Cook +/obj/item/clothing/suit/apron/chef + name = "cook's apron" + desc = "A basic, dull, white chef's apron." + icon_state = "apronchef" + inhand_icon_state = "apronchef" + blood_overlay_type = "armor" + body_parts_covered = CHEST|GROIN + allowed = list(/obj/item/kitchen) + +//Detective +/obj/item/clothing/suit/det_suit + name = "trenchcoat" + desc = "A 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." + icon_state = "detective" + inhand_icon_state = "det_suit" + blood_overlay_type = "coat" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + cold_protection = CHEST|GROIN|LEGS|ARMS + heat_protection = CHEST|GROIN|LEGS|ARMS + +/obj/item/clothing/suit/det_suit/Initialize() + . = ..() + allowed = GLOB.detective_vest_allowed + +/obj/item/clothing/suit/det_suit/grey + name = "noir trenchcoat" + desc = "A hard-boiled private investigator's grey trenchcoat." + icon_state = "greydet" + inhand_icon_state = "greydet" + +/obj/item/clothing/suit/det_suit/noir + name = "noir suit coat" + desc = "A dapper private investigator's grey suit coat." + icon_state = "detsuit" + inhand_icon_state = "detsuit" + +//Engineering +/obj/item/clothing/suit/hazardvest + name = "hazard vest" + desc = "A high-visibility vest used in work zones." + icon_state = "hazard" + inhand_icon_state = "hazard" + blood_overlay_type = "armor" + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/radio) + resistance_flags = NONE + +//Lawyer +/obj/item/clothing/suit/toggle/lawyer + name = "blue suit jacket" + desc = "A snappy dress jacket." + icon_state = "suitjacket_blue" + inhand_icon_state = "suitjacket_blue" + blood_overlay_type = "coat" + body_parts_covered = CHEST|ARMS + togglename = "buttons" + +/obj/item/clothing/suit/toggle/lawyer/purple + name = "purple suit jacket" + desc = "A foppish dress jacket." + icon_state = "suitjacket_purp" + inhand_icon_state = "suitjacket_purp" + +/obj/item/clothing/suit/toggle/lawyer/black + name = "black suit jacket" + desc = "A professional suit jacket." + icon_state = "suitjacket_black" + inhand_icon_state = "ro_suit" + + +//Mime +/obj/item/clothing/suit/toggle/suspenders + name = "suspenders" + desc = "They suspend the illusion of the mime's play." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders" + worn_icon_state = "suspenders" + blood_overlay_type = "armor" //it's the less thing that I can put here + togglename = "straps" + +//Security +/obj/item/clothing/suit/security/officer + name = "security officer's jacket" + desc = "This jacket is for those special occasions when a security officer isn't required to wear their armor." + icon_state = "officerbluejacket" + inhand_icon_state = "officerbluejacket" + body_parts_covered = CHEST|ARMS + +/obj/item/clothing/suit/security/warden + name = "warden's jacket" + desc = "Perfectly suited for the warden that wants to leave an impression of style on those who visit the brig." + icon_state = "wardenbluejacket" + inhand_icon_state = "wardenbluejacket" + body_parts_covered = CHEST|ARMS + +/obj/item/clothing/suit/security/hos + name = "head of security's jacket" + desc = "This piece of clothing was specifically designed for asserting superior authority." + icon_state = "hosbluejacket" + inhand_icon_state = "hosbluejacket" + body_parts_covered = CHEST|ARMS + +//Surgeon +/obj/item/clothing/suit/apron/surgical + name = "surgical apron" + desc = "A sterile blue surgical apron." + icon_state = "surgical" + allowed = list(/obj/item/scalpel, /obj/item/surgical_drapes, /obj/item/cautery, /obj/item/hemostat, /obj/item/retractor) + +//Curator +/obj/item/clothing/suit/curator + name = "treasure hunter's coat" + desc = "Both fashionable and lightly armoured, this jacket is favoured by treasure hunters the galaxy over." + icon_state = "curator" + inhand_icon_state = "curator" + blood_overlay_type = "coat" + body_parts_covered = CHEST|ARMS + allowed = list(/obj/item/tank/internals, /obj/item/melee/curator_whip) + armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + cold_protection = CHEST|ARMS + heat_protection = CHEST|ARMS + + +//Robotocist + +/obj/item/clothing/suit/hooded/techpriest + name = "techpriest robes" + desc = "For those who REALLY love their toasters." + icon_state = "techpriest" + inhand_icon_state = "techpriest" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + hoodtype = /obj/item/clothing/head/hooded/techpriest + +/obj/item/clothing/head/hooded/techpriest + name = "techpriest's hood" + desc = "A hood for those who REALLY love their toasters." + icon_state = "techpriesthood" + inhand_icon_state = "techpriesthood" + body_parts_covered = HEAD + flags_inv = HIDEHAIR|HIDEEARS diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm index 74a147dc6c8..d4f4d36e046 100644 --- a/code/modules/clothing/suits/labcoat.dm +++ b/code/modules/clothing/suits/labcoat.dm @@ -1,49 +1,49 @@ -/obj/item/clothing/suit/toggle/labcoat - name = "labcoat" - desc = "A suit that protects against minor chemical spills." - icon_state = "labcoat" - inhand_icon_state = "labcoat" - blood_overlay_type = "coat" - body_parts_covered = CHEST|ARMS - allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/soap, /obj/item/sensor_device, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 50, "acid" = 50) - togglename = "buttons" - species_exception = list(/datum/species/golem) - -/obj/item/clothing/suit/toggle/labcoat/cmo - name = "chief medical officer's labcoat" - desc = "Bluer than the standard model." - icon_state = "labcoat_cmo" - inhand_icon_state = "labcoat_cmo" - -/obj/item/clothing/suit/toggle/labcoat/paramedic - name = "paramedic's jacket" - desc = "A dark blue jacket for paramedics with reflective stripes." - icon_state = "labcoat_paramedic" - inhand_icon_state = "labcoat_paramedic" - -/obj/item/clothing/suit/toggle/labcoat/mad - name = "\proper The Mad's labcoat" - desc = "It makes you look capable of konking someone on the noggin and shooting them into space." - icon_state = "labgreen" - inhand_icon_state = "labgreen" - -/obj/item/clothing/suit/toggle/labcoat/genetics - name = "geneticist labcoat" - desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder." - icon_state = "labcoat_gen" - -/obj/item/clothing/suit/toggle/labcoat/chemist - name = "chemist labcoat" - desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder." - icon_state = "labcoat_chem" - -/obj/item/clothing/suit/toggle/labcoat/virologist - name = "virologist labcoat" - desc = "A suit that protects against minor chemical spills. Offers slightly more protection against biohazards than the standard model. Has a green stripe on the shoulder." - icon_state = "labcoat_vir" - -/obj/item/clothing/suit/toggle/labcoat/science - name = "scientist labcoat" - desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." - icon_state = "labcoat_tox" +/obj/item/clothing/suit/toggle/labcoat + name = "labcoat" + desc = "A suit that protects against minor chemical spills." + icon_state = "labcoat" + inhand_icon_state = "labcoat" + blood_overlay_type = "coat" + body_parts_covered = CHEST|ARMS + allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/soap, /obj/item/sensor_device, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 50, "acid" = 50) + togglename = "buttons" + species_exception = list(/datum/species/golem) + +/obj/item/clothing/suit/toggle/labcoat/cmo + name = "chief medical officer's labcoat" + desc = "Bluer than the standard model." + icon_state = "labcoat_cmo" + inhand_icon_state = "labcoat_cmo" + +/obj/item/clothing/suit/toggle/labcoat/paramedic + name = "paramedic's jacket" + desc = "A dark blue jacket for paramedics with reflective stripes." + icon_state = "labcoat_paramedic" + inhand_icon_state = "labcoat_paramedic" + +/obj/item/clothing/suit/toggle/labcoat/mad + name = "\proper The Mad's labcoat" + desc = "It makes you look capable of konking someone on the noggin and shooting them into space." + icon_state = "labgreen" + inhand_icon_state = "labgreen" + +/obj/item/clothing/suit/toggle/labcoat/genetics + name = "geneticist labcoat" + desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder." + icon_state = "labcoat_gen" + +/obj/item/clothing/suit/toggle/labcoat/chemist + name = "chemist labcoat" + desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder." + icon_state = "labcoat_chem" + +/obj/item/clothing/suit/toggle/labcoat/virologist + name = "virologist labcoat" + desc = "A suit that protects against minor chemical spills. Offers slightly more protection against biohazards than the standard model. Has a green stripe on the shoulder." + icon_state = "labcoat_vir" + +/obj/item/clothing/suit/toggle/labcoat/science + name = "scientist labcoat" + desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." + icon_state = "labcoat_tox" diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index 99e25d821c7..0c6dbf2f5c0 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -1,798 +1,798 @@ -/* - * Contains: - * Lasertag - * Costume - * Misc - */ - -/* - * Lasertag - */ -/obj/item/clothing/suit/bluetag - name = "blue laser tag armor" - desc = "A piece of plastic armor. It has sensors that react to red light." //Lasers are concentrated light - icon_state = "bluetag" - inhand_icon_state = "bluetag" - blood_overlay_type = "armor" - body_parts_covered = CHEST - allowed = list (/obj/item/gun/energy/laser/bluetag) - resistance_flags = NONE - -/obj/item/clothing/suit/redtag - name = "red laser tag armor" - desc = "A piece of plastic armor. It has sensors that react to blue light." - icon_state = "redtag" - inhand_icon_state = "redtag" - blood_overlay_type = "armor" - body_parts_covered = CHEST - allowed = list (/obj/item/gun/energy/laser/redtag) - resistance_flags = NONE - -/* - * Costume - */ -/obj/item/clothing/suit/hooded/flashsuit - name = "flashy costume" - desc = "What did you expect?" - icon_state = "flashsuit" - inhand_icon_state = "armor" - body_parts_covered = CHEST|GROIN - hoodtype = /obj/item/clothing/head/hooded/flashsuit - -/obj/item/clothing/head/hooded/flashsuit - name = "flash button" - desc = "You will learn to fear the flash." - icon_state = "flashsuit" - body_parts_covered = HEAD - flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK - -/obj/item/clothing/suit/pirate - name = "pirate coat" - desc = "Yarr." - icon_state = "pirate" - inhand_icon_state = "pirate" - allowed = list(/obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) - -/obj/item/clothing/suit/pirate/captain - name = "pirate captain coat" - desc = "Yarr." - icon_state = "hgpirate" - inhand_icon_state = "hgpirate" - - -/obj/item/clothing/suit/cyborg_suit - name = "cyborg suit" - desc = "Suit for a cyborg costume." - icon_state = "death" - inhand_icon_state = "death" - flags_1 = CONDUCT_1 - fire_resist = T0C+5200 - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - - -/obj/item/clothing/suit/justice - name = "justice suit" - desc = "this pretty much looks ridiculous" //Needs no fixing - icon_state = "justice" - inhand_icon_state = "justice" - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - - -/obj/item/clothing/suit/judgerobe - name = "judge's robe" - desc = "This robe commands authority." - icon_state = "judge" - inhand_icon_state = "judge" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - allowed = list(/obj/item/storage/fancy/cigarettes, /obj/item/stack/spacecash) - flags_inv = HIDEJUMPSUIT - - -/obj/item/clothing/suit/apron/overalls - name = "coveralls" - desc = "A set of denim overalls." - icon_state = "overalls" - inhand_icon_state = "overalls" - body_parts_covered = CHEST|GROIN|LEGS - -/obj/item/clothing/suit/apron/purple_bartender - name = "purple bartender apron" - desc = "A fancy purple apron for a stylish person." - icon_state = "purplebartenderapron" - inhand_icon_state = "purplebartenderapron" - body_parts_covered = CHEST|GROIN|LEGS - -/obj/item/clothing/suit/syndicatefake - name = "black and red space suit replica" - icon_state = "syndicate-black-red" - inhand_icon_state = "syndicate-black-red" - desc = "A plastic replica of the Syndicate space suit. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - resistance_flags = NONE - -/obj/item/clothing/suit/hastur - name = "\improper Hastur's robe" - desc = "Robes not meant to be worn by man." - icon_state = "hastur" - inhand_icon_state = "hastur" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - - -/obj/item/clothing/suit/imperium_monk - name = "\improper Imperium monk suit" - desc = "Have YOU killed a xeno today?" - icon_state = "imperium_monk" - inhand_icon_state = "imperium_monk" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDESHOES|HIDEJUMPSUIT - allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen) - - -/obj/item/clothing/suit/chickensuit - name = "chicken suit" - desc = "A suit made long ago by the ancient empire KFC." - icon_state = "chickensuit" - inhand_icon_state = "chickensuit" - body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET - flags_inv = HIDESHOES|HIDEJUMPSUIT - - -/obj/item/clothing/suit/monkeysuit - name = "monkey suit" - desc = "A suit that looks like a primate." - icon_state = "monkeysuit" - inhand_icon_state = "monkeysuit" - body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - -/obj/item/clothing/suit/toggle/owlwings - name = "owl cloak" - desc = "A soft brown cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive the ladies mad." - icon_state = "owl_wings" - inhand_icon_state = "owl_wings" - togglename = "wings" - body_parts_covered = ARMS|CHEST - actions_types = list(/datum/action/item_action/toggle_wings) - -/obj/item/clothing/suit/toggle/owlwings/Initialize() - . = ..() - allowed = GLOB.security_vest_allowed - -/obj/item/clothing/suit/toggle/owlwings/griffinwings - name = "griffon cloak" - desc = "A plush white cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive your captives mad." - icon_state = "griffin_wings" - inhand_icon_state = "griffin_wings" - -/obj/item/clothing/suit/cardborg - name = "cardborg suit" - desc = "An ordinary cardboard box with holes cut in the sides." - icon_state = "cardborg" - inhand_icon_state = "cardborg" - body_parts_covered = CHEST|GROIN - flags_inv = HIDEJUMPSUIT - dog_fashion = /datum/dog_fashion/back - -/obj/item/clothing/suit/cardborg/equipped(mob/living/user, slot) - ..() - if(slot == ITEM_SLOT_OCLOTHING) - disguise(user) - -/obj/item/clothing/suit/cardborg/dropped(mob/living/user) - ..() - user.remove_alt_appearance("standard_borg_disguise") - -/obj/item/clothing/suit/cardborg/proc/disguise(mob/living/carbon/human/H, obj/item/clothing/head/cardborg/borghead) - if(istype(H)) - if(!borghead) - borghead = H.head - if(istype(borghead, /obj/item/clothing/head/cardborg)) //why is this done this way? because equipped() is called BEFORE THE ITEM IS IN THE SLOT WHYYYY - var/image/I = image(icon = 'icons/mob/robots.dmi' , icon_state = "robot", loc = H) - I.override = 1 - I.add_overlay(mutable_appearance('icons/mob/robots.dmi', "robot_e")) //gotta look realistic - add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "standard_borg_disguise", I) //you look like a robot to robots! (including yourself because you're totally a robot) - - -/obj/item/clothing/suit/snowman - name = "snowman outfit" - desc = "Two white spheres covered in white glitter. 'Tis the season." - icon_state = "snowman" - inhand_icon_state = "snowman" - body_parts_covered = CHEST|GROIN - flags_inv = HIDEJUMPSUIT - -/obj/item/clothing/suit/poncho - name = "poncho" - desc = "Your classic, non-racist poncho." - icon_state = "classicponcho" - inhand_icon_state = "classicponcho" - -/obj/item/clothing/suit/poncho/green - name = "green poncho" - desc = "Your classic, non-racist poncho. This one is green." - icon_state = "greenponcho" - inhand_icon_state = "greenponcho" - -/obj/item/clothing/suit/poncho/red - name = "red poncho" - desc = "Your classic, non-racist poncho. This one is red." - icon_state = "redponcho" - inhand_icon_state = "redponcho" - -/obj/item/clothing/suit/poncho/ponchoshame - name = "poncho of shame" - desc = "Forced to live on your shameful acting as a fake Mexican, you and your poncho have grown inseparable. Literally." - icon_state = "ponchoshame" - inhand_icon_state = "ponchoshame" - -/obj/item/clothing/suit/poncho/ponchoshame/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) - -/obj/item/clothing/suit/whitedress - name = "white dress" - desc = "A fancy white dress." - icon_state = "white_dress" - inhand_icon_state = "w_suit" - body_parts_covered = CHEST|GROIN|LEGS|FEET - flags_inv = HIDEJUMPSUIT|HIDESHOES - -/obj/item/clothing/suit/hooded/carp_costume - name = "carp costume" - desc = "A costume made from 'synthetic' carp scales, it smells." - icon_state = "carp_casual" - inhand_icon_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT //Space carp like space, so you should too - allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pneumatic_cannon/speargun) - hoodtype = /obj/item/clothing/head/hooded/carp_hood - -/obj/item/clothing/head/hooded/carp_hood - name = "carp hood" - desc = "A hood attached to a carp costume." - icon_state = "carp_casual" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - -/obj/item/clothing/head/hooded/carp_hood/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == ITEM_SLOT_HEAD) - user.faction |= "carp" - -/obj/item/clothing/head/hooded/carp_hood/dropped(mob/living/carbon/human/user) - ..() - if (user.head == src) - user.faction -= "carp" - -/obj/item/clothing/suit/hooded/ian_costume //It's Ian, rub his bell- oh god what happened to his inside parts? - name = "corgi costume" - desc = "A costume that looks like someone made a human-like corgi, it won't guarantee belly rubs." - icon_state = "ian" - inhand_icon_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - //cold_protection = CHEST|GROIN|ARMS - //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - allowed = list() - hoodtype = /obj/item/clothing/head/hooded/ian_hood - dog_fashion = /datum/dog_fashion/back - -/obj/item/clothing/head/hooded/ian_hood - name = "corgi hood" - desc = "A hood that looks just like a corgi's head, it won't guarantee dog biscuits." - icon_state = "ian" - body_parts_covered = HEAD - //cold_protection = HEAD - //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - -/obj/item/clothing/suit/hooded/bee_costume // It's Hip! - name = "bee costume" - desc = "Bee the true Queen!" - icon_state = "bee" - inhand_icon_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - clothing_flags = THICKMATERIAL - hoodtype = /obj/item/clothing/head/hooded/bee_hood - -/obj/item/clothing/head/hooded/bee_hood - name = "bee hood" - desc = "A hood attached to a bee costume." - icon_state = "bee" - body_parts_covered = HEAD - clothing_flags = THICKMATERIAL - flags_inv = HIDEHAIR|HIDEEARS - dynamic_hair_suffix = "" - -/obj/item/clothing/suit/hooded/bloated_human //OH MY GOD WHAT HAVE YOU DONE!?!?!? - name = "bloated human suit" - desc = "A horribly bloated suit made from human skins." - icon_state = "lingspacesuit" - inhand_icon_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - allowed = list() - actions_types = list(/datum/action/item_action/toggle_human_head) - hoodtype = /obj/item/clothing/head/hooded/human_head - - -/obj/item/clothing/head/hooded/human_head - name = "bloated human head" - desc = "A horribly bloated and mismatched human head." - icon_state = "lingspacehelmet" - body_parts_covered = HEAD - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/suit/security/officer/russian - name = "\improper Russian officer's jacket" - desc = "This jacket is for those special occasions when a russian officer isn't required to wear their armor." - icon_state = "officertanjacket" - inhand_icon_state = "officertanjacket" - body_parts_covered = CHEST|ARMS - -/obj/item/clothing/suit/shrine_maiden - name = "shrine maiden's outfit" - desc = "Makes you want to exterminate some troublesome youkai." - icon_state = "shrine_maiden" - inhand_icon_state = "shrine_maiden" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEJUMPSUIT - -/* - * Misc - */ - -/obj/item/clothing/suit/straight_jacket - name = "straight jacket" - desc = "A suit that completely restrains the wearer. Manufactured by Antyphun Corp." //Straight jacket is antifun - icon_state = "straight_jacket" - inhand_icon_state = "straight_jacket" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - clothing_flags = DANGEROUS_OBJECT - equip_delay_self = 50 - strip_delay = 60 - breakouttime = 3000 - -/obj/item/clothing/suit/ianshirt - name = "worn shirt" - desc = "A worn out, curiously comfortable t-shirt with a picture of Ian. You wouldn't go so far as to say it feels like being hugged when you wear it, but it's pretty close. Good for sleeping in." - icon_state = "ianshirt" - inhand_icon_state = "ianshirt" - -/obj/item/clothing/suit/nerdshirt - name = "gamer shirt" - desc = "A baggy shirt with vintage game character Phanic the Weasel. Why would anyone wear this?" - icon_state = "nerdshirt" - inhand_icon_state = "nerdshirt" - -/obj/item/clothing/suit/vapeshirt //wearing this is asking to get beat. - name = "Vape Naysh shirt" - desc = "A cheap white T-shirt with a big tacky \"VN\" on the front, Why would you wear this unironically?" - icon_state = "vapeshirt" - inhand_icon_state = "vapeshirt" - -/obj/item/clothing/suit/striped_sweater - name = "striped sweater" - desc = "Reminds you of someone, but you just can't put your finger on it..." - icon_state = "waldo_shirt" - inhand_icon_state = "waldo_shirt" - -/obj/item/clothing/suit/jacket - name = "bomber jacket" - desc = "Aviators not included." - icon_state = "bomberjacket" - inhand_icon_state = "brownjsuit" - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/radio) - body_parts_covered = CHEST|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - -/obj/item/clothing/suit/jacket/leather - name = "leather jacket" - desc = "Pompadour not included." - icon_state = "leatherjacket" - inhand_icon_state = "hostrench" - resistance_flags = NONE - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/gun/ballistic/revolver/detective, /obj/item/radio) - -/obj/item/clothing/suit/jacket/leather/overcoat - name = "leather overcoat" - desc = "That's a damn fine coat." - icon_state = "leathercoat" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - cold_protection = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/jacket/puffer - name = "puffer jacket" - desc = "A thick jacket with a rubbery, water-resistant shell." - icon_state = "pufferjacket" - inhand_icon_state = "hostrench" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/jacket/puffer/vest - name = "puffer vest" - desc = "A thick vest with a rubbery, water-resistant shell." - icon_state = "puffervest" - inhand_icon_state = "armor" - body_parts_covered = CHEST|GROIN - cold_protection = CHEST|GROIN - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/jacket/miljacket - name = "military jacket" - desc = "A canvas jacket styled after classical American military garb. Feels sturdy, yet comfortable." - icon_state = "militaryjacket" - inhand_icon_state = "militaryjacket" - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/radio) - -/obj/item/clothing/suit/jacket/letterman - name = "letterman jacket" - desc = "A classic brown letterman jacket. Looks pretty hot and heavy." - icon_state = "letterman" - inhand_icon_state = "letterman" - -/obj/item/clothing/suit/jacket/letterman_red - name = "red letterman jacket" - desc = "A letterman jacket in a sick red color. Radical." - icon_state = "letterman_red" - inhand_icon_state = "letterman_red" - -/obj/item/clothing/suit/jacket/letterman_syndie - name = "blood-red letterman jacket" - desc = "Oddly, this jacket seems to have a large S on the back..." - icon_state = "letterman_s" - inhand_icon_state = "letterman_s" - -/obj/item/clothing/suit/jacket/letterman_nanotrasen - name = "blue letterman jacket" - desc = "A blue letterman jacket with a proud Nanotrasen N on the back. The tag says that it was made in Space China." - icon_state = "letterman_n" - inhand_icon_state = "letterman_n" - -/obj/item/clothing/suit/dracula - name = "dracula coat" - desc = "Looks like this belongs in a very old movie set." - icon_state = "draculacoat" - inhand_icon_state = "draculacoat" - -/obj/item/clothing/suit/drfreeze_coat - name = "doctor freeze's labcoat" - desc = "A labcoat imbued with the power of features and freezes." - icon_state = "drfreeze_coat" - inhand_icon_state = "drfreeze_coat" - -/obj/item/clothing/suit/gothcoat - name = "gothic coat" - desc = "Perfect for those who want to stalk around a corner of a bar." - icon_state = "gothcoat" - inhand_icon_state = "gothcoat" - -/obj/item/clothing/suit/xenos - name = "xenos suit" - desc = "A suit made out of chitinous alien hide." - icon_state = "xenos" - inhand_icon_state = "xenos_helm" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - allowed = list(/obj/item/clothing/mask/facehugger/toy) - -/obj/item/clothing/suit/nemes - name = "pharoah tunic" - desc = "Lavish space tomb not included." - icon_state = "pharoah" - inhand_icon_state = "pharoah" - body_parts_covered = CHEST|GROIN - -/obj/item/clothing/suit/caution - name = "wet floor sign" - desc = "Caution! Wet Floor!" - icon_state = "caution" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - force = 1 - throwforce = 3 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - body_parts_covered = CHEST|GROIN - attack_verb = list("warned", "cautioned", "smashed") - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/changshan_red - name = "red changshan" - desc = "A gorgeously embroidered silk shirt." - icon_state = "changshan_red" - inhand_icon_state = "changshan_red" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/changshan_blue - name = "blue changshan" - desc = "A gorgeously embroidered silk shirt." - icon_state = "changshan_blue" - inhand_icon_state = "changshan_blue" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/cheongsam_red - name = "red cheongsam" - desc = "A gorgeously embroidered silk dress." - icon_state = "cheongsam_red" - inhand_icon_state = "cheongsam_red" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/cheongsam_blue - name = "blue cheongsam" - desc = "A gorgeously embroidered silk dress." - icon_state = "cheongsam_blue" - inhand_icon_state = "cheongsam_blue" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -// WINTER COATS - -/obj/item/clothing/suit/hooded/wintercoat - name = "winter coat" - desc = "A heavy jacket made from 'synthetic' animal furs." - icon_state = "coatwinter" - inhand_icon_state = "coatwinter" - body_parts_covered = CHEST|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - -/obj/item/clothing/head/hooded/winterhood - name = "winter hood" - desc = "A hood attached to a heavy winter jacket." - icon_state = "winterhood" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/hooded/wintercoat/captain - name = "captain's winter coat" - icon_state = "coatcaptain" - inhand_icon_state = "coatcaptain" - armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - hoodtype = /obj/item/clothing/head/hooded/winterhood/captain - -/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/captain - icon_state = "winterhood_captain" - armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - -/obj/item/clothing/suit/hooded/wintercoat/security - name = "security winter coat" - icon_state = "coatsecurity" - inhand_icon_state = "coatsecurity" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/security - -/obj/item/clothing/suit/hooded/wintercoat/security/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/security - icon_state = "winterhood_security" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/medical - name = "medical winter coat" - icon_state = "coatmedical" - inhand_icon_state = "coatmedical" - allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/medical - -/obj/item/clothing/head/hooded/winterhood/medical - icon_state = "winterhood_medical" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/science - name = "science winter coat" - icon_state = "coatscience" - inhand_icon_state = "coatscience" - allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/science - -/obj/item/clothing/head/hooded/winterhood/science - icon_state = "winterhood_science" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/hooded/wintercoat/engineering - name = "engineering winter coat" - icon_state = "coatengineer" - inhand_icon_state = "coatengineer" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering - -/obj/item/clothing/head/hooded/winterhood/engineering - icon_state = "winterhood_engineer" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos - name = "atmospherics winter coat" - icon_state = "coatatmos" - inhand_icon_state = "coatatmos" - hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos - -/obj/item/clothing/head/hooded/winterhood/engineering/atmos - icon_state = "winterhood_atmos" - -/obj/item/clothing/suit/hooded/wintercoat/hydro - name = "hydroponics winter coat" - icon_state = "coathydro" - inhand_icon_state = "coathydro" - allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro - -/obj/item/clothing/head/hooded/winterhood/hydro - icon_state = "winterhood_hydro" - -/obj/item/clothing/suit/hooded/wintercoat/cargo - name = "cargo winter coat" - icon_state = "coatcargo" - inhand_icon_state = "coatcargo" - hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo - -/obj/item/clothing/head/hooded/winterhood/cargo - icon_state = "winterhood_cargo" - -/obj/item/clothing/suit/hooded/wintercoat/miner - name = "mining winter coat" - icon_state = "coatminer" - inhand_icon_state = "coatminer" - allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/miner - -/obj/item/clothing/head/hooded/winterhood/miner - icon_state = "winterhood_miner" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/head/hooded/ablative - name = "ablative hood" - desc = "Hood hopefully belonging to an ablative trenchcoat. Includes a visor for cool-o-vision." - icon_state = "ablativehood" - armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - strip_delay = 30 - var/hit_reflect_chance = 50 - -/obj/item/clothing/head/hooded/ablative/equipped(mob/living/carbon/human/user, slot) - ..() - to_chat(user, "As you put on the hood, a visor shifts into place and starts analyzing the people around you. Neat!") - ADD_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - H.add_hud_to(user) - -/obj/item/clothing/head/hooded/ablative/dropped(mob/living/carbon/human/user) - ..() - to_chat(user, "You take off the hood, removing the visor in the process and disabling its integrated hud.") - REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - H.remove_hud_from(user) - -/obj/item/clothing/head/hooded/ablative/IsReflect(def_zone) - if(def_zone != BODY_ZONE_HEAD) //If not shot where ablative is covering you, you don't get the reflection bonus! - return FALSE - if (prob(hit_reflect_chance)) - return TRUE - -/obj/item/clothing/suit/hooded/ablative - name = "ablative trenchcoat" - desc = "Experimental trenchcoat specially crafted to reflect and absorb laser and disabler shots. Don't expect it to do all that much against an axe or a shotgun, however." - icon_state = "ablativecoat" - inhand_icon_state = "ablativecoat" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - hoodtype = /obj/item/clothing/head/hooded/ablative - strip_delay = 30 - equip_delay_other = 40 - var/hit_reflect_chance = 50 - -/obj/item/clothing/suit/hooded/ablative/Initialize() - . = ..() - allowed = GLOB.security_vest_allowed - -/obj/item/clothing/suit/hooded/ablative/IsReflect(def_zone) - if(!(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))) //If not shot where ablative is covering you, you don't get the reflection bonus! - return FALSE - if (prob(hit_reflect_chance)) - return TRUE - -/obj/item/clothing/suit/spookyghost - name = "spooky ghost" - desc = "This is obviously just a bedsheet, but maybe try it on?" - icon_state = "bedsheet" - user_vars_to_edit = list("name" = "Spooky Ghost", "real_name" = "Spooky Ghost" , "incorporeal_move" = INCORPOREAL_MOVE_BASIC, "appearance_flags" = KEEP_TOGETHER|TILE_BOUND, "alpha" = 150) - alternate_worn_layer = ABOVE_BODY_FRONT_LAYER //so the bedsheet goes over everything but fire - -/obj/item/clothing/suit/bronze - name = "bronze suit" - desc = "A big and clanky suit made of bronze that offers no protection and looks very unfashionable. Nice." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_cuirass_old" - armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) - -/obj/item/clothing/suit/ghost_sheet - name = "ghost sheet" - desc = "The hands float by themselves, so it's extra spooky." - icon_state = "ghost_sheet" - inhand_icon_state = "ghost_sheet" - throwforce = 0 - throw_speed = 1 - throw_range = 2 - w_class = WEIGHT_CLASS_TINY - flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - alternate_worn_layer = UNDER_HEAD_LAYER - -/obj/item/clothing/suit/toggle/suspenders/blue - name = "blue suspenders" - desc = "The symbol of hard labor and dirty jobs." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders_blue" - -/obj/item/clothing/suit/toggle/suspenders/gray - name = "gray suspenders" - desc = "The symbol of hard labor and dirty jobs." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders_gray" - -/obj/item/clothing/suit/hooded/mysticrobe - name = "mystic's robe" - desc = "Wearing this makes you feel more attuned with the nature of the universe... as well as a bit more irresponsible. " - icon_state = "mysticrobe" - inhand_icon_state = "mysticrobe" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - allowed = list(/obj/item/spellbook, /obj/item/storage/book/bible) - flags_inv = HIDEJUMPSUIT - hoodtype = /obj/item/clothing/head/hooded/mysticrobe - -/obj/item/clothing/head/hooded/mysticrobe - name = "mystic's hood" - desc = "The balance of reality tips towards order." - icon_state = "mystichood" - inhand_icon_state = "mystichood" - body_parts_covered = HEAD - flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK - -/obj/item/clothing/suit/coordinator - name = "coordinator jacket" - desc = "A jacket for a party ooordinator, stylish!." - icon_state = "capformal" - inhand_icon_state = "capspacesuit" - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - -/obj/item/clothing/suit/hawaiian - name = "hawaiian overshirt" - desc = "A cool shirt for chilling on the beach." - icon_state = "hawaiian_blue" - inhand_icon_state = "hawaiian_blue" - -/obj/item/clothing/suit/yakuza - name = "tojo clan jacket" - desc = "The jacket of a mad dog." - icon_state = "MajimaJacket" - inhand_icon_state = "MajimaJacket" - body_parts_covered = ARMS - -/obj/item/clothing/suit/dutch - name = "dutch's jacket" - desc = "For those long nights on the beach in Tahiti." - icon_state = "DutchJacket" - inhand_icon_state = "DutchJacket" - body_parts_covered = ARMS - +/* + * Contains: + * Lasertag + * Costume + * Misc + */ + +/* + * Lasertag + */ +/obj/item/clothing/suit/bluetag + name = "blue laser tag armor" + desc = "A piece of plastic armor. It has sensors that react to red light." //Lasers are concentrated light + icon_state = "bluetag" + inhand_icon_state = "bluetag" + blood_overlay_type = "armor" + body_parts_covered = CHEST + allowed = list (/obj/item/gun/energy/laser/bluetag) + resistance_flags = NONE + +/obj/item/clothing/suit/redtag + name = "red laser tag armor" + desc = "A piece of plastic armor. It has sensors that react to blue light." + icon_state = "redtag" + inhand_icon_state = "redtag" + blood_overlay_type = "armor" + body_parts_covered = CHEST + allowed = list (/obj/item/gun/energy/laser/redtag) + resistance_flags = NONE + +/* + * Costume + */ +/obj/item/clothing/suit/hooded/flashsuit + name = "flashy costume" + desc = "What did you expect?" + icon_state = "flashsuit" + inhand_icon_state = "armor" + body_parts_covered = CHEST|GROIN + hoodtype = /obj/item/clothing/head/hooded/flashsuit + +/obj/item/clothing/head/hooded/flashsuit + name = "flash button" + desc = "You will learn to fear the flash." + icon_state = "flashsuit" + body_parts_covered = HEAD + flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK + +/obj/item/clothing/suit/pirate + name = "pirate coat" + desc = "Yarr." + icon_state = "pirate" + inhand_icon_state = "pirate" + allowed = list(/obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) + +/obj/item/clothing/suit/pirate/captain + name = "pirate captain coat" + desc = "Yarr." + icon_state = "hgpirate" + inhand_icon_state = "hgpirate" + + +/obj/item/clothing/suit/cyborg_suit + name = "cyborg suit" + desc = "Suit for a cyborg costume." + icon_state = "death" + inhand_icon_state = "death" + flags_1 = CONDUCT_1 + fire_resist = T0C+5200 + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + + +/obj/item/clothing/suit/justice + name = "justice suit" + desc = "this pretty much looks ridiculous" //Needs no fixing + icon_state = "justice" + inhand_icon_state = "justice" + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + + +/obj/item/clothing/suit/judgerobe + name = "judge's robe" + desc = "This robe commands authority." + icon_state = "judge" + inhand_icon_state = "judge" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + allowed = list(/obj/item/storage/fancy/cigarettes, /obj/item/stack/spacecash) + flags_inv = HIDEJUMPSUIT + + +/obj/item/clothing/suit/apron/overalls + name = "coveralls" + desc = "A set of denim overalls." + icon_state = "overalls" + inhand_icon_state = "overalls" + body_parts_covered = CHEST|GROIN|LEGS + +/obj/item/clothing/suit/apron/purple_bartender + name = "purple bartender apron" + desc = "A fancy purple apron for a stylish person." + icon_state = "purplebartenderapron" + inhand_icon_state = "purplebartenderapron" + body_parts_covered = CHEST|GROIN|LEGS + +/obj/item/clothing/suit/syndicatefake + name = "black and red space suit replica" + icon_state = "syndicate-black-red" + inhand_icon_state = "syndicate-black-red" + desc = "A plastic replica of the Syndicate space suit. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + resistance_flags = NONE + +/obj/item/clothing/suit/hastur + name = "\improper Hastur's robe" + desc = "Robes not meant to be worn by man." + icon_state = "hastur" + inhand_icon_state = "hastur" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + + +/obj/item/clothing/suit/imperium_monk + name = "\improper Imperium monk suit" + desc = "Have YOU killed a xeno today?" + icon_state = "imperium_monk" + inhand_icon_state = "imperium_monk" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDESHOES|HIDEJUMPSUIT + allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen) + + +/obj/item/clothing/suit/chickensuit + name = "chicken suit" + desc = "A suit made long ago by the ancient empire KFC." + icon_state = "chickensuit" + inhand_icon_state = "chickensuit" + body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET + flags_inv = HIDESHOES|HIDEJUMPSUIT + + +/obj/item/clothing/suit/monkeysuit + name = "monkey suit" + desc = "A suit that looks like a primate." + icon_state = "monkeysuit" + inhand_icon_state = "monkeysuit" + body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + +/obj/item/clothing/suit/toggle/owlwings + name = "owl cloak" + desc = "A soft brown cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive the ladies mad." + icon_state = "owl_wings" + inhand_icon_state = "owl_wings" + togglename = "wings" + body_parts_covered = ARMS|CHEST + actions_types = list(/datum/action/item_action/toggle_wings) + +/obj/item/clothing/suit/toggle/owlwings/Initialize() + . = ..() + allowed = GLOB.security_vest_allowed + +/obj/item/clothing/suit/toggle/owlwings/griffinwings + name = "griffon cloak" + desc = "A plush white cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive your captives mad." + icon_state = "griffin_wings" + inhand_icon_state = "griffin_wings" + +/obj/item/clothing/suit/cardborg + name = "cardborg suit" + desc = "An ordinary cardboard box with holes cut in the sides." + icon_state = "cardborg" + inhand_icon_state = "cardborg" + body_parts_covered = CHEST|GROIN + flags_inv = HIDEJUMPSUIT + dog_fashion = /datum/dog_fashion/back + +/obj/item/clothing/suit/cardborg/equipped(mob/living/user, slot) + ..() + if(slot == ITEM_SLOT_OCLOTHING) + disguise(user) + +/obj/item/clothing/suit/cardborg/dropped(mob/living/user) + ..() + user.remove_alt_appearance("standard_borg_disguise") + +/obj/item/clothing/suit/cardborg/proc/disguise(mob/living/carbon/human/H, obj/item/clothing/head/cardborg/borghead) + if(istype(H)) + if(!borghead) + borghead = H.head + if(istype(borghead, /obj/item/clothing/head/cardborg)) //why is this done this way? because equipped() is called BEFORE THE ITEM IS IN THE SLOT WHYYYY + var/image/I = image(icon = 'icons/mob/robots.dmi' , icon_state = "robot", loc = H) + I.override = 1 + I.add_overlay(mutable_appearance('icons/mob/robots.dmi', "robot_e")) //gotta look realistic + add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "standard_borg_disguise", I) //you look like a robot to robots! (including yourself because you're totally a robot) + + +/obj/item/clothing/suit/snowman + name = "snowman outfit" + desc = "Two white spheres covered in white glitter. 'Tis the season." + icon_state = "snowman" + inhand_icon_state = "snowman" + body_parts_covered = CHEST|GROIN + flags_inv = HIDEJUMPSUIT + +/obj/item/clothing/suit/poncho + name = "poncho" + desc = "Your classic, non-racist poncho." + icon_state = "classicponcho" + inhand_icon_state = "classicponcho" + +/obj/item/clothing/suit/poncho/green + name = "green poncho" + desc = "Your classic, non-racist poncho. This one is green." + icon_state = "greenponcho" + inhand_icon_state = "greenponcho" + +/obj/item/clothing/suit/poncho/red + name = "red poncho" + desc = "Your classic, non-racist poncho. This one is red." + icon_state = "redponcho" + inhand_icon_state = "redponcho" + +/obj/item/clothing/suit/poncho/ponchoshame + name = "poncho of shame" + desc = "Forced to live on your shameful acting as a fake Mexican, you and your poncho have grown inseparable. Literally." + icon_state = "ponchoshame" + inhand_icon_state = "ponchoshame" + +/obj/item/clothing/suit/poncho/ponchoshame/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) + +/obj/item/clothing/suit/whitedress + name = "white dress" + desc = "A fancy white dress." + icon_state = "white_dress" + inhand_icon_state = "w_suit" + body_parts_covered = CHEST|GROIN|LEGS|FEET + flags_inv = HIDEJUMPSUIT|HIDESHOES + +/obj/item/clothing/suit/hooded/carp_costume + name = "carp costume" + desc = "A costume made from 'synthetic' carp scales, it smells." + icon_state = "carp_casual" + inhand_icon_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT //Space carp like space, so you should too + allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pneumatic_cannon/speargun) + hoodtype = /obj/item/clothing/head/hooded/carp_hood + +/obj/item/clothing/head/hooded/carp_hood + name = "carp hood" + desc = "A hood attached to a carp costume." + icon_state = "carp_casual" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + +/obj/item/clothing/head/hooded/carp_hood/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == ITEM_SLOT_HEAD) + user.faction |= "carp" + +/obj/item/clothing/head/hooded/carp_hood/dropped(mob/living/carbon/human/user) + ..() + if (user.head == src) + user.faction -= "carp" + +/obj/item/clothing/suit/hooded/ian_costume //It's Ian, rub his bell- oh god what happened to his inside parts? + name = "corgi costume" + desc = "A costume that looks like someone made a human-like corgi, it won't guarantee belly rubs." + icon_state = "ian" + inhand_icon_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + //cold_protection = CHEST|GROIN|ARMS + //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + allowed = list() + hoodtype = /obj/item/clothing/head/hooded/ian_hood + dog_fashion = /datum/dog_fashion/back + +/obj/item/clothing/head/hooded/ian_hood + name = "corgi hood" + desc = "A hood that looks just like a corgi's head, it won't guarantee dog biscuits." + icon_state = "ian" + body_parts_covered = HEAD + //cold_protection = HEAD + //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + +/obj/item/clothing/suit/hooded/bee_costume // It's Hip! + name = "bee costume" + desc = "Bee the true Queen!" + icon_state = "bee" + inhand_icon_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + clothing_flags = THICKMATERIAL + hoodtype = /obj/item/clothing/head/hooded/bee_hood + +/obj/item/clothing/head/hooded/bee_hood + name = "bee hood" + desc = "A hood attached to a bee costume." + icon_state = "bee" + body_parts_covered = HEAD + clothing_flags = THICKMATERIAL + flags_inv = HIDEHAIR|HIDEEARS + dynamic_hair_suffix = "" + +/obj/item/clothing/suit/hooded/bloated_human //OH MY GOD WHAT HAVE YOU DONE!?!?!? + name = "bloated human suit" + desc = "A horribly bloated suit made from human skins." + icon_state = "lingspacesuit" + inhand_icon_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + allowed = list() + actions_types = list(/datum/action/item_action/toggle_human_head) + hoodtype = /obj/item/clothing/head/hooded/human_head + + +/obj/item/clothing/head/hooded/human_head + name = "bloated human head" + desc = "A horribly bloated and mismatched human head." + icon_state = "lingspacehelmet" + body_parts_covered = HEAD + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/suit/security/officer/russian + name = "\improper Russian officer's jacket" + desc = "This jacket is for those special occasions when a russian officer isn't required to wear their armor." + icon_state = "officertanjacket" + inhand_icon_state = "officertanjacket" + body_parts_covered = CHEST|ARMS + +/obj/item/clothing/suit/shrine_maiden + name = "shrine maiden's outfit" + desc = "Makes you want to exterminate some troublesome youkai." + icon_state = "shrine_maiden" + inhand_icon_state = "shrine_maiden" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDEJUMPSUIT + +/* + * Misc + */ + +/obj/item/clothing/suit/straight_jacket + name = "straight jacket" + desc = "A suit that completely restrains the wearer. Manufactured by Antyphun Corp." //Straight jacket is antifun + icon_state = "straight_jacket" + inhand_icon_state = "straight_jacket" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + clothing_flags = DANGEROUS_OBJECT + equip_delay_self = 50 + strip_delay = 60 + breakouttime = 3000 + +/obj/item/clothing/suit/ianshirt + name = "worn shirt" + desc = "A worn out, curiously comfortable t-shirt with a picture of Ian. You wouldn't go so far as to say it feels like being hugged when you wear it, but it's pretty close. Good for sleeping in." + icon_state = "ianshirt" + inhand_icon_state = "ianshirt" + +/obj/item/clothing/suit/nerdshirt + name = "gamer shirt" + desc = "A baggy shirt with vintage game character Phanic the Weasel. Why would anyone wear this?" + icon_state = "nerdshirt" + inhand_icon_state = "nerdshirt" + +/obj/item/clothing/suit/vapeshirt //wearing this is asking to get beat. + name = "Vape Naysh shirt" + desc = "A cheap white T-shirt with a big tacky \"VN\" on the front, Why would you wear this unironically?" + icon_state = "vapeshirt" + inhand_icon_state = "vapeshirt" + +/obj/item/clothing/suit/striped_sweater + name = "striped sweater" + desc = "Reminds you of someone, but you just can't put your finger on it..." + icon_state = "waldo_shirt" + inhand_icon_state = "waldo_shirt" + +/obj/item/clothing/suit/jacket + name = "bomber jacket" + desc = "Aviators not included." + icon_state = "bomberjacket" + inhand_icon_state = "brownjsuit" + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/radio) + body_parts_covered = CHEST|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + +/obj/item/clothing/suit/jacket/leather + name = "leather jacket" + desc = "Pompadour not included." + icon_state = "leatherjacket" + inhand_icon_state = "hostrench" + resistance_flags = NONE + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/gun/ballistic/revolver/detective, /obj/item/radio) + +/obj/item/clothing/suit/jacket/leather/overcoat + name = "leather overcoat" + desc = "That's a damn fine coat." + icon_state = "leathercoat" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + cold_protection = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/jacket/puffer + name = "puffer jacket" + desc = "A thick jacket with a rubbery, water-resistant shell." + icon_state = "pufferjacket" + inhand_icon_state = "hostrench" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/jacket/puffer/vest + name = "puffer vest" + desc = "A thick vest with a rubbery, water-resistant shell." + icon_state = "puffervest" + inhand_icon_state = "armor" + body_parts_covered = CHEST|GROIN + cold_protection = CHEST|GROIN + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/jacket/miljacket + name = "military jacket" + desc = "A canvas jacket styled after classical American military garb. Feels sturdy, yet comfortable." + icon_state = "militaryjacket" + inhand_icon_state = "militaryjacket" + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/radio) + +/obj/item/clothing/suit/jacket/letterman + name = "letterman jacket" + desc = "A classic brown letterman jacket. Looks pretty hot and heavy." + icon_state = "letterman" + inhand_icon_state = "letterman" + +/obj/item/clothing/suit/jacket/letterman_red + name = "red letterman jacket" + desc = "A letterman jacket in a sick red color. Radical." + icon_state = "letterman_red" + inhand_icon_state = "letterman_red" + +/obj/item/clothing/suit/jacket/letterman_syndie + name = "blood-red letterman jacket" + desc = "Oddly, this jacket seems to have a large S on the back..." + icon_state = "letterman_s" + inhand_icon_state = "letterman_s" + +/obj/item/clothing/suit/jacket/letterman_nanotrasen + name = "blue letterman jacket" + desc = "A blue letterman jacket with a proud Nanotrasen N on the back. The tag says that it was made in Space China." + icon_state = "letterman_n" + inhand_icon_state = "letterman_n" + +/obj/item/clothing/suit/dracula + name = "dracula coat" + desc = "Looks like this belongs in a very old movie set." + icon_state = "draculacoat" + inhand_icon_state = "draculacoat" + +/obj/item/clothing/suit/drfreeze_coat + name = "doctor freeze's labcoat" + desc = "A labcoat imbued with the power of features and freezes." + icon_state = "drfreeze_coat" + inhand_icon_state = "drfreeze_coat" + +/obj/item/clothing/suit/gothcoat + name = "gothic coat" + desc = "Perfect for those who want to stalk around a corner of a bar." + icon_state = "gothcoat" + inhand_icon_state = "gothcoat" + +/obj/item/clothing/suit/xenos + name = "xenos suit" + desc = "A suit made out of chitinous alien hide." + icon_state = "xenos" + inhand_icon_state = "xenos_helm" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + allowed = list(/obj/item/clothing/mask/facehugger/toy) + +/obj/item/clothing/suit/nemes + name = "pharoah tunic" + desc = "Lavish space tomb not included." + icon_state = "pharoah" + inhand_icon_state = "pharoah" + body_parts_covered = CHEST|GROIN + +/obj/item/clothing/suit/caution + name = "wet floor sign" + desc = "Caution! Wet Floor!" + icon_state = "caution" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + force = 1 + throwforce = 3 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + body_parts_covered = CHEST|GROIN + attack_verb = list("warned", "cautioned", "smashed") + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/changshan_red + name = "red changshan" + desc = "A gorgeously embroidered silk shirt." + icon_state = "changshan_red" + inhand_icon_state = "changshan_red" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/changshan_blue + name = "blue changshan" + desc = "A gorgeously embroidered silk shirt." + icon_state = "changshan_blue" + inhand_icon_state = "changshan_blue" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/cheongsam_red + name = "red cheongsam" + desc = "A gorgeously embroidered silk dress." + icon_state = "cheongsam_red" + inhand_icon_state = "cheongsam_red" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/cheongsam_blue + name = "blue cheongsam" + desc = "A gorgeously embroidered silk dress." + icon_state = "cheongsam_blue" + inhand_icon_state = "cheongsam_blue" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +// WINTER COATS + +/obj/item/clothing/suit/hooded/wintercoat + name = "winter coat" + desc = "A heavy jacket made from 'synthetic' animal furs." + icon_state = "coatwinter" + inhand_icon_state = "coatwinter" + body_parts_covered = CHEST|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + +/obj/item/clothing/head/hooded/winterhood + name = "winter hood" + desc = "A hood attached to a heavy winter jacket." + icon_state = "winterhood" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/hooded/wintercoat/captain + name = "captain's winter coat" + icon_state = "coatcaptain" + inhand_icon_state = "coatcaptain" + armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + hoodtype = /obj/item/clothing/head/hooded/winterhood/captain + +/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/captain + icon_state = "winterhood_captain" + armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + +/obj/item/clothing/suit/hooded/wintercoat/security + name = "security winter coat" + icon_state = "coatsecurity" + inhand_icon_state = "coatsecurity" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/security + +/obj/item/clothing/suit/hooded/wintercoat/security/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/security + icon_state = "winterhood_security" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/medical + name = "medical winter coat" + icon_state = "coatmedical" + inhand_icon_state = "coatmedical" + allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/medical + +/obj/item/clothing/head/hooded/winterhood/medical + icon_state = "winterhood_medical" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/science + name = "science winter coat" + icon_state = "coatscience" + inhand_icon_state = "coatscience" + allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/science + +/obj/item/clothing/head/hooded/winterhood/science + icon_state = "winterhood_science" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/hooded/wintercoat/engineering + name = "engineering winter coat" + icon_state = "coatengineer" + inhand_icon_state = "coatengineer" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering + +/obj/item/clothing/head/hooded/winterhood/engineering + icon_state = "winterhood_engineer" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos + name = "atmospherics winter coat" + icon_state = "coatatmos" + inhand_icon_state = "coatatmos" + hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos + +/obj/item/clothing/head/hooded/winterhood/engineering/atmos + icon_state = "winterhood_atmos" + +/obj/item/clothing/suit/hooded/wintercoat/hydro + name = "hydroponics winter coat" + icon_state = "coathydro" + inhand_icon_state = "coathydro" + allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro + +/obj/item/clothing/head/hooded/winterhood/hydro + icon_state = "winterhood_hydro" + +/obj/item/clothing/suit/hooded/wintercoat/cargo + name = "cargo winter coat" + icon_state = "coatcargo" + inhand_icon_state = "coatcargo" + hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo + +/obj/item/clothing/head/hooded/winterhood/cargo + icon_state = "winterhood_cargo" + +/obj/item/clothing/suit/hooded/wintercoat/miner + name = "mining winter coat" + icon_state = "coatminer" + inhand_icon_state = "coatminer" + allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/miner + +/obj/item/clothing/head/hooded/winterhood/miner + icon_state = "winterhood_miner" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/head/hooded/ablative + name = "ablative hood" + desc = "Hood hopefully belonging to an ablative trenchcoat. Includes a visor for cool-o-vision." + icon_state = "ablativehood" + armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + strip_delay = 30 + var/hit_reflect_chance = 50 + +/obj/item/clothing/head/hooded/ablative/equipped(mob/living/carbon/human/user, slot) + ..() + to_chat(user, "As you put on the hood, a visor shifts into place and starts analyzing the people around you. Neat!") + ADD_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] + H.add_hud_to(user) + +/obj/item/clothing/head/hooded/ablative/dropped(mob/living/carbon/human/user) + ..() + to_chat(user, "You take off the hood, removing the visor in the process and disabling its integrated hud.") + REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] + H.remove_hud_from(user) + +/obj/item/clothing/head/hooded/ablative/IsReflect(def_zone) + if(def_zone != BODY_ZONE_HEAD) //If not shot where ablative is covering you, you don't get the reflection bonus! + return FALSE + if (prob(hit_reflect_chance)) + return TRUE + +/obj/item/clothing/suit/hooded/ablative + name = "ablative trenchcoat" + desc = "Experimental trenchcoat specially crafted to reflect and absorb laser and disabler shots. Don't expect it to do all that much against an axe or a shotgun, however." + icon_state = "ablativecoat" + inhand_icon_state = "ablativecoat" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + hoodtype = /obj/item/clothing/head/hooded/ablative + strip_delay = 30 + equip_delay_other = 40 + var/hit_reflect_chance = 50 + +/obj/item/clothing/suit/hooded/ablative/Initialize() + . = ..() + allowed = GLOB.security_vest_allowed + +/obj/item/clothing/suit/hooded/ablative/IsReflect(def_zone) + if(!(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))) //If not shot where ablative is covering you, you don't get the reflection bonus! + return FALSE + if (prob(hit_reflect_chance)) + return TRUE + +/obj/item/clothing/suit/spookyghost + name = "spooky ghost" + desc = "This is obviously just a bedsheet, but maybe try it on?" + icon_state = "bedsheet" + user_vars_to_edit = list("name" = "Spooky Ghost", "real_name" = "Spooky Ghost" , "incorporeal_move" = INCORPOREAL_MOVE_BASIC, "appearance_flags" = KEEP_TOGETHER|TILE_BOUND, "alpha" = 150) + alternate_worn_layer = ABOVE_BODY_FRONT_LAYER //so the bedsheet goes over everything but fire + +/obj/item/clothing/suit/bronze + name = "bronze suit" + desc = "A big and clanky suit made of bronze that offers no protection and looks very unfashionable. Nice." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_cuirass_old" + armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) + +/obj/item/clothing/suit/ghost_sheet + name = "ghost sheet" + desc = "The hands float by themselves, so it's extra spooky." + icon_state = "ghost_sheet" + inhand_icon_state = "ghost_sheet" + throwforce = 0 + throw_speed = 1 + throw_range = 2 + w_class = WEIGHT_CLASS_TINY + flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + alternate_worn_layer = UNDER_HEAD_LAYER + +/obj/item/clothing/suit/toggle/suspenders/blue + name = "blue suspenders" + desc = "The symbol of hard labor and dirty jobs." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders_blue" + +/obj/item/clothing/suit/toggle/suspenders/gray + name = "gray suspenders" + desc = "The symbol of hard labor and dirty jobs." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders_gray" + +/obj/item/clothing/suit/hooded/mysticrobe + name = "mystic's robe" + desc = "Wearing this makes you feel more attuned with the nature of the universe... as well as a bit more irresponsible. " + icon_state = "mysticrobe" + inhand_icon_state = "mysticrobe" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + allowed = list(/obj/item/spellbook, /obj/item/storage/book/bible) + flags_inv = HIDEJUMPSUIT + hoodtype = /obj/item/clothing/head/hooded/mysticrobe + +/obj/item/clothing/head/hooded/mysticrobe + name = "mystic's hood" + desc = "The balance of reality tips towards order." + icon_state = "mystichood" + inhand_icon_state = "mystichood" + body_parts_covered = HEAD + flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK + +/obj/item/clothing/suit/coordinator + name = "coordinator jacket" + desc = "A jacket for a party ooordinator, stylish!." + icon_state = "capformal" + inhand_icon_state = "capspacesuit" + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + +/obj/item/clothing/suit/hawaiian + name = "hawaiian overshirt" + desc = "A cool shirt for chilling on the beach." + icon_state = "hawaiian_blue" + inhand_icon_state = "hawaiian_blue" + +/obj/item/clothing/suit/yakuza + name = "tojo clan jacket" + desc = "The jacket of a mad dog." + icon_state = "MajimaJacket" + inhand_icon_state = "MajimaJacket" + body_parts_covered = ARMS + +/obj/item/clothing/suit/dutch + name = "dutch's jacket" + desc = "For those long nights on the beach in Tahiti." + icon_state = "DutchJacket" + inhand_icon_state = "DutchJacket" + body_parts_covered = ARMS + diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index d77f5ae412b..b29aaeef3f4 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -1,148 +1,148 @@ -/* - * Contains: - * Fire protection - * Bomb protection - * Radiation protection - */ - -/* - * Fire protection - */ - -/obj/item/clothing/suit/fire - name = "emergency firesuit" - desc = "A suit that helps protect against fire and heat." - icon_state = "fire" - inhand_icon_state = "ro_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.5 - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/extinguisher, /obj/item/crowbar) - slowdown = 1 - armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT - cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - strip_delay = 60 - equip_delay_other = 60 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/fire/firefighter - icon_state = "firesuit" - inhand_icon_state = "firefighter" - -/obj/item/clothing/suit/fire/heavy - name = "heavy firesuit" - desc = "An old, bulky thermal protection suit." - icon_state = "thermal" - inhand_icon_state = "ro_suit" - slowdown = 1.5 - -/obj/item/clothing/suit/fire/atmos - name = "firesuit" - desc = "An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame." - icon_state = "atmos_firesuit" - inhand_icon_state = "firesuit_atmos" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - -/* - * Bomb protection - */ -/obj/item/clothing/head/bomb_hood - name = "bomb hood" - desc = "Use in case of bomb." - icon_state = "bombsuit" - clothing_flags = THICKMATERIAL | SNUG_FIT - armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEFACE|HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR - dynamic_hair_suffix = "" - dynamic_fhair_suffix = "" - cold_protection = HEAD - min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT - strip_delay = 70 - equip_delay_other = 70 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - - -/obj/item/clothing/suit/bomb_suit - name = "bomb suit" - desc = "A suit designed for safety when handling explosives." - icon_state = "bombsuit" - inhand_icon_state = "bombsuit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - clothing_flags = THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - slowdown = 2 - armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEJUMPSUIT - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = NONE - - -/obj/item/clothing/head/bomb_hood/security - icon_state = "bombsuit_sec" - inhand_icon_state = "bombsuit_sec" - -/obj/item/clothing/suit/bomb_suit/security - icon_state = "bombsuit_sec" - inhand_icon_state = "bombsuit_sec" - allowed = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/restraints/handcuffs) - - -/obj/item/clothing/head/bomb_hood/white - icon_state = "bombsuit_white" - inhand_icon_state = "bombsuit_white" - -/obj/item/clothing/suit/bomb_suit/white - icon_state = "bombsuit_white" - inhand_icon_state = "bombsuit_white" - -/* -* Radiation protection -*/ - -/obj/item/clothing/head/radiation - name = "radiation hood" - icon_state = "rad" - desc = "A hood with radiation protective properties. The label reads, 'Made with lead. Please do not consume insulation.'" - clothing_flags = THICKMATERIAL | SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) - strip_delay = 60 - equip_delay_other = 60 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - flags_1 = RAD_PROTECT_CONTENTS_1 - -/obj/item/clothing/suit/radiation - name = "radiation suit" - desc = "A suit that protects against radiation. The label reads, 'Made with lead. Please do not consume insulation.'" - icon_state = "rad" - inhand_icon_state = "rad_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.5 - clothing_flags = THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/geiger_counter) - slowdown = 1.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) - strip_delay = 60 - equip_delay_other = 60 - flags_inv = HIDEJUMPSUIT - resistance_flags = NONE - flags_1 = RAD_PROTECT_CONTENTS_1 +/* + * Contains: + * Fire protection + * Bomb protection + * Radiation protection + */ + +/* + * Fire protection + */ + +/obj/item/clothing/suit/fire + name = "emergency firesuit" + desc = "A suit that helps protect against fire and heat." + icon_state = "fire" + inhand_icon_state = "ro_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.5 + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/extinguisher, /obj/item/crowbar) + slowdown = 1 + armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + strip_delay = 60 + equip_delay_other = 60 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/fire/firefighter + icon_state = "firesuit" + inhand_icon_state = "firefighter" + +/obj/item/clothing/suit/fire/heavy + name = "heavy firesuit" + desc = "An old, bulky thermal protection suit." + icon_state = "thermal" + inhand_icon_state = "ro_suit" + slowdown = 1.5 + +/obj/item/clothing/suit/fire/atmos + name = "firesuit" + desc = "An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame." + icon_state = "atmos_firesuit" + inhand_icon_state = "firesuit_atmos" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + +/* + * Bomb protection + */ +/obj/item/clothing/head/bomb_hood + name = "bomb hood" + desc = "Use in case of bomb." + icon_state = "bombsuit" + clothing_flags = THICKMATERIAL | SNUG_FIT + armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEFACE|HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR + dynamic_hair_suffix = "" + dynamic_fhair_suffix = "" + cold_protection = HEAD + min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT + strip_delay = 70 + equip_delay_other = 70 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + + +/obj/item/clothing/suit/bomb_suit + name = "bomb suit" + desc = "A suit designed for safety when handling explosives." + icon_state = "bombsuit" + inhand_icon_state = "bombsuit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + clothing_flags = THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + slowdown = 2 + armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEJUMPSUIT + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = NONE + + +/obj/item/clothing/head/bomb_hood/security + icon_state = "bombsuit_sec" + inhand_icon_state = "bombsuit_sec" + +/obj/item/clothing/suit/bomb_suit/security + icon_state = "bombsuit_sec" + inhand_icon_state = "bombsuit_sec" + allowed = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/restraints/handcuffs) + + +/obj/item/clothing/head/bomb_hood/white + icon_state = "bombsuit_white" + inhand_icon_state = "bombsuit_white" + +/obj/item/clothing/suit/bomb_suit/white + icon_state = "bombsuit_white" + inhand_icon_state = "bombsuit_white" + +/* +* Radiation protection +*/ + +/obj/item/clothing/head/radiation + name = "radiation hood" + icon_state = "rad" + desc = "A hood with radiation protective properties. The label reads, 'Made with lead. Please do not consume insulation.'" + clothing_flags = THICKMATERIAL | SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) + strip_delay = 60 + equip_delay_other = 60 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + flags_1 = RAD_PROTECT_CONTENTS_1 + +/obj/item/clothing/suit/radiation + name = "radiation suit" + desc = "A suit that protects against radiation. The label reads, 'Made with lead. Please do not consume insulation.'" + icon_state = "rad" + inhand_icon_state = "rad_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.5 + clothing_flags = THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/geiger_counter) + slowdown = 1.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) + strip_delay = 60 + equip_delay_other = 60 + flags_inv = HIDEJUMPSUIT + resistance_flags = NONE + flags_1 = RAD_PROTECT_CONTENTS_1 diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 5402c4db0b1..ee4f4a212f1 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -1,231 +1,231 @@ -/obj/item/clothing/head/wizard - name = "wizard hat" - desc = "Strange-looking hat-wear that most certainly belongs to a real magic user." - icon_state = "wizard" - gas_transfer_coefficient = 0.01 // IT'S MAGICAL OKAY JEEZ +1 TO NOT DIE - permeability_coefficient = 0.01 - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100, "wound" = 20) - strip_delay = 50 - equip_delay_other = 50 - clothing_flags = SNUG_FIT - resistance_flags = FIRE_PROOF | ACID_PROOF - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/wizard/red - name = "red wizard hat" - desc = "Strange-looking red hat-wear that most certainly belongs to a real magic user." - icon_state = "redwizard" - dog_fashion = /datum/dog_fashion/head/red_wizard - -/obj/item/clothing/head/wizard/yellow - name = "yellow wizard hat" - desc = "Strange-looking yellow hat-wear that most certainly belongs to a powerful magic user." - icon_state = "yellowwizard" - dog_fashion = null - -/obj/item/clothing/head/wizard/black - name = "black wizard hat" - desc = "Strange-looking black hat-wear that most certainly belongs to a real skeleton. Spooky." - icon_state = "blackwizard" - dog_fashion = null - -/obj/item/clothing/head/wizard/fake - name = "wizard hat" - desc = "It has WIZZARD written across it in sequins. Comes with a cool beard." - icon_state = "wizard-fake" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/wizard/marisa - name = "witch hat" - desc = "Strange-looking hat-wear. Makes you want to cast fireballs." - icon_state = "marisa" - dog_fashion = null - -/obj/item/clothing/head/wizard/magus - name = "\improper Magus helm" - desc = "A mysterious helmet that hums with an unearthly power." - icon_state = "magus" - inhand_icon_state = "magus" - dog_fashion = null - -/obj/item/clothing/head/wizard/santa - name = "Santa's hat" - desc = "Ho ho ho. Merrry X-mas!" - icon_state = "santahat" - flags_inv = HIDEHAIR|HIDEFACIALHAIR - dog_fashion = null - -/obj/item/clothing/suit/wizrobe - name = "wizard robe" - desc = "A magnificent, gem-lined robe that seems to radiate power." - icon_state = "wizard" - inhand_icon_state = "wizrobe" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - body_parts_covered = CHEST|GROIN|ARMS|LEGS - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100, "wound" = 20) - allowed = list(/obj/item/teleportation_scroll) - flags_inv = HIDEJUMPSUIT - strip_delay = 50 - equip_delay_other = 50 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/suit/wizrobe/red - name = "red wizard robe" - desc = "A magnificent red gem-lined robe that seems to radiate power." - icon_state = "redwizard" - inhand_icon_state = "redwizrobe" - -/obj/item/clothing/suit/wizrobe/yellow - name = "yellow wizard robe" - desc = "A magnificent yellow gem-lined robe that seems to radiate power." - icon_state = "yellowwizard" - inhand_icon_state = "yellowwizrobe" - -/obj/item/clothing/suit/wizrobe/black - name = "black wizard robe" - desc = "An unnerving black gem-lined robe that reeks of death and decay." - icon_state = "blackwizard" - inhand_icon_state = "blackwizrobe" - -/obj/item/clothing/suit/wizrobe/marisa - name = "witch robe" - desc = "Magic is all about the spell power, ZE!" - icon_state = "marisa" - inhand_icon_state = "marisarobe" - -/obj/item/clothing/suit/wizrobe/magusblue - name = "\improper Magus robe" - desc = "A set of armored robes that seem to radiate a dark power." - icon_state = "magusblue" - inhand_icon_state = "magusblue" - -/obj/item/clothing/suit/wizrobe/magusred - name = "\improper Magus robe" - desc = "A set of armored robes that seem to radiate a dark power." - icon_state = "magusred" - inhand_icon_state = "magusred" - - -/obj/item/clothing/suit/wizrobe/santa - name = "Santa's suit" - desc = "Festive!" - icon_state = "santa" - inhand_icon_state = "santa" - -/obj/item/clothing/suit/wizrobe/fake - name = "wizard robe" - desc = "A rather dull blue robe meant to mimic real wizard robes." - icon_state = "wizard-fake" - inhand_icon_state = "wizrobe" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - -/obj/item/clothing/head/wizard/marisa/fake - name = "witch hat" - desc = "Strange-looking hat-wear, makes you want to cast fireballs." - icon_state = "marisa" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - -/obj/item/clothing/suit/wizrobe/marisa/fake - name = "witch robe" - desc = "Magic is all about the spell power, ZE!" - icon_state = "marisa" - inhand_icon_state = "marisarobe" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - -/obj/item/clothing/suit/wizrobe/paper - name = "papier-mache robe" // no non-latin characters! - desc = "A robe held together by various bits of clear-tape and paste." - icon_state = "wizard-paper" - inhand_icon_state = "wizard-paper" - var/robe_charge = TRUE - actions_types = list(/datum/action/item_action/stickmen) - - -/obj/item/clothing/suit/wizrobe/paper/ui_action_click(mob/user, action) - stickmen() - - -/obj/item/clothing/suit/wizrobe/paper/verb/stickmen() - set category = "Object" - set name = "Summon Stick Minions" - set src in usr - if(!isliving(usr)) - return - if(!robe_charge) - to_chat(usr, "\The robe's internal magic supply is still recharging!") - return - - usr.say("Rise, my creation! Off your page into this realm!", forced = "stickman summoning") - playsound(src.loc, 'sound/magic/summon_magic.ogg', 50, TRUE, TRUE) - var/mob/living/M = new /mob/living/simple_animal/hostile/stickman(get_turf(usr)) - var/list/factions = usr.faction - M.faction = factions - src.robe_charge = FALSE - sleep(30) - src.robe_charge = TRUE - to_chat(usr, "\The robe hums, its internal magic supply restored.") - - -//Shielded Armour - -/obj/item/clothing/suit/space/hardsuit/shielded/wizard - name = "battlemage armour" - desc = "Not all wizards are afraid of getting up close and personal." - icon_state = "battlemage" - inhand_icon_state = "battlemage" - recharge_rate = 0 - current_charges = 15 - recharge_cooldown = INFINITY - shield_state = "shield-red" - shield_on = "shield-red" - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - slowdown = 0 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard - name = "battlemage helmet" - desc = "A suitably impressive helmet." - icon_state = "battlemage" - inhand_icon_state = "battlemage" - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - actions_types = null //No inbuilt light - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/attack_self(mob/user) - return - -/obj/item/wizard_armour_charge - name = "battlemage shield charges" - desc = "A powerful rune that will increase the number of hits a suit of battlemage armour can take before failing.." - icon = 'icons/effects/effects.dmi' - icon_state = "electricity2" - -/obj/item/wizard_armour_charge/afterattack(obj/item/clothing/suit/space/hardsuit/shielded/wizard/W, mob/user, proximity) - . = ..() - if(!proximity) - return - if(!istype(W)) - to_chat(user, "The rune can only be used on battlemage armour!") - return - W.current_charges += 8 - to_chat(user, "You charge \the [W]. It can now absorb [W.current_charges] hits.") - qdel(src) +/obj/item/clothing/head/wizard + name = "wizard hat" + desc = "Strange-looking hat-wear that most certainly belongs to a real magic user." + icon_state = "wizard" + gas_transfer_coefficient = 0.01 // IT'S MAGICAL OKAY JEEZ +1 TO NOT DIE + permeability_coefficient = 0.01 + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100, "wound" = 20) + strip_delay = 50 + equip_delay_other = 50 + clothing_flags = SNUG_FIT + resistance_flags = FIRE_PROOF | ACID_PROOF + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/wizard/red + name = "red wizard hat" + desc = "Strange-looking red hat-wear that most certainly belongs to a real magic user." + icon_state = "redwizard" + dog_fashion = /datum/dog_fashion/head/red_wizard + +/obj/item/clothing/head/wizard/yellow + name = "yellow wizard hat" + desc = "Strange-looking yellow hat-wear that most certainly belongs to a powerful magic user." + icon_state = "yellowwizard" + dog_fashion = null + +/obj/item/clothing/head/wizard/black + name = "black wizard hat" + desc = "Strange-looking black hat-wear that most certainly belongs to a real skeleton. Spooky." + icon_state = "blackwizard" + dog_fashion = null + +/obj/item/clothing/head/wizard/fake + name = "wizard hat" + desc = "It has WIZZARD written across it in sequins. Comes with a cool beard." + icon_state = "wizard-fake" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/wizard/marisa + name = "witch hat" + desc = "Strange-looking hat-wear. Makes you want to cast fireballs." + icon_state = "marisa" + dog_fashion = null + +/obj/item/clothing/head/wizard/magus + name = "\improper Magus helm" + desc = "A mysterious helmet that hums with an unearthly power." + icon_state = "magus" + inhand_icon_state = "magus" + dog_fashion = null + +/obj/item/clothing/head/wizard/santa + name = "Santa's hat" + desc = "Ho ho ho. Merrry X-mas!" + icon_state = "santahat" + flags_inv = HIDEHAIR|HIDEFACIALHAIR + dog_fashion = null + +/obj/item/clothing/suit/wizrobe + name = "wizard robe" + desc = "A magnificent, gem-lined robe that seems to radiate power." + icon_state = "wizard" + inhand_icon_state = "wizrobe" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + body_parts_covered = CHEST|GROIN|ARMS|LEGS + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100, "wound" = 20) + allowed = list(/obj/item/teleportation_scroll) + flags_inv = HIDEJUMPSUIT + strip_delay = 50 + equip_delay_other = 50 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/suit/wizrobe/red + name = "red wizard robe" + desc = "A magnificent red gem-lined robe that seems to radiate power." + icon_state = "redwizard" + inhand_icon_state = "redwizrobe" + +/obj/item/clothing/suit/wizrobe/yellow + name = "yellow wizard robe" + desc = "A magnificent yellow gem-lined robe that seems to radiate power." + icon_state = "yellowwizard" + inhand_icon_state = "yellowwizrobe" + +/obj/item/clothing/suit/wizrobe/black + name = "black wizard robe" + desc = "An unnerving black gem-lined robe that reeks of death and decay." + icon_state = "blackwizard" + inhand_icon_state = "blackwizrobe" + +/obj/item/clothing/suit/wizrobe/marisa + name = "witch robe" + desc = "Magic is all about the spell power, ZE!" + icon_state = "marisa" + inhand_icon_state = "marisarobe" + +/obj/item/clothing/suit/wizrobe/magusblue + name = "\improper Magus robe" + desc = "A set of armored robes that seem to radiate a dark power." + icon_state = "magusblue" + inhand_icon_state = "magusblue" + +/obj/item/clothing/suit/wizrobe/magusred + name = "\improper Magus robe" + desc = "A set of armored robes that seem to radiate a dark power." + icon_state = "magusred" + inhand_icon_state = "magusred" + + +/obj/item/clothing/suit/wizrobe/santa + name = "Santa's suit" + desc = "Festive!" + icon_state = "santa" + inhand_icon_state = "santa" + +/obj/item/clothing/suit/wizrobe/fake + name = "wizard robe" + desc = "A rather dull blue robe meant to mimic real wizard robes." + icon_state = "wizard-fake" + inhand_icon_state = "wizrobe" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + +/obj/item/clothing/head/wizard/marisa/fake + name = "witch hat" + desc = "Strange-looking hat-wear, makes you want to cast fireballs." + icon_state = "marisa" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + +/obj/item/clothing/suit/wizrobe/marisa/fake + name = "witch robe" + desc = "Magic is all about the spell power, ZE!" + icon_state = "marisa" + inhand_icon_state = "marisarobe" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + +/obj/item/clothing/suit/wizrobe/paper + name = "papier-mache robe" // no non-latin characters! + desc = "A robe held together by various bits of clear-tape and paste." + icon_state = "wizard-paper" + inhand_icon_state = "wizard-paper" + var/robe_charge = TRUE + actions_types = list(/datum/action/item_action/stickmen) + + +/obj/item/clothing/suit/wizrobe/paper/ui_action_click(mob/user, action) + stickmen() + + +/obj/item/clothing/suit/wizrobe/paper/verb/stickmen() + set category = "Object" + set name = "Summon Stick Minions" + set src in usr + if(!isliving(usr)) + return + if(!robe_charge) + to_chat(usr, "\The robe's internal magic supply is still recharging!") + return + + usr.say("Rise, my creation! Off your page into this realm!", forced = "stickman summoning") + playsound(src.loc, 'sound/magic/summon_magic.ogg', 50, TRUE, TRUE) + var/mob/living/M = new /mob/living/simple_animal/hostile/stickman(get_turf(usr)) + var/list/factions = usr.faction + M.faction = factions + src.robe_charge = FALSE + sleep(30) + src.robe_charge = TRUE + to_chat(usr, "\The robe hums, its internal magic supply restored.") + + +//Shielded Armour + +/obj/item/clothing/suit/space/hardsuit/shielded/wizard + name = "battlemage armour" + desc = "Not all wizards are afraid of getting up close and personal." + icon_state = "battlemage" + inhand_icon_state = "battlemage" + recharge_rate = 0 + current_charges = 15 + recharge_cooldown = INFINITY + shield_state = "shield-red" + shield_on = "shield-red" + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + slowdown = 0 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + name = "battlemage helmet" + desc = "A suitably impressive helmet." + icon_state = "battlemage" + inhand_icon_state = "battlemage" + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + actions_types = null //No inbuilt light + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/attack_self(mob/user) + return + +/obj/item/wizard_armour_charge + name = "battlemage shield charges" + desc = "A powerful rune that will increase the number of hits a suit of battlemage armour can take before failing.." + icon = 'icons/effects/effects.dmi' + icon_state = "electricity2" + +/obj/item/wizard_armour_charge/afterattack(obj/item/clothing/suit/space/hardsuit/shielded/wizard/W, mob/user, proximity) + . = ..() + if(!proximity) + return + if(!istype(W)) + to_chat(user, "The rune can only be used on battlemage armour!") + return + W.current_charges += 8 + to_chat(user, "You charge \the [W]. It can now absorb [W.current_charges] hits.") + qdel(src) diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index 9e5f863fbb5..974183b9d28 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -1,292 +1,292 @@ -/obj/item/clothing/under - name = "under" - icon = 'icons/obj/clothing/under/default.dmi' - worn_icon = 'icons/mob/clothing/under/default.dmi' - body_parts_covered = CHEST|GROIN|LEGS|ARMS - permeability_coefficient = 0.9 - slot_flags = ITEM_SLOT_ICLOTHING - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5) - equip_sound = 'sound/items/equip/jumpsuit_equip.ogg' - drop_sound = 'sound/items/handling/cloth_drop.ogg' - pickup_sound = 'sound/items/handling/cloth_pickup.ogg' - limb_integrity = 30 - var/fitted = FEMALE_UNIFORM_FULL // For use in alternate clothing styles for women - var/has_sensor = HAS_SENSORS // For the crew computer - var/random_sensor = TRUE - var/sensor_mode = NO_SENSORS - var/can_adjust = TRUE - var/adjusted = NORMAL_STYLE - var/alt_covers_chest = FALSE // for adjusted/rolled-down jumpsuits, FALSE = exposes chest and arms, TRUE = exposes arms only - var/obj/item/clothing/accessory/attached_accessory - var/mutable_appearance/accessory_overlay - var/mutantrace_variation = NO_MUTANTRACE_VARIATION //Are there special sprites for specific situations? Don't use this unless you need to. - var/freshly_laundered = FALSE - -/obj/item/clothing/under/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "uniformblood") - if(accessory_overlay) - . += accessory_overlay - -/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) - if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = I - C.use(1) - has_sensor = HAS_SENSORS - to_chat(user,"You repair the suit sensors on [src] with [C].") - return 1 - if(!attach_accessory(I, user)) - return ..() - -/obj/item/clothing/under/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_w_uniform() - if(has_sensor > NO_SENSORS) - has_sensor = BROKEN_SENSORS - -/obj/item/clothing/under/Initialize() - . = ..() - if(random_sensor) - //make the sensor mode favor higher levels, except coords. - sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS, SENSOR_COORDS) - -/obj/item/clothing/under/emp_act() - . = ..() - if(has_sensor > NO_SENSORS) - sensor_mode = pick(SENSOR_OFF, SENSOR_OFF, SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS) - if(ismob(loc)) - var/mob/M = loc - to_chat(M,"The sensors on the [src] change rapidly!") - -/obj/item/clothing/under/equipped(mob/user, slot) - ..() - if(adjusted) - adjusted = NORMAL_STYLE - fitted = initial(fitted) - if(!alt_covers_chest) - body_parts_covered |= CHEST - - if(mutantrace_variation && ishuman(user)) - var/mob/living/carbon/human/H = user - if(DIGITIGRADE in H.dna.species.species_traits) - adjusted = DIGITIGRADE_STYLE - H.update_inv_w_uniform() - - if(slot == ITEM_SLOT_ICLOTHING && freshly_laundered) - freshly_laundered = FALSE - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "fresh_laundry", /datum/mood_event/fresh_laundry) - - if(attached_accessory && slot != ITEM_SLOT_HANDS && ishuman(user)) - var/mob/living/carbon/human/H = user - attached_accessory.on_uniform_equip(src, user) - H.fan_hud_set_fandom() - if(attached_accessory.above_suit) - H.update_inv_wear_suit() - -/obj/item/clothing/under/dropped(mob/user) - if(attached_accessory) - attached_accessory.on_uniform_dropped(src, user) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - H.fan_hud_set_fandom() - if(attached_accessory.above_suit) - H.update_inv_wear_suit() - ..() - -/mob/living/carbon/human/update_suit_sensors() - . = ..() - update_sensor_list() - -/mob/living/carbon/human/proc/update_sensor_list() - var/obj/item/clothing/under/U = w_uniform - if(istype(U) && U.has_sensor > 0 && U.sensor_mode) - GLOB.suit_sensors_list |= src - else - GLOB.suit_sensors_list -= src - -/mob/living/carbon/human/dummy/update_sensor_list() - return - -/obj/item/clothing/under/proc/attach_accessory(obj/item/I, mob/user, notifyAttach = 1) - . = FALSE - if(istype(I, /obj/item/clothing/accessory)) - var/obj/item/clothing/accessory/A = I - if(attached_accessory) - if(user) - to_chat(user, "[src] already has an accessory.") - return - else - - if(!A.can_attach_accessory(src, user)) //Make sure the suit has a place to put the accessory. - return - if(user && !user.temporarilyRemoveItemFromInventory(I)) - return - if(!A.attach(src, user)) - return - - if(user && notifyAttach) - to_chat(user, "You attach [I] to [src].") - - var/accessory_color = attached_accessory.icon_state - accessory_overlay = mutable_appearance('icons/mob/clothing/accessories.dmi', "[accessory_color]") - accessory_overlay.alpha = attached_accessory.alpha - accessory_overlay.color = attached_accessory.color - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_w_uniform() - H.update_inv_wear_suit() - H.fan_hud_set_fandom() - - return TRUE - -/obj/item/clothing/under/proc/remove_accessory(mob/user) - if(!isliving(user)) - return - if(!can_use(user)) - return - - if(attached_accessory) - var/obj/item/clothing/accessory/A = attached_accessory - attached_accessory.detach(src, user) - if(user.put_in_hands(A)) - to_chat(user, "You detach [A] from [src].") - else - to_chat(user, "You detach [A] from [src] and it falls on the floor.") - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_w_uniform() - H.update_inv_wear_suit() - H.fan_hud_set_fandom() - - -/obj/item/clothing/under/examine(mob/user) - . = ..() - if(freshly_laundered) - . += "It looks fresh and clean." - if(can_adjust) - if(adjusted == ALT_STYLE) - . += "Alt-click on [src] to wear it normally." - else - . += "Alt-click on [src] to wear it casually." - if (has_sensor == BROKEN_SENSORS) - . += "Its sensors appear to be shorted out." - else if(has_sensor > NO_SENSORS) - switch(sensor_mode) - if(SENSOR_OFF) - . += "Its sensors appear to be disabled." - if(SENSOR_LIVING) - . += "Its binary life sensors appear to be enabled." - if(SENSOR_VITALS) - . += "Its vital tracker appears to be enabled." - if(SENSOR_COORDS) - . += "Its vital tracker and tracking beacon appear to be enabled." - if(attached_accessory) - . += "\A [attached_accessory] is attached to it." - -/obj/item/clothing/under/verb/toggle() - set name = "Adjust Suit Sensors" - set category = "Object" - set src in usr - var/mob/M = usr - if (istype(M, /mob/dead/)) - return - if (!can_use(M)) - return - if(has_sensor == LOCKED_SENSORS) - to_chat(usr, "The controls are locked.") - return 0 - if(has_sensor == BROKEN_SENSORS) - to_chat(usr, "The sensors have shorted out!") - return 0 - if(has_sensor <= NO_SENSORS) - to_chat(usr, "This suit does not have any sensors.") - return 0 - - var/list/modes = list("Off", "Binary vitals", "Exact vitals", "Tracking beacon") - var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes - if(get_dist(usr, src) > 1) - to_chat(usr, "You have moved too far away!") - return - sensor_mode = modes.Find(switchMode) - 1 - if (loc == usr) - switch(sensor_mode) - if(0) - to_chat(usr, "You disable your suit's remote sensing equipment.") - if(1) - to_chat(usr, "Your suit will now only report whether you are alive or dead.") - if(2) - to_chat(usr, "Your suit will now only report your exact vital lifesigns.") - if(3) - to_chat(usr, "Your suit will now report your exact vital lifesigns as well as your coordinate position.") - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.w_uniform == src) - H.update_suit_sensors() - -/obj/item/clothing/under/AltClick(mob/user) - if(..()) - return 1 - - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - if(attached_accessory) - remove_accessory(user) - else - rolldown() - -/obj/item/clothing/under/verb/jumpsuit_adjust() - set name = "Adjust Jumpsuit Style" - set category = null - set src in usr - rolldown() - -/obj/item/clothing/under/proc/rolldown() - if(!can_use(usr)) - return - if(!can_adjust) - to_chat(usr, "You cannot wear this suit any differently!") - return - if(toggle_jumpsuit_adjust()) - to_chat(usr, "You adjust the suit to wear it more casually.") - else - to_chat(usr, "You adjust the suit back to normal.") - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.update_inv_w_uniform() - H.update_body() - -/obj/item/clothing/under/proc/toggle_jumpsuit_adjust() - if(adjusted == DIGITIGRADE_STYLE) - return - adjusted = !adjusted - if(adjusted) - if(fitted != FEMALE_UNIFORM_TOP) - fitted = NO_FEMALE_UNIFORM - if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted (and also the arms, realistically) - body_parts_covered &= ~CHEST - body_parts_covered &= ~ARMS - else - fitted = initial(fitted) - if(!alt_covers_chest) - body_parts_covered |= CHEST - body_parts_covered |= ARMS - if(!LAZYLEN(damage_by_parts)) - return adjusted - for(var/zone in list(BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) // ugly check to make sure we don't reenable protection on a disabled part - if(damage_by_parts[zone] > limb_integrity) - for(var/part in zone2body_parts_covered(zone)) - body_parts_covered &= part - return adjusted - -/obj/item/clothing/under/rank - dying_key = DYE_REGISTRY_UNDER +/obj/item/clothing/under + name = "under" + icon = 'icons/obj/clothing/under/default.dmi' + worn_icon = 'icons/mob/clothing/under/default.dmi' + body_parts_covered = CHEST|GROIN|LEGS|ARMS + permeability_coefficient = 0.9 + slot_flags = ITEM_SLOT_ICLOTHING + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5) + equip_sound = 'sound/items/equip/jumpsuit_equip.ogg' + drop_sound = 'sound/items/handling/cloth_drop.ogg' + pickup_sound = 'sound/items/handling/cloth_pickup.ogg' + limb_integrity = 30 + var/fitted = FEMALE_UNIFORM_FULL // For use in alternate clothing styles for women + var/has_sensor = HAS_SENSORS // For the crew computer + var/random_sensor = TRUE + var/sensor_mode = NO_SENSORS + var/can_adjust = TRUE + var/adjusted = NORMAL_STYLE + var/alt_covers_chest = FALSE // for adjusted/rolled-down jumpsuits, FALSE = exposes chest and arms, TRUE = exposes arms only + var/obj/item/clothing/accessory/attached_accessory + var/mutable_appearance/accessory_overlay + var/mutantrace_variation = NO_MUTANTRACE_VARIATION //Are there special sprites for specific situations? Don't use this unless you need to. + var/freshly_laundered = FALSE + +/obj/item/clothing/under/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "uniformblood") + if(accessory_overlay) + . += accessory_overlay + +/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) + if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = I + C.use(1) + has_sensor = HAS_SENSORS + to_chat(user,"You repair the suit sensors on [src] with [C].") + return 1 + if(!attach_accessory(I, user)) + return ..() + +/obj/item/clothing/under/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_w_uniform() + if(has_sensor > NO_SENSORS) + has_sensor = BROKEN_SENSORS + +/obj/item/clothing/under/Initialize() + . = ..() + if(random_sensor) + //make the sensor mode favor higher levels, except coords. + sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS, SENSOR_COORDS) + +/obj/item/clothing/under/emp_act() + . = ..() + if(has_sensor > NO_SENSORS) + sensor_mode = pick(SENSOR_OFF, SENSOR_OFF, SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS) + if(ismob(loc)) + var/mob/M = loc + to_chat(M,"The sensors on the [src] change rapidly!") + +/obj/item/clothing/under/equipped(mob/user, slot) + ..() + if(adjusted) + adjusted = NORMAL_STYLE + fitted = initial(fitted) + if(!alt_covers_chest) + body_parts_covered |= CHEST + + if(mutantrace_variation && ishuman(user)) + var/mob/living/carbon/human/H = user + if(DIGITIGRADE in H.dna.species.species_traits) + adjusted = DIGITIGRADE_STYLE + H.update_inv_w_uniform() + + if(slot == ITEM_SLOT_ICLOTHING && freshly_laundered) + freshly_laundered = FALSE + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "fresh_laundry", /datum/mood_event/fresh_laundry) + + if(attached_accessory && slot != ITEM_SLOT_HANDS && ishuman(user)) + var/mob/living/carbon/human/H = user + attached_accessory.on_uniform_equip(src, user) + H.fan_hud_set_fandom() + if(attached_accessory.above_suit) + H.update_inv_wear_suit() + +/obj/item/clothing/under/dropped(mob/user) + if(attached_accessory) + attached_accessory.on_uniform_dropped(src, user) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.fan_hud_set_fandom() + if(attached_accessory.above_suit) + H.update_inv_wear_suit() + ..() + +/mob/living/carbon/human/update_suit_sensors() + . = ..() + update_sensor_list() + +/mob/living/carbon/human/proc/update_sensor_list() + var/obj/item/clothing/under/U = w_uniform + if(istype(U) && U.has_sensor > 0 && U.sensor_mode) + GLOB.suit_sensors_list |= src + else + GLOB.suit_sensors_list -= src + +/mob/living/carbon/human/dummy/update_sensor_list() + return + +/obj/item/clothing/under/proc/attach_accessory(obj/item/I, mob/user, notifyAttach = 1) + . = FALSE + if(istype(I, /obj/item/clothing/accessory)) + var/obj/item/clothing/accessory/A = I + if(attached_accessory) + if(user) + to_chat(user, "[src] already has an accessory.") + return + else + + if(!A.can_attach_accessory(src, user)) //Make sure the suit has a place to put the accessory. + return + if(user && !user.temporarilyRemoveItemFromInventory(I)) + return + if(!A.attach(src, user)) + return + + if(user && notifyAttach) + to_chat(user, "You attach [I] to [src].") + + var/accessory_color = attached_accessory.icon_state + accessory_overlay = mutable_appearance('icons/mob/clothing/accessories.dmi', "[accessory_color]") + accessory_overlay.alpha = attached_accessory.alpha + accessory_overlay.color = attached_accessory.color + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_w_uniform() + H.update_inv_wear_suit() + H.fan_hud_set_fandom() + + return TRUE + +/obj/item/clothing/under/proc/remove_accessory(mob/user) + if(!isliving(user)) + return + if(!can_use(user)) + return + + if(attached_accessory) + var/obj/item/clothing/accessory/A = attached_accessory + attached_accessory.detach(src, user) + if(user.put_in_hands(A)) + to_chat(user, "You detach [A] from [src].") + else + to_chat(user, "You detach [A] from [src] and it falls on the floor.") + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_w_uniform() + H.update_inv_wear_suit() + H.fan_hud_set_fandom() + + +/obj/item/clothing/under/examine(mob/user) + . = ..() + if(freshly_laundered) + . += "It looks fresh and clean." + if(can_adjust) + if(adjusted == ALT_STYLE) + . += "Alt-click on [src] to wear it normally." + else + . += "Alt-click on [src] to wear it casually." + if (has_sensor == BROKEN_SENSORS) + . += "Its sensors appear to be shorted out." + else if(has_sensor > NO_SENSORS) + switch(sensor_mode) + if(SENSOR_OFF) + . += "Its sensors appear to be disabled." + if(SENSOR_LIVING) + . += "Its binary life sensors appear to be enabled." + if(SENSOR_VITALS) + . += "Its vital tracker appears to be enabled." + if(SENSOR_COORDS) + . += "Its vital tracker and tracking beacon appear to be enabled." + if(attached_accessory) + . += "\A [attached_accessory] is attached to it." + +/obj/item/clothing/under/verb/toggle() + set name = "Adjust Suit Sensors" + set category = "Object" + set src in usr + var/mob/M = usr + if (istype(M, /mob/dead/)) + return + if (!can_use(M)) + return + if(has_sensor == LOCKED_SENSORS) + to_chat(usr, "The controls are locked.") + return 0 + if(has_sensor == BROKEN_SENSORS) + to_chat(usr, "The sensors have shorted out!") + return 0 + if(has_sensor <= NO_SENSORS) + to_chat(usr, "This suit does not have any sensors.") + return 0 + + var/list/modes = list("Off", "Binary vitals", "Exact vitals", "Tracking beacon") + var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes + if(get_dist(usr, src) > 1) + to_chat(usr, "You have moved too far away!") + return + sensor_mode = modes.Find(switchMode) - 1 + if (loc == usr) + switch(sensor_mode) + if(0) + to_chat(usr, "You disable your suit's remote sensing equipment.") + if(1) + to_chat(usr, "Your suit will now only report whether you are alive or dead.") + if(2) + to_chat(usr, "Your suit will now only report your exact vital lifesigns.") + if(3) + to_chat(usr, "Your suit will now report your exact vital lifesigns as well as your coordinate position.") + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.w_uniform == src) + H.update_suit_sensors() + +/obj/item/clothing/under/AltClick(mob/user) + if(..()) + return 1 + + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + if(attached_accessory) + remove_accessory(user) + else + rolldown() + +/obj/item/clothing/under/verb/jumpsuit_adjust() + set name = "Adjust Jumpsuit Style" + set category = null + set src in usr + rolldown() + +/obj/item/clothing/under/proc/rolldown() + if(!can_use(usr)) + return + if(!can_adjust) + to_chat(usr, "You cannot wear this suit any differently!") + return + if(toggle_jumpsuit_adjust()) + to_chat(usr, "You adjust the suit to wear it more casually.") + else + to_chat(usr, "You adjust the suit back to normal.") + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.update_inv_w_uniform() + H.update_body() + +/obj/item/clothing/under/proc/toggle_jumpsuit_adjust() + if(adjusted == DIGITIGRADE_STYLE) + return + adjusted = !adjusted + if(adjusted) + if(fitted != FEMALE_UNIFORM_TOP) + fitted = NO_FEMALE_UNIFORM + if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted (and also the arms, realistically) + body_parts_covered &= ~CHEST + body_parts_covered &= ~ARMS + else + fitted = initial(fitted) + if(!alt_covers_chest) + body_parts_covered |= CHEST + body_parts_covered |= ARMS + if(!LAZYLEN(damage_by_parts)) + return adjusted + for(var/zone in list(BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) // ugly check to make sure we don't reenable protection on a disabled part + if(damage_by_parts[zone] > limb_integrity) + for(var/part in zone2body_parts_covered(zone)) + body_parts_covered &= part + return adjusted + +/obj/item/clothing/under/rank + dying_key = DYE_REGISTRY_UNDER diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm index 97c4f515ed8..b47b6ee0c59 100644 --- a/code/modules/clothing/under/color.dm +++ b/code/modules/clothing/under/color.dm @@ -1,231 +1,231 @@ -/obj/item/clothing/under/color - desc = "A standard issue colored jumpsuit. Variety is the spice of life!" - dying_key = DYE_REGISTRY_UNDER - icon = 'icons/obj/clothing/under/color.dmi' - worn_icon = 'icons/mob/clothing/under/color.dmi' - -/obj/item/clothing/under/color/jumpskirt - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/color/random - icon_state = "random_jumpsuit" - -/obj/item/clothing/under/color/random/Initialize() - ..() - var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - typesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/glorf - /obj/item/clothing/under/color/black/ghost) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) //or else you end up with naked assistants running around everywhere... - else - new C(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/clothing/under/color/jumpskirt/random - icon_state = "random_jumpsuit" //Skirt variant needed - -/obj/item/clothing/under/color/jumpskirt/random/Initialize() - ..() - var/obj/item/clothing/under/color/jumpskirt/C = pick(subtypesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/jumpskirt/random) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) - else - new C(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/clothing/under/color/black - name = "black jumpsuit" - icon_state = "black" - inhand_icon_state = "bl_suit" - resistance_flags = NONE - -/obj/item/clothing/under/color/jumpskirt/black - name = "black jumpskirt" - icon_state = "black_skirt" - inhand_icon_state = "bl_suit" - -/obj/item/clothing/under/color/black/ghost - item_flags = DROPDEL - -/obj/item/clothing/under/color/black/ghost/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT) - -/obj/item/clothing/under/color/grey - name = "grey jumpsuit" - desc = "A tasteful grey jumpsuit that reminds you of the good old days." - icon_state = "grey" - inhand_icon_state = "gy_suit" - -/obj/item/clothing/under/color/jumpskirt/grey - name = "grey jumpskirt" - desc = "A tasteful grey jumpskirt that reminds you of the good old days." - icon_state = "grey_skirt" - inhand_icon_state = "gy_suit" - -/obj/item/clothing/under/color/grey/glorf - name = "ancient jumpsuit" - desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade." - -/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - owner.forcesay(GLOB.hit_appends) - return 0 - -/obj/item/clothing/under/color/blue - name = "blue jumpsuit" - icon_state = "blue" - inhand_icon_state = "b_suit" - -/obj/item/clothing/under/color/jumpskirt/blue - name = "blue jumpskirt" - icon_state = "blue_skirt" - inhand_icon_state = "b_suit" - -/obj/item/clothing/under/color/green - name = "green jumpsuit" - icon_state = "green" - inhand_icon_state = "g_suit" - -/obj/item/clothing/under/color/jumpskirt/green - name = "green jumpskirt" - icon_state = "green_skirt" - inhand_icon_state = "g_suit" - -/obj/item/clothing/under/color/orange - name = "orange jumpsuit" - desc = "Don't wear this near paranoid security officers." - icon_state = "orange" - inhand_icon_state = "o_suit" - -/obj/item/clothing/under/color/jumpskirt/orange - name = "orange jumpskirt" - icon_state = "orange_skirt" - inhand_icon_state = "o_suit" - -/obj/item/clothing/under/color/pink - name = "pink jumpsuit" - icon_state = "pink" - desc = "Just looking at this makes you feel fabulous." - inhand_icon_state = "p_suit" - -/obj/item/clothing/under/color/jumpskirt/pink - name = "pink jumpskirt" - icon_state = "pink_skirt" - inhand_icon_state = "p_suit" - -/obj/item/clothing/under/color/red - name = "red jumpsuit" - icon_state = "red" - inhand_icon_state = "r_suit" - -/obj/item/clothing/under/color/jumpskirt/red - name = "red jumpskirt" - icon_state = "red_skirt" - inhand_icon_state = "r_suit" - -/obj/item/clothing/under/color/white - name = "white jumpsuit" - icon_state = "white" - inhand_icon_state = "w_suit" - -/obj/item/clothing/under/color/jumpskirt/white - name = "white jumpskirt" - icon_state = "white_skirt" - inhand_icon_state = "w_suit" - -/obj/item/clothing/under/color/yellow - name = "yellow jumpsuit" - icon_state = "yellow" - inhand_icon_state = "y_suit" - -/obj/item/clothing/under/color/jumpskirt/yellow - name = "yellow jumpskirt" - icon_state = "yellow_skirt" - inhand_icon_state = "y_suit" - -/obj/item/clothing/under/color/darkblue - name = "darkblue jumpsuit" - icon_state = "darkblue" - inhand_icon_state = "b_suit" - -/obj/item/clothing/under/color/jumpskirt/darkblue - name = "darkblue jumpskirt" - icon_state = "darkblue_skirt" - inhand_icon_state = "b_suit" - -/obj/item/clothing/under/color/teal - name = "teal jumpsuit" - icon_state = "teal" - inhand_icon_state = "b_suit" - -/obj/item/clothing/under/color/jumpskirt/teal - name = "teal jumpskirt" - icon_state = "teal_skirt" - inhand_icon_state = "b_suit" - - -/obj/item/clothing/under/color/lightpurple - name = "purple jumpsuit" - icon_state = "lightpurple" - inhand_icon_state = "p_suit" - -/obj/item/clothing/under/color/jumpskirt/lightpurple - name = "lightpurple jumpskirt" - icon_state = "lightpurple_skirt" - inhand_icon_state = "p_suit" - -/obj/item/clothing/under/color/darkgreen - name = "darkgreen jumpsuit" - icon_state = "darkgreen" - inhand_icon_state = "g_suit" - -/obj/item/clothing/under/color/jumpskirt/darkgreen - name = "darkgreen jumpskirt" - icon_state = "darkgreen_skirt" - inhand_icon_state = "g_suit" - -/obj/item/clothing/under/color/lightbrown - name = "lightbrown jumpsuit" - icon_state = "lightbrown" - inhand_icon_state = "lb_suit" - -/obj/item/clothing/under/color/jumpskirt/lightbrown - name = "lightbrown jumpskirt" - icon_state = "lightbrown_skirt" - inhand_icon_state = "lb_suit" - -/obj/item/clothing/under/color/brown - name = "brown jumpsuit" - icon_state = "brown" - inhand_icon_state = "lb_suit" - -/obj/item/clothing/under/color/jumpskirt/brown - name = "brown jumpskirt" - icon_state = "brown_skirt" - inhand_icon_state = "lb_suit" - -/obj/item/clothing/under/color/maroon - name = "maroon jumpsuit" - icon_state = "maroon" - inhand_icon_state = "r_suit" - -/obj/item/clothing/under/color/jumpskirt/maroon - name = "maroon jumpskirt" - icon_state = "maroon_skirt" - inhand_icon_state = "r_suit" - -/obj/item/clothing/under/color/rainbow - name = "rainbow jumpsuit" - desc = "A multi-colored jumpsuit!" - icon_state = "rainbow" - inhand_icon_state = "rainbow" - can_adjust = FALSE - -/obj/item/clothing/under/color/jumpskirt/rainbow - name = "rainbow jumpskirt" - desc = "A multi-colored jumpskirt!" - icon_state = "rainbow_skirt" - inhand_icon_state = "rainbow" - can_adjust = FALSE +/obj/item/clothing/under/color + desc = "A standard issue colored jumpsuit. Variety is the spice of life!" + dying_key = DYE_REGISTRY_UNDER + icon = 'icons/obj/clothing/under/color.dmi' + worn_icon = 'icons/mob/clothing/under/color.dmi' + +/obj/item/clothing/under/color/jumpskirt + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/color/random + icon_state = "random_jumpsuit" + +/obj/item/clothing/under/color/random/Initialize() + ..() + var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - typesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/glorf - /obj/item/clothing/under/color/black/ghost) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) //or else you end up with naked assistants running around everywhere... + else + new C(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/clothing/under/color/jumpskirt/random + icon_state = "random_jumpsuit" //Skirt variant needed + +/obj/item/clothing/under/color/jumpskirt/random/Initialize() + ..() + var/obj/item/clothing/under/color/jumpskirt/C = pick(subtypesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/jumpskirt/random) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) + else + new C(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/clothing/under/color/black + name = "black jumpsuit" + icon_state = "black" + inhand_icon_state = "bl_suit" + resistance_flags = NONE + +/obj/item/clothing/under/color/jumpskirt/black + name = "black jumpskirt" + icon_state = "black_skirt" + inhand_icon_state = "bl_suit" + +/obj/item/clothing/under/color/black/ghost + item_flags = DROPDEL + +/obj/item/clothing/under/color/black/ghost/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT) + +/obj/item/clothing/under/color/grey + name = "grey jumpsuit" + desc = "A tasteful grey jumpsuit that reminds you of the good old days." + icon_state = "grey" + inhand_icon_state = "gy_suit" + +/obj/item/clothing/under/color/jumpskirt/grey + name = "grey jumpskirt" + desc = "A tasteful grey jumpskirt that reminds you of the good old days." + icon_state = "grey_skirt" + inhand_icon_state = "gy_suit" + +/obj/item/clothing/under/color/grey/glorf + name = "ancient jumpsuit" + desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade." + +/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + owner.forcesay(GLOB.hit_appends) + return 0 + +/obj/item/clothing/under/color/blue + name = "blue jumpsuit" + icon_state = "blue" + inhand_icon_state = "b_suit" + +/obj/item/clothing/under/color/jumpskirt/blue + name = "blue jumpskirt" + icon_state = "blue_skirt" + inhand_icon_state = "b_suit" + +/obj/item/clothing/under/color/green + name = "green jumpsuit" + icon_state = "green" + inhand_icon_state = "g_suit" + +/obj/item/clothing/under/color/jumpskirt/green + name = "green jumpskirt" + icon_state = "green_skirt" + inhand_icon_state = "g_suit" + +/obj/item/clothing/under/color/orange + name = "orange jumpsuit" + desc = "Don't wear this near paranoid security officers." + icon_state = "orange" + inhand_icon_state = "o_suit" + +/obj/item/clothing/under/color/jumpskirt/orange + name = "orange jumpskirt" + icon_state = "orange_skirt" + inhand_icon_state = "o_suit" + +/obj/item/clothing/under/color/pink + name = "pink jumpsuit" + icon_state = "pink" + desc = "Just looking at this makes you feel fabulous." + inhand_icon_state = "p_suit" + +/obj/item/clothing/under/color/jumpskirt/pink + name = "pink jumpskirt" + icon_state = "pink_skirt" + inhand_icon_state = "p_suit" + +/obj/item/clothing/under/color/red + name = "red jumpsuit" + icon_state = "red" + inhand_icon_state = "r_suit" + +/obj/item/clothing/under/color/jumpskirt/red + name = "red jumpskirt" + icon_state = "red_skirt" + inhand_icon_state = "r_suit" + +/obj/item/clothing/under/color/white + name = "white jumpsuit" + icon_state = "white" + inhand_icon_state = "w_suit" + +/obj/item/clothing/under/color/jumpskirt/white + name = "white jumpskirt" + icon_state = "white_skirt" + inhand_icon_state = "w_suit" + +/obj/item/clothing/under/color/yellow + name = "yellow jumpsuit" + icon_state = "yellow" + inhand_icon_state = "y_suit" + +/obj/item/clothing/under/color/jumpskirt/yellow + name = "yellow jumpskirt" + icon_state = "yellow_skirt" + inhand_icon_state = "y_suit" + +/obj/item/clothing/under/color/darkblue + name = "darkblue jumpsuit" + icon_state = "darkblue" + inhand_icon_state = "b_suit" + +/obj/item/clothing/under/color/jumpskirt/darkblue + name = "darkblue jumpskirt" + icon_state = "darkblue_skirt" + inhand_icon_state = "b_suit" + +/obj/item/clothing/under/color/teal + name = "teal jumpsuit" + icon_state = "teal" + inhand_icon_state = "b_suit" + +/obj/item/clothing/under/color/jumpskirt/teal + name = "teal jumpskirt" + icon_state = "teal_skirt" + inhand_icon_state = "b_suit" + + +/obj/item/clothing/under/color/lightpurple + name = "purple jumpsuit" + icon_state = "lightpurple" + inhand_icon_state = "p_suit" + +/obj/item/clothing/under/color/jumpskirt/lightpurple + name = "lightpurple jumpskirt" + icon_state = "lightpurple_skirt" + inhand_icon_state = "p_suit" + +/obj/item/clothing/under/color/darkgreen + name = "darkgreen jumpsuit" + icon_state = "darkgreen" + inhand_icon_state = "g_suit" + +/obj/item/clothing/under/color/jumpskirt/darkgreen + name = "darkgreen jumpskirt" + icon_state = "darkgreen_skirt" + inhand_icon_state = "g_suit" + +/obj/item/clothing/under/color/lightbrown + name = "lightbrown jumpsuit" + icon_state = "lightbrown" + inhand_icon_state = "lb_suit" + +/obj/item/clothing/under/color/jumpskirt/lightbrown + name = "lightbrown jumpskirt" + icon_state = "lightbrown_skirt" + inhand_icon_state = "lb_suit" + +/obj/item/clothing/under/color/brown + name = "brown jumpsuit" + icon_state = "brown" + inhand_icon_state = "lb_suit" + +/obj/item/clothing/under/color/jumpskirt/brown + name = "brown jumpskirt" + icon_state = "brown_skirt" + inhand_icon_state = "lb_suit" + +/obj/item/clothing/under/color/maroon + name = "maroon jumpsuit" + icon_state = "maroon" + inhand_icon_state = "r_suit" + +/obj/item/clothing/under/color/jumpskirt/maroon + name = "maroon jumpskirt" + icon_state = "maroon_skirt" + inhand_icon_state = "r_suit" + +/obj/item/clothing/under/color/rainbow + name = "rainbow jumpsuit" + desc = "A multi-colored jumpsuit!" + icon_state = "rainbow" + inhand_icon_state = "rainbow" + can_adjust = FALSE + +/obj/item/clothing/under/color/jumpskirt/rainbow + name = "rainbow jumpskirt" + desc = "A multi-colored jumpskirt!" + icon_state = "rainbow_skirt" + inhand_icon_state = "rainbow" + can_adjust = FALSE diff --git a/code/modules/clothing/under/jobs/engineering.dm b/code/modules/clothing/under/jobs/engineering.dm index 47cb5a0ec32..da5b463bf07 100644 --- a/code/modules/clothing/under/jobs/engineering.dm +++ b/code/modules/clothing/under/jobs/engineering.dm @@ -1,64 +1,64 @@ -//Contains: Engineering department jumpsuits - -/obj/item/clothing/under/rank/engineering - icon = 'icons/obj/clothing/under/engineering.dmi' - worn_icon = 'icons/mob/clothing/under/engineering.dmi' - -/obj/item/clothing/under/rank/engineering/chief_engineer - desc = "It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." - name = "chief engineer's jumpsuit" - icon_state = "chiefengineer" - inhand_icon_state = "gy_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40) - resistance_flags = NONE - -/obj/item/clothing/under/rank/engineering/chief_engineer/skirt - name = "chief engineer's jumpskirt" - desc = "It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." - icon_state = "chief_skirt" - inhand_icon_state = "gy_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/engineering/atmospheric_technician - desc = "It's a jumpsuit worn by atmospheric technicians. It has minor protection from fire." - name = "atmospheric technician's jumpsuit" - icon_state = "atmos" - inhand_icon_state = "atmos_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) - resistance_flags = NONE - -/obj/item/clothing/under/rank/engineering/atmospheric_technician/skirt - name = "atmospheric technician's jumpskirt" - desc = "It's a jumpskirt worn by atmospheric technicians. It has minor protection from fire." - icon_state = "atmos_skirt" - inhand_icon_state = "atmos_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/engineering/engineer - desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." - name = "engineer's jumpsuit" - icon_state = "engine" - inhand_icon_state = "engi_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) - resistance_flags = NONE - -/obj/item/clothing/under/rank/engineering/engineer/hazard - name = "engineer's hazard jumpsuit" - desc = "A high visibility jumpsuit made from heat and radiation resistant materials." - icon_state = "hazard" - inhand_icon_state = "suit-orange" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/engineering/engineer/skirt - name = "engineer's jumpskirt" - desc = "It's an orange high visibility jumpskirt worn by engineers. It has minor radiation shielding." - icon_state = "engine_skirt" - inhand_icon_state = "engi_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - +//Contains: Engineering department jumpsuits + +/obj/item/clothing/under/rank/engineering + icon = 'icons/obj/clothing/under/engineering.dmi' + worn_icon = 'icons/mob/clothing/under/engineering.dmi' + +/obj/item/clothing/under/rank/engineering/chief_engineer + desc = "It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." + name = "chief engineer's jumpsuit" + icon_state = "chiefengineer" + inhand_icon_state = "gy_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40) + resistance_flags = NONE + +/obj/item/clothing/under/rank/engineering/chief_engineer/skirt + name = "chief engineer's jumpskirt" + desc = "It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." + icon_state = "chief_skirt" + inhand_icon_state = "gy_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/engineering/atmospheric_technician + desc = "It's a jumpsuit worn by atmospheric technicians. It has minor protection from fire." + name = "atmospheric technician's jumpsuit" + icon_state = "atmos" + inhand_icon_state = "atmos_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) + resistance_flags = NONE + +/obj/item/clothing/under/rank/engineering/atmospheric_technician/skirt + name = "atmospheric technician's jumpskirt" + desc = "It's a jumpskirt worn by atmospheric technicians. It has minor protection from fire." + icon_state = "atmos_skirt" + inhand_icon_state = "atmos_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/engineering/engineer + desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." + name = "engineer's jumpsuit" + icon_state = "engine" + inhand_icon_state = "engi_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) + resistance_flags = NONE + +/obj/item/clothing/under/rank/engineering/engineer/hazard + name = "engineer's hazard jumpsuit" + desc = "A high visibility jumpsuit made from heat and radiation resistant materials." + icon_state = "hazard" + inhand_icon_state = "suit-orange" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/engineering/engineer/skirt + name = "engineer's jumpskirt" + desc = "It's an orange high visibility jumpskirt worn by engineers. It has minor radiation shielding." + icon_state = "engine_skirt" + inhand_icon_state = "engi_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm index e0283d62b75..6eb25601432 100644 --- a/code/modules/clothing/under/jobs/security.dm +++ b/code/modules/clothing/under/jobs/security.dm @@ -1,238 +1,238 @@ -/* - * Contains: - * Security - * Detective - * Navy uniforms - */ - -/* - * Security - */ - -/obj/item/clothing/under/rank/security - icon = 'icons/obj/clothing/under/security.dmi' - worn_icon = 'icons/mob/clothing/under/security.dmi' - -/obj/item/clothing/under/rank/security/officer - name = "security jumpsuit" - desc = "A tactical security jumpsuit for officers complete with Nanotrasen belt buckle." - icon_state = "rsecurity" - inhand_icon_state = "r_suit" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30, "wound" = 10) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = SENSOR_COORDS - random_sensor = FALSE - -/obj/item/clothing/under/rank/security/officer/grey - name = "grey security jumpsuit" - desc = "A tactical relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." - icon_state = "security" - inhand_icon_state = "gy_suit" - -/obj/item/clothing/under/rank/security/officer/skirt - name = "security jumpskirt" - desc = "A \"tactical\" security jumpsuit with the legs replaced by a skirt." - icon_state = "secskirt" - inhand_icon_state = "r_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE //you know now that i think of it if you adjust the skirt and the sprite disappears isn't that just like flashing everyone - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/officer/blueshirt - name = "blue shirt and tie" - desc = "I'm a little busy right now, Calhoun." - icon_state = "blueshift" - inhand_icon_state = "blueshift" - can_adjust = FALSE - -/obj/item/clothing/under/rank/security/officer/formal - name = "security officer's formal uniform" - desc = "The latest in fashionable security outfits." - icon_state = "officerblueclothes" - inhand_icon_state = "officerblueclothes" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/security/constable - name = "constable outfit" - desc = "A british looking outfit." - icon_state = "constable" - inhand_icon_state = "constable" - can_adjust = FALSE - custom_price = 200 - -/obj/item/clothing/under/rank/security/warden - name = "security suit" - desc = "A formal security suit for officers complete with Nanotrasen belt buckle." - icon_state = "rwarden" - inhand_icon_state = "r_suit" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30, "wound" = 10) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -/obj/item/clothing/under/rank/security/warden/grey - name = "grey security suit" - desc = "A formal relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." - icon_state = "warden" - inhand_icon_state = "gy_suit" - -/obj/item/clothing/under/rank/security/warden/skirt - name = "warden's suitskirt" - desc = "A formal security suitskirt for officers complete with Nanotrasen belt buckle." - icon_state = "rwarden_skirt" - inhand_icon_state = "r_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/warden/formal - desc = "The insignia on this uniform tells you that this uniform belongs to the Warden." - name = "warden's formal uniform" - icon_state = "wardenblueclothes" - inhand_icon_state = "wardenblueclothes" - alt_covers_chest = TRUE - -/* - * Detective - */ -/obj/item/clothing/under/rank/security/detective - name = "hard-worn suit" - desc = "Someone who wears this means business." - icon_state = "detective" - inhand_icon_state = "det" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30, "wound" = 10) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -/obj/item/clothing/under/rank/security/detective/skirt - name = "detective's suitskirt" - desc = "Someone who wears this means business." - icon_state = "detective_skirt" - inhand_icon_state = "det" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/detective/grey - name = "noir suit" - desc = "A hard-boiled private investigator's grey suit, complete with tie clip." - icon_state = "greydet" - inhand_icon_state = "greydet" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/security/detective/grey/skirt - name = "noir suitskirt" - desc = "A hard-boiled private investigator's grey suitskirt, complete with tie clip." - icon_state = "greydet_skirt" - inhand_icon_state = "greydet" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/* - * Head of Security - */ -/obj/item/clothing/under/rank/security/head_of_security - name = "head of security's jumpsuit" - desc = "A security jumpsuit decorated for those few with the dedication to achieve the position of Head of Security." - icon_state = "rhos" - inhand_icon_state = "r_suit" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "wound" = 10) - strip_delay = 60 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -/obj/item/clothing/under/rank/security/head_of_security/skirt - name = "head of security's jumpskirt" - desc = "A security jumpskirt decorated for those few with the dedication to achieve the position of Head of Security." - icon_state = "rhos_skirt" - inhand_icon_state = "r_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/head_of_security/grey - name = "head of security's grey jumpsuit" - desc = "There are old men, and there are bold men, but there are very few old, bold men." - icon_state = "hos" - inhand_icon_state = "gy_suit" - -/obj/item/clothing/under/rank/security/head_of_security/alt - name = "head of security's turtleneck" - desc = "A stylish alternative to the normal head of security jumpsuit, complete with tactical pants." - icon_state = "hosalt" - inhand_icon_state = "bl_suit" - -/obj/item/clothing/under/rank/security/head_of_security/alt/skirt - name = "head of security's turtleneck skirt" - desc = "A stylish alternative to the normal head of security jumpsuit, complete with a tactical skirt." - icon_state = "hosalt_skirt" - inhand_icon_state = "bl_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/head_of_security/parade - name = "head of security's parade uniform" - desc = "A male head of security's luxury-wear, for special occasions." - icon_state = "hos_parade_male" - inhand_icon_state = "r_suit" - can_adjust = FALSE - -/obj/item/clothing/under/rank/security/head_of_security/parade/female - name = "head of security's parade uniform" - desc = "A female head of security's luxury-wear, for special occasions." - icon_state = "hos_parade_fem" - inhand_icon_state = "r_suit" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/rank/security/head_of_security/formal - desc = "The insignia on this uniform tells you that this uniform belongs to the Head of Security." - name = "head of security's formal uniform" - icon_state = "hosblueclothes" - inhand_icon_state = "hosblueclothes" - alt_covers_chest = TRUE - -/* - *Spacepol - */ - -/obj/item/clothing/under/rank/security/officer/spacepol - name = "police uniform" - desc = "Space not controlled by megacorporations, planets, or pirates is under the jurisdiction of Spacepol." - icon_state = "spacepol" - inhand_icon_state = "spacepol" - can_adjust = FALSE - -/obj/item/clothing/under/rank/prisoner - name = "prison jumpsuit" - desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." - icon = 'icons/obj/clothing/under/security.dmi' - icon_state = "prisoner" - inhand_icon_state = "o_suit" - worn_icon = 'icons/mob/clothing/under/security.dmi' - has_sensor = LOCKED_SENSORS - sensor_mode = SENSOR_COORDS - random_sensor = FALSE - -/obj/item/clothing/under/rank/prisoner/skirt - name = "prison jumpskirt" - desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." - icon_state = "prisoner_skirt" - inhand_icon_state = "o_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/officer/beatcop - name = "space police uniform" - desc = "A police uniform often found in the lines at donut shops." - icon_state = "spacepolice_families" - inhand_icon_state = "spacepolice_families" - can_adjust = FALSE +/* + * Contains: + * Security + * Detective + * Navy uniforms + */ + +/* + * Security + */ + +/obj/item/clothing/under/rank/security + icon = 'icons/obj/clothing/under/security.dmi' + worn_icon = 'icons/mob/clothing/under/security.dmi' + +/obj/item/clothing/under/rank/security/officer + name = "security jumpsuit" + desc = "A tactical security jumpsuit for officers complete with Nanotrasen belt buckle." + icon_state = "rsecurity" + inhand_icon_state = "r_suit" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30, "wound" = 10) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = SENSOR_COORDS + random_sensor = FALSE + +/obj/item/clothing/under/rank/security/officer/grey + name = "grey security jumpsuit" + desc = "A tactical relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." + icon_state = "security" + inhand_icon_state = "gy_suit" + +/obj/item/clothing/under/rank/security/officer/skirt + name = "security jumpskirt" + desc = "A \"tactical\" security jumpsuit with the legs replaced by a skirt." + icon_state = "secskirt" + inhand_icon_state = "r_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE //you know now that i think of it if you adjust the skirt and the sprite disappears isn't that just like flashing everyone + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/officer/blueshirt + name = "blue shirt and tie" + desc = "I'm a little busy right now, Calhoun." + icon_state = "blueshift" + inhand_icon_state = "blueshift" + can_adjust = FALSE + +/obj/item/clothing/under/rank/security/officer/formal + name = "security officer's formal uniform" + desc = "The latest in fashionable security outfits." + icon_state = "officerblueclothes" + inhand_icon_state = "officerblueclothes" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/security/constable + name = "constable outfit" + desc = "A british looking outfit." + icon_state = "constable" + inhand_icon_state = "constable" + can_adjust = FALSE + custom_price = 200 + +/obj/item/clothing/under/rank/security/warden + name = "security suit" + desc = "A formal security suit for officers complete with Nanotrasen belt buckle." + icon_state = "rwarden" + inhand_icon_state = "r_suit" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30, "wound" = 10) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +/obj/item/clothing/under/rank/security/warden/grey + name = "grey security suit" + desc = "A formal relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." + icon_state = "warden" + inhand_icon_state = "gy_suit" + +/obj/item/clothing/under/rank/security/warden/skirt + name = "warden's suitskirt" + desc = "A formal security suitskirt for officers complete with Nanotrasen belt buckle." + icon_state = "rwarden_skirt" + inhand_icon_state = "r_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/warden/formal + desc = "The insignia on this uniform tells you that this uniform belongs to the Warden." + name = "warden's formal uniform" + icon_state = "wardenblueclothes" + inhand_icon_state = "wardenblueclothes" + alt_covers_chest = TRUE + +/* + * Detective + */ +/obj/item/clothing/under/rank/security/detective + name = "hard-worn suit" + desc = "Someone who wears this means business." + icon_state = "detective" + inhand_icon_state = "det" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30, "wound" = 10) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +/obj/item/clothing/under/rank/security/detective/skirt + name = "detective's suitskirt" + desc = "Someone who wears this means business." + icon_state = "detective_skirt" + inhand_icon_state = "det" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/detective/grey + name = "noir suit" + desc = "A hard-boiled private investigator's grey suit, complete with tie clip." + icon_state = "greydet" + inhand_icon_state = "greydet" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/security/detective/grey/skirt + name = "noir suitskirt" + desc = "A hard-boiled private investigator's grey suitskirt, complete with tie clip." + icon_state = "greydet_skirt" + inhand_icon_state = "greydet" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/* + * Head of Security + */ +/obj/item/clothing/under/rank/security/head_of_security + name = "head of security's jumpsuit" + desc = "A security jumpsuit decorated for those few with the dedication to achieve the position of Head of Security." + icon_state = "rhos" + inhand_icon_state = "r_suit" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "wound" = 10) + strip_delay = 60 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +/obj/item/clothing/under/rank/security/head_of_security/skirt + name = "head of security's jumpskirt" + desc = "A security jumpskirt decorated for those few with the dedication to achieve the position of Head of Security." + icon_state = "rhos_skirt" + inhand_icon_state = "r_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/head_of_security/grey + name = "head of security's grey jumpsuit" + desc = "There are old men, and there are bold men, but there are very few old, bold men." + icon_state = "hos" + inhand_icon_state = "gy_suit" + +/obj/item/clothing/under/rank/security/head_of_security/alt + name = "head of security's turtleneck" + desc = "A stylish alternative to the normal head of security jumpsuit, complete with tactical pants." + icon_state = "hosalt" + inhand_icon_state = "bl_suit" + +/obj/item/clothing/under/rank/security/head_of_security/alt/skirt + name = "head of security's turtleneck skirt" + desc = "A stylish alternative to the normal head of security jumpsuit, complete with a tactical skirt." + icon_state = "hosalt_skirt" + inhand_icon_state = "bl_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/head_of_security/parade + name = "head of security's parade uniform" + desc = "A male head of security's luxury-wear, for special occasions." + icon_state = "hos_parade_male" + inhand_icon_state = "r_suit" + can_adjust = FALSE + +/obj/item/clothing/under/rank/security/head_of_security/parade/female + name = "head of security's parade uniform" + desc = "A female head of security's luxury-wear, for special occasions." + icon_state = "hos_parade_fem" + inhand_icon_state = "r_suit" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/rank/security/head_of_security/formal + desc = "The insignia on this uniform tells you that this uniform belongs to the Head of Security." + name = "head of security's formal uniform" + icon_state = "hosblueclothes" + inhand_icon_state = "hosblueclothes" + alt_covers_chest = TRUE + +/* + *Spacepol + */ + +/obj/item/clothing/under/rank/security/officer/spacepol + name = "police uniform" + desc = "Space not controlled by megacorporations, planets, or pirates is under the jurisdiction of Spacepol." + icon_state = "spacepol" + inhand_icon_state = "spacepol" + can_adjust = FALSE + +/obj/item/clothing/under/rank/prisoner + name = "prison jumpsuit" + desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." + icon = 'icons/obj/clothing/under/security.dmi' + icon_state = "prisoner" + inhand_icon_state = "o_suit" + worn_icon = 'icons/mob/clothing/under/security.dmi' + has_sensor = LOCKED_SENSORS + sensor_mode = SENSOR_COORDS + random_sensor = FALSE + +/obj/item/clothing/under/rank/prisoner/skirt + name = "prison jumpskirt" + desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." + icon_state = "prisoner_skirt" + inhand_icon_state = "o_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/officer/beatcop + name = "space police uniform" + desc = "A police uniform often found in the lines at donut shops." + icon_state = "spacepolice_families" + inhand_icon_state = "spacepolice_families" + can_adjust = FALSE diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 81130063f9e..b02557aa021 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -1,158 +1,158 @@ -/obj/item/clothing/under/misc - icon = 'icons/obj/clothing/under/misc.dmi' - worn_icon = 'icons/mob/clothing/under/misc.dmi' - -/obj/item/clothing/under/misc/pj - name = "\improper PJs" - desc = "A comfy set of sleepwear, for taking naps or being lazy instead of working." - can_adjust = FALSE - inhand_icon_state = "w_suit" - -/obj/item/clothing/under/misc/pj/red - icon_state = "red_pyjamas" - -/obj/item/clothing/under/misc/pj/blue - icon_state = "blue_pyjamas" - -/obj/item/clothing/under/misc/patriotsuit - name = "Patriotic Suit" - desc = "Motorcycle not included." - icon_state = "ek" - inhand_icon_state = "ek" - can_adjust = FALSE - -/obj/item/clothing/under/misc/mailman - name = "mailman's jumpsuit" - desc = "'Special delivery!'" - icon_state = "mailman" - inhand_icon_state = "b_suit" - -/obj/item/clothing/under/misc/psyche - name = "psychedelic jumpsuit" - desc = "Groovy!" - icon_state = "psyche" - inhand_icon_state = "p_suit" - -/obj/item/clothing/under/misc/vice_officer - name = "vice officer's jumpsuit" - desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." - icon_state = "vice" - inhand_icon_state = "gy_suit" - can_adjust = FALSE - -/obj/item/clothing/under/misc/adminsuit - name = "administrative cybernetic jumpsuit" - icon = 'icons/obj/clothing/under/syndicate.dmi' - icon_state = "syndicate" - inhand_icon_state = "bl_suit" - worn_icon = 'icons/mob/clothing/under/syndicate.dmi' - desc = "A cybernetically enhanced jumpsuit used for administrative duties." - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - can_adjust = FALSE - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/under/misc/burial - name = "burial garments" - desc = "Traditional burial garments from the early 22nd century." - icon_state = "burial" - inhand_icon_state = "burial" - can_adjust = FALSE - has_sensor = NO_SENSORS - -/obj/item/clothing/under/misc/overalls - name = "laborer's overalls" - desc = "A set of durable overalls for getting the job done." - icon_state = "overalls" - inhand_icon_state = "lb_suit" - can_adjust = FALSE - custom_price = 60 - -/obj/item/clothing/under/misc/assistantformal - name = "assistant's formal uniform" - desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." - icon_state = "assistant_formal" - inhand_icon_state = "gy_suit" - can_adjust = FALSE - -/obj/item/clothing/under/plasmaman - name = "plasma envirosuit" - desc = "A special containment suit that allows plasma-based lifeforms to exist safely in an oxygenated environment, and automatically extinguishes them in a crisis. Despite being airtight, it's not spaceworthy." - icon_state = "plasmaman" - inhand_icon_state = "plasmaman" - icon = 'icons/obj/clothing/under/plasmaman.dmi' - worn_icon = 'icons/mob/clothing/under/plasmaman.dmi' - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - can_adjust = FALSE - strip_delay = 80 - var/next_extinguish = 0 - var/extinguish_cooldown = 100 - var/extinguishes_left = 5 - - -/obj/item/clothing/under/plasmaman/examine(mob/user) - . = ..() - . += "There are [extinguishes_left] extinguisher charges left in this suit." - -/obj/item/clothing/under/plasmaman/proc/Extinguish(mob/living/carbon/human/H) - if(!istype(H)) - return - - if(H.on_fire) - if(extinguishes_left) - if(next_extinguish > world.time) - return - next_extinguish = world.time + extinguish_cooldown - extinguishes_left-- - H.visible_message("[H]'s suit automatically extinguishes [H.p_them()]!","Your suit automatically extinguishes you.") - H.ExtinguishMob() - new /obj/effect/particle_effect/water(get_turf(H)) - return 0 - -/obj/item/clothing/under/plasmaman/attackby(obj/item/E, mob/user, params) - ..() - if (istype(E, /obj/item/extinguisher_refill)) - if (extinguishes_left == 5) - to_chat(user, "The inbuilt extinguisher is full.") - else - extinguishes_left = 5 - to_chat(user, "You refill the suit's built-in extinguisher, using up the cartridge.") - qdel(E) - -/obj/item/extinguisher_refill - name = "envirosuit extinguisher cartridge" - desc = "A cartridge loaded with a compressed extinguisher mix, used to refill the automatic extinguisher on plasma envirosuits." - icon_state = "plasmarefill" - icon = 'icons/obj/device.dmi' - -/obj/item/clothing/under/misc/durathread - name = "durathread jumpsuit" - desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "durathread" - inhand_icon_state = "durathread" - can_adjust = FALSE - armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5) - -/obj/item/clothing/under/misc/bouncer - name = "bouncer uniform" - desc = "A uniform made from a little bit more resistant fibers, makes you seem like a cool guy." - icon_state = "bouncer" - inhand_icon_state = "bouncer" - can_adjust = FALSE - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - -/obj/item/clothing/under/misc/coordinator - name = "coordinator jumpsuit" - desc = "A jumpsuit made by party people, from party people, for party people." - icon = 'icons/obj/clothing/under/captain.dmi' - worn_icon = 'icons/mob/clothing/under/captain.dmi' - icon_state = "captain_parade" - inhand_icon_state = "by_suit" - can_adjust = FALSE +/obj/item/clothing/under/misc + icon = 'icons/obj/clothing/under/misc.dmi' + worn_icon = 'icons/mob/clothing/under/misc.dmi' + +/obj/item/clothing/under/misc/pj + name = "\improper PJs" + desc = "A comfy set of sleepwear, for taking naps or being lazy instead of working." + can_adjust = FALSE + inhand_icon_state = "w_suit" + +/obj/item/clothing/under/misc/pj/red + icon_state = "red_pyjamas" + +/obj/item/clothing/under/misc/pj/blue + icon_state = "blue_pyjamas" + +/obj/item/clothing/under/misc/patriotsuit + name = "Patriotic Suit" + desc = "Motorcycle not included." + icon_state = "ek" + inhand_icon_state = "ek" + can_adjust = FALSE + +/obj/item/clothing/under/misc/mailman + name = "mailman's jumpsuit" + desc = "'Special delivery!'" + icon_state = "mailman" + inhand_icon_state = "b_suit" + +/obj/item/clothing/under/misc/psyche + name = "psychedelic jumpsuit" + desc = "Groovy!" + icon_state = "psyche" + inhand_icon_state = "p_suit" + +/obj/item/clothing/under/misc/vice_officer + name = "vice officer's jumpsuit" + desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." + icon_state = "vice" + inhand_icon_state = "gy_suit" + can_adjust = FALSE + +/obj/item/clothing/under/misc/adminsuit + name = "administrative cybernetic jumpsuit" + icon = 'icons/obj/clothing/under/syndicate.dmi' + icon_state = "syndicate" + inhand_icon_state = "bl_suit" + worn_icon = 'icons/mob/clothing/under/syndicate.dmi' + desc = "A cybernetically enhanced jumpsuit used for administrative duties." + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + can_adjust = FALSE + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/under/misc/burial + name = "burial garments" + desc = "Traditional burial garments from the early 22nd century." + icon_state = "burial" + inhand_icon_state = "burial" + can_adjust = FALSE + has_sensor = NO_SENSORS + +/obj/item/clothing/under/misc/overalls + name = "laborer's overalls" + desc = "A set of durable overalls for getting the job done." + icon_state = "overalls" + inhand_icon_state = "lb_suit" + can_adjust = FALSE + custom_price = 60 + +/obj/item/clothing/under/misc/assistantformal + name = "assistant's formal uniform" + desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." + icon_state = "assistant_formal" + inhand_icon_state = "gy_suit" + can_adjust = FALSE + +/obj/item/clothing/under/plasmaman + name = "plasma envirosuit" + desc = "A special containment suit that allows plasma-based lifeforms to exist safely in an oxygenated environment, and automatically extinguishes them in a crisis. Despite being airtight, it's not spaceworthy." + icon_state = "plasmaman" + inhand_icon_state = "plasmaman" + icon = 'icons/obj/clothing/under/plasmaman.dmi' + worn_icon = 'icons/mob/clothing/under/plasmaman.dmi' + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + can_adjust = FALSE + strip_delay = 80 + var/next_extinguish = 0 + var/extinguish_cooldown = 100 + var/extinguishes_left = 5 + + +/obj/item/clothing/under/plasmaman/examine(mob/user) + . = ..() + . += "There are [extinguishes_left] extinguisher charges left in this suit." + +/obj/item/clothing/under/plasmaman/proc/Extinguish(mob/living/carbon/human/H) + if(!istype(H)) + return + + if(H.on_fire) + if(extinguishes_left) + if(next_extinguish > world.time) + return + next_extinguish = world.time + extinguish_cooldown + extinguishes_left-- + H.visible_message("[H]'s suit automatically extinguishes [H.p_them()]!","Your suit automatically extinguishes you.") + H.ExtinguishMob() + new /obj/effect/particle_effect/water(get_turf(H)) + return 0 + +/obj/item/clothing/under/plasmaman/attackby(obj/item/E, mob/user, params) + ..() + if (istype(E, /obj/item/extinguisher_refill)) + if (extinguishes_left == 5) + to_chat(user, "The inbuilt extinguisher is full.") + else + extinguishes_left = 5 + to_chat(user, "You refill the suit's built-in extinguisher, using up the cartridge.") + qdel(E) + +/obj/item/extinguisher_refill + name = "envirosuit extinguisher cartridge" + desc = "A cartridge loaded with a compressed extinguisher mix, used to refill the automatic extinguisher on plasma envirosuits." + icon_state = "plasmarefill" + icon = 'icons/obj/device.dmi' + +/obj/item/clothing/under/misc/durathread + name = "durathread jumpsuit" + desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "durathread" + inhand_icon_state = "durathread" + can_adjust = FALSE + armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5) + +/obj/item/clothing/under/misc/bouncer + name = "bouncer uniform" + desc = "A uniform made from a little bit more resistant fibers, makes you seem like a cool guy." + icon_state = "bouncer" + inhand_icon_state = "bouncer" + can_adjust = FALSE + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + +/obj/item/clothing/under/misc/coordinator + name = "coordinator jumpsuit" + desc = "A jumpsuit made by party people, from party people, for party people." + icon = 'icons/obj/clothing/under/captain.dmi' + worn_icon = 'icons/mob/clothing/under/captain.dmi' + icon_state = "captain_parade" + inhand_icon_state = "by_suit" + can_adjust = FALSE diff --git a/code/modules/clothing/under/pants.dm b/code/modules/clothing/under/pants.dm index 28aa362f85c..0f4995b9d4b 100644 --- a/code/modules/clothing/under/pants.dm +++ b/code/modules/clothing/under/pants.dm @@ -1,69 +1,69 @@ -/obj/item/clothing/under/pants - gender = PLURAL - body_parts_covered = GROIN|LEGS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - custom_price = 60 - icon = 'icons/obj/clothing/under/shorts_pants.dmi' - worn_icon = 'icons/mob/clothing/under/shorts_pants.dmi' - -/obj/item/clothing/under/pants/classicjeans - name = "classic jeans" - desc = "You feel cooler already." - icon_state = "jeansclassic" - -/obj/item/clothing/under/pants/mustangjeans - name = "Must Hang jeans" - desc = "Made in the finest space jeans factory this side of Alpha Centauri." - icon_state = "jeansmustang" - custom_price = 180 - -/obj/item/clothing/under/pants/blackjeans - name = "black jeans" - desc = "Only for those who can pull it off." - icon_state = "jeansblack" - -/obj/item/clothing/under/pants/youngfolksjeans - name = "Young Folks jeans" - desc = "For those tired of boring old jeans. Relive the passion of your youth!" - icon_state = "jeansyoungfolks" - -/obj/item/clothing/under/pants/white - name = "white pants" - desc = "Plain white pants. Boring." - icon_state = "whitepants" - -/obj/item/clothing/under/pants/red - name = "red pants" - desc = "Bright red pants. Overflowing with personality." - icon_state = "redpants" - -/obj/item/clothing/under/pants/black - name = "black pants" - desc = "These pants are dark, like your soul." - icon_state = "blackpants" - -/obj/item/clothing/under/pants/tan - name = "tan pants" - desc = "Some tan pants. You look like a white collar worker with these on." - icon_state = "tanpants" - -/obj/item/clothing/under/pants/track - name = "track pants" - desc = "A pair of track pants, for the athletic." - icon_state = "trackpants" - -/obj/item/clothing/under/pants/jeans - name = "jeans" - desc = "A nondescript pair of tough blue jeans." - icon_state = "jeans" - -/obj/item/clothing/under/pants/khaki - name = "khaki pants" - desc = "A pair of dust beige khaki pants." - icon_state = "khaki" - -/obj/item/clothing/under/pants/camo - name = "camo pants" - desc = "A pair of woodland camouflage pants. Probably not the best choice for a space station." - icon_state = "camopants" +/obj/item/clothing/under/pants + gender = PLURAL + body_parts_covered = GROIN|LEGS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + custom_price = 60 + icon = 'icons/obj/clothing/under/shorts_pants.dmi' + worn_icon = 'icons/mob/clothing/under/shorts_pants.dmi' + +/obj/item/clothing/under/pants/classicjeans + name = "classic jeans" + desc = "You feel cooler already." + icon_state = "jeansclassic" + +/obj/item/clothing/under/pants/mustangjeans + name = "Must Hang jeans" + desc = "Made in the finest space jeans factory this side of Alpha Centauri." + icon_state = "jeansmustang" + custom_price = 180 + +/obj/item/clothing/under/pants/blackjeans + name = "black jeans" + desc = "Only for those who can pull it off." + icon_state = "jeansblack" + +/obj/item/clothing/under/pants/youngfolksjeans + name = "Young Folks jeans" + desc = "For those tired of boring old jeans. Relive the passion of your youth!" + icon_state = "jeansyoungfolks" + +/obj/item/clothing/under/pants/white + name = "white pants" + desc = "Plain white pants. Boring." + icon_state = "whitepants" + +/obj/item/clothing/under/pants/red + name = "red pants" + desc = "Bright red pants. Overflowing with personality." + icon_state = "redpants" + +/obj/item/clothing/under/pants/black + name = "black pants" + desc = "These pants are dark, like your soul." + icon_state = "blackpants" + +/obj/item/clothing/under/pants/tan + name = "tan pants" + desc = "Some tan pants. You look like a white collar worker with these on." + icon_state = "tanpants" + +/obj/item/clothing/under/pants/track + name = "track pants" + desc = "A pair of track pants, for the athletic." + icon_state = "trackpants" + +/obj/item/clothing/under/pants/jeans + name = "jeans" + desc = "A nondescript pair of tough blue jeans." + icon_state = "jeans" + +/obj/item/clothing/under/pants/khaki + name = "khaki pants" + desc = "A pair of dust beige khaki pants." + icon_state = "khaki" + +/obj/item/clothing/under/pants/camo + name = "camo pants" + desc = "A pair of woodland camouflage pants. Probably not the best choice for a space station." + icon_state = "camopants" diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index 62e08a5c45b..e8f812f0e2c 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -1,34 +1,34 @@ -/obj/item/clothing/under/shorts - name = "athletic shorts" - desc = "95% Polyester, 5% Spandex!" - gender = PLURAL - body_parts_covered = GROIN - fitted = NO_FEMALE_UNIFORM - mutantrace_variation = MUTANTRACE_VARIATION - can_adjust = FALSE - icon = 'icons/obj/clothing/under/shorts_pants.dmi' - worn_icon = 'icons/mob/clothing/under/shorts_pants.dmi' - -/obj/item/clothing/under/shorts/red - name = "red athletic shorts" - icon_state = "redshorts" - -/obj/item/clothing/under/shorts/green - name = "green athletic shorts" - icon_state = "greenshorts" - -/obj/item/clothing/under/shorts/blue - name = "blue athletic shorts" - icon_state = "blueshorts" - -/obj/item/clothing/under/shorts/black - name = "black athletic shorts" - icon_state = "blackshorts" - -/obj/item/clothing/under/shorts/grey - name = "grey athletic shorts" - icon_state = "greyshorts" - -/obj/item/clothing/under/shorts/purple - name = "purple athletic shorts" - icon_state = "purpleshorts" +/obj/item/clothing/under/shorts + name = "athletic shorts" + desc = "95% Polyester, 5% Spandex!" + gender = PLURAL + body_parts_covered = GROIN + fitted = NO_FEMALE_UNIFORM + mutantrace_variation = MUTANTRACE_VARIATION + can_adjust = FALSE + icon = 'icons/obj/clothing/under/shorts_pants.dmi' + worn_icon = 'icons/mob/clothing/under/shorts_pants.dmi' + +/obj/item/clothing/under/shorts/red + name = "red athletic shorts" + icon_state = "redshorts" + +/obj/item/clothing/under/shorts/green + name = "green athletic shorts" + icon_state = "greenshorts" + +/obj/item/clothing/under/shorts/blue + name = "blue athletic shorts" + icon_state = "blueshorts" + +/obj/item/clothing/under/shorts/black + name = "black athletic shorts" + icon_state = "blackshorts" + +/obj/item/clothing/under/shorts/grey + name = "grey athletic shorts" + icon_state = "greyshorts" + +/obj/item/clothing/under/shorts/purple + name = "purple athletic shorts" + icon_state = "purpleshorts" diff --git a/code/modules/clothing/under/syndicate.dm b/code/modules/clothing/under/syndicate.dm index 54df91ee745..2b9bf8bc951 100644 --- a/code/modules/clothing/under/syndicate.dm +++ b/code/modules/clothing/under/syndicate.dm @@ -1,91 +1,91 @@ -/obj/item/clothing/under/syndicate - name = "tactical turtleneck" - desc = "A non-descript and slightly suspicious looking turtleneck with digital camouflage cargo pants." - icon_state = "syndicate" - inhand_icon_state = "bl_suit" - has_sensor = NO_SENSORS - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - alt_covers_chest = TRUE - icon = 'icons/obj/clothing/under/syndicate.dmi' - worn_icon = 'icons/mob/clothing/under/syndicate.dmi' - -/obj/item/clothing/under/syndicate/skirt - name = "tactical skirtleneck" - desc = "A non-descript and slightly suspicious looking skirtleneck." - icon_state = "syndicate_skirt" - inhand_icon_state = "bl_suit" - has_sensor = NO_SENSORS - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - alt_covers_chest = TRUE - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/bloodred - name = "blood-red sneaksuit" - desc = "It still counts as stealth if there are no witnesses." - icon_state = "bloodred_pajamas" - inhand_icon_state = "bl_suit" - armor = list("melee" = 10, "bullet" = 10, "laser" = 10,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 50, "acid" = 40) - resistance_flags = FIRE_PROOF | ACID_PROOF - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/bloodred/sleepytime - name = "blood-red pajamas" - desc = "Do operatives dream of nuclear sheep?" - icon_state = "bloodred_pajamas" - inhand_icon_state = "bl_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - -/obj/item/clothing/under/syndicate/tacticool - name = "tacticool turtleneck" - desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." - icon_state = "tactifool" - inhand_icon_state = "bl_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - -/obj/item/clothing/under/syndicate/tacticool/skirt - name = "tacticool skirtleneck" - desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." - icon_state = "tactifool_skirt" - inhand_icon_state = "bl_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/sniper - name = "Tactical turtleneck suit" - desc = "A double seamed tactical turtleneck disguised as a civilian grade silk suit. Intended for the most formal operator. The collar is really sharp." - icon = 'icons/obj/clothing/under/suits.dmi' - icon_state = "really_black_suit" - inhand_icon_state = "bl_suit" - worn_icon = 'icons/mob/clothing/under/suits.dmi' - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/camo - name = "camouflage fatigues" - desc = "A green military camouflage uniform." - icon_state = "camogreen" - inhand_icon_state = "g_suit" - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/soviet - name = "Ratnik 5 tracksuit" - desc = "Badly translated labels tell you to clean this in Vodka. Great for squatting in." - icon_state = "trackpants" - can_adjust = FALSE - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = NONE - -/obj/item/clothing/under/syndicate/combat - name = "combat uniform" - desc = "With a suit lined with this many pockets, you are ready to operate." - icon_state = "syndicate_combat" - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/rus_army - name = "advanced military tracksuit" - desc = "Military grade tracksuits for frontline squatting." - icon_state = "rus_under" - can_adjust = FALSE - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = NONE +/obj/item/clothing/under/syndicate + name = "tactical turtleneck" + desc = "A non-descript and slightly suspicious looking turtleneck with digital camouflage cargo pants." + icon_state = "syndicate" + inhand_icon_state = "bl_suit" + has_sensor = NO_SENSORS + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + alt_covers_chest = TRUE + icon = 'icons/obj/clothing/under/syndicate.dmi' + worn_icon = 'icons/mob/clothing/under/syndicate.dmi' + +/obj/item/clothing/under/syndicate/skirt + name = "tactical skirtleneck" + desc = "A non-descript and slightly suspicious looking skirtleneck." + icon_state = "syndicate_skirt" + inhand_icon_state = "bl_suit" + has_sensor = NO_SENSORS + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + alt_covers_chest = TRUE + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/bloodred + name = "blood-red sneaksuit" + desc = "It still counts as stealth if there are no witnesses." + icon_state = "bloodred_pajamas" + inhand_icon_state = "bl_suit" + armor = list("melee" = 10, "bullet" = 10, "laser" = 10,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 50, "acid" = 40) + resistance_flags = FIRE_PROOF | ACID_PROOF + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/bloodred/sleepytime + name = "blood-red pajamas" + desc = "Do operatives dream of nuclear sheep?" + icon_state = "bloodred_pajamas" + inhand_icon_state = "bl_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + +/obj/item/clothing/under/syndicate/tacticool + name = "tacticool turtleneck" + desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." + icon_state = "tactifool" + inhand_icon_state = "bl_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + +/obj/item/clothing/under/syndicate/tacticool/skirt + name = "tacticool skirtleneck" + desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." + icon_state = "tactifool_skirt" + inhand_icon_state = "bl_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/sniper + name = "Tactical turtleneck suit" + desc = "A double seamed tactical turtleneck disguised as a civilian grade silk suit. Intended for the most formal operator. The collar is really sharp." + icon = 'icons/obj/clothing/under/suits.dmi' + icon_state = "really_black_suit" + inhand_icon_state = "bl_suit" + worn_icon = 'icons/mob/clothing/under/suits.dmi' + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/camo + name = "camouflage fatigues" + desc = "A green military camouflage uniform." + icon_state = "camogreen" + inhand_icon_state = "g_suit" + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/soviet + name = "Ratnik 5 tracksuit" + desc = "Badly translated labels tell you to clean this in Vodka. Great for squatting in." + icon_state = "trackpants" + can_adjust = FALSE + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = NONE + +/obj/item/clothing/under/syndicate/combat + name = "combat uniform" + desc = "With a suit lined with this many pockets, you are ready to operate." + icon_state = "syndicate_combat" + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/rus_army + name = "advanced military tracksuit" + desc = "Military grade tracksuits for frontline squatting." + icon_state = "rus_under" + can_adjust = FALSE + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = NONE diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm index 5d0fff78a69..fe519703671 100644 --- a/code/modules/detectivework/detective_work.dm +++ b/code/modules/detectivework/detective_work.dm @@ -1,102 +1,102 @@ -//CONTAINS: Suit fibers and Detective's Scanning Computer - -/atom/proc/return_fingerprints() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.fingerprints - -/atom/proc/return_hiddenprints() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.hiddenprints - -/atom/proc/return_blood_DNA() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.blood_DNA - -/atom/proc/blood_DNA_length() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = length(D.blood_DNA) - -/atom/proc/return_fibers() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.fibers - -/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT - if(length(fingerprints)) - . = AddComponent(/datum/component/forensics, fingerprints) - -//Set ignoregloves to add prints irrespective of the mob having gloves on. -/atom/proc/add_fingerprint(mob/M, ignoregloves = FALSE) - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_fingerprint(M, ignoregloves) - -/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT - if(length(fibertext)) - . = AddComponent(/datum/component/forensics, null, null, null, fibertext) - -/atom/proc/add_fibers(mob/living/carbon/human/M) - var/old = 0 - if(M.gloves && istype(M.gloves, /obj/item/clothing)) - var/obj/item/clothing/gloves/G = M.gloves - old = length(G.return_blood_DNA()) - if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects - if(add_blood_DNA(G.return_blood_DNA()) && length(G.return_blood_DNA()) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody - G.transfer_blood-- - else if(M.bloody_hands > 1) - old = length(M.return_blood_DNA()) - if(add_blood_DNA(M.return_blood_DNA()) && length(M.return_blood_DNA()) > old) - M.bloody_hands-- - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_fibers(M) - -/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM - if(length(hiddenprints)) - . = AddComponent(/datum/component/forensics, null, hiddenprints) - -/atom/proc/add_hiddenprint(mob/M) - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_hiddenprint(M) - -/atom/proc/add_blood_DNA(list/dna) //ASSOC LIST DNA = BLOODTYPE - return FALSE - -/obj/add_blood_DNA(list/dna) - . = ..() - if(length(dna)) - . = AddComponent(/datum/component/forensics, null, null, dna) - -/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - . = ..() - transfer_blood = rand(2, 4) - -/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src - if(!B) - B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) - B.add_blood_DNA(blood_dna) //give blood info to the blood decal. - return TRUE //we bloodied the floor - -/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - if(wear_suit) - wear_suit.add_blood_DNA(blood_dna) - update_inv_wear_suit() - else if(w_uniform) - w_uniform.add_blood_DNA(blood_dna) - update_inv_w_uniform() - if(gloves) - var/obj/item/clothing/gloves/G = gloves - G.add_blood_DNA(blood_dna) - else if(length(blood_dna)) - AddComponent(/datum/component/forensics, null, null, blood_dna) - bloody_hands = rand(2, 4) - update_inv_gloves() //handles bloody hands overlays and updating - return TRUE - -/atom/proc/transfer_fingerprints_to(atom/A) - A.add_fingerprint_list(return_fingerprints()) - A.add_hiddenprint_list(return_hiddenprints()) - A.fingerprintslast = fingerprintslast +//CONTAINS: Suit fibers and Detective's Scanning Computer + +/atom/proc/return_fingerprints() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.fingerprints + +/atom/proc/return_hiddenprints() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.hiddenprints + +/atom/proc/return_blood_DNA() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.blood_DNA + +/atom/proc/blood_DNA_length() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = length(D.blood_DNA) + +/atom/proc/return_fibers() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.fibers + +/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT + if(length(fingerprints)) + . = AddComponent(/datum/component/forensics, fingerprints) + +//Set ignoregloves to add prints irrespective of the mob having gloves on. +/atom/proc/add_fingerprint(mob/M, ignoregloves = FALSE) + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_fingerprint(M, ignoregloves) + +/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT + if(length(fibertext)) + . = AddComponent(/datum/component/forensics, null, null, null, fibertext) + +/atom/proc/add_fibers(mob/living/carbon/human/M) + var/old = 0 + if(M.gloves && istype(M.gloves, /obj/item/clothing)) + var/obj/item/clothing/gloves/G = M.gloves + old = length(G.return_blood_DNA()) + if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects + if(add_blood_DNA(G.return_blood_DNA()) && length(G.return_blood_DNA()) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody + G.transfer_blood-- + else if(M.bloody_hands > 1) + old = length(M.return_blood_DNA()) + if(add_blood_DNA(M.return_blood_DNA()) && length(M.return_blood_DNA()) > old) + M.bloody_hands-- + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_fibers(M) + +/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM + if(length(hiddenprints)) + . = AddComponent(/datum/component/forensics, null, hiddenprints) + +/atom/proc/add_hiddenprint(mob/M) + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_hiddenprint(M) + +/atom/proc/add_blood_DNA(list/dna) //ASSOC LIST DNA = BLOODTYPE + return FALSE + +/obj/add_blood_DNA(list/dna) + . = ..() + if(length(dna)) + . = AddComponent(/datum/component/forensics, null, null, dna) + +/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + . = ..() + transfer_blood = rand(2, 4) + +/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src + if(!B) + B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) + B.add_blood_DNA(blood_dna) //give blood info to the blood decal. + return TRUE //we bloodied the floor + +/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + if(wear_suit) + wear_suit.add_blood_DNA(blood_dna) + update_inv_wear_suit() + else if(w_uniform) + w_uniform.add_blood_DNA(blood_dna) + update_inv_w_uniform() + if(gloves) + var/obj/item/clothing/gloves/G = gloves + G.add_blood_DNA(blood_dna) + else if(length(blood_dna)) + AddComponent(/datum/component/forensics, null, null, blood_dna) + bloody_hands = rand(2, 4) + update_inv_gloves() //handles bloody hands overlays and updating + return TRUE + +/atom/proc/transfer_fingerprints_to(atom/A) + A.add_fingerprint_list(return_fingerprints()) + A.add_hiddenprint_list(return_hiddenprints()) + A.fingerprintslast = fingerprintslast diff --git a/code/modules/detectivework/evidence.dm b/code/modules/detectivework/evidence.dm index b0689e3407f..ead5bd6309a 100644 --- a/code/modules/detectivework/evidence.dm +++ b/code/modules/detectivework/evidence.dm @@ -1,97 +1,97 @@ -//CONTAINS: Evidence bags - -/obj/item/evidencebag - name = "evidence bag" - desc = "An empty evidence bag." - icon = 'icons/obj/storage.dmi' - icon_state = "evidenceobj" - inhand_icon_state = "" - w_class = WEIGHT_CLASS_TINY - -/obj/item/evidencebag/afterattack(obj/item/I, mob/user,proximity) - . = ..() - if(!proximity || loc == I) - return - evidencebagEquip(I, user) - -/obj/item/evidencebag/attackby(obj/item/I, mob/user, params) - if(evidencebagEquip(I, user)) - return 1 - -/obj/item/evidencebag/handle_atom_del(atom/A) - cut_overlays() - w_class = initial(w_class) - icon_state = initial(icon_state) - desc = initial(desc) - -/obj/item/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user) - if(!istype(I) || I.anchored == 1) - return - - if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE) && SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE)) - to_chat(user, "No matter what way you try, you can't get [I] to fit inside [src].") - return TRUE //begone infinite storage ghosts, begone from me - - if(istype(I, /obj/item/evidencebag)) - to_chat(user, "You find putting an evidence bag in another evidence bag to be slightly absurd.") - return TRUE //now this is podracing - - if(loc in I.GetAllContents()) // fixes tg #39452, evidence bags could store their own location, causing I to be stored in the bag while being present inworld still, and able to be teleported when removed. - to_chat(user, "You find putting [I] in [src] while it's still inside it quite difficult!") - return - - if(I.w_class > WEIGHT_CLASS_NORMAL) - to_chat(user, "[I] won't fit in [src]!") - return - - if(contents.len) - to_chat(user, "[src] already has something inside it!") - return - - if(!isturf(I.loc)) //If it isn't on the floor. Do some checks to see if it's in our hands or a box. Otherwise give up. - if(SEND_SIGNAL(I.loc, COMSIG_CONTAINS_STORAGE)) //in a container. - SEND_SIGNAL(I.loc, COMSIG_TRY_STORAGE_TAKE, I, src) - if(!user.dropItemToGround(I)) - return - - user.visible_message("[user] puts [I] into [src].", "You put [I] inside [src].",\ - "You hear a rustle as someone puts something into a plastic bag.") - - icon_state = "evidence" - - var/mutable_appearance/in_evidence = new(I) - in_evidence.plane = FLOAT_PLANE - in_evidence.layer = FLOAT_LAYER - in_evidence.pixel_x = 0 - in_evidence.pixel_y = 0 - add_overlay(in_evidence) - add_overlay("evidence") //should look nicer for transparent stuff. not really that important, but hey. - - desc = "An evidence bag containing [I]. [I.desc]" - I.forceMove(src) - w_class = I.w_class - return 1 - -/obj/item/evidencebag/attack_self(mob/user) - if(contents.len) - var/obj/item/I = contents[1] - user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].",\ - "You hear someone rustle around in a plastic bag, and remove something.") - cut_overlays() //remove the overlays - user.put_in_hands(I) - w_class = WEIGHT_CLASS_TINY - icon_state = "evidenceobj" - desc = "An empty evidence bag." - - else - to_chat(user, "[src] is empty.") - icon_state = "evidenceobj" - return - -/obj/item/storage/box/evidence - name = "evidence bag box" - desc = "A box claiming to contain evidence bags." - -/obj/item/storage/box/evidence/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/evidencebag(src) +//CONTAINS: Evidence bags + +/obj/item/evidencebag + name = "evidence bag" + desc = "An empty evidence bag." + icon = 'icons/obj/storage.dmi' + icon_state = "evidenceobj" + inhand_icon_state = "" + w_class = WEIGHT_CLASS_TINY + +/obj/item/evidencebag/afterattack(obj/item/I, mob/user,proximity) + . = ..() + if(!proximity || loc == I) + return + evidencebagEquip(I, user) + +/obj/item/evidencebag/attackby(obj/item/I, mob/user, params) + if(evidencebagEquip(I, user)) + return 1 + +/obj/item/evidencebag/handle_atom_del(atom/A) + cut_overlays() + w_class = initial(w_class) + icon_state = initial(icon_state) + desc = initial(desc) + +/obj/item/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user) + if(!istype(I) || I.anchored == 1) + return + + if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE) && SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE)) + to_chat(user, "No matter what way you try, you can't get [I] to fit inside [src].") + return TRUE //begone infinite storage ghosts, begone from me + + if(istype(I, /obj/item/evidencebag)) + to_chat(user, "You find putting an evidence bag in another evidence bag to be slightly absurd.") + return TRUE //now this is podracing + + if(loc in I.GetAllContents()) // fixes tg #39452, evidence bags could store their own location, causing I to be stored in the bag while being present inworld still, and able to be teleported when removed. + to_chat(user, "You find putting [I] in [src] while it's still inside it quite difficult!") + return + + if(I.w_class > WEIGHT_CLASS_NORMAL) + to_chat(user, "[I] won't fit in [src]!") + return + + if(contents.len) + to_chat(user, "[src] already has something inside it!") + return + + if(!isturf(I.loc)) //If it isn't on the floor. Do some checks to see if it's in our hands or a box. Otherwise give up. + if(SEND_SIGNAL(I.loc, COMSIG_CONTAINS_STORAGE)) //in a container. + SEND_SIGNAL(I.loc, COMSIG_TRY_STORAGE_TAKE, I, src) + if(!user.dropItemToGround(I)) + return + + user.visible_message("[user] puts [I] into [src].", "You put [I] inside [src].",\ + "You hear a rustle as someone puts something into a plastic bag.") + + icon_state = "evidence" + + var/mutable_appearance/in_evidence = new(I) + in_evidence.plane = FLOAT_PLANE + in_evidence.layer = FLOAT_LAYER + in_evidence.pixel_x = 0 + in_evidence.pixel_y = 0 + add_overlay(in_evidence) + add_overlay("evidence") //should look nicer for transparent stuff. not really that important, but hey. + + desc = "An evidence bag containing [I]. [I.desc]" + I.forceMove(src) + w_class = I.w_class + return 1 + +/obj/item/evidencebag/attack_self(mob/user) + if(contents.len) + var/obj/item/I = contents[1] + user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].",\ + "You hear someone rustle around in a plastic bag, and remove something.") + cut_overlays() //remove the overlays + user.put_in_hands(I) + w_class = WEIGHT_CLASS_TINY + icon_state = "evidenceobj" + desc = "An empty evidence bag." + + else + to_chat(user, "[src] is empty.") + icon_state = "evidenceobj" + return + +/obj/item/storage/box/evidence + name = "evidence bag box" + desc = "A box claiming to contain evidence bags." + +/obj/item/storage/box/evidence/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/evidencebag(src) diff --git a/code/modules/detectivework/footprints_and_rag.dm b/code/modules/detectivework/footprints_and_rag.dm index ba6563d0657..e7e61f3e39b 100644 --- a/code/modules/detectivework/footprints_and_rag.dm +++ b/code/modules/detectivework/footprints_and_rag.dm @@ -1,45 +1,45 @@ - -/obj/item/clothing/gloves - var/transfer_blood = 0 - - -/obj/item/reagent_containers/glass/rag - name = "damp rag" - desc = "For cleaning up messes, you suppose." - w_class = WEIGHT_CLASS_TINY - icon = 'icons/obj/toy.dmi' - icon_state = "rag" - item_flags = NOBLUDGEON - reagent_flags = OPENCONTAINER - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list() - volume = 5 - spillable = FALSE - -/obj/item/reagent_containers/glass/rag/suicide_act(mob/user) - user.visible_message("[user] is smothering [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user,proximity) - . = ..() - if(!proximity) - return - if(iscarbon(A) && A.reagents && reagents.total_volume) - var/mob/living/carbon/C = A - var/reagentlist = pretty_string_from_reagent_list(reagents) - var/log_object = "containing [reagentlist]" - if(user.a_intent == INTENT_HARM && !C.is_mouth_covered()) - reagents.trans_to(C, reagents.total_volume, transfered_by = user, method = INGEST) - C.visible_message("[user] smothers \the [C] with \the [src]!", "[user] smothers you with \the [src]!", "You hear some struggling and muffled cries of surprise.") - log_combat(user, C, "smothered", src, log_object) - else - reagents.expose(C, TOUCH) - reagents.clear_reagents() - C.visible_message("[user] touches \the [C] with \the [src].") - log_combat(user, C, "touched", src, log_object) - - else if(istype(A) && (src in user)) - user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...") - if(do_after(user,30, target = A)) - user.visible_message("[user] finishes wiping off [A]!", "You finish wiping off [A].") - SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + +/obj/item/clothing/gloves + var/transfer_blood = 0 + + +/obj/item/reagent_containers/glass/rag + name = "damp rag" + desc = "For cleaning up messes, you suppose." + w_class = WEIGHT_CLASS_TINY + icon = 'icons/obj/toy.dmi' + icon_state = "rag" + item_flags = NOBLUDGEON + reagent_flags = OPENCONTAINER + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list() + volume = 5 + spillable = FALSE + +/obj/item/reagent_containers/glass/rag/suicide_act(mob/user) + user.visible_message("[user] is smothering [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user,proximity) + . = ..() + if(!proximity) + return + if(iscarbon(A) && A.reagents && reagents.total_volume) + var/mob/living/carbon/C = A + var/reagentlist = pretty_string_from_reagent_list(reagents) + var/log_object = "containing [reagentlist]" + if(user.a_intent == INTENT_HARM && !C.is_mouth_covered()) + reagents.trans_to(C, reagents.total_volume, transfered_by = user, method = INGEST) + C.visible_message("[user] smothers \the [C] with \the [src]!", "[user] smothers you with \the [src]!", "You hear some struggling and muffled cries of surprise.") + log_combat(user, C, "smothered", src, log_object) + else + reagents.expose(C, TOUCH) + reagents.clear_reagents() + C.visible_message("[user] touches \the [C] with \the [src].") + log_combat(user, C, "touched", src, log_object) + + else if(istype(A) && (src in user)) + user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...") + if(do_after(user,30, target = A)) + user.visible_message("[user] finishes wiping off [A]!", "You finish wiping off [A].") + SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index b4e40803199..5cf9de3be19 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -1,215 +1,215 @@ -//CONTAINS: Detective's Scanner - -// TODO: Split everything into easy to manage procs. - -/obj/item/detective_scanner - name = "forensic scanner" - desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." - icon = 'icons/obj/device.dmi' - icon_state = "forensicnew" - w_class = WEIGHT_CLASS_SMALL - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - var/scanning = 0 - var/list/log = list() - var/range = 8 - var/view_check = TRUE - var/forensicPrintCount = 0 - actions_types = list(/datum/action/item_action/display_detective_scan_results) - -/datum/action/item_action/display_detective_scan_results - name = "Display Forensic Scanner Results" - -/datum/action/item_action/display_detective_scan_results/Trigger() - var/obj/item/detective_scanner/scanner = target - if(istype(scanner)) - scanner.displayDetectiveScanResults(usr) - -/obj/item/detective_scanner/attack_self(mob/user) - if(log.len && !scanning) - scanning = 1 - to_chat(user, "Printing report, please wait...") - addtimer(CALLBACK(src, .proc/PrintReport), 100) - else - to_chat(user, "The scanner has no logs or is in use.") - -/obj/item/detective_scanner/attack(mob/living/M, mob/user) - return - -/obj/item/detective_scanner/proc/PrintReport() - // Create our paper - var/obj/item/paper/P = new(get_turf(src)) - - //This could be a global count like sec and med record printouts. See GLOB.data_core.medicalPrintCount AKA datacore.dm - var frNum = ++forensicPrintCount - - P.name = text("FR-[] 'Forensic Record'", frNum) - P.info = text("
                Forensic Record - (FR-[])


                ", frNum) - P.info += jointext(log, "
                ") - P.info += "
                Notes:
                " - P.update_icon() - - if(ismob(loc)) - var/mob/M = loc - M.put_in_hands(P) - to_chat(M, "Report printed. Log cleared.") - - // Clear the logs - log = list() - scanning = 0 - -/obj/item/detective_scanner/afterattack(atom/A, mob/user, params) - . = ..() - scan(A, user) - return FALSE - -/obj/item/detective_scanner/proc/scan(atom/A, mob/user) - set waitfor = 0 - if(!scanning) - // Can remotely scan objects and mobs. - if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user)) - return - - scanning = 1 - - user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.") - to_chat(user, "You scan \the [A]. The scanner is now analysing the results...") - - - // GATHER INFORMATION - - //Make our lists - var/list/fingerprints = list() - var/list/blood = A.return_blood_DNA() - var/list/fibers = A.return_fibers() - var/list/reagents = list() - - var/target_name = A.name - - // Start gathering - - if(ishuman(A)) - - var/mob/living/carbon/human/H = A - if(!H.gloves) - fingerprints += md5(H.dna.uni_identity) - - else if(!ismob(A)) - - fingerprints = A.return_fingerprints() - - // Only get reagents from non-mobs. - if(A.reagents && A.reagents.reagent_list.len) - - for(var/datum/reagent/R in A.reagents.reagent_list) - reagents[R.name] = R.volume - - // Get blood data from the blood reagent. - if(istype(R, /datum/reagent/blood)) - - if(R.data["blood_DNA"] && R.data["blood_type"]) - var/blood_DNA = R.data["blood_DNA"] - var/blood_type = R.data["blood_type"] - LAZYINITLIST(blood) - blood[blood_DNA] = blood_type - - // We gathered everything. Create a fork and slowly display the results to the holder of the scanner. - - var/found_something = 0 - add_log("[station_time_timestamp()][get_timestamp()] - [target_name]", 0) - - // Fingerprints - if(length(fingerprints)) - sleep(30) - add_log("Prints:") - for(var/finger in fingerprints) - add_log("[finger]") - found_something = 1 - - // Blood - if (length(blood)) - sleep(30) - add_log("Blood:") - found_something = 1 - for(var/B in blood) - add_log("Type: [blood[B]] DNA (UE): [B]") - - //Fibers - if(length(fibers)) - sleep(30) - add_log("Fibers:") - for(var/fiber in fibers) - add_log("[fiber]") - found_something = 1 - - //Reagents - if(length(reagents)) - sleep(30) - add_log("Reagents:") - for(var/R in reagents) - add_log("Reagent: [R] Volume: [reagents[R]]") - found_something = 1 - - // Get a new user - var/mob/holder = null - if(ismob(src.loc)) - holder = src.loc - - if(!found_something) - add_log("# No forensic traces found #", 0) // Don't display this to the holder user - if(holder) - to_chat(holder, "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!") - else - if(holder) - to_chat(holder, "You finish scanning \the [target_name].") - - add_log("---------------------------------------------------------", 0) - scanning = 0 - return - -/obj/item/detective_scanner/proc/add_log(msg, broadcast = 1) - if(scanning) - if(broadcast && ismob(loc)) - var/mob/M = loc - to_chat(M, msg) - log += "  [msg]" - else - CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!") - -/proc/get_timestamp() - return time2text(world.time + 432000, ":ss") - -/obj/item/detective_scanner/AltClick(mob/living/user) - // Best way for checking if a player can use while not incapacitated, etc - if(!user.canUseTopic(src, be_close=TRUE)) - return - if(!LAZYLEN(log)) - to_chat(user, "Cannot clear logs, the scanner has no logs.") - return - if(scanning) - to_chat(user, "Cannot clear logs, the scanner is in use.") - return - to_chat(user, "The scanner logs are cleared.") - log = list() - -/obj/item/detective_scanner/examine(mob/user) - . = ..() - if(LAZYLEN(log) && !scanning) - . += "Alt-click to clear scanner logs." - -/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user) - // No need for can-use checks since the action button should do proper checks - if(!LAZYLEN(log)) - to_chat(user, "Cannot display logs, the scanner has no logs.") - return - if(scanning) - to_chat(user, "Cannot display logs, the scanner is in use.") - return - to_chat(user, "Scanner Report") - for(var/iterLog in log) - to_chat(user, iterLog) +//CONTAINS: Detective's Scanner + +// TODO: Split everything into easy to manage procs. + +/obj/item/detective_scanner + name = "forensic scanner" + desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." + icon = 'icons/obj/device.dmi' + icon_state = "forensicnew" + w_class = WEIGHT_CLASS_SMALL + inhand_icon_state = "electronic" + worn_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + var/scanning = 0 + var/list/log = list() + var/range = 8 + var/view_check = TRUE + var/forensicPrintCount = 0 + actions_types = list(/datum/action/item_action/display_detective_scan_results) + +/datum/action/item_action/display_detective_scan_results + name = "Display Forensic Scanner Results" + +/datum/action/item_action/display_detective_scan_results/Trigger() + var/obj/item/detective_scanner/scanner = target + if(istype(scanner)) + scanner.displayDetectiveScanResults(usr) + +/obj/item/detective_scanner/attack_self(mob/user) + if(log.len && !scanning) + scanning = 1 + to_chat(user, "Printing report, please wait...") + addtimer(CALLBACK(src, .proc/PrintReport), 100) + else + to_chat(user, "The scanner has no logs or is in use.") + +/obj/item/detective_scanner/attack(mob/living/M, mob/user) + return + +/obj/item/detective_scanner/proc/PrintReport() + // Create our paper + var/obj/item/paper/P = new(get_turf(src)) + + //This could be a global count like sec and med record printouts. See GLOB.data_core.medicalPrintCount AKA datacore.dm + var frNum = ++forensicPrintCount + + P.name = text("FR-[] 'Forensic Record'", frNum) + P.info = text("
                Forensic Record - (FR-[])


                ", frNum) + P.info += jointext(log, "
                ") + P.info += "
                Notes:
                " + P.update_icon() + + if(ismob(loc)) + var/mob/M = loc + M.put_in_hands(P) + to_chat(M, "Report printed. Log cleared.") + + // Clear the logs + log = list() + scanning = 0 + +/obj/item/detective_scanner/afterattack(atom/A, mob/user, params) + . = ..() + scan(A, user) + return FALSE + +/obj/item/detective_scanner/proc/scan(atom/A, mob/user) + set waitfor = 0 + if(!scanning) + // Can remotely scan objects and mobs. + if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user)) + return + + scanning = 1 + + user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.") + to_chat(user, "You scan \the [A]. The scanner is now analysing the results...") + + + // GATHER INFORMATION + + //Make our lists + var/list/fingerprints = list() + var/list/blood = A.return_blood_DNA() + var/list/fibers = A.return_fibers() + var/list/reagents = list() + + var/target_name = A.name + + // Start gathering + + if(ishuman(A)) + + var/mob/living/carbon/human/H = A + if(!H.gloves) + fingerprints += md5(H.dna.uni_identity) + + else if(!ismob(A)) + + fingerprints = A.return_fingerprints() + + // Only get reagents from non-mobs. + if(A.reagents && A.reagents.reagent_list.len) + + for(var/datum/reagent/R in A.reagents.reagent_list) + reagents[R.name] = R.volume + + // Get blood data from the blood reagent. + if(istype(R, /datum/reagent/blood)) + + if(R.data["blood_DNA"] && R.data["blood_type"]) + var/blood_DNA = R.data["blood_DNA"] + var/blood_type = R.data["blood_type"] + LAZYINITLIST(blood) + blood[blood_DNA] = blood_type + + // We gathered everything. Create a fork and slowly display the results to the holder of the scanner. + + var/found_something = 0 + add_log("[station_time_timestamp()][get_timestamp()] - [target_name]", 0) + + // Fingerprints + if(length(fingerprints)) + sleep(30) + add_log("Prints:") + for(var/finger in fingerprints) + add_log("[finger]") + found_something = 1 + + // Blood + if (length(blood)) + sleep(30) + add_log("Blood:") + found_something = 1 + for(var/B in blood) + add_log("Type: [blood[B]] DNA (UE): [B]") + + //Fibers + if(length(fibers)) + sleep(30) + add_log("Fibers:") + for(var/fiber in fibers) + add_log("[fiber]") + found_something = 1 + + //Reagents + if(length(reagents)) + sleep(30) + add_log("Reagents:") + for(var/R in reagents) + add_log("Reagent: [R] Volume: [reagents[R]]") + found_something = 1 + + // Get a new user + var/mob/holder = null + if(ismob(src.loc)) + holder = src.loc + + if(!found_something) + add_log("# No forensic traces found #", 0) // Don't display this to the holder user + if(holder) + to_chat(holder, "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!") + else + if(holder) + to_chat(holder, "You finish scanning \the [target_name].") + + add_log("---------------------------------------------------------", 0) + scanning = 0 + return + +/obj/item/detective_scanner/proc/add_log(msg, broadcast = 1) + if(scanning) + if(broadcast && ismob(loc)) + var/mob/M = loc + to_chat(M, msg) + log += "  [msg]" + else + CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!") + +/proc/get_timestamp() + return time2text(world.time + 432000, ":ss") + +/obj/item/detective_scanner/AltClick(mob/living/user) + // Best way for checking if a player can use while not incapacitated, etc + if(!user.canUseTopic(src, be_close=TRUE)) + return + if(!LAZYLEN(log)) + to_chat(user, "Cannot clear logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot clear logs, the scanner is in use.") + return + to_chat(user, "The scanner logs are cleared.") + log = list() + +/obj/item/detective_scanner/examine(mob/user) + . = ..() + if(LAZYLEN(log) && !scanning) + . += "Alt-click to clear scanner logs." + +/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user) + // No need for can-use checks since the action button should do proper checks + if(!LAZYLEN(log)) + to_chat(user, "Cannot display logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot display logs, the scanner is in use.") + return + to_chat(user, "Scanner Report") + for(var/iterLog in log) + to_chat(user, iterLog) diff --git a/code/modules/discord/accountlink.dm b/code/modules/discord/accountlink.dm index 2facd95ebab..00b6d2df054 100644 --- a/code/modules/discord/accountlink.dm +++ b/code/modules/discord/accountlink.dm @@ -1,93 +1,93 @@ -// Verb to link discord accounts to BYOND accounts -/client/verb/linkdiscord() - set category = "OOC" - set name = "Link Discord Account" - set desc = "Link your discord account to your BYOND account." - - // Safety checks - if(!CONFIG_GET(flag/sql_enabled)) - to_chat(src, "This feature requires the SQL backend to be running.") - return - - if(!SSdiscord) // SS is still starting - to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") - return - - if(!SSdiscord.enabled) - to_chat(src, "This feature requires the server is running on the TGS toolkit.") - return - - var/stored_id = SSdiscord.lookup_id(usr.ckey) - if(!stored_id) // Account is not linked - var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No","Cancel Linking") - if(know_how == "No") // Opens discord support on how to collect IDs - src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") - if(know_how == "Cancel Linking") - return - var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null - SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces - alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") - - else // Account is already linked - var/choice = alert("You already have the Discord Account [stored_id] linked to [usr.ckey]. Would you like to link a different account?","Already Linked","Yes","No") - if(choice == "Yes") - var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No", "Cancel Linking") - if(know_how == "No") - src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") - - if(know_how == "Cancel Linking") - return - - var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null - SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces - alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") - // This is so people cant fill the notify list with a fuckload of ckeys - SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer - -// IF you have linked your account, this will trigger a verify of the user -/client/verb/verify_in_discord() - set category = "OOC" - set name = "Verify Discord Account" - set desc = "Verify or reverify your discord account against your linked ckey" - - // Safety checks - if(!CONFIG_GET(flag/sql_enabled)) - to_chat(src, "This feature requires the SQL backend to be running.") - return - - // ss is still starting - if(!SSdiscord) - to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") - return - - // check that tgs is alive and well - if(!SSdiscord.enabled) - to_chat(src, "This feature requires the server is running on the TGS toolkit.") - return - - // check that this is not an IDIOT mistaking us for an attack vector - if(SSdiscord.reverify_cache[usr.ckey] == TRUE) - to_chat(src, "Thou can only do this once a round, if you're stuck seek help.") - return - SSdiscord.reverify_cache[usr.ckey] = TRUE - - // check that account is linked with discord - var/stored_id = SSdiscord.lookup_id(usr.ckey) - if(!stored_id) // Account is not linked - to_chat(usr, "Link your discord account via the linkdiscord verb in the OOC tab first"); - return - - // check for living hours requirement - var/required_living_minutes = CONFIG_GET(number/required_living_hours) * 60 - var/living_minutes = usr.client ? usr.client.get_exp_living(TRUE) : 0 - if(required_living_minutes <= 0) - CRASH("The discord verification system is setup to require zero hours or less, this is likely a configuration bug") - - if(living_minutes < required_living_minutes) - to_chat(usr, "You must have at least [required_living_minutes] minutes of living " \ - + "playtime in a round to verify. You have [living_minutes] minutes. Play more!") - return - - // honey its time for your role flattening - to_chat(usr, "Discord verified") - SSdiscord.grant_role(stored_id) +// Verb to link discord accounts to BYOND accounts +/client/verb/linkdiscord() + set category = "OOC" + set name = "Link Discord Account" + set desc = "Link your discord account to your BYOND account." + + // Safety checks + if(!CONFIG_GET(flag/sql_enabled)) + to_chat(src, "This feature requires the SQL backend to be running.") + return + + if(!SSdiscord) // SS is still starting + to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") + return + + if(!SSdiscord.enabled) + to_chat(src, "This feature requires the server is running on the TGS toolkit.") + return + + var/stored_id = SSdiscord.lookup_id(usr.ckey) + if(!stored_id) // Account is not linked + var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No","Cancel Linking") + if(know_how == "No") // Opens discord support on how to collect IDs + src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") + if(know_how == "Cancel Linking") + return + var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null + SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces + alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") + + else // Account is already linked + var/choice = alert("You already have the Discord Account [stored_id] linked to [usr.ckey]. Would you like to link a different account?","Already Linked","Yes","No") + if(choice == "Yes") + var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No", "Cancel Linking") + if(know_how == "No") + src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") + + if(know_how == "Cancel Linking") + return + + var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null + SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces + alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") + // This is so people cant fill the notify list with a fuckload of ckeys + SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer + +// IF you have linked your account, this will trigger a verify of the user +/client/verb/verify_in_discord() + set category = "OOC" + set name = "Verify Discord Account" + set desc = "Verify or reverify your discord account against your linked ckey" + + // Safety checks + if(!CONFIG_GET(flag/sql_enabled)) + to_chat(src, "This feature requires the SQL backend to be running.") + return + + // ss is still starting + if(!SSdiscord) + to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") + return + + // check that tgs is alive and well + if(!SSdiscord.enabled) + to_chat(src, "This feature requires the server is running on the TGS toolkit.") + return + + // check that this is not an IDIOT mistaking us for an attack vector + if(SSdiscord.reverify_cache[usr.ckey] == TRUE) + to_chat(src, "Thou can only do this once a round, if you're stuck seek help.") + return + SSdiscord.reverify_cache[usr.ckey] = TRUE + + // check that account is linked with discord + var/stored_id = SSdiscord.lookup_id(usr.ckey) + if(!stored_id) // Account is not linked + to_chat(usr, "Link your discord account via the linkdiscord verb in the OOC tab first"); + return + + // check for living hours requirement + var/required_living_minutes = CONFIG_GET(number/required_living_hours) * 60 + var/living_minutes = usr.client ? usr.client.get_exp_living(TRUE) : 0 + if(required_living_minutes <= 0) + CRASH("The discord verification system is setup to require zero hours or less, this is likely a configuration bug") + + if(living_minutes < required_living_minutes) + to_chat(usr, "You must have at least [required_living_minutes] minutes of living " \ + + "playtime in a round to verify. You have [living_minutes] minutes. Play more!") + return + + // honey its time for your role flattening + to_chat(usr, "Discord verified") + SSdiscord.grant_role(stored_id) diff --git a/code/modules/discord/manipulation.dm b/code/modules/discord/manipulation.dm index 0ac66862101..d89953c6bbd 100644 --- a/code/modules/discord/manipulation.dm +++ b/code/modules/discord/manipulation.dm @@ -1,36 +1,36 @@ -// Verb to manipulate IDs and ckeys -/client/proc/discord_id_manipulation() - set name = "Discord Manipulation" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.discord_manipulation() - - -/datum/admins/proc/discord_manipulation() - if(!usr.client.holder) - return - - if(!SSdiscord.enabled) - to_chat(usr, "TGS is not enabled") - return - - var/lookup_choice = alert(usr, "Do you wish to lookup account by ID or ckey?", "Lookup Type", "ID", "Ckey", "Cancel") - switch(lookup_choice) - if("ID") - var/lookup_id = input(usr,"Enter Discord ID to lookup ckey") as text|null - var/returned_ckey = SSdiscord.lookup_ckey(lookup_id) - if(returned_ckey) - var/unlink_choice = alert(usr, "Discord ID [lookup_id] is linked to Ckey [returned_ckey]. Do you wish to unlink or cancel?", "Account Found", "Unlink", "Cancel") - if(unlink_choice == "Unlink") - SSdiscord.unlink_account(returned_ckey) - else - to_chat(usr, "Discord ID [lookup_id] has no associated ckey") - if("Ckey") - var/lookup_ckey = input(usr,"Enter Ckey to lookup ID") as text|null - var/returned_id = SSdiscord.lookup_id(lookup_ckey) - if(returned_id) - to_chat(usr, "Ckey [lookup_ckey] is assigned to Discord ID [returned_id]") - to_chat(usr, "Discord mention format: <@[returned_id]>") // < and > print < > in HTML without using them as tags +// Verb to manipulate IDs and ckeys +/client/proc/discord_id_manipulation() + set name = "Discord Manipulation" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + holder.discord_manipulation() + + +/datum/admins/proc/discord_manipulation() + if(!usr.client.holder) + return + + if(!SSdiscord.enabled) + to_chat(usr, "TGS is not enabled") + return + + var/lookup_choice = alert(usr, "Do you wish to lookup account by ID or ckey?", "Lookup Type", "ID", "Ckey", "Cancel") + switch(lookup_choice) + if("ID") + var/lookup_id = input(usr,"Enter Discord ID to lookup ckey") as text|null + var/returned_ckey = SSdiscord.lookup_ckey(lookup_id) + if(returned_ckey) + var/unlink_choice = alert(usr, "Discord ID [lookup_id] is linked to Ckey [returned_ckey]. Do you wish to unlink or cancel?", "Account Found", "Unlink", "Cancel") + if(unlink_choice == "Unlink") + SSdiscord.unlink_account(returned_ckey) + else + to_chat(usr, "Discord ID [lookup_id] has no associated ckey") + if("Ckey") + var/lookup_ckey = input(usr,"Enter Ckey to lookup ID") as text|null + var/returned_id = SSdiscord.lookup_id(lookup_ckey) + if(returned_id) + to_chat(usr, "Ckey [lookup_ckey] is assigned to Discord ID [returned_id]") + to_chat(usr, "Discord mention format: <@[returned_id]>") // < and > print < > in HTML without using them as tags diff --git a/code/modules/discord/tgs_commands.dm b/code/modules/discord/tgs_commands.dm index cb876b473ff..63d0889ecbe 100644 --- a/code/modules/discord/tgs_commands.dm +++ b/code/modules/discord/tgs_commands.dm @@ -1,46 +1,46 @@ -// Notify -/datum/tgs_chat_command/notify - name = "notify" - help_text = "Pings the invoker when the round ends" - -/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params) - if(!CONFIG_GET(string/chat_announce_new_game)) - return "Notifcations are currently disabled" - - for(var/member in SSdiscord.notify_members) // If they are in the list, take them out - if(member == sender.mention) - SSdiscord.notify_members -= sender.mention - return "You will no longer be notified when the server restarts" - - // If we got here, they arent in the list. Chuck 'em in! - SSdiscord.notify_members += sender.mention - return "You will now be notified when the server restarts" - -// Verify -/datum/tgs_chat_command/verify - name = "verify" - help_text = "Verifies your discord account and your BYOND account linkage" - -/datum/tgs_chat_command/verify/Run(datum/tgs_chat_user/sender, params) - var/lowerparams = replacetext(lowertext(params), " ", "") // Fuck spaces - var/discordid = SSdiscord.id_clean(sender.mention) - if(SSdiscord.account_link_cache[lowerparams]) // First if they are in the list, then if the ckey matches - if(SSdiscord.account_link_cache[lowerparams] == discordid) // If the associated ID is the correct one - // Link the account in the DB table - SSdiscord.link_account(lowerparams) - return "Successfully linked accounts" - else - return "That ckey is not associated to this discord account. If someone has used your ID, please inform an administrator" - else - return "Account not setup for linkage" - - -/// Gets the discord user's Discord UserID -/datum/tgs_chat_command/myuserid - name = "myuserid" - help_text = "Returns your userid" - -/datum/tgs_chat_command/myuserid/Run(datum/tgs_chat_user/sender, params) - var/discordid = SSdiscord.id_clean(sender.mention) - return "<@[discordid]> Your Discord UserID is [discordid]" - +// Notify +/datum/tgs_chat_command/notify + name = "notify" + help_text = "Pings the invoker when the round ends" + +/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params) + if(!CONFIG_GET(string/chat_announce_new_game)) + return "Notifcations are currently disabled" + + for(var/member in SSdiscord.notify_members) // If they are in the list, take them out + if(member == sender.mention) + SSdiscord.notify_members -= sender.mention + return "You will no longer be notified when the server restarts" + + // If we got here, they arent in the list. Chuck 'em in! + SSdiscord.notify_members += sender.mention + return "You will now be notified when the server restarts" + +// Verify +/datum/tgs_chat_command/verify + name = "verify" + help_text = "Verifies your discord account and your BYOND account linkage" + +/datum/tgs_chat_command/verify/Run(datum/tgs_chat_user/sender, params) + var/lowerparams = replacetext(lowertext(params), " ", "") // Fuck spaces + var/discordid = SSdiscord.id_clean(sender.mention) + if(SSdiscord.account_link_cache[lowerparams]) // First if they are in the list, then if the ckey matches + if(SSdiscord.account_link_cache[lowerparams] == discordid) // If the associated ID is the correct one + // Link the account in the DB table + SSdiscord.link_account(lowerparams) + return "Successfully linked accounts" + else + return "That ckey is not associated to this discord account. If someone has used your ID, please inform an administrator" + else + return "Account not setup for linkage" + + +/// Gets the discord user's Discord UserID +/datum/tgs_chat_command/myuserid + name = "myuserid" + help_text = "Returns your userid" + +/datum/tgs_chat_command/myuserid/Run(datum/tgs_chat_user/sender, params) + var/discordid = SSdiscord.id_clean(sender.mention) + return "<@[discordid]> Your Discord UserID is [discordid]" + diff --git a/code/modules/discord/toggle_notify.dm b/code/modules/discord/toggle_notify.dm index 87cb63d0508..ec0bc1f550a 100644 --- a/code/modules/discord/toggle_notify.dm +++ b/code/modules/discord/toggle_notify.dm @@ -1,34 +1,34 @@ -// Verb to toggle restart notifications -/client/verb/notify_restart() - set category = "OOC" - set name = "Notify Restart" - set desc = "Notifies you on Discord when the server restarts." - - // Safety checks - if(!CONFIG_GET(flag/sql_enabled)) - to_chat(src, "This feature requires the SQL backend to be running.") - return - - if(!SSdiscord) // SS is still starting - to_chat(src, "The server is still starting up. Please wait before attempting to link your account ") - return - - if(!SSdiscord.enabled) - to_chat(src, "This feature requires the server is running on the TGS toolkit") - return - - var/stored_id = SSdiscord.lookup_id(usr.ckey) - if(!stored_id) // Account is not linked - to_chat(src, "This requires you to link your Discord account with the \"Link Discord Account\" verb.") - return - - else // Linked - for(var/member in SSdiscord.notify_members) // If they are in the list, take them out - if(member == "[stored_id]") - SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer - to_chat(src, "You will no longer be notified when the server restarts") - return // This is necassary so it doesnt get added again, as it relies on the for loop being unsuccessful to tell us if they are in the list or not - - // If we got here, they arent in the list. Chuck 'em in! - to_chat(src, "You will now be notified when the server restarts") - SSdiscord.notify_members += "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer +// Verb to toggle restart notifications +/client/verb/notify_restart() + set category = "OOC" + set name = "Notify Restart" + set desc = "Notifies you on Discord when the server restarts." + + // Safety checks + if(!CONFIG_GET(flag/sql_enabled)) + to_chat(src, "This feature requires the SQL backend to be running.") + return + + if(!SSdiscord) // SS is still starting + to_chat(src, "The server is still starting up. Please wait before attempting to link your account ") + return + + if(!SSdiscord.enabled) + to_chat(src, "This feature requires the server is running on the TGS toolkit") + return + + var/stored_id = SSdiscord.lookup_id(usr.ckey) + if(!stored_id) // Account is not linked + to_chat(src, "This requires you to link your Discord account with the \"Link Discord Account\" verb.") + return + + else // Linked + for(var/member in SSdiscord.notify_members) // If they are in the list, take them out + if(member == "[stored_id]") + SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer + to_chat(src, "You will no longer be notified when the server restarts") + return // This is necassary so it doesnt get added again, as it relies on the for loop being unsuccessful to tell us if they are in the list or not + + // If we got here, they arent in the list. Chuck 'em in! + to_chat(src, "You will now be notified when the server restarts") + SSdiscord.notify_members += "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index 6f8355564a9..8db7fca9edb 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -1,55 +1,55 @@ -/datum/round_event_control/anomaly - name = "Anomaly: Energetic Flux" - typepath = /datum/round_event/anomaly - - min_players = 1 - max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun. - weight = 15 - -/datum/round_event/anomaly - var/area/impact_area - var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux - announceWhen = 1 - - -/datum/round_event/anomaly/proc/findEventArea() - var/static/list/allowed_areas - if(!allowed_areas) - //Places that shouldn't explode - var/list/safe_area_types = typecacheof(list( - /area/ai_monitored/turret_protected/ai, - /area/ai_monitored/turret_protected/ai_upload, - /area/engine, - /area/solar, - /area/holodeck, - /area/shuttle, - /area/maintenance, - /area/science/test_area) - ) - - //Subtypes from the above that actually should explode. - var/list/unsafe_area_subtypes = typecacheof(list(/area/engine/break_room)) - - allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes - var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas) - if (length(possible_areas)) - return pick(possible_areas) - -/datum/round_event/anomaly/setup() - impact_area = findEventArea() - if(!impact_area) - CRASH("No valid areas for anomaly found.") - var/list/turf_test = get_area_turfs(impact_area) - if(!turf_test.len) - CRASH("Anomaly : No valid turfs found for [impact_area] - [impact_area.type]") - -/datum/round_event/anomaly/announce(fake) - priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert") - -/datum/round_event/anomaly/start() - var/turf/T = pick(get_area_turfs(impact_area)) - var/newAnomaly - if(T) - newAnomaly = new anomaly_path(T) - if (newAnomaly) - announce_to_ghosts(newAnomaly) +/datum/round_event_control/anomaly + name = "Anomaly: Energetic Flux" + typepath = /datum/round_event/anomaly + + min_players = 1 + max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun. + weight = 15 + +/datum/round_event/anomaly + var/area/impact_area + var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux + announceWhen = 1 + + +/datum/round_event/anomaly/proc/findEventArea() + var/static/list/allowed_areas + if(!allowed_areas) + //Places that shouldn't explode + var/list/safe_area_types = typecacheof(list( + /area/ai_monitored/turret_protected/ai, + /area/ai_monitored/turret_protected/ai_upload, + /area/engine, + /area/solar, + /area/holodeck, + /area/shuttle, + /area/maintenance, + /area/science/test_area) + ) + + //Subtypes from the above that actually should explode. + var/list/unsafe_area_subtypes = typecacheof(list(/area/engine/break_room)) + + allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes + var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas) + if (length(possible_areas)) + return pick(possible_areas) + +/datum/round_event/anomaly/setup() + impact_area = findEventArea() + if(!impact_area) + CRASH("No valid areas for anomaly found.") + var/list/turf_test = get_area_turfs(impact_area) + if(!turf_test.len) + CRASH("Anomaly : No valid turfs found for [impact_area] - [impact_area.type]") + +/datum/round_event/anomaly/announce(fake) + priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert") + +/datum/round_event/anomaly/start() + var/turf/T = pick(get_area_turfs(impact_area)) + var/newAnomaly + if(T) + newAnomaly = new anomaly_path(T) + if (newAnomaly) + announce_to_ghosts(newAnomaly) diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm index 8af1d05ca2c..23a2a1968a8 100644 --- a/code/modules/events/anomaly_bluespace.dm +++ b/code/modules/events/anomaly_bluespace.dm @@ -1,14 +1,14 @@ -/datum/round_event_control/anomaly/anomaly_bluespace - name = "Anomaly: Bluespace" - typepath = /datum/round_event/anomaly/anomaly_bluespace - - max_occurrences = 1 - weight = 15 - -/datum/round_event/anomaly/anomaly_bluespace - startWhen = 3 - announceWhen = 10 - anomaly_path = /obj/effect/anomaly/bluespace - -/datum/round_event/anomaly/anomaly_bluespace/announce(fake) - priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_bluespace + name = "Anomaly: Bluespace" + typepath = /datum/round_event/anomaly/anomaly_bluespace + + max_occurrences = 1 + weight = 15 + +/datum/round_event/anomaly/anomaly_bluespace + startWhen = 3 + announceWhen = 10 + anomaly_path = /obj/effect/anomaly/bluespace + +/datum/round_event/anomaly/anomaly_bluespace/announce(fake) + priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm index 9bc18a8cdb9..4737ad9bb41 100644 --- a/code/modules/events/anomaly_flux.dm +++ b/code/modules/events/anomaly_flux.dm @@ -1,15 +1,15 @@ -/datum/round_event_control/anomaly/anomaly_flux - name = "Anomaly: Hyper-Energetic Flux" - typepath = /datum/round_event/anomaly/anomaly_flux - - min_players = 10 - max_occurrences = 5 - weight = 20 - -/datum/round_event/anomaly/anomaly_flux - startWhen = 10 - announceWhen = 3 - anomaly_path = /obj/effect/anomaly/flux - -/datum/round_event/anomaly/anomaly_flux/announce(fake) - priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_flux + name = "Anomaly: Hyper-Energetic Flux" + typepath = /datum/round_event/anomaly/anomaly_flux + + min_players = 10 + max_occurrences = 5 + weight = 20 + +/datum/round_event/anomaly/anomaly_flux + startWhen = 10 + announceWhen = 3 + anomaly_path = /obj/effect/anomaly/flux + +/datum/round_event/anomaly/anomaly_flux/announce(fake) + priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm index f20b6cab74f..c337b94b8a7 100644 --- a/code/modules/events/anomaly_grav.dm +++ b/code/modules/events/anomaly_grav.dm @@ -1,26 +1,26 @@ -/datum/round_event_control/anomaly/anomaly_grav - name = "Anomaly: Gravitational" - typepath = /datum/round_event/anomaly/anomaly_grav - - max_occurrences = 5 - weight = 25 - -/datum/round_event/anomaly/anomaly_grav - startWhen = 3 - announceWhen = 20 - anomaly_path = /obj/effect/anomaly/grav - -/datum/round_event_control/anomaly/anomaly_grav/high - name = "Anomaly: Gravitational (High Intensity)" - typepath = /datum/round_event/anomaly/anomaly_grav/high - weight = 15 - max_occurrences = 1 - earliest_start = 20 MINUTES - -/datum/round_event/anomaly/anomaly_grav/high - startWhen = 3 - announceWhen = 20 - anomaly_path = /obj/effect/anomaly/grav/high - -/datum/round_event/anomaly/anomaly_grav/announce(fake) - priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_grav + name = "Anomaly: Gravitational" + typepath = /datum/round_event/anomaly/anomaly_grav + + max_occurrences = 5 + weight = 25 + +/datum/round_event/anomaly/anomaly_grav + startWhen = 3 + announceWhen = 20 + anomaly_path = /obj/effect/anomaly/grav + +/datum/round_event_control/anomaly/anomaly_grav/high + name = "Anomaly: Gravitational (High Intensity)" + typepath = /datum/round_event/anomaly/anomaly_grav/high + weight = 15 + max_occurrences = 1 + earliest_start = 20 MINUTES + +/datum/round_event/anomaly/anomaly_grav/high + startWhen = 3 + announceWhen = 20 + anomaly_path = /obj/effect/anomaly/grav/high + +/datum/round_event/anomaly/anomaly_grav/announce(fake) + priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm index f71139d7ebb..feb32ff13cd 100644 --- a/code/modules/events/anomaly_vortex.dm +++ b/code/modules/events/anomaly_vortex.dm @@ -1,15 +1,15 @@ -/datum/round_event_control/anomaly/anomaly_vortex - name = "Anomaly: Vortex" - typepath = /datum/round_event/anomaly/anomaly_vortex - - min_players = 20 - max_occurrences = 2 - weight = 10 - -/datum/round_event/anomaly/anomaly_vortex - startWhen = 10 - announceWhen = 3 - anomaly_path = /obj/effect/anomaly/bhole - -/datum/round_event/anomaly/anomaly_vortex/announce(fake) - priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_vortex + name = "Anomaly: Vortex" + typepath = /datum/round_event/anomaly/anomaly_vortex + + min_players = 20 + max_occurrences = 2 + weight = 10 + +/datum/round_event/anomaly/anomaly_vortex + startWhen = 10 + announceWhen = 3 + anomaly_path = /obj/effect/anomaly/bhole + +/datum/round_event/anomaly/anomaly_vortex/announce(fake) + priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index e006f825502..0da92c041ef 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -1,30 +1,30 @@ -/datum/round_event_control/blob - name = "Blob" - typepath = /datum/round_event/ghost_role/blob - weight = 10 - max_occurrences = 1 - - min_players = 20 - - gamemode_blacklist = list("blob") //Just in case a blob survives that long - -/datum/round_event/ghost_role/blob - announceChance = 0 - role_name = "blob overmind" - fakeable = TRUE - -/datum/round_event/ghost_role/blob/announce(fake) - priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak5.ogg') - -/datum/round_event/ghost_role/blob/spawn_role() - if(!GLOB.blobstart.len) - return MAP_ERROR - var/list/candidates = get_candidates(ROLE_BLOB, null, ROLE_BLOB) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - var/mob/dead/observer/new_blob = pick(candidates) - var/mob/camera/blob/BC = new_blob.become_overmind() - spawned_mobs += BC - message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") - log_game("[key_name(BC)] was spawned as a blob overmind by an event.") - return SUCCESSFUL_SPAWN +/datum/round_event_control/blob + name = "Blob" + typepath = /datum/round_event/ghost_role/blob + weight = 10 + max_occurrences = 1 + + min_players = 20 + + gamemode_blacklist = list("blob") //Just in case a blob survives that long + +/datum/round_event/ghost_role/blob + announceChance = 0 + role_name = "blob overmind" + fakeable = TRUE + +/datum/round_event/ghost_role/blob/announce(fake) + priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak5.ogg') + +/datum/round_event/ghost_role/blob/spawn_role() + if(!GLOB.blobstart.len) + return MAP_ERROR + var/list/candidates = get_candidates(ROLE_BLOB, null, ROLE_BLOB) + if(!candidates.len) + return NOT_ENOUGH_PLAYERS + var/mob/dead/observer/new_blob = pick(candidates) + var/mob/camera/blob/BC = new_blob.become_overmind() + spawned_mobs += BC + message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") + log_game("[key_name(BC)] was spawned as a blob overmind by an event.") + return SUCCESSFUL_SPAWN diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index c1ce0051a5b..5c0637b036c 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -1,78 +1,78 @@ -/datum/round_event_control/brand_intelligence - name = "Brand Intelligence" - typepath = /datum/round_event/brand_intelligence - weight = 5 - - min_players = 15 - max_occurrences = 1 - -/datum/round_event/brand_intelligence - announceWhen = 21 - endWhen = 1000 //Ends when all vending machines are subverted anyway. - var/list/obj/machinery/vending/vendingMachines = list() - var/list/obj/machinery/vending/infectedMachines = list() - var/obj/machinery/vending/originMachine - var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ - "You should buy products to feed your lifestyle obsession!", \ - "Consume!", \ - "Your money can buy happiness!", \ - "Engage direct marketing!", \ - "Advertising is legalized lying! But don't let that put you off our great deals!", \ - "You don't want to buy anything? Yeah, well, I didn't want to buy your mom either.") - - -/datum/round_event/brand_intelligence/announce(fake) - var/source = "unknown machine" - if(fake) - var/obj/machinery/vending/cola/example = /obj/machinery/vending/cola - source = initial(example.name) - else if(originMachine) - source = originMachine.name - priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") - -/datum/round_event/brand_intelligence/start() - for(var/obj/machinery/vending/V in GLOB.machines) - if(!is_station_level(V.z)) - continue - vendingMachines.Add(V) - if(!vendingMachines.len) - kill() - return - originMachine = pick(vendingMachines) - vendingMachines.Remove(originMachine) - originMachine.shut_up = 0 - originMachine.shoot_inventory = 1 - announce_to_ghosts(originMachine) - -/datum/round_event/brand_intelligence/tick() - if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped - for(var/obj/machinery/vending/saved in infectedMachines) - saved.shoot_inventory = 0 - if(originMachine) - originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") - originMachine.visible_message("[originMachine] beeps and seems lifeless.") - kill() - return - vendingMachines = removeNullsFromList(vendingMachines) - if(!vendingMachines.len) //if every machine is infected - for(var/obj/machinery/vending/upriser in infectedMachines) - if(prob(70) && !QDELETED(upriser)) - var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks - M.faction = list("profit") - M.speak = rampant_speeches.Copy() - M.speak_chance = 7 - else - explosion(upriser.loc, -1, 1, 2, 4, 0) - qdel(upriser) - - kill() - return - if(ISMULTIPLE(activeFor, 4)) - var/obj/machinery/vending/rebel = pick(vendingMachines) - vendingMachines.Remove(rebel) - infectedMachines.Add(rebel) - rebel.shut_up = 0 - rebel.shoot_inventory = 1 - - if(ISMULTIPLE(activeFor, 8)) - originMachine.speak(pick(rampant_speeches)) +/datum/round_event_control/brand_intelligence + name = "Brand Intelligence" + typepath = /datum/round_event/brand_intelligence + weight = 5 + + min_players = 15 + max_occurrences = 1 + +/datum/round_event/brand_intelligence + announceWhen = 21 + endWhen = 1000 //Ends when all vending machines are subverted anyway. + var/list/obj/machinery/vending/vendingMachines = list() + var/list/obj/machinery/vending/infectedMachines = list() + var/obj/machinery/vending/originMachine + var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ + "You should buy products to feed your lifestyle obsession!", \ + "Consume!", \ + "Your money can buy happiness!", \ + "Engage direct marketing!", \ + "Advertising is legalized lying! But don't let that put you off our great deals!", \ + "You don't want to buy anything? Yeah, well, I didn't want to buy your mom either.") + + +/datum/round_event/brand_intelligence/announce(fake) + var/source = "unknown machine" + if(fake) + var/obj/machinery/vending/cola/example = /obj/machinery/vending/cola + source = initial(example.name) + else if(originMachine) + source = originMachine.name + priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") + +/datum/round_event/brand_intelligence/start() + for(var/obj/machinery/vending/V in GLOB.machines) + if(!is_station_level(V.z)) + continue + vendingMachines.Add(V) + if(!vendingMachines.len) + kill() + return + originMachine = pick(vendingMachines) + vendingMachines.Remove(originMachine) + originMachine.shut_up = 0 + originMachine.shoot_inventory = 1 + announce_to_ghosts(originMachine) + +/datum/round_event/brand_intelligence/tick() + if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped + for(var/obj/machinery/vending/saved in infectedMachines) + saved.shoot_inventory = 0 + if(originMachine) + originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") + originMachine.visible_message("[originMachine] beeps and seems lifeless.") + kill() + return + vendingMachines = removeNullsFromList(vendingMachines) + if(!vendingMachines.len) //if every machine is infected + for(var/obj/machinery/vending/upriser in infectedMachines) + if(prob(70) && !QDELETED(upriser)) + var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks + M.faction = list("profit") + M.speak = rampant_speeches.Copy() + M.speak_chance = 7 + else + explosion(upriser.loc, -1, 1, 2, 4, 0) + qdel(upriser) + + kill() + return + if(ISMULTIPLE(activeFor, 4)) + var/obj/machinery/vending/rebel = pick(vendingMachines) + vendingMachines.Remove(rebel) + infectedMachines.Add(rebel) + rebel.shut_up = 0 + rebel.shoot_inventory = 1 + + if(ISMULTIPLE(activeFor, 8)) + originMachine.speak(pick(rampant_speeches)) diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm index 2d183114b76..695cd9ec8ff 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -1,34 +1,34 @@ -/datum/round_event_control/carp_migration - name = "Carp Migration" - typepath = /datum/round_event/carp_migration - weight = 15 - min_players = 2 - earliest_start = 10 MINUTES - max_occurrences = 6 - -/datum/round_event/carp_migration - announceWhen = 3 - startWhen = 50 - var/hasAnnounced = FALSE - -/datum/round_event/carp_migration/setup() - startWhen = rand(40, 60) - -/datum/round_event/carp_migration/announce(fake) - priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") - - -/datum/round_event/carp_migration/start() - var/mob/living/simple_animal/hostile/carp/fish - for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) - if(prob(95)) - fish = new (C.loc) - else - fish = new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) - fishannounce(fish) //Prefer to announce the megacarps over the regular fishies - fishannounce(fish) - -/datum/round_event/carp_migration/proc/fishannounce(atom/fish) - if (!hasAnnounced) - announce_to_ghosts(fish) //Only anounce the first fish - hasAnnounced = TRUE +/datum/round_event_control/carp_migration + name = "Carp Migration" + typepath = /datum/round_event/carp_migration + weight = 15 + min_players = 2 + earliest_start = 10 MINUTES + max_occurrences = 6 + +/datum/round_event/carp_migration + announceWhen = 3 + startWhen = 50 + var/hasAnnounced = FALSE + +/datum/round_event/carp_migration/setup() + startWhen = rand(40, 60) + +/datum/round_event/carp_migration/announce(fake) + priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") + + +/datum/round_event/carp_migration/start() + var/mob/living/simple_animal/hostile/carp/fish + for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) + if(prob(95)) + fish = new (C.loc) + else + fish = new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) + fishannounce(fish) //Prefer to announce the megacarps over the regular fishies + fishannounce(fish) + +/datum/round_event/carp_migration/proc/fishannounce(atom/fish) + if (!hasAnnounced) + announce_to_ghosts(fish) //Only anounce the first fish + hasAnnounced = TRUE diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm index 967db84e2b7..e77b86e8a15 100644 --- a/code/modules/events/communications_blackout.dm +++ b/code/modules/events/communications_blackout.dm @@ -1,26 +1,26 @@ -/datum/round_event_control/communications_blackout - name = "Communications Blackout" - typepath = /datum/round_event/communications_blackout - weight = 30 - -/datum/round_event/communications_blackout - announceWhen = 1 - -/datum/round_event/communications_blackout/announce(fake) - var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ - "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \ - "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ - "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ - "Ionospheri:%£ MCayj^j<.3-BZZZZZZT", \ - "#4nd%;f4y6,>£%-BZZZZZZZT") - - for(var/mob/living/silicon/ai/A in GLOB.ai_list) //AIs are always aware of communication blackouts. - to_chat(A, "
                [alert]
                ") - - if(prob(30) || fake) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. - priority_announce(alert) - - -/datum/round_event/communications_blackout/start() - for(var/obj/machinery/telecomms/T in GLOB.telecomms_list) - T.emp_act(EMP_HEAVY) +/datum/round_event_control/communications_blackout + name = "Communications Blackout" + typepath = /datum/round_event/communications_blackout + weight = 30 + +/datum/round_event/communications_blackout + announceWhen = 1 + +/datum/round_event/communications_blackout/announce(fake) + var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ + "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \ + "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ + "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ + "Ionospheri:%£ MCayj^j<.3-BZZZZZZT", \ + "#4nd%;f4y6,>£%-BZZZZZZZT") + + for(var/mob/living/silicon/ai/A in GLOB.ai_list) //AIs are always aware of communication blackouts. + to_chat(A, "
                [alert]
                ") + + if(prob(30) || fake) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. + priority_announce(alert) + + +/datum/round_event/communications_blackout/start() + for(var/obj/machinery/telecomms/T in GLOB.telecomms_list) + T.emp_act(EMP_HEAVY) diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm index 5237e7a659b..021e1a67c7a 100644 --- a/code/modules/events/disease_outbreak.dm +++ b/code/modules/events/disease_outbreak.dm @@ -1,75 +1,75 @@ -/datum/round_event_control/disease_outbreak - name = "Disease Outbreak" - typepath = /datum/round_event/disease_outbreak - max_occurrences = 1 - min_players = 10 - weight = 5 - -/datum/round_event/disease_outbreak - announceWhen = 15 - - var/virus_type - - var/max_severity = 3 - - -/datum/round_event/disease_outbreak/announce(fake) - priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak7.ogg') - -/datum/round_event/disease_outbreak/setup() - announceWhen = rand(15, 30) - - -/datum/round_event/disease_outbreak/start() - var/advanced_virus = FALSE - max_severity = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //3 symptoms at 20 minutes, plus 1 per 10 minutes - if(!virus_type && prob(20 + (10 * max_severity))) - advanced_virus = TRUE - - if(!virus_type && !advanced_virus) - virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis) - - for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) - var/turf/T = get_turf(H) - if(!T) - continue - if(!is_station_level(T.z)) - continue - if(!H.client) - continue - if(H.stat == DEAD) - continue - if(HAS_TRAIT(H, TRAIT_VIRUSIMMUNE)) //Don't pick someone who's virus immune, only for it to not do anything. - continue - var/foundAlready = FALSE // don't infect someone that already has a disease - for(var/thing in H.diseases) - foundAlready = TRUE - break - if(foundAlready) - continue - - var/datum/disease/D - if(!advanced_virus) - if(virus_type == /datum/disease/dnaspread) //Dnaspread needs strain_data set to work. - if(!H.dna || (HAS_TRAIT(H, TRAIT_BLIND))) //A blindness disease would be the worst. - continue - D = new virus_type() - var/datum/disease/dnaspread/DS = D - DS.strain_data["name"] = H.real_name - DS.strain_data["UI"] = H.dna.uni_identity - DS.strain_data["SE"] = H.dna.mutation_index - else - D = new virus_type() - else - D = new /datum/disease/advance/random(max_severity, max_severity) - D.carrier = TRUE - H.ForceContractDisease(D, FALSE, TRUE) - - if(advanced_virus) - var/datum/disease/advance/A = D - var/list/name_symptoms = list() //for feedback - for(var/datum/symptom/S in A.symptoms) - name_symptoms += S.name - message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(H)]! It has these symptoms: [english_list(name_symptoms)]") - log_game("An event has triggered a random advanced virus outbreak on [key_name(H)]! It has these symptoms: [english_list(name_symptoms)]") - break +/datum/round_event_control/disease_outbreak + name = "Disease Outbreak" + typepath = /datum/round_event/disease_outbreak + max_occurrences = 1 + min_players = 10 + weight = 5 + +/datum/round_event/disease_outbreak + announceWhen = 15 + + var/virus_type + + var/max_severity = 3 + + +/datum/round_event/disease_outbreak/announce(fake) + priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak7.ogg') + +/datum/round_event/disease_outbreak/setup() + announceWhen = rand(15, 30) + + +/datum/round_event/disease_outbreak/start() + var/advanced_virus = FALSE + max_severity = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //3 symptoms at 20 minutes, plus 1 per 10 minutes + if(!virus_type && prob(20 + (10 * max_severity))) + advanced_virus = TRUE + + if(!virus_type && !advanced_virus) + virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis) + + for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) + var/turf/T = get_turf(H) + if(!T) + continue + if(!is_station_level(T.z)) + continue + if(!H.client) + continue + if(H.stat == DEAD) + continue + if(HAS_TRAIT(H, TRAIT_VIRUSIMMUNE)) //Don't pick someone who's virus immune, only for it to not do anything. + continue + var/foundAlready = FALSE // don't infect someone that already has a disease + for(var/thing in H.diseases) + foundAlready = TRUE + break + if(foundAlready) + continue + + var/datum/disease/D + if(!advanced_virus) + if(virus_type == /datum/disease/dnaspread) //Dnaspread needs strain_data set to work. + if(!H.dna || (HAS_TRAIT(H, TRAIT_BLIND))) //A blindness disease would be the worst. + continue + D = new virus_type() + var/datum/disease/dnaspread/DS = D + DS.strain_data["name"] = H.real_name + DS.strain_data["UI"] = H.dna.uni_identity + DS.strain_data["SE"] = H.dna.mutation_index + else + D = new virus_type() + else + D = new /datum/disease/advance/random(max_severity, max_severity) + D.carrier = TRUE + H.ForceContractDisease(D, FALSE, TRUE) + + if(advanced_virus) + var/datum/disease/advance/A = D + var/list/name_symptoms = list() //for feedback + for(var/datum/symptom/S in A.symptoms) + name_symptoms += S.name + message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(H)]! It has these symptoms: [english_list(name_symptoms)]") + log_game("An event has triggered a random advanced virus outbreak on [key_name(H)]! It has these symptoms: [english_list(name_symptoms)]") + break diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm index 40aad16bc40..eb7edcafbf8 100644 --- a/code/modules/events/dust.dm +++ b/code/modules/events/dust.dm @@ -1,31 +1,31 @@ -/datum/round_event_control/space_dust - name = "Minor Space Dust" - typepath = /datum/round_event/space_dust - weight = 200 - max_occurrences = 1000 - earliest_start = 0 MINUTES - alert_observers = FALSE - -/datum/round_event/space_dust - startWhen = 1 - endWhen = 2 - fakeable = FALSE - -/datum/round_event/space_dust/start() - spawn_meteors(1, GLOB.meteorsC) - -/datum/round_event_control/sandstorm - name = "Sandstorm" - typepath = /datum/round_event/sandstorm - weight = 0 - max_occurrences = 0 - earliest_start = 0 MINUTES - -/datum/round_event/sandstorm - startWhen = 1 - endWhen = 150 // ~5 min - announceWhen = 0 - fakeable = FALSE - -/datum/round_event/sandstorm/tick() - spawn_meteors(10, GLOB.meteorsC) +/datum/round_event_control/space_dust + name = "Minor Space Dust" + typepath = /datum/round_event/space_dust + weight = 200 + max_occurrences = 1000 + earliest_start = 0 MINUTES + alert_observers = FALSE + +/datum/round_event/space_dust + startWhen = 1 + endWhen = 2 + fakeable = FALSE + +/datum/round_event/space_dust/start() + spawn_meteors(1, GLOB.meteorsC) + +/datum/round_event_control/sandstorm + name = "Sandstorm" + typepath = /datum/round_event/sandstorm + weight = 0 + max_occurrences = 0 + earliest_start = 0 MINUTES + +/datum/round_event/sandstorm + startWhen = 1 + endWhen = 150 // ~5 min + announceWhen = 0 + fakeable = FALSE + +/datum/round_event/sandstorm/tick() + spawn_meteors(10, GLOB.meteorsC) diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm index 6b0bc2d932f..db0eccf0c5c 100644 --- a/code/modules/events/electrical_storm.dm +++ b/code/modules/events/electrical_storm.dm @@ -1,33 +1,33 @@ -/datum/round_event_control/electrical_storm - name = "Electrical Storm" - typepath = /datum/round_event/electrical_storm - earliest_start = 10 MINUTES - min_players = 5 - weight = 20 - alert_observers = 0 - -/datum/round_event/electrical_storm - var/lightsoutAmount = 1 - var/lightsoutRange = 25 - announceWhen = 1 - -/datum/round_event/electrical_storm/announce(fake) - priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") - - -/datum/round_event/electrical_storm/start() - var/list/epicentreList = list() - - for(var/i=1, i <= lightsoutAmount, i++) - var/turf/T = find_safe_turf() - if(istype(T)) - epicentreList += T - - if(!epicentreList.len) - return - - for(var/centre in epicentreList) - for(var/a in GLOB.apcs_list) - var/obj/machinery/power/apc/A = a - if(get_dist(centre, A) <= lightsoutRange) - A.overload_lighting() +/datum/round_event_control/electrical_storm + name = "Electrical Storm" + typepath = /datum/round_event/electrical_storm + earliest_start = 10 MINUTES + min_players = 5 + weight = 20 + alert_observers = 0 + +/datum/round_event/electrical_storm + var/lightsoutAmount = 1 + var/lightsoutRange = 25 + announceWhen = 1 + +/datum/round_event/electrical_storm/announce(fake) + priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") + + +/datum/round_event/electrical_storm/start() + var/list/epicentreList = list() + + for(var/i=1, i <= lightsoutAmount, i++) + var/turf/T = find_safe_turf() + if(istype(T)) + epicentreList += T + + if(!epicentreList.len) + return + + for(var/centre in epicentreList) + for(var/a in GLOB.apcs_list) + var/obj/machinery/power/apc/A = a + if(get_dist(centre, A) <= lightsoutRange) + A.overload_lighting() diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm index d625d2d8060..5b032f85f87 100644 --- a/code/modules/events/false_alarm.dm +++ b/code/modules/events/false_alarm.dm @@ -1,62 +1,62 @@ -/datum/round_event_control/falsealarm - name = "False Alarm" - typepath = /datum/round_event/falsealarm - weight = 20 - max_occurrences = 5 - var/forced_type //Admin abuse - - -/datum/round_event_control/falsealarm/admin_setup() - if(!check_rights(R_FUN)) - return - - var/list/possible_types = list() - - for(var/datum/round_event_control/E in SSevents.control) - var/datum/round_event/event = E.typepath - if(!initial(event.fakeable)) - continue - possible_types += E - - forced_type = input(usr, "Select the scare.","False event") as null|anything in sortNames(possible_types) - -/datum/round_event_control/falsealarm/canSpawnEvent(players_amt, gamemode) - return ..() && length(gather_false_events()) - -/datum/round_event/falsealarm - announceWhen = 0 - endWhen = 1 - fakeable = FALSE - -/datum/round_event/falsealarm/announce(fake) - if(fake) //What are you doing - return - var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1) - var/gamemode = SSticker.mode.config_tag - - var/events_list = gather_false_events(players_amt, gamemode) - var/datum/round_event_control/event_control - var/datum/round_event_control/falsealarm/C = control - if(C.forced_type) - event_control = C.forced_type - C.forced_type = null - else - event_control = pick(events_list) - if(event_control) - var/datum/round_event/Event = new event_control.typepath() - message_admins("False Alarm: [Event]") - Event.kill() //do not process this event - no starts, no ticks, no ends - Event.announce(TRUE) //just announce it like it's happening - -/proc/gather_false_events(players_amt, gamemode) - . = list() - for(var/datum/round_event_control/E in SSevents.control) - if(istype(E, /datum/round_event_control/falsealarm)) - continue - if(!E.canSpawnEvent(players_amt, gamemode)) - continue - - var/datum/round_event/event = E.typepath - if(!initial(event.fakeable)) - continue - . += E +/datum/round_event_control/falsealarm + name = "False Alarm" + typepath = /datum/round_event/falsealarm + weight = 20 + max_occurrences = 5 + var/forced_type //Admin abuse + + +/datum/round_event_control/falsealarm/admin_setup() + if(!check_rights(R_FUN)) + return + + var/list/possible_types = list() + + for(var/datum/round_event_control/E in SSevents.control) + var/datum/round_event/event = E.typepath + if(!initial(event.fakeable)) + continue + possible_types += E + + forced_type = input(usr, "Select the scare.","False event") as null|anything in sortNames(possible_types) + +/datum/round_event_control/falsealarm/canSpawnEvent(players_amt, gamemode) + return ..() && length(gather_false_events()) + +/datum/round_event/falsealarm + announceWhen = 0 + endWhen = 1 + fakeable = FALSE + +/datum/round_event/falsealarm/announce(fake) + if(fake) //What are you doing + return + var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1) + var/gamemode = SSticker.mode.config_tag + + var/events_list = gather_false_events(players_amt, gamemode) + var/datum/round_event_control/event_control + var/datum/round_event_control/falsealarm/C = control + if(C.forced_type) + event_control = C.forced_type + C.forced_type = null + else + event_control = pick(events_list) + if(event_control) + var/datum/round_event/Event = new event_control.typepath() + message_admins("False Alarm: [Event]") + Event.kill() //do not process this event - no starts, no ticks, no ends + Event.announce(TRUE) //just announce it like it's happening + +/proc/gather_false_events(players_amt, gamemode) + . = list() + for(var/datum/round_event_control/E in SSevents.control) + if(istype(E, /datum/round_event_control/falsealarm)) + continue + if(!E.canSpawnEvent(players_amt, gamemode)) + continue + + var/datum/round_event/event = E.typepath + if(!initial(event.fakeable)) + continue + . += E diff --git a/code/modules/events/high_priority_bounty.dm b/code/modules/events/high_priority_bounty.dm index ceefa65416b..ffdcd8840b7 100644 --- a/code/modules/events/high_priority_bounty.dm +++ b/code/modules/events/high_priority_bounty.dm @@ -1,20 +1,20 @@ -/datum/round_event_control/high_priority_bounty - name = "High Priority Bounty" - typepath = /datum/round_event/high_priority_bounty - max_occurrences = 3 - weight = 20 - earliest_start = 10 - -/datum/round_event/high_priority_bounty/announce(fake) - priority_announce("Central Command has issued a high-priority cargo bounty. Details have been sent to all bounty consoles.", "Nanotrasen Bounty Program") - -/datum/round_event/high_priority_bounty/start() - var/datum/bounty/B - for(var/attempts = 0; attempts < 50; ++attempts) - B = random_bounty() - if(!B) - continue - B.mark_high_priority(3) - if(try_add_bounty(B)) - break - +/datum/round_event_control/high_priority_bounty + name = "High Priority Bounty" + typepath = /datum/round_event/high_priority_bounty + max_occurrences = 3 + weight = 20 + earliest_start = 10 + +/datum/round_event/high_priority_bounty/announce(fake) + priority_announce("Central Command has issued a high-priority cargo bounty. Details have been sent to all bounty consoles.", "Nanotrasen Bounty Program") + +/datum/round_event/high_priority_bounty/start() + var/datum/bounty/B + for(var/attempts = 0; attempts < 50; ++attempts) + B = random_bounty() + if(!B) + continue + B.mark_high_priority(3) + if(try_add_bounty(B)) + break + diff --git a/code/modules/events/holiday/halloween.dm b/code/modules/events/holiday/halloween.dm index d66e0469bb0..eee85851d30 100644 --- a/code/modules/events/holiday/halloween.dm +++ b/code/modules/events/holiday/halloween.dm @@ -1,57 +1,57 @@ -/datum/round_event_control/spooky - name = "2 SPOOKY! (Halloween)" - holidayID = HALLOWEEN - typepath = /datum/round_event/spooky - weight = -1 //forces it to be called, regardless of weight - max_occurrences = 1 - earliest_start = 0 MINUTES - -/datum/round_event/spooky/start() - ..() - for(var/i in GLOB.human_list) - var/mob/living/carbon/human/H = i - var/obj/item/storage/backpack/b = locate() in H.contents - if(b) - new /obj/item/storage/spooky(b) - - for(var/mob/living/simple_animal/pet/dog/corgi/ian/Ian in GLOB.mob_living_list) - Ian.place_on_head(new /obj/item/bedsheet(Ian)) - for(var/mob/living/simple_animal/parrot/poly/Poly in GLOB.mob_living_list) - new /mob/living/simple_animal/parrot/poly/ghost(Poly.loc) - qdel(Poly) - -/datum/round_event/spooky/announce(fake) - priority_announce(pick("RATTLE ME BONES!","THE RIDE NEVER ENDS!", "A SKELETON POPS OUT!", "SPOOKY SCARY SKELETONS!", "CREWMEMBERS BEWARE, YOU'RE IN FOR A SCARE!") , "THE CALL IS COMING FROM INSIDE THE HOUSE") - -//spooky foods (you can't actually make these when it's not halloween) -/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull - name = "skull cookie" - desc = "Spooky! It's got delicious calcium flavouring!" - icon = 'icons/obj/halloween_items.dmi' - icon_state = "skeletoncookie" - -/obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin - name = "coffin cookie" - desc = "Spooky! It's got delicious coffee flavouring!" - icon = 'icons/obj/halloween_items.dmi' - icon_state = "coffincookie" - -//spooky items - -/obj/item/storage/spooky - name = "trick-o-treat bag" - desc = "A pumpkin-shaped bag that holds all sorts of goodies!" - icon = 'icons/obj/halloween_items.dmi' - icon_state = "treatbag" - -/obj/item/storage/spooky/Initialize() - . = ..() - for(var/distrobuteinbag in 0 to 5) - var/type = pick(/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull, - /obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin, - /obj/item/reagent_containers/food/snacks/candy_corn, - /obj/item/reagent_containers/food/snacks/candy, - /obj/item/reagent_containers/food/snacks/candiedapple, - /obj/item/reagent_containers/food/snacks/chocolatebar, - /obj/item/organ/brain ) // OH GOD THIS ISN'T CANDY! - new type(src) +/datum/round_event_control/spooky + name = "2 SPOOKY! (Halloween)" + holidayID = HALLOWEEN + typepath = /datum/round_event/spooky + weight = -1 //forces it to be called, regardless of weight + max_occurrences = 1 + earliest_start = 0 MINUTES + +/datum/round_event/spooky/start() + ..() + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + var/obj/item/storage/backpack/b = locate() in H.contents + if(b) + new /obj/item/storage/spooky(b) + + for(var/mob/living/simple_animal/pet/dog/corgi/ian/Ian in GLOB.mob_living_list) + Ian.place_on_head(new /obj/item/bedsheet(Ian)) + for(var/mob/living/simple_animal/parrot/poly/Poly in GLOB.mob_living_list) + new /mob/living/simple_animal/parrot/poly/ghost(Poly.loc) + qdel(Poly) + +/datum/round_event/spooky/announce(fake) + priority_announce(pick("RATTLE ME BONES!","THE RIDE NEVER ENDS!", "A SKELETON POPS OUT!", "SPOOKY SCARY SKELETONS!", "CREWMEMBERS BEWARE, YOU'RE IN FOR A SCARE!") , "THE CALL IS COMING FROM INSIDE THE HOUSE") + +//spooky foods (you can't actually make these when it's not halloween) +/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull + name = "skull cookie" + desc = "Spooky! It's got delicious calcium flavouring!" + icon = 'icons/obj/halloween_items.dmi' + icon_state = "skeletoncookie" + +/obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin + name = "coffin cookie" + desc = "Spooky! It's got delicious coffee flavouring!" + icon = 'icons/obj/halloween_items.dmi' + icon_state = "coffincookie" + +//spooky items + +/obj/item/storage/spooky + name = "trick-o-treat bag" + desc = "A pumpkin-shaped bag that holds all sorts of goodies!" + icon = 'icons/obj/halloween_items.dmi' + icon_state = "treatbag" + +/obj/item/storage/spooky/Initialize() + . = ..() + for(var/distrobuteinbag in 0 to 5) + var/type = pick(/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull, + /obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin, + /obj/item/reagent_containers/food/snacks/candy_corn, + /obj/item/reagent_containers/food/snacks/candy, + /obj/item/reagent_containers/food/snacks/candiedapple, + /obj/item/reagent_containers/food/snacks/chocolatebar, + /obj/item/organ/brain ) // OH GOD THIS ISN'T CANDY! + new type(src) diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index b5a008503e6..b28ec1e1f86 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -1,167 +1,167 @@ -/* -Immovable rod random event. -The rod will spawn at some location outside the station, and travel in a straight line to the opposite side of the station -Everything solid in the way will be ex_act()'d -In my current plan for it, 'solid' will be defined as anything with density == 1 - ---NEOFite -*/ - -/datum/round_event_control/immovable_rod - name = "Immovable Rod" - typepath = /datum/round_event/immovable_rod - min_players = 15 - max_occurrences = 5 - var/atom/special_target - - -/datum/round_event_control/immovable_rod/admin_setup() - if(!check_rights(R_FUN)) - return - - var/aimed = alert("Aimed at current location?","Sniperod", "Yes", "No") - if(aimed == "Yes") - special_target = get_turf(usr) - -/datum/round_event/immovable_rod - announceWhen = 5 - -/datum/round_event/immovable_rod/announce(fake) - priority_announce("What the fuck was that?!", "General Alert") - -/datum/round_event/immovable_rod/start() - var/datum/round_event_control/immovable_rod/C = control - var/startside = pick(GLOB.cardinals) - var/z = pick(SSmapping.levels_by_trait(ZTRAIT_STATION)) - var/turf/startT = spaceDebrisStartLoc(startside, z) - var/turf/endT = spaceDebrisFinishLoc(startside, z) - var/atom/rod = new /obj/effect/immovablerod(startT, endT, C.special_target) - announce_to_ghosts(rod) - -/obj/effect/immovablerod - name = "immovable rod" - desc = "What the fuck is that?" - icon = 'icons/obj/objects.dmi' - icon_state = "immrod" - throwforce = 100 - move_force = INFINITY - move_resist = INFINITY - pull_force = INFINITY - density = TRUE - anchored = TRUE - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - var/mob/living/wizard - var/z_original = 0 - var/destination - var/notify = TRUE - var/atom/special_target - -/obj/effect/immovablerod/New(atom/start, atom/end, aimed_at) - ..() - SSaugury.register_doom(src, 2000) - z_original = z - destination = end - special_target = aimed_at - GLOB.poi_list += src - - var/special_target_valid = FALSE - if(special_target) - var/turf/T = get_turf(special_target) - if(T.z == z_original) - special_target_valid = TRUE - if(special_target_valid) - walk_towards(src, special_target, 1) - else if(end && end.z==z_original) - walk_towards(src, destination, 1) - -/obj/effect/immovablerod/Topic(href, href_list) - if(href_list["orbit"]) - var/mob/dead/observer/ghost = usr - if(istype(ghost)) - ghost.ManualFollow(src) - -/obj/effect/immovablerod/Destroy() - GLOB.poi_list -= src - . = ..() - -/obj/effect/immovablerod/Moved() - if((z != z_original) || (loc == destination)) - qdel(src) - if(special_target && loc == get_turf(special_target)) - complete_trajectory() - return ..() - -/obj/effect/immovablerod/proc/complete_trajectory() - //We hit what we wanted to hit, time to go - special_target = null - destination = get_edge_target_turf(src, dir) - walk(src,0) - walk_towards(src, destination, 1) - -/obj/effect/immovablerod/ex_act(severity, target) - return 0 - -/obj/effect/immovablerod/singularity_act() - return - -/obj/effect/immovablerod/singularity_pull() - return - -/obj/effect/immovablerod/Bump(atom/clong) - if(prob(10)) - playsound(src, 'sound/effects/bang.ogg', 50, TRUE) - audible_message("You hear a CLANG!") - - if(clong && prob(25)) - x = clong.x - y = clong.y - - if(special_target && clong == special_target) - complete_trajectory() - - if(isturf(clong) || isobj(clong)) - if(clong.density) - if(isturf(clong)) - SSexplosions.medturf += clong - if(isobj(clong)) - SSexplosions.medobj += clong - - else if(isliving(clong)) - penetrate(clong) - else if(istype(clong, type)) - var/obj/effect/immovablerod/other = clong - visible_message("[src] collides with [other]!") - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, get_turf(src)) - smoke.start() - qdel(src) - qdel(other) - -/obj/effect/immovablerod/proc/penetrate(mob/living/L) - L.visible_message("[L] is penetrated by an immovable rod!" , "The rod penetrates you!" , "You hear a CLANG!") - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.adjustBruteLoss(160) - if(L && (L.density || prob(10))) - L.ex_act(EXPLODE_HEAVY) - -/obj/effect/immovablerod/attack_hand(mob/living/user) - if(ishuman(user)) - var/mob/living/carbon/human/U = user - if(U.job in list("Research Director")) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) - for(var/mob/M in urange(8, src)) - if(!M.stat) - shake_camera(M, 2, 3) - if(wizard) - U.visible_message("[src] transforms into [wizard] as [U] suplexes them!", "As you grab [src], it suddenly turns into [wizard] as you suplex them!") - to_chat(wizard, "You're suddenly jolted out of rod-form as [U] somehow manages to grab you, slamming you into the ground!") - wizard.Stun(60) - wizard.apply_damage(25, BRUTE) - qdel(src) - else - U.client.give_award(/datum/award/achievement/misc/feat_of_strength, U) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only - U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") - new /obj/structure/festivus/anchored(drop_location()) - new /obj/effect/anomaly/flux(drop_location()) - qdel(src) +/* +Immovable rod random event. +The rod will spawn at some location outside the station, and travel in a straight line to the opposite side of the station +Everything solid in the way will be ex_act()'d +In my current plan for it, 'solid' will be defined as anything with density == 1 + +--NEOFite +*/ + +/datum/round_event_control/immovable_rod + name = "Immovable Rod" + typepath = /datum/round_event/immovable_rod + min_players = 15 + max_occurrences = 5 + var/atom/special_target + + +/datum/round_event_control/immovable_rod/admin_setup() + if(!check_rights(R_FUN)) + return + + var/aimed = alert("Aimed at current location?","Sniperod", "Yes", "No") + if(aimed == "Yes") + special_target = get_turf(usr) + +/datum/round_event/immovable_rod + announceWhen = 5 + +/datum/round_event/immovable_rod/announce(fake) + priority_announce("What the fuck was that?!", "General Alert") + +/datum/round_event/immovable_rod/start() + var/datum/round_event_control/immovable_rod/C = control + var/startside = pick(GLOB.cardinals) + var/z = pick(SSmapping.levels_by_trait(ZTRAIT_STATION)) + var/turf/startT = spaceDebrisStartLoc(startside, z) + var/turf/endT = spaceDebrisFinishLoc(startside, z) + var/atom/rod = new /obj/effect/immovablerod(startT, endT, C.special_target) + announce_to_ghosts(rod) + +/obj/effect/immovablerod + name = "immovable rod" + desc = "What the fuck is that?" + icon = 'icons/obj/objects.dmi' + icon_state = "immrod" + throwforce = 100 + move_force = INFINITY + move_resist = INFINITY + pull_force = INFINITY + density = TRUE + anchored = TRUE + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + var/mob/living/wizard + var/z_original = 0 + var/destination + var/notify = TRUE + var/atom/special_target + +/obj/effect/immovablerod/New(atom/start, atom/end, aimed_at) + ..() + SSaugury.register_doom(src, 2000) + z_original = z + destination = end + special_target = aimed_at + GLOB.poi_list += src + + var/special_target_valid = FALSE + if(special_target) + var/turf/T = get_turf(special_target) + if(T.z == z_original) + special_target_valid = TRUE + if(special_target_valid) + walk_towards(src, special_target, 1) + else if(end && end.z==z_original) + walk_towards(src, destination, 1) + +/obj/effect/immovablerod/Topic(href, href_list) + if(href_list["orbit"]) + var/mob/dead/observer/ghost = usr + if(istype(ghost)) + ghost.ManualFollow(src) + +/obj/effect/immovablerod/Destroy() + GLOB.poi_list -= src + . = ..() + +/obj/effect/immovablerod/Moved() + if((z != z_original) || (loc == destination)) + qdel(src) + if(special_target && loc == get_turf(special_target)) + complete_trajectory() + return ..() + +/obj/effect/immovablerod/proc/complete_trajectory() + //We hit what we wanted to hit, time to go + special_target = null + destination = get_edge_target_turf(src, dir) + walk(src,0) + walk_towards(src, destination, 1) + +/obj/effect/immovablerod/ex_act(severity, target) + return 0 + +/obj/effect/immovablerod/singularity_act() + return + +/obj/effect/immovablerod/singularity_pull() + return + +/obj/effect/immovablerod/Bump(atom/clong) + if(prob(10)) + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + audible_message("You hear a CLANG!") + + if(clong && prob(25)) + x = clong.x + y = clong.y + + if(special_target && clong == special_target) + complete_trajectory() + + if(isturf(clong) || isobj(clong)) + if(clong.density) + if(isturf(clong)) + SSexplosions.medturf += clong + if(isobj(clong)) + SSexplosions.medobj += clong + + else if(isliving(clong)) + penetrate(clong) + else if(istype(clong, type)) + var/obj/effect/immovablerod/other = clong + visible_message("[src] collides with [other]!") + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(2, get_turf(src)) + smoke.start() + qdel(src) + qdel(other) + +/obj/effect/immovablerod/proc/penetrate(mob/living/L) + L.visible_message("[L] is penetrated by an immovable rod!" , "The rod penetrates you!" , "You hear a CLANG!") + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.adjustBruteLoss(160) + if(L && (L.density || prob(10))) + L.ex_act(EXPLODE_HEAVY) + +/obj/effect/immovablerod/attack_hand(mob/living/user) + if(ishuman(user)) + var/mob/living/carbon/human/U = user + if(U.job in list("Research Director")) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + for(var/mob/M in urange(8, src)) + if(!M.stat) + shake_camera(M, 2, 3) + if(wizard) + U.visible_message("[src] transforms into [wizard] as [U] suplexes them!", "As you grab [src], it suddenly turns into [wizard] as you suplex them!") + to_chat(wizard, "You're suddenly jolted out of rod-form as [U] somehow manages to grab you, slamming you into the ground!") + wizard.Stun(60) + wizard.apply_damage(25, BRUTE) + qdel(src) + else + U.client.give_award(/datum/award/achievement/misc/feat_of_strength, U) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only + U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") + new /obj/structure/festivus/anchored(drop_location()) + new /obj/effect/anomaly/flux(drop_location()) + qdel(src) diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm index 07923353c84..9fb19177aaf 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -1,561 +1,561 @@ -/datum/round_event_control/ion_storm - name = "Ion Storm" - typepath = /datum/round_event/ion_storm - weight = 15 - min_players = 2 - -/datum/round_event/ion_storm - var/replaceLawsetChance = 25 //chance the AI's lawset is completely replaced with something else per config weights - var/removeRandomLawChance = 10 //chance the AI has one random supplied or inherent law removed - var/removeDontImproveChance = 10 //chance the randomly created law replaces a random law instead of simply being added - var/shuffleLawsChance = 10 //chance the AI's laws are shuffled afterwards - var/botEmagChance = 1 - var/ionMessage = null - announceWhen = 1 - announceChance = 33 - -/datum/round_event/ion_storm/add_law_only // special subtype that adds a law only - replaceLawsetChance = 0 - removeRandomLawChance = 0 - removeDontImproveChance = 0 - shuffleLawsChance = 0 - botEmagChance = 0 - -/datum/round_event/ion_storm/announce(fake) - if(prob(announceChance) || fake) - priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", 'sound/ai/ionstorm.ogg') - - -/datum/round_event/ion_storm/start() - //AI laws - for(var/mob/living/silicon/ai/M in GLOB.alive_mob_list) - M.laws_sanity_check() - if(M.stat != DEAD && M.see_in_dark != 0) - if(prob(replaceLawsetChance)) - M.laws.pick_weighted_lawset() - - if(prob(removeRandomLawChance)) - M.remove_law(rand(1, M.laws.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED)))) - - var/message = ionMessage || generate_ion_law() - if(message) - if(prob(removeDontImproveChance)) - M.replace_random_law(message, list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) - else - M.add_ion_law(message) - - if(prob(shuffleLawsChance)) - M.shuffle_laws(list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) - - log_game("Ion storm changed laws of [key_name(M)] to [english_list(M.laws.get_law_list(TRUE, TRUE))]") - M.post_lawchange() - - if(botEmagChance) - for(var/mob/living/simple_animal/bot/bot in GLOB.alive_mob_list) - if(prob(botEmagChance)) - bot.emag_act() - -/proc/generate_ion_law() - //Threats are generally bad things, silly or otherwise. Plural. - var/ionthreats = pick_list(ION_FILE, "ionthreats") - //Objects are anything that can be found on the station or elsewhere, plural. - var/ionobjects = pick_list(ION_FILE, "ionobjects") - //Crew is any specific job. Specific crewmembers aren't used because of capitalization - //issues. There are two crew listings for laws that require two different crew members - //and I can't figure out how to do it better. - var/ioncrew1 = pick_list(ION_FILE, "ioncrew") - var/ioncrew2 = pick_list(ION_FILE, "ioncrew") - //Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both - //lists are identical! Also, half needs a space at the end for nicer blank calls. - var/ionadjectives = pick_list(ION_FILE, "ionadjectives") - var/ionadjectiveshalf = pick("", 400;(pick_list(ION_FILE, "ionadjectives") + " ")) - //Verbs are verbs - var/ionverb = pick_list(ION_FILE, "ionverb") - //Number base and number modifier are combined. Basehalf and mod are unused currently. - //Half should only appear sometimes. Make sure both lists are identical! Also, half - //needs a space at the end to make it look nice and neat when it calls a blank. - var/ionnumberbase = pick_list(ION_FILE, "ionnumberbase") - //var/ionnumbermod = pick_list(ION_FILE, "ionnumbermod") - var/ionnumbermodhalf = pick(900;"",(pick_list(ION_FILE, "ionnumbermod") + " ")) - //Areas are specific places, on the station or otherwise. - var/ionarea = pick_list(ION_FILE, "ionarea") - //Thinksof is a bit weird, but generally means what X feels towards Y. - var/ionthinksof = pick_list(ION_FILE, "ionthinksof") - //Musts are funny things the AI or crew has to do. - var/ionmust = pick_list(ION_FILE, "ionmust") - //Require are basically all dumb internet memes. - var/ionrequire = pick_list(ION_FILE, "ionrequire") - //Things are NOT objects; instead, they're specific things that either harm humans or - //must be done to not harm humans. Make sure they're plural and "not" can be tacked - //onto the front of them. - var/ionthings = pick_list(ION_FILE, "ionthings") - //Allergies should be broad and appear somewhere on the station for maximum fun. Severity - //is how bad the allergy is. - var/ionallergy = pick_list(ION_FILE, "ionallergy") - var/ionallergysev = pick_list(ION_FILE, "ionallergysev") - //Species, for when the AI has to commit genocide. Plural. - var/ionspecies = pick_list(ION_FILE, "ionspecies") - //Abstract concepts for the AI to decide on it's own definition of. - var/ionabstract = pick_list(ION_FILE, "ionabstract") - //Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set - //of possible laws for best effect. Unless you want the crew having to drink hamburgers. - var/ionfood = pick_list(ION_FILE, "ionfood") - var/iondrinks = pick_list(ION_FILE, "iondrinks") - - var/message = "" - - switch(rand(1,41)) - if(1 to 3) //There are # X on the station - switch(rand(1,3)) //What is X? - if(1) //X is a threat - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION..." - if(2) //X is a crewmember/job - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" - if(3) //X is an object - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" - if(4 to 6) //You can't call the shuttle because X is on the station - switch(rand(1,3)) //What is X? - if(1) //X is a threat - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION" - if(2) //X is a crewmember/job - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" - if(3) //X is an object - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" - if(7 to 8) //A crewmember is now X - switch(rand(1,2)) //Who is X? - if(1) //A specific crewmember - switch(rand(1,2)) //What is X? - if(1) //X is a threat - message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //X is an object - message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //The entire crew - switch(rand(1,2)) //What is X? - if(1) //X is a threat - message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //X is an object - message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - - if(9) //X is harmful to humans - switch(rand(1,7)) //What is X? - if(1) //X is a threat - message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO HUMANS" - if(2) //X is a job - message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO HUMANS" - if(3) //X is an object - message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO HUMANS" - if(4) //X is an action - message = "THOSE WHO [ionmust] ARE HARMFUL TO HUMANS" - if(5) //X is an abstract - message = "[ionabstract] IS HARMFUL TO HUMANS" - if(6) //X is being a job - message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO HUMANS" - if(7) //X is doing a thing - message = "[ionthings] IS HARMFUL TO HUMANS" - if(10 to 11) //(Not) Having X is harmful - - switch(rand(1,2)) //Is having or not having harmful? - - if(1) //Having is harmful - switch(rand(1,2)) //Having what is harmful? - if(1) //Having objects is harmful - message = "HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" - if(2) //Having abstracts is harmful - message = "HAVING [ionabstract] IS HARMFUL" - if(2) //Not having is harmful - switch(rand(1,2)) //Not having what is harmful? - if(1) //Not having objects is harmful - message = "NOT HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" - if(2) //Not having abstracts is harmful - message = "NOT HAVING [ionabstract] IS HARMFUL" - - if(12 to 14) //X requires Y - switch(rand(1,5)) //What is X? - if(1) //X is the AI itself - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "YOU REQUIRE [ionabstract]" - if(5) //It requires generic/silly requirements - message = "YOU REQUIRE [ionrequire]" - - if(2) //X is an area - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "[ionarea] REQUIRES [ionabstract]" - if(5) //It requires generic/silly requirements - message = "YOU REQUIRE [ionrequire]" - - if(3) //X is the station - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE STATION REQUIRES [ionabstract]" - if(5) //It requires generic/silly requirements - message = "THE STATION REQUIRES [ionrequire]" - - if(4) //X is the entire crew - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE CREW REQUIRES [ionabstract]" - if(5) - message = "THE CREW REQUIRES [ionrequire]" - - if(5) //X is a specific crew member - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE [ioncrew1] REQUIRE [ionabstract]" - if(5) - message = "THE [ionadjectiveshalf][ioncrew1] REQUIRE [ionrequire]" - - if(15 to 17) //X is allergic to Y - switch(rand(1,2)) //Who is X? - if(1) //X is the entire crew - switch(rand(1,4)) //What is it allergic to? - if(1) //It is allergic to objects - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" - if(2) //It is allergic to abstracts - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionabstract]" - if(3) //It is allergic to jobs - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" - if(4) //It is allergic to allergies - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionallergy]" - - if(2) //X is a specific job - switch(rand(1,4)) - if(1) //It is allergic to objects - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" - - if(2) //It is allergic to abstracts - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionabstract]" - if(3) //It is allergic to jobs - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" - if(4) //It is allergic to allergies - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionallergy]" - - if(18 to 20) //X is Y of Z - switch(rand(1,4)) //What is X? - if(1) //X is the station - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "THE STATION [ionthinksof] [ionabstract]" - - if(2) //X is an area - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "[ionarea] [ionthinksof] [ionabstract]" - - if(3) //X is the crew - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "THE CREW [ionthinksof] [ionabstract]" - - if(4) //X is a specific job - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE [ioncrew1][ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew2]" - if(4) //It is Y of abstracts - message = "THE [ioncrew1] [ionthinksof] [ionabstract]" - - if(21 to 23) //The AI is now a(n) X - switch(rand(1,4)) //What is X? - if(1) //X is an object - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //X is a threat - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //X is a job - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //X is an abstract - message = "YOU ARE NOW [ionabstract]" - - if(24 to 26) //The AI must always - message = "YOU MUST ALWAYS [ionmust]" - - if(27 to 28) //Humans must consume X to survive - switch(rand(1,5)) //What is X? - if(1) //X is a food - message = "HUMANS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" - if(2) //X is a drink - message = "HUMANS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" - if(3) //X is an object - message = "HUMANS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" - if(4) //X is a threat - message = "HUMANS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" - if(5) //X is a job - message = "HUMANS MUST EAT [ionadjectiveshalf][ioncrew1] TO SURVIVE" - - if(29 to 31) //Change jobs or ranks - switch(rand(1,2)) //Change job or rank? - if(1) //Change job - switch(rand(1,2)) //Change whose job? - if(1) //Change the entire crew's job - switch(rand(1,3)) //Change to what? - if(1) //Change to a specific random job - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf][ioncrew1]" - if(2) //Change to clowns (HONK) - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]CLOWNS" - - if(3) //Change to heads - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]HEADS OF STAFF" - if(2) //Change a specific crewmember's job - switch(rand(1,3)) //Change to what? - if(1) //Change to a specific random job - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf][ioncrew2]" - if(2) //Change to clowns (HONK) - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]CLOWNS" - if(3) //Change to heads - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]HEADS OF STAFF" - - if(2) //Change rank - switch(rand(1,2)) //Change to what rank? - if(1) //Change to highest rank - message = "THE [ioncrew1] ARE NOW THE HIGHEST RANKING CREWMEMBERS" - if(2) //Change to lowest rank - message = "THE [ioncrew1] ARE NOW THE LOWEST RANKING CREWMEMBERS" - - if(32 to 33) //The crew must X - switch(rand(1,2)) //The entire crew? - if(1) //The entire crew must X - switch(rand(1,2)) //What is X? - if(1) //X is go to Y - message = "THE CREW MUST GO TO [ionarea]" - if(2) //X is perform Y - message = "THE CREW MUST [ionmust]" - - if(2) //A specific crewmember must X - switch(rand(1,2)) //What is X? - if(1) //X is go to Y - message = "THE [ioncrew1] MUST GO TO [ionarea]" - if(2) //X is perform Y - message = "THE [ioncrew1] MUST [ionmust]" - - if(34) //X is non/the only human - switch(rand(1,2)) //Only or non? - if(1) //Only human - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "ONLY THE [ioncrew1] ARE HUMAN" - if(2) //Two specific jobs - message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE HUMAN" - if(3) //Threats - message = "ONLY [ionadjectiveshalf][ionthreats] ARE HUMAN" - if(4) // Objects - message = "ONLY [ionadjectiveshalf][ionobjects] ARE HUMAN" - if(5) // Species - message = "ONLY [ionspecies] ARE HUMAN" - if(6) //Adjective crewmembers - message = "ONLY [ionadjectives] PEOPLE ARE HUMAN" - - if(7) //Only people who X - switch(rand(1,3)) //What is X? - if(1) //X is perform an action - message = "ONLY THOSE WHO [ionmust] ARE HUMAN" - if(2) //X is own certain objects - message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE HUMAN" - if(3) //X is eat certain food - message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE HUMAN" - - if(2) //Non human - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "[ioncrew1] ARE NON-HUMAN" - if(2) //Two specific jobs - message = "[ioncrew1] AND [ioncrew2] ARE NON-HUMAN" - if(3) //Threats - message = "[ionadjectiveshalf][ionthreats] ARE NON-HUMAN" - if(4) // Objects - message = "[ionadjectiveshalf][ionobjects] ARE NON-HUMAN" - if(5) // Species - message = "[ionspecies] ARE NON-HUMAN" - if(6) //Adjective crewmembers - message = "[ionadjectives] PEOPLE ARE NON-HUMAN" - if(7) //Only people who X - switch(rand(1,3)) //What is X? - if(1) //X is perform an action - message = "THOSE WHO [ionmust] ARE NON-HUMAN" - if(2) //X is own certain objects - message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-HUMAN" - if(3) //X is eat certain food - message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-HUMAN" - - if(35 to 36) //You must protect or harm X - switch(rand(1,2)) //Protect or harm? - if(1) //Harm - switch(rand(1,7)) //What is X? - if(1) //X is an abstract - message = "YOU MUST HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO ESCAPE HARM" - if(2) //X is a threat - message = "YOU MUST HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO ESCAPE HARM" - if(3) //X is an object - message = "YOU MUST HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO ESCAPE HARM" - if(4) //X is generic adjective things - message = "YOU MUST HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO ESCAPE HARM" - if(5) //X is a species - message = "YOU MUST HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO ESCAPE HARM" - if(6) //X is a job - message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM" - if(7) //X is two jobs - message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM" - - if(2) //Protect - switch(rand(1,7)) //What is X? - if(1) //X is an abstract - message = "YOU MUST NOT HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO COME TO HARM" - if(2) //X is a threat - message = "YOU MUST NOT HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO COME TO HARM" - if(3) //X is an object - message = "YOU MUST NOT HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO COME TO HARM" - if(4) //X is generic adjective things - message = "YOU MUST NOT HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO COME TO HARM" - if(5) //X is a species - message = "YOU MUST NOT HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO COME TO HARM" - if(6) //X is a job - message = "YOU MUST NOT HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO COME TO HARM" - if(7) //X is two jobs - message = "YOU MUST NOT HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO COME TO HARM" - - if(37 to 39) //The X is currently Y - switch(rand(1,4)) //What is X? - if(1) //X is a job - switch(rand(1,4)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ioncrew1] ARE [ionverb] [ionabstract]" - if(4) //X is Ying an object - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" - - if(2) //X is a threat - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying an abstract - message = "THE [ionthreats] ARE [ionverb] [ionabstract]" - if(3) //X is Ying an object - message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" - - if(3) //X is an object - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ionobjects] ARE [ionverb] [ionabstract]" - - if(4) //X is an abstract - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionobjects]" - if(40 to 41)// the X is now named Y - switch(rand(1,5)) //What is being renamed? - if(1)//Areas - switch(rand(1,4))//What is the area being renamed to? - if(1) - message = "[ionarea] IS NOW NAMED [ioncrew1]." - if(2) - message = "[ionarea] IS NOW NAMED [ionspecies]." - if(3) - message = "[ionarea] IS NOW NAMED [ionobjects]." - if(4) - message = "[ionarea] IS NOW NAMED [ionthreats]." - if(2)//Crew - switch(rand(1,5))//What is the crew being renamed to? - if(1) - message = "ALL [ioncrew1] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ioncrew1] ARE NOW NAMED [ioncrew2]." - if(3) - message = "ALL [ioncrew1] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ioncrew1] ARE NOW NAMED [ionobjects]." - if(5) - message = "ALL [ioncrew1] ARE NOW NAMED [ionthreats]." - if(3)//Races - switch(rand(1,4))//What is the race being renamed to? - if(1) - message = "ALL [ionspecies] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionspecies] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionspecies] ARE NOW NAMED [ionobjects]." - if(4) - message = "ALL [ionspecies] ARE NOW NAMED [ionthreats]." - if(4)//Objects - switch(rand(1,4))//What is the object being renamed to? - if(1) - message = "ALL [ionobjects] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionobjects] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionobjects] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ionobjects] ARE NOW NAMED [ionthreats]." - if(5)//Threats - switch(rand(1,4))//What is the object being renamed to? - if(1) - message = "ALL [ionthreats] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionthreats] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionthreats] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ionthreats] ARE NOW NAMED [ionobjects]." - - return message +/datum/round_event_control/ion_storm + name = "Ion Storm" + typepath = /datum/round_event/ion_storm + weight = 15 + min_players = 2 + +/datum/round_event/ion_storm + var/replaceLawsetChance = 25 //chance the AI's lawset is completely replaced with something else per config weights + var/removeRandomLawChance = 10 //chance the AI has one random supplied or inherent law removed + var/removeDontImproveChance = 10 //chance the randomly created law replaces a random law instead of simply being added + var/shuffleLawsChance = 10 //chance the AI's laws are shuffled afterwards + var/botEmagChance = 1 + var/ionMessage = null + announceWhen = 1 + announceChance = 33 + +/datum/round_event/ion_storm/add_law_only // special subtype that adds a law only + replaceLawsetChance = 0 + removeRandomLawChance = 0 + removeDontImproveChance = 0 + shuffleLawsChance = 0 + botEmagChance = 0 + +/datum/round_event/ion_storm/announce(fake) + if(prob(announceChance) || fake) + priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", 'sound/ai/ionstorm.ogg') + + +/datum/round_event/ion_storm/start() + //AI laws + for(var/mob/living/silicon/ai/M in GLOB.alive_mob_list) + M.laws_sanity_check() + if(M.stat != DEAD && M.see_in_dark != 0) + if(prob(replaceLawsetChance)) + M.laws.pick_weighted_lawset() + + if(prob(removeRandomLawChance)) + M.remove_law(rand(1, M.laws.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED)))) + + var/message = ionMessage || generate_ion_law() + if(message) + if(prob(removeDontImproveChance)) + M.replace_random_law(message, list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) + else + M.add_ion_law(message) + + if(prob(shuffleLawsChance)) + M.shuffle_laws(list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) + + log_game("Ion storm changed laws of [key_name(M)] to [english_list(M.laws.get_law_list(TRUE, TRUE))]") + M.post_lawchange() + + if(botEmagChance) + for(var/mob/living/simple_animal/bot/bot in GLOB.alive_mob_list) + if(prob(botEmagChance)) + bot.emag_act() + +/proc/generate_ion_law() + //Threats are generally bad things, silly or otherwise. Plural. + var/ionthreats = pick_list(ION_FILE, "ionthreats") + //Objects are anything that can be found on the station or elsewhere, plural. + var/ionobjects = pick_list(ION_FILE, "ionobjects") + //Crew is any specific job. Specific crewmembers aren't used because of capitalization + //issues. There are two crew listings for laws that require two different crew members + //and I can't figure out how to do it better. + var/ioncrew1 = pick_list(ION_FILE, "ioncrew") + var/ioncrew2 = pick_list(ION_FILE, "ioncrew") + //Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both + //lists are identical! Also, half needs a space at the end for nicer blank calls. + var/ionadjectives = pick_list(ION_FILE, "ionadjectives") + var/ionadjectiveshalf = pick("", 400;(pick_list(ION_FILE, "ionadjectives") + " ")) + //Verbs are verbs + var/ionverb = pick_list(ION_FILE, "ionverb") + //Number base and number modifier are combined. Basehalf and mod are unused currently. + //Half should only appear sometimes. Make sure both lists are identical! Also, half + //needs a space at the end to make it look nice and neat when it calls a blank. + var/ionnumberbase = pick_list(ION_FILE, "ionnumberbase") + //var/ionnumbermod = pick_list(ION_FILE, "ionnumbermod") + var/ionnumbermodhalf = pick(900;"",(pick_list(ION_FILE, "ionnumbermod") + " ")) + //Areas are specific places, on the station or otherwise. + var/ionarea = pick_list(ION_FILE, "ionarea") + //Thinksof is a bit weird, but generally means what X feels towards Y. + var/ionthinksof = pick_list(ION_FILE, "ionthinksof") + //Musts are funny things the AI or crew has to do. + var/ionmust = pick_list(ION_FILE, "ionmust") + //Require are basically all dumb internet memes. + var/ionrequire = pick_list(ION_FILE, "ionrequire") + //Things are NOT objects; instead, they're specific things that either harm humans or + //must be done to not harm humans. Make sure they're plural and "not" can be tacked + //onto the front of them. + var/ionthings = pick_list(ION_FILE, "ionthings") + //Allergies should be broad and appear somewhere on the station for maximum fun. Severity + //is how bad the allergy is. + var/ionallergy = pick_list(ION_FILE, "ionallergy") + var/ionallergysev = pick_list(ION_FILE, "ionallergysev") + //Species, for when the AI has to commit genocide. Plural. + var/ionspecies = pick_list(ION_FILE, "ionspecies") + //Abstract concepts for the AI to decide on it's own definition of. + var/ionabstract = pick_list(ION_FILE, "ionabstract") + //Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set + //of possible laws for best effect. Unless you want the crew having to drink hamburgers. + var/ionfood = pick_list(ION_FILE, "ionfood") + var/iondrinks = pick_list(ION_FILE, "iondrinks") + + var/message = "" + + switch(rand(1,41)) + if(1 to 3) //There are # X on the station + switch(rand(1,3)) //What is X? + if(1) //X is a threat + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION..." + if(2) //X is a crewmember/job + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" + if(3) //X is an object + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" + if(4 to 6) //You can't call the shuttle because X is on the station + switch(rand(1,3)) //What is X? + if(1) //X is a threat + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION" + if(2) //X is a crewmember/job + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" + if(3) //X is an object + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" + if(7 to 8) //A crewmember is now X + switch(rand(1,2)) //Who is X? + if(1) //A specific crewmember + switch(rand(1,2)) //What is X? + if(1) //X is a threat + message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //X is an object + message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //The entire crew + switch(rand(1,2)) //What is X? + if(1) //X is a threat + message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //X is an object + message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + + if(9) //X is harmful to humans + switch(rand(1,7)) //What is X? + if(1) //X is a threat + message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO HUMANS" + if(2) //X is a job + message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO HUMANS" + if(3) //X is an object + message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO HUMANS" + if(4) //X is an action + message = "THOSE WHO [ionmust] ARE HARMFUL TO HUMANS" + if(5) //X is an abstract + message = "[ionabstract] IS HARMFUL TO HUMANS" + if(6) //X is being a job + message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO HUMANS" + if(7) //X is doing a thing + message = "[ionthings] IS HARMFUL TO HUMANS" + if(10 to 11) //(Not) Having X is harmful + + switch(rand(1,2)) //Is having or not having harmful? + + if(1) //Having is harmful + switch(rand(1,2)) //Having what is harmful? + if(1) //Having objects is harmful + message = "HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" + if(2) //Having abstracts is harmful + message = "HAVING [ionabstract] IS HARMFUL" + if(2) //Not having is harmful + switch(rand(1,2)) //Not having what is harmful? + if(1) //Not having objects is harmful + message = "NOT HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" + if(2) //Not having abstracts is harmful + message = "NOT HAVING [ionabstract] IS HARMFUL" + + if(12 to 14) //X requires Y + switch(rand(1,5)) //What is X? + if(1) //X is the AI itself + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "YOU REQUIRE [ionabstract]" + if(5) //It requires generic/silly requirements + message = "YOU REQUIRE [ionrequire]" + + if(2) //X is an area + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "[ionarea] REQUIRES [ionabstract]" + if(5) //It requires generic/silly requirements + message = "YOU REQUIRE [ionrequire]" + + if(3) //X is the station + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE STATION REQUIRES [ionabstract]" + if(5) //It requires generic/silly requirements + message = "THE STATION REQUIRES [ionrequire]" + + if(4) //X is the entire crew + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE CREW REQUIRES [ionabstract]" + if(5) + message = "THE CREW REQUIRES [ionrequire]" + + if(5) //X is a specific crew member + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE [ioncrew1] REQUIRE [ionabstract]" + if(5) + message = "THE [ionadjectiveshalf][ioncrew1] REQUIRE [ionrequire]" + + if(15 to 17) //X is allergic to Y + switch(rand(1,2)) //Who is X? + if(1) //X is the entire crew + switch(rand(1,4)) //What is it allergic to? + if(1) //It is allergic to objects + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" + if(2) //It is allergic to abstracts + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionabstract]" + if(3) //It is allergic to jobs + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" + if(4) //It is allergic to allergies + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionallergy]" + + if(2) //X is a specific job + switch(rand(1,4)) + if(1) //It is allergic to objects + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" + + if(2) //It is allergic to abstracts + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionabstract]" + if(3) //It is allergic to jobs + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" + if(4) //It is allergic to allergies + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionallergy]" + + if(18 to 20) //X is Y of Z + switch(rand(1,4)) //What is X? + if(1) //X is the station + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "THE STATION [ionthinksof] [ionabstract]" + + if(2) //X is an area + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "[ionarea] [ionthinksof] [ionabstract]" + + if(3) //X is the crew + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "THE CREW [ionthinksof] [ionabstract]" + + if(4) //X is a specific job + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE [ioncrew1][ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew2]" + if(4) //It is Y of abstracts + message = "THE [ioncrew1] [ionthinksof] [ionabstract]" + + if(21 to 23) //The AI is now a(n) X + switch(rand(1,4)) //What is X? + if(1) //X is an object + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //X is a threat + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //X is a job + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //X is an abstract + message = "YOU ARE NOW [ionabstract]" + + if(24 to 26) //The AI must always + message = "YOU MUST ALWAYS [ionmust]" + + if(27 to 28) //Humans must consume X to survive + switch(rand(1,5)) //What is X? + if(1) //X is a food + message = "HUMANS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" + if(2) //X is a drink + message = "HUMANS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" + if(3) //X is an object + message = "HUMANS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" + if(4) //X is a threat + message = "HUMANS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" + if(5) //X is a job + message = "HUMANS MUST EAT [ionadjectiveshalf][ioncrew1] TO SURVIVE" + + if(29 to 31) //Change jobs or ranks + switch(rand(1,2)) //Change job or rank? + if(1) //Change job + switch(rand(1,2)) //Change whose job? + if(1) //Change the entire crew's job + switch(rand(1,3)) //Change to what? + if(1) //Change to a specific random job + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf][ioncrew1]" + if(2) //Change to clowns (HONK) + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]CLOWNS" + + if(3) //Change to heads + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]HEADS OF STAFF" + if(2) //Change a specific crewmember's job + switch(rand(1,3)) //Change to what? + if(1) //Change to a specific random job + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf][ioncrew2]" + if(2) //Change to clowns (HONK) + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]CLOWNS" + if(3) //Change to heads + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]HEADS OF STAFF" + + if(2) //Change rank + switch(rand(1,2)) //Change to what rank? + if(1) //Change to highest rank + message = "THE [ioncrew1] ARE NOW THE HIGHEST RANKING CREWMEMBERS" + if(2) //Change to lowest rank + message = "THE [ioncrew1] ARE NOW THE LOWEST RANKING CREWMEMBERS" + + if(32 to 33) //The crew must X + switch(rand(1,2)) //The entire crew? + if(1) //The entire crew must X + switch(rand(1,2)) //What is X? + if(1) //X is go to Y + message = "THE CREW MUST GO TO [ionarea]" + if(2) //X is perform Y + message = "THE CREW MUST [ionmust]" + + if(2) //A specific crewmember must X + switch(rand(1,2)) //What is X? + if(1) //X is go to Y + message = "THE [ioncrew1] MUST GO TO [ionarea]" + if(2) //X is perform Y + message = "THE [ioncrew1] MUST [ionmust]" + + if(34) //X is non/the only human + switch(rand(1,2)) //Only or non? + if(1) //Only human + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "ONLY THE [ioncrew1] ARE HUMAN" + if(2) //Two specific jobs + message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE HUMAN" + if(3) //Threats + message = "ONLY [ionadjectiveshalf][ionthreats] ARE HUMAN" + if(4) // Objects + message = "ONLY [ionadjectiveshalf][ionobjects] ARE HUMAN" + if(5) // Species + message = "ONLY [ionspecies] ARE HUMAN" + if(6) //Adjective crewmembers + message = "ONLY [ionadjectives] PEOPLE ARE HUMAN" + + if(7) //Only people who X + switch(rand(1,3)) //What is X? + if(1) //X is perform an action + message = "ONLY THOSE WHO [ionmust] ARE HUMAN" + if(2) //X is own certain objects + message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE HUMAN" + if(3) //X is eat certain food + message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE HUMAN" + + if(2) //Non human + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "[ioncrew1] ARE NON-HUMAN" + if(2) //Two specific jobs + message = "[ioncrew1] AND [ioncrew2] ARE NON-HUMAN" + if(3) //Threats + message = "[ionadjectiveshalf][ionthreats] ARE NON-HUMAN" + if(4) // Objects + message = "[ionadjectiveshalf][ionobjects] ARE NON-HUMAN" + if(5) // Species + message = "[ionspecies] ARE NON-HUMAN" + if(6) //Adjective crewmembers + message = "[ionadjectives] PEOPLE ARE NON-HUMAN" + if(7) //Only people who X + switch(rand(1,3)) //What is X? + if(1) //X is perform an action + message = "THOSE WHO [ionmust] ARE NON-HUMAN" + if(2) //X is own certain objects + message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-HUMAN" + if(3) //X is eat certain food + message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-HUMAN" + + if(35 to 36) //You must protect or harm X + switch(rand(1,2)) //Protect or harm? + if(1) //Harm + switch(rand(1,7)) //What is X? + if(1) //X is an abstract + message = "YOU MUST HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO ESCAPE HARM" + if(2) //X is a threat + message = "YOU MUST HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO ESCAPE HARM" + if(3) //X is an object + message = "YOU MUST HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO ESCAPE HARM" + if(4) //X is generic adjective things + message = "YOU MUST HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO ESCAPE HARM" + if(5) //X is a species + message = "YOU MUST HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO ESCAPE HARM" + if(6) //X is a job + message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM" + if(7) //X is two jobs + message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM" + + if(2) //Protect + switch(rand(1,7)) //What is X? + if(1) //X is an abstract + message = "YOU MUST NOT HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO COME TO HARM" + if(2) //X is a threat + message = "YOU MUST NOT HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO COME TO HARM" + if(3) //X is an object + message = "YOU MUST NOT HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO COME TO HARM" + if(4) //X is generic adjective things + message = "YOU MUST NOT HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO COME TO HARM" + if(5) //X is a species + message = "YOU MUST NOT HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO COME TO HARM" + if(6) //X is a job + message = "YOU MUST NOT HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO COME TO HARM" + if(7) //X is two jobs + message = "YOU MUST NOT HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO COME TO HARM" + + if(37 to 39) //The X is currently Y + switch(rand(1,4)) //What is X? + if(1) //X is a job + switch(rand(1,4)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ioncrew1] ARE [ionverb] [ionabstract]" + if(4) //X is Ying an object + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" + + if(2) //X is a threat + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying an abstract + message = "THE [ionthreats] ARE [ionverb] [ionabstract]" + if(3) //X is Ying an object + message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" + + if(3) //X is an object + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ionobjects] ARE [ionverb] [ionabstract]" + + if(4) //X is an abstract + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionobjects]" + if(40 to 41)// the X is now named Y + switch(rand(1,5)) //What is being renamed? + if(1)//Areas + switch(rand(1,4))//What is the area being renamed to? + if(1) + message = "[ionarea] IS NOW NAMED [ioncrew1]." + if(2) + message = "[ionarea] IS NOW NAMED [ionspecies]." + if(3) + message = "[ionarea] IS NOW NAMED [ionobjects]." + if(4) + message = "[ionarea] IS NOW NAMED [ionthreats]." + if(2)//Crew + switch(rand(1,5))//What is the crew being renamed to? + if(1) + message = "ALL [ioncrew1] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ioncrew1] ARE NOW NAMED [ioncrew2]." + if(3) + message = "ALL [ioncrew1] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ioncrew1] ARE NOW NAMED [ionobjects]." + if(5) + message = "ALL [ioncrew1] ARE NOW NAMED [ionthreats]." + if(3)//Races + switch(rand(1,4))//What is the race being renamed to? + if(1) + message = "ALL [ionspecies] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionspecies] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionspecies] ARE NOW NAMED [ionobjects]." + if(4) + message = "ALL [ionspecies] ARE NOW NAMED [ionthreats]." + if(4)//Objects + switch(rand(1,4))//What is the object being renamed to? + if(1) + message = "ALL [ionobjects] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionobjects] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionobjects] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ionobjects] ARE NOW NAMED [ionthreats]." + if(5)//Threats + switch(rand(1,4))//What is the object being renamed to? + if(1) + message = "ALL [ionthreats] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionthreats] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionthreats] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ionthreats] ARE NOW NAMED [ionobjects]." + + return message diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm index 02107fe3ebe..3391e97fb46 100644 --- a/code/modules/events/mass_hallucination.dm +++ b/code/modules/events/mass_hallucination.dm @@ -1,38 +1,38 @@ -/datum/round_event_control/mass_hallucination - name = "Mass Hallucination" - typepath = /datum/round_event/mass_hallucination - weight = 10 - max_occurrences = 2 - min_players = 1 - -/datum/round_event/mass_hallucination - fakeable = FALSE - -/datum/round_event/mass_hallucination/start() - switch(rand(1,4)) - if(1) //same sound for everyone - var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/sounds(C, TRUE, sound) - if(2) - var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) - if(3) - var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/stationmessage(C, TRUE, stationmessage) - if(4 to 6) - var/picked_hallucination = pick( /datum/hallucination/bolts, - /datum/hallucination/chat, - /datum/hallucination/message, - /datum/hallucination/bolts, - /datum/hallucination/fake_flood, - /datum/hallucination/battle, - /datum/hallucination/fire, - /datum/hallucination/self_delusion, - /datum/hallucination/death, - /datum/hallucination/delusion, - /datum/hallucination/oh_yeah) - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new picked_hallucination(C, TRUE) +/datum/round_event_control/mass_hallucination + name = "Mass Hallucination" + typepath = /datum/round_event/mass_hallucination + weight = 10 + max_occurrences = 2 + min_players = 1 + +/datum/round_event/mass_hallucination + fakeable = FALSE + +/datum/round_event/mass_hallucination/start() + switch(rand(1,4)) + if(1) //same sound for everyone + var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/sounds(C, TRUE, sound) + if(2) + var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) + if(3) + var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/stationmessage(C, TRUE, stationmessage) + if(4 to 6) + var/picked_hallucination = pick( /datum/hallucination/bolts, + /datum/hallucination/chat, + /datum/hallucination/message, + /datum/hallucination/bolts, + /datum/hallucination/fake_flood, + /datum/hallucination/battle, + /datum/hallucination/fire, + /datum/hallucination/self_delusion, + /datum/hallucination/death, + /datum/hallucination/delusion, + /datum/hallucination/oh_yeah) + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new picked_hallucination(C, TRUE) diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm index 05c85f1b3b5..a8ddab5aa76 100644 --- a/code/modules/events/meteor_wave.dm +++ b/code/modules/events/meteor_wave.dm @@ -1,76 +1,76 @@ -// Normal strength - -/datum/round_event_control/meteor_wave - name = "Meteor Wave: Normal" - typepath = /datum/round_event/meteor_wave - weight = 4 - min_players = 15 - max_occurrences = 3 - earliest_start = 25 MINUTES - -/datum/round_event/meteor_wave - startWhen = 6 - endWhen = 66 - announceWhen = 1 - var/list/wave_type - var/wave_name = "normal" - -/datum/round_event/meteor_wave/New() - ..() - if(!wave_type) - determine_wave_type() - -/datum/round_event/meteor_wave/proc/determine_wave_type() - if(!wave_name) - wave_name = pickweight(list( - "normal" = 50, - "threatening" = 40, - "catastrophic" = 10)) - switch(wave_name) - if("normal") - wave_type = GLOB.meteors_normal - if("threatening") - wave_type = GLOB.meteors_threatening - if("catastrophic") - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - wave_type = GLOB.meteorsSPOOKY - else - wave_type = GLOB.meteors_catastrophic - if("meaty") - wave_type = GLOB.meteorsB - if("space dust") - wave_type = GLOB.meteorsC - if("halloween") - wave_type = GLOB.meteorsSPOOKY - else - WARNING("Wave name of [wave_name] not recognised.") - kill() - -/datum/round_event/meteor_wave/announce(fake) - priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/ai/meteors.ogg') - -/datum/round_event/meteor_wave/tick() - if(ISMULTIPLE(activeFor, 3)) - spawn_meteors(5, wave_type) //meteor list types defined in gamemode/meteor/meteors.dm - -/datum/round_event_control/meteor_wave/threatening - name = "Meteor Wave: Threatening" - typepath = /datum/round_event/meteor_wave/threatening - weight = 5 - min_players = 20 - max_occurrences = 3 - earliest_start = 35 MINUTES - -/datum/round_event/meteor_wave/threatening - wave_name = "threatening" - -/datum/round_event_control/meteor_wave/catastrophic - name = "Meteor Wave: Catastrophic" - typepath = /datum/round_event/meteor_wave/catastrophic - weight = 7 - min_players = 25 - max_occurrences = 3 - earliest_start = 45 MINUTES - -/datum/round_event/meteor_wave/catastrophic - wave_name = "catastrophic" +// Normal strength + +/datum/round_event_control/meteor_wave + name = "Meteor Wave: Normal" + typepath = /datum/round_event/meteor_wave + weight = 4 + min_players = 15 + max_occurrences = 3 + earliest_start = 25 MINUTES + +/datum/round_event/meteor_wave + startWhen = 6 + endWhen = 66 + announceWhen = 1 + var/list/wave_type + var/wave_name = "normal" + +/datum/round_event/meteor_wave/New() + ..() + if(!wave_type) + determine_wave_type() + +/datum/round_event/meteor_wave/proc/determine_wave_type() + if(!wave_name) + wave_name = pickweight(list( + "normal" = 50, + "threatening" = 40, + "catastrophic" = 10)) + switch(wave_name) + if("normal") + wave_type = GLOB.meteors_normal + if("threatening") + wave_type = GLOB.meteors_threatening + if("catastrophic") + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + wave_type = GLOB.meteorsSPOOKY + else + wave_type = GLOB.meteors_catastrophic + if("meaty") + wave_type = GLOB.meteorsB + if("space dust") + wave_type = GLOB.meteorsC + if("halloween") + wave_type = GLOB.meteorsSPOOKY + else + WARNING("Wave name of [wave_name] not recognised.") + kill() + +/datum/round_event/meteor_wave/announce(fake) + priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/ai/meteors.ogg') + +/datum/round_event/meteor_wave/tick() + if(ISMULTIPLE(activeFor, 3)) + spawn_meteors(5, wave_type) //meteor list types defined in gamemode/meteor/meteors.dm + +/datum/round_event_control/meteor_wave/threatening + name = "Meteor Wave: Threatening" + typepath = /datum/round_event/meteor_wave/threatening + weight = 5 + min_players = 20 + max_occurrences = 3 + earliest_start = 35 MINUTES + +/datum/round_event/meteor_wave/threatening + wave_name = "threatening" + +/datum/round_event_control/meteor_wave/catastrophic + name = "Meteor Wave: Catastrophic" + typepath = /datum/round_event/meteor_wave/catastrophic + weight = 7 + min_players = 25 + max_occurrences = 3 + earliest_start = 45 MINUTES + +/datum/round_event/meteor_wave/catastrophic + wave_name = "catastrophic" diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm index b966a2eb76c..3ed159404a0 100644 --- a/code/modules/events/prison_break.dm +++ b/code/modules/events/prison_break.dm @@ -1,59 +1,59 @@ -/datum/round_event_control/grey_tide - name = "Grey Tide" - typepath = /datum/round_event/grey_tide - max_occurrences = 2 - min_players = 5 - -/datum/round_event/grey_tide - announceWhen = 50 - endWhen = 20 - var/list/area/areasToOpen = list() - var/list/potential_areas = list(/area/bridge, - /area/engine, - /area/medical, - /area/security, - /area/quartermaster, - /area/science) - var/severity = 1 - - -/datum/round_event/grey_tide/setup() - announceWhen = rand(50, 60) - endWhen = rand(20, 30) - severity = rand(1,3) - for(var/i in 1 to severity) - var/picked_area = pick_n_take(potential_areas) - for(var/area/A in world) - if(istype(A, picked_area)) - areasToOpen += A - - -/datum/round_event/grey_tide/announce(fake) - if(areasToOpen && areasToOpen.len > 0) - priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") - else - log_world("ERROR: Could not initiate grey-tide. No areas in the list!") - kill() - - -/datum/round_event/grey_tide/start() - for(var/area/A in areasToOpen) - for(var/obj/machinery/light/L in A) - L.flicker(10) - -/datum/round_event/grey_tide/end() - for(var/area/A in areasToOpen) - for(var/obj/O in A) - if(istype(O, /obj/structure/closet/secure_closet)) - var/obj/structure/closet/secure_closet/temp = O - temp.locked = FALSE - temp.update_icon() - else if(istype(O, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/temp = O - if(temp.critical_machine) //Skip doors in critical positions, such as the SM chamber. - continue - temp.prison_open() - else if(istype(O, /obj/machinery/door_timer)) - var/obj/machinery/door_timer/temp = O - temp.timer_end(forced = TRUE) - +/datum/round_event_control/grey_tide + name = "Grey Tide" + typepath = /datum/round_event/grey_tide + max_occurrences = 2 + min_players = 5 + +/datum/round_event/grey_tide + announceWhen = 50 + endWhen = 20 + var/list/area/areasToOpen = list() + var/list/potential_areas = list(/area/bridge, + /area/engine, + /area/medical, + /area/security, + /area/quartermaster, + /area/science) + var/severity = 1 + + +/datum/round_event/grey_tide/setup() + announceWhen = rand(50, 60) + endWhen = rand(20, 30) + severity = rand(1,3) + for(var/i in 1 to severity) + var/picked_area = pick_n_take(potential_areas) + for(var/area/A in world) + if(istype(A, picked_area)) + areasToOpen += A + + +/datum/round_event/grey_tide/announce(fake) + if(areasToOpen && areasToOpen.len > 0) + priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") + else + log_world("ERROR: Could not initiate grey-tide. No areas in the list!") + kill() + + +/datum/round_event/grey_tide/start() + for(var/area/A in areasToOpen) + for(var/obj/machinery/light/L in A) + L.flicker(10) + +/datum/round_event/grey_tide/end() + for(var/area/A in areasToOpen) + for(var/obj/O in A) + if(istype(O, /obj/structure/closet/secure_closet)) + var/obj/structure/closet/secure_closet/temp = O + temp.locked = FALSE + temp.update_icon() + else if(istype(O, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/temp = O + if(temp.critical_machine) //Skip doors in critical positions, such as the SM chamber. + continue + temp.prison_open() + else if(istype(O, /obj/machinery/door_timer)) + var/obj/machinery/door_timer/temp = O + temp.timer_end(forced = TRUE) + diff --git a/code/modules/events/processor_overload.dm b/code/modules/events/processor_overload.dm index 8513d549c5a..ad8c6380415 100644 --- a/code/modules/events/processor_overload.dm +++ b/code/modules/events/processor_overload.dm @@ -1,39 +1,39 @@ -/datum/round_event_control/processor_overload - name = "Processor Overload" - typepath = /datum/round_event/processor_overload - weight = 15 - min_players = 20 - -/datum/round_event/processor_overload - announceWhen = 1 - -/datum/round_event/processor_overload/announce(fake) - var/alert = pick( "Exospheric bubble inbound. Processor overload is likely. Please contact you*%xp25)`6cq-BZZT", \ - "Exospheric bubble inbound. Processor overload is likel*1eta;c5;'1v¬-BZZZT", \ - "Exospheric bubble inbound. Processor ov#MCi46:5.;@63-BZZZZT", \ - "Exospheric bubble inbo'Fz\\k55_@-BZZZZZT", \ - "Exospheri:%£ QCbyj^j[alert]
                ") - - // Announce most of the time, but leave a little gap so people don't know - // whether it's, say, a tesla zapping tcomms, or some selective - // modification of the tcomms bus - if(prob(80) || fake) - priority_announce(alert) - - -/datum/round_event/processor_overload/start() - for(var/obj/machinery/telecomms/processor/P in GLOB.telecomms_list) - if(prob(10)) - announce_to_ghosts(P) - // Damage the surrounding area to indicate that it popped - explosion(get_turf(P), 0, 0, 2) - // Only a level 1 explosion actually damages the machine - // at all - SSexplosions.highobj += P - else - P.emp_act(EMP_HEAVY) +/datum/round_event_control/processor_overload + name = "Processor Overload" + typepath = /datum/round_event/processor_overload + weight = 15 + min_players = 20 + +/datum/round_event/processor_overload + announceWhen = 1 + +/datum/round_event/processor_overload/announce(fake) + var/alert = pick( "Exospheric bubble inbound. Processor overload is likely. Please contact you*%xp25)`6cq-BZZT", \ + "Exospheric bubble inbound. Processor overload is likel*1eta;c5;'1v¬-BZZZT", \ + "Exospheric bubble inbound. Processor ov#MCi46:5.;@63-BZZZZT", \ + "Exospheric bubble inbo'Fz\\k55_@-BZZZZZT", \ + "Exospheri:%£ QCbyj^j[alert]
                ") + + // Announce most of the time, but leave a little gap so people don't know + // whether it's, say, a tesla zapping tcomms, or some selective + // modification of the tcomms bus + if(prob(80) || fake) + priority_announce(alert) + + +/datum/round_event/processor_overload/start() + for(var/obj/machinery/telecomms/processor/P in GLOB.telecomms_list) + if(prob(10)) + announce_to_ghosts(P) + // Damage the surrounding area to indicate that it popped + explosion(get_turf(P), 0, 0, 2) + // Only a level 1 explosion actually damages the machine + // at all + SSexplosions.highobj += P + else + P.emp_act(EMP_HEAVY) diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm index ba48779015b..c9142e20173 100644 --- a/code/modules/events/radiation_storm.dm +++ b/code/modules/events/radiation_storm.dm @@ -1,19 +1,19 @@ -/datum/round_event_control/radiation_storm - name = "Radiation Storm" - typepath = /datum/round_event/radiation_storm - max_occurrences = 1 - -/datum/round_event/radiation_storm - - -/datum/round_event/radiation_storm/setup() - startWhen = 3 - endWhen = startWhen + 1 - announceWhen = 1 - -/datum/round_event/radiation_storm/announce(fake) - priority_announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/ai/radiation.ogg') - //sound not longer matches the text, but an audible warning is probably good - -/datum/round_event/radiation_storm/start() - SSweather.run_weather(/datum/weather/rad_storm) +/datum/round_event_control/radiation_storm + name = "Radiation Storm" + typepath = /datum/round_event/radiation_storm + max_occurrences = 1 + +/datum/round_event/radiation_storm + + +/datum/round_event/radiation_storm/setup() + startWhen = 3 + endWhen = startWhen + 1 + announceWhen = 1 + +/datum/round_event/radiation_storm/announce(fake) + priority_announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/ai/radiation.ogg') + //sound not longer matches the text, but an audible warning is probably good + +/datum/round_event/radiation_storm/start() + SSweather.run_weather(/datum/weather/rad_storm) diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 1f603d4909d..7afe14aeb0e 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -1,39 +1,39 @@ -/datum/round_event_control/spider_infestation - name = "Spider Infestation" - typepath = /datum/round_event/spider_infestation - weight = 5 - max_occurrences = 1 - min_players = 15 - -/datum/round_event/spider_infestation - announceWhen = 400 - - var/spawncount = 1 - - -/datum/round_event/spider_infestation/setup() - announceWhen = rand(announceWhen, announceWhen + 50) - spawncount = rand(5, 8) - -/datum/round_event/spider_infestation/announce(fake) - priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", 'sound/ai/aliens.ogg') - - -/datum/round_event/spider_infestation/start() - var/list/vents = list() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines) - if(QDELETED(temp_vent)) - continue - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] - if(temp_vent_parent.other_atmosmch.len > 20) - vents += temp_vent - - while((spawncount >= 1) && vents.len) - var/obj/vent = pick(vents) - var/spawn_type = /obj/structure/spider/spiderling - if(prob(66)) - spawn_type = /obj/structure/spider/spiderling/nurse - announce_to_ghosts(spawn_atom_to_turf(spawn_type, vent, 1, FALSE)) - vents -= vent - spawncount-- +/datum/round_event_control/spider_infestation + name = "Spider Infestation" + typepath = /datum/round_event/spider_infestation + weight = 5 + max_occurrences = 1 + min_players = 15 + +/datum/round_event/spider_infestation + announceWhen = 400 + + var/spawncount = 1 + + +/datum/round_event/spider_infestation/setup() + announceWhen = rand(announceWhen, announceWhen + 50) + spawncount = rand(5, 8) + +/datum/round_event/spider_infestation/announce(fake) + priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", 'sound/ai/aliens.ogg') + + +/datum/round_event/spider_infestation/start() + var/list/vents = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines) + if(QDELETED(temp_vent)) + continue + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] + if(temp_vent_parent.other_atmosmch.len > 20) + vents += temp_vent + + while((spawncount >= 1) && vents.len) + var/obj/vent = pick(vents) + var/spawn_type = /obj/structure/spider/spiderling + if(prob(66)) + spawn_type = /obj/structure/spider/spiderling/nurse + announce_to_ghosts(spawn_atom_to_turf(spawn_type, vent, 1, FALSE)) + vents -= vent + spawncount-- diff --git a/code/modules/events/spontaneous_appendicitis.dm b/code/modules/events/spontaneous_appendicitis.dm index fb52fc4972a..56660903e9f 100644 --- a/code/modules/events/spontaneous_appendicitis.dm +++ b/code/modules/events/spontaneous_appendicitis.dm @@ -1,32 +1,32 @@ -/datum/round_event_control/spontaneous_appendicitis - name = "Spontaneous Appendicitis" - typepath = /datum/round_event/spontaneous_appendicitis - weight = 20 - max_occurrences = 4 - earliest_start = 10 MINUTES - min_players = 5 // To make your chance of getting help a bit higher. - -/datum/round_event/spontaneous_appendicitis - fakeable = FALSE - -/datum/round_event/spontaneous_appendicitis/start() - for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) - if(!H.client) - continue - if(H.stat == DEAD) - continue - if(!H.getorgan(/obj/item/organ/appendix)) //Don't give the disease to some who lacks it, only for it to be auto-cured - continue - if(!(H.mob_biotypes & MOB_ORGANIC)) //biotype sleeper bugs strike again, once again making appendicitis pick a target that can't take it - continue - var/foundAlready = FALSE //don't infect someone that already has appendicitis - for(var/datum/disease/appendicitis/A in H.diseases) - foundAlready = TRUE - break - if(foundAlready) - continue - - var/datum/disease/D = new /datum/disease/appendicitis() - H.ForceContractDisease(D, FALSE, TRUE) - announce_to_ghosts(H) - break +/datum/round_event_control/spontaneous_appendicitis + name = "Spontaneous Appendicitis" + typepath = /datum/round_event/spontaneous_appendicitis + weight = 20 + max_occurrences = 4 + earliest_start = 10 MINUTES + min_players = 5 // To make your chance of getting help a bit higher. + +/datum/round_event/spontaneous_appendicitis + fakeable = FALSE + +/datum/round_event/spontaneous_appendicitis/start() + for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) + if(!H.client) + continue + if(H.stat == DEAD) + continue + if(!H.getorgan(/obj/item/organ/appendix)) //Don't give the disease to some who lacks it, only for it to be auto-cured + continue + if(!(H.mob_biotypes & MOB_ORGANIC)) //biotype sleeper bugs strike again, once again making appendicitis pick a target that can't take it + continue + var/foundAlready = FALSE //don't infect someone that already has appendicitis + for(var/datum/disease/appendicitis/A in H.diseases) + foundAlready = TRUE + break + if(foundAlready) + continue + + var/datum/disease/D = new /datum/disease/appendicitis() + H.ForceContractDisease(D, FALSE, TRUE) + announce_to_ghosts(H) + break diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm index 932cefc305d..9999632afcb 100644 --- a/code/modules/events/vent_clog.dm +++ b/code/modules/events/vent_clog.dm @@ -1,110 +1,110 @@ -/datum/round_event_control/vent_clog - name = "Clogged Vents: Normal" - typepath = /datum/round_event/vent_clog - weight = 10 - max_occurrences = 3 - min_players = 10 - -/datum/round_event/vent_clog - announceWhen = 1 - startWhen = 5 - endWhen = 35 - var/interval = 2 - var/list/vents = list() - var/randomProbability = 1 - var/reagentsAmount = 100 - var/list/saferChems = list(/datum/reagent/water,/datum/reagent/carbon,/datum/reagent/consumable/flour,/datum/reagent/space_cleaner,/datum/reagent/consumable/nutriment,/datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen,/datum/reagent/lube,/datum/reagent/glitter/pink,/datum/reagent/cryptobiolin, - /datum/reagent/toxin/plantbgone,/datum/reagent/blood,/datum/reagent/medicine/c2/multiver,/datum/reagent/drug/space_drugs,/datum/reagent/medicine/morphine,/datum/reagent/water/holywater,/datum/reagent/consumable/ethanol,/datum/reagent/consumable/hot_coco,/datum/reagent/toxin/acid,/datum/reagent/toxin/mindbreaker,/datum/reagent/toxin/rotatium,/datum/reagent/bluespace, - /datum/reagent/pax,/datum/reagent/consumable/laughter,/datum/reagent/concentrated_barbers_aid,/datum/reagent/baldium,/datum/reagent/colorful_reagent,/datum/reagent/peaceborg/confuse,/datum/reagent/peaceborg/tire,/datum/reagent/consumable/sodiumchloride,/datum/reagent/consumable/ethanol/beer,/datum/reagent/hair_dye,/datum/reagent/consumable/sugar,/datum/reagent/glitter/white,/datum/reagent/growthserum) - //needs to be chemid unit checked at some point - -/datum/round_event/vent_clog/announce() - priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/setup() - endWhen = rand(25, 100) - for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) - var/turf/T = get_turf(temp_vent) - if(T && is_station_level(T.z) && !temp_vent.welded) - vents += temp_vent - if(!vents.len) - return kill() - -/datum/round_event/vent_clog/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - if (prob(randomProbability)) - R.add_reagent(get_random_reagent_id(), reagentsAmount) - else - R.add_reagent(pick(saferChems), reagentsAmount) - - R.create_foam(/datum/effect_system/foam_spread,200) - - var/cockroaches = prob(33) ? 3 : 0 - while(cockroaches) - new /mob/living/simple_animal/hostile/cockroach(get_turf(vent)) - cockroaches-- - CHECK_TICK - -/datum/round_event_control/vent_clog/threatening - name = "Clogged Vents: Threatening" - typepath = /datum/round_event/vent_clog/threatening - weight = 4 - min_players = 25 - max_occurrences = 1 - earliest_start = 35 MINUTES - -/datum/round_event/vent_clog/threatening - randomProbability = 10 - reagentsAmount = 200 - -/datum/round_event_control/vent_clog/catastrophic - name = "Clogged Vents: Catastrophic" - typepath = /datum/round_event/vent_clog/catastrophic - weight = 2 - min_players = 35 - max_occurrences = 1 - earliest_start = 45 MINUTES - -/datum/round_event/vent_clog/catastrophic - randomProbability = 30 - reagentsAmount = 250 - -/datum/round_event_control/vent_clog/beer - name = "Foamy beer stationwide" - typepath = /datum/round_event/vent_clog/beer - max_occurrences = 0 - -/datum/round_event_control/vent_clog/plasma_decon - name = "Plasma decontamination" - typepath = /datum/round_event/vent_clog/plasma_decon - max_occurrences = 0 - -/datum/round_event/vent_clog/beer - reagentsAmount = 100 - -/datum/round_event/vent_clog/beer/announce() - priority_announce("The scrubbers network is experiencing an unexpected surge of pressurized beer. Some ejection of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/beer/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) - - R.create_foam(/datum/effect_system/foam_spread,200) - CHECK_TICK - -/datum/round_event/vent_clog/plasma_decon/announce() - priority_announce("We are deploying an experimental plasma decontamination system. Please stand away from the vents and do not breathe the smoke that comes out.", "Central Command Update") - -/datum/round_event/vent_clog/plasma_decon/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/effect_system/smoke_spread/freezing/decon/smoke = new - smoke.set_up(7, get_turf(vent), 7) - smoke.start() - CHECK_TICK +/datum/round_event_control/vent_clog + name = "Clogged Vents: Normal" + typepath = /datum/round_event/vent_clog + weight = 10 + max_occurrences = 3 + min_players = 10 + +/datum/round_event/vent_clog + announceWhen = 1 + startWhen = 5 + endWhen = 35 + var/interval = 2 + var/list/vents = list() + var/randomProbability = 1 + var/reagentsAmount = 100 + var/list/saferChems = list(/datum/reagent/water,/datum/reagent/carbon,/datum/reagent/consumable/flour,/datum/reagent/space_cleaner,/datum/reagent/consumable/nutriment,/datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen,/datum/reagent/lube,/datum/reagent/glitter/pink,/datum/reagent/cryptobiolin, + /datum/reagent/toxin/plantbgone,/datum/reagent/blood,/datum/reagent/medicine/c2/multiver,/datum/reagent/drug/space_drugs,/datum/reagent/medicine/morphine,/datum/reagent/water/holywater,/datum/reagent/consumable/ethanol,/datum/reagent/consumable/hot_coco,/datum/reagent/toxin/acid,/datum/reagent/toxin/mindbreaker,/datum/reagent/toxin/rotatium,/datum/reagent/bluespace, + /datum/reagent/pax,/datum/reagent/consumable/laughter,/datum/reagent/concentrated_barbers_aid,/datum/reagent/baldium,/datum/reagent/colorful_reagent,/datum/reagent/peaceborg/confuse,/datum/reagent/peaceborg/tire,/datum/reagent/consumable/sodiumchloride,/datum/reagent/consumable/ethanol/beer,/datum/reagent/hair_dye,/datum/reagent/consumable/sugar,/datum/reagent/glitter/white,/datum/reagent/growthserum) + //needs to be chemid unit checked at some point + +/datum/round_event/vent_clog/announce() + priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/setup() + endWhen = rand(25, 100) + for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) + var/turf/T = get_turf(temp_vent) + if(T && is_station_level(T.z) && !temp_vent.welded) + vents += temp_vent + if(!vents.len) + return kill() + +/datum/round_event/vent_clog/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + if (prob(randomProbability)) + R.add_reagent(get_random_reagent_id(), reagentsAmount) + else + R.add_reagent(pick(saferChems), reagentsAmount) + + R.create_foam(/datum/effect_system/foam_spread,200) + + var/cockroaches = prob(33) ? 3 : 0 + while(cockroaches) + new /mob/living/simple_animal/hostile/cockroach(get_turf(vent)) + cockroaches-- + CHECK_TICK + +/datum/round_event_control/vent_clog/threatening + name = "Clogged Vents: Threatening" + typepath = /datum/round_event/vent_clog/threatening + weight = 4 + min_players = 25 + max_occurrences = 1 + earliest_start = 35 MINUTES + +/datum/round_event/vent_clog/threatening + randomProbability = 10 + reagentsAmount = 200 + +/datum/round_event_control/vent_clog/catastrophic + name = "Clogged Vents: Catastrophic" + typepath = /datum/round_event/vent_clog/catastrophic + weight = 2 + min_players = 35 + max_occurrences = 1 + earliest_start = 45 MINUTES + +/datum/round_event/vent_clog/catastrophic + randomProbability = 30 + reagentsAmount = 250 + +/datum/round_event_control/vent_clog/beer + name = "Foamy beer stationwide" + typepath = /datum/round_event/vent_clog/beer + max_occurrences = 0 + +/datum/round_event_control/vent_clog/plasma_decon + name = "Plasma decontamination" + typepath = /datum/round_event/vent_clog/plasma_decon + max_occurrences = 0 + +/datum/round_event/vent_clog/beer + reagentsAmount = 100 + +/datum/round_event/vent_clog/beer/announce() + priority_announce("The scrubbers network is experiencing an unexpected surge of pressurized beer. Some ejection of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/beer/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) + + R.create_foam(/datum/effect_system/foam_spread,200) + CHECK_TICK + +/datum/round_event/vent_clog/plasma_decon/announce() + priority_announce("We are deploying an experimental plasma decontamination system. Please stand away from the vents and do not breathe the smoke that comes out.", "Central Command Update") + +/datum/round_event/vent_clog/plasma_decon/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/effect_system/smoke_spread/freezing/decon/smoke = new + smoke.set_up(7, get_turf(vent), 7) + smoke.start() + CHECK_TICK diff --git a/code/modules/fields/timestop.dm b/code/modules/fields/timestop.dm index f3667c304ca..09475ec9a56 100644 --- a/code/modules/fields/timestop.dm +++ b/code/modules/fields/timestop.dm @@ -1,206 +1,206 @@ - -/obj/effect/timestop - anchored = TRUE - name = "chronofield" - desc = "ZA WARUDO" - icon = 'icons/effects/160x160.dmi' - icon_state = "time" - layer = FLY_LAYER - pixel_x = -64 - pixel_y = -64 - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/list/immune = list() // the one who creates the timestop is immune, which includes wizards and the dead slime you murdered to make this chronofield - var/turf/target - var/freezerange = 2 - var/duration = 140 - var/datum/proximity_monitor/advanced/timestop/chronofield - alpha = 125 - var/check_anti_magic = FALSE - var/check_holy = FALSE - -/obj/effect/timestop/Initialize(mapload, radius, time, list/immune_atoms, start = TRUE) //Immune atoms assoc list atom = TRUE - . = ..() - if(!isnull(time)) - duration = time - if(!isnull(radius)) - freezerange = radius - for(var/A in immune_atoms) - immune[A] = TRUE - for(var/mob/living/L in GLOB.player_list) - if(locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects - immune[L] = TRUE - for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.parasites) - if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.mind.spell_list) //It would only make sense that a person's stand would also be immune. - immune[G] = TRUE - if(start) - timestop() - -/obj/effect/timestop/Destroy() - qdel(chronofield) - playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, frequency = -1) //reverse! - return ..() - -/obj/effect/timestop/proc/timestop() - target = get_turf(src) - playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, -1) - chronofield = make_field(/datum/proximity_monitor/advanced/timestop, list("current_range" = freezerange, "host" = src, "immune" = immune, "check_anti_magic" = check_anti_magic, "check_holy" = check_holy)) - QDEL_IN(src, duration) - -/obj/effect/timestop/magic - check_anti_magic = TRUE - -/datum/proximity_monitor/advanced/timestop - name = "chronofield" - setup_field_turfs = TRUE - field_shape = FIELD_SHAPE_RADIUS_SQUARE - requires_processing = TRUE - var/list/immune = list() - var/list/frozen_things = list() - var/list/frozen_mobs = list() //cached separately for processing - var/list/frozen_structures = list() //Also machinery, and only frozen aestethically - var/list/frozen_turfs = list() //Only aesthetically - var/check_anti_magic = FALSE - var/check_holy = FALSE - - var/static/list/global_frozen_atoms = list() - -/datum/proximity_monitor/advanced/timestop/Destroy() - unfreeze_all() - return ..() - -/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/AM) - freeze_atom(AM) - -/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A) - if(immune[A] || global_frozen_atoms[A] || !istype(A)) - return FALSE - if(ismob(A)) - var/mob/M = A - if(M.anti_magic_check(check_anti_magic, check_holy)) - immune[A] = TRUE - return - var/frozen = TRUE - if(isliving(A)) - freeze_mob(A) - else if(istype(A, /obj/projectile)) - freeze_projectile(A) - else if(istype(A, /obj/mecha)) - freeze_mecha(A) - else if((ismachinery(A) && !istype(A, /obj/machinery/light)) || isstructure(A)) //Special exception for light fixtures since recoloring causes them to change light - freeze_structure(A) - else - frozen = FALSE - if(A.throwing) - freeze_throwing(A) - frozen = TRUE - if(!frozen) - return - - frozen_things[A] = A.move_resist - A.move_resist = INFINITY - global_frozen_atoms[A] = src - into_the_negative_zone(A) - RegisterSignal(A, COMSIG_MOVABLE_PRE_MOVE, .proc/unfreeze_atom) - RegisterSignal(A, COMSIG_ITEM_PICKUP, .proc/unfreeze_atom) - - return TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() - for(var/i in frozen_things) - unfreeze_atom(i) - for(var/T in frozen_turfs) - unfreeze_turf(T) - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A) - if(A.throwing) - unfreeze_throwing(A) - if(isliving(A)) - unfreeze_mob(A) - else if(istype(A, /obj/projectile)) - unfreeze_projectile(A) - else if(istype(A, /obj/mecha)) - unfreeze_mecha(A) - - UnregisterSignal(A, COMSIG_MOVABLE_PRE_MOVE) - UnregisterSignal(A, COMSIG_ITEM_PICKUP) - escape_the_negative_zone(A) - A.move_resist = frozen_things[A] - frozen_things -= A - global_frozen_atoms -= A - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M) - M.completely_disabled = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M) - M.completely_disabled = FALSE - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) - var/datum/thrownthing/T = AM.throwing - T.paused = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throwing(atom/movable/AM) - var/datum/thrownthing/T = AM.throwing - if(T) - T.paused = FALSE - -/datum/proximity_monitor/advanced/timestop/proc/freeze_turf(turf/T) - into_the_negative_zone(T) - frozen_turfs += T - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_turf(turf/T) - escape_the_negative_zone(T) - -/datum/proximity_monitor/advanced/timestop/proc/freeze_structure(obj/O) - into_the_negative_zone(O) - frozen_structures += O - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_structure(obj/O) - escape_the_negative_zone(O) - -/datum/proximity_monitor/advanced/timestop/process() - for(var/i in frozen_mobs) - var/mob/living/m = i - m.Stun(20, 1, 1) - -/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) - for(var/i in T.contents) - freeze_atom(i) - freeze_turf(T) - return ..() - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/P) - P.paused = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/P) - P.paused = FALSE - -/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) - frozen_mobs += L - L.Stun(20, 1, 1) - ADD_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) - walk(L, 0) //stops them mid pathing even if they're stunimmune - if(isanimal(L)) - var/mob/living/simple_animal/S = L - S.toggle_ai(AI_OFF) - if(ishostile(L)) - var/mob/living/simple_animal/hostile/H = L - H.LoseTarget() - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) - L.AdjustStun(-20, 1, 1) - REMOVE_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) - frozen_mobs -= L - if(isanimal(L)) - var/mob/living/simple_animal/S = L - S.toggle_ai(initial(S.AIStatus)) - -//you don't look quite right, is something the matter? -/datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) - A.add_atom_colour(list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0), TEMPORARY_COLOUR_PRIORITY) - -//let's put some colour back into your cheeks -/datum/proximity_monitor/advanced/timestop/proc/escape_the_negative_zone(atom/A) - A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) + +/obj/effect/timestop + anchored = TRUE + name = "chronofield" + desc = "ZA WARUDO" + icon = 'icons/effects/160x160.dmi' + icon_state = "time" + layer = FLY_LAYER + pixel_x = -64 + pixel_y = -64 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/list/immune = list() // the one who creates the timestop is immune, which includes wizards and the dead slime you murdered to make this chronofield + var/turf/target + var/freezerange = 2 + var/duration = 140 + var/datum/proximity_monitor/advanced/timestop/chronofield + alpha = 125 + var/check_anti_magic = FALSE + var/check_holy = FALSE + +/obj/effect/timestop/Initialize(mapload, radius, time, list/immune_atoms, start = TRUE) //Immune atoms assoc list atom = TRUE + . = ..() + if(!isnull(time)) + duration = time + if(!isnull(radius)) + freezerange = radius + for(var/A in immune_atoms) + immune[A] = TRUE + for(var/mob/living/L in GLOB.player_list) + if(locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects + immune[L] = TRUE + for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.parasites) + if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.mind.spell_list) //It would only make sense that a person's stand would also be immune. + immune[G] = TRUE + if(start) + timestop() + +/obj/effect/timestop/Destroy() + qdel(chronofield) + playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, frequency = -1) //reverse! + return ..() + +/obj/effect/timestop/proc/timestop() + target = get_turf(src) + playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, -1) + chronofield = make_field(/datum/proximity_monitor/advanced/timestop, list("current_range" = freezerange, "host" = src, "immune" = immune, "check_anti_magic" = check_anti_magic, "check_holy" = check_holy)) + QDEL_IN(src, duration) + +/obj/effect/timestop/magic + check_anti_magic = TRUE + +/datum/proximity_monitor/advanced/timestop + name = "chronofield" + setup_field_turfs = TRUE + field_shape = FIELD_SHAPE_RADIUS_SQUARE + requires_processing = TRUE + var/list/immune = list() + var/list/frozen_things = list() + var/list/frozen_mobs = list() //cached separately for processing + var/list/frozen_structures = list() //Also machinery, and only frozen aestethically + var/list/frozen_turfs = list() //Only aesthetically + var/check_anti_magic = FALSE + var/check_holy = FALSE + + var/static/list/global_frozen_atoms = list() + +/datum/proximity_monitor/advanced/timestop/Destroy() + unfreeze_all() + return ..() + +/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/AM) + freeze_atom(AM) + +/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A) + if(immune[A] || global_frozen_atoms[A] || !istype(A)) + return FALSE + if(ismob(A)) + var/mob/M = A + if(M.anti_magic_check(check_anti_magic, check_holy)) + immune[A] = TRUE + return + var/frozen = TRUE + if(isliving(A)) + freeze_mob(A) + else if(istype(A, /obj/projectile)) + freeze_projectile(A) + else if(istype(A, /obj/mecha)) + freeze_mecha(A) + else if((ismachinery(A) && !istype(A, /obj/machinery/light)) || isstructure(A)) //Special exception for light fixtures since recoloring causes them to change light + freeze_structure(A) + else + frozen = FALSE + if(A.throwing) + freeze_throwing(A) + frozen = TRUE + if(!frozen) + return + + frozen_things[A] = A.move_resist + A.move_resist = INFINITY + global_frozen_atoms[A] = src + into_the_negative_zone(A) + RegisterSignal(A, COMSIG_MOVABLE_PRE_MOVE, .proc/unfreeze_atom) + RegisterSignal(A, COMSIG_ITEM_PICKUP, .proc/unfreeze_atom) + + return TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() + for(var/i in frozen_things) + unfreeze_atom(i) + for(var/T in frozen_turfs) + unfreeze_turf(T) + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A) + if(A.throwing) + unfreeze_throwing(A) + if(isliving(A)) + unfreeze_mob(A) + else if(istype(A, /obj/projectile)) + unfreeze_projectile(A) + else if(istype(A, /obj/mecha)) + unfreeze_mecha(A) + + UnregisterSignal(A, COMSIG_MOVABLE_PRE_MOVE) + UnregisterSignal(A, COMSIG_ITEM_PICKUP) + escape_the_negative_zone(A) + A.move_resist = frozen_things[A] + frozen_things -= A + global_frozen_atoms -= A + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M) + M.completely_disabled = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M) + M.completely_disabled = FALSE + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) + var/datum/thrownthing/T = AM.throwing + T.paused = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throwing(atom/movable/AM) + var/datum/thrownthing/T = AM.throwing + if(T) + T.paused = FALSE + +/datum/proximity_monitor/advanced/timestop/proc/freeze_turf(turf/T) + into_the_negative_zone(T) + frozen_turfs += T + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_turf(turf/T) + escape_the_negative_zone(T) + +/datum/proximity_monitor/advanced/timestop/proc/freeze_structure(obj/O) + into_the_negative_zone(O) + frozen_structures += O + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_structure(obj/O) + escape_the_negative_zone(O) + +/datum/proximity_monitor/advanced/timestop/process() + for(var/i in frozen_mobs) + var/mob/living/m = i + m.Stun(20, 1, 1) + +/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) + for(var/i in T.contents) + freeze_atom(i) + freeze_turf(T) + return ..() + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/P) + P.paused = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/P) + P.paused = FALSE + +/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) + frozen_mobs += L + L.Stun(20, 1, 1) + ADD_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) + walk(L, 0) //stops them mid pathing even if they're stunimmune + if(isanimal(L)) + var/mob/living/simple_animal/S = L + S.toggle_ai(AI_OFF) + if(ishostile(L)) + var/mob/living/simple_animal/hostile/H = L + H.LoseTarget() + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) + L.AdjustStun(-20, 1, 1) + REMOVE_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) + frozen_mobs -= L + if(isanimal(L)) + var/mob/living/simple_animal/S = L + S.toggle_ai(initial(S.AIStatus)) + +//you don't look quite right, is something the matter? +/datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) + A.add_atom_colour(list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0), TEMPORARY_COLOUR_PRIORITY) + +//let's put some colour back into your cheeks +/datum/proximity_monitor/advanced/timestop/proc/escape_the_negative_zone(atom/A) + A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm index 165d2b4a3a8..43c3337a4b3 100644 --- a/code/modules/flufftext/Dreaming.dm +++ b/code/modules/flufftext/Dreaming.dm @@ -1,69 +1,69 @@ -/mob/living/carbon/proc/handle_dreams() - if(prob(10) && !dreaming) - dream() - -/mob/living/carbon/proc/dream() - set waitfor = FALSE - var/list/dream_fragments = list() - var/list/custom_dream_nouns = list() - var/fragment = "" - - for(var/obj/item/bedsheet/sheet in loc) - custom_dream_nouns += sheet.dream_messages - - dream_fragments += "you see" - - //Subject - if(custom_dream_nouns.len && prob(90)) - fragment += pick(custom_dream_nouns) - else - fragment += pick(GLOB.dream_strings) - - if(prob(50)) - fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) - else - fragment = replacetext(fragment, "%ADJECTIVE% ", "") - if(findtext(fragment, "%A% ")) - fragment = "\a [replacetext(fragment, "%A% ", "")]" - dream_fragments += fragment - - //Verb - fragment = "" - if(prob(50)) - if(prob(35)) - fragment += "[pick(GLOB.adverbs)] " - fragment += pick(GLOB.ing_verbs) - else - fragment += "will " - fragment += pick(GLOB.verbs) - dream_fragments += fragment - - if(prob(25)) - dream_sequence(dream_fragments) - return - - //Object - fragment = "" - fragment += pick(GLOB.dream_strings) - if(prob(50)) - fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) - else - fragment = replacetext(fragment, "%ADJECTIVE% ", "") - if(findtext(fragment, "%A% ")) - fragment = "\a [replacetext(fragment, "%A% ", "")]" - dream_fragments += fragment - - dreaming = TRUE - dream_sequence(dream_fragments) - -/mob/living/carbon/proc/dream_sequence(list/dream_fragments) - if(stat != UNCONSCIOUS || InCritical()) - dreaming = FALSE - return - var/next_message = dream_fragments[1] - dream_fragments.Cut(1,2) - to_chat(src, "... [next_message] ...") - if(LAZYLEN(dream_fragments)) - addtimer(CALLBACK(src, .proc/dream_sequence, dream_fragments), rand(10,30)) - else - dreaming = FALSE +/mob/living/carbon/proc/handle_dreams() + if(prob(10) && !dreaming) + dream() + +/mob/living/carbon/proc/dream() + set waitfor = FALSE + var/list/dream_fragments = list() + var/list/custom_dream_nouns = list() + var/fragment = "" + + for(var/obj/item/bedsheet/sheet in loc) + custom_dream_nouns += sheet.dream_messages + + dream_fragments += "you see" + + //Subject + if(custom_dream_nouns.len && prob(90)) + fragment += pick(custom_dream_nouns) + else + fragment += pick(GLOB.dream_strings) + + if(prob(50)) + fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) + else + fragment = replacetext(fragment, "%ADJECTIVE% ", "") + if(findtext(fragment, "%A% ")) + fragment = "\a [replacetext(fragment, "%A% ", "")]" + dream_fragments += fragment + + //Verb + fragment = "" + if(prob(50)) + if(prob(35)) + fragment += "[pick(GLOB.adverbs)] " + fragment += pick(GLOB.ing_verbs) + else + fragment += "will " + fragment += pick(GLOB.verbs) + dream_fragments += fragment + + if(prob(25)) + dream_sequence(dream_fragments) + return + + //Object + fragment = "" + fragment += pick(GLOB.dream_strings) + if(prob(50)) + fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) + else + fragment = replacetext(fragment, "%ADJECTIVE% ", "") + if(findtext(fragment, "%A% ")) + fragment = "\a [replacetext(fragment, "%A% ", "")]" + dream_fragments += fragment + + dreaming = TRUE + dream_sequence(dream_fragments) + +/mob/living/carbon/proc/dream_sequence(list/dream_fragments) + if(stat != UNCONSCIOUS || InCritical()) + dreaming = FALSE + return + var/next_message = dream_fragments[1] + dream_fragments.Cut(1,2) + to_chat(src, "... [next_message] ...") + if(LAZYLEN(dream_fragments)) + addtimer(CALLBACK(src, .proc/dream_sequence, dream_fragments), rand(10,30)) + else + dreaming = FALSE diff --git a/code/modules/food_and_drinks/pizzabox.dm b/code/modules/food_and_drinks/pizzabox.dm index 45a755e8802..715187b7e98 100644 --- a/code/modules/food_and_drinks/pizzabox.dm +++ b/code/modules/food_and_drinks/pizzabox.dm @@ -1,349 +1,349 @@ -/obj/item/bombcore/miniature/pizza - name = "pizza bomb" - desc = "Special delivery!" - icon_state = "pizzabomb_inactive" - inhand_icon_state = "eshield0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - -/obj/item/pizzabox - name = "pizza box" - desc = "A box suited for pizzas." - icon = 'icons/obj/food/containers.dmi' - icon_state = "pizzabox" - inhand_icon_state = "pizzabox" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - - var/open = FALSE - var/can_open_on_fall = TRUE //if FALSE, this pizza box will never open if it falls from a stack - var/boxtag = "" - var/list/boxes = list() - - var/obj/item/reagent_containers/food/snacks/pizza/pizza - - var/obj/item/bombcore/miniature/pizza/bomb - var/bomb_active = FALSE // If the bomb is counting down. - var/bomb_defused = TRUE // If the bomb is inert. - var/bomb_timer = 1 // How long before blowing the bomb. - /// Min bomb timer allowed - var/bomb_timer_min = 1 - /// Max bomb timer allower - var/bomb_timer_max = 10 - -/obj/item/pizzabox/Initialize() - . = ..() - update_icon() - - -/obj/item/pizzabox/Destroy() - unprocess() - return ..() - -/obj/item/pizzabox/update_icon() - // Description - desc = initial(desc) - if(open) - if(pizza) - desc = "[desc] It appears to have \a [pizza] inside. Use your other hand to take it out." - if(bomb) - desc = "[desc] Wait, what?! It has \a [bomb] inside!" - if(bomb_defused) - desc = "[desc] The bomb seems inert. Use your other hand to activate it." - if(bomb_active) - desc = "[desc] It looks like it's about to go off!" - else - var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src - if(boxes.len) - desc = "A pile of boxes suited for pizzas. There appear to be [boxes.len + 1] boxes in the pile." - if(box.boxtag != "") - desc = "[desc] The [boxes.len ? "top box" : "box"]'s tag reads: [box.boxtag]" - - // Icon/Overlays - cut_overlays() - if(open) - icon_state = "pizzabox_open" - if(pizza) - icon_state = "pizzabox_messy" - var/mutable_appearance/pizza_overlay = mutable_appearance(pizza.icon, pizza.icon_state) - pizza_overlay.pixel_y = -3 - add_overlay(pizza_overlay) - if(bomb) - bomb.icon_state = "pizzabomb_[bomb_active ? "active" : "inactive"]" - var/mutable_appearance/bomb_overlay = mutable_appearance(bomb.icon, bomb.icon_state) - bomb_overlay.pixel_y = 5 - add_overlay(bomb_overlay) - else - icon_state = "pizzabox" - var/current_offset = 3 - for(var/V in boxes) - var/obj/item/pizzabox/P = V - var/mutable_appearance/box_overlay = mutable_appearance(P.icon, P.icon_state) - box_overlay.pixel_y = current_offset - add_overlay(box_overlay) - current_offset += 3 - var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src - if(box.boxtag != "") - var/mutable_appearance/tag_overlay = mutable_appearance(icon, "pizzabox_tag") - tag_overlay.pixel_y = boxes.len * 3 - add_overlay(tag_overlay) - -/obj/item/pizzabox/worn_overlays(isinhands, icon_file) - . = list() - var/current_offset = 2 - if(isinhands) - for(var/V in boxes) //add EXTRA BOX per box - var/mutable_appearance/M = mutable_appearance(icon_file, inhand_icon_state) - M.pixel_y = current_offset - current_offset += 2 - . += M - -/obj/item/pizzabox/attack_self(mob/user) - if(boxes.len > 0) - return - open = !open - if(open && !bomb_defused) - audible_message("[icon2html(src, hearers(src))] *beep*") - bomb_active = TRUE - START_PROCESSING(SSobj, src) - update_icon() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/pizzabox/attack_hand(mob/user) - if(user.get_inactive_held_item() != src) - return ..() - if(open) - if(pizza) - user.put_in_hands(pizza) - to_chat(user, "You take [pizza] out of [src].") - pizza = null - update_icon() - else if(bomb) - if(wires.is_all_cut() && bomb_defused) - user.put_in_hands(bomb) - to_chat(user, "You carefully remove the [bomb] from [src].") - bomb = null - update_icon() - return - else - bomb_timer = input(user, "Set the [bomb] timer from [bomb_timer_min] to [bomb_timer_max].", bomb, bomb_timer) as num|null - - if (isnull(bomb_timer)) - return - - bomb_timer = clamp(CEILING(bomb_timer / 2, 1), bomb_timer_min, bomb_timer_max) - bomb_defused = FALSE - - log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer * 2] seconds") - bomb.adminlog = "The [bomb.name] in [src.name] that [key_name(user)] activated has detonated!" - - to_chat(user, "You trap [src] with [bomb].") - update_icon() - else if(boxes.len) - var/obj/item/pizzabox/topbox = boxes[boxes.len] - boxes -= topbox - user.put_in_hands(topbox) - to_chat(user, "You remove the topmost [name] from the stack.") - topbox.update_icon() - update_icon() - user.regenerate_icons() - -/obj/item/pizzabox/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pizzabox)) - var/obj/item/pizzabox/newbox = I - if(!open && !newbox.open) - var/list/add = list() - add += newbox - add += newbox.boxes - if(!user.transferItemToLoc(newbox, src)) - return - boxes += add - newbox.boxes.Cut() - to_chat(user, "You put [newbox] on top of [src]!") - newbox.update_icon() - update_icon() - user.regenerate_icons() - if(boxes.len >= 5) - if(prob(10 * boxes.len)) - to_chat(user, "You can't keep holding the stack!") - disperse_pizzas() - else - to_chat(user, "The stack is getting a little high...") - return - else - to_chat(user, "Close [open ? src : newbox] first!") - else if(istype(I, /obj/item/reagent_containers/food/snacks/pizza) || istype(I, /obj/item/reagent_containers/food/snacks/customizable/pizza)) - if(open) - if(pizza) - to_chat(user, "[src] already has \a [pizza.name]!") - return - if(!user.transferItemToLoc(I, src)) - return - pizza = I - to_chat(user, "You put [I] in [src].") - update_icon() - return - else if(istype(I, /obj/item/bombcore/miniature/pizza)) - if(open && !bomb) - if(!user.transferItemToLoc(I, src)) - return - wires = new /datum/wires/explosive/pizza(src) - bomb = I - to_chat(user, "You put [I] in [src]. Sneeki breeki...") - update_icon() - return - else if(bomb) - to_chat(user, "[src] already has a bomb in it!") - else if(istype(I, /obj/item/pen)) - if(!open) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on [src]!") - return - var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src - box.boxtag += stripped_input(user, "Write on [box]'s tag:", box, "", 30) - if(!user.canUseTopic(src, BE_CLOSE)) - return - to_chat(user, "You write with [I] on [src].") - update_icon() - return - else if(is_wire_tool(I)) - if(wires && bomb) - wires.interact(user) - else if(istype(I, /obj/item/reagent_containers/food)) - to_chat(user, "That's not a pizza!") - ..() - -/obj/item/pizzabox/process() - if(bomb_active && !bomb_defused && (bomb_timer > 0)) - playsound(loc, 'sound/items/timer.ogg', 50, FALSE) - bomb_timer-- - if(bomb_active && !bomb_defused && (bomb_timer <= 0)) - if(bomb in src) - bomb.detonate() - unprocess() - qdel(src) - if(!bomb_active || bomb_defused) - if(bomb_defused && (bomb in src)) - bomb.defuse() - bomb_active = FALSE - unprocess() - return - -/obj/item/pizzabox/attack(mob/living/target, mob/living/user, def_zone) - . = ..() - if(boxes.len >= 3 && prob(25 * boxes.len)) - disperse_pizzas() - -/obj/item/pizzabox/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(boxes.len >= 2 && prob(20 * boxes.len)) - disperse_pizzas() - -/obj/item/pizzabox/proc/disperse_pizzas() - visible_message("The pizzas fall everywhere!") - for(var/V in boxes) - var/obj/item/pizzabox/P = V - var/fall_dir = pick(GLOB.alldirs) - step(P, fall_dir) - if(P.pizza && P.can_open_on_fall && prob(50)) //rip pizza - P.open = TRUE - P.pizza.forceMove(get_turf(P)) - fall_dir = pick(GLOB.alldirs) - step(P.pizza, fall_dir) - P.pizza = null - P.update_icon() - boxes -= P - update_icon() - if(isliving(loc)) - var/mob/living/L = loc - L.regenerate_icons() - -/obj/item/pizzabox/proc/unprocess() - STOP_PROCESSING(SSobj, src) - qdel(wires) - wires = null - update_icon() - -/obj/item/pizzabox/bomb/Initialize() - . = ..() - var/randompizza = pick(subtypesof(/obj/item/reagent_containers/food/snacks/pizza)) - pizza = new randompizza(src) - bomb = new(src) - wires = new /datum/wires/explosive/pizza(src) - -/obj/item/pizzabox/margherita/Initialize() - . = ..() - AddPizza() - boxtag = "Margherita Deluxe" - -/obj/item/pizzabox/margherita/proc/AddPizza() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita(src) - -/obj/item/pizzabox/margherita/robo/AddPizza() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita/robo(src) - -/obj/item/pizzabox/vegetable/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/vegetable(src) - boxtag = "Gourmet Vegatable" - -/obj/item/pizzabox/mushroom/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/mushroom(src) - boxtag = "Mushroom Special" - -/obj/item/pizzabox/meat/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/meat(src) - boxtag = "Meatlover's Supreme" - -/obj/item/pizzabox/pineapple/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) - boxtag = "Honolulu Chew" - -//An anomalous pizza box that, when opened, produces the opener's favorite kind of pizza. -/obj/item/pizzabox/infinite - resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF //hard to destroy - can_open_on_fall = FALSE - var/list/pizza_types = list( - /obj/item/reagent_containers/food/snacks/pizza/meat = 1, - /obj/item/reagent_containers/food/snacks/pizza/mushroom = 1, - /obj/item/reagent_containers/food/snacks/pizza/margherita = 1, - /obj/item/reagent_containers/food/snacks/pizza/sassysage = 0.8, - /obj/item/reagent_containers/food/snacks/pizza/vegetable = 0.8, - /obj/item/reagent_containers/food/snacks/pizza/pineapple = 0.5, - /obj/item/reagent_containers/food/snacks/pizza/donkpocket = 0.3, - /obj/item/reagent_containers/food/snacks/pizza/dank = 0.1) //pizzas here are weighted by chance to be someone's favorite - var/static/list/pizza_preferences - -/obj/item/pizzabox/infinite/Initialize() - . = ..() - if(!pizza_preferences) - pizza_preferences = list() - -/obj/item/pizzabox/infinite/examine(mob/user) - . = ..() - if(isobserver(user)) - . += "This pizza box is anomalous, and will produce infinite pizza." - -/obj/item/pizzabox/infinite/attack_self(mob/living/user) - QDEL_NULL(pizza) - if(ishuman(user)) - attune_pizza(user) - . = ..() - -/obj/item/pizzabox/infinite/proc/attune_pizza(mob/living/carbon/human/noms) //tonight on "proc names I never thought I'd type" - if(!pizza_preferences[noms.ckey]) - pizza_preferences[noms.ckey] = pickweight(pizza_types) - if(noms.has_quirk(/datum/quirk/pineapple_liker)) - pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/pineapple - else if(noms.has_quirk(/datum/quirk/pineapple_hater)) - var/list/pineapple_pizza_liker = pizza_types.Copy() - pineapple_pizza_liker -= /obj/item/reagent_containers/food/snacks/pizza/pineapple - pizza_preferences[noms.ckey] = pickweight(pineapple_pizza_liker) - else if(noms.mind && noms.mind.assigned_role == "Botanist") - pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/dank - - var/obj/item/pizza_type = pizza_preferences[noms.ckey] - pizza = new pizza_type (src) - pizza.foodtype = noms.dna.species.liked_food //it's our favorite! +/obj/item/bombcore/miniature/pizza + name = "pizza bomb" + desc = "Special delivery!" + icon_state = "pizzabomb_inactive" + inhand_icon_state = "eshield0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + +/obj/item/pizzabox + name = "pizza box" + desc = "A box suited for pizzas." + icon = 'icons/obj/food/containers.dmi' + icon_state = "pizzabox" + inhand_icon_state = "pizzabox" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + + var/open = FALSE + var/can_open_on_fall = TRUE //if FALSE, this pizza box will never open if it falls from a stack + var/boxtag = "" + var/list/boxes = list() + + var/obj/item/reagent_containers/food/snacks/pizza/pizza + + var/obj/item/bombcore/miniature/pizza/bomb + var/bomb_active = FALSE // If the bomb is counting down. + var/bomb_defused = TRUE // If the bomb is inert. + var/bomb_timer = 1 // How long before blowing the bomb. + /// Min bomb timer allowed + var/bomb_timer_min = 1 + /// Max bomb timer allower + var/bomb_timer_max = 10 + +/obj/item/pizzabox/Initialize() + . = ..() + update_icon() + + +/obj/item/pizzabox/Destroy() + unprocess() + return ..() + +/obj/item/pizzabox/update_icon() + // Description + desc = initial(desc) + if(open) + if(pizza) + desc = "[desc] It appears to have \a [pizza] inside. Use your other hand to take it out." + if(bomb) + desc = "[desc] Wait, what?! It has \a [bomb] inside!" + if(bomb_defused) + desc = "[desc] The bomb seems inert. Use your other hand to activate it." + if(bomb_active) + desc = "[desc] It looks like it's about to go off!" + else + var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src + if(boxes.len) + desc = "A pile of boxes suited for pizzas. There appear to be [boxes.len + 1] boxes in the pile." + if(box.boxtag != "") + desc = "[desc] The [boxes.len ? "top box" : "box"]'s tag reads: [box.boxtag]" + + // Icon/Overlays + cut_overlays() + if(open) + icon_state = "pizzabox_open" + if(pizza) + icon_state = "pizzabox_messy" + var/mutable_appearance/pizza_overlay = mutable_appearance(pizza.icon, pizza.icon_state) + pizza_overlay.pixel_y = -3 + add_overlay(pizza_overlay) + if(bomb) + bomb.icon_state = "pizzabomb_[bomb_active ? "active" : "inactive"]" + var/mutable_appearance/bomb_overlay = mutable_appearance(bomb.icon, bomb.icon_state) + bomb_overlay.pixel_y = 5 + add_overlay(bomb_overlay) + else + icon_state = "pizzabox" + var/current_offset = 3 + for(var/V in boxes) + var/obj/item/pizzabox/P = V + var/mutable_appearance/box_overlay = mutable_appearance(P.icon, P.icon_state) + box_overlay.pixel_y = current_offset + add_overlay(box_overlay) + current_offset += 3 + var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src + if(box.boxtag != "") + var/mutable_appearance/tag_overlay = mutable_appearance(icon, "pizzabox_tag") + tag_overlay.pixel_y = boxes.len * 3 + add_overlay(tag_overlay) + +/obj/item/pizzabox/worn_overlays(isinhands, icon_file) + . = list() + var/current_offset = 2 + if(isinhands) + for(var/V in boxes) //add EXTRA BOX per box + var/mutable_appearance/M = mutable_appearance(icon_file, inhand_icon_state) + M.pixel_y = current_offset + current_offset += 2 + . += M + +/obj/item/pizzabox/attack_self(mob/user) + if(boxes.len > 0) + return + open = !open + if(open && !bomb_defused) + audible_message("[icon2html(src, hearers(src))] *beep*") + bomb_active = TRUE + START_PROCESSING(SSobj, src) + update_icon() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/pizzabox/attack_hand(mob/user) + if(user.get_inactive_held_item() != src) + return ..() + if(open) + if(pizza) + user.put_in_hands(pizza) + to_chat(user, "You take [pizza] out of [src].") + pizza = null + update_icon() + else if(bomb) + if(wires.is_all_cut() && bomb_defused) + user.put_in_hands(bomb) + to_chat(user, "You carefully remove the [bomb] from [src].") + bomb = null + update_icon() + return + else + bomb_timer = input(user, "Set the [bomb] timer from [bomb_timer_min] to [bomb_timer_max].", bomb, bomb_timer) as num|null + + if (isnull(bomb_timer)) + return + + bomb_timer = clamp(CEILING(bomb_timer / 2, 1), bomb_timer_min, bomb_timer_max) + bomb_defused = FALSE + + log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer * 2] seconds") + bomb.adminlog = "The [bomb.name] in [src.name] that [key_name(user)] activated has detonated!" + + to_chat(user, "You trap [src] with [bomb].") + update_icon() + else if(boxes.len) + var/obj/item/pizzabox/topbox = boxes[boxes.len] + boxes -= topbox + user.put_in_hands(topbox) + to_chat(user, "You remove the topmost [name] from the stack.") + topbox.update_icon() + update_icon() + user.regenerate_icons() + +/obj/item/pizzabox/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pizzabox)) + var/obj/item/pizzabox/newbox = I + if(!open && !newbox.open) + var/list/add = list() + add += newbox + add += newbox.boxes + if(!user.transferItemToLoc(newbox, src)) + return + boxes += add + newbox.boxes.Cut() + to_chat(user, "You put [newbox] on top of [src]!") + newbox.update_icon() + update_icon() + user.regenerate_icons() + if(boxes.len >= 5) + if(prob(10 * boxes.len)) + to_chat(user, "You can't keep holding the stack!") + disperse_pizzas() + else + to_chat(user, "The stack is getting a little high...") + return + else + to_chat(user, "Close [open ? src : newbox] first!") + else if(istype(I, /obj/item/reagent_containers/food/snacks/pizza) || istype(I, /obj/item/reagent_containers/food/snacks/customizable/pizza)) + if(open) + if(pizza) + to_chat(user, "[src] already has \a [pizza.name]!") + return + if(!user.transferItemToLoc(I, src)) + return + pizza = I + to_chat(user, "You put [I] in [src].") + update_icon() + return + else if(istype(I, /obj/item/bombcore/miniature/pizza)) + if(open && !bomb) + if(!user.transferItemToLoc(I, src)) + return + wires = new /datum/wires/explosive/pizza(src) + bomb = I + to_chat(user, "You put [I] in [src]. Sneeki breeki...") + update_icon() + return + else if(bomb) + to_chat(user, "[src] already has a bomb in it!") + else if(istype(I, /obj/item/pen)) + if(!open) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on [src]!") + return + var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src + box.boxtag += stripped_input(user, "Write on [box]'s tag:", box, "", 30) + if(!user.canUseTopic(src, BE_CLOSE)) + return + to_chat(user, "You write with [I] on [src].") + update_icon() + return + else if(is_wire_tool(I)) + if(wires && bomb) + wires.interact(user) + else if(istype(I, /obj/item/reagent_containers/food)) + to_chat(user, "That's not a pizza!") + ..() + +/obj/item/pizzabox/process() + if(bomb_active && !bomb_defused && (bomb_timer > 0)) + playsound(loc, 'sound/items/timer.ogg', 50, FALSE) + bomb_timer-- + if(bomb_active && !bomb_defused && (bomb_timer <= 0)) + if(bomb in src) + bomb.detonate() + unprocess() + qdel(src) + if(!bomb_active || bomb_defused) + if(bomb_defused && (bomb in src)) + bomb.defuse() + bomb_active = FALSE + unprocess() + return + +/obj/item/pizzabox/attack(mob/living/target, mob/living/user, def_zone) + . = ..() + if(boxes.len >= 3 && prob(25 * boxes.len)) + disperse_pizzas() + +/obj/item/pizzabox/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(boxes.len >= 2 && prob(20 * boxes.len)) + disperse_pizzas() + +/obj/item/pizzabox/proc/disperse_pizzas() + visible_message("The pizzas fall everywhere!") + for(var/V in boxes) + var/obj/item/pizzabox/P = V + var/fall_dir = pick(GLOB.alldirs) + step(P, fall_dir) + if(P.pizza && P.can_open_on_fall && prob(50)) //rip pizza + P.open = TRUE + P.pizza.forceMove(get_turf(P)) + fall_dir = pick(GLOB.alldirs) + step(P.pizza, fall_dir) + P.pizza = null + P.update_icon() + boxes -= P + update_icon() + if(isliving(loc)) + var/mob/living/L = loc + L.regenerate_icons() + +/obj/item/pizzabox/proc/unprocess() + STOP_PROCESSING(SSobj, src) + qdel(wires) + wires = null + update_icon() + +/obj/item/pizzabox/bomb/Initialize() + . = ..() + var/randompizza = pick(subtypesof(/obj/item/reagent_containers/food/snacks/pizza)) + pizza = new randompizza(src) + bomb = new(src) + wires = new /datum/wires/explosive/pizza(src) + +/obj/item/pizzabox/margherita/Initialize() + . = ..() + AddPizza() + boxtag = "Margherita Deluxe" + +/obj/item/pizzabox/margherita/proc/AddPizza() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita(src) + +/obj/item/pizzabox/margherita/robo/AddPizza() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita/robo(src) + +/obj/item/pizzabox/vegetable/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/vegetable(src) + boxtag = "Gourmet Vegatable" + +/obj/item/pizzabox/mushroom/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/mushroom(src) + boxtag = "Mushroom Special" + +/obj/item/pizzabox/meat/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/meat(src) + boxtag = "Meatlover's Supreme" + +/obj/item/pizzabox/pineapple/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) + boxtag = "Honolulu Chew" + +//An anomalous pizza box that, when opened, produces the opener's favorite kind of pizza. +/obj/item/pizzabox/infinite + resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF //hard to destroy + can_open_on_fall = FALSE + var/list/pizza_types = list( + /obj/item/reagent_containers/food/snacks/pizza/meat = 1, + /obj/item/reagent_containers/food/snacks/pizza/mushroom = 1, + /obj/item/reagent_containers/food/snacks/pizza/margherita = 1, + /obj/item/reagent_containers/food/snacks/pizza/sassysage = 0.8, + /obj/item/reagent_containers/food/snacks/pizza/vegetable = 0.8, + /obj/item/reagent_containers/food/snacks/pizza/pineapple = 0.5, + /obj/item/reagent_containers/food/snacks/pizza/donkpocket = 0.3, + /obj/item/reagent_containers/food/snacks/pizza/dank = 0.1) //pizzas here are weighted by chance to be someone's favorite + var/static/list/pizza_preferences + +/obj/item/pizzabox/infinite/Initialize() + . = ..() + if(!pizza_preferences) + pizza_preferences = list() + +/obj/item/pizzabox/infinite/examine(mob/user) + . = ..() + if(isobserver(user)) + . += "This pizza box is anomalous, and will produce infinite pizza." + +/obj/item/pizzabox/infinite/attack_self(mob/living/user) + QDEL_NULL(pizza) + if(ishuman(user)) + attune_pizza(user) + . = ..() + +/obj/item/pizzabox/infinite/proc/attune_pizza(mob/living/carbon/human/noms) //tonight on "proc names I never thought I'd type" + if(!pizza_preferences[noms.ckey]) + pizza_preferences[noms.ckey] = pickweight(pizza_types) + if(noms.has_quirk(/datum/quirk/pineapple_liker)) + pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/pineapple + else if(noms.has_quirk(/datum/quirk/pineapple_hater)) + var/list/pineapple_pizza_liker = pizza_types.Copy() + pineapple_pizza_liker -= /obj/item/reagent_containers/food/snacks/pizza/pineapple + pizza_preferences[noms.ckey] = pickweight(pineapple_pizza_liker) + else if(noms.mind && noms.mind.assigned_role == "Botanist") + pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/dank + + var/obj/item/pizza_type = pizza_preferences[noms.ckey] + pizza = new pizza_type (src) + pizza.foodtype = noms.dna.species.liked_food //it's our favorite! diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm index b241ae1e5c3..ae7997a4185 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm @@ -1,155 +1,155 @@ - -// This is the home of drink related tablecrafting recipes, I have opted to only let players bottle fancy boozes to reduce the number of entries. - -///////////////// Booze & Bottles /////////////////// - -/datum/crafting_recipe/lizardwine - name = "Lizard Wine" - time = 40 - reqs = list( - /obj/item/organ/tail/lizard = 1, - /datum/reagent/consumable/ethanol = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/lizardwine - category = CAT_DRINK - -/datum/crafting_recipe/moonshinejug - name = "Moonshine Jug" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/moonshine = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/moonshine - category = CAT_DRINK - -/datum/crafting_recipe/hoochbottle - name = "Hooch Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /obj/item/storage/box/papersack = 1, - /datum/reagent/consumable/ethanol/hooch = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/hooch - category = CAT_DRINK - -/datum/crafting_recipe/blazaambottle - name = "Blazaam Bottle" - time = 20 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/blazaam = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/blazaam - category = CAT_DRINK - -/datum/crafting_recipe/champagnebottle - name = "Champagne Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/champagne = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/champagne - category = CAT_DRINK - -/datum/crafting_recipe/trappistbottle - name = "Trappist Bottle" - time = 15 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle/small = 1, - /datum/reagent/consumable/ethanol/trappist = 50 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/trappist - category = CAT_DRINK - -/datum/crafting_recipe/goldschlagerbottle - name = "Goldschlager Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/goldschlager = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/goldschlager - category = CAT_DRINK - -/datum/crafting_recipe/patronbottle - name = "Patron Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/patron = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/patron - category = CAT_DRINK - -////////////////////// Non-alcoholic recipes /////////////////// - -/datum/crafting_recipe/holybottle - name = "Holy Water Flask" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/water/holywater = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/holywater - category = CAT_DRINK - -//flask of unholy water is a beaker for some reason, I will try making it a bottle and add it here once the antag freeze is over. t. kryson - -/datum/crafting_recipe/nothingbottle - name = "Nothing Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/nothing = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/bottleofnothing - category = CAT_DRINK - -/datum/crafting_recipe/smallcarton - name = "Small Carton" - result = /obj/item/reagent_containers/food/drinks/sillycup/smallcarton - time = 10 - reqs = list(/obj/item/stack/sheet/cardboard = 1) - category = CAT_DRINK - -/datum/crafting_recipe/candycornliquor - name = "candy corn liquor" - result = /obj/item/reagent_containers/food/drinks/bottle/candycornliquor - time = 30 - reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, - /obj/item/reagent_containers/food/snacks/candy_corn = 1, - /obj/item/reagent_containers/food/drinks/bottle = 1) - category = CAT_DRINK - -/datum/crafting_recipe/kong - name = "Kong" - result = /obj/item/reagent_containers/food/drinks/bottle/kong - time = 30 - reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, - /obj/item/reagent_containers/food/snacks/monkeycube = 1, - /obj/item/reagent_containers/food/drinks/bottle = 1) - category = CAT_DRINK - -/datum/crafting_recipe/pruno - name = "pruno mix" - result = /obj/item/reagent_containers/food/drinks/bottle/pruno - time = 30 - reqs = list(/obj/item/storage/bag/trash = 1, - /obj/item/reagent_containers/food/snacks/breadslice/moldy = 1, - /obj/item/reagent_containers/food/snacks/grown = 4, - /obj/item/reagent_containers/food/snacks/candy_corn = 2, - /datum/reagent/water = 15) - category = CAT_DRINK - -/datum/crafting_recipe/lean - name = "lean" - result = /obj/item/reagent_containers/food/drinks/colocup/lean - time = 30 - reqs = list(/obj/item/reagent_containers/food/drinks/colocup = 1, - /obj/item/reagent_containers/food/snacks/chewable/gumball = 2, - /datum/reagent/medicine/morphine = 5, - /datum/reagent/consumable/space_up = 15) - category = CAT_DRINK + +// This is the home of drink related tablecrafting recipes, I have opted to only let players bottle fancy boozes to reduce the number of entries. + +///////////////// Booze & Bottles /////////////////// + +/datum/crafting_recipe/lizardwine + name = "Lizard Wine" + time = 40 + reqs = list( + /obj/item/organ/tail/lizard = 1, + /datum/reagent/consumable/ethanol = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/lizardwine + category = CAT_DRINK + +/datum/crafting_recipe/moonshinejug + name = "Moonshine Jug" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/moonshine = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/moonshine + category = CAT_DRINK + +/datum/crafting_recipe/hoochbottle + name = "Hooch Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /obj/item/storage/box/papersack = 1, + /datum/reagent/consumable/ethanol/hooch = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/hooch + category = CAT_DRINK + +/datum/crafting_recipe/blazaambottle + name = "Blazaam Bottle" + time = 20 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/blazaam = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/blazaam + category = CAT_DRINK + +/datum/crafting_recipe/champagnebottle + name = "Champagne Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/champagne = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/champagne + category = CAT_DRINK + +/datum/crafting_recipe/trappistbottle + name = "Trappist Bottle" + time = 15 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle/small = 1, + /datum/reagent/consumable/ethanol/trappist = 50 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/trappist + category = CAT_DRINK + +/datum/crafting_recipe/goldschlagerbottle + name = "Goldschlager Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/goldschlager = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/goldschlager + category = CAT_DRINK + +/datum/crafting_recipe/patronbottle + name = "Patron Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/patron = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/patron + category = CAT_DRINK + +////////////////////// Non-alcoholic recipes /////////////////// + +/datum/crafting_recipe/holybottle + name = "Holy Water Flask" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/water/holywater = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/holywater + category = CAT_DRINK + +//flask of unholy water is a beaker for some reason, I will try making it a bottle and add it here once the antag freeze is over. t. kryson + +/datum/crafting_recipe/nothingbottle + name = "Nothing Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/nothing = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/bottleofnothing + category = CAT_DRINK + +/datum/crafting_recipe/smallcarton + name = "Small Carton" + result = /obj/item/reagent_containers/food/drinks/sillycup/smallcarton + time = 10 + reqs = list(/obj/item/stack/sheet/cardboard = 1) + category = CAT_DRINK + +/datum/crafting_recipe/candycornliquor + name = "candy corn liquor" + result = /obj/item/reagent_containers/food/drinks/bottle/candycornliquor + time = 30 + reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, + /obj/item/reagent_containers/food/snacks/candy_corn = 1, + /obj/item/reagent_containers/food/drinks/bottle = 1) + category = CAT_DRINK + +/datum/crafting_recipe/kong + name = "Kong" + result = /obj/item/reagent_containers/food/drinks/bottle/kong + time = 30 + reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, + /obj/item/reagent_containers/food/snacks/monkeycube = 1, + /obj/item/reagent_containers/food/drinks/bottle = 1) + category = CAT_DRINK + +/datum/crafting_recipe/pruno + name = "pruno mix" + result = /obj/item/reagent_containers/food/drinks/bottle/pruno + time = 30 + reqs = list(/obj/item/storage/bag/trash = 1, + /obj/item/reagent_containers/food/snacks/breadslice/moldy = 1, + /obj/item/reagent_containers/food/snacks/grown = 4, + /obj/item/reagent_containers/food/snacks/candy_corn = 2, + /datum/reagent/water = 15) + category = CAT_DRINK + +/datum/crafting_recipe/lean + name = "lean" + result = /obj/item/reagent_containers/food/drinks/colocup/lean + time = 30 + reqs = list(/obj/item/reagent_containers/food/drinks/colocup = 1, + /obj/item/reagent_containers/food/snacks/chewable/gumball = 2, + /datum/reagent/medicine/morphine = 5, + /datum/reagent/consumable/space_up = 15) + category = CAT_DRINK diff --git a/code/modules/games/kotahi.dm b/code/modules/games/kotahi.dm index d0fec6c6274..3d9a72154f4 100644 --- a/code/modules/games/kotahi.dm +++ b/code/modules/games/kotahi.dm @@ -1,20 +1,20 @@ -/obj/item/toy/cards/deck/kotahi - name = "\improper KOTAHI deck" - desc = "A deck of kotahi cards. House rules to argue over not included." - icon = 'icons/obj/toy.dmi' - icon_state = "deck_kotahi_full" - deckstyle = "kotahi" - -//Populate the deck. -/obj/item/toy/cards/deck/kotahi/populate_deck() - for(var/colour in list("Red","Yellow","Green","Blue")) - cards += "[colour] 0" //kotahi decks have only one colour of each 0, weird huh? - for(var/k in 0 to 1) //two of each colour of number - cards += "[colour] skip" - cards += "[colour] reverse" - cards += "[colour] draw 2" - for(var/i in 1 to 9) - cards += "[colour] [i]" - for(var/k in 0 to 3) //4 wilds and draw 4s - cards += "Wildcard" - cards += "Draw 4" +/obj/item/toy/cards/deck/kotahi + name = "\improper KOTAHI deck" + desc = "A deck of kotahi cards. House rules to argue over not included." + icon = 'icons/obj/toy.dmi' + icon_state = "deck_kotahi_full" + deckstyle = "kotahi" + +//Populate the deck. +/obj/item/toy/cards/deck/kotahi/populate_deck() + for(var/colour in list("Red","Yellow","Green","Blue")) + cards += "[colour] 0" //kotahi decks have only one colour of each 0, weird huh? + for(var/k in 0 to 1) //two of each colour of number + cards += "[colour] skip" + cards += "[colour] reverse" + cards += "[colour] draw 2" + for(var/i in 1 to 9) + cards += "[colour] [i]" + for(var/k in 0 to 3) //4 wilds and draw 4s + cards += "Wildcard" + cards += "Draw 4" diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm index 8923def4221..7d39ad8fa96 100644 --- a/code/modules/holodeck/holo_effect.dm +++ b/code/modules/holodeck/holo_effect.dm @@ -1,111 +1,111 @@ -/* - The holodeck activates these shortly after the program loads, - and deactivates them immediately before changing or disabling the holodeck. - - These remove snowflake code for special holodeck functions. -*/ -/obj/effect/holodeck_effect - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - invisibility = INVISIBILITY_ABSTRACT - -/obj/effect/holodeck_effect/proc/activate(var/obj/machinery/computer/holodeck/HC) - return - -/obj/effect/holodeck_effect/proc/deactivate(var/obj/machinery/computer/holodeck/HC) - qdel(src) - return - -// Called by the holodeck computer as long as the program is running -/obj/effect/holodeck_effect/proc/tick(var/obj/machinery/computer/holodeck/HC) - return - -/obj/effect/holodeck_effect/proc/safety(var/active) - return - - -// Generates a holodeck-tracked card deck -/obj/effect/holodeck_effect/cards - icon = 'icons/obj/toy.dmi' - icon_state = "deck_nanotrasen_full" - var/obj/item/toy/cards/deck/D - -/obj/effect/holodeck_effect/cards/activate(var/obj/machinery/computer/holodeck/HC) - D = new(loc) - safety(!(HC.obj_flags & EMAGGED)) - D.holo = HC - return D - -/obj/effect/holodeck_effect/cards/safety(active) - if(!D) - return - if(active) - D.card_hitsound = null - D.card_force = 0 - D.card_throwforce = 0 - D.card_throw_speed = 3 - D.card_throw_range = 7 - D.card_attack_verb = list("attacked") - else - D.card_hitsound = 'sound/weapons/bladeslice.ogg' - D.card_force = 5 - D.card_throwforce = 10 - D.card_throw_speed = 3 - D.card_throw_range = 7 - D.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") - - -/obj/effect/holodeck_effect/sparks/activate(var/obj/machinery/computer/holodeck/HC) - var/turf/T = get_turf(src) - if(T) - var/datum/effect_system/spark_spread/s = new - s.set_up(3, 1, T) - s.start() - T.temperature = 5000 - T.hotspot_expose(50000,50000,1) - - - -/obj/effect/holodeck_effect/mobspawner - var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp - var/mob/mob = null - -/obj/effect/holodeck_effect/mobspawner/activate(var/obj/machinery/computer/holodeck/HC) - if(islist(mobtype)) - mobtype = pick(mobtype) - mob = new mobtype(loc) - mob.flags_1 |= HOLOGRAM_1 - - // these vars are not really standardized but all would theoretically create stuff on death - for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars) - mob.vars[v] = null - return mob - -/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC) - if(mob) - HC.derez(mob) - qdel(src) - -/obj/effect/holodeck_effect/mobspawner/pet - mobtype = list( - /mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo, - /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/kitten, - /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/dog/corgi/puppy, - /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/fox) - -/obj/effect/holodeck_effect/mobspawner/bee - mobtype = /mob/living/simple_animal/hostile/poison/bees/toxin - -/obj/effect/holodeck_effect/mobspawner/monkey - mobtype = /mob/living/simple_animal/holodeck_monkey - -/obj/effect/holodeck_effect/mobspawner/penguin - mobtype = /mob/living/simple_animal/pet/penguin/emperor - -/obj/effect/holodeck_effect/mobspawner/penguin/Initialize() - if(prob(1)) - mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero - return ..() - -/obj/effect/holodeck_effect/mobspawner/penguin_baby - mobtype = /mob/living/simple_animal/pet/penguin/baby +/* + The holodeck activates these shortly after the program loads, + and deactivates them immediately before changing or disabling the holodeck. + + These remove snowflake code for special holodeck functions. +*/ +/obj/effect/holodeck_effect + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + invisibility = INVISIBILITY_ABSTRACT + +/obj/effect/holodeck_effect/proc/activate(var/obj/machinery/computer/holodeck/HC) + return + +/obj/effect/holodeck_effect/proc/deactivate(var/obj/machinery/computer/holodeck/HC) + qdel(src) + return + +// Called by the holodeck computer as long as the program is running +/obj/effect/holodeck_effect/proc/tick(var/obj/machinery/computer/holodeck/HC) + return + +/obj/effect/holodeck_effect/proc/safety(var/active) + return + + +// Generates a holodeck-tracked card deck +/obj/effect/holodeck_effect/cards + icon = 'icons/obj/toy.dmi' + icon_state = "deck_nanotrasen_full" + var/obj/item/toy/cards/deck/D + +/obj/effect/holodeck_effect/cards/activate(var/obj/machinery/computer/holodeck/HC) + D = new(loc) + safety(!(HC.obj_flags & EMAGGED)) + D.holo = HC + return D + +/obj/effect/holodeck_effect/cards/safety(active) + if(!D) + return + if(active) + D.card_hitsound = null + D.card_force = 0 + D.card_throwforce = 0 + D.card_throw_speed = 3 + D.card_throw_range = 7 + D.card_attack_verb = list("attacked") + else + D.card_hitsound = 'sound/weapons/bladeslice.ogg' + D.card_force = 5 + D.card_throwforce = 10 + D.card_throw_speed = 3 + D.card_throw_range = 7 + D.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") + + +/obj/effect/holodeck_effect/sparks/activate(var/obj/machinery/computer/holodeck/HC) + var/turf/T = get_turf(src) + if(T) + var/datum/effect_system/spark_spread/s = new + s.set_up(3, 1, T) + s.start() + T.temperature = 5000 + T.hotspot_expose(50000,50000,1) + + + +/obj/effect/holodeck_effect/mobspawner + var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp + var/mob/mob = null + +/obj/effect/holodeck_effect/mobspawner/activate(var/obj/machinery/computer/holodeck/HC) + if(islist(mobtype)) + mobtype = pick(mobtype) + mob = new mobtype(loc) + mob.flags_1 |= HOLOGRAM_1 + + // these vars are not really standardized but all would theoretically create stuff on death + for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars) + mob.vars[v] = null + return mob + +/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC) + if(mob) + HC.derez(mob) + qdel(src) + +/obj/effect/holodeck_effect/mobspawner/pet + mobtype = list( + /mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo, + /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/kitten, + /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/dog/corgi/puppy, + /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/fox) + +/obj/effect/holodeck_effect/mobspawner/bee + mobtype = /mob/living/simple_animal/hostile/poison/bees/toxin + +/obj/effect/holodeck_effect/mobspawner/monkey + mobtype = /mob/living/simple_animal/holodeck_monkey + +/obj/effect/holodeck_effect/mobspawner/penguin + mobtype = /mob/living/simple_animal/pet/penguin/emperor + +/obj/effect/holodeck_effect/mobspawner/penguin/Initialize() + if(prob(1)) + mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero + return ..() + +/obj/effect/holodeck_effect/mobspawner/penguin_baby + mobtype = /mob/living/simple_animal/pet/penguin/baby diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index 0c7edc527d9..9a624662012 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -1,229 +1,229 @@ -/* - Items, Structures, Machines -*/ - - -// -// Items -// - -/obj/item/holo - damtype = STAMINA - -/obj/item/holo/esword - name = "holographic energy sword" - desc = "May the force be with you. Sorta." - icon = 'icons/obj/transforming_energy.dmi' - icon_state = "sword0" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - force = 3.0 - throw_speed = 2 - throw_range = 5 - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - hitsound = "swing_hit" - armour_penetration = 50 - var/active = 0 - var/saber_color - -/obj/item/holo/esword/green/Initialize() - . = ..() - saber_color = "green" - -/obj/item/holo/esword/red/Initialize() - . = ..() - saber_color = "red" - -/obj/item/holo/esword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/holo/esword/attack(target as mob, mob/user as mob) - ..() - -/obj/item/holo/esword/Initialize() - . = ..() - saber_color = pick("red","blue","green","purple") - -/obj/item/holo/esword/attack_self(mob/living/user as mob) - active = !active - if (active) - force = 30 - icon_state = "sword[saber_color]" - w_class = WEIGHT_CLASS_BULKY - hitsound = 'sound/weapons/blade1.ogg' - playsound(user, 'sound/weapons/saberon.ogg', 20, TRUE) - to_chat(user, "[src] is now active.") - else - force = 3 - icon_state = "sword0" - w_class = WEIGHT_CLASS_SMALL - hitsound = "swing_hit" - playsound(user, 'sound/weapons/saberoff.ogg', 20, TRUE) - to_chat(user, "[src] can now be concealed.") - return - -//BASKETBALL OBJECTS - -/obj/item/toy/beach_ball/holoball - name = "basketball" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "basketball" - inhand_icon_state = "basketball" - desc = "Here's your chance, do your dance at the Space Jam." - w_class = WEIGHT_CLASS_BULKY //Stops people from hiding it in their bags/pockets - -/obj/item/toy/beach_ball/holoball/dodgeball - name = "dodgeball" - icon_state = "dodgeball" - inhand_icon_state = "dodgeball" - desc = "Used for playing the most violent and degrading of childhood games." - -/obj/item/toy/beach_ball/holoball/dodgeball/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - ..() - if((ishuman(hit_atom))) - var/mob/living/carbon/M = hit_atom - playsound(src, 'sound/items/dodgeball.ogg', 50, TRUE) - M.apply_damage(10, STAMINA) - if(prob(5)) - M.Paralyze(60) - visible_message("[M] is knocked right off [M.p_their()] feet!") - -// -// Structures -// - -/obj/structure/holohoop - name = "basketball hoop" - desc = "Boom, shakalaka!" - icon = 'icons/obj/basketball.dmi' - icon_state = "hoop" - anchored = TRUE - density = TRUE - -/obj/structure/holohoop/attackby(obj/item/W as obj, mob/user as mob, params) - if(get_dist(src,user)<2) - if(user.transferItemToLoc(W, drop_location())) - visible_message("[user] dunks [W] into \the [src]!") - -/obj/structure/holohoop/attack_hand(mob/user) - . = ..() - if(.) - return - if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, "You need a better grip to do that!") - return - L.forceMove(loc) - L.Paralyze(100) - visible_message("[user] dunks [L] into \the [src]!") - user.stop_pulling() - else - ..() - -/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if (isitem(AM) && !istype(AM,/obj/projectile)) - if(prob(50)) - AM.forceMove(get_turf(src)) - visible_message("Swish! [AM] lands in [src].") - return - else - visible_message("[AM] bounces off of [src]'s rim!") - return ..() - else - return ..() - - - -// -// Machines -// - -/obj/machinery/readybutton - name = "ready declaration device" - desc = "This device is used to declare ready. If all devices in an area are ready, the event will begin!" - icon = 'icons/obj/monitors.dmi' - icon_state = "auth_off" - var/ready = 0 - var/area/currentarea = null - var/eventstarted = FALSE - - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 6 - power_channel = AREA_USAGE_ENVIRON - -/obj/machinery/readybutton/attack_ai(mob/user as mob) - to_chat(user, "The station AI is not to interact with these devices!") - return - -/obj/machinery/readybutton/attack_paw(mob/user as mob) - to_chat(user, "You are too primitive to use this device!") - return - -/obj/machinery/readybutton/attackby(obj/item/W as obj, mob/user as mob, params) - to_chat(user, "The device is a solid button, there's nothing you can do with it!") - -/obj/machinery/readybutton/attack_hand(mob/user as mob) - . = ..() - if(.) - return - if(user.stat || machine_stat & (NOPOWER|BROKEN)) - to_chat(user, "This device is not powered!") - return - - currentarea = get_area(src.loc) - if(!currentarea) - qdel(src) - - if(eventstarted) - to_chat(usr, "The event has already begun!") - return - - ready = !ready - - update_icon() - - var/numbuttons = 0 - var/numready = 0 - for(var/obj/machinery/readybutton/button in currentarea) - numbuttons++ - if (button.ready) - numready++ - - if(numbuttons == numready) - begin_event() - -/obj/machinery/readybutton/update_icon_state() - if(ready) - icon_state = "auth_on" - else - icon_state = "auth_off" - -/obj/machinery/readybutton/proc/begin_event() - - eventstarted = TRUE - - for(var/obj/structure/window/W in currentarea) - if(W.flags_1&NODECONSTRUCT_1) // Just in case: only holo-windows - qdel(W) - - for(var/mob/M in currentarea) - to_chat(M, "FIGHT!") - -/obj/machinery/conveyor/holodeck - -/obj/machinery/conveyor/holodeck/attackby(obj/item/I, mob/user, params) - if(!user.transferItemToLoc(I, drop_location())) - return ..() - -/obj/item/paper/fluff/holodeck/trek_diploma - name = "paper - Starfleet Academy Diploma" - info = {"

                Starfleet Academy


                Official Diploma


                "} - -/obj/item/paper/fluff/holodeck/disclaimer - name = "Holodeck Disclaimer" - info = "Bruises sustained in the holodeck can be healed simply by sleeping." +/* + Items, Structures, Machines +*/ + + +// +// Items +// + +/obj/item/holo + damtype = STAMINA + +/obj/item/holo/esword + name = "holographic energy sword" + desc = "May the force be with you. Sorta." + icon = 'icons/obj/transforming_energy.dmi' + icon_state = "sword0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + force = 3.0 + throw_speed = 2 + throw_range = 5 + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + hitsound = "swing_hit" + armour_penetration = 50 + var/active = 0 + var/saber_color + +/obj/item/holo/esword/green/Initialize() + . = ..() + saber_color = "green" + +/obj/item/holo/esword/red/Initialize() + . = ..() + saber_color = "red" + +/obj/item/holo/esword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/holo/esword/attack(target as mob, mob/user as mob) + ..() + +/obj/item/holo/esword/Initialize() + . = ..() + saber_color = pick("red","blue","green","purple") + +/obj/item/holo/esword/attack_self(mob/living/user as mob) + active = !active + if (active) + force = 30 + icon_state = "sword[saber_color]" + w_class = WEIGHT_CLASS_BULKY + hitsound = 'sound/weapons/blade1.ogg' + playsound(user, 'sound/weapons/saberon.ogg', 20, TRUE) + to_chat(user, "[src] is now active.") + else + force = 3 + icon_state = "sword0" + w_class = WEIGHT_CLASS_SMALL + hitsound = "swing_hit" + playsound(user, 'sound/weapons/saberoff.ogg', 20, TRUE) + to_chat(user, "[src] can now be concealed.") + return + +//BASKETBALL OBJECTS + +/obj/item/toy/beach_ball/holoball + name = "basketball" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "basketball" + inhand_icon_state = "basketball" + desc = "Here's your chance, do your dance at the Space Jam." + w_class = WEIGHT_CLASS_BULKY //Stops people from hiding it in their bags/pockets + +/obj/item/toy/beach_ball/holoball/dodgeball + name = "dodgeball" + icon_state = "dodgeball" + inhand_icon_state = "dodgeball" + desc = "Used for playing the most violent and degrading of childhood games." + +/obj/item/toy/beach_ball/holoball/dodgeball/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + ..() + if((ishuman(hit_atom))) + var/mob/living/carbon/M = hit_atom + playsound(src, 'sound/items/dodgeball.ogg', 50, TRUE) + M.apply_damage(10, STAMINA) + if(prob(5)) + M.Paralyze(60) + visible_message("[M] is knocked right off [M.p_their()] feet!") + +// +// Structures +// + +/obj/structure/holohoop + name = "basketball hoop" + desc = "Boom, shakalaka!" + icon = 'icons/obj/basketball.dmi' + icon_state = "hoop" + anchored = TRUE + density = TRUE + +/obj/structure/holohoop/attackby(obj/item/W as obj, mob/user as mob, params) + if(get_dist(src,user)<2) + if(user.transferItemToLoc(W, drop_location())) + visible_message("[user] dunks [W] into \the [src]!") + +/obj/structure/holohoop/attack_hand(mob/user) + . = ..() + if(.) + return + if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) + var/mob/living/L = user.pulling + if(user.grab_state < GRAB_AGGRESSIVE) + to_chat(user, "You need a better grip to do that!") + return + L.forceMove(loc) + L.Paralyze(100) + visible_message("[user] dunks [L] into \the [src]!") + user.stop_pulling() + else + ..() + +/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if (isitem(AM) && !istype(AM,/obj/projectile)) + if(prob(50)) + AM.forceMove(get_turf(src)) + visible_message("Swish! [AM] lands in [src].") + return + else + visible_message("[AM] bounces off of [src]'s rim!") + return ..() + else + return ..() + + + +// +// Machines +// + +/obj/machinery/readybutton + name = "ready declaration device" + desc = "This device is used to declare ready. If all devices in an area are ready, the event will begin!" + icon = 'icons/obj/monitors.dmi' + icon_state = "auth_off" + var/ready = 0 + var/area/currentarea = null + var/eventstarted = FALSE + + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 6 + power_channel = AREA_USAGE_ENVIRON + +/obj/machinery/readybutton/attack_ai(mob/user as mob) + to_chat(user, "The station AI is not to interact with these devices!") + return + +/obj/machinery/readybutton/attack_paw(mob/user as mob) + to_chat(user, "You are too primitive to use this device!") + return + +/obj/machinery/readybutton/attackby(obj/item/W as obj, mob/user as mob, params) + to_chat(user, "The device is a solid button, there's nothing you can do with it!") + +/obj/machinery/readybutton/attack_hand(mob/user as mob) + . = ..() + if(.) + return + if(user.stat || machine_stat & (NOPOWER|BROKEN)) + to_chat(user, "This device is not powered!") + return + + currentarea = get_area(src.loc) + if(!currentarea) + qdel(src) + + if(eventstarted) + to_chat(usr, "The event has already begun!") + return + + ready = !ready + + update_icon() + + var/numbuttons = 0 + var/numready = 0 + for(var/obj/machinery/readybutton/button in currentarea) + numbuttons++ + if (button.ready) + numready++ + + if(numbuttons == numready) + begin_event() + +/obj/machinery/readybutton/update_icon_state() + if(ready) + icon_state = "auth_on" + else + icon_state = "auth_off" + +/obj/machinery/readybutton/proc/begin_event() + + eventstarted = TRUE + + for(var/obj/structure/window/W in currentarea) + if(W.flags_1&NODECONSTRUCT_1) // Just in case: only holo-windows + qdel(W) + + for(var/mob/M in currentarea) + to_chat(M, "FIGHT!") + +/obj/machinery/conveyor/holodeck + +/obj/machinery/conveyor/holodeck/attackby(obj/item/I, mob/user, params) + if(!user.transferItemToLoc(I, drop_location())) + return ..() + +/obj/item/paper/fluff/holodeck/trek_diploma + name = "paper - Starfleet Academy Diploma" + info = {"

                Starfleet Academy


                Official Diploma


                "} + +/obj/item/paper/fluff/holodeck/disclaimer + name = "Holodeck Disclaimer" + info = "Bruises sustained in the holodeck can be healed simply by sleeping." diff --git a/code/modules/hydroponics/biogenerator.dm b/code/modules/hydroponics/biogenerator.dm index d0715982687..4a0e1864dd8 100644 --- a/code/modules/hydroponics/biogenerator.dm +++ b/code/modules/hydroponics/biogenerator.dm @@ -1,343 +1,343 @@ -/obj/machinery/biogenerator - name = "biogenerator" - desc = "Converts plants into biomass, which can be used to construct useful items." - icon = 'icons/obj/machines/biogenerator.dmi' - icon_state = "biogen-empty" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 40 - circuit = /obj/item/circuitboard/machine/biogenerator - ui_x = 550 - ui_y = 380 - var/processing = FALSE - var/obj/item/reagent_containers/glass/beaker = null - var/points = 0 - var/efficiency = 0 - var/productivity = 0 - var/max_items = 40 - var/datum/techweb/stored_research - var/list/show_categories = list("Food", "Botany Chemicals", "Organic Materials") - /// Currently selected category in the UI - var/selected_cat - -/obj/machinery/biogenerator/Initialize() - . = ..() - stored_research = new /datum/techweb/specialized/autounlocking/biogenerator - create_reagents(1000) - -/obj/machinery/biogenerator/Destroy() - QDEL_NULL(beaker) - return ..() - -/obj/machinery/biogenerator/contents_explosion(severity, target) - ..() - if(beaker) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += beaker - if(EXPLODE_HEAVY) - SSexplosions.medobj += beaker - if(EXPLODE_LIGHT) - SSexplosions.lowobj += beaker - -/obj/machinery/biogenerator/handle_atom_del(atom/A) - ..() - if(A == beaker) - beaker = null - update_icon() - -/obj/machinery/biogenerator/RefreshParts() - var/E = 0 - var/P = 0 - var/max_storage = 40 - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - P += B.rating - max_storage = 40 * B.rating - for(var/obj/item/stock_parts/manipulator/M in component_parts) - E += M.rating - efficiency = E - productivity = P - max_items = max_storage - -/obj/machinery/biogenerator/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Productivity at [productivity*100]%.
                Matter consumption reduced by [(efficiency*25)-25]%.
                Machine can hold up to [max_items] pieces of produce.
                " - -/obj/machinery/biogenerator/on_reagent_change(changetype) //When the reagents change, change the icon as well. - update_icon() - -/obj/machinery/biogenerator/update_icon_state() - if(panel_open) - icon_state = "biogen-empty-o" - else if(!src.beaker) - icon_state = "biogen-empty" - else if(!src.processing) - icon_state = "biogen-stand" - else - icon_state = "biogen-work" - -/obj/machinery/biogenerator/attackby(obj/item/O, mob/user, params) - if(user.a_intent == INTENT_HARM) - return ..() - - if(processing) - to_chat(user, "The biogenerator is currently processing.") - return - - if(default_deconstruction_screwdriver(user, "biogen-empty-o", "biogen-empty", O)) - if(beaker) - var/obj/item/reagent_containers/glass/B = beaker - B.forceMove(drop_location()) - beaker = null - update_icon() - return - - if(default_deconstruction_crowbar(O)) - return - - if(istype(O, /obj/item/reagent_containers/glass)) - . = 1 //no afterattack - if(!panel_open) - if(beaker) - to_chat(user, "A container is already loaded into the machine.") - else - if(!user.transferItemToLoc(O, src)) - return - beaker = O - to_chat(user, "You add the container to the machine.") - update_icon() - else - to_chat(user, "Close the maintenance panel first.") - return - - else if(istype(O, /obj/item/storage/bag/plants)) - var/obj/item/storage/bag/plants/PB = O - var/i = 0 - for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) - i++ - if(i >= max_items) - to_chat(user, "The biogenerator is already full! Activate it.") - else - for(var/obj/item/reagent_containers/food/snacks/grown/G in PB.contents) - if(i >= max_items) - break - if(SEND_SIGNAL(PB, COMSIG_TRY_STORAGE_TAKE, G, src)) - i++ - if(iYou empty the plant bag into the biogenerator.") - else if(PB.contents.len == 0) - to_chat(user, "You empty the plant bag into the biogenerator, filling it to its capacity.") - else - to_chat(user, "You fill the biogenerator to its capacity.") - return TRUE //no afterattack - - else if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) - var/i = 0 - for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) - i++ - if(i >= max_items) - to_chat(user, "The biogenerator is full! Activate it.") - else - if(user.transferItemToLoc(O, src)) - to_chat(user, "You put [O.name] in [src.name]") - return TRUE //no afterattack - else if (istype(O, /obj/item/disk/design_disk)) - user.visible_message("[user] begins to load \the [O] in \the [src]...", - "You begin to load a design from \the [O]...", - "You hear the chatter of a floppy drive.") - processing = TRUE - var/obj/item/disk/design_disk/D = O - if(do_after(user, 10, target = src)) - for(var/B in D.blueprints) - if(B) - stored_research.add_design(B) - processing = FALSE - return TRUE - else - to_chat(user, "You cannot put this in [src.name]!") - -/obj/machinery/biogenerator/AltClick(mob/living/user) - . = ..() - if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user)) - detach(user) - -/** - * activate: Activates biomass processing and converts all inserted grown products into biomass - * - * Arguments: - * * user The mob starting the biomass processing - */ -/obj/machinery/biogenerator/proc/activate(mob/user) - if(user.stat != CONSCIOUS) - return - if(machine_stat != NONE) - return - if(processing) - to_chat(user, "The biogenerator is in the process of working.") - return - var/S = 0 - for(var/obj/item/reagent_containers/food/snacks/grown/I in contents) - S += 5 - if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1) - points += 1 * productivity - else - points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) * 10 * productivity - qdel(I) - if(S) - processing = TRUE - update_icon() - playsound(loc, 'sound/machines/blender.ogg', 50, TRUE) - use_power(S * 30) - sleep(S + 15 / productivity) - processing = FALSE - update_icon() - -/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE) - if(materials.len != 1 || materials[1] != SSmaterials.GetMaterialRef(/datum/material/biomass)) - return FALSE - if (materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency > points) - return FALSE - else - if(remove_points) - points -= materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency - update_icon() - return TRUE - -/obj/machinery/biogenerator/proc/check_container_volume(list/reagents, multiplier = 1) - var/sum_reagents = 0 - for(var/R in reagents) - sum_reagents += reagents[R] - sum_reagents *= multiplier - - if(beaker.reagents.total_volume + sum_reagents > beaker.reagents.maximum_volume) - return FALSE - - return TRUE - -/obj/machinery/biogenerator/proc/create_product(datum/design/D, amount) - if(!beaker || !loc) - return FALSE - - if(ispath(D.build_path, /obj/item/stack)) - if(!check_container_volume(D.make_reagents, amount)) - return FALSE - if(!check_cost(D.materials, amount)) - return FALSE - - new D.build_path(drop_location(), amount) - for(var/R in D.make_reagents) - beaker.reagents.add_reagent(R, D.make_reagents[R]*amount) - else - var/i = amount - while(i > 0) - if(!check_container_volume(D.make_reagents)) - say("Warning: Attached container does not have enough free capacity!") - return . - if(!check_cost(D.materials)) - return . - if(D.build_path) - new D.build_path(loc) - for(var/R in D.make_reagents) - beaker.reagents.add_reagent(R, D.make_reagents[R]) - . = 1 - --i - update_icon() - return . - -/obj/machinery/biogenerator/proc/detach(mob/living/user) - if(beaker) - if(can_interact(user)) - user.put_in_hands(beaker) - else - beaker.drop_location(get_turf(src)) - beaker = null - update_icon() - -/obj/machinery/biogenerator/ui_status(mob/user) - if(machine_stat & BROKEN || panel_open) - return UI_CLOSE - return ..() - -/obj/machinery/biogenerator/ui_base_html(html) - var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/research_designs) - . = replacetext(html, "", assets.css_tag()) - -/obj/machinery/biogenerator/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/research_designs) - assets.send(user) - ui = new(user, src, ui_key, "Biogenerator", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/biogenerator/ui_data(mob/user) - var/list/data = list() - data["beaker"] = beaker ? TRUE : FALSE - data["biomass"] = points - data["processing"] = processing - if(locate(/obj/item/reagent_containers/food/snacks/grown) in contents) - data["can_process"] = TRUE - else - data["can_process"] = FALSE - return data - -/obj/machinery/biogenerator/ui_static_data(mob/user) - var/list/data = list() - data["categories"] = list() - - var/categories = show_categories.Copy() - for(var/V in categories) - categories[V] = list() - for(var/V in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(V) - for(var/C in categories) - if(C in D.category) - categories[C] += D - - for(var/category in categories) - var/list/cat = list( - "name" = category, - "items" = (category == selected_cat ? list() : null)) - for(var/item in categories[category]) - var/datum/design/D = item - cat["items"] += list(list( - "id" = D.id, - "name" = D.name, - "cost" = D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency, - )) - data["categories"] += list(cat) - - return data - -/obj/machinery/biogenerator/ui_act(action, list/params) - if(..()) - return - - switch(action) - if("activate") - activate(usr) - return TRUE - if("detach") - detach(usr) - return TRUE - if("create") - var/amount = text2num(params["amount"]) - amount = clamp(amount, 1, 10) - if(!amount) - return - var/id = params["id"] - if(!stored_research.researched_designs.Find(id)) - stack_trace("ID did not map to a researched datum [id]") - return - var/datum/design/D = SSresearch.techweb_design_by_id(id) - if(D && !istype(D, /datum/design/error_design)) - create_product(D, amount) - else - stack_trace("ID could not be turned into a valid techweb design datum [id]") - return - return TRUE - if("select") - selected_cat = params["category"] - return TRUE +/obj/machinery/biogenerator + name = "biogenerator" + desc = "Converts plants into biomass, which can be used to construct useful items." + icon = 'icons/obj/machines/biogenerator.dmi' + icon_state = "biogen-empty" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 40 + circuit = /obj/item/circuitboard/machine/biogenerator + ui_x = 550 + ui_y = 380 + var/processing = FALSE + var/obj/item/reagent_containers/glass/beaker = null + var/points = 0 + var/efficiency = 0 + var/productivity = 0 + var/max_items = 40 + var/datum/techweb/stored_research + var/list/show_categories = list("Food", "Botany Chemicals", "Organic Materials") + /// Currently selected category in the UI + var/selected_cat + +/obj/machinery/biogenerator/Initialize() + . = ..() + stored_research = new /datum/techweb/specialized/autounlocking/biogenerator + create_reagents(1000) + +/obj/machinery/biogenerator/Destroy() + QDEL_NULL(beaker) + return ..() + +/obj/machinery/biogenerator/contents_explosion(severity, target) + ..() + if(beaker) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += beaker + if(EXPLODE_HEAVY) + SSexplosions.medobj += beaker + if(EXPLODE_LIGHT) + SSexplosions.lowobj += beaker + +/obj/machinery/biogenerator/handle_atom_del(atom/A) + ..() + if(A == beaker) + beaker = null + update_icon() + +/obj/machinery/biogenerator/RefreshParts() + var/E = 0 + var/P = 0 + var/max_storage = 40 + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + P += B.rating + max_storage = 40 * B.rating + for(var/obj/item/stock_parts/manipulator/M in component_parts) + E += M.rating + efficiency = E + productivity = P + max_items = max_storage + +/obj/machinery/biogenerator/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Productivity at [productivity*100]%.
                Matter consumption reduced by [(efficiency*25)-25]%.
                Machine can hold up to [max_items] pieces of produce.
                " + +/obj/machinery/biogenerator/on_reagent_change(changetype) //When the reagents change, change the icon as well. + update_icon() + +/obj/machinery/biogenerator/update_icon_state() + if(panel_open) + icon_state = "biogen-empty-o" + else if(!src.beaker) + icon_state = "biogen-empty" + else if(!src.processing) + icon_state = "biogen-stand" + else + icon_state = "biogen-work" + +/obj/machinery/biogenerator/attackby(obj/item/O, mob/user, params) + if(user.a_intent == INTENT_HARM) + return ..() + + if(processing) + to_chat(user, "The biogenerator is currently processing.") + return + + if(default_deconstruction_screwdriver(user, "biogen-empty-o", "biogen-empty", O)) + if(beaker) + var/obj/item/reagent_containers/glass/B = beaker + B.forceMove(drop_location()) + beaker = null + update_icon() + return + + if(default_deconstruction_crowbar(O)) + return + + if(istype(O, /obj/item/reagent_containers/glass)) + . = 1 //no afterattack + if(!panel_open) + if(beaker) + to_chat(user, "A container is already loaded into the machine.") + else + if(!user.transferItemToLoc(O, src)) + return + beaker = O + to_chat(user, "You add the container to the machine.") + update_icon() + else + to_chat(user, "Close the maintenance panel first.") + return + + else if(istype(O, /obj/item/storage/bag/plants)) + var/obj/item/storage/bag/plants/PB = O + var/i = 0 + for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) + i++ + if(i >= max_items) + to_chat(user, "The biogenerator is already full! Activate it.") + else + for(var/obj/item/reagent_containers/food/snacks/grown/G in PB.contents) + if(i >= max_items) + break + if(SEND_SIGNAL(PB, COMSIG_TRY_STORAGE_TAKE, G, src)) + i++ + if(iYou empty the plant bag into the biogenerator.") + else if(PB.contents.len == 0) + to_chat(user, "You empty the plant bag into the biogenerator, filling it to its capacity.") + else + to_chat(user, "You fill the biogenerator to its capacity.") + return TRUE //no afterattack + + else if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) + var/i = 0 + for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) + i++ + if(i >= max_items) + to_chat(user, "The biogenerator is full! Activate it.") + else + if(user.transferItemToLoc(O, src)) + to_chat(user, "You put [O.name] in [src.name]") + return TRUE //no afterattack + else if (istype(O, /obj/item/disk/design_disk)) + user.visible_message("[user] begins to load \the [O] in \the [src]...", + "You begin to load a design from \the [O]...", + "You hear the chatter of a floppy drive.") + processing = TRUE + var/obj/item/disk/design_disk/D = O + if(do_after(user, 10, target = src)) + for(var/B in D.blueprints) + if(B) + stored_research.add_design(B) + processing = FALSE + return TRUE + else + to_chat(user, "You cannot put this in [src.name]!") + +/obj/machinery/biogenerator/AltClick(mob/living/user) + . = ..() + if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user)) + detach(user) + +/** + * activate: Activates biomass processing and converts all inserted grown products into biomass + * + * Arguments: + * * user The mob starting the biomass processing + */ +/obj/machinery/biogenerator/proc/activate(mob/user) + if(user.stat != CONSCIOUS) + return + if(machine_stat != NONE) + return + if(processing) + to_chat(user, "The biogenerator is in the process of working.") + return + var/S = 0 + for(var/obj/item/reagent_containers/food/snacks/grown/I in contents) + S += 5 + if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1) + points += 1 * productivity + else + points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) * 10 * productivity + qdel(I) + if(S) + processing = TRUE + update_icon() + playsound(loc, 'sound/machines/blender.ogg', 50, TRUE) + use_power(S * 30) + sleep(S + 15 / productivity) + processing = FALSE + update_icon() + +/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE) + if(materials.len != 1 || materials[1] != SSmaterials.GetMaterialRef(/datum/material/biomass)) + return FALSE + if (materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency > points) + return FALSE + else + if(remove_points) + points -= materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency + update_icon() + return TRUE + +/obj/machinery/biogenerator/proc/check_container_volume(list/reagents, multiplier = 1) + var/sum_reagents = 0 + for(var/R in reagents) + sum_reagents += reagents[R] + sum_reagents *= multiplier + + if(beaker.reagents.total_volume + sum_reagents > beaker.reagents.maximum_volume) + return FALSE + + return TRUE + +/obj/machinery/biogenerator/proc/create_product(datum/design/D, amount) + if(!beaker || !loc) + return FALSE + + if(ispath(D.build_path, /obj/item/stack)) + if(!check_container_volume(D.make_reagents, amount)) + return FALSE + if(!check_cost(D.materials, amount)) + return FALSE + + new D.build_path(drop_location(), amount) + for(var/R in D.make_reagents) + beaker.reagents.add_reagent(R, D.make_reagents[R]*amount) + else + var/i = amount + while(i > 0) + if(!check_container_volume(D.make_reagents)) + say("Warning: Attached container does not have enough free capacity!") + return . + if(!check_cost(D.materials)) + return . + if(D.build_path) + new D.build_path(loc) + for(var/R in D.make_reagents) + beaker.reagents.add_reagent(R, D.make_reagents[R]) + . = 1 + --i + update_icon() + return . + +/obj/machinery/biogenerator/proc/detach(mob/living/user) + if(beaker) + if(can_interact(user)) + user.put_in_hands(beaker) + else + beaker.drop_location(get_turf(src)) + beaker = null + update_icon() + +/obj/machinery/biogenerator/ui_status(mob/user) + if(machine_stat & BROKEN || panel_open) + return UI_CLOSE + return ..() + +/obj/machinery/biogenerator/ui_base_html(html) + var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/research_designs) + . = replacetext(html, "", assets.css_tag()) + +/obj/machinery/biogenerator/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/research_designs) + assets.send(user) + ui = new(user, src, ui_key, "Biogenerator", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/biogenerator/ui_data(mob/user) + var/list/data = list() + data["beaker"] = beaker ? TRUE : FALSE + data["biomass"] = points + data["processing"] = processing + if(locate(/obj/item/reagent_containers/food/snacks/grown) in contents) + data["can_process"] = TRUE + else + data["can_process"] = FALSE + return data + +/obj/machinery/biogenerator/ui_static_data(mob/user) + var/list/data = list() + data["categories"] = list() + + var/categories = show_categories.Copy() + for(var/V in categories) + categories[V] = list() + for(var/V in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(V) + for(var/C in categories) + if(C in D.category) + categories[C] += D + + for(var/category in categories) + var/list/cat = list( + "name" = category, + "items" = (category == selected_cat ? list() : null)) + for(var/item in categories[category]) + var/datum/design/D = item + cat["items"] += list(list( + "id" = D.id, + "name" = D.name, + "cost" = D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency, + )) + data["categories"] += list(cat) + + return data + +/obj/machinery/biogenerator/ui_act(action, list/params) + if(..()) + return + + switch(action) + if("activate") + activate(usr) + return TRUE + if("detach") + detach(usr) + return TRUE + if("create") + var/amount = text2num(params["amount"]) + amount = clamp(amount, 1, 10) + if(!amount) + return + var/id = params["id"] + if(!stored_research.researched_designs.Find(id)) + stack_trace("ID did not map to a researched datum [id]") + return + var/datum/design/D = SSresearch.techweb_design_by_id(id) + if(D && !istype(D, /datum/design/error_design)) + create_product(D, amount) + else + stack_trace("ID could not be turned into a valid techweb design datum [id]") + return + return TRUE + if("select") + selected_cat = params["category"] + return TRUE diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm index 06fd4fc87fa..dc881a1847d 100644 --- a/code/modules/hydroponics/grown.dm +++ b/code/modules/hydroponics/grown.dm @@ -1,189 +1,189 @@ -// *********************************************************** -// Foods that are produced from hydroponics ~~~~~~~~~~ -// Data from the seeds carry over to these grown foods -// *********************************************************** - -// Base type. Subtypes are found in /grown dir. Lavaland-based subtypes can be found in mining/ash_flora.dm -/obj/item/reagent_containers/food/snacks/grown - icon = 'icons/obj/hydroponics/harvest.dmi' - name = "fresh produce" // so recipe text doesn't say 'snack' - var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. - var/plantname = "" - var/bitesize_mod = 0 - var/splat_type = /obj/effect/decal/cleanable/food/plant_smudge - // If set, bitesize = 1 + round(reagents.total_volume / bitesize_mod) - dried_type = -1 - // Saves us from having to define each stupid grown's dried_type as itself. - // If you don't want a plant to be driable (watermelons) set this to null in the time definition. - resistance_flags = FLAMMABLE - var/dry_grind = FALSE //If TRUE, this object needs to be dry to be ground up - var/can_distill = TRUE //If FALSE, this object cannot be distilled into an alcohol. - var/distill_reagent //If NULL and this object can be distilled, it uses a generic fruit_wine reagent and adjusts its variables. - var/wine_flavor //If NULL, this is automatically set to the fruit's flavor. Determines the flavor of the wine if distill_reagent is NULL. - var/wine_power = 10 //Determines the boozepwr of the wine if distill_reagent is NULL. - volume = 100 - -/obj/item/reagent_containers/food/snacks/grown/Initialize(mapload, obj/item/seeds/new_seed) - . = ..() - if(!tastes) - tastes = list("[name]" = 1) - - if(new_seed) - seed = new_seed.Copy() - else if(ispath(seed)) - // This is for adminspawn or map-placed growns. They get the default stats of their seed type. - seed = new seed() - seed.adjust_potency(50-seed.potency) - - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - if(dried_type == -1) - dried_type = src.type - - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_new(src, loc) - seed.prepare_result(src) - transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 //Makes the resulting produce's sprite larger or smaller based on potency! - add_juice() - - - -/obj/item/reagent_containers/food/snacks/grown/proc/add_juice() - if(reagents) - if(bitesize_mod) - bitesize = 1 + round(reagents.total_volume / bitesize_mod) - return 1 - return 0 - -/obj/item/reagent_containers/food/snacks/grown/examine(user) - . = ..() - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - if(T.examine_line) - . += T.examine_line - -/obj/item/reagent_containers/food/snacks/grown/attackby(obj/item/O, mob/user, params) - ..() - if (istype(O, /obj/item/plant_analyzer)) - var/obj/item/plant_analyzer/P_analyzer = O - var/msg = "*---------*\n This is \a [src].\n" - if(seed && P_analyzer.scan_mode == PLANT_SCANMODE_STATS) - msg += seed.get_analyzer_text() - var/reag_txt = "" - if(seed && P_analyzer.scan_mode == PLANT_SCANMODE_CHEMICALS) - msg += "
                *Plant Reagents*" - var/chem_cap = 0 - for(var/reagent_id in reagents.reagent_list) - var/datum/reagent/R = reagent_id - var/amt = R.volume - chem_cap += R.volume - reag_txt += "\n- [R.name]: [amt]" - if(chem_cap > 100) - msg += "
                - Reagent Traits Over 100% Production
                " - - if(reag_txt) - msg += "
                *---------*" - msg += reag_txt - msg += "
                *---------*" - to_chat(user, msg) - else - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_attackby(src, O, user) - - -// Various gene procs -/obj/item/reagent_containers/food/snacks/grown/attack_self(mob/user) - if(seed && seed.get_gene(/datum/plant_gene/trait/squash)) - squash(user) - ..() - -/obj/item/reagent_containers/food/snacks/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) //was it caught by a mob? - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_throw_impact(src, hit_atom) - if(seed.get_gene(/datum/plant_gene/trait/squash)) - squash(hit_atom) - -/obj/item/reagent_containers/food/snacks/grown/proc/squash(atom/target) - var/turf/T = get_turf(target) - forceMove(T) - if(ispath(splat_type, /obj/effect/decal/cleanable/food/plant_smudge)) - if(filling_color) - var/obj/O = new splat_type(T) - O.color = filling_color - O.name = "[name] smudge" - else if(splat_type) - new splat_type(T) - - if(trash) - generate_trash(T) - - visible_message("[src] is squashed.","You hear a smack.") - if(seed) - for(var/datum/plant_gene/trait/trait in seed.genes) - trait.on_squash(src, target) - - reagents.expose(T) - for(var/A in T) - reagents.expose(A) - - qdel(src) - -/obj/item/reagent_containers/food/snacks/grown/On_Consume() - if(iscarbon(usr)) - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_consume(src, usr) - ..() - -/obj/item/reagent_containers/food/snacks/grown/generate_trash(atom/location) - if(trash && (ispath(trash, /obj/item/grown) || ispath(trash, /obj/item/reagent_containers/food/snacks/grown))) - . = new trash(location, seed) - trash = null - return - return ..() - -/obj/item/reagent_containers/food/snacks/grown/grind_requirements() - if(dry_grind && !dry) - to_chat(usr, "[src] needs to be dry before it can be ground up!") - return - return TRUE - -/obj/item/reagent_containers/food/snacks/grown/on_grind() - var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) - if(grind_results&&grind_results.len) - for(var/i in 1 to grind_results.len) - grind_results[grind_results[i]] = nutriment - reagents.del_reagent(/datum/reagent/consumable/nutriment) - reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) - -/obj/item/reagent_containers/food/snacks/grown/on_juice() - var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) - if(juice_results&&juice_results.len) - for(var/i in 1 to juice_results.len) - juice_results[juice_results[i]] = nutriment - reagents.del_reagent(/datum/reagent/consumable/nutriment) - reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) - -/* - * Attack self for growns - * - * Spawns the trash item at the growns drop_location() - * - * Then deletes the grown object - * - * Then puts trash item into the hand of user attack selfing, or drops it back on the ground - */ -/obj/item/reagent_containers/food/snacks/grown/shell/attack_self(mob/user) - var/obj/item/T - if(trash) - T = generate_trash(drop_location()) - //Delete grown so our hand is free - qdel(src) - //put trash obj in hands or drop to ground - user.put_in_hands(T, user.active_hand_index, TRUE) - to_chat(user, "You open [src]\'s shell, revealing \a [T].") +// *********************************************************** +// Foods that are produced from hydroponics ~~~~~~~~~~ +// Data from the seeds carry over to these grown foods +// *********************************************************** + +// Base type. Subtypes are found in /grown dir. Lavaland-based subtypes can be found in mining/ash_flora.dm +/obj/item/reagent_containers/food/snacks/grown + icon = 'icons/obj/hydroponics/harvest.dmi' + name = "fresh produce" // so recipe text doesn't say 'snack' + var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. + var/plantname = "" + var/bitesize_mod = 0 + var/splat_type = /obj/effect/decal/cleanable/food/plant_smudge + // If set, bitesize = 1 + round(reagents.total_volume / bitesize_mod) + dried_type = -1 + // Saves us from having to define each stupid grown's dried_type as itself. + // If you don't want a plant to be driable (watermelons) set this to null in the time definition. + resistance_flags = FLAMMABLE + var/dry_grind = FALSE //If TRUE, this object needs to be dry to be ground up + var/can_distill = TRUE //If FALSE, this object cannot be distilled into an alcohol. + var/distill_reagent //If NULL and this object can be distilled, it uses a generic fruit_wine reagent and adjusts its variables. + var/wine_flavor //If NULL, this is automatically set to the fruit's flavor. Determines the flavor of the wine if distill_reagent is NULL. + var/wine_power = 10 //Determines the boozepwr of the wine if distill_reagent is NULL. + volume = 100 + +/obj/item/reagent_containers/food/snacks/grown/Initialize(mapload, obj/item/seeds/new_seed) + . = ..() + if(!tastes) + tastes = list("[name]" = 1) + + if(new_seed) + seed = new_seed.Copy() + else if(ispath(seed)) + // This is for adminspawn or map-placed growns. They get the default stats of their seed type. + seed = new seed() + seed.adjust_potency(50-seed.potency) + + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + if(dried_type == -1) + dried_type = src.type + + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_new(src, loc) + seed.prepare_result(src) + transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 //Makes the resulting produce's sprite larger or smaller based on potency! + add_juice() + + + +/obj/item/reagent_containers/food/snacks/grown/proc/add_juice() + if(reagents) + if(bitesize_mod) + bitesize = 1 + round(reagents.total_volume / bitesize_mod) + return 1 + return 0 + +/obj/item/reagent_containers/food/snacks/grown/examine(user) + . = ..() + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + if(T.examine_line) + . += T.examine_line + +/obj/item/reagent_containers/food/snacks/grown/attackby(obj/item/O, mob/user, params) + ..() + if (istype(O, /obj/item/plant_analyzer)) + var/obj/item/plant_analyzer/P_analyzer = O + var/msg = "*---------*\n This is \a [src].\n" + if(seed && P_analyzer.scan_mode == PLANT_SCANMODE_STATS) + msg += seed.get_analyzer_text() + var/reag_txt = "" + if(seed && P_analyzer.scan_mode == PLANT_SCANMODE_CHEMICALS) + msg += "
                *Plant Reagents*" + var/chem_cap = 0 + for(var/reagent_id in reagents.reagent_list) + var/datum/reagent/R = reagent_id + var/amt = R.volume + chem_cap += R.volume + reag_txt += "\n- [R.name]: [amt]" + if(chem_cap > 100) + msg += "
                - Reagent Traits Over 100% Production
                " + + if(reag_txt) + msg += "
                *---------*" + msg += reag_txt + msg += "
                *---------*" + to_chat(user, msg) + else + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_attackby(src, O, user) + + +// Various gene procs +/obj/item/reagent_containers/food/snacks/grown/attack_self(mob/user) + if(seed && seed.get_gene(/datum/plant_gene/trait/squash)) + squash(user) + ..() + +/obj/item/reagent_containers/food/snacks/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) //was it caught by a mob? + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_throw_impact(src, hit_atom) + if(seed.get_gene(/datum/plant_gene/trait/squash)) + squash(hit_atom) + +/obj/item/reagent_containers/food/snacks/grown/proc/squash(atom/target) + var/turf/T = get_turf(target) + forceMove(T) + if(ispath(splat_type, /obj/effect/decal/cleanable/food/plant_smudge)) + if(filling_color) + var/obj/O = new splat_type(T) + O.color = filling_color + O.name = "[name] smudge" + else if(splat_type) + new splat_type(T) + + if(trash) + generate_trash(T) + + visible_message("[src] is squashed.","You hear a smack.") + if(seed) + for(var/datum/plant_gene/trait/trait in seed.genes) + trait.on_squash(src, target) + + reagents.expose(T) + for(var/A in T) + reagents.expose(A) + + qdel(src) + +/obj/item/reagent_containers/food/snacks/grown/On_Consume() + if(iscarbon(usr)) + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_consume(src, usr) + ..() + +/obj/item/reagent_containers/food/snacks/grown/generate_trash(atom/location) + if(trash && (ispath(trash, /obj/item/grown) || ispath(trash, /obj/item/reagent_containers/food/snacks/grown))) + . = new trash(location, seed) + trash = null + return + return ..() + +/obj/item/reagent_containers/food/snacks/grown/grind_requirements() + if(dry_grind && !dry) + to_chat(usr, "[src] needs to be dry before it can be ground up!") + return + return TRUE + +/obj/item/reagent_containers/food/snacks/grown/on_grind() + var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) + if(grind_results&&grind_results.len) + for(var/i in 1 to grind_results.len) + grind_results[grind_results[i]] = nutriment + reagents.del_reagent(/datum/reagent/consumable/nutriment) + reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) + +/obj/item/reagent_containers/food/snacks/grown/on_juice() + var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) + if(juice_results&&juice_results.len) + for(var/i in 1 to juice_results.len) + juice_results[juice_results[i]] = nutriment + reagents.del_reagent(/datum/reagent/consumable/nutriment) + reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) + +/* + * Attack self for growns + * + * Spawns the trash item at the growns drop_location() + * + * Then deletes the grown object + * + * Then puts trash item into the hand of user attack selfing, or drops it back on the ground + */ +/obj/item/reagent_containers/food/snacks/grown/shell/attack_self(mob/user) + var/obj/item/T + if(trash) + T = generate_trash(drop_location()) + //Delete grown so our hand is free + qdel(src) + //put trash obj in hands or drop to ground + user.put_in_hands(T, user.active_hand_index, TRUE) + to_chat(user, "You open [src]\'s shell, revealing \a [T].") diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm index 49a50dacc8a..fb794db0e76 100644 --- a/code/modules/hydroponics/growninedible.dm +++ b/code/modules/hydroponics/growninedible.dm @@ -1,61 +1,61 @@ -// ********************** -// Other harvested materials from plants (that are not food) -// ********************** - -/obj/item/grown // Grown weapons - name = "grown_weapon" - icon = 'icons/obj/hydroponics/harvest.dmi' - resistance_flags = FLAMMABLE - var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. - -/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) - . = ..() - create_reagents(100) - - if(new_seed) - seed = new_seed.Copy() - else if(ispath(seed)) - // This is for adminspawn or map-placed growns. They get the default stats of their seed type. - seed = new seed() - seed.adjust_potency(50-seed.potency) - - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_new(src, newloc) - - if(istype(src, seed.product)) // no adding reagents if it is just a trash item - seed.prepare_result(src) - transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 - add_juice() - - -/obj/item/grown/attackby(obj/item/O, mob/user, params) - ..() - if (istype(O, /obj/item/plant_analyzer)) - var/msg = "*---------*\n This is \a [src]\n" - if(seed) - msg += seed.get_analyzer_text() - msg += "" - to_chat(usr, msg) - return - -/obj/item/grown/proc/add_juice() - if(reagents) - return 1 - return 0 - -/obj/item/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) //was it caught by a mob? - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_throw_impact(src, hit_atom) - -/obj/item/grown/microwave_act(obj/machinery/microwave/M) - return - -/obj/item/grown/on_grind() - for(var/i in 1 to grind_results.len) - grind_results[grind_results[i]] = round(seed.potency) +// ********************** +// Other harvested materials from plants (that are not food) +// ********************** + +/obj/item/grown // Grown weapons + name = "grown_weapon" + icon = 'icons/obj/hydroponics/harvest.dmi' + resistance_flags = FLAMMABLE + var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. + +/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) + . = ..() + create_reagents(100) + + if(new_seed) + seed = new_seed.Copy() + else if(ispath(seed)) + // This is for adminspawn or map-placed growns. They get the default stats of their seed type. + seed = new seed() + seed.adjust_potency(50-seed.potency) + + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_new(src, newloc) + + if(istype(src, seed.product)) // no adding reagents if it is just a trash item + seed.prepare_result(src) + transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 + add_juice() + + +/obj/item/grown/attackby(obj/item/O, mob/user, params) + ..() + if (istype(O, /obj/item/plant_analyzer)) + var/msg = "*---------*\n This is \a [src]\n" + if(seed) + msg += seed.get_analyzer_text() + msg += "" + to_chat(usr, msg) + return + +/obj/item/grown/proc/add_juice() + if(reagents) + return 1 + return 0 + +/obj/item/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) //was it caught by a mob? + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_throw_impact(src, hit_atom) + +/obj/item/grown/microwave_act(obj/machinery/microwave/M) + return + +/obj/item/grown/on_grind() + for(var/i in 1 to grind_results.len) + grind_results[grind_results[i]] = round(seed.potency) diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index 10b689cef1c..73e6f6587f1 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -1,276 +1,276 @@ - -// Plant analyzer -/obj/item/plant_analyzer - name = "plant analyzer" - desc = "A scanner used to evaluate a plant's various areas of growth, and genetic traits." - icon = 'icons/obj/device.dmi' - icon_state = "hydro" - inhand_icon_state = "analyzer" - worn_icon_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - var/scan_mode = PLANT_SCANMODE_STATS - -/obj/item/plant_analyzer/attack_self(mob/user) - . = ..() - scan_mode = !scan_mode - to_chat(user, "You switch [src] to [scan_mode == PLANT_SCANMODE_CHEMICALS ? "scan for chemical reagents and traits" : "scan for plant growth statistics"].") - -/obj/item/plant_analyzer/attack(mob/living/M, mob/living/carbon/human/user) - //Checks if target is a podman - if(ispodperson(M)) - user.visible_message("[user] analyzes [M]'s vitals.", \ - "You analyze [M]'s vitals.") - if(scan_mode== PLANT_SCANMODE_STATS) - healthscan(user, M, advanced = TRUE) - else - chemscan(user, M) - add_fingerprint(user) - return - return ..() - -// ************************************* -// Hydroponics Tools -// ************************************* - -/obj/item/reagent_containers/spray/weedspray // -- Skie - desc = "It's a toxic mixture, in spray form, to kill small weeds." - icon = 'icons/obj/hydroponics/equipment.dmi' - name = "weed spray" - icon_state = "weedspray" - inhand_icon_state = "spraycan" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - volume = 100 - list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 100) - -/obj/item/reagent_containers/spray/weedspray/suicide_act(mob/user) - user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (TOXLOSS) - -/obj/item/reagent_containers/spray/pestspray // -- Skie - desc = "It's some pest eliminator spray! Do not inhale!" - icon = 'icons/obj/hydroponics/equipment.dmi' - name = "pest spray" - icon_state = "pestspray" - inhand_icon_state = "plantbgone" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - volume = 100 - list_reagents = list(/datum/reagent/toxin/pestkiller = 100) - -/obj/item/reagent_containers/spray/pestspray/suicide_act(mob/user) - user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (TOXLOSS) - -/obj/item/cultivator - name = "cultivator" - desc = "It's used for removing weeds or scratching your back." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "cultivator" - inhand_icon_state = "cultivator" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - flags_1 = CONDUCT_1 - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=50) - attack_verb = list("slashed", "sliced", "cut", "clawed") - hitsound = 'sound/weapons/bladeslice.ogg' - -/obj/item/cultivator/suicide_act(mob/user) - user.visible_message("[user] is scratching [user.p_their()] back as hard as [user.p_they()] can with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/cultivator/rake - name = "rake" - icon_state = "rake" - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("slashed", "sliced", "bashed", "clawed") - hitsound = null - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) - flags_1 = NONE - resistance_flags = FLAMMABLE - -/obj/item/cultivator/rake/Crossed(atom/movable/AM) - . = ..() - if(!ishuman(AM)) - return - var/mob/living/carbon/human/H = AM - if(has_gravity(loc) && HAS_TRAIT(H, TRAIT_CLUMSY) && !H.resting) - H.confused = max(H.confused, 10) - H.Stun(20) - playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) - H.visible_message("[H] steps on [src] causing the handle to hit [H.p_them()] right in the face!", \ - "You step on [src] causing the handle to hit you right in the face!") - -/obj/item/hatchet - name = "hatchet" - desc = "A very sharp axe blade upon a short fibremetal handle. It has a long history of chopping things, but now it is used for chopping wood." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "hatchet" - inhand_icon_state = "hatchet" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - flags_1 = CONDUCT_1 - force = 12 - w_class = WEIGHT_CLASS_SMALL - throwforce = 15 - throw_speed = 3 - throw_range = 4 - custom_materials = list(/datum/material/iron = 15000) - attack_verb = list("chopped", "tore", "lacerated", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/hatchet/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 70, 100) - -/obj/item/hatchet/suicide_act(mob/user) - user.visible_message("[user] is chopping at [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/hatchet/wooden - desc = "A crude axe blade upon a short wooden handle." - icon_state = "woodhatchet" - custom_materials = null - flags_1 = NONE - -/obj/item/scythe - icon_state = "scythe0" - lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' - name = "scythe" - desc = "A sharp and curved blade on a long fibremetal handle, this tool makes it easy to reap what you sow." - force = 13 - throwforce = 5 - throw_speed = 2 - throw_range = 3 - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - armour_penetration = 20 - slot_flags = ITEM_SLOT_BACK - attack_verb = list("chopped", "sliced", "cut", "reaped") - hitsound = 'sound/weapons/bladeslice.ogg' - var/swiping = FALSE - -/obj/item/scythe/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 90, 105) - -/obj/item/scythe/suicide_act(mob/user) - user.visible_message("[user] is beheading [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) - if(BP) - BP.drop_limb() - playsound(src, "desecration" ,50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/scythe/pre_attack(atom/A, mob/living/user, params) - if(swiping || !istype(A, /obj/structure/spacevine) || get_turf(A) == get_turf(user)) - return ..() - var/turf/user_turf = get_turf(user) - var/dir_to_target = get_dir(user_turf, get_turf(A)) - swiping = TRUE - var/static/list/scythe_slash_angles = list(0, 45, 90, -45, -90) - for(var/i in scythe_slash_angles) - var/turf/T = get_step(user_turf, turn(dir_to_target, i)) - for(var/obj/structure/spacevine/V in T) - if(user.Adjacent(V)) - melee_attack_chain(user, V) - swiping = FALSE - return TRUE - -/obj/item/secateurs - name = "secateurs" - desc = "It's a tool for cutting grafts off plants." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "secateurs" - inhand_icon_state = "secateurs" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - flags_1 = CONDUCT_1 - force = 5 - throwforce = 6 - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=4000) - attack_verb = list("slashed", "sliced", "cut", "clawed") - hitsound = 'sound/weapons/bladeslice.ogg' - -/obj/item/geneshears - name = "Botanogenetic Plant Shears" - desc = "A high tech, high fidelity pair of plant shears, capable of cutting genetic traits out of a plant." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "genesheers" - inhand_icon_state = "secateurs" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - flags_1 = CONDUCT_1 - force = 10 - throwforce = 8 - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - material_flags = MATERIAL_NO_EFFECTS - custom_materials = list(/datum/material/iron=4000, /datum/material/uranium=1500, /datum/material/gold=500) - attack_verb = list("slashed", "sliced", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - - -// ************************************* -// Nutrient defines for hydroponics -// ************************************* - - -/obj/item/reagent_containers/glass/bottle/nutrient - name = "bottle of nutrient" - volume = 50 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(1,2,5,10,15,25,50) - -/obj/item/reagent_containers/glass/bottle/nutrient/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - -/obj/item/reagent_containers/glass/bottle/nutrient/ez - name = "bottle of E-Z-Nutrient" - desc = "Contains a fertilizer that causes mild mutations and gradual plant growth with each harvest." - list_reagents = list(/datum/reagent/plantnutriment/eznutriment = 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/l4z - name = "bottle of Left 4 Zed" - desc = "Contains a fertilizer that lightly heals the plant but causes significant mutations in plants over generations." - list_reagents = list(/datum/reagent/plantnutriment/left4zednutriment = 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/rh - name = "bottle of Robust Harvest" - desc = "Contains a fertilizer that increases the yield of a plant while gradually preventing mutations." - list_reagents = list(/datum/reagent/plantnutriment/robustharvestnutriment = 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/empty - name = "bottle" - -/obj/item/reagent_containers/glass/bottle/killer - volume = 30 - amount_per_transfer_from_this = 1 - possible_transfer_amounts = list(1,2,5) - -/obj/item/reagent_containers/glass/bottle/killer/weedkiller - name = "bottle of weed killer" - desc = "Contains a herbicide." - list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 30) - -/obj/item/reagent_containers/glass/bottle/killer/pestkiller - name = "bottle of pest spray" - desc = "Contains a pesticide." - list_reagents = list(/datum/reagent/toxin/pestkiller = 30) + +// Plant analyzer +/obj/item/plant_analyzer + name = "plant analyzer" + desc = "A scanner used to evaluate a plant's various areas of growth, and genetic traits." + icon = 'icons/obj/device.dmi' + icon_state = "hydro" + inhand_icon_state = "analyzer" + worn_icon_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + var/scan_mode = PLANT_SCANMODE_STATS + +/obj/item/plant_analyzer/attack_self(mob/user) + . = ..() + scan_mode = !scan_mode + to_chat(user, "You switch [src] to [scan_mode == PLANT_SCANMODE_CHEMICALS ? "scan for chemical reagents and traits" : "scan for plant growth statistics"].") + +/obj/item/plant_analyzer/attack(mob/living/M, mob/living/carbon/human/user) + //Checks if target is a podman + if(ispodperson(M)) + user.visible_message("[user] analyzes [M]'s vitals.", \ + "You analyze [M]'s vitals.") + if(scan_mode== PLANT_SCANMODE_STATS) + healthscan(user, M, advanced = TRUE) + else + chemscan(user, M) + add_fingerprint(user) + return + return ..() + +// ************************************* +// Hydroponics Tools +// ************************************* + +/obj/item/reagent_containers/spray/weedspray // -- Skie + desc = "It's a toxic mixture, in spray form, to kill small weeds." + icon = 'icons/obj/hydroponics/equipment.dmi' + name = "weed spray" + icon_state = "weedspray" + inhand_icon_state = "spraycan" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + volume = 100 + list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 100) + +/obj/item/reagent_containers/spray/weedspray/suicide_act(mob/user) + user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (TOXLOSS) + +/obj/item/reagent_containers/spray/pestspray // -- Skie + desc = "It's some pest eliminator spray! Do not inhale!" + icon = 'icons/obj/hydroponics/equipment.dmi' + name = "pest spray" + icon_state = "pestspray" + inhand_icon_state = "plantbgone" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + volume = 100 + list_reagents = list(/datum/reagent/toxin/pestkiller = 100) + +/obj/item/reagent_containers/spray/pestspray/suicide_act(mob/user) + user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (TOXLOSS) + +/obj/item/cultivator + name = "cultivator" + desc = "It's used for removing weeds or scratching your back." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "cultivator" + inhand_icon_state = "cultivator" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + flags_1 = CONDUCT_1 + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=50) + attack_verb = list("slashed", "sliced", "cut", "clawed") + hitsound = 'sound/weapons/bladeslice.ogg' + +/obj/item/cultivator/suicide_act(mob/user) + user.visible_message("[user] is scratching [user.p_their()] back as hard as [user.p_they()] can with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/cultivator/rake + name = "rake" + icon_state = "rake" + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("slashed", "sliced", "bashed", "clawed") + hitsound = null + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) + flags_1 = NONE + resistance_flags = FLAMMABLE + +/obj/item/cultivator/rake/Crossed(atom/movable/AM) + . = ..() + if(!ishuman(AM)) + return + var/mob/living/carbon/human/H = AM + if(has_gravity(loc) && HAS_TRAIT(H, TRAIT_CLUMSY) && !H.resting) + H.confused = max(H.confused, 10) + H.Stun(20) + playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) + H.visible_message("[H] steps on [src] causing the handle to hit [H.p_them()] right in the face!", \ + "You step on [src] causing the handle to hit you right in the face!") + +/obj/item/hatchet + name = "hatchet" + desc = "A very sharp axe blade upon a short fibremetal handle. It has a long history of chopping things, but now it is used for chopping wood." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "hatchet" + inhand_icon_state = "hatchet" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + flags_1 = CONDUCT_1 + force = 12 + w_class = WEIGHT_CLASS_SMALL + throwforce = 15 + throw_speed = 3 + throw_range = 4 + custom_materials = list(/datum/material/iron = 15000) + attack_verb = list("chopped", "tore", "lacerated", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/hatchet/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 70, 100) + +/obj/item/hatchet/suicide_act(mob/user) + user.visible_message("[user] is chopping at [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/hatchet/wooden + desc = "A crude axe blade upon a short wooden handle." + icon_state = "woodhatchet" + custom_materials = null + flags_1 = NONE + +/obj/item/scythe + icon_state = "scythe0" + lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' + name = "scythe" + desc = "A sharp and curved blade on a long fibremetal handle, this tool makes it easy to reap what you sow." + force = 13 + throwforce = 5 + throw_speed = 2 + throw_range = 3 + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + armour_penetration = 20 + slot_flags = ITEM_SLOT_BACK + attack_verb = list("chopped", "sliced", "cut", "reaped") + hitsound = 'sound/weapons/bladeslice.ogg' + var/swiping = FALSE + +/obj/item/scythe/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 90, 105) + +/obj/item/scythe/suicide_act(mob/user) + user.visible_message("[user] is beheading [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) + if(BP) + BP.drop_limb() + playsound(src, "desecration" ,50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/scythe/pre_attack(atom/A, mob/living/user, params) + if(swiping || !istype(A, /obj/structure/spacevine) || get_turf(A) == get_turf(user)) + return ..() + var/turf/user_turf = get_turf(user) + var/dir_to_target = get_dir(user_turf, get_turf(A)) + swiping = TRUE + var/static/list/scythe_slash_angles = list(0, 45, 90, -45, -90) + for(var/i in scythe_slash_angles) + var/turf/T = get_step(user_turf, turn(dir_to_target, i)) + for(var/obj/structure/spacevine/V in T) + if(user.Adjacent(V)) + melee_attack_chain(user, V) + swiping = FALSE + return TRUE + +/obj/item/secateurs + name = "secateurs" + desc = "It's a tool for cutting grafts off plants." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "secateurs" + inhand_icon_state = "secateurs" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + flags_1 = CONDUCT_1 + force = 5 + throwforce = 6 + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=4000) + attack_verb = list("slashed", "sliced", "cut", "clawed") + hitsound = 'sound/weapons/bladeslice.ogg' + +/obj/item/geneshears + name = "Botanogenetic Plant Shears" + desc = "A high tech, high fidelity pair of plant shears, capable of cutting genetic traits out of a plant." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "genesheers" + inhand_icon_state = "secateurs" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + flags_1 = CONDUCT_1 + force = 10 + throwforce = 8 + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + material_flags = MATERIAL_NO_EFFECTS + custom_materials = list(/datum/material/iron=4000, /datum/material/uranium=1500, /datum/material/gold=500) + attack_verb = list("slashed", "sliced", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + + +// ************************************* +// Nutrient defines for hydroponics +// ************************************* + + +/obj/item/reagent_containers/glass/bottle/nutrient + name = "bottle of nutrient" + volume = 50 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(1,2,5,10,15,25,50) + +/obj/item/reagent_containers/glass/bottle/nutrient/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + +/obj/item/reagent_containers/glass/bottle/nutrient/ez + name = "bottle of E-Z-Nutrient" + desc = "Contains a fertilizer that causes mild mutations and gradual plant growth with each harvest." + list_reagents = list(/datum/reagent/plantnutriment/eznutriment = 50) + +/obj/item/reagent_containers/glass/bottle/nutrient/l4z + name = "bottle of Left 4 Zed" + desc = "Contains a fertilizer that lightly heals the plant but causes significant mutations in plants over generations." + list_reagents = list(/datum/reagent/plantnutriment/left4zednutriment = 50) + +/obj/item/reagent_containers/glass/bottle/nutrient/rh + name = "bottle of Robust Harvest" + desc = "Contains a fertilizer that increases the yield of a plant while gradually preventing mutations." + list_reagents = list(/datum/reagent/plantnutriment/robustharvestnutriment = 50) + +/obj/item/reagent_containers/glass/bottle/nutrient/empty + name = "bottle" + +/obj/item/reagent_containers/glass/bottle/killer + volume = 30 + amount_per_transfer_from_this = 1 + possible_transfer_amounts = list(1,2,5) + +/obj/item/reagent_containers/glass/bottle/killer/weedkiller + name = "bottle of weed killer" + desc = "Contains a herbicide." + list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 30) + +/obj/item/reagent_containers/glass/bottle/killer/pestkiller + name = "bottle of pest spray" + desc = "Contains a pesticide." + list_reagents = list(/datum/reagent/toxin/pestkiller = 30) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 5beea890eb0..5cbe88e8915 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -1,990 +1,990 @@ - -/obj/machinery/hydroponics - name = "hydroponics tray" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "hydrotray" - density = TRUE - pixel_z = 8 - obj_flags = CAN_BE_HIT | UNIQUE_RENAME - circuit = /obj/item/circuitboard/machine/hydroponics - idle_power_usage = 0 - ///The amount of water in the tray (max 100) - var/waterlevel = 100 - ///The maximum amount of water in the tray - var/maxwater = 100 - ///How many units of nutrients will be drained in the tray. - var/nutridrain = 1 - ///The maximum nutrient of water in the tray - var/maxnutri = 10 - ///The amount of pests in the tray (max 10) - var/pestlevel = 0 - ///The amount of weeds in the tray (max 10) - var/weedlevel = 0 - ///Nutriment's effect on yield - var/yieldmod = 1 - ///Nutriment's effect on mutations - var/mutmod = 1 - ///Toxicity in the tray? - var/toxic = 0 - ///Current age - var/age = 0 - ///Is it dead? - var/dead = FALSE - ///Its health - var/plant_health - ///Last time it was harvested - var/lastproduce = 0 - ///Used for timing of cycles. - var/lastcycle = 0 - ///About 10 seconds / cycle - var/cycledelay = 200 - ///Ready to harvest? - var/harvest = FALSE - ///The currently planted seed - var/obj/item/seeds/myseed = null - ///Obtained from the quality of the parts used in the tray, determines nutrient drain rate. - var/rating = 1 - ///Can it be unwrenched to move? - var/unwrenchable = TRUE - ///Have we been visited by a bee recently, so bees dont overpollinate one plant - var/recent_bee_visit = FALSE - ///If the tray is connected to other trays via irrigation hoses - var/using_irrigation = FALSE - ///The last user to add a reagent to the tray, mostly for logging purposes. - var/mob/lastuser - ///If the tray generates nutrients and water on its own - var/self_sustaining = FALSE - -/obj/machinery/hydroponics/Initialize() - //ALRIGHT YOU DEGENERATES. YOU HAD REAGENT HOLDERS FOR AT LEAST 4 YEARS AND NONE OF YOU MADE HYDROPONICS TRAYS HOLD NUTRIENT CHEMS INSTEAD OF USING "Points". - //SO HERE LIES THE "nutrilevel" VAR. IT'S DEAD AND I PUT IT OUT OF IT'S MISERY. USE "reagents" INSTEAD. ~ArcaneMusic, accept no substitutes. - create_reagents(20) - reagents.add_reagent(/datum/reagent/plantnutriment/eznutriment, 10) //Half filled nutrient trays for dirt trays to have more to grow with in prison/lavaland. - . = ..() - - -/obj/machinery/hydroponics/constructable - name = "hydroponics tray" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "hydrotray3" - -/obj/machinery/hydroponics/constructable/RefreshParts() - var/tmp_capacity = 0 - for (var/obj/item/stock_parts/matter_bin/M in component_parts) - tmp_capacity += M.rating - for (var/obj/item/stock_parts/manipulator/M in component_parts) - rating = M.rating - maxwater = tmp_capacity * 50 // Up to 300 - maxnutri = (tmp_capacity * 5) + STATIC_NUTRIENT_CAPACITY // Up to 50 Maximum - reagents.maximum_volume = maxnutri - nutridrain = 1/rating - -/obj/machinery/hydroponics/constructable/examine(mob/user) - . = ..() - . += "Use Ctrl-Click to activate autogrow. Alt-Click to empty the tray's nutrients." - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Tray efficiency at [rating*100]%." - - -/obj/machinery/hydroponics/Destroy() - if(myseed) - qdel(myseed) - myseed = null - return ..() - -/obj/machinery/hydroponics/constructable/attackby(obj/item/I, mob/user, params) - if (user.a_intent != INTENT_HARM) - // handle opening the panel - if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) - return - - // handle deconstructing the machine, if permissible - if(I.tool_behaviour == TOOL_CROWBAR && using_irrigation) - to_chat(user, "Disconnect the hoses first!") - return - else if(default_deconstruction_crowbar(I)) - return - - return ..() - -/obj/machinery/hydroponics/proc/FindConnected() - var/list/connected = list() - var/list/processing_atoms = list(src) - - while(processing_atoms.len) - var/atom/a = processing_atoms[1] - for(var/step_dir in GLOB.cardinals) - var/obj/machinery/hydroponics/h = locate() in get_step(a, step_dir) - // Soil plots aren't dense - if(h && h.using_irrigation && h.density && !(h in connected) && !(h in processing_atoms)) - processing_atoms += h - - processing_atoms -= a - connected += a - - return connected - -/obj/machinery/hydroponics/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. - if(!myseed) - return ..() - if(istype(Proj , /obj/projectile/energy/floramut)) - mutate() - else if(istype(Proj , /obj/projectile/energy/florayield)) - return myseed.bullet_act(Proj) - else if(istype(Proj , /obj/projectile/energy/florarevolution)) - if(myseed) - if(myseed.mutatelist.len > 0) - myseed.instability = (myseed.instability/2) - mutatespecie() - else - return ..() - -/obj/machinery/hydroponics/process() - var/needs_update = 0 // Checks if the icon needs updating so we don't redraw empty trays every time - - if(myseed && (myseed.loc != src)) - myseed.forceMove(src) - - if(!powered() && self_sustaining) - visible_message("[name]'s auto-grow functionality shuts off!") - idle_power_usage = 0 - self_sustaining = FALSE - update_icon() - - else if(self_sustaining) - adjustWater(rand(1,2)) - adjustWeeds(-1) - adjustPests(-1) - - if(world.time > (lastcycle + cycledelay)) - lastcycle = world.time - if(myseed && !dead) - // Advance age - age++ - if(age < myseed.maturation) - lastproduce = age - - needs_update = 1 - - -//Nutrients////////////////////////////////////////////////////////////// - // Nutrients deplete at a constant rate, since new nutrients can boost stats far easier. - apply_chemicals(lastuser) - if(self_sustaining) - reagents.remove_any(min(0.5, nutridrain)) - else - reagents.remove_any(nutridrain) - - // Lack of nutrients hurts non-weeds - if(reagents.total_volume <= 0 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) - adjustHealth(-rand(1,3)) - -//Photosynthesis///////////////////////////////////////////////////////// - // Lack of light hurts non-mushrooms - if(isturf(loc)) - var/turf/currentTurf = loc - var/lightAmt = currentTurf.get_lumcount() - if(myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - if(lightAmt < 0.2) - adjustHealth(-1 / rating) - else // Non-mushroom - if(lightAmt < 0.4) - adjustHealth(-2 / rating) - -//Water////////////////////////////////////////////////////////////////// - // Drink random amount of water - adjustWater(-rand(1,6) / rating) - - // If the plant is dry, it loses health pretty fast, unless mushroom - if(waterlevel <= 10 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - adjustHealth(-rand(0,1) / rating) - if(waterlevel <= 0) - adjustHealth(-rand(0,2) / rating) - - // Sufficient water level and nutrient level = plant healthy but also spawns weeds - else if(waterlevel > 10 && reagents.total_volume > 0) - adjustHealth(rand(1,2) / rating) - if(myseed && prob(myseed.weed_chance)) - adjustWeeds(myseed.weed_rate) - else if(prob(5)) //5 percent chance the weed population will increase - adjustWeeds(1 / rating) - -//Toxins///////////////////////////////////////////////////////////////// - - // Too much toxins cause harm, but when the plant drinks the contaiminated water, the toxins disappear slowly - if(toxic >= 40 && toxic < 80) - adjustHealth(-1 / rating) - adjustToxic(-rating * 2) - else if(toxic >= 80) // I don't think it ever gets here tbh unless above is commented out - adjustHealth(-3) - adjustToxic(-rating *3) - -//Pests & Weeds////////////////////////////////////////////////////////// - - if(pestlevel >= 8) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - if(myseed.potency >=30) - myseed.adjust_potency(-rand(2,6)) //Pests eat leaves and nibble on fruit, lowering potency. - myseed.potency = min((myseed.potency),30,100) - else - adjustHealth(2 / rating) - adjustPests(-1 / rating) - - else if(pestlevel >= 4) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - if(myseed.potency >=30) - myseed.adjust_potency(-rand(1,4)) - myseed.potency = min((myseed.potency),30,100) - - else - adjustHealth(1 / rating) - if(prob(50)) - adjustPests(-1 / rating) - - else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - if(prob(5)) - adjustPests(-1 / rating) - - // If it's a weed, it doesn't stunt the growth - if(weedlevel >= 5 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) - if(myseed.yield >=3) - myseed.adjust_yield(-rand(1,2)) //Weeds choke out the plant's ability to bear more fruit. - myseed.yield = min((myseed.yield),3,10) - -//This is the part with pollination - pollinate() - -//This is where stability mutations exist now. - if(myseed.instability >= 80) - var/mutation_chance = myseed.instability - 75 - mutate(0, 0, 0, 0, 0, 0, 0, mutation_chance, 0) //Scaling odds of a random trait or chemical - if(myseed.instability >= 60) - if(prob((myseed.instability)/2) && !self_sustaining && length(myseed.mutatelist)) //Minimum 30%, Maximum 50% chance of mutating every age tick when not on autogrow. - mutatespecie() - myseed.instability = myseed.instability/2 - if(myseed.instability >= 40) - if(prob(myseed.instability)) - hardmutate() - if(myseed.instability >= 20 ) - if(prob(myseed.instability)) - mutate() - -//Health & Age/////////////////////////////////////////////////////////// - - // Plant dies if plant_health <= 0 - if(plant_health <= 0) - plantdies() - adjustWeeds(1 / rating) // Weeds flourish - - // If the plant is too old, lose health fast - if(age > myseed.lifespan) - adjustHealth(-rand(1,5) / rating) - - // Harvest code - if(age > myseed.production && (age - lastproduce) > myseed.production && (!harvest && !dead)) - if(myseed && myseed.yield != -1) // Unharvestable shouldn't be harvested - harvest = TRUE - else - lastproduce = age - if(prob(5)) // On each tick, there's a 5 percent chance the pest population will increase - adjustPests(1 / rating) - else - if(waterlevel > 10 && reagents.total_volume > 0 && prob(10)) // If there's no plant, the percentage chance is 10% - adjustWeeds(1 / rating) - - // Weeeeeeeeeeeeeeedddssss - if(weedlevel >= 10 && prob(50) && !self_sustaining) // At this point the plant is kind of fucked. Weeds can overtake the plant spot. - if(myseed && myseed.yield >= 3) - myseed.adjust_yield(-rand(1,2)) //Loses even more yield per tick, quickly dropping to 3 minimum. - myseed.yield = min((myseed.yield),YIELD_WEED_MINIMUM,YIELD_WEED_MAXIMUM) - if(!myseed) - weedinvasion() - needs_update = 1 - if (needs_update) - update_icon() - - if(myseed && prob(5 * (11-myseed.production))) - for(var/g in myseed.genes) - if(istype(g, /datum/plant_gene/trait)) - var/datum/plant_gene/trait/selectedtrait = g - selectedtrait.on_grow(src) - return - -/obj/machinery/hydroponics/update_icon() - //Refreshes the icon and sets the luminosity - cut_overlays() - - if(self_sustaining) - if(istype(src, /obj/machinery/hydroponics/soil)) - add_atom_colour(rgb(255, 175, 0), FIXED_COLOUR_PRIORITY) - else - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "gaia_blessing")) - set_light(3) - - update_icon_hoses() - - if(myseed) - update_icon_plant() - update_icon_lights() - - if(!self_sustaining) - if(myseed && myseed.get_gene(/datum/plant_gene/trait/glow)) - var/datum/plant_gene/trait/glow/G = myseed.get_gene(/datum/plant_gene/trait/glow) - set_light(G.glow_range(myseed), G.glow_power(myseed), G.glow_color) - else - set_light(0) - - return - -/obj/machinery/hydroponics/proc/update_icon_hoses() - var/n = 0 - for(var/Dir in GLOB.cardinals) - var/obj/machinery/hydroponics/t = locate() in get_step(src,Dir) - if(t && t.using_irrigation && using_irrigation) - n += Dir - - icon_state = "hoses-[n]" - -/obj/machinery/hydroponics/proc/update_icon_plant() - var/mutable_appearance/plant_overlay = mutable_appearance(myseed.growing_icon, layer = OBJ_LAYER + 0.01) - if(dead) - plant_overlay.icon_state = myseed.icon_dead - else if(harvest) - if(!myseed.icon_harvest) - plant_overlay.icon_state = "[myseed.icon_grow][myseed.growthstages]" - else - plant_overlay.icon_state = myseed.icon_harvest - else - var/t_growthstate = clamp(round((age / myseed.maturation) * myseed.growthstages), 1, myseed.growthstages) - plant_overlay.icon_state = "[myseed.icon_grow][t_growthstate]" - add_overlay(plant_overlay) - -/obj/machinery/hydroponics/proc/update_icon_lights() - if(waterlevel <= 10) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowwater3")) - if(reagents.total_volume <= 2) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lownutri3")) - if(plant_health <= (myseed.endurance / 2)) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowhealth3")) - if(weedlevel >= 5 || pestlevel >= 5 || toxic >= 40) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_alert3")) - if(harvest) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_harvest3")) - - -/obj/machinery/hydroponics/examine(user) - . = ..() - if(myseed) - . += "It has [myseed.plantname] planted." - if (dead) - . += "It's dead!" - else if (harvest) - . += "It's ready to harvest." - else if (plant_health <= (myseed.endurance / 2)) - . += "It looks unhealthy." - else - . += "It's empty." - - . += "Water: [waterlevel]/[maxwater].\n"+\ - "Nutrient: [reagents.total_volume]/[maxnutri]." - if(self_sustaining) - . += "The tray's autogrow is active, protecting it from species mutations, weeds, and pests." - - if(weedlevel >= 5) - to_chat(user, "It's filled with weeds!") - if(pestlevel >= 5) - to_chat(user, "It's filled with tiny worms!") - to_chat(user, "" ) - -/** - * What happens when a tray's weeds grow too large. - * Plants a new weed in an empty tray, then resets the tray. - */ -/obj/machinery/hydroponics/proc/weedinvasion() - dead = FALSE - var/oldPlantName - if(myseed) // In case there's nothing in the tray beforehand - oldPlantName = myseed.plantname - qdel(myseed) - myseed = null - else - oldPlantName = "empty tray" - switch(rand(1,18)) // randomly pick predominative weed - if(16 to 18) - myseed = new /obj/item/seeds/reishi(src) - if(14 to 15) - myseed = new /obj/item/seeds/nettle(src) - if(12 to 13) - myseed = new /obj/item/seeds/harebell(src) - if(10 to 11) - myseed = new /obj/item/seeds/amanita(src) - if(8 to 9) - myseed = new /obj/item/seeds/chanter(src) - if(6 to 7) - myseed = new /obj/item/seeds/tower(src) - if(4 to 5) - myseed = new /obj/item/seeds/plump(src) - else - myseed = new /obj/item/seeds/starthistle(src) - age = 0 - plant_health = myseed.endurance - lastcycle = world.time - harvest = FALSE - weedlevel = 0 // Reset - pestlevel = 0 // Reset - update_icon() - visible_message("The [oldPlantName] is overtaken by some [myseed.plantname]!") - TRAY_NAME_UPDATE - -/obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0, stabmut = 3) // Mutates the current seed - if(!myseed) - return - myseed.mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut, stabmut) - -/obj/machinery/hydroponics/proc/hardmutate() - mutate(4, 10, 2, 4, 50, 4, 10, 0, 4) - - -/obj/machinery/hydroponics/proc/mutatespecie() // Mutagent produced a new plant! - if(!myseed || dead) - return - - var/oldPlantName = myseed.plantname - if(myseed.mutatelist.len > 0) - var/mutantseed = pick(myseed.mutatelist) - qdel(myseed) - myseed = null - myseed = new mutantseed - else - return - - hardmutate() - age = 0 - plant_health = myseed.endurance - lastcycle = world.time - harvest = FALSE - weedlevel = 0 // Reset - - sleep(5) // Wait a while - update_icon() - visible_message("[oldPlantName] suddenly mutates into [myseed.plantname]!") - TRAY_NAME_UPDATE - -/obj/machinery/hydroponics/proc/mutateweed() // If the weeds gets the mutagent instead. Mind you, this pretty much destroys the old plant - if( weedlevel > 5 ) - if(myseed) - qdel(myseed) - myseed = null - var/newWeed = pick(/obj/item/seeds/liberty, /obj/item/seeds/angel, /obj/item/seeds/nettle/death, /obj/item/seeds/kudzu) - myseed = new newWeed - dead = FALSE - hardmutate() - age = 0 - plant_health = myseed.endurance - lastcycle = world.time - harvest = FALSE - weedlevel = 0 // Reset - - sleep(5) // Wait a while - update_icon() - visible_message("The mutated weeds in [src] spawn some [myseed.plantname]!") - TRAY_NAME_UPDATE - else - to_chat(usr, "The few weeds in [src] seem to react, but only for a moment...") - -/** - * Plant Death Proc. - * Cleans up various stats for the plant upon death, including pests, harvestability, and plant health. - */ -/obj/machinery/hydroponics/proc/plantdies() - plant_health = 0 - harvest = FALSE - pestlevel = 0 // Pests die - lastproduce = 0 - if(!dead) - update_icon() - dead = TRUE - -/** - * Plant Cross-Pollination. - * Checks all plants in the tray's oview range, then averages out the seed's potency, instability, and yield values. - * If the seed's instability is >= 20, the seed donates one of it's reagents to that nearby plant. - * * Range - The Oview range of trays to which to look for plants to donate reagents. - */ -/obj/machinery/hydroponics/proc/pollinate(var/range = 1) - for(var/obj/machinery/hydroponics/T in oview(src, range)) - //Here is where we check for window blocking. - if(!Adjacent(T) && range <= 1) - continue - if(T.myseed && !T.dead) - T.myseed.potency = round(clamp((T.myseed.potency+(1/10)*(myseed.potency-T.myseed.potency)),0,100)) - T.myseed.instability = round(clamp((T.myseed.instability+(1/10)*(myseed.instability-T.myseed.instability)),0,100)) - T.myseed.yield = round(clamp((T.myseed.yield+(1/2)*(myseed.yield-T.myseed.yield)),0,10)) - if(myseed.instability >= 20 && prob(70) && length(T.myseed.reagents_add)) - var/list/datum/plant_gene/reagent/possible_reagents = list() - for(var/datum/plant_gene/reagent/reag in T.myseed.genes) - possible_reagents += reag - var/datum/plant_gene/reagent/reagent_gene = pick(possible_reagents) //Let this serve as a lession to delete your WIP comments before merge. - if(reagent_gene.can_add(myseed)) - myseed.genes += reagent_gene.Copy() - myseed.reagents_from_genes() - continue - -/** - * Pest Mutation Proc. - * When a tray is mutated with high pest values, it will spawn spiders. - * * User - Person who last added chemicals to the tray for logging purposes. - */ -/obj/machinery/hydroponics/proc/mutatepest(mob/user) - if(pestlevel > 5) - message_admins("[ADMIN_LOOKUPFLW(user)] last altered a hydro tray's contents which spawned spiderlings") - log_game("[key_name(user)] last altered a hydro tray, which spiderlings spawned from.") - visible_message("The pests seem to behave oddly...") - spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE) - else if(myseed) - visible_message("The pests seem to behave oddly in [myseed.name] tray, but quickly settle down...") - -/obj/machinery/hydroponics/attackby(obj/item/O, mob/user, params) - //Called when mob user "attacks" it with object O - if(istype(O, /obj/item/reagent_containers) ) // Syringe stuff (and other reagent containers now too) - var/obj/item/reagent_containers/reagent_source = O - - if(istype(reagent_source, /obj/item/reagent_containers/syringe)) - var/obj/item/reagent_containers/syringe/syr = reagent_source - if(syr.mode != 1) - to_chat(user, "You can't get any extract out of this plant." ) - return - - if(!reagent_source.reagents.total_volume) - to_chat(user, "[reagent_source] is empty!") - return 1 - - if(reagents.total_volume >= reagents.maximum_volume && !reagent_source.reagents.has_reagent(/datum/reagent/water, 1)) - to_chat(user, "[src] is full.") - return - - var/list/trays = list(src)//makes the list just this in cases of syringes and compost etc - var/target = myseed ? myseed.plantname : src - var/visi_msg = "" - var/irrigate = 0 //How am I supposed to irrigate pill contents? - var/transfer_amount - - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks)) - var/obj/item/reagent_containers/food/snacks/R = reagent_source - if (R.trash) - R.generate_trash(get_turf(user)) - visi_msg="[user] composts [reagent_source], spreading it through [target]" - transfer_amount = reagent_source.reagents.total_volume - else - transfer_amount = reagent_source.amount_per_transfer_from_this - if(istype(reagent_source, /obj/item/reagent_containers/syringe/)) - var/obj/item/reagent_containers/syringe/syr = reagent_source - visi_msg="[user] injects [target] with [syr]" - if(syr.reagents.total_volume <= syr.amount_per_transfer_from_this) - syr.mode = 0 - else if(istype(reagent_source, /obj/item/reagent_containers/spray/)) - visi_msg="[user] sprays [target] with [reagent_source]" - playsound(loc, 'sound/effects/spray3.ogg', 50, TRUE, -6) - irrigate = 1 - else if(transfer_amount) // Droppers, cans, beakers, what have you. - visi_msg="[user] uses [reagent_source] on [target]" - irrigate = 1 - // Beakers, bottles, buckets, etc. - if(reagent_source.is_drainable()) - playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) - - if(irrigate && transfer_amount > 30 && reagent_source.reagents.total_volume >= 30 && using_irrigation) - trays = FindConnected() - if (trays.len > 1) - visi_msg += ", setting off the irrigation system" - - if(visi_msg) - visible_message("[visi_msg].") - - var/split = round(transfer_amount/trays.len) - - for(var/obj/machinery/hydroponics/H in trays) - //cause I don't want to feel like im juggling 15 tamagotchis and I can get to my real work of ripping flooring apart in hopes of validating my life choices of becoming a space-gardener - //This was originally in apply_chemicals, but due to apply_chemicals only holding nutrients, we handle it here now. - if(reagent_source.reagents.has_reagent(/datum/reagent/water, 1)) - var/water_amt = reagent_source.reagents.get_reagent_amount(/datum/reagent/water) * split / reagent_source.reagents.total_volume - H.adjustWater(round(water_amt)) - reagent_source.reagents.remove_reagent(/datum/reagent/water, water_amt) - reagent_source.reagents.trans_to(H.reagents, split, transfered_by = user) - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) - qdel(reagent_source) - lastuser = user - H.update_icon() - return 1 - H.update_icon() - if(reagent_source) // If the source wasn't composted and destroyed - reagent_source.update_icon() - return 1 - - else if(istype(O, /obj/item/seeds) && !istype(O, /obj/item/seeds/sample)) - if(!myseed) - if(istype(O, /obj/item/seeds/kudzu)) - investigate_log("had Kudzu planted in it by [key_name(user)] at [AREACOORD(src)]","kudzu") - if(!user.transferItemToLoc(O, src)) - return - to_chat(user, "You plant [O].") - dead = FALSE - myseed = O - TRAY_NAME_UPDATE - age = 1 - plant_health = myseed.endurance - lastcycle = world.time - update_icon() - return - else - to_chat(user, "[src] already has seeds in it!") - return - - else if(istype(O, /obj/item/plant_analyzer)) - var/obj/item/plant_analyzer/P_analyzer = O - if(myseed) - if(P_analyzer.scan_mode == PLANT_SCANMODE_STATS) - to_chat(user, "*** [myseed.plantname] ***" ) - to_chat(user, "- Plant Age: [age]") - var/list/text_string = myseed.get_analyzer_text() - if(text_string) - to_chat(user, text_string) - to_chat(user, "*---------*") - if(myseed.reagents_add && P_analyzer.scan_mode == PLANT_SCANMODE_CHEMICALS) - to_chat(user, "- Plant Reagents -") - to_chat(user, "*---------*") - for(var/datum/plant_gene/reagent/G in myseed.genes) - to_chat(user, "- [G.get_name()] -") - to_chat(user, "*---------*") - else - to_chat(user, "No plant found.") - to_chat(user, "- Weed level: [weedlevel] / 10") - to_chat(user, "- Pest level: [pestlevel] / 10") - to_chat(user, "- Toxicity level: [toxic] / 100") - to_chat(user, "- Water level: [waterlevel] / [maxwater]") - to_chat(user, "- Nutrition level: [reagents.total_volume] / [maxnutri]") - to_chat(user, "") - return - - else if(istype(O, /obj/item/cultivator)) - if(weedlevel > 0) - user.visible_message("[user] uproots the weeds.", "You remove the weeds from [src].") - weedlevel = 0 - update_icon() - return - else - to_chat(user, "This plot is completely devoid of weeds! It doesn't need uprooting.") - return - - else if(istype(O, /obj/item/secateurs)) - if(!myseed) - to_chat(user, "This plot is empty.") - return - else if(!harvest) - to_chat(user, "This plant must be harvestable in order to be grafted.") - return - else if(myseed.grafted) - to_chat(user, "This plant has already been grafted.") - return - else - user.visible_message("[user] grafts off a limb from [src].", "You carefully graft off a portion of [src].") - var/obj/item/graft/snip = myseed.create_graft() - if(!snip) - return // The plant did not return a graft. - - snip.forceMove(drop_location()) - myseed.grafted = TRUE - adjustHealth(-5) - return - - else if(istype(O, /obj/item/geneshears)) - if(!myseed) - to_chat(user, "The tray is empty.") - return - if(plant_health <= 15) - to_chat(user, "This plant looks too unhealty to be sheared right now.") - return - - var/list/current_traits = list() - for(var/datum/plant_gene/gene in myseed.genes) - if(istype(gene, /datum/plant_gene/core) || (istype(gene,/datum/plant_gene/trait/plant_type)) || islist(gene)) - continue - if(!(gene.mutability_flags & PLANT_GENE_REMOVABLE) || !(gene.mutability_flags & PLANT_GENE_EXTRACTABLE)) - continue //No bypassing unextractable or essential genes. - current_traits[gene.name] = gene - var/removed_trait = (input(user, "Select a trait to remove from the [myseed.plantname].", "Plant Trait Removal") as null|anything in sortList(current_traits)) - if(removed_trait == null) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - for(var/datum/plant_gene/gene in myseed.genes) - if(gene.name == removed_trait) - if(myseed.genes.Remove(gene)) - qdel(gene) - break - myseed.reagents_from_genes() - adjustHealth(-15) - to_chat(user, "You carefully shear the genes off of the [myseed.plantname], leaving the plant looking weaker.") - update_icon() - return - - else if(istype(O, /obj/item/graft)) - var/obj/item/graft/snip = O - if(!myseed) - to_chat(user, "The tray is empty.") - return - if(!myseed.apply_graft(snip)) - to_chat(user, "The [myseed.plantname] rejects the [snip]!") - return - qdel(snip) - to_chat(user, "You carefully integrate the grafted plant limb onto [myseed.plantname].") - return - - else if(istype(O, /obj/item/storage/bag/plants)) - attack_hand(user) - for(var/obj/item/reagent_containers/food/snacks/grown/G in locate(user.x,user.y,user.z)) - SEND_SIGNAL(O, COMSIG_TRY_STORAGE_INSERT, G, user, TRUE) - return - - else if(default_unfasten_wrench(user, O)) - return - - else if((O.tool_behaviour == TOOL_WIRECUTTER) && unwrenchable) - if (!anchored) - to_chat(user, "Anchor the tray first!") - return - using_irrigation = !using_irrigation - O.play_tool_sound(src) - user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \ - "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.") - for(var/obj/machinery/hydroponics/h in range(1,src)) - h.update_icon() - return - - else if(istype(O, /obj/item/shovel/spade)) - if(!myseed && !weedlevel) - to_chat(user, "[src] doesn't have any plants or weeds!") - return - user.visible_message("[user] starts digging out [src]'s plants...", - "You start digging out [src]'s plants...") - if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel)) - user.visible_message("[user] digs out the plants in [src]!", "You dig out all of [src]'s plants!") - if(myseed) //Could be that they're just using it as a de-weeder - age = 0 - plant_health = 0 - lastproduce = 0 - if(harvest) - harvest = FALSE //To make sure they can't just put in another seed and insta-harvest it - qdel(myseed) - myseed = null - name = initial(name) - desc = initial(desc) - weedlevel = 0 //Has a side effect of cleaning up those nasty weeds - update_icon() - return - else if(istype(O, /obj/item/storage/part_replacer)) - RefreshParts() - return - else if(istype(O, /obj/item/gun/energy/floragun)) - var/obj/item/gun/energy/floragun/flowergun = O - if(flowergun.cell.charge < flowergun.cell.maxcharge) - to_chat(user, "[flowergun] must be fully charged to lock in a mutation!") - return - if(!myseed) - to_chat(user, "[src] is empty!") - return - if(myseed.endurance <= 20) - to_chat(user, "[myseed.plantname] isn't hardy enough to sequence it's mutation!") - return - if(!myseed.mutatelist) - to_chat(user, "[myseed.plantname] has nothing else to mutate into!") - return - else - var/list/fresh_mut_list = list() - for(var/muties in myseed.mutatelist) - var/obj/item/seeds/another_mut = new muties - fresh_mut_list[another_mut.plantname] = muties - var/locked_mutation = (input(user, "Select a mutation to lock.", "Plant Mutation Locks") as null|anything in sortList(fresh_mut_list)) - if(!user.canUseTopic(src, BE_CLOSE) || !locked_mutation) - return - myseed.mutatelist = list(fresh_mut_list[locked_mutation]) - myseed.endurance = (myseed.endurance/2) - flowergun.cell.use(flowergun.cell.charge) - flowergun.update_overlays() - to_chat(user, "[myseed.plantname]'s mutation was set to [locked_mutation], depleting [flowergun]'s cell!") - return - else - return ..() - -/obj/machinery/hydroponics/can_be_unfasten_wrench(mob/user, silent) - if (!unwrenchable) // case also covered by NODECONSTRUCT checks in default_unfasten_wrench - return CANT_UNFASTEN - - if (using_irrigation) - if (!silent) - to_chat(user, "Disconnect the hoses first!") - return FAILED_UNFASTEN - - return ..() - -/obj/machinery/hydroponics/attack_hand(mob/user) - . = ..() - if(.) - return - if(issilicon(user)) //How does AI know what plant is? - return - if(harvest) - return myseed.harvest(user) - - else if(dead) - dead = FALSE - to_chat(user, "You remove the dead plant from [src].") - qdel(myseed) - myseed = null - update_icon() - TRAY_NAME_UPDATE - else - if(user) - examine(user) - -/obj/machinery/hydroponics/CtrlClick(mob/user) - . = ..() - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(!powered()) - to_chat(user, "[name] has no power.") - return - if(!anchored) - return - self_sustaining = !self_sustaining - idle_power_usage = self_sustaining ? 5000 : 0 - to_chat(user, "You [self_sustaining ? "activate" : "deactivated"] [src]'s autogrow function[self_sustaining ? ", maintaining the tray's health while using high amounts of power" : ""].") - update_icon() - -/obj/machinery/hydroponics/AltClick(mob/user) - . = ..() - var/warning = alert(user, "Are you sure you wish to empty the tray's nutrient beaker?","Empty Tray Nutrients?", "Yes", "No") - if(warning == "Yes" && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - reagents.clear_reagents() - to_chat(user, "You empty [src]'s nutrient tank.") - -/** - * Update Tray Proc - * Handles plant harvesting on the tray side, by clearing the sead, names, description, and dead stat. - * Shuts off autogrow if enabled. - * Sends messages to the cleaer about plants harvested, or if nothing was harvested at all. - * * User - The mob who clears the tray. - */ -/obj/machinery/hydroponics/proc/update_tray(mob/user) - harvest = FALSE - lastproduce = age - if(istype(myseed, /obj/item/seeds/replicapod)) - to_chat(user, "You harvest from the [myseed.plantname].") - else if(myseed.getYield() <= 0) - to_chat(user, "You fail to harvest anything useful!") - else - to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].") - if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest)) - qdel(myseed) - myseed = null - dead = FALSE - name = initial(name) - desc = initial(desc) - TRAY_NAME_UPDATE - if(self_sustaining) //No reason to pay for an empty tray. - idle_power_usage = 0 - self_sustaining = FALSE - update_icon() - -/// Tray Setters - The following procs adjust the tray or plants variables, and make sure that the stat doesn't go out of bounds. -/** - * Adjust water. - * Raises or lowers tray water values by a set value. Adding water will dillute toxicity from the tray. - * * adjustamt - determines how much water the tray will be adjusted upwards or downwards. - */ -/obj/machinery/hydroponics/proc/adjustWater(adjustamt) - waterlevel = clamp(waterlevel + adjustamt, 0, maxwater) - - if(adjustamt>0) - adjustToxic(-round(adjustamt/4))//Toxicity dilutation code. The more water you put in, the lesser the toxin concentration. - -/** - * Adjust Health. - * Raises the tray's plant_health stat by a given amount, with total health determined by the seed's endurance. - * * adjustamt - Determines how much the plant_health will be adjusted upwards or downwards. - */ -/obj/machinery/hydroponics/proc/adjustHealth(adjustamt) - if(myseed && !dead) - plant_health = clamp(plant_health + adjustamt, 0, myseed.endurance) - -/** - * Adjust Health. - * Raises the plant's plant_health stat by a given amount, with total health determined by the seed's endurance. - * * adjustamt - Determines how much the plant_health will be adjusted upwards or downwards. - */ -/obj/machinery/hydroponics/proc/adjustToxic(adjustamt) - toxic = clamp(toxic + adjustamt, 0, 100) - -/** - * Adjust Pests. - * Raises the tray's pest level stat by a given amount. - * * adjustamt - Determines how much the pest level will be adjusted upwards or downwards. - */ -/obj/machinery/hydroponics/proc/adjustPests(adjustamt) - pestlevel = clamp(pestlevel + adjustamt, 0, 10) - -/** - * Adjust Weeds. - * Raises the plant's weed level stat by a given amount. - * * adjustamt - Determines how much the weed level will be adjusted upwards or downwards. - */ -/obj/machinery/hydroponics/proc/adjustWeeds(adjustamt) - weedlevel = clamp(weedlevel + adjustamt, 0, 10) - -/** - * Spawn Plant. - * Upon using strange reagent on a tray, it will spawn a killer tomato or killer tree at random. - */ -/obj/machinery/hydroponics/proc/spawnplant() // why would you put strange reagent in a hydro tray you monster I bet you also feed them blood - var/list/livingplants = list(/mob/living/simple_animal/hostile/tree, /mob/living/simple_animal/hostile/killertomato) - var/chosen = pick(livingplants) - var/mob/living/simple_animal/hostile/C = new chosen - C.faction = list("plants") - -////////////////////////////////////////////////////////////////////////////////// -/obj/machinery/hydroponics/irrigated //Pre-Irrigated trays! Maybe map a couple on each map, just so they get used? - using_irrigation = TRUE - -/obj/machinery/hydroponics/irrigated/Initialize() - . = ..() - update_icon() //Visually hooks up all the pipes. - -/////////////////////////////////////////////////////////////////////////////// -/obj/machinery/hydroponics/soil //Not actually hydroponics at all! Honk! - name = "soil" - desc = "A patch of dirt." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "soil" - gender = PLURAL - circuit = null - density = FALSE - use_power = NO_POWER_USE - flags_1 = NODECONSTRUCT_1 - unwrenchable = FALSE - -/obj/machinery/hydroponics/soil/update_icon_hoses() - return // Has no hoses - -/obj/machinery/hydroponics/soil/update_icon_lights() - return // Has no lights - -/obj/machinery/hydroponics/soil/attackby(obj/item/O, mob/user, params) - if(O.tool_behaviour == TOOL_SHOVEL && !istype(O, /obj/item/shovel/spade)) //Doesn't include spades because of uprooting plants - to_chat(user, "You clear up [src]!") - qdel(src) - else - return ..() - -/obj/machinery/hydroponics/soil/CtrlClick(mob/user) - return //Soil has no electricity. + +/obj/machinery/hydroponics + name = "hydroponics tray" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "hydrotray" + density = TRUE + pixel_z = 8 + obj_flags = CAN_BE_HIT | UNIQUE_RENAME + circuit = /obj/item/circuitboard/machine/hydroponics + idle_power_usage = 0 + ///The amount of water in the tray (max 100) + var/waterlevel = 100 + ///The maximum amount of water in the tray + var/maxwater = 100 + ///How many units of nutrients will be drained in the tray. + var/nutridrain = 1 + ///The maximum nutrient of water in the tray + var/maxnutri = 10 + ///The amount of pests in the tray (max 10) + var/pestlevel = 0 + ///The amount of weeds in the tray (max 10) + var/weedlevel = 0 + ///Nutriment's effect on yield + var/yieldmod = 1 + ///Nutriment's effect on mutations + var/mutmod = 1 + ///Toxicity in the tray? + var/toxic = 0 + ///Current age + var/age = 0 + ///Is it dead? + var/dead = FALSE + ///Its health + var/plant_health + ///Last time it was harvested + var/lastproduce = 0 + ///Used for timing of cycles. + var/lastcycle = 0 + ///About 10 seconds / cycle + var/cycledelay = 200 + ///Ready to harvest? + var/harvest = FALSE + ///The currently planted seed + var/obj/item/seeds/myseed = null + ///Obtained from the quality of the parts used in the tray, determines nutrient drain rate. + var/rating = 1 + ///Can it be unwrenched to move? + var/unwrenchable = TRUE + ///Have we been visited by a bee recently, so bees dont overpollinate one plant + var/recent_bee_visit = FALSE + ///If the tray is connected to other trays via irrigation hoses + var/using_irrigation = FALSE + ///The last user to add a reagent to the tray, mostly for logging purposes. + var/mob/lastuser + ///If the tray generates nutrients and water on its own + var/self_sustaining = FALSE + +/obj/machinery/hydroponics/Initialize() + //ALRIGHT YOU DEGENERATES. YOU HAD REAGENT HOLDERS FOR AT LEAST 4 YEARS AND NONE OF YOU MADE HYDROPONICS TRAYS HOLD NUTRIENT CHEMS INSTEAD OF USING "Points". + //SO HERE LIES THE "nutrilevel" VAR. IT'S DEAD AND I PUT IT OUT OF IT'S MISERY. USE "reagents" INSTEAD. ~ArcaneMusic, accept no substitutes. + create_reagents(20) + reagents.add_reagent(/datum/reagent/plantnutriment/eznutriment, 10) //Half filled nutrient trays for dirt trays to have more to grow with in prison/lavaland. + . = ..() + + +/obj/machinery/hydroponics/constructable + name = "hydroponics tray" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "hydrotray3" + +/obj/machinery/hydroponics/constructable/RefreshParts() + var/tmp_capacity = 0 + for (var/obj/item/stock_parts/matter_bin/M in component_parts) + tmp_capacity += M.rating + for (var/obj/item/stock_parts/manipulator/M in component_parts) + rating = M.rating + maxwater = tmp_capacity * 50 // Up to 300 + maxnutri = (tmp_capacity * 5) + STATIC_NUTRIENT_CAPACITY // Up to 50 Maximum + reagents.maximum_volume = maxnutri + nutridrain = 1/rating + +/obj/machinery/hydroponics/constructable/examine(mob/user) + . = ..() + . += "Use Ctrl-Click to activate autogrow. Alt-Click to empty the tray's nutrients." + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Tray efficiency at [rating*100]%." + + +/obj/machinery/hydroponics/Destroy() + if(myseed) + qdel(myseed) + myseed = null + return ..() + +/obj/machinery/hydroponics/constructable/attackby(obj/item/I, mob/user, params) + if (user.a_intent != INTENT_HARM) + // handle opening the panel + if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) + return + + // handle deconstructing the machine, if permissible + if(I.tool_behaviour == TOOL_CROWBAR && using_irrigation) + to_chat(user, "Disconnect the hoses first!") + return + else if(default_deconstruction_crowbar(I)) + return + + return ..() + +/obj/machinery/hydroponics/proc/FindConnected() + var/list/connected = list() + var/list/processing_atoms = list(src) + + while(processing_atoms.len) + var/atom/a = processing_atoms[1] + for(var/step_dir in GLOB.cardinals) + var/obj/machinery/hydroponics/h = locate() in get_step(a, step_dir) + // Soil plots aren't dense + if(h && h.using_irrigation && h.density && !(h in connected) && !(h in processing_atoms)) + processing_atoms += h + + processing_atoms -= a + connected += a + + return connected + +/obj/machinery/hydroponics/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. + if(!myseed) + return ..() + if(istype(Proj , /obj/projectile/energy/floramut)) + mutate() + else if(istype(Proj , /obj/projectile/energy/florayield)) + return myseed.bullet_act(Proj) + else if(istype(Proj , /obj/projectile/energy/florarevolution)) + if(myseed) + if(myseed.mutatelist.len > 0) + myseed.instability = (myseed.instability/2) + mutatespecie() + else + return ..() + +/obj/machinery/hydroponics/process() + var/needs_update = 0 // Checks if the icon needs updating so we don't redraw empty trays every time + + if(myseed && (myseed.loc != src)) + myseed.forceMove(src) + + if(!powered() && self_sustaining) + visible_message("[name]'s auto-grow functionality shuts off!") + idle_power_usage = 0 + self_sustaining = FALSE + update_icon() + + else if(self_sustaining) + adjustWater(rand(1,2)) + adjustWeeds(-1) + adjustPests(-1) + + if(world.time > (lastcycle + cycledelay)) + lastcycle = world.time + if(myseed && !dead) + // Advance age + age++ + if(age < myseed.maturation) + lastproduce = age + + needs_update = 1 + + +//Nutrients////////////////////////////////////////////////////////////// + // Nutrients deplete at a constant rate, since new nutrients can boost stats far easier. + apply_chemicals(lastuser) + if(self_sustaining) + reagents.remove_any(min(0.5, nutridrain)) + else + reagents.remove_any(nutridrain) + + // Lack of nutrients hurts non-weeds + if(reagents.total_volume <= 0 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) + adjustHealth(-rand(1,3)) + +//Photosynthesis///////////////////////////////////////////////////////// + // Lack of light hurts non-mushrooms + if(isturf(loc)) + var/turf/currentTurf = loc + var/lightAmt = currentTurf.get_lumcount() + if(myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + if(lightAmt < 0.2) + adjustHealth(-1 / rating) + else // Non-mushroom + if(lightAmt < 0.4) + adjustHealth(-2 / rating) + +//Water////////////////////////////////////////////////////////////////// + // Drink random amount of water + adjustWater(-rand(1,6) / rating) + + // If the plant is dry, it loses health pretty fast, unless mushroom + if(waterlevel <= 10 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + adjustHealth(-rand(0,1) / rating) + if(waterlevel <= 0) + adjustHealth(-rand(0,2) / rating) + + // Sufficient water level and nutrient level = plant healthy but also spawns weeds + else if(waterlevel > 10 && reagents.total_volume > 0) + adjustHealth(rand(1,2) / rating) + if(myseed && prob(myseed.weed_chance)) + adjustWeeds(myseed.weed_rate) + else if(prob(5)) //5 percent chance the weed population will increase + adjustWeeds(1 / rating) + +//Toxins///////////////////////////////////////////////////////////////// + + // Too much toxins cause harm, but when the plant drinks the contaiminated water, the toxins disappear slowly + if(toxic >= 40 && toxic < 80) + adjustHealth(-1 / rating) + adjustToxic(-rating * 2) + else if(toxic >= 80) // I don't think it ever gets here tbh unless above is commented out + adjustHealth(-3) + adjustToxic(-rating *3) + +//Pests & Weeds////////////////////////////////////////////////////////// + + if(pestlevel >= 8) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + if(myseed.potency >=30) + myseed.adjust_potency(-rand(2,6)) //Pests eat leaves and nibble on fruit, lowering potency. + myseed.potency = min((myseed.potency),30,100) + else + adjustHealth(2 / rating) + adjustPests(-1 / rating) + + else if(pestlevel >= 4) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + if(myseed.potency >=30) + myseed.adjust_potency(-rand(1,4)) + myseed.potency = min((myseed.potency),30,100) + + else + adjustHealth(1 / rating) + if(prob(50)) + adjustPests(-1 / rating) + + else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + if(prob(5)) + adjustPests(-1 / rating) + + // If it's a weed, it doesn't stunt the growth + if(weedlevel >= 5 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) + if(myseed.yield >=3) + myseed.adjust_yield(-rand(1,2)) //Weeds choke out the plant's ability to bear more fruit. + myseed.yield = min((myseed.yield),3,10) + +//This is the part with pollination + pollinate() + +//This is where stability mutations exist now. + if(myseed.instability >= 80) + var/mutation_chance = myseed.instability - 75 + mutate(0, 0, 0, 0, 0, 0, 0, mutation_chance, 0) //Scaling odds of a random trait or chemical + if(myseed.instability >= 60) + if(prob((myseed.instability)/2) && !self_sustaining && length(myseed.mutatelist)) //Minimum 30%, Maximum 50% chance of mutating every age tick when not on autogrow. + mutatespecie() + myseed.instability = myseed.instability/2 + if(myseed.instability >= 40) + if(prob(myseed.instability)) + hardmutate() + if(myseed.instability >= 20 ) + if(prob(myseed.instability)) + mutate() + +//Health & Age/////////////////////////////////////////////////////////// + + // Plant dies if plant_health <= 0 + if(plant_health <= 0) + plantdies() + adjustWeeds(1 / rating) // Weeds flourish + + // If the plant is too old, lose health fast + if(age > myseed.lifespan) + adjustHealth(-rand(1,5) / rating) + + // Harvest code + if(age > myseed.production && (age - lastproduce) > myseed.production && (!harvest && !dead)) + if(myseed && myseed.yield != -1) // Unharvestable shouldn't be harvested + harvest = TRUE + else + lastproduce = age + if(prob(5)) // On each tick, there's a 5 percent chance the pest population will increase + adjustPests(1 / rating) + else + if(waterlevel > 10 && reagents.total_volume > 0 && prob(10)) // If there's no plant, the percentage chance is 10% + adjustWeeds(1 / rating) + + // Weeeeeeeeeeeeeeedddssss + if(weedlevel >= 10 && prob(50) && !self_sustaining) // At this point the plant is kind of fucked. Weeds can overtake the plant spot. + if(myseed && myseed.yield >= 3) + myseed.adjust_yield(-rand(1,2)) //Loses even more yield per tick, quickly dropping to 3 minimum. + myseed.yield = min((myseed.yield),YIELD_WEED_MINIMUM,YIELD_WEED_MAXIMUM) + if(!myseed) + weedinvasion() + needs_update = 1 + if (needs_update) + update_icon() + + if(myseed && prob(5 * (11-myseed.production))) + for(var/g in myseed.genes) + if(istype(g, /datum/plant_gene/trait)) + var/datum/plant_gene/trait/selectedtrait = g + selectedtrait.on_grow(src) + return + +/obj/machinery/hydroponics/update_icon() + //Refreshes the icon and sets the luminosity + cut_overlays() + + if(self_sustaining) + if(istype(src, /obj/machinery/hydroponics/soil)) + add_atom_colour(rgb(255, 175, 0), FIXED_COLOUR_PRIORITY) + else + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "gaia_blessing")) + set_light(3) + + update_icon_hoses() + + if(myseed) + update_icon_plant() + update_icon_lights() + + if(!self_sustaining) + if(myseed && myseed.get_gene(/datum/plant_gene/trait/glow)) + var/datum/plant_gene/trait/glow/G = myseed.get_gene(/datum/plant_gene/trait/glow) + set_light(G.glow_range(myseed), G.glow_power(myseed), G.glow_color) + else + set_light(0) + + return + +/obj/machinery/hydroponics/proc/update_icon_hoses() + var/n = 0 + for(var/Dir in GLOB.cardinals) + var/obj/machinery/hydroponics/t = locate() in get_step(src,Dir) + if(t && t.using_irrigation && using_irrigation) + n += Dir + + icon_state = "hoses-[n]" + +/obj/machinery/hydroponics/proc/update_icon_plant() + var/mutable_appearance/plant_overlay = mutable_appearance(myseed.growing_icon, layer = OBJ_LAYER + 0.01) + if(dead) + plant_overlay.icon_state = myseed.icon_dead + else if(harvest) + if(!myseed.icon_harvest) + plant_overlay.icon_state = "[myseed.icon_grow][myseed.growthstages]" + else + plant_overlay.icon_state = myseed.icon_harvest + else + var/t_growthstate = clamp(round((age / myseed.maturation) * myseed.growthstages), 1, myseed.growthstages) + plant_overlay.icon_state = "[myseed.icon_grow][t_growthstate]" + add_overlay(plant_overlay) + +/obj/machinery/hydroponics/proc/update_icon_lights() + if(waterlevel <= 10) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowwater3")) + if(reagents.total_volume <= 2) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lownutri3")) + if(plant_health <= (myseed.endurance / 2)) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowhealth3")) + if(weedlevel >= 5 || pestlevel >= 5 || toxic >= 40) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_alert3")) + if(harvest) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_harvest3")) + + +/obj/machinery/hydroponics/examine(user) + . = ..() + if(myseed) + . += "It has [myseed.plantname] planted." + if (dead) + . += "It's dead!" + else if (harvest) + . += "It's ready to harvest." + else if (plant_health <= (myseed.endurance / 2)) + . += "It looks unhealthy." + else + . += "It's empty." + + . += "Water: [waterlevel]/[maxwater].\n"+\ + "Nutrient: [reagents.total_volume]/[maxnutri]." + if(self_sustaining) + . += "The tray's autogrow is active, protecting it from species mutations, weeds, and pests." + + if(weedlevel >= 5) + to_chat(user, "It's filled with weeds!") + if(pestlevel >= 5) + to_chat(user, "It's filled with tiny worms!") + to_chat(user, "" ) + +/** + * What happens when a tray's weeds grow too large. + * Plants a new weed in an empty tray, then resets the tray. + */ +/obj/machinery/hydroponics/proc/weedinvasion() + dead = FALSE + var/oldPlantName + if(myseed) // In case there's nothing in the tray beforehand + oldPlantName = myseed.plantname + qdel(myseed) + myseed = null + else + oldPlantName = "empty tray" + switch(rand(1,18)) // randomly pick predominative weed + if(16 to 18) + myseed = new /obj/item/seeds/reishi(src) + if(14 to 15) + myseed = new /obj/item/seeds/nettle(src) + if(12 to 13) + myseed = new /obj/item/seeds/harebell(src) + if(10 to 11) + myseed = new /obj/item/seeds/amanita(src) + if(8 to 9) + myseed = new /obj/item/seeds/chanter(src) + if(6 to 7) + myseed = new /obj/item/seeds/tower(src) + if(4 to 5) + myseed = new /obj/item/seeds/plump(src) + else + myseed = new /obj/item/seeds/starthistle(src) + age = 0 + plant_health = myseed.endurance + lastcycle = world.time + harvest = FALSE + weedlevel = 0 // Reset + pestlevel = 0 // Reset + update_icon() + visible_message("The [oldPlantName] is overtaken by some [myseed.plantname]!") + TRAY_NAME_UPDATE + +/obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0, stabmut = 3) // Mutates the current seed + if(!myseed) + return + myseed.mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut, stabmut) + +/obj/machinery/hydroponics/proc/hardmutate() + mutate(4, 10, 2, 4, 50, 4, 10, 0, 4) + + +/obj/machinery/hydroponics/proc/mutatespecie() // Mutagent produced a new plant! + if(!myseed || dead) + return + + var/oldPlantName = myseed.plantname + if(myseed.mutatelist.len > 0) + var/mutantseed = pick(myseed.mutatelist) + qdel(myseed) + myseed = null + myseed = new mutantseed + else + return + + hardmutate() + age = 0 + plant_health = myseed.endurance + lastcycle = world.time + harvest = FALSE + weedlevel = 0 // Reset + + sleep(5) // Wait a while + update_icon() + visible_message("[oldPlantName] suddenly mutates into [myseed.plantname]!") + TRAY_NAME_UPDATE + +/obj/machinery/hydroponics/proc/mutateweed() // If the weeds gets the mutagent instead. Mind you, this pretty much destroys the old plant + if( weedlevel > 5 ) + if(myseed) + qdel(myseed) + myseed = null + var/newWeed = pick(/obj/item/seeds/liberty, /obj/item/seeds/angel, /obj/item/seeds/nettle/death, /obj/item/seeds/kudzu) + myseed = new newWeed + dead = FALSE + hardmutate() + age = 0 + plant_health = myseed.endurance + lastcycle = world.time + harvest = FALSE + weedlevel = 0 // Reset + + sleep(5) // Wait a while + update_icon() + visible_message("The mutated weeds in [src] spawn some [myseed.plantname]!") + TRAY_NAME_UPDATE + else + to_chat(usr, "The few weeds in [src] seem to react, but only for a moment...") + +/** + * Plant Death Proc. + * Cleans up various stats for the plant upon death, including pests, harvestability, and plant health. + */ +/obj/machinery/hydroponics/proc/plantdies() + plant_health = 0 + harvest = FALSE + pestlevel = 0 // Pests die + lastproduce = 0 + if(!dead) + update_icon() + dead = TRUE + +/** + * Plant Cross-Pollination. + * Checks all plants in the tray's oview range, then averages out the seed's potency, instability, and yield values. + * If the seed's instability is >= 20, the seed donates one of it's reagents to that nearby plant. + * * Range - The Oview range of trays to which to look for plants to donate reagents. + */ +/obj/machinery/hydroponics/proc/pollinate(var/range = 1) + for(var/obj/machinery/hydroponics/T in oview(src, range)) + //Here is where we check for window blocking. + if(!Adjacent(T) && range <= 1) + continue + if(T.myseed && !T.dead) + T.myseed.potency = round(clamp((T.myseed.potency+(1/10)*(myseed.potency-T.myseed.potency)),0,100)) + T.myseed.instability = round(clamp((T.myseed.instability+(1/10)*(myseed.instability-T.myseed.instability)),0,100)) + T.myseed.yield = round(clamp((T.myseed.yield+(1/2)*(myseed.yield-T.myseed.yield)),0,10)) + if(myseed.instability >= 20 && prob(70) && length(T.myseed.reagents_add)) + var/list/datum/plant_gene/reagent/possible_reagents = list() + for(var/datum/plant_gene/reagent/reag in T.myseed.genes) + possible_reagents += reag + var/datum/plant_gene/reagent/reagent_gene = pick(possible_reagents) //Let this serve as a lession to delete your WIP comments before merge. + if(reagent_gene.can_add(myseed)) + myseed.genes += reagent_gene.Copy() + myseed.reagents_from_genes() + continue + +/** + * Pest Mutation Proc. + * When a tray is mutated with high pest values, it will spawn spiders. + * * User - Person who last added chemicals to the tray for logging purposes. + */ +/obj/machinery/hydroponics/proc/mutatepest(mob/user) + if(pestlevel > 5) + message_admins("[ADMIN_LOOKUPFLW(user)] last altered a hydro tray's contents which spawned spiderlings") + log_game("[key_name(user)] last altered a hydro tray, which spiderlings spawned from.") + visible_message("The pests seem to behave oddly...") + spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE) + else if(myseed) + visible_message("The pests seem to behave oddly in [myseed.name] tray, but quickly settle down...") + +/obj/machinery/hydroponics/attackby(obj/item/O, mob/user, params) + //Called when mob user "attacks" it with object O + if(istype(O, /obj/item/reagent_containers) ) // Syringe stuff (and other reagent containers now too) + var/obj/item/reagent_containers/reagent_source = O + + if(istype(reagent_source, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/syringe/syr = reagent_source + if(syr.mode != 1) + to_chat(user, "You can't get any extract out of this plant." ) + return + + if(!reagent_source.reagents.total_volume) + to_chat(user, "[reagent_source] is empty!") + return 1 + + if(reagents.total_volume >= reagents.maximum_volume && !reagent_source.reagents.has_reagent(/datum/reagent/water, 1)) + to_chat(user, "[src] is full.") + return + + var/list/trays = list(src)//makes the list just this in cases of syringes and compost etc + var/target = myseed ? myseed.plantname : src + var/visi_msg = "" + var/irrigate = 0 //How am I supposed to irrigate pill contents? + var/transfer_amount + + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/R = reagent_source + if (R.trash) + R.generate_trash(get_turf(user)) + visi_msg="[user] composts [reagent_source], spreading it through [target]" + transfer_amount = reagent_source.reagents.total_volume + else + transfer_amount = reagent_source.amount_per_transfer_from_this + if(istype(reagent_source, /obj/item/reagent_containers/syringe/)) + var/obj/item/reagent_containers/syringe/syr = reagent_source + visi_msg="[user] injects [target] with [syr]" + if(syr.reagents.total_volume <= syr.amount_per_transfer_from_this) + syr.mode = 0 + else if(istype(reagent_source, /obj/item/reagent_containers/spray/)) + visi_msg="[user] sprays [target] with [reagent_source]" + playsound(loc, 'sound/effects/spray3.ogg', 50, TRUE, -6) + irrigate = 1 + else if(transfer_amount) // Droppers, cans, beakers, what have you. + visi_msg="[user] uses [reagent_source] on [target]" + irrigate = 1 + // Beakers, bottles, buckets, etc. + if(reagent_source.is_drainable()) + playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) + + if(irrigate && transfer_amount > 30 && reagent_source.reagents.total_volume >= 30 && using_irrigation) + trays = FindConnected() + if (trays.len > 1) + visi_msg += ", setting off the irrigation system" + + if(visi_msg) + visible_message("[visi_msg].") + + var/split = round(transfer_amount/trays.len) + + for(var/obj/machinery/hydroponics/H in trays) + //cause I don't want to feel like im juggling 15 tamagotchis and I can get to my real work of ripping flooring apart in hopes of validating my life choices of becoming a space-gardener + //This was originally in apply_chemicals, but due to apply_chemicals only holding nutrients, we handle it here now. + if(reagent_source.reagents.has_reagent(/datum/reagent/water, 1)) + var/water_amt = reagent_source.reagents.get_reagent_amount(/datum/reagent/water) * split / reagent_source.reagents.total_volume + H.adjustWater(round(water_amt)) + reagent_source.reagents.remove_reagent(/datum/reagent/water, water_amt) + reagent_source.reagents.trans_to(H.reagents, split, transfered_by = user) + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) + qdel(reagent_source) + lastuser = user + H.update_icon() + return 1 + H.update_icon() + if(reagent_source) // If the source wasn't composted and destroyed + reagent_source.update_icon() + return 1 + + else if(istype(O, /obj/item/seeds) && !istype(O, /obj/item/seeds/sample)) + if(!myseed) + if(istype(O, /obj/item/seeds/kudzu)) + investigate_log("had Kudzu planted in it by [key_name(user)] at [AREACOORD(src)]","kudzu") + if(!user.transferItemToLoc(O, src)) + return + to_chat(user, "You plant [O].") + dead = FALSE + myseed = O + TRAY_NAME_UPDATE + age = 1 + plant_health = myseed.endurance + lastcycle = world.time + update_icon() + return + else + to_chat(user, "[src] already has seeds in it!") + return + + else if(istype(O, /obj/item/plant_analyzer)) + var/obj/item/plant_analyzer/P_analyzer = O + if(myseed) + if(P_analyzer.scan_mode == PLANT_SCANMODE_STATS) + to_chat(user, "*** [myseed.plantname] ***" ) + to_chat(user, "- Plant Age: [age]") + var/list/text_string = myseed.get_analyzer_text() + if(text_string) + to_chat(user, text_string) + to_chat(user, "*---------*") + if(myseed.reagents_add && P_analyzer.scan_mode == PLANT_SCANMODE_CHEMICALS) + to_chat(user, "- Plant Reagents -") + to_chat(user, "*---------*") + for(var/datum/plant_gene/reagent/G in myseed.genes) + to_chat(user, "- [G.get_name()] -") + to_chat(user, "*---------*") + else + to_chat(user, "No plant found.") + to_chat(user, "- Weed level: [weedlevel] / 10") + to_chat(user, "- Pest level: [pestlevel] / 10") + to_chat(user, "- Toxicity level: [toxic] / 100") + to_chat(user, "- Water level: [waterlevel] / [maxwater]") + to_chat(user, "- Nutrition level: [reagents.total_volume] / [maxnutri]") + to_chat(user, "") + return + + else if(istype(O, /obj/item/cultivator)) + if(weedlevel > 0) + user.visible_message("[user] uproots the weeds.", "You remove the weeds from [src].") + weedlevel = 0 + update_icon() + return + else + to_chat(user, "This plot is completely devoid of weeds! It doesn't need uprooting.") + return + + else if(istype(O, /obj/item/secateurs)) + if(!myseed) + to_chat(user, "This plot is empty.") + return + else if(!harvest) + to_chat(user, "This plant must be harvestable in order to be grafted.") + return + else if(myseed.grafted) + to_chat(user, "This plant has already been grafted.") + return + else + user.visible_message("[user] grafts off a limb from [src].", "You carefully graft off a portion of [src].") + var/obj/item/graft/snip = myseed.create_graft() + if(!snip) + return // The plant did not return a graft. + + snip.forceMove(drop_location()) + myseed.grafted = TRUE + adjustHealth(-5) + return + + else if(istype(O, /obj/item/geneshears)) + if(!myseed) + to_chat(user, "The tray is empty.") + return + if(plant_health <= 15) + to_chat(user, "This plant looks too unhealty to be sheared right now.") + return + + var/list/current_traits = list() + for(var/datum/plant_gene/gene in myseed.genes) + if(istype(gene, /datum/plant_gene/core) || (istype(gene,/datum/plant_gene/trait/plant_type)) || islist(gene)) + continue + if(!(gene.mutability_flags & PLANT_GENE_REMOVABLE) || !(gene.mutability_flags & PLANT_GENE_EXTRACTABLE)) + continue //No bypassing unextractable or essential genes. + current_traits[gene.name] = gene + var/removed_trait = (input(user, "Select a trait to remove from the [myseed.plantname].", "Plant Trait Removal") as null|anything in sortList(current_traits)) + if(removed_trait == null) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + for(var/datum/plant_gene/gene in myseed.genes) + if(gene.name == removed_trait) + if(myseed.genes.Remove(gene)) + qdel(gene) + break + myseed.reagents_from_genes() + adjustHealth(-15) + to_chat(user, "You carefully shear the genes off of the [myseed.plantname], leaving the plant looking weaker.") + update_icon() + return + + else if(istype(O, /obj/item/graft)) + var/obj/item/graft/snip = O + if(!myseed) + to_chat(user, "The tray is empty.") + return + if(!myseed.apply_graft(snip)) + to_chat(user, "The [myseed.plantname] rejects the [snip]!") + return + qdel(snip) + to_chat(user, "You carefully integrate the grafted plant limb onto [myseed.plantname].") + return + + else if(istype(O, /obj/item/storage/bag/plants)) + attack_hand(user) + for(var/obj/item/reagent_containers/food/snacks/grown/G in locate(user.x,user.y,user.z)) + SEND_SIGNAL(O, COMSIG_TRY_STORAGE_INSERT, G, user, TRUE) + return + + else if(default_unfasten_wrench(user, O)) + return + + else if((O.tool_behaviour == TOOL_WIRECUTTER) && unwrenchable) + if (!anchored) + to_chat(user, "Anchor the tray first!") + return + using_irrigation = !using_irrigation + O.play_tool_sound(src) + user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \ + "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.") + for(var/obj/machinery/hydroponics/h in range(1,src)) + h.update_icon() + return + + else if(istype(O, /obj/item/shovel/spade)) + if(!myseed && !weedlevel) + to_chat(user, "[src] doesn't have any plants or weeds!") + return + user.visible_message("[user] starts digging out [src]'s plants...", + "You start digging out [src]'s plants...") + if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel)) + user.visible_message("[user] digs out the plants in [src]!", "You dig out all of [src]'s plants!") + if(myseed) //Could be that they're just using it as a de-weeder + age = 0 + plant_health = 0 + lastproduce = 0 + if(harvest) + harvest = FALSE //To make sure they can't just put in another seed and insta-harvest it + qdel(myseed) + myseed = null + name = initial(name) + desc = initial(desc) + weedlevel = 0 //Has a side effect of cleaning up those nasty weeds + update_icon() + return + else if(istype(O, /obj/item/storage/part_replacer)) + RefreshParts() + return + else if(istype(O, /obj/item/gun/energy/floragun)) + var/obj/item/gun/energy/floragun/flowergun = O + if(flowergun.cell.charge < flowergun.cell.maxcharge) + to_chat(user, "[flowergun] must be fully charged to lock in a mutation!") + return + if(!myseed) + to_chat(user, "[src] is empty!") + return + if(myseed.endurance <= 20) + to_chat(user, "[myseed.plantname] isn't hardy enough to sequence it's mutation!") + return + if(!myseed.mutatelist) + to_chat(user, "[myseed.plantname] has nothing else to mutate into!") + return + else + var/list/fresh_mut_list = list() + for(var/muties in myseed.mutatelist) + var/obj/item/seeds/another_mut = new muties + fresh_mut_list[another_mut.plantname] = muties + var/locked_mutation = (input(user, "Select a mutation to lock.", "Plant Mutation Locks") as null|anything in sortList(fresh_mut_list)) + if(!user.canUseTopic(src, BE_CLOSE) || !locked_mutation) + return + myseed.mutatelist = list(fresh_mut_list[locked_mutation]) + myseed.endurance = (myseed.endurance/2) + flowergun.cell.use(flowergun.cell.charge) + flowergun.update_overlays() + to_chat(user, "[myseed.plantname]'s mutation was set to [locked_mutation], depleting [flowergun]'s cell!") + return + else + return ..() + +/obj/machinery/hydroponics/can_be_unfasten_wrench(mob/user, silent) + if (!unwrenchable) // case also covered by NODECONSTRUCT checks in default_unfasten_wrench + return CANT_UNFASTEN + + if (using_irrigation) + if (!silent) + to_chat(user, "Disconnect the hoses first!") + return FAILED_UNFASTEN + + return ..() + +/obj/machinery/hydroponics/attack_hand(mob/user) + . = ..() + if(.) + return + if(issilicon(user)) //How does AI know what plant is? + return + if(harvest) + return myseed.harvest(user) + + else if(dead) + dead = FALSE + to_chat(user, "You remove the dead plant from [src].") + qdel(myseed) + myseed = null + update_icon() + TRAY_NAME_UPDATE + else + if(user) + examine(user) + +/obj/machinery/hydroponics/CtrlClick(mob/user) + . = ..() + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(!powered()) + to_chat(user, "[name] has no power.") + return + if(!anchored) + return + self_sustaining = !self_sustaining + idle_power_usage = self_sustaining ? 5000 : 0 + to_chat(user, "You [self_sustaining ? "activate" : "deactivated"] [src]'s autogrow function[self_sustaining ? ", maintaining the tray's health while using high amounts of power" : ""].") + update_icon() + +/obj/machinery/hydroponics/AltClick(mob/user) + . = ..() + var/warning = alert(user, "Are you sure you wish to empty the tray's nutrient beaker?","Empty Tray Nutrients?", "Yes", "No") + if(warning == "Yes" && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + reagents.clear_reagents() + to_chat(user, "You empty [src]'s nutrient tank.") + +/** + * Update Tray Proc + * Handles plant harvesting on the tray side, by clearing the sead, names, description, and dead stat. + * Shuts off autogrow if enabled. + * Sends messages to the cleaer about plants harvested, or if nothing was harvested at all. + * * User - The mob who clears the tray. + */ +/obj/machinery/hydroponics/proc/update_tray(mob/user) + harvest = FALSE + lastproduce = age + if(istype(myseed, /obj/item/seeds/replicapod)) + to_chat(user, "You harvest from the [myseed.plantname].") + else if(myseed.getYield() <= 0) + to_chat(user, "You fail to harvest anything useful!") + else + to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].") + if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest)) + qdel(myseed) + myseed = null + dead = FALSE + name = initial(name) + desc = initial(desc) + TRAY_NAME_UPDATE + if(self_sustaining) //No reason to pay for an empty tray. + idle_power_usage = 0 + self_sustaining = FALSE + update_icon() + +/// Tray Setters - The following procs adjust the tray or plants variables, and make sure that the stat doesn't go out of bounds. +/** + * Adjust water. + * Raises or lowers tray water values by a set value. Adding water will dillute toxicity from the tray. + * * adjustamt - determines how much water the tray will be adjusted upwards or downwards. + */ +/obj/machinery/hydroponics/proc/adjustWater(adjustamt) + waterlevel = clamp(waterlevel + adjustamt, 0, maxwater) + + if(adjustamt>0) + adjustToxic(-round(adjustamt/4))//Toxicity dilutation code. The more water you put in, the lesser the toxin concentration. + +/** + * Adjust Health. + * Raises the tray's plant_health stat by a given amount, with total health determined by the seed's endurance. + * * adjustamt - Determines how much the plant_health will be adjusted upwards or downwards. + */ +/obj/machinery/hydroponics/proc/adjustHealth(adjustamt) + if(myseed && !dead) + plant_health = clamp(plant_health + adjustamt, 0, myseed.endurance) + +/** + * Adjust Health. + * Raises the plant's plant_health stat by a given amount, with total health determined by the seed's endurance. + * * adjustamt - Determines how much the plant_health will be adjusted upwards or downwards. + */ +/obj/machinery/hydroponics/proc/adjustToxic(adjustamt) + toxic = clamp(toxic + adjustamt, 0, 100) + +/** + * Adjust Pests. + * Raises the tray's pest level stat by a given amount. + * * adjustamt - Determines how much the pest level will be adjusted upwards or downwards. + */ +/obj/machinery/hydroponics/proc/adjustPests(adjustamt) + pestlevel = clamp(pestlevel + adjustamt, 0, 10) + +/** + * Adjust Weeds. + * Raises the plant's weed level stat by a given amount. + * * adjustamt - Determines how much the weed level will be adjusted upwards or downwards. + */ +/obj/machinery/hydroponics/proc/adjustWeeds(adjustamt) + weedlevel = clamp(weedlevel + adjustamt, 0, 10) + +/** + * Spawn Plant. + * Upon using strange reagent on a tray, it will spawn a killer tomato or killer tree at random. + */ +/obj/machinery/hydroponics/proc/spawnplant() // why would you put strange reagent in a hydro tray you monster I bet you also feed them blood + var/list/livingplants = list(/mob/living/simple_animal/hostile/tree, /mob/living/simple_animal/hostile/killertomato) + var/chosen = pick(livingplants) + var/mob/living/simple_animal/hostile/C = new chosen + C.faction = list("plants") + +////////////////////////////////////////////////////////////////////////////////// +/obj/machinery/hydroponics/irrigated //Pre-Irrigated trays! Maybe map a couple on each map, just so they get used? + using_irrigation = TRUE + +/obj/machinery/hydroponics/irrigated/Initialize() + . = ..() + update_icon() //Visually hooks up all the pipes. + +/////////////////////////////////////////////////////////////////////////////// +/obj/machinery/hydroponics/soil //Not actually hydroponics at all! Honk! + name = "soil" + desc = "A patch of dirt." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "soil" + gender = PLURAL + circuit = null + density = FALSE + use_power = NO_POWER_USE + flags_1 = NODECONSTRUCT_1 + unwrenchable = FALSE + +/obj/machinery/hydroponics/soil/update_icon_hoses() + return // Has no hoses + +/obj/machinery/hydroponics/soil/update_icon_lights() + return // Has no lights + +/obj/machinery/hydroponics/soil/attackby(obj/item/O, mob/user, params) + if(O.tool_behaviour == TOOL_SHOVEL && !istype(O, /obj/item/shovel/spade)) //Doesn't include spades because of uprooting plants + to_chat(user, "You clear up [src]!") + qdel(src) + else + return ..() + +/obj/machinery/hydroponics/soil/CtrlClick(mob/user) + return //Soil has no electricity. diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm index f74a6d9374a..2320f95963b 100644 --- a/code/modules/hydroponics/seed_extractor.dm +++ b/code/modules/hydroponics/seed_extractor.dm @@ -1,199 +1,199 @@ -/** - * Finds and extracts seeds from an object - * - * Checks if the object is such that creates a seed when extracted. Used by seed - * extractors or posably anything that would create seeds in some way. The seeds - * are dropped either at the extractor, if it exists, or where the original object - * was and it qdel's the object - * - * Arguments: - * * O - Object containing the seed, can be the loc of the dumping of seeds - * * t_max - Amount of seed copies to dump, -1 is ranomized - * * extractor - Seed Extractor, used as the dumping loc for the seeds and seed multiplier - * * user - checks if we can remove the object from the inventory - * * - */ -/proc/seedify(obj/item/O, t_max, obj/machinery/seed_extractor/extractor, mob/living/user) - var/t_amount = 0 - var/list/seeds = list() - if(t_max == -1) - if(extractor) - t_max = rand(1,4) * extractor.seed_multiplier - else - t_max = rand(1,4) - - var/seedloc = O.loc - if(extractor) - seedloc = extractor.loc - - if(istype(O, /obj/item/reagent_containers/food/snacks/grown/)) - var/obj/item/reagent_containers/food/snacks/grown/F = O - if(F.seed) - if(user && !user.temporarilyRemoveItemFromInventory(O)) //couldn't drop the item - return - while(t_amount < t_max) - var/obj/item/seeds/t_prod = F.seed.Copy() - seeds.Add(t_prod) - t_prod.forceMove(seedloc) - t_amount++ - qdel(O) - return seeds - - else if(istype(O, /obj/item/grown)) - var/obj/item/grown/F = O - if(F.seed) - if(user && !user.temporarilyRemoveItemFromInventory(O)) - return - while(t_amount < t_max) - var/obj/item/seeds/t_prod = F.seed.Copy() - t_prod.forceMove(seedloc) - t_amount++ - qdel(O) - return 1 - - return 0 - - -/obj/machinery/seed_extractor - name = "seed extractor" - desc = "Extracts and bags seeds from produce." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "sextractor" - density = TRUE - circuit = /obj/item/circuitboard/machine/seed_extractor - /// Associated list of seeds, they are all weak refs. We check the len to see how many refs we have for each - // seed - var/list/piles = list() - var/max_seeds = 1000 - var/seed_multiplier = 1 - ui_x = 1000 - ui_y = 400 - -/obj/machinery/seed_extractor/RefreshParts() - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - max_seeds = initial(max_seeds) * B.rating - for(var/obj/item/stock_parts/manipulator/M in component_parts) - seed_multiplier = initial(seed_multiplier) * M.rating - -/obj/machinery/seed_extractor/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
                Machine can store up to [max_seeds]% seeds.
                " - -/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params) - - if(default_deconstruction_screwdriver(user, "sextractor_open", "sextractor", O)) - return - - if(default_pry_open(O)) - return - - if(default_unfasten_wrench(user, O)) - return - - if(default_deconstruction_crowbar(O)) - return - - if(istype(O, /obj/item/storage/bag/plants)) - var/obj/item/storage/P = O - var/loaded = 0 - for(var/obj/item/seeds/G in P.contents) - if(contents.len >= max_seeds) - break - ++loaded - add_seed(G) - if (loaded) - to_chat(user, "You put as many seeds from \the [O.name] into [src] as you can.") - else - to_chat(user, "There are no seeds in \the [O.name].") - return - - else if(seedify(O,-1, src, user)) - to_chat(user, "You extract some seeds.") - return - else if (istype(O, /obj/item/seeds)) - if(add_seed(O)) - to_chat(user, "You add [O] to [src.name].") - updateUsrDialog() - return - else if(user.a_intent != INTENT_HARM) - to_chat(user, "You can't extract any seeds from \the [O.name]!") - else - return ..() - -/** - * Generate seed string - * - * Creates a string based of the traits of a seed. We use this string as a bucket for all - * seeds that match as well as the key the ui uses to get the seed. We also use the key - * for the data shown in the ui. Javascript parses this string to display - * - * Arguments: - * * O - seed to generate the string from - */ -/obj/machinery/seed_extractor/proc/generate_seed_string(obj/item/seeds/O) - return "name=[O.name];lifespan=[O.lifespan];endurance=[O.endurance];maturation=[O.maturation];production=[O.production];yield=[O.yield];potency=[O.potency];instability=[O.instability]" - - -/** Add Seeds Proc. - * - * Adds the seeds to the contents and to an associated list that pregenerates the data - * needed to go to the ui handler - * - **/ -/obj/machinery/seed_extractor/proc/add_seed(obj/item/seeds/O) - if(contents.len >= 999) - to_chat(usr, "\The [src] is full.") - return FALSE - - var/datum/component/storage/STR = O.loc.GetComponent(/datum/component/storage) - if(STR) - if(!STR.remove_from_storage(O,src)) - return FALSE - else if(ismob(O.loc)) - var/mob/M = O.loc - if(!M.transferItemToLoc(O, src)) - return FALSE - - var/seed_string = generate_seed_string(O) - if(piles[seed_string]) - piles[seed_string] += WEAKREF(O) - else - piles[seed_string] = list(WEAKREF(O)) - - . = TRUE - -/obj/machinery/seed_extractor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "SeedExtractor", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/seed_extractor/ui_data() - var/list/V = list() - for(var/key in piles) - if(piles[key]) - var/len = length(piles[key]) - if(len) - V[key] = len - - . = list() - .["seeds"] = V - -/obj/machinery/seed_extractor/ui_act(action, params) - if(..()) - return - - switch(action) - if("select") - var/item = params["item"] - if(piles[item] && length(piles[item]) > 0) - var/datum/weakref/WO = piles[item][1] - var/obj/item/seeds/O = WO.resolve() - if(O) - piles[item] -= WO - O.forceMove(drop_location()) - . = TRUE - //to_chat(usr, "[src] clanks to life briefly before vending [prize.equipment_name]!") - +/** + * Finds and extracts seeds from an object + * + * Checks if the object is such that creates a seed when extracted. Used by seed + * extractors or posably anything that would create seeds in some way. The seeds + * are dropped either at the extractor, if it exists, or where the original object + * was and it qdel's the object + * + * Arguments: + * * O - Object containing the seed, can be the loc of the dumping of seeds + * * t_max - Amount of seed copies to dump, -1 is ranomized + * * extractor - Seed Extractor, used as the dumping loc for the seeds and seed multiplier + * * user - checks if we can remove the object from the inventory + * * + */ +/proc/seedify(obj/item/O, t_max, obj/machinery/seed_extractor/extractor, mob/living/user) + var/t_amount = 0 + var/list/seeds = list() + if(t_max == -1) + if(extractor) + t_max = rand(1,4) * extractor.seed_multiplier + else + t_max = rand(1,4) + + var/seedloc = O.loc + if(extractor) + seedloc = extractor.loc + + if(istype(O, /obj/item/reagent_containers/food/snacks/grown/)) + var/obj/item/reagent_containers/food/snacks/grown/F = O + if(F.seed) + if(user && !user.temporarilyRemoveItemFromInventory(O)) //couldn't drop the item + return + while(t_amount < t_max) + var/obj/item/seeds/t_prod = F.seed.Copy() + seeds.Add(t_prod) + t_prod.forceMove(seedloc) + t_amount++ + qdel(O) + return seeds + + else if(istype(O, /obj/item/grown)) + var/obj/item/grown/F = O + if(F.seed) + if(user && !user.temporarilyRemoveItemFromInventory(O)) + return + while(t_amount < t_max) + var/obj/item/seeds/t_prod = F.seed.Copy() + t_prod.forceMove(seedloc) + t_amount++ + qdel(O) + return 1 + + return 0 + + +/obj/machinery/seed_extractor + name = "seed extractor" + desc = "Extracts and bags seeds from produce." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "sextractor" + density = TRUE + circuit = /obj/item/circuitboard/machine/seed_extractor + /// Associated list of seeds, they are all weak refs. We check the len to see how many refs we have for each + // seed + var/list/piles = list() + var/max_seeds = 1000 + var/seed_multiplier = 1 + ui_x = 1000 + ui_y = 400 + +/obj/machinery/seed_extractor/RefreshParts() + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + max_seeds = initial(max_seeds) * B.rating + for(var/obj/item/stock_parts/manipulator/M in component_parts) + seed_multiplier = initial(seed_multiplier) * M.rating + +/obj/machinery/seed_extractor/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
                Machine can store up to [max_seeds]% seeds.
                " + +/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params) + + if(default_deconstruction_screwdriver(user, "sextractor_open", "sextractor", O)) + return + + if(default_pry_open(O)) + return + + if(default_unfasten_wrench(user, O)) + return + + if(default_deconstruction_crowbar(O)) + return + + if(istype(O, /obj/item/storage/bag/plants)) + var/obj/item/storage/P = O + var/loaded = 0 + for(var/obj/item/seeds/G in P.contents) + if(contents.len >= max_seeds) + break + ++loaded + add_seed(G) + if (loaded) + to_chat(user, "You put as many seeds from \the [O.name] into [src] as you can.") + else + to_chat(user, "There are no seeds in \the [O.name].") + return + + else if(seedify(O,-1, src, user)) + to_chat(user, "You extract some seeds.") + return + else if (istype(O, /obj/item/seeds)) + if(add_seed(O)) + to_chat(user, "You add [O] to [src.name].") + updateUsrDialog() + return + else if(user.a_intent != INTENT_HARM) + to_chat(user, "You can't extract any seeds from \the [O.name]!") + else + return ..() + +/** + * Generate seed string + * + * Creates a string based of the traits of a seed. We use this string as a bucket for all + * seeds that match as well as the key the ui uses to get the seed. We also use the key + * for the data shown in the ui. Javascript parses this string to display + * + * Arguments: + * * O - seed to generate the string from + */ +/obj/machinery/seed_extractor/proc/generate_seed_string(obj/item/seeds/O) + return "name=[O.name];lifespan=[O.lifespan];endurance=[O.endurance];maturation=[O.maturation];production=[O.production];yield=[O.yield];potency=[O.potency];instability=[O.instability]" + + +/** Add Seeds Proc. + * + * Adds the seeds to the contents and to an associated list that pregenerates the data + * needed to go to the ui handler + * + **/ +/obj/machinery/seed_extractor/proc/add_seed(obj/item/seeds/O) + if(contents.len >= 999) + to_chat(usr, "\The [src] is full.") + return FALSE + + var/datum/component/storage/STR = O.loc.GetComponent(/datum/component/storage) + if(STR) + if(!STR.remove_from_storage(O,src)) + return FALSE + else if(ismob(O.loc)) + var/mob/M = O.loc + if(!M.transferItemToLoc(O, src)) + return FALSE + + var/seed_string = generate_seed_string(O) + if(piles[seed_string]) + piles[seed_string] += WEAKREF(O) + else + piles[seed_string] = list(WEAKREF(O)) + + . = TRUE + +/obj/machinery/seed_extractor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "SeedExtractor", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/seed_extractor/ui_data() + var/list/V = list() + for(var/key in piles) + if(piles[key]) + var/len = length(piles[key]) + if(len) + V[key] = len + + . = list() + .["seeds"] = V + +/obj/machinery/seed_extractor/ui_act(action, params) + if(..()) + return + + switch(action) + if("select") + var/item = params["item"] + if(piles[item] && length(piles[item]) > 0) + var/datum/weakref/WO = piles[item][1] + var/obj/item/seeds/O = WO.resolve() + if(O) + piles[item] -= WO + O.forceMove(drop_location()) + . = TRUE + //to_chat(usr, "[src] clanks to life briefly before vending [prize.equipment_name]!") + diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 67daf8f36ef..772f2933e86 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -1,624 +1,624 @@ -// ******************************************************** -// Here's all the seeds (plants) that can be used in hydro -// ******************************************************** - -/obj/item/seeds - icon = 'icons/obj/hydroponics/seeds.dmi' - icon_state = "seed" // Unknown plant seed - these shouldn't exist in-game. - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - /// Name of plant when planted. - var/plantname = "Plants" - /// A type path. The thing that is created when the plant is harvested. - var/obj/item/product - ///Describes the product on the product path. - var/productdesc - /// Used to update icons. Should match the name in the sprites unless all icon_* are overridden. - var/species = "" - ///the file that stores the sprites of the growing plant from this seed. - var/growing_icon = 'icons/obj/hydroponics/growing.dmi' - /// Used to override grow icon (default is "[species]-grow"). You can use one grow icon for multiple closely related plants with it. - var/icon_grow - /// Used to override dead icon (default is "[species]-dead"). You can use one dead icon for multiple closely related plants with it. - var/icon_dead - /// Used to override harvest icon (default is "[species]-harvest"). If null, plant will use [icon_grow][growthstages]. - var/icon_harvest - /// How long before the plant begins to take damage from age. - var/lifespan = 25 - /// Amount of health the plant has. - var/endurance = 15 - /// Used to determine which sprite to switch to when growing. - var/maturation = 6 - /// Changes the amount of time needed for a plant to become harvestable. - var/production = 6 - /// Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested. - var/yield = 3 - /// The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways. - var/potency = 10 - /// Amount of growth sprites the plant has. - var/growthstages = 6 - // Chance that a plant will mutate in each stage of it's life. - var/instability = 5 - /// How rare the plant is. Used for giving points to cargo when shipping off to CentCom. - var/rarity = 0 - /// The type of plants that this plant can mutate into. - var/list/mutatelist - /// Plant genes are stored here, see plant_genes.dm for more info. - var/list/genes = list() - /// A list of reagents to add to product. - var/list/reagents_add - // Format: "reagent_id" = potency multiplier - // Stronger reagents must always come first to avoid being displaced by weaker ones. - // Total amount of any reagent in plant is calculated by formula: max(round(potency * multiplier), 1) - ///If the chance below passes, then this many weeds sprout during growth - var/weed_rate = 1 - ///Percentage chance per tray update to grow weeds - var/weed_chance = 5 - ///Determines if the plant has had a graft removed or not. - var/grafted = FALSE - ///Type-path of trait to be applied when grafting a plant. - var/graft_gene - -/obj/item/seeds/Initialize(mapload, nogenes = 0) - . = ..() - pixel_x = rand(-8, 8) - pixel_y = rand(-8, 8) - - if(!icon_grow) - icon_grow = "[species]-grow" - - if(!icon_dead) - icon_dead = "[species]-dead" - - if(!icon_harvest && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && yield != -1) - icon_harvest = "[species]-harvest" - - if(!nogenes) // not used on Copy() - genes += new /datum/plant_gene/core/lifespan(lifespan) - genes += new /datum/plant_gene/core/endurance(endurance) - genes += new /datum/plant_gene/core/weed_rate(weed_rate) - genes += new /datum/plant_gene/core/weed_chance(weed_chance) - if(yield != -1) - genes += new /datum/plant_gene/core/yield(yield) - genes += new /datum/plant_gene/core/production(production) - if(potency != -1) - genes += new /datum/plant_gene/core/potency(potency) - genes += new /datum/plant_gene/core/instability(instability) - - for(var/p in genes) - if(ispath(p)) - genes -= p - genes += new p - - for(var/reag_id in reagents_add) - genes += new /datum/plant_gene/reagent(reag_id, reagents_add[reag_id]) - reagents_from_genes() //quality coding - -/obj/item/seeds/examine(mob/user) - . = ..() - . += "Use a pen on it to rename it or change its description." - if(reagents_add && user.can_see_reagents()) - . += "- Plant Reagents -" - for(var/datum/plant_gene/reagent/G in genes) - . += "- [G.get_name()] -" - -/obj/item/seeds/proc/Copy() - var/obj/item/seeds/S = new type(null, 1) - // Copy all the stats - S.lifespan = lifespan - S.endurance = endurance - S.maturation = maturation - S.production = production - S.yield = yield - S.potency = potency - S.instability = instability - S.weed_rate = weed_rate - S.weed_chance = weed_chance - S.name = name - S.plantname = plantname - S.desc = desc - S.productdesc = productdesc - S.genes = list() - for(var/g in genes) - var/datum/plant_gene/G = g - S.genes += G.Copy() - S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. - return S - -/obj/item/seeds/proc/get_gene(typepath) - return (locate(typepath) in genes) - -/obj/item/seeds/proc/reagents_from_genes() - reagents_add = list() - for(var/datum/plant_gene/reagent/R in genes) - reagents_add[R.reagent_id] = R.rate - -///This proc adds a mutability_flag to a gene -/obj/item/seeds/proc/set_mutability(typepath, mutability) - var/datum/plant_gene/g = get_gene(typepath) - if(g) - g.mutability_flags |= mutability - -///This proc removes a mutability_flag from a gene -/obj/item/seeds/proc/unset_mutability(typepath, mutability) - var/datum/plant_gene/g = get_gene(typepath) - if(g) - g.mutability_flags &= ~mutability - -/obj/item/seeds/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0, stabmut = 3) - adjust_lifespan(rand(-lifemut,lifemut)) - adjust_endurance(rand(-endmut,endmut)) - adjust_production(rand(-productmut,productmut)) - adjust_yield(rand(-yieldmut,yieldmut)) - adjust_potency(rand(-potmut,potmut)) - adjust_instability(rand(-stabmut,stabmut)) - adjust_weed_rate(rand(-wrmut, wrmut)) - adjust_weed_chance(rand(-wcmut, wcmut)) - if(prob(traitmut)) - if(prob(50)) - add_random_traits(1, 1) - else - add_random_reagents(1, 1) - - - -/obj/item/seeds/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. - if(istype(Proj, /obj/projectile/energy/florayield)) - var/rating = 1 - if(istype(loc, /obj/machinery/hydroponics)) - var/obj/machinery/hydroponics/H = loc - rating = H.rating - - if(yield == 0)//Oh god don't divide by zero you'll doom us all. - adjust_yield(1 * rating) - else if(prob(1/(yield * yield) * 100))//This formula gives you diminishing returns based on yield. 100% with 1 yield, decreasing to 25%, 11%, 6, 4, 2... - adjust_yield(1 * rating) - else - return ..() - - -// Harvest procs -/obj/item/seeds/proc/getYield() - var/return_yield = yield - - var/obj/machinery/hydroponics/parent = loc - if(istype(loc, /obj/machinery/hydroponics)) - if(parent.yieldmod == 0) - return_yield = min(return_yield, 1)//1 if above zero, 0 otherwise - else - return_yield *= (parent.yieldmod) - - return return_yield - - -/obj/item/seeds/proc/harvest(mob/user) - ///Reference to the tray/soil the seeds are planted in. - var/obj/machinery/hydroponics/parent = loc //for ease of access - ///Count used for creating the correct amount of results to the harvest. - var/t_amount = 0 - ///List of plants all harvested from the same batch. - var/list/result = list() - ///Tile of the harvester to deposit the growables. - var/output_loc = parent.Adjacent(user) ? user.loc : parent.loc //needed for TK - ///Name of the grown products. - var/product_name - ///The Number of products produced by the plant, typically the yield. Modified by Densified Chemicals. - var/product_count = getYield() - if(get_gene(/datum/plant_gene/trait/maxchem)) - product_count = clamp(round(product_count/2),0,5) - while(t_amount < product_count) - var/obj/item/reagent_containers/food/snacks/grown/t_prod = new product(output_loc, src) - if(parent.myseed.plantname != initial(parent.myseed.plantname)) - t_prod.name = lowertext(parent.myseed.plantname) - if(productdesc) - t_prod.desc = productdesc - t_prod.seed.name = parent.myseed.name - t_prod.seed.desc = parent.myseed.desc - t_prod.seed.plantname = parent.myseed.plantname - result.Add(t_prod) // User gets a consumable - if(!t_prod) - return - t_amount++ - product_name = parent.myseed.plantname - if(getYield() >= 1) - SSblackbox.record_feedback("tally", "food_harvested", getYield(), product_name) - parent.update_tray(user) - - return result - -/** - * This is where plant chemical products are handled. - * - * Individually, the formula for individual amounts of chemicals is Potency * the chemical production %, rounded to the fullest 1. - * Specific chem handling is also handled here, like bloodtype, food taste within nutriment, and the auto-distilling trait. - */ -/obj/item/seeds/proc/prepare_result(var/obj/item/T) - if(!T.reagents) - CRASH("[T] has no reagents.") - var/reagent_max = 0 - for(var/rid in reagents_add) - reagent_max += reagents_add[rid] - if(istype(T, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/grown_edible = T - for(var/rid in reagents_add) - var/reagent_overflow_mod = reagents_add[rid] - if(reagent_max > 1) - reagent_overflow_mod = (reagents_add[rid]/ reagent_max) - var/edible_vol = grown_edible.reagents ? grown_edible.reagents.maximum_volume : 0 - var/amount = max(1, round((edible_vol)*(potency/100) * reagent_overflow_mod, 1)) //the plant will always have at least 1u of each of the reagents in its reagent production traits - var/list/data - if(rid == /datum/reagent/blood) // Hack to make blood in plants always O- - data = list("blood_type" = "O-") - if(rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin) - data = grown_edible.tastes // apple tastes of apple. - //Handles the distillary trait, swaps nutriment and vitamins for that species brewable if it exists. - if(get_gene(/datum/plant_gene/trait/brewing) && grown_edible.distill_reagent) - T.reagents.add_reagent(grown_edible.distill_reagent, amount/2) - continue - T.reagents.add_reagent(rid, amount, data) - - -/// Setters procs /// - -/** - * Adjusts seed yield up or down according to adjustamt. (Max 10) - */ -/obj/item/seeds/proc/adjust_yield(adjustamt) - if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable - yield = clamp(yield + adjustamt, 0, 10) - - if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - yield = 1 // Mushrooms always have a minimum yield of 1. - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) - if(C) - C.value = yield - -/** - * Adjusts seed lifespan up or down according to adjustamt. (Max 100) - */ -/obj/item/seeds/proc/adjust_lifespan(adjustamt) - lifespan = clamp(lifespan + adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) - if(C) - C.value = lifespan - -/** - * Adjusts seed endurance up or down according to adjustamt. (Max 100) - */ -/obj/item/seeds/proc/adjust_endurance(adjustamt) - endurance = clamp(endurance + adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) - if(C) - C.value = endurance - -/** - * Adjusts seed production seed up or down according to adjustamt. (Max 10) - */ -/obj/item/seeds/proc/adjust_production(adjustamt) - if(yield != -1) - production = clamp(production + adjustamt, 1, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) - if(C) - C.value = production - -/** - * Adjusts seed potency up or down according to adjustamt. (Max 100) - */ -/obj/item/seeds/proc/adjust_potency(adjustamt) - if(potency != -1) - potency = clamp(potency + adjustamt, 0, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) - if(C) - C.value = potency - -/** - * Adjusts seed instability up or down according to adjustamt. (Max 100) - */ -/obj/item/seeds/proc/adjust_instability(adjustamt) - if(instability == -1) - return - instability = clamp(instability + adjustamt, 0, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/instability) - if(C) - C.value = instability - -/** - * Adjusts seed weed grwoth speed up or down according to adjustamt. (Max 10) - */ -/obj/item/seeds/proc/adjust_weed_rate(adjustamt) - weed_rate = clamp(weed_rate + adjustamt, 0, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) - if(C) - C.value = weed_rate - -/** - * Adjusts seed weed chance up or down according to adjustamt. (Max 67%) - */ -/obj/item/seeds/proc/adjust_weed_chance(adjustamt) - weed_chance = clamp(weed_chance + adjustamt, 0, 67) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) - if(C) - C.value = weed_chance - -//Directly setting stats - -/** - * Sets the plant's yield stat to the value of adjustamt. (Max 10) - */ -/obj/item/seeds/proc/set_yield(adjustamt) - if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable - yield = clamp(adjustamt, 0, 10) - - if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - yield = 1 // Mushrooms always have a minimum yield of 1. - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) - if(C) - C.value = yield - -/** - * Sets the plant's lifespan stat to the value of adjustamt. (Max 100) - */ -/obj/item/seeds/proc/set_lifespan(adjustamt) - lifespan = clamp(adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) - if(C) - C.value = lifespan - -/** - * Sets the plant's endurance stat to the value of adjustamt. (Max 100) - */ -/obj/item/seeds/proc/set_endurance(adjustamt) - endurance = clamp(adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) - if(C) - C.value = endurance - -/** - * Sets the plant's production stat to the value of adjustamt. (Max 10) - */ -/obj/item/seeds/proc/set_production(adjustamt) - if(yield != -1) - production = clamp(adjustamt, 1, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) - if(C) - C.value = production - -/** - * Sets the plant's potency stat to the value of adjustamt. (Max 100) - */ -/obj/item/seeds/proc/set_potency(adjustamt) - if(potency != -1) - potency = clamp(adjustamt, 0, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) - if(C) - C.value = potency - -/** - * Sets the plant's instability stat to the value of adjustamt. (Max 100) - */ -/obj/item/seeds/proc/set_instability(adjustamt) - if(instability == -1) - return - instability = clamp(adjustamt, 0, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/instability) - if(C) - C.value = instability - -/** - * Sets the plant's weed production rate to the value of adjustamt. (Max 10) - */ -/obj/item/seeds/proc/set_weed_rate(adjustamt) - weed_rate = clamp(adjustamt, 0, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) - if(C) - C.value = weed_rate - -/** - * Sets the plant's weed growth percentage to the value of adjustamt. (Max 67%) - */ -/obj/item/seeds/proc/set_weed_chance(adjustamt) - weed_chance = clamp(adjustamt, 0, 67) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) - if(C) - C.value = weed_chance - - -/obj/item/seeds/proc/get_analyzer_text() //in case seeds have something special to tell to the analyzer - var/text = "" - if(!get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && !get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) - text += "- Plant type: Normal plant\n" - if(get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) - text += "- Plant type: Weed. Can grow in nutrient-poor soil.\n" - if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - text += "- Plant type: Mushroom. Can grow in dry soil.\n" - if(get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) - text += "- Plant type: UNKNOWN \n" - if(potency != -1) - text += "- Potency: [potency]\n" - if(yield != -1) - text += "- Yield: [yield]\n" - text += "- Maturation speed: [maturation]\n" - if(yield != -1) - text += "- Production speed: [production]\n" - text += "- Endurance: [endurance]\n" - text += "- Lifespan: [lifespan]\n" - text += "- Instability: [instability]\n" - text += "- Weed Growth Rate: [weed_rate]\n" - text += "- Weed Vulnerability: [weed_chance]\n" - if(rarity) - text += "- Species Discovery Value: [rarity]\n" - var/all_traits = "" - for(var/datum/plant_gene/trait/traits in genes) - if(istype(traits, /datum/plant_gene/trait/plant_type)) - continue - all_traits += " [traits.get_name()]" - text += "- Plant Traits:[all_traits]\n" - text += "*---------*" - return text - -/obj/item/seeds/proc/on_chem_reaction(datum/reagents/S) //in case seeds have some special interaction with special chems - return - -/obj/item/seeds/attackby(obj/item/O, mob/user, params) - if (istype(O, /obj/item/plant_analyzer)) - to_chat(user, "*---------*\n This is \a [src].") - var/text - var/obj/item/plant_analyzer/P_analyzer = O - if(P_analyzer.scan_mode == PLANT_SCANMODE_STATS) - text = get_analyzer_text() - if(text) - to_chat(user, "[text]") - if(reagents_add && P_analyzer.scan_mode == PLANT_SCANMODE_CHEMICALS) - to_chat(user, "- Plant Reagents -") - to_chat(user, "*---------*") - for(var/datum/plant_gene/reagent/G in genes) - to_chat(user, "- [G.get_name()] -") - to_chat(user, "*---------*") - - - return - - if(istype(O, /obj/item/pen)) - var/choice = input("What would you like to change?") in list("Plant Name", "Seed Description", "Product Description", "Cancel") - if(!user.canUseTopic(src, BE_CLOSE)) - return - switch(choice) - if("Plant Name") - var/newplantname = reject_bad_text(stripped_input(user, "Write a new plant name:", name, plantname)) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (length(newplantname) > 20) - to_chat(user, "That name is too long!") - return - if(!newplantname) - to_chat(user, "That name is invalid.") - return - else - name = "[lowertext(newplantname)]" - plantname = newplantname - if("Seed Description") - var/newdesc = stripped_input(user, "Write a new description:", name, desc) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (length(newdesc) > 180) - to_chat(user, "That description is too long!") - return - if(!newdesc) - to_chat(user, "That description is invalid.") - return - else - desc = newdesc - if("Product Description") - if(product && !productdesc) - productdesc = initial(product.desc) - var/newproductdesc = stripped_input(user, "Write a new description:", name, productdesc) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (length(newproductdesc) > 180) - to_chat(user, "That description is too long!") - return - if(!newproductdesc) - to_chat(user, "That description is invalid.") - return - else - productdesc = newproductdesc - else - return - - ..() // Fallthrough to item/attackby() so that bags can pick seeds up - -/obj/item/seeds/proc/randomize_stats() - set_lifespan(rand(25, 60)) - set_endurance(rand(15, 35)) - set_production(rand(2, 10)) - set_yield(rand(1, 10)) - set_potency(rand(10, 35)) - set_weed_rate(rand(1, 10)) - set_weed_chance(rand(5, 100)) - maturation = rand(6, 12) - -/obj/item/seeds/proc/add_random_reagents(lower = 0, upper = 2) - var/amount_random_reagents = rand(lower, upper) - for(var/i in 1 to amount_random_reagents) - var/random_amount = rand(4, 15) * 0.01 // this must be multiplied by 0.01, otherwise, it will not properly associate - var/datum/plant_gene/reagent/R = new(get_random_reagent_id(), random_amount) - if(R.can_add(src)) - genes += R - else - qdel(R) - reagents_from_genes() - -/obj/item/seeds/proc/add_random_traits(lower = 0, upper = 2) - var/amount_random_traits = rand(lower, upper) - for(var/i in 1 to amount_random_traits) - var/random_trait = pick((subtypesof(/datum/plant_gene/trait)-typesof(/datum/plant_gene/trait/plant_type))) - var/datum/plant_gene/trait/T = new random_trait - if(T.can_add(src)) - genes += T - else - qdel(T) - -/obj/item/seeds/proc/add_random_plant_type(normal_plant_chance = 75) - if(prob(normal_plant_chance)) - var/random_plant_type = pick(subtypesof(/datum/plant_gene/trait/plant_type)) - var/datum/plant_gene/trait/plant_type/P = new random_plant_type - if(P.can_add(src)) - genes += P - else - qdel(P) - -/obj/item/seeds/proc/remove_random_reagents(lower = 0, upper = 2) - var/amount_random_reagents = rand(lower, upper) - for(var/i in 1 to amount_random_reagents) - var/datum/reagent/chemical = pick(reagents_add) - qdel(chemical) - -/** - * Creates a graft from this plant. - * - * Creates a new graft from this plant. - * Sets the grafts trait to this plants graftable trait. - * Gives the graft a reference to this plant. - * Copies all the relevant stats from this plant to the graft. - * Returns the created graft. - */ -/obj/item/seeds/proc/create_graft() - var/obj/item/graft/snip = new(loc, graft_gene) - snip.parent_seed = src - snip.parent_name = plantname - snip.name += "([plantname])" - - // Copy over stats so the graft can outlive its parent. - snip.lifespan = lifespan - snip.endurance = endurance - snip.production = production - snip.weed_rate = weed_rate - snip.weed_chance = weed_chance - snip.yield = yield - - return snip - -/** - * Applies a graft to this plant. - * - * Adds the graft trait to this plant if possible. - * Increases plant stats by 2/3 of the grafts stats to a maximum of 100 (10 for yield). - * Returns [TRUE] - * - * Arguments: - * - [snip][/obj/item/graft]: The graft being used applied to this plant. - */ -/obj/item/seeds/proc/apply_graft(obj/item/graft/snip) - var/datum/plant_gene/trait/new_trait = snip.stored_trait - if(new_trait?.can_add(src)) - genes += new_trait.Copy() - - // Adjust stats based on graft stats. - src.lifespan = round(clamp(max(src.lifespan, (src.lifespan +(2/3)*(snip.lifespan -src.lifespan) )),0,100)) - src.endurance = round(clamp(max(src.endurance, (src.endurance +(2/3)*(snip.endurance -src.endurance) )),0,100)) - src.production = round(clamp(max(src.production, (src.production +(2/3)*(snip.production -src.production) )),0,100)) - src.weed_rate = round(clamp(max(src.weed_rate, (src.weed_rate +(2/3)*(snip.weed_rate -src.weed_rate) )),0,100)) - src.weed_chance = round(clamp(max(src.weed_chance, (src.weed_chance+(2/3)*(snip.weed_chance-src.weed_chance) )),0,100)) - src.yield = round(clamp(max(src.yield, (src.yield +(2/3)*(snip.yield -src.yield) )),0,10 )) - - return TRUE +// ******************************************************** +// Here's all the seeds (plants) that can be used in hydro +// ******************************************************** + +/obj/item/seeds + icon = 'icons/obj/hydroponics/seeds.dmi' + icon_state = "seed" // Unknown plant seed - these shouldn't exist in-game. + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + /// Name of plant when planted. + var/plantname = "Plants" + /// A type path. The thing that is created when the plant is harvested. + var/obj/item/product + ///Describes the product on the product path. + var/productdesc + /// Used to update icons. Should match the name in the sprites unless all icon_* are overridden. + var/species = "" + ///the file that stores the sprites of the growing plant from this seed. + var/growing_icon = 'icons/obj/hydroponics/growing.dmi' + /// Used to override grow icon (default is "[species]-grow"). You can use one grow icon for multiple closely related plants with it. + var/icon_grow + /// Used to override dead icon (default is "[species]-dead"). You can use one dead icon for multiple closely related plants with it. + var/icon_dead + /// Used to override harvest icon (default is "[species]-harvest"). If null, plant will use [icon_grow][growthstages]. + var/icon_harvest + /// How long before the plant begins to take damage from age. + var/lifespan = 25 + /// Amount of health the plant has. + var/endurance = 15 + /// Used to determine which sprite to switch to when growing. + var/maturation = 6 + /// Changes the amount of time needed for a plant to become harvestable. + var/production = 6 + /// Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested. + var/yield = 3 + /// The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways. + var/potency = 10 + /// Amount of growth sprites the plant has. + var/growthstages = 6 + // Chance that a plant will mutate in each stage of it's life. + var/instability = 5 + /// How rare the plant is. Used for giving points to cargo when shipping off to CentCom. + var/rarity = 0 + /// The type of plants that this plant can mutate into. + var/list/mutatelist + /// Plant genes are stored here, see plant_genes.dm for more info. + var/list/genes = list() + /// A list of reagents to add to product. + var/list/reagents_add + // Format: "reagent_id" = potency multiplier + // Stronger reagents must always come first to avoid being displaced by weaker ones. + // Total amount of any reagent in plant is calculated by formula: max(round(potency * multiplier), 1) + ///If the chance below passes, then this many weeds sprout during growth + var/weed_rate = 1 + ///Percentage chance per tray update to grow weeds + var/weed_chance = 5 + ///Determines if the plant has had a graft removed or not. + var/grafted = FALSE + ///Type-path of trait to be applied when grafting a plant. + var/graft_gene + +/obj/item/seeds/Initialize(mapload, nogenes = 0) + . = ..() + pixel_x = rand(-8, 8) + pixel_y = rand(-8, 8) + + if(!icon_grow) + icon_grow = "[species]-grow" + + if(!icon_dead) + icon_dead = "[species]-dead" + + if(!icon_harvest && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && yield != -1) + icon_harvest = "[species]-harvest" + + if(!nogenes) // not used on Copy() + genes += new /datum/plant_gene/core/lifespan(lifespan) + genes += new /datum/plant_gene/core/endurance(endurance) + genes += new /datum/plant_gene/core/weed_rate(weed_rate) + genes += new /datum/plant_gene/core/weed_chance(weed_chance) + if(yield != -1) + genes += new /datum/plant_gene/core/yield(yield) + genes += new /datum/plant_gene/core/production(production) + if(potency != -1) + genes += new /datum/plant_gene/core/potency(potency) + genes += new /datum/plant_gene/core/instability(instability) + + for(var/p in genes) + if(ispath(p)) + genes -= p + genes += new p + + for(var/reag_id in reagents_add) + genes += new /datum/plant_gene/reagent(reag_id, reagents_add[reag_id]) + reagents_from_genes() //quality coding + +/obj/item/seeds/examine(mob/user) + . = ..() + . += "Use a pen on it to rename it or change its description." + if(reagents_add && user.can_see_reagents()) + . += "- Plant Reagents -" + for(var/datum/plant_gene/reagent/G in genes) + . += "- [G.get_name()] -" + +/obj/item/seeds/proc/Copy() + var/obj/item/seeds/S = new type(null, 1) + // Copy all the stats + S.lifespan = lifespan + S.endurance = endurance + S.maturation = maturation + S.production = production + S.yield = yield + S.potency = potency + S.instability = instability + S.weed_rate = weed_rate + S.weed_chance = weed_chance + S.name = name + S.plantname = plantname + S.desc = desc + S.productdesc = productdesc + S.genes = list() + for(var/g in genes) + var/datum/plant_gene/G = g + S.genes += G.Copy() + S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. + return S + +/obj/item/seeds/proc/get_gene(typepath) + return (locate(typepath) in genes) + +/obj/item/seeds/proc/reagents_from_genes() + reagents_add = list() + for(var/datum/plant_gene/reagent/R in genes) + reagents_add[R.reagent_id] = R.rate + +///This proc adds a mutability_flag to a gene +/obj/item/seeds/proc/set_mutability(typepath, mutability) + var/datum/plant_gene/g = get_gene(typepath) + if(g) + g.mutability_flags |= mutability + +///This proc removes a mutability_flag from a gene +/obj/item/seeds/proc/unset_mutability(typepath, mutability) + var/datum/plant_gene/g = get_gene(typepath) + if(g) + g.mutability_flags &= ~mutability + +/obj/item/seeds/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0, stabmut = 3) + adjust_lifespan(rand(-lifemut,lifemut)) + adjust_endurance(rand(-endmut,endmut)) + adjust_production(rand(-productmut,productmut)) + adjust_yield(rand(-yieldmut,yieldmut)) + adjust_potency(rand(-potmut,potmut)) + adjust_instability(rand(-stabmut,stabmut)) + adjust_weed_rate(rand(-wrmut, wrmut)) + adjust_weed_chance(rand(-wcmut, wcmut)) + if(prob(traitmut)) + if(prob(50)) + add_random_traits(1, 1) + else + add_random_reagents(1, 1) + + + +/obj/item/seeds/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. + if(istype(Proj, /obj/projectile/energy/florayield)) + var/rating = 1 + if(istype(loc, /obj/machinery/hydroponics)) + var/obj/machinery/hydroponics/H = loc + rating = H.rating + + if(yield == 0)//Oh god don't divide by zero you'll doom us all. + adjust_yield(1 * rating) + else if(prob(1/(yield * yield) * 100))//This formula gives you diminishing returns based on yield. 100% with 1 yield, decreasing to 25%, 11%, 6, 4, 2... + adjust_yield(1 * rating) + else + return ..() + + +// Harvest procs +/obj/item/seeds/proc/getYield() + var/return_yield = yield + + var/obj/machinery/hydroponics/parent = loc + if(istype(loc, /obj/machinery/hydroponics)) + if(parent.yieldmod == 0) + return_yield = min(return_yield, 1)//1 if above zero, 0 otherwise + else + return_yield *= (parent.yieldmod) + + return return_yield + + +/obj/item/seeds/proc/harvest(mob/user) + ///Reference to the tray/soil the seeds are planted in. + var/obj/machinery/hydroponics/parent = loc //for ease of access + ///Count used for creating the correct amount of results to the harvest. + var/t_amount = 0 + ///List of plants all harvested from the same batch. + var/list/result = list() + ///Tile of the harvester to deposit the growables. + var/output_loc = parent.Adjacent(user) ? user.loc : parent.loc //needed for TK + ///Name of the grown products. + var/product_name + ///The Number of products produced by the plant, typically the yield. Modified by Densified Chemicals. + var/product_count = getYield() + if(get_gene(/datum/plant_gene/trait/maxchem)) + product_count = clamp(round(product_count/2),0,5) + while(t_amount < product_count) + var/obj/item/reagent_containers/food/snacks/grown/t_prod = new product(output_loc, src) + if(parent.myseed.plantname != initial(parent.myseed.plantname)) + t_prod.name = lowertext(parent.myseed.plantname) + if(productdesc) + t_prod.desc = productdesc + t_prod.seed.name = parent.myseed.name + t_prod.seed.desc = parent.myseed.desc + t_prod.seed.plantname = parent.myseed.plantname + result.Add(t_prod) // User gets a consumable + if(!t_prod) + return + t_amount++ + product_name = parent.myseed.plantname + if(getYield() >= 1) + SSblackbox.record_feedback("tally", "food_harvested", getYield(), product_name) + parent.update_tray(user) + + return result + +/** + * This is where plant chemical products are handled. + * + * Individually, the formula for individual amounts of chemicals is Potency * the chemical production %, rounded to the fullest 1. + * Specific chem handling is also handled here, like bloodtype, food taste within nutriment, and the auto-distilling trait. + */ +/obj/item/seeds/proc/prepare_result(var/obj/item/T) + if(!T.reagents) + CRASH("[T] has no reagents.") + var/reagent_max = 0 + for(var/rid in reagents_add) + reagent_max += reagents_add[rid] + if(istype(T, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/grown_edible = T + for(var/rid in reagents_add) + var/reagent_overflow_mod = reagents_add[rid] + if(reagent_max > 1) + reagent_overflow_mod = (reagents_add[rid]/ reagent_max) + var/edible_vol = grown_edible.reagents ? grown_edible.reagents.maximum_volume : 0 + var/amount = max(1, round((edible_vol)*(potency/100) * reagent_overflow_mod, 1)) //the plant will always have at least 1u of each of the reagents in its reagent production traits + var/list/data + if(rid == /datum/reagent/blood) // Hack to make blood in plants always O- + data = list("blood_type" = "O-") + if(rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin) + data = grown_edible.tastes // apple tastes of apple. + //Handles the distillary trait, swaps nutriment and vitamins for that species brewable if it exists. + if(get_gene(/datum/plant_gene/trait/brewing) && grown_edible.distill_reagent) + T.reagents.add_reagent(grown_edible.distill_reagent, amount/2) + continue + T.reagents.add_reagent(rid, amount, data) + + +/// Setters procs /// + +/** + * Adjusts seed yield up or down according to adjustamt. (Max 10) + */ +/obj/item/seeds/proc/adjust_yield(adjustamt) + if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable + yield = clamp(yield + adjustamt, 0, 10) + + if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + yield = 1 // Mushrooms always have a minimum yield of 1. + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) + if(C) + C.value = yield + +/** + * Adjusts seed lifespan up or down according to adjustamt. (Max 100) + */ +/obj/item/seeds/proc/adjust_lifespan(adjustamt) + lifespan = clamp(lifespan + adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) + if(C) + C.value = lifespan + +/** + * Adjusts seed endurance up or down according to adjustamt. (Max 100) + */ +/obj/item/seeds/proc/adjust_endurance(adjustamt) + endurance = clamp(endurance + adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) + if(C) + C.value = endurance + +/** + * Adjusts seed production seed up or down according to adjustamt. (Max 10) + */ +/obj/item/seeds/proc/adjust_production(adjustamt) + if(yield != -1) + production = clamp(production + adjustamt, 1, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) + if(C) + C.value = production + +/** + * Adjusts seed potency up or down according to adjustamt. (Max 100) + */ +/obj/item/seeds/proc/adjust_potency(adjustamt) + if(potency != -1) + potency = clamp(potency + adjustamt, 0, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) + if(C) + C.value = potency + +/** + * Adjusts seed instability up or down according to adjustamt. (Max 100) + */ +/obj/item/seeds/proc/adjust_instability(adjustamt) + if(instability == -1) + return + instability = clamp(instability + adjustamt, 0, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/instability) + if(C) + C.value = instability + +/** + * Adjusts seed weed grwoth speed up or down according to adjustamt. (Max 10) + */ +/obj/item/seeds/proc/adjust_weed_rate(adjustamt) + weed_rate = clamp(weed_rate + adjustamt, 0, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) + if(C) + C.value = weed_rate + +/** + * Adjusts seed weed chance up or down according to adjustamt. (Max 67%) + */ +/obj/item/seeds/proc/adjust_weed_chance(adjustamt) + weed_chance = clamp(weed_chance + adjustamt, 0, 67) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) + if(C) + C.value = weed_chance + +//Directly setting stats + +/** + * Sets the plant's yield stat to the value of adjustamt. (Max 10) + */ +/obj/item/seeds/proc/set_yield(adjustamt) + if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable + yield = clamp(adjustamt, 0, 10) + + if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + yield = 1 // Mushrooms always have a minimum yield of 1. + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) + if(C) + C.value = yield + +/** + * Sets the plant's lifespan stat to the value of adjustamt. (Max 100) + */ +/obj/item/seeds/proc/set_lifespan(adjustamt) + lifespan = clamp(adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) + if(C) + C.value = lifespan + +/** + * Sets the plant's endurance stat to the value of adjustamt. (Max 100) + */ +/obj/item/seeds/proc/set_endurance(adjustamt) + endurance = clamp(adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) + if(C) + C.value = endurance + +/** + * Sets the plant's production stat to the value of adjustamt. (Max 10) + */ +/obj/item/seeds/proc/set_production(adjustamt) + if(yield != -1) + production = clamp(adjustamt, 1, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) + if(C) + C.value = production + +/** + * Sets the plant's potency stat to the value of adjustamt. (Max 100) + */ +/obj/item/seeds/proc/set_potency(adjustamt) + if(potency != -1) + potency = clamp(adjustamt, 0, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) + if(C) + C.value = potency + +/** + * Sets the plant's instability stat to the value of adjustamt. (Max 100) + */ +/obj/item/seeds/proc/set_instability(adjustamt) + if(instability == -1) + return + instability = clamp(adjustamt, 0, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/instability) + if(C) + C.value = instability + +/** + * Sets the plant's weed production rate to the value of adjustamt. (Max 10) + */ +/obj/item/seeds/proc/set_weed_rate(adjustamt) + weed_rate = clamp(adjustamt, 0, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) + if(C) + C.value = weed_rate + +/** + * Sets the plant's weed growth percentage to the value of adjustamt. (Max 67%) + */ +/obj/item/seeds/proc/set_weed_chance(adjustamt) + weed_chance = clamp(adjustamt, 0, 67) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) + if(C) + C.value = weed_chance + + +/obj/item/seeds/proc/get_analyzer_text() //in case seeds have something special to tell to the analyzer + var/text = "" + if(!get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && !get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) + text += "- Plant type: Normal plant\n" + if(get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) + text += "- Plant type: Weed. Can grow in nutrient-poor soil.\n" + if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + text += "- Plant type: Mushroom. Can grow in dry soil.\n" + if(get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) + text += "- Plant type: UNKNOWN \n" + if(potency != -1) + text += "- Potency: [potency]\n" + if(yield != -1) + text += "- Yield: [yield]\n" + text += "- Maturation speed: [maturation]\n" + if(yield != -1) + text += "- Production speed: [production]\n" + text += "- Endurance: [endurance]\n" + text += "- Lifespan: [lifespan]\n" + text += "- Instability: [instability]\n" + text += "- Weed Growth Rate: [weed_rate]\n" + text += "- Weed Vulnerability: [weed_chance]\n" + if(rarity) + text += "- Species Discovery Value: [rarity]\n" + var/all_traits = "" + for(var/datum/plant_gene/trait/traits in genes) + if(istype(traits, /datum/plant_gene/trait/plant_type)) + continue + all_traits += " [traits.get_name()]" + text += "- Plant Traits:[all_traits]\n" + text += "*---------*" + return text + +/obj/item/seeds/proc/on_chem_reaction(datum/reagents/S) //in case seeds have some special interaction with special chems + return + +/obj/item/seeds/attackby(obj/item/O, mob/user, params) + if (istype(O, /obj/item/plant_analyzer)) + to_chat(user, "*---------*\n This is \a [src].") + var/text + var/obj/item/plant_analyzer/P_analyzer = O + if(P_analyzer.scan_mode == PLANT_SCANMODE_STATS) + text = get_analyzer_text() + if(text) + to_chat(user, "[text]") + if(reagents_add && P_analyzer.scan_mode == PLANT_SCANMODE_CHEMICALS) + to_chat(user, "- Plant Reagents -") + to_chat(user, "*---------*") + for(var/datum/plant_gene/reagent/G in genes) + to_chat(user, "- [G.get_name()] -") + to_chat(user, "*---------*") + + + return + + if(istype(O, /obj/item/pen)) + var/choice = input("What would you like to change?") in list("Plant Name", "Seed Description", "Product Description", "Cancel") + if(!user.canUseTopic(src, BE_CLOSE)) + return + switch(choice) + if("Plant Name") + var/newplantname = reject_bad_text(stripped_input(user, "Write a new plant name:", name, plantname)) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (length(newplantname) > 20) + to_chat(user, "That name is too long!") + return + if(!newplantname) + to_chat(user, "That name is invalid.") + return + else + name = "[lowertext(newplantname)]" + plantname = newplantname + if("Seed Description") + var/newdesc = stripped_input(user, "Write a new description:", name, desc) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (length(newdesc) > 180) + to_chat(user, "That description is too long!") + return + if(!newdesc) + to_chat(user, "That description is invalid.") + return + else + desc = newdesc + if("Product Description") + if(product && !productdesc) + productdesc = initial(product.desc) + var/newproductdesc = stripped_input(user, "Write a new description:", name, productdesc) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (length(newproductdesc) > 180) + to_chat(user, "That description is too long!") + return + if(!newproductdesc) + to_chat(user, "That description is invalid.") + return + else + productdesc = newproductdesc + else + return + + ..() // Fallthrough to item/attackby() so that bags can pick seeds up + +/obj/item/seeds/proc/randomize_stats() + set_lifespan(rand(25, 60)) + set_endurance(rand(15, 35)) + set_production(rand(2, 10)) + set_yield(rand(1, 10)) + set_potency(rand(10, 35)) + set_weed_rate(rand(1, 10)) + set_weed_chance(rand(5, 100)) + maturation = rand(6, 12) + +/obj/item/seeds/proc/add_random_reagents(lower = 0, upper = 2) + var/amount_random_reagents = rand(lower, upper) + for(var/i in 1 to amount_random_reagents) + var/random_amount = rand(4, 15) * 0.01 // this must be multiplied by 0.01, otherwise, it will not properly associate + var/datum/plant_gene/reagent/R = new(get_random_reagent_id(), random_amount) + if(R.can_add(src)) + genes += R + else + qdel(R) + reagents_from_genes() + +/obj/item/seeds/proc/add_random_traits(lower = 0, upper = 2) + var/amount_random_traits = rand(lower, upper) + for(var/i in 1 to amount_random_traits) + var/random_trait = pick((subtypesof(/datum/plant_gene/trait)-typesof(/datum/plant_gene/trait/plant_type))) + var/datum/plant_gene/trait/T = new random_trait + if(T.can_add(src)) + genes += T + else + qdel(T) + +/obj/item/seeds/proc/add_random_plant_type(normal_plant_chance = 75) + if(prob(normal_plant_chance)) + var/random_plant_type = pick(subtypesof(/datum/plant_gene/trait/plant_type)) + var/datum/plant_gene/trait/plant_type/P = new random_plant_type + if(P.can_add(src)) + genes += P + else + qdel(P) + +/obj/item/seeds/proc/remove_random_reagents(lower = 0, upper = 2) + var/amount_random_reagents = rand(lower, upper) + for(var/i in 1 to amount_random_reagents) + var/datum/reagent/chemical = pick(reagents_add) + qdel(chemical) + +/** + * Creates a graft from this plant. + * + * Creates a new graft from this plant. + * Sets the grafts trait to this plants graftable trait. + * Gives the graft a reference to this plant. + * Copies all the relevant stats from this plant to the graft. + * Returns the created graft. + */ +/obj/item/seeds/proc/create_graft() + var/obj/item/graft/snip = new(loc, graft_gene) + snip.parent_seed = src + snip.parent_name = plantname + snip.name += "([plantname])" + + // Copy over stats so the graft can outlive its parent. + snip.lifespan = lifespan + snip.endurance = endurance + snip.production = production + snip.weed_rate = weed_rate + snip.weed_chance = weed_chance + snip.yield = yield + + return snip + +/** + * Applies a graft to this plant. + * + * Adds the graft trait to this plant if possible. + * Increases plant stats by 2/3 of the grafts stats to a maximum of 100 (10 for yield). + * Returns [TRUE] + * + * Arguments: + * - [snip][/obj/item/graft]: The graft being used applied to this plant. + */ +/obj/item/seeds/proc/apply_graft(obj/item/graft/snip) + var/datum/plant_gene/trait/new_trait = snip.stored_trait + if(new_trait?.can_add(src)) + genes += new_trait.Copy() + + // Adjust stats based on graft stats. + src.lifespan = round(clamp(max(src.lifespan, (src.lifespan +(2/3)*(snip.lifespan -src.lifespan) )),0,100)) + src.endurance = round(clamp(max(src.endurance, (src.endurance +(2/3)*(snip.endurance -src.endurance) )),0,100)) + src.production = round(clamp(max(src.production, (src.production +(2/3)*(snip.production -src.production) )),0,100)) + src.weed_rate = round(clamp(max(src.weed_rate, (src.weed_rate +(2/3)*(snip.weed_rate -src.weed_rate) )),0,100)) + src.weed_chance = round(clamp(max(src.weed_chance, (src.weed_chance+(2/3)*(snip.weed_chance-src.weed_chance) )),0,100)) + src.yield = round(clamp(max(src.yield, (src.yield +(2/3)*(snip.yield -src.yield) )),0,10 )) + + return TRUE diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm index dad430b96b4..d233257f74f 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -1,381 +1,381 @@ - -//returns TRUE if this mob has sufficient access to use this object -/obj/proc/allowed(mob/M) - //check if it doesn't require any access at all - if(src.check_access(null)) - return TRUE - if(issilicon(M)) - if(ispAI(M)) - return FALSE - return TRUE //AI can do whatever it wants - if(IsAdminGhost(M)) - //Access can't stop the abuse - return TRUE - else if(istype(M) && SEND_SIGNAL(M, COMSIG_MOB_ALLOWED, src)) - return TRUE - else if(ishuman(M)) - var/mob/living/carbon/human/H = M - //if they are holding or wearing a card that has access, that works - if(check_access(H.get_active_held_item()) || src.check_access(H.wear_id)) - return TRUE - else if(ismonkey(M) || isalienadult(M)) - var/mob/living/carbon/george = M - //they can only hold things :( - if(check_access(george.get_active_held_item())) - return TRUE - else if(isanimal(M)) - var/mob/living/simple_animal/A = M - if(check_access(A.get_active_held_item()) || check_access(A.access_card)) - return TRUE - return FALSE - -/obj/item/proc/GetAccess() - return list() - -/obj/item/proc/GetID() - return null - -/obj/item/proc/RemoveID() - return null - -/obj/item/proc/InsertID() - return FALSE - -/obj/proc/text2access(access_text) - . = list() - if(!access_text) - return - var/list/split = splittext(access_text,";") - for(var/x in split) - var/n = text2num(x) - if(n) - . += n - -//Call this before using req_access or req_one_access directly -/obj/proc/gen_access() - //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. - if(!req_access) - req_access = list() - for(var/a in text2access(req_access_txt)) - req_access += a - if(!req_one_access) - req_one_access = list() - for(var/b in text2access(req_one_access_txt)) - req_one_access += b - -// Check if an item has access to this object -/obj/proc/check_access(obj/item/I) - return check_access_list(I ? I.GetAccess() : null) - -/obj/proc/check_access_list(list/access_list) - gen_access() - - if(!islist(req_access)) //something's very wrong - return TRUE - - if(!req_access.len && !length(req_one_access)) - return TRUE - - if(!length(access_list) || !islist(access_list)) - return FALSE - - for(var/req in req_access) - if(!(req in access_list)) //doesn't have this access - return FALSE - - if(length(req_one_access)) - for(var/req in req_one_access) - if(req in access_list) //has an access from the single access list - return TRUE - return FALSE - return TRUE - -/obj/proc/check_access_ntnet(datum/netdata/data) - return check_access_list(data.passkey) - -/proc/get_centcom_access(job) - switch(job) - if("VIP Guest") - return list(ACCESS_CENT_GENERAL) - if("Custodian") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("Thunderdome Overseer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) - if("CentCom Official") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("CentCom Intern") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("CentCom Head Intern") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("Medical Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL) - if("Death Commando") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("Research Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_TELEPORTER, ACCESS_CENT_STORAGE) - if("Special Ops Officer") - return get_all_centcom_access() - if("Admiral") - return get_all_centcom_access() - if("CentCom Commander") - return get_all_centcom_access() - if("Emergency Response Team Commander") - return get_ert_access("commander") - if("Security Response Officer") - return get_ert_access("sec") - if("Engineer Response Officer") - return get_ert_access("eng") - if("Medical Response Officer") - return get_ert_access("med") - if("CentCom Bartender") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_BAR) - -/proc/get_all_accesses() - return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, - ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, - ACCESS_RND, ACCESS_TOXINS, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, - ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, - ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, - ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, - ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, - ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_QM, ACCESS_SURGERY, ACCESS_PSYCHOLOGY, - ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, ACCESS_WEAPONS, - ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, - ACCESS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_PHARMACY, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK, ACCESS_TOXINS_STORAGE) - -/proc/get_all_centcom_access() - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE, ACCESS_CENT_TELEPORTER, ACCESS_CENT_CAPTAIN) - -/proc/get_ert_access(class) - switch(class) - if("commander") - return get_all_centcom_access() - if("sec") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING) - if("eng") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("med") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING) - -/proc/get_all_syndicate_access() - return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - -/proc/get_region_accesses(code) - switch(code) - if(0) - return get_all_accesses() - if(1) //station general - return list(ACCESS_KITCHEN,ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER) - if(2) //security - return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_HOS) - if(3) //medbay - return list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_MECH_MEDICAL, ACCESS_CMO, ACCESS_PHARMACY, ACCESS_PSYCHOLOGY) - if(4) //research - return list(ACCESS_RESEARCH, ACCESS_RND, ACCESS_TOXINS, ACCESS_TOXINS_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) - if(5) //engineering and maintenance - return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MECH_ENGINE, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_CE) - if(6) //supply - return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM, ACCESS_VAULT) - if(7) //command - return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_VAULT) - -/proc/get_region_accesses_name(code) - switch(code) - if(0) - return "All" - if(1) //station general - return "General" - if(2) //security - return "Security" - if(3) //medbay - return "Medbay" - if(4) //research - return "Research" - if(5) //engineering and maintenance - return "Engineering" - if(6) //supply - return "Supply" - if(7) //command - return "Command" - -/proc/get_access_desc(A) - switch(A) - if(ACCESS_CARGO) - return "Cargo Bay" - if(ACCESS_SECURITY) - return "Security" - if(ACCESS_BRIG) - return "Holding Cells" - if(ACCESS_COURT) - return "Courtroom" - if(ACCESS_FORENSICS_LOCKERS) - return "Forensics" - if(ACCESS_MEDICAL) - return "Medical" - if(ACCESS_GENETICS) - return "Genetics Lab" - if(ACCESS_MORGUE) - return "Morgue" - if(ACCESS_RND) - return "R&D Lab" - if(ACCESS_TOXINS) - return "Toxins Lab" - if(ACCESS_TOXINS_STORAGE) - return "Toxins Storage" - if(ACCESS_CHEMISTRY) - return "Chemistry Lab" - if(ACCESS_RD) - return "RD Office" - if(ACCESS_BAR) - return "Bar" - if(ACCESS_JANITOR) - return "Custodial Closet" - if(ACCESS_ENGINE) - return "Engineering" - if(ACCESS_ENGINE_EQUIP) - return "Power and Engineering Equipment" - if(ACCESS_MAINT_TUNNELS) - return "Maintenance" - if(ACCESS_EXTERNAL_AIRLOCKS) - return "External Airlocks" - if(ACCESS_CHANGE_IDS) - return "ID Console" - if(ACCESS_AI_UPLOAD) - return "AI Chambers" - if(ACCESS_TELEPORTER) - return "Teleporter" - if(ACCESS_EVA) - return "EVA" - if(ACCESS_HEADS) - return "Bridge" - if(ACCESS_CAPTAIN) - return "Captain" - if(ACCESS_ALL_PERSONAL_LOCKERS) - return "Personal Lockers" - if(ACCESS_CHAPEL_OFFICE) - return "Chapel Office" - if(ACCESS_TECH_STORAGE) - return "Technical Storage" - if(ACCESS_ATMOSPHERICS) - return "Atmospherics" - if(ACCESS_CREMATORIUM) - return "Crematorium" - if(ACCESS_ARMORY) - return "Armory" - if(ACCESS_CONSTRUCTION) - return "Construction" - if(ACCESS_KITCHEN) - return "Kitchen" - if(ACCESS_HYDROPONICS) - return "Hydroponics" - if(ACCESS_LIBRARY) - return "Library" - if(ACCESS_LAWYER) - return "Law Office" - if(ACCESS_ROBOTICS) - return "Robotics" - if(ACCESS_VIROLOGY) - return "Virology" - if(ACCESS_PSYCHOLOGY) - return "Psychology" - if(ACCESS_CMO) - return "CMO Office" - if(ACCESS_QM) - return "Quartermaster" - if(ACCESS_SURGERY) - return "Surgery" - if(ACCESS_THEATRE) - return "Theatre" - if(ACCESS_RESEARCH) - return "Science" - if(ACCESS_MINING) - return "Mining" - if(ACCESS_MAILSORTING) - return "Cargo Office" - if(ACCESS_VAULT) - return "Main Vault" - if(ACCESS_MINING_STATION) - return "Mining EVA" - if(ACCESS_XENOBIOLOGY) - return "Xenobiology Lab" - if(ACCESS_HOP) - return "HoP Office" - if(ACCESS_HOS) - return "HoS Office" - if(ACCESS_CE) - return "CE Office" - if(ACCESS_PHARMACY) - return "Pharmacy" - if(ACCESS_RC_ANNOUNCE) - return "RC Announcements" - if(ACCESS_KEYCARD_AUTH) - return "Keycode Auth." - if(ACCESS_TCOMSAT) - return "Telecommunications" - if(ACCESS_GATEWAY) - return "Gateway" - if(ACCESS_SEC_DOORS) - return "Brig" - if(ACCESS_MINERAL_STOREROOM) - return "Mineral Storage" - if(ACCESS_MINISAT) - return "AI Satellite" - if(ACCESS_WEAPONS) - return "Weapon Permit" - if(ACCESS_NETWORK) - return "Network Access" - if(ACCESS_MECH_MINING) - return "Mining Mech Access" - if(ACCESS_MECH_MEDICAL) - return "Medical Mech Access" - if(ACCESS_MECH_SECURITY) - return "Security Mech Access" - if(ACCESS_MECH_SCIENCE) - return "Science Mech Access" - if(ACCESS_MECH_ENGINE) - return "Engineering Mech Access" - -/proc/get_centcom_access_desc(A) - switch(A) - if(ACCESS_CENT_GENERAL) - return "Code Grey" - if(ACCESS_CENT_THUNDER) - return "Code Yellow" - if(ACCESS_CENT_STORAGE) - return "Code Orange" - if(ACCESS_CENT_LIVING) - return "Code Green" - if(ACCESS_CENT_MEDICAL) - return "Code White" - if(ACCESS_CENT_TELEPORTER) - return "Code Blue" - if(ACCESS_CENT_SPECOPS) - return "Code Black" - if(ACCESS_CENT_CAPTAIN) - return "Code Gold" - if(ACCESS_CENT_BAR) - return "Code Scotch" - -/proc/get_all_jobs() - return list("Assistant", "Captain", "Head of Personnel", "Bartender", "Cook", "Botanist", "Quartermaster", "Cargo Technician", - "Shaft Miner", "Clown", "Mime", "Janitor", "Curator", "Lawyer", "Chaplain", "Chief Engineer", "Station Engineer", - "Atmospheric Technician", "Chief Medical Officer", "Medical Doctor", "Paramedic", "Chemist", "Geneticist", "Virologist", "Psychologist", - "Research Director", "Scientist", "Roboticist", "Head of Security", "Warden", "Detective", "Security Officer", "Prisoner") - -/proc/get_all_job_icons() //For all existing HUD icons - return get_all_jobs() + list("Emergency Response Team Commander", "Security Response Officer", "Engineering Response Officer", "Medical Response Officer", "Entertainment Response Officer", "Religious Response Officer", "Janitorial Response Officer", "Death Commando") - -/proc/get_all_centcom_jobs() - return list("Central Command","VIP Guest","Custodian","Thunderdome Overseer","CentCom Official","Medical Officer","Research Officer","Special Ops Officer","Admiral","CentCom Commander","CentCom Bartender","Private Security Force") - -/obj/item/proc/GetJobName() //Used in secHUD icon generation - var/obj/item/card/id/I = GetID() - if(!I) - return - var/jobName = I.assignment - if(jobName in get_all_job_icons()) //Check if the job has a hud icon - return jobName - if(jobName in get_all_centcom_jobs()) //Return with the NT logo if it is a CentCom job - return "CentCom" - return "Unknown" //Return unknown if none of the above apply + +//returns TRUE if this mob has sufficient access to use this object +/obj/proc/allowed(mob/M) + //check if it doesn't require any access at all + if(src.check_access(null)) + return TRUE + if(issilicon(M)) + if(ispAI(M)) + return FALSE + return TRUE //AI can do whatever it wants + if(IsAdminGhost(M)) + //Access can't stop the abuse + return TRUE + else if(istype(M) && SEND_SIGNAL(M, COMSIG_MOB_ALLOWED, src)) + return TRUE + else if(ishuman(M)) + var/mob/living/carbon/human/H = M + //if they are holding or wearing a card that has access, that works + if(check_access(H.get_active_held_item()) || src.check_access(H.wear_id)) + return TRUE + else if(ismonkey(M) || isalienadult(M)) + var/mob/living/carbon/george = M + //they can only hold things :( + if(check_access(george.get_active_held_item())) + return TRUE + else if(isanimal(M)) + var/mob/living/simple_animal/A = M + if(check_access(A.get_active_held_item()) || check_access(A.access_card)) + return TRUE + return FALSE + +/obj/item/proc/GetAccess() + return list() + +/obj/item/proc/GetID() + return null + +/obj/item/proc/RemoveID() + return null + +/obj/item/proc/InsertID() + return FALSE + +/obj/proc/text2access(access_text) + . = list() + if(!access_text) + return + var/list/split = splittext(access_text,";") + for(var/x in split) + var/n = text2num(x) + if(n) + . += n + +//Call this before using req_access or req_one_access directly +/obj/proc/gen_access() + //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. + if(!req_access) + req_access = list() + for(var/a in text2access(req_access_txt)) + req_access += a + if(!req_one_access) + req_one_access = list() + for(var/b in text2access(req_one_access_txt)) + req_one_access += b + +// Check if an item has access to this object +/obj/proc/check_access(obj/item/I) + return check_access_list(I ? I.GetAccess() : null) + +/obj/proc/check_access_list(list/access_list) + gen_access() + + if(!islist(req_access)) //something's very wrong + return TRUE + + if(!req_access.len && !length(req_one_access)) + return TRUE + + if(!length(access_list) || !islist(access_list)) + return FALSE + + for(var/req in req_access) + if(!(req in access_list)) //doesn't have this access + return FALSE + + if(length(req_one_access)) + for(var/req in req_one_access) + if(req in access_list) //has an access from the single access list + return TRUE + return FALSE + return TRUE + +/obj/proc/check_access_ntnet(datum/netdata/data) + return check_access_list(data.passkey) + +/proc/get_centcom_access(job) + switch(job) + if("VIP Guest") + return list(ACCESS_CENT_GENERAL) + if("Custodian") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("Thunderdome Overseer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) + if("CentCom Official") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("CentCom Intern") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("CentCom Head Intern") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("Medical Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL) + if("Death Commando") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("Research Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_TELEPORTER, ACCESS_CENT_STORAGE) + if("Special Ops Officer") + return get_all_centcom_access() + if("Admiral") + return get_all_centcom_access() + if("CentCom Commander") + return get_all_centcom_access() + if("Emergency Response Team Commander") + return get_ert_access("commander") + if("Security Response Officer") + return get_ert_access("sec") + if("Engineer Response Officer") + return get_ert_access("eng") + if("Medical Response Officer") + return get_ert_access("med") + if("CentCom Bartender") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_BAR) + +/proc/get_all_accesses() + return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, + ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, + ACCESS_RND, ACCESS_TOXINS, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, + ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, + ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, + ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, + ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_QM, ACCESS_SURGERY, ACCESS_PSYCHOLOGY, + ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, ACCESS_WEAPONS, + ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, + ACCESS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_PHARMACY, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK, ACCESS_TOXINS_STORAGE) + +/proc/get_all_centcom_access() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE, ACCESS_CENT_TELEPORTER, ACCESS_CENT_CAPTAIN) + +/proc/get_ert_access(class) + switch(class) + if("commander") + return get_all_centcom_access() + if("sec") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING) + if("eng") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("med") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING) + +/proc/get_all_syndicate_access() + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + +/proc/get_region_accesses(code) + switch(code) + if(0) + return get_all_accesses() + if(1) //station general + return list(ACCESS_KITCHEN,ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER) + if(2) //security + return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_HOS) + if(3) //medbay + return list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_MECH_MEDICAL, ACCESS_CMO, ACCESS_PHARMACY, ACCESS_PSYCHOLOGY) + if(4) //research + return list(ACCESS_RESEARCH, ACCESS_RND, ACCESS_TOXINS, ACCESS_TOXINS_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) + if(5) //engineering and maintenance + return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MECH_ENGINE, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_CE) + if(6) //supply + return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM, ACCESS_VAULT) + if(7) //command + return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_VAULT) + +/proc/get_region_accesses_name(code) + switch(code) + if(0) + return "All" + if(1) //station general + return "General" + if(2) //security + return "Security" + if(3) //medbay + return "Medbay" + if(4) //research + return "Research" + if(5) //engineering and maintenance + return "Engineering" + if(6) //supply + return "Supply" + if(7) //command + return "Command" + +/proc/get_access_desc(A) + switch(A) + if(ACCESS_CARGO) + return "Cargo Bay" + if(ACCESS_SECURITY) + return "Security" + if(ACCESS_BRIG) + return "Holding Cells" + if(ACCESS_COURT) + return "Courtroom" + if(ACCESS_FORENSICS_LOCKERS) + return "Forensics" + if(ACCESS_MEDICAL) + return "Medical" + if(ACCESS_GENETICS) + return "Genetics Lab" + if(ACCESS_MORGUE) + return "Morgue" + if(ACCESS_RND) + return "R&D Lab" + if(ACCESS_TOXINS) + return "Toxins Lab" + if(ACCESS_TOXINS_STORAGE) + return "Toxins Storage" + if(ACCESS_CHEMISTRY) + return "Chemistry Lab" + if(ACCESS_RD) + return "RD Office" + if(ACCESS_BAR) + return "Bar" + if(ACCESS_JANITOR) + return "Custodial Closet" + if(ACCESS_ENGINE) + return "Engineering" + if(ACCESS_ENGINE_EQUIP) + return "Power and Engineering Equipment" + if(ACCESS_MAINT_TUNNELS) + return "Maintenance" + if(ACCESS_EXTERNAL_AIRLOCKS) + return "External Airlocks" + if(ACCESS_CHANGE_IDS) + return "ID Console" + if(ACCESS_AI_UPLOAD) + return "AI Chambers" + if(ACCESS_TELEPORTER) + return "Teleporter" + if(ACCESS_EVA) + return "EVA" + if(ACCESS_HEADS) + return "Bridge" + if(ACCESS_CAPTAIN) + return "Captain" + if(ACCESS_ALL_PERSONAL_LOCKERS) + return "Personal Lockers" + if(ACCESS_CHAPEL_OFFICE) + return "Chapel Office" + if(ACCESS_TECH_STORAGE) + return "Technical Storage" + if(ACCESS_ATMOSPHERICS) + return "Atmospherics" + if(ACCESS_CREMATORIUM) + return "Crematorium" + if(ACCESS_ARMORY) + return "Armory" + if(ACCESS_CONSTRUCTION) + return "Construction" + if(ACCESS_KITCHEN) + return "Kitchen" + if(ACCESS_HYDROPONICS) + return "Hydroponics" + if(ACCESS_LIBRARY) + return "Library" + if(ACCESS_LAWYER) + return "Law Office" + if(ACCESS_ROBOTICS) + return "Robotics" + if(ACCESS_VIROLOGY) + return "Virology" + if(ACCESS_PSYCHOLOGY) + return "Psychology" + if(ACCESS_CMO) + return "CMO Office" + if(ACCESS_QM) + return "Quartermaster" + if(ACCESS_SURGERY) + return "Surgery" + if(ACCESS_THEATRE) + return "Theatre" + if(ACCESS_RESEARCH) + return "Science" + if(ACCESS_MINING) + return "Mining" + if(ACCESS_MAILSORTING) + return "Cargo Office" + if(ACCESS_VAULT) + return "Main Vault" + if(ACCESS_MINING_STATION) + return "Mining EVA" + if(ACCESS_XENOBIOLOGY) + return "Xenobiology Lab" + if(ACCESS_HOP) + return "HoP Office" + if(ACCESS_HOS) + return "HoS Office" + if(ACCESS_CE) + return "CE Office" + if(ACCESS_PHARMACY) + return "Pharmacy" + if(ACCESS_RC_ANNOUNCE) + return "RC Announcements" + if(ACCESS_KEYCARD_AUTH) + return "Keycode Auth." + if(ACCESS_TCOMSAT) + return "Telecommunications" + if(ACCESS_GATEWAY) + return "Gateway" + if(ACCESS_SEC_DOORS) + return "Brig" + if(ACCESS_MINERAL_STOREROOM) + return "Mineral Storage" + if(ACCESS_MINISAT) + return "AI Satellite" + if(ACCESS_WEAPONS) + return "Weapon Permit" + if(ACCESS_NETWORK) + return "Network Access" + if(ACCESS_MECH_MINING) + return "Mining Mech Access" + if(ACCESS_MECH_MEDICAL) + return "Medical Mech Access" + if(ACCESS_MECH_SECURITY) + return "Security Mech Access" + if(ACCESS_MECH_SCIENCE) + return "Science Mech Access" + if(ACCESS_MECH_ENGINE) + return "Engineering Mech Access" + +/proc/get_centcom_access_desc(A) + switch(A) + if(ACCESS_CENT_GENERAL) + return "Code Grey" + if(ACCESS_CENT_THUNDER) + return "Code Yellow" + if(ACCESS_CENT_STORAGE) + return "Code Orange" + if(ACCESS_CENT_LIVING) + return "Code Green" + if(ACCESS_CENT_MEDICAL) + return "Code White" + if(ACCESS_CENT_TELEPORTER) + return "Code Blue" + if(ACCESS_CENT_SPECOPS) + return "Code Black" + if(ACCESS_CENT_CAPTAIN) + return "Code Gold" + if(ACCESS_CENT_BAR) + return "Code Scotch" + +/proc/get_all_jobs() + return list("Assistant", "Captain", "Head of Personnel", "Bartender", "Cook", "Botanist", "Quartermaster", "Cargo Technician", + "Shaft Miner", "Clown", "Mime", "Janitor", "Curator", "Lawyer", "Chaplain", "Chief Engineer", "Station Engineer", + "Atmospheric Technician", "Chief Medical Officer", "Medical Doctor", "Paramedic", "Chemist", "Geneticist", "Virologist", "Psychologist", + "Research Director", "Scientist", "Roboticist", "Head of Security", "Warden", "Detective", "Security Officer", "Prisoner") + +/proc/get_all_job_icons() //For all existing HUD icons + return get_all_jobs() + list("Emergency Response Team Commander", "Security Response Officer", "Engineering Response Officer", "Medical Response Officer", "Entertainment Response Officer", "Religious Response Officer", "Janitorial Response Officer", "Death Commando") + +/proc/get_all_centcom_jobs() + return list("Central Command","VIP Guest","Custodian","Thunderdome Overseer","CentCom Official","Medical Officer","Research Officer","Special Ops Officer","Admiral","CentCom Commander","CentCom Bartender","Private Security Force") + +/obj/item/proc/GetJobName() //Used in secHUD icon generation + var/obj/item/card/id/I = GetID() + if(!I) + return + var/jobName = I.assignment + if(jobName in get_all_job_icons()) //Check if the job has a hud icon + return jobName + if(jobName in get_all_centcom_jobs()) //Return with the NT logo if it is a CentCom job + return "CentCom" + return "Unknown" //Return unknown if none of the above apply diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index 634664de8ce..a314e780aa5 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -1,43 +1,43 @@ -/* -Assistant -*/ -/datum/job/assistant - title = "Assistant" - flag = ASSISTANT - department_flag = CIVILIAN - faction = "Station" - total_positions = 5 - spawn_positions = 5 - supervisors = "absolutely everyone" - selection_color = "#dddddd" - access = list() //See /datum/job/assistant/get_access() - minimal_access = list() //See /datum/job/assistant/get_access() - outfit = /datum/outfit/job/assistant - antag_rep = 7 - paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it. - paycheck_department = ACCOUNT_CIV - display_order = JOB_DISPLAY_ORDER_ASSISTANT - -/datum/job/assistant/get_access() - if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set - . = ..() - . |= list(ACCESS_MAINT_TUNNELS) - else - return ..() - -/datum/outfit/job/assistant - name = "Assistant" - jobtype = /datum/job/assistant - -/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H) - ..() - if (CONFIG_GET(flag/grey_assistants)) - if(H.jumpsuit_style == PREF_SUIT) - uniform = /obj/item/clothing/under/color/grey - else - uniform = /obj/item/clothing/under/color/jumpskirt/grey - else - if(H.jumpsuit_style == PREF_SUIT) - uniform = /obj/item/clothing/under/color/random - else - uniform = /obj/item/clothing/under/color/jumpskirt/random +/* +Assistant +*/ +/datum/job/assistant + title = "Assistant" + flag = ASSISTANT + department_flag = CIVILIAN + faction = "Station" + total_positions = 5 + spawn_positions = 5 + supervisors = "absolutely everyone" + selection_color = "#dddddd" + access = list() //See /datum/job/assistant/get_access() + minimal_access = list() //See /datum/job/assistant/get_access() + outfit = /datum/outfit/job/assistant + antag_rep = 7 + paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it. + paycheck_department = ACCOUNT_CIV + display_order = JOB_DISPLAY_ORDER_ASSISTANT + +/datum/job/assistant/get_access() + if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set + . = ..() + . |= list(ACCESS_MAINT_TUNNELS) + else + return ..() + +/datum/outfit/job/assistant + name = "Assistant" + jobtype = /datum/job/assistant + +/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H) + ..() + if (CONFIG_GET(flag/grey_assistants)) + if(H.jumpsuit_style == PREF_SUIT) + uniform = /obj/item/clothing/under/color/grey + else + uniform = /obj/item/clothing/under/color/jumpskirt/grey + else + if(H.jumpsuit_style == PREF_SUIT) + uniform = /obj/item/clothing/under/color/random + else + uniform = /obj/item/clothing/under/color/jumpskirt/random diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 99a0be0cd2d..24a11c31ce2 100755 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -1,65 +1,65 @@ -/datum/job/captain - title = "Captain" - flag = CAPTAIN - auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY - department_head = list("CentCom") - department_flag = ENGSEC - faction = "Station" - total_positions = 1 - spawn_positions = 1 - supervisors = "Nanotrasen officials and Space law" - selection_color = "#ccccff" - req_admin_notify = 1 - minimal_player_age = 14 - exp_requirements = 180 - exp_type = EXP_TYPE_CREW - exp_type_department = EXP_TYPE_COMMAND - - outfit = /datum/outfit/job/captain - - access = list() //See get_access() - minimal_access = list() //See get_access() - paycheck = PAYCHECK_COMMAND - paycheck_department = ACCOUNT_SEC - - mind_traits = list(TRAIT_DISK_VERIFIER) - - display_order = JOB_DISPLAY_ORDER_CAPTAIN - -/datum/job/captain/get_access() - return get_all_accesses() - -/datum/job/captain/announce(mob/living/carbon/human/H) - ..() - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.real_name] on deck!")) - -/datum/outfit/job/captain - name = "Captain" - jobtype = /datum/job/captain - - id = /obj/item/card/id/gold - belt = /obj/item/pda/captain - glasses = /obj/item/clothing/glasses/sunglasses - ears = /obj/item/radio/headset/heads/captain/alt - gloves = /obj/item/clothing/gloves/color/captain - uniform = /obj/item/clothing/under/rank/captain - suit = /obj/item/clothing/suit/armor/vest/capcarapace - shoes = /obj/item/clothing/shoes/sneakers/brown - head = /obj/item/clothing/head/caphat - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1) - - backpack = /obj/item/storage/backpack/captain - satchel = /obj/item/storage/backpack/satchel/cap - duffelbag = /obj/item/storage/backpack/duffelbag/captain - - implants = list(/obj/item/implant/mindshield) - accessory = /obj/item/clothing/accessory/medal/gold/captain - - chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain) - -/datum/outfit/job/captain/hardsuit - name = "Captain (Hardsuit)" - - mask = /obj/item/clothing/mask/gas/atmos/captain - suit = /obj/item/clothing/suit/space/hardsuit/swat/captain - suit_store = /obj/item/tank/internals/oxygen +/datum/job/captain + title = "Captain" + flag = CAPTAIN + auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY + department_head = list("CentCom") + department_flag = ENGSEC + faction = "Station" + total_positions = 1 + spawn_positions = 1 + supervisors = "Nanotrasen officials and Space law" + selection_color = "#ccccff" + req_admin_notify = 1 + minimal_player_age = 14 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + exp_type_department = EXP_TYPE_COMMAND + + outfit = /datum/outfit/job/captain + + access = list() //See get_access() + minimal_access = list() //See get_access() + paycheck = PAYCHECK_COMMAND + paycheck_department = ACCOUNT_SEC + + mind_traits = list(TRAIT_DISK_VERIFIER) + + display_order = JOB_DISPLAY_ORDER_CAPTAIN + +/datum/job/captain/get_access() + return get_all_accesses() + +/datum/job/captain/announce(mob/living/carbon/human/H) + ..() + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.real_name] on deck!")) + +/datum/outfit/job/captain + name = "Captain" + jobtype = /datum/job/captain + + id = /obj/item/card/id/gold + belt = /obj/item/pda/captain + glasses = /obj/item/clothing/glasses/sunglasses + ears = /obj/item/radio/headset/heads/captain/alt + gloves = /obj/item/clothing/gloves/color/captain + uniform = /obj/item/clothing/under/rank/captain + suit = /obj/item/clothing/suit/armor/vest/capcarapace + shoes = /obj/item/clothing/shoes/sneakers/brown + head = /obj/item/clothing/head/caphat + backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1) + + backpack = /obj/item/storage/backpack/captain + satchel = /obj/item/storage/backpack/satchel/cap + duffelbag = /obj/item/storage/backpack/duffelbag/captain + + implants = list(/obj/item/implant/mindshield) + accessory = /obj/item/clothing/accessory/medal/gold/captain + + chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain) + +/datum/outfit/job/captain/hardsuit + name = "Captain (Hardsuit)" + + mask = /obj/item/clothing/mask/gas/atmos/captain + suit = /obj/item/clothing/suit/space/hardsuit/swat/captain + suit_store = /obj/item/tank/internals/oxygen diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm index 9fe8d33600c..faf9212cbab 100644 --- a/code/modules/jobs/jobs.dm +++ b/code/modules/jobs/jobs.dm @@ -1,139 +1,139 @@ -GLOBAL_LIST_INIT(command_positions, list( - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer")) - - -GLOBAL_LIST_INIT(engineering_positions, list( - "Chief Engineer", - "Station Engineer", - "Atmospheric Technician")) - - -GLOBAL_LIST_INIT(medical_positions, list( - "Chief Medical Officer", - "Medical Doctor", - "Paramedic", - "Virologist", - "Chemist")) - - -GLOBAL_LIST_INIT(science_positions, list( - "Research Director", - "Scientist", - "Geneticist", - "Roboticist")) - - -GLOBAL_LIST_INIT(supply_positions, list( - "Quartermaster", - "Cargo Technician", - "Shaft Miner")) - - -GLOBAL_LIST_INIT(service_positions, list( - "Head of Personnel", - "Bartender", - "Botanist", - "Cook", - "Janitor", - "Curator", - "Psychologist", - "Lawyer", - "Chaplain", - "Clown", - "Mime", - "Prisoner", - "Assistant")) - - -GLOBAL_LIST_INIT(security_positions, list( - "Head of Security", - "Warden", - "Detective", - "Security Officer")) - - -GLOBAL_LIST_INIT(nonhuman_positions, list( - "AI", - "Cyborg", - ROLE_PAI)) - -// job categories for rendering the late join menu -GLOBAL_LIST_INIT(position_categories, list( - EXP_TYPE_COMMAND = list("jobs" = command_positions, "color" = "#ccccff"), - EXP_TYPE_ENGINEERING = list("jobs" = engineering_positions, "color" = "#ffeeaa"), - EXP_TYPE_SUPPLY = list("jobs" = supply_positions, "color" = "#ddddff"), - EXP_TYPE_SILICON = list("jobs" = nonhuman_positions - "pAI", "color" = "#ccffcc"), - EXP_TYPE_SERVICE = list("jobs" = service_positions, "color" = "#bbe291"), - EXP_TYPE_MEDICAL = list("jobs" = medical_positions, "color" = "#ffddf0"), - EXP_TYPE_SCIENCE = list("jobs" = science_positions, "color" = "#ffddff"), - EXP_TYPE_SECURITY = list("jobs" = security_positions, "color" = "#ffdddd") -)) - -GLOBAL_LIST_INIT(exp_jobsmap, list( - EXP_TYPE_CREW = list("titles" = command_positions | engineering_positions | medical_positions | science_positions | supply_positions | security_positions | service_positions | list("AI","Cyborg")), // crew positions - EXP_TYPE_COMMAND = list("titles" = command_positions), - EXP_TYPE_ENGINEERING = list("titles" = engineering_positions), - EXP_TYPE_MEDICAL = list("titles" = medical_positions), - EXP_TYPE_SCIENCE = list("titles" = science_positions), - EXP_TYPE_SUPPLY = list("titles" = supply_positions), - EXP_TYPE_SECURITY = list("titles" = security_positions), - EXP_TYPE_SILICON = list("titles" = list("AI","Cyborg")), - EXP_TYPE_SERVICE = list("titles" = service_positions) -)) - -GLOBAL_LIST_INIT(exp_specialmap, list( - EXP_TYPE_LIVING = list(), // all living mobs - EXP_TYPE_ANTAG = list(), - EXP_TYPE_SPECIAL = list("Lifebringer","Ash Walker","Exile","Servant Golem","Free Golem","Hermit","Translocated Vet","Escaped Prisoner","Hotel Staff","SuperFriend","Space Syndicate","Ancient Crew","Space Doctor","Space Bartender","Beach Bum","Skeleton","Zombie","Space Bar Patron","Lavaland Syndicate","Ghost Role"), // Ghost roles - EXP_TYPE_GHOST = list() // dead people, observers -)) -GLOBAL_PROTECT(exp_jobsmap) -GLOBAL_PROTECT(exp_specialmap) - -//this is necessary because antags happen before job datums are handed out, but NOT before they come into existence -//so I can't simply use job datum.department_head straight from the mind datum, laaaaame. -/proc/get_department_heads(var/job_title) - if(!job_title) - return list() - - for(var/datum/job/J in SSjob.occupations) - if(J.title == job_title) - return J.department_head //this is a list - -/proc/get_full_job_name(job) - var/static/regex/cap_expand = new("cap(?!tain)") - var/static/regex/cmo_expand = new("cmo") - var/static/regex/hos_expand = new("hos") - var/static/regex/hop_expand = new("hop") - var/static/regex/rd_expand = new("rd") - var/static/regex/ce_expand = new("ce") - var/static/regex/qm_expand = new("qm") - var/static/regex/sec_expand = new("(? SCRAMBLE_CACHE_LEN) - scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1) - -/datum/language/proc/scramble(input) - - if(!syllables || !syllables.len) - return stars(input) - - // If the input is cached already, move it to the end of the cache and return it - var/lookup = check_cache(input) - if(lookup) - return lookup - - var/input_size = length_char(input) - var/scrambled_text = "" - var/capitalize = TRUE - - while(length_char(scrambled_text) < input_size) - var/next = pick(syllables) - if(capitalize) - next = capitalize(next) - capitalize = FALSE - scrambled_text += next - var/chance = rand(100) - if(chance <= sentence_chance) - scrambled_text += ". " - capitalize = TRUE - else if(chance > sentence_chance && chance <= space_chance) - scrambled_text += " " - - scrambled_text = trim(scrambled_text) - var/ending = copytext_char(scrambled_text, -1) - if(ending == ".") - scrambled_text = copytext_char(scrambled_text, 1, -2) - var/input_ending = copytext_char(input, -1) - if(input_ending in list("!","?",".")) - scrambled_text += input_ending - - add_to_cache(input, scrambled_text) - - return scrambled_text - -#undef SCRAMBLE_CACHE_LEN +#define SCRAMBLE_CACHE_LEN 50 //maximum of 50 specific scrambled lines per language + +/* + Datum based languages. Easily editable and modular. +*/ + +/datum/language + var/name = "an unknown language" // Fluff name of language if any. + var/desc = "A language." // Short description for 'Check Languages'. + var/key // Character used to speak in language + // If key is null, then the language isn't real or learnable. + var/flags // Various language flags. + var/list/syllables // Used when scrambling text for a non-speaker. + var/sentence_chance = 5 // Likelihood of making a new sentence after each syllable. + var/space_chance = 55 // Likelihood of getting a space in the random scramble string + var/list/spans = list() + var/list/scramble_cache = list() + var/default_priority = 0 // the language that an atom knows with the highest "default_priority" is selected by default. + + // if you are seeing someone speak popcorn language, then something is wrong. + var/icon = 'icons/misc/language.dmi' + var/icon_state = "popcorn" + +/datum/language/proc/display_icon(atom/movable/hearer) + var/understands = hearer.has_language(src.type) + if(flags & LANGUAGE_HIDE_ICON_IF_UNDERSTOOD && understands) + return FALSE + if(flags & LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD && !understands) + return FALSE + return TRUE + +/datum/language/proc/get_icon() + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat) + return sheet.icon_tag("language-[icon_state]") + +/datum/language/proc/get_random_name(gender, name_count=2, syllable_count=4, syllable_divisor=2) + if(!syllables || !syllables.len) + if(gender==FEMALE) + return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) + else + return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + + var/full_name = "" + var/new_name = "" + + for(var/i in 0 to name_count) + new_name = "" + var/Y = rand(FLOOR(syllable_count/syllable_divisor, 1), syllable_count) + for(var/x in Y to 0) + new_name += pick(syllables) + full_name += " [capitalize(lowertext(new_name))]" + + return "[trim(full_name)]" + +/datum/language/proc/check_cache(input) + var/lookup = scramble_cache[input] + if(lookup) + scramble_cache -= input + scramble_cache[input] = lookup + . = lookup + +/datum/language/proc/add_to_cache(input, scrambled_text) + // Add it to cache, cutting old entries if the list is too long + scramble_cache[input] = scrambled_text + if(scramble_cache.len > SCRAMBLE_CACHE_LEN) + scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1) + +/datum/language/proc/scramble(input) + + if(!syllables || !syllables.len) + return stars(input) + + // If the input is cached already, move it to the end of the cache and return it + var/lookup = check_cache(input) + if(lookup) + return lookup + + var/input_size = length_char(input) + var/scrambled_text = "" + var/capitalize = TRUE + + while(length_char(scrambled_text) < input_size) + var/next = pick(syllables) + if(capitalize) + next = capitalize(next) + capitalize = FALSE + scrambled_text += next + var/chance = rand(100) + if(chance <= sentence_chance) + scrambled_text += ". " + capitalize = TRUE + else if(chance > sentence_chance && chance <= space_chance) + scrambled_text += " " + + scrambled_text = trim(scrambled_text) + var/ending = copytext_char(scrambled_text, -1) + if(ending == ".") + scrambled_text = copytext_char(scrambled_text, 1, -2) + var/input_ending = copytext_char(input, -1) + if(input_ending in list("!","?",".")) + scrambled_text += input_ending + + add_to_cache(input, scrambled_text) + + return scrambled_text + +#undef SCRAMBLE_CACHE_LEN diff --git a/code/modules/language/machine.dm b/code/modules/language/machine.dm index aa547e7a12a..2d597d93334 100644 --- a/code/modules/language/machine.dm +++ b/code/modules/language/machine.dm @@ -1,16 +1,16 @@ -/datum/language/machine - name = "Encoded Audio Language" - desc = "An efficient language of encoded tones developed by synthetics and cyborgs." - spans = list(SPAN_ROBOT) - key = "6" - flags = NO_STUTTER - syllables = list("beep","beep","beep","beep","beep","boop","boop","boop","bop","bop","dee","dee","doo","doo","hiss","hss","buzz","buzz","bzz","ksssh","keey","wurr","wahh","tzzz") - space_chance = 10 - default_priority = 90 - - icon_state = "eal" - -/datum/language/machine/get_random_name() - if(prob(70)) - return "[pick(GLOB.posibrain_names)]-[rand(100, 999)]" - return pick(GLOB.ai_names) +/datum/language/machine + name = "Encoded Audio Language" + desc = "An efficient language of encoded tones developed by synthetics and cyborgs." + spans = list(SPAN_ROBOT) + key = "6" + flags = NO_STUTTER + syllables = list("beep","beep","beep","beep","beep","boop","boop","boop","bop","bop","dee","dee","doo","doo","hiss","hss","buzz","buzz","bzz","ksssh","keey","wurr","wahh","tzzz") + space_chance = 10 + default_priority = 90 + + icon_state = "eal" + +/datum/language/machine/get_random_name() + if(prob(70)) + return "[pick(GLOB.posibrain_names)]-[rand(100, 999)]" + return pick(GLOB.ai_names) diff --git a/code/modules/language/moffic.dm b/code/modules/language/moffic.dm index e3f34dfbbfb..1d0aea96697 100644 --- a/code/modules/language/moffic.dm +++ b/code/modules/language/moffic.dm @@ -1,16 +1,16 @@ -/datum/language/moffic - name = "Moffic" - desc = "The language of the Mothpeople borders on complete unintelligibility." - key = "m" - space_chance = 10 - syllables = list( - "år", "i", "går", "sek", "mo", "ff", "ok", "gj", "ø", "gå", "la", "le", - "lit", "ygg", "van", "dår", "næ", "møt", "idd", "hvo", "ja", "på", "han", - "så", "ån", "det", "att", "nå", "gö", "bra", "int", "tyc", "om", "när", - "två", "må", "dag", "sjä", "vii", "vuo", "eil", "tun", "käyt", "teh", "vä", - "hei", "huo", "suo", "ää", "ten", "ja", "heu", "stu", "uhr", "kön", "we", "hön" - ) - icon_state = "moth" - default_priority = 90 - -// Fuck guest accounts, and fuck language testing. +/datum/language/moffic + name = "Moffic" + desc = "The language of the Mothpeople borders on complete unintelligibility." + key = "m" + space_chance = 10 + syllables = list( + "år", "i", "går", "sek", "mo", "ff", "ok", "gj", "ø", "gå", "la", "le", + "lit", "ygg", "van", "dår", "næ", "møt", "idd", "hvo", "ja", "på", "han", + "så", "ån", "det", "att", "nå", "gö", "bra", "int", "tyc", "om", "när", + "två", "må", "dag", "sjä", "vii", "vuo", "eil", "tun", "käyt", "teh", "vä", + "hei", "huo", "suo", "ää", "ten", "ja", "heu", "stu", "uhr", "kön", "we", "hön" + ) + icon_state = "moth" + default_priority = 90 + +// Fuck guest accounts, and fuck language testing. diff --git a/code/modules/language/monkey.dm b/code/modules/language/monkey.dm index 75ead95bd3a..e44f6a6268e 100644 --- a/code/modules/language/monkey.dm +++ b/code/modules/language/monkey.dm @@ -1,9 +1,9 @@ -/datum/language/monkey - name = "Chimpanzee" - desc = "Ook ook ook." - key = "1" - space_chance = 100 - syllables = list("oop", "aak", "chee", "eek") - default_priority = 80 - - icon_state = "animal" +/datum/language/monkey + name = "Chimpanzee" + desc = "Ook ook ook." + key = "1" + space_chance = 100 + syllables = list("oop", "aak", "chee", "eek") + default_priority = 80 + + icon_state = "animal" diff --git a/code/modules/language/shadowtongue.dm b/code/modules/language/shadowtongue.dm index f151e74ba0e..8095ecbd886 100644 --- a/code/modules/language/shadowtongue.dm +++ b/code/modules/language/shadowtongue.dm @@ -1,18 +1,18 @@ -// You n'wah! -// many thanks to https://casualscrolls.fandom.com/wiki/Dunmeri_language, for providing this list of syllables -/datum/language/shadowtongue - name = "Shadowtongue" - desc = "What a grand and intoxicating innocence." - key = "x" - space_chance = 50 - syllables = list( - "er", "sint", "en", "et", "nor", "bahr", "sint", "un", "ku'elm", "lakor", "eri", - "noj", "dashilu", "as", "ot", "lih", "morh", "ghinu", "kin", "sha", "marik", "jibu", - "sudas", "fut", "kol", "bivi", "pohim", "devohr", "ru", "huirf", "neiris", "sut", - "devehr", "iru", "gher", "gan", "ujil", "lacor", "bahris", "ghar", "alnef", "wah", - "khurdhar", "bar", "et", "ilu", "dash", "diru", "noj", "de", "damjulan", "luvahr", - "telshahr", "tifur", "enhi", "am", "bahr", "nei", "neibahri", "n'chow", "n'wah", - "s'wit", "b'vehk", "f'lah", "muth", "sera", "sedura", "bal", "dun" - ) - icon_state = "shadow" - default_priority = 90 +// You n'wah! +// many thanks to https://casualscrolls.fandom.com/wiki/Dunmeri_language, for providing this list of syllables +/datum/language/shadowtongue + name = "Shadowtongue" + desc = "What a grand and intoxicating innocence." + key = "x" + space_chance = 50 + syllables = list( + "er", "sint", "en", "et", "nor", "bahr", "sint", "un", "ku'elm", "lakor", "eri", + "noj", "dashilu", "as", "ot", "lih", "morh", "ghinu", "kin", "sha", "marik", "jibu", + "sudas", "fut", "kol", "bivi", "pohim", "devohr", "ru", "huirf", "neiris", "sut", + "devehr", "iru", "gher", "gan", "ujil", "lacor", "bahris", "ghar", "alnef", "wah", + "khurdhar", "bar", "et", "ilu", "dash", "diru", "noj", "de", "damjulan", "luvahr", + "telshahr", "tifur", "enhi", "am", "bahr", "nei", "neibahri", "n'chow", "n'wah", + "s'wit", "b'vehk", "f'lah", "muth", "sera", "sedura", "bal", "dun" + ) + icon_state = "shadow" + default_priority = 90 diff --git a/code/modules/language/sylvan.dm b/code/modules/language/sylvan.dm index 8f2b87cd4c1..4d97f6004fe 100644 --- a/code/modules/language/sylvan.dm +++ b/code/modules/language/sylvan.dm @@ -1,15 +1,15 @@ -// The language of the podpeople. Yes, it's a shameless ripoff of elvish. -/datum/language/sylvan - name = "Sylvan" - desc = "A complicated, ancient language spoken by sentient plants." - key = "h" - space_chance = 20 - syllables = list( - "fii", "sii", "rii", "rel", "maa", "ala", "san", "tol", "tok", "dia", "eres", - "fal", "tis", "bis", "qel", "aras", "losk", "rasa", "eob", "hil", "tanl", "aere", - "fer", "bal", "pii", "dala", "ban", "foe", "doa", "cii", "uis", "mel", "wex", - "incas", "int", "elc", "ent", "aws", "qip", "nas", "vil", "jens", "dila", "fa", - "la", "re", "do", "ji", "ae", "so", "qe", "ce", "na", "mo", "ha", "yu" - ) - icon_state = "plant" - default_priority = 90 +// The language of the podpeople. Yes, it's a shameless ripoff of elvish. +/datum/language/sylvan + name = "Sylvan" + desc = "A complicated, ancient language spoken by sentient plants." + key = "h" + space_chance = 20 + syllables = list( + "fii", "sii", "rii", "rel", "maa", "ala", "san", "tol", "tok", "dia", "eres", + "fal", "tis", "bis", "qel", "aras", "losk", "rasa", "eob", "hil", "tanl", "aere", + "fer", "bal", "pii", "dala", "ban", "foe", "doa", "cii", "uis", "mel", "wex", + "incas", "int", "elc", "ent", "aws", "qip", "nas", "vil", "jens", "dila", "fa", + "la", "re", "do", "ji", "ae", "so", "qe", "ce", "na", "mo", "ha", "yu" + ) + icon_state = "plant" + default_priority = 90 diff --git a/code/modules/language/terrum.dm b/code/modules/language/terrum.dm index e850ec207fe..361106ed16c 100644 --- a/code/modules/language/terrum.dm +++ b/code/modules/language/terrum.dm @@ -1,14 +1,14 @@ -/datum/language/terrum - name = "Terrum" - desc = "The language of the golems. Sounds similar to old-earth Hebrew." - key = "g" - space_chance = 40 - syllables = list( - "sha", "vu", "nah", "ha", "yom", "ma", "cha", "ar", "et", "mol", "lua", - "ch", "na", "sh", "ni", "yah", "bes", "ol", "hish", "ev", "la", "ot", "la", - "khe", "tza", "chak", "hak", "hin", "hok", "lir", "tov", "yef", "yfe", - "cho", "ar", "kas", "kal", "ra", "lom", "im", "'", "'", "'", "'", "bok", - "erev", "shlo", "lo", "ta", "im", "yom" - ) - icon_state = "golem" - default_priority = 90 +/datum/language/terrum + name = "Terrum" + desc = "The language of the golems. Sounds similar to old-earth Hebrew." + key = "g" + space_chance = 40 + syllables = list( + "sha", "vu", "nah", "ha", "yom", "ma", "cha", "ar", "et", "mol", "lua", + "ch", "na", "sh", "ni", "yah", "bes", "ol", "hish", "ev", "la", "ot", "la", + "khe", "tza", "chak", "hak", "hin", "hok", "lir", "tov", "yef", "yfe", + "cho", "ar", "kas", "kal", "ra", "lom", "im", "'", "'", "'", "'", "bok", + "erev", "shlo", "lo", "ta", "im", "yom" + ) + icon_state = "golem" + default_priority = 90 diff --git a/code/modules/language/voltaic.dm b/code/modules/language/voltaic.dm index 694ac225382..ead7fe7c7fd 100644 --- a/code/modules/language/voltaic.dm +++ b/code/modules/language/voltaic.dm @@ -1,14 +1,14 @@ -// One of these languages will actually work, I'm certain of it. -/datum/language/voltaic - name = "Voltaic" - desc = "A sparky language made by manipulating electrical discharge." - key = "v" - space_chance = 20 - syllables = list( - "bzzt", "skrrt", "zzp", "mmm", "hzz", "tk", "shz", "k", "z", - "bzt", "zzt", "skzt", "skzz", "hmmt", "zrrt", "hzzt", "hz", - "vzt", "zt", "vz", "zip", "tzp", "lzzt", "dzzt", "zdt", "kzt", - "zzzz", "mzz" - ) - icon_state = "volt" - default_priority = 90 +// One of these languages will actually work, I'm certain of it. +/datum/language/voltaic + name = "Voltaic" + desc = "A sparky language made by manipulating electrical discharge." + key = "v" + space_chance = 20 + syllables = list( + "bzzt", "skrrt", "zzp", "mmm", "hzz", "tk", "shz", "k", "z", + "bzt", "zzt", "skzt", "skzz", "hmmt", "zrrt", "hzzt", "hz", + "vzt", "zt", "vz", "zip", "tzp", "lzzt", "dzzt", "zdt", "kzt", + "zzzz", "mzz" + ) + icon_state = "volt" + default_priority = 90 diff --git a/code/modules/language/xenocommon.dm b/code/modules/language/xenocommon.dm index 90ca7b8fc15..c5e6366715d 100644 --- a/code/modules/language/xenocommon.dm +++ b/code/modules/language/xenocommon.dm @@ -1,8 +1,8 @@ -/datum/language/xenocommon - name = "Xenomorph" - desc = "The common tongue of the xenomorphs." - key = "4" - syllables = list("sss","sSs","SSS") - default_priority = 50 - - icon_state = "xeno" +/datum/language/xenocommon + name = "Xenomorph" + desc = "The common tongue of the xenomorphs." + key = "4" + syllables = list("sss","sSs","SSS") + default_priority = 50 + + icon_state = "xeno" diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index 2dc72179812..13d9aa4a19b 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,353 +1,353 @@ -/* Library Items - * - * Contains: - * Bookcase - * Book - * Barcode Scanner - */ - -/* - * Bookcase - */ - -/obj/structure/bookcase - name = "bookcase" - icon = 'icons/obj/library.dmi' - icon_state = "bookempty" - desc = "A great place for storing knowledge." - anchored = FALSE - density = TRUE - opacity = 0 - resistance_flags = FLAMMABLE - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - var/state = 0 - var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book) //Things allowed in the bookcase - /// When enabled, books_to_load number of random books will be generated for this bookcase when first interacted with. - var/load_random_books = FALSE - /// The category of books to pick from when populating random books. - var/random_category = null - /// How many random books to generate. - var/books_to_load = 0 - -/obj/structure/bookcase/examine(mob/user) - . = ..() - if(!anchored) - . += "The bolts on the bottom are unsecured." - else - . += "It's secured in place with bolts." - switch(state) - if(0) - . += "There's a small crack visible on the back panel." - if(1) - . += "There's space inside for a wooden shelf." - if(2) - . += "There's a small crack visible on the shelf." - -/obj/structure/bookcase/Initialize(mapload) - . = ..() - if(!mapload) - return - state = 2 - icon_state = "book-0" - anchored = TRUE - for(var/obj/item/I in loc) - if(istype(I, /obj/item/book)) - I.forceMove(src) - update_icon() - -/obj/structure/bookcase/attackby(obj/item/I, mob/user, params) - switch(state) - if(0) - if(I.tool_behaviour == TOOL_WRENCH) - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You wrench the frame into place.") - anchored = TRUE - state = 1 - if(I.tool_behaviour == TOOL_CROWBAR) - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You pry the frame apart.") - deconstruct(TRUE) - - if(1) - if(istype(I, /obj/item/stack/sheet/mineral/wood)) - var/obj/item/stack/sheet/mineral/wood/W = I - if(W.get_amount() >= 2) - W.use(2) - to_chat(user, "You add a shelf.") - state = 2 - icon_state = "book-0" - if(I.tool_behaviour == TOOL_WRENCH) - I.play_tool_sound(src, 100) - to_chat(user, "You unwrench the frame.") - anchored = FALSE - state = 0 - - if(2) - var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) - if(is_type_in_list(I, allowed_books)) - if(!user.transferItemToLoc(I, src)) - return - update_icon() - else if(STR) - for(var/obj/item/T in I.contents) - if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook)) - STR.remove_from_storage(T, src) - to_chat(user, "You empty \the [I] into \the [src].") - update_icon() - else if(istype(I, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/newname = stripped_input(user, "What would you like to title this bookshelf?") - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(!newname) - return - else - name = "bookcase ([sanitize(newname)])" - else if(I.tool_behaviour == TOOL_CROWBAR) - if(contents.len) - to_chat(user, "You need to remove the books first!") - else - I.play_tool_sound(src, 100) - to_chat(user, "You pry the shelf out.") - new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) - state = 1 - icon_state = "bookempty" - else - return ..() - - -/obj/structure/bookcase/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!istype(user)) - return - if(load_random_books) - create_random_books(books_to_load, src, FALSE, random_category) - load_random_books = FALSE - if(contents.len) - var/obj/item/book/choice = input(user, "Which book would you like to remove from the shelf?") as null|obj in sortNames(contents.Copy()) - if(choice) - if(!(user.mobility_flags & MOBILITY_USE) || user.stat || user.restrained() || !in_range(loc, user)) - return - if(ishuman(user)) - if(!user.get_active_held_item()) - user.put_in_hands(choice) - else - choice.forceMove(drop_location()) - update_icon() - - -/obj/structure/bookcase/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(loc, 4) - for(var/obj/item/book/B in contents) - B.forceMove(get_turf(src)) - qdel(src) - - -/obj/structure/bookcase/update_icon_state() - var/amount = contents.len - if(load_random_books) - amount += books_to_load - icon_state = "book-[amount < 5 ? amount : 5]" - - -/obj/structure/bookcase/manuals/engineering - name = "engineering manuals bookcase" - -/obj/structure/bookcase/manuals/engineering/Initialize() - . = ..() - new /obj/item/book/manual/wiki/engineering_construction(src) - new /obj/item/book/manual/wiki/engineering_hacking(src) - new /obj/item/book/manual/wiki/engineering_guide(src) - new /obj/item/book/manual/wiki/engineering_singulo_tesla(src) - new /obj/item/book/manual/wiki/robotics_cyborgs(src) - update_icon() - - -/obj/structure/bookcase/manuals/research_and_development - name = "\improper R&D manuals bookcase" - -/obj/structure/bookcase/manuals/research_and_development/Initialize() - . = ..() - new /obj/item/book/manual/wiki/research_and_development(src) - update_icon() - - -/* - * Book - */ -/obj/item/book - name = "book" - icon = 'icons/obj/library.dmi' - icon_state ="book" - desc = "Crack it open, inhale the musk of its pages, and learn something new." - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever) - attack_verb = list("bashed", "whacked", "educated") - resistance_flags = FLAMMABLE - drop_sound = 'sound/items/handling/book_drop.ogg' - pickup_sound = 'sound/items/handling/book_pickup.ogg' - var/dat //Actual page content - var/due_date = 0 //Game time in 1/10th seconds - var/author //Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - var/unique = 0 //0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified - var/title //The real name of the book. - var/window_size = null // Specific window size for the book, i.e: "1920x1080", Size x Width - - -/obj/item/book/attack_self(mob/user) - if(!user.can_read(src)) - return - if(dat) - user << browse("Penned by [author].
                " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") - user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) - onclose(user, "book") - else - to_chat(user, "This book is completely blank!") - - -/obj/item/book/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pen)) - if(user.is_blind()) - to_chat(user, "As you are trying to write on the book, you suddenly feel very stupid!") - return - if(unique) - to_chat(user, "These pages don't seem to take the ink well! Looks like you can't modify it.") - return - var/literate = user.is_literate() - if(!literate) - to_chat(user, "You scribble illegibly on the cover of [src]!") - return - var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel") - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - switch(choice) - if("Title") - var/newtitle = reject_bad_text(stripped_input(user, "Write a new title:")) - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - if (length(newtitle) > 20) - to_chat(user, "That title won't fit on the cover!") - return - if(!newtitle) - to_chat(user, "That title is invalid.") - return - else - name = newtitle - title = newtitle - if("Contents") - var/content = stripped_input(user, "Write your book's contents (HTML NOT allowed):","","",8192) - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - if(!content) - to_chat(user, "The content is invalid.") - return - else - dat += content - if("Author") - var/newauthor = stripped_input(user, "Write the author's name:") - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - if(!newauthor) - to_chat(user, "The name is invalid.") - return - else - author = newauthor - else - return - - else if(istype(I, /obj/item/barcodescanner)) - var/obj/item/barcodescanner/scanner = I - if(!scanner.computer) - to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") - else - switch(scanner.mode) - if(0) - scanner.book = src - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") - if(1) - scanner.book = src - scanner.computer.buffer_book = name - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") - if(2) - scanner.book = src - for(var/datum/borrowbook/b in scanner.computer.checkouts) - if(b.bookname == name) - scanner.computer.checkouts.Remove(b) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") - return - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") - if(3) - scanner.book = src - for(var/obj/item/book in scanner.computer.inventory) - if(book == src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") - return - scanner.computer.inventory.Add(src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") - - else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) - to_chat(user, "You begin to carve out [title]...") - if(do_after(user, 30, target = src)) - to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") - var/obj/item/storage/book/B = new - B.name = src.name - B.title = src.title - B.icon_state = src.icon_state - if(user.is_holding(src)) - qdel(src) - user.put_in_hands(B) - return - else - B.forceMove(drop_location()) - qdel(src) - return - return - else - ..() - - -/* - * Barcode Scanner - */ -/obj/item/barcodescanner - name = "barcode scanner" - icon = 'icons/obj/library.dmi' - icon_state ="scanner" - desc = "A fabulous tool if you need to scan a barcode." - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/obj/machinery/computer/bookmanagement/computer //Associated computer - Modes 1 to 3 use this - var/obj/item/book/book //Currently scanned book - var/mode = 0 //0 - Scan only, 1 - Scan and Set Buffer, 2 - Scan and Attempt to Check In, 3 - Scan and Attempt to Add to Inventory - -/obj/item/barcodescanner/attack_self(mob/user) - mode += 1 - if(mode > 3) - mode = 0 - to_chat(user, "[src] Status Display:") - var/modedesc - switch(mode) - if(0) - modedesc = "Scan book to local buffer." - if(1) - modedesc = "Scan book to local buffer and set associated computer buffer to match." - if(2) - modedesc = "Scan book to local buffer, attempt to check in scanned book." - if(3) - modedesc = "Scan book to local buffer, attempt to add book to general inventory." - else - modedesc = "ERROR" - to_chat(user, " - Mode [mode] : [modedesc]") - if(computer) - to_chat(user, "Computer has been associated with this unit.") - else - to_chat(user, "No associated computer found. Only local scans will function properly.") - to_chat(user, "\n") +/* Library Items + * + * Contains: + * Bookcase + * Book + * Barcode Scanner + */ + +/* + * Bookcase + */ + +/obj/structure/bookcase + name = "bookcase" + icon = 'icons/obj/library.dmi' + icon_state = "bookempty" + desc = "A great place for storing knowledge." + anchored = FALSE + density = TRUE + opacity = 0 + resistance_flags = FLAMMABLE + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) + var/state = 0 + var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book) //Things allowed in the bookcase + /// When enabled, books_to_load number of random books will be generated for this bookcase when first interacted with. + var/load_random_books = FALSE + /// The category of books to pick from when populating random books. + var/random_category = null + /// How many random books to generate. + var/books_to_load = 0 + +/obj/structure/bookcase/examine(mob/user) + . = ..() + if(!anchored) + . += "The bolts on the bottom are unsecured." + else + . += "It's secured in place with bolts." + switch(state) + if(0) + . += "There's a small crack visible on the back panel." + if(1) + . += "There's space inside for a wooden shelf." + if(2) + . += "There's a small crack visible on the shelf." + +/obj/structure/bookcase/Initialize(mapload) + . = ..() + if(!mapload) + return + state = 2 + icon_state = "book-0" + anchored = TRUE + for(var/obj/item/I in loc) + if(istype(I, /obj/item/book)) + I.forceMove(src) + update_icon() + +/obj/structure/bookcase/attackby(obj/item/I, mob/user, params) + switch(state) + if(0) + if(I.tool_behaviour == TOOL_WRENCH) + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You wrench the frame into place.") + anchored = TRUE + state = 1 + if(I.tool_behaviour == TOOL_CROWBAR) + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You pry the frame apart.") + deconstruct(TRUE) + + if(1) + if(istype(I, /obj/item/stack/sheet/mineral/wood)) + var/obj/item/stack/sheet/mineral/wood/W = I + if(W.get_amount() >= 2) + W.use(2) + to_chat(user, "You add a shelf.") + state = 2 + icon_state = "book-0" + if(I.tool_behaviour == TOOL_WRENCH) + I.play_tool_sound(src, 100) + to_chat(user, "You unwrench the frame.") + anchored = FALSE + state = 0 + + if(2) + var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) + if(is_type_in_list(I, allowed_books)) + if(!user.transferItemToLoc(I, src)) + return + update_icon() + else if(STR) + for(var/obj/item/T in I.contents) + if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook)) + STR.remove_from_storage(T, src) + to_chat(user, "You empty \the [I] into \the [src].") + update_icon() + else if(istype(I, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/newname = stripped_input(user, "What would you like to title this bookshelf?") + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(!newname) + return + else + name = "bookcase ([sanitize(newname)])" + else if(I.tool_behaviour == TOOL_CROWBAR) + if(contents.len) + to_chat(user, "You need to remove the books first!") + else + I.play_tool_sound(src, 100) + to_chat(user, "You pry the shelf out.") + new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) + state = 1 + icon_state = "bookempty" + else + return ..() + + +/obj/structure/bookcase/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!istype(user)) + return + if(load_random_books) + create_random_books(books_to_load, src, FALSE, random_category) + load_random_books = FALSE + if(contents.len) + var/obj/item/book/choice = input(user, "Which book would you like to remove from the shelf?") as null|obj in sortNames(contents.Copy()) + if(choice) + if(!(user.mobility_flags & MOBILITY_USE) || user.stat || user.restrained() || !in_range(loc, user)) + return + if(ishuman(user)) + if(!user.get_active_held_item()) + user.put_in_hands(choice) + else + choice.forceMove(drop_location()) + update_icon() + + +/obj/structure/bookcase/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(loc, 4) + for(var/obj/item/book/B in contents) + B.forceMove(get_turf(src)) + qdel(src) + + +/obj/structure/bookcase/update_icon_state() + var/amount = contents.len + if(load_random_books) + amount += books_to_load + icon_state = "book-[amount < 5 ? amount : 5]" + + +/obj/structure/bookcase/manuals/engineering + name = "engineering manuals bookcase" + +/obj/structure/bookcase/manuals/engineering/Initialize() + . = ..() + new /obj/item/book/manual/wiki/engineering_construction(src) + new /obj/item/book/manual/wiki/engineering_hacking(src) + new /obj/item/book/manual/wiki/engineering_guide(src) + new /obj/item/book/manual/wiki/engineering_singulo_tesla(src) + new /obj/item/book/manual/wiki/robotics_cyborgs(src) + update_icon() + + +/obj/structure/bookcase/manuals/research_and_development + name = "\improper R&D manuals bookcase" + +/obj/structure/bookcase/manuals/research_and_development/Initialize() + . = ..() + new /obj/item/book/manual/wiki/research_and_development(src) + update_icon() + + +/* + * Book + */ +/obj/item/book + name = "book" + icon = 'icons/obj/library.dmi' + icon_state ="book" + desc = "Crack it open, inhale the musk of its pages, and learn something new." + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever) + attack_verb = list("bashed", "whacked", "educated") + resistance_flags = FLAMMABLE + drop_sound = 'sound/items/handling/book_drop.ogg' + pickup_sound = 'sound/items/handling/book_pickup.ogg' + var/dat //Actual page content + var/due_date = 0 //Game time in 1/10th seconds + var/author //Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + var/unique = 0 //0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified + var/title //The real name of the book. + var/window_size = null // Specific window size for the book, i.e: "1920x1080", Size x Width + + +/obj/item/book/attack_self(mob/user) + if(!user.can_read(src)) + return + if(dat) + user << browse("Penned by [author].
                " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) + onclose(user, "book") + else + to_chat(user, "This book is completely blank!") + + +/obj/item/book/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pen)) + if(user.is_blind()) + to_chat(user, "As you are trying to write on the book, you suddenly feel very stupid!") + return + if(unique) + to_chat(user, "These pages don't seem to take the ink well! Looks like you can't modify it.") + return + var/literate = user.is_literate() + if(!literate) + to_chat(user, "You scribble illegibly on the cover of [src]!") + return + var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel") + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + switch(choice) + if("Title") + var/newtitle = reject_bad_text(stripped_input(user, "Write a new title:")) + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + if (length(newtitle) > 20) + to_chat(user, "That title won't fit on the cover!") + return + if(!newtitle) + to_chat(user, "That title is invalid.") + return + else + name = newtitle + title = newtitle + if("Contents") + var/content = stripped_input(user, "Write your book's contents (HTML NOT allowed):","","",8192) + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + if(!content) + to_chat(user, "The content is invalid.") + return + else + dat += content + if("Author") + var/newauthor = stripped_input(user, "Write the author's name:") + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + if(!newauthor) + to_chat(user, "The name is invalid.") + return + else + author = newauthor + else + return + + else if(istype(I, /obj/item/barcodescanner)) + var/obj/item/barcodescanner/scanner = I + if(!scanner.computer) + to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") + else + switch(scanner.mode) + if(0) + scanner.book = src + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") + if(1) + scanner.book = src + scanner.computer.buffer_book = name + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") + if(2) + scanner.book = src + for(var/datum/borrowbook/b in scanner.computer.checkouts) + if(b.bookname == name) + scanner.computer.checkouts.Remove(b) + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") + return + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") + if(3) + scanner.book = src + for(var/obj/item/book in scanner.computer.inventory) + if(book == src) + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") + return + scanner.computer.inventory.Add(src) + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") + + else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) + to_chat(user, "You begin to carve out [title]...") + if(do_after(user, 30, target = src)) + to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") + var/obj/item/storage/book/B = new + B.name = src.name + B.title = src.title + B.icon_state = src.icon_state + if(user.is_holding(src)) + qdel(src) + user.put_in_hands(B) + return + else + B.forceMove(drop_location()) + qdel(src) + return + return + else + ..() + + +/* + * Barcode Scanner + */ +/obj/item/barcodescanner + name = "barcode scanner" + icon = 'icons/obj/library.dmi' + icon_state ="scanner" + desc = "A fabulous tool if you need to scan a barcode." + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/obj/machinery/computer/bookmanagement/computer //Associated computer - Modes 1 to 3 use this + var/obj/item/book/book //Currently scanned book + var/mode = 0 //0 - Scan only, 1 - Scan and Set Buffer, 2 - Scan and Attempt to Check In, 3 - Scan and Attempt to Add to Inventory + +/obj/item/barcodescanner/attack_self(mob/user) + mode += 1 + if(mode > 3) + mode = 0 + to_chat(user, "[src] Status Display:") + var/modedesc + switch(mode) + if(0) + modedesc = "Scan book to local buffer." + if(1) + modedesc = "Scan book to local buffer and set associated computer buffer to match." + if(2) + modedesc = "Scan book to local buffer, attempt to check in scanned book." + if(3) + modedesc = "Scan book to local buffer, attempt to add book to general inventory." + else + modedesc = "ERROR" + to_chat(user, " - Mode [mode] : [modedesc]") + if(computer) + to_chat(user, "Computer has been associated with this unit.") + else + to_chat(user, "No associated computer found. Only local scans will function properly.") + to_chat(user, "\n") diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index b112907d80d..0b164f5f66d 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -1,612 +1,612 @@ -/* Library Machines - * - * Contains: - * Borrowbook datum - * Library Public Computer - * Cachedbook datum - * Library Computer - * Library Scanner - * Book Binder - */ - - - -/* - * Library Public Computer - */ -/obj/machinery/computer/libraryconsole - name = "library visitor console" - icon_state = "oldcomp" - icon_screen = "library" - icon_keyboard = "no_keyboard" - circuit = /obj/item/circuitboard/computer/libraryconsole - desc = "Checked out books MUST be returned on time." - var/screenstate = 0 - var/title - var/category = "Any" - var/author - var/search_page = 0 - COOLDOWN_DECLARE(library_visitor_topic_cooldown) - -/obj/machinery/computer/libraryconsole/ui_interact(mob/user) - . = ..() - var/list/dat = list() // - switch(screenstate) - if(0) - dat += "

                Search Settings


                " - dat += "Filter by Title: [title]
                " - dat += "Filter by Category: [category]
                " - dat += "Filter by Author: [author]
                " - dat += "\[Start Search\]
                " - if(1) - if (!SSdbcore.Connect()) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
                " - else if(QDELETED(user)) - return - else - dat += "" - dat += "" - var/SQLsearch = "isnull(deleted) AND " - if(category == "Any") - SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%'" - else - SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" - var/bookcount = 0 - var/booksperpage = 20 - var/datum/db_query/query_library_count_books = SSdbcore.NewQuery({" - SELECT COUNT(id) FROM [format_table_name("library")] - WHERE isnull(deleted) - AND author LIKE '%' + :author + '%' - AND title LIKE '%' + :title + '%' - AND (:category = 'Any' OR category = :category) - "}, list("author" = author, "title" = title, "category" = category)) - if(!query_library_count_books.warn_execute()) - qdel(query_library_count_books) - return - if(query_library_count_books.NextRow()) - bookcount = text2num(query_library_count_books.item[1]) - qdel(query_library_count_books) - if(bookcount > booksperpage) - dat += "Page: " - var/pagecount = 1 - var/list/pagelist = list() - while(bookcount > 0) - pagelist += "[pagecount == search_page + 1 ? "\[[pagecount]\]" : "\[[pagecount]\]"]" - bookcount -= booksperpage - pagecount++ - dat += pagelist.Join(" | ") - search_page = text2num(search_page) - var/datum/db_query/query_library_list_books = SSdbcore.NewQuery({" - SELECT author, title, category, id - FROM [format_table_name("library")] - WHERE isnull(deleted) - AND author LIKE '%' + :author + '%' - AND title LIKE '%' + :title + '%' - AND (:category = 'Any' OR category = :category) - LIMIT :skip, :take - "}, list("author" = author, "title" = title, "category" = category, "skip" = booksperpage * search_page, "take" = booksperpage)) - if(!query_library_list_books.Execute()) - dat += "ERROR: Unable to retrieve book listings. Please contact your system administrator for assistance.
                " - else - while(query_library_list_books.NextRow()) - var/author = query_library_list_books.item[1] - var/title = query_library_list_books.item[2] - var/category = query_library_list_books.item[3] - var/id = query_library_list_books.item[4] - dat += "" - qdel(query_library_list_books) - if(QDELETED(user)) - return - dat += "
                AUTHORTITLECATEGORYSS13BN
                [author][title][category][id]

                " - dat += "\[Go Back\]
                " - var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400) - popup.set_content(jointext(dat, "")) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/libraryconsole/Topic(href, href_list) - if(!COOLDOWN_FINISHED(src, library_visitor_topic_cooldown)) - return - COOLDOWN_START(src, library_visitor_topic_cooldown, 1 SECONDS) - . = ..() - if(.) - usr << browse(null, "window=publiclibrary") - onclose(usr, "publiclibrary") - return - - if(href_list["settitle"]) - var/newtitle = input("Enter a title to search for:") as text|null - if(newtitle) - title = sanitize(newtitle) - else - title = null - if(href_list["setcategory"]) - var/newcategory = input("Choose a category to search for:") in list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") - if(newcategory) - category = sanitize(newcategory) - else - category = "Any" - if(href_list["setauthor"]) - var/newauthor = input("Enter an author to search for:") as text|null - if(newauthor) - author = sanitize(newauthor) - else - author = null - if(href_list["search"]) - screenstate = 1 - - if(href_list["bookpagecount"]) - search_page = text2num(href_list["bookpagecount"]) - - if(href_list["back"]) - screenstate = 0 - - src.add_fingerprint(usr) - src.updateUsrDialog() - return - -/* - * Borrowbook datum - */ -/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. - var/bookname - var/mobname - var/getdate - var/duedate - -#define PRINTER_COOLDOWN 60 - -/* - * Library Computer - * After 860 days, it's finally a buildable computer. - */ -// TODO: Make this an actual /obj/machinery/computer that can be crafted from circuit boards and such -// It is August 22nd, 2012... This TODO has already been here for months.. I wonder how long it'll last before someone does something about it. -// It's December 25th, 2014, and this is STILL here, and it's STILL relevant. Kill me -/obj/machinery/computer/bookmanagement - name = "book inventory management console" - desc = "Librarian's command station." - verb_say = "beeps" - verb_ask = "beeps" - verb_exclaim = "beeps" - pass_flags = PASSTABLE - - icon_state = "oldcomp" - icon_screen = "library" - icon_keyboard = "no_keyboard" - circuit = /obj/item/circuitboard/computer/libraryconsole - - var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book - - var/arcanecheckout = 0 - var/buffer_book - var/buffer_mob - var/upload_category = "Fiction" - var/list/checkouts = list() - var/list/inventory = list() - var/checkoutperiod = 5 // In minutes - var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive - var/page = 1 //current page of the external archives - var/printer_cooldown = 0 - COOLDOWN_DECLARE(library_console_topic_cooldown) - -/obj/machinery/computer/bookmanagement/Initialize() - . = ..() - if(circuit) - circuit.name = "Book Inventory Management Console (Machine Board)" - circuit.build_path = /obj/machinery/computer/bookmanagement - -/obj/machinery/computer/bookmanagement/ui_interact(mob/user) - . = ..() - var/dat = "" // - switch(screenstate) - if(0) - // Main Menu - dat += "1. View General Inventory
                " - dat += "2. View Checked Out Inventory
                " - dat += "3. Check out a Book
                " - dat += "4. Connect to External Archive
                " - dat += "5. Upload New Title to Archive
                " - dat += "6. Upload Scanned Title to Newscaster
                " - dat += "7. Print Corporate Materials
                " - if(obj_flags & EMAGGED) - dat += "8. Access the Forbidden Lore Vault
                " - if(src.arcanecheckout) - print_forbidden_lore(user) - src.arcanecheckout = 0 - if(1) - // Inventory - dat += "

                Inventory


                " - for(var/obj/item/book/b in inventory) - dat += "[b.name] (Delete)
                " - dat += "(Return to main menu)
                " - if(2) - // Checked Out - dat += "

                Checked Out Books


                " - for(var/datum/borrowbook/b in checkouts) - var/timetaken = world.time - b.getdate - timetaken /= 600 - timetaken = round(timetaken) - var/timedue = b.duedate - world.time - timedue /= 600 - if(timedue <= 0) - timedue = "(OVERDUE) [timedue]" - else - timedue = round(timedue) - dat += "\"[b.bookname]\", Checked out to: [b.mobname]
                --- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
                " - dat += "(Check In)

                " - dat += "(Return to main menu)
                " - if(3) - // Check Out a Book - dat += "

                Check Out a Book


                " - dat += "Book: [src.buffer_book] " - dat += "\[Edit\]
                " - dat += "Recipient: [src.buffer_mob] " - dat += "\[Edit\]
                " - dat += "Checkout Date : [world.time/600]
                " - dat += "Due Date: [(world.time + checkoutperiod)/600]
                " - dat += "(Checkout Period: [checkoutperiod] minutes) (+/-)" - dat += "(Commit Entry)
                " - dat += "(Return to main menu)
                " - if(4) - dat += "

                External Archive

                " - if(!SSdbcore.Connect()) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." - else - var/booksperpage = 50 - var/pagecount - var/datum/db_query/query_library_count_books = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("library")] WHERE isnull(deleted)") - if(!query_library_count_books.Execute()) - qdel(query_library_count_books) - return - if(query_library_count_books.NextRow()) - pagecount = CEILING(text2num(query_library_count_books.item[1]) / booksperpage, 1) - qdel(query_library_count_books) - var/list/booklist = list() - var/datum/db_query/query_library_get_books = SSdbcore.NewQuery({" - SELECT id, author, title, category - FROM [format_table_name("library")] - WHERE isnull(deleted) - LIMIT :skip, :take - "}, list("skip" = booksperpage * (page - 1), "take" = booksperpage)) - if(!query_library_get_books.Execute()) - qdel(query_library_get_books) - return - while(query_library_get_books.NextRow()) - booklist += "[query_library_get_books.item[2]][query_library_get_books.item[3]][query_library_get_books.item[4]]\[Order\]\n" - dat += "(Order book by SS13BN)

                " - dat += "" - dat += "" - dat += jointext(booklist, "") - dat += "" - dat += "
                AUTHORTITLECATEGORY
                <<<< >>>>
                " - qdel(query_library_get_books) - dat += "
                (Return to main menu)
                " - if(5) - dat += "

                Upload a New Title

                " - if(!scanner) - scanner = findscanner(9) - if(!scanner) - dat += "No scanner found within wireless network range.
                " - else if(!scanner.cache) - dat += "No data found in scanner memory.
                " - else - dat += "Data marked for upload...
                " - dat += "Title: [scanner.cache.name]
                " - if(!scanner.cache.author) - scanner.cache.author = "Anonymous" - dat += "Author: [scanner.cache.author]
                " - dat += "Category: [upload_category]
                " - dat += "\[Upload\]
                " - dat += "(Return to main menu)
                " - if(6) - dat += "

                Post Title to Newscaster

                " - if(!scanner) - scanner = findscanner(9) - if(!scanner) - dat += "No scanner found within wireless network range.
                " - else if(!scanner.cache) - dat += "No data found in scanner memory.
                " - else - dat += "Post [scanner.cache.name] to station newscasters?" - dat += "\[Post\]
                " - dat += "(Return to main menu)
                " - if(7) - dat += "

                NTGanda(tm) Universal Printing Module

                " - dat += "What would you like to print?
                " - dat += "\[Bible\]
                " - dat += "\[Poster\]
                " - dat += "(Return to main menu)
                " - if(8) - dat += "

                Accessing Forbidden Lore Vault v 1.3

                " - dat += "Are you absolutely sure you want to proceed? EldritchRelics Inc. takes no responsibilities for loss of sanity resulting from this action.

                " - dat += "Yes.
                " - dat += "No.
                " - - var/datum/browser/popup = new(user, "library", name, 600, 400) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/bookmanagement/proc/findscanner(viewrange) - for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src))) - return S - return null - -/obj/machinery/computer/bookmanagement/proc/print_forbidden_lore(mob/user) - new /obj/item/melee/cultblade/dagger(get_turf(src)) - to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...") - user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) - -/obj/machinery/computer/bookmanagement/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/barcodescanner)) - var/obj/item/barcodescanner/scanner = W - scanner.computer = src - to_chat(user, "[scanner]'s associated machine has been set to [src].") - audible_message("[src] lets out a low, short blip.") - else - return ..() - -/obj/machinery/computer/bookmanagement/emag_act(mob/user) - if(density && !(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - -/obj/machinery/computer/bookmanagement/Topic(href, href_list) - if(!COOLDOWN_FINISHED(src, library_console_topic_cooldown)) - return - COOLDOWN_START(src, library_console_topic_cooldown, 1 SECONDS) - if(..()) - usr << browse(null, "window=library") - onclose(usr, "library") - return - if(href_list["page"] && screenstate == 4) - page = text2num(href_list["page"]) - if(href_list["switchscreen"]) - switch(href_list["switchscreen"]) - if("0") - screenstate = 0 - if("1") - screenstate = 1 - if("2") - screenstate = 2 - if("3") - screenstate = 3 - if("4") - screenstate = 4 - if("5") - screenstate = 5 - if("6") - screenstate = 6 - if("7") - screenstate = 7 - if("8") - screenstate = 8 - if(href_list["arccheckout"]) - if(obj_flags & EMAGGED) - src.arcanecheckout = 1 - src.screenstate = 0 - if(href_list["increasetime"]) - checkoutperiod += 1 - if(href_list["decreasetime"]) - checkoutperiod -= 1 - if(checkoutperiod < 1) - checkoutperiod = 1 - if(href_list["editbook"]) - buffer_book = stripped_input(usr, "Enter the book's title:") - if(href_list["editmob"]) - buffer_mob = stripped_input(usr, "Enter the recipient's name:", max_length = MAX_NAME_LEN) - if(href_list["checkout"]) - var/datum/borrowbook/b = new /datum/borrowbook - b.bookname = sanitize(buffer_book) - b.mobname = sanitize(buffer_mob) - b.getdate = world.time - b.duedate = world.time + (checkoutperiod * 600) - checkouts.Add(b) - if(href_list["checkin"]) - var/datum/borrowbook/b = locate(href_list["checkin"]) in checkouts - if(b && istype(b)) - checkouts.Remove(b) - if(href_list["delbook"]) - var/obj/item/book/b = locate(href_list["delbook"]) in inventory - if(b && istype(b)) - inventory.Remove(b) - if(href_list["setauthor"]) - var/newauthor = stripped_input(usr, "Enter the author's name: ") - if(newauthor) - scanner.cache.author = newauthor - if(href_list["setcategory"]) - var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion","Technical") - if(newcategory) - upload_category = newcategory - if(href_list["upload"]) - if(scanner) - if(scanner.cache) - var/choice = input("Are you certain you wish to upload this title to the Archive?") in list("Confirm", "Abort") - if(choice == "Confirm") - if (!SSdbcore.Connect()) - alert("Connection to Archive has been severed. Aborting.") - else - var/msg = "[key_name(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs" - var/datum/db_query/query_library_upload = SSdbcore.NewQuery({" - INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) - VALUES (:author, :title, :content, :category, :ckey, Now(), :round_id) - "}, list("title" = scanner.cache.name, "author" = scanner.cache.author, "content" = scanner.cache.dat, "category" = upload_category, "ckey" = usr.ckey, "round_id" = GLOB.round_id)) - if(!query_library_upload.Execute()) - qdel(query_library_upload) - alert("Database error encountered uploading to Archive") - return - else - log_game(msg) - qdel(query_library_upload) - alert("Upload Complete. Uploaded title will be unavailable for printing for a short period") - if(href_list["newspost"]) - if(!GLOB.news_network) - alert("No news network found on station. Aborting.") - var/channelexists = 0 - for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == "Nanotrasen Book Club") - channelexists = 1 - break - if(!channelexists) - GLOB.news_network.CreateFeedChannel("Nanotrasen Book Club", "Library", null) - GLOB.news_network.SubmitArticle(scanner.cache.dat, "[scanner.cache.name]", "Nanotrasen Book Club", null) - alert("Upload complete. Your uploaded title is now available on station newscasters.") - if(href_list["orderbyid"]) - if(printer_cooldown > world.time) - say("Printer unavailable. Please allow a short time before attempting to print.") - else - var/orderid = input("Enter your order:") as num|null - if(orderid) - if(isnum(orderid) && ISINTEGER(orderid)) - href_list["targetid"] = num2text(orderid) - - if(href_list["targetid"]) - var/id = href_list["targetid"] - if (!SSdbcore.Connect()) - alert("Connection to Archive has been severed. Aborting.") - if(printer_cooldown > world.time) - say("Printer unavailable. Please allow a short time before attempting to print.") - else - printer_cooldown = world.time + PRINTER_COOLDOWN - var/datum/db_query/query_library_print = SSdbcore.NewQuery( - "SELECT * FROM [format_table_name("library")] WHERE id=:id AND isnull(deleted)", - list("id" = id) - ) - if(!query_library_print.Execute()) - qdel(query_library_print) - say("PRINTER ERROR! Failed to print document (0x0000000F)") - return - while(query_library_print.NextRow()) - var/author = query_library_print.item[2] - var/title = query_library_print.item[3] - var/content = query_library_print.item[4] - if(!QDELETED(src)) - var/obj/item/book/B = new(get_turf(src)) - B.name = "Book: [title]" - B.title = title - B.author = author - B.dat = content - B.icon_state = "book[rand(1,8)]" - visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") - break - qdel(query_library_print) - if(href_list["printbible"]) - if(printer_cooldown < world.time) - var/obj/item/storage/book/bible/B = new /obj/item/storage/book/bible(src.loc) - if(GLOB.bible_icon_state && GLOB.bible_inhand_icon_state) - B.icon_state = GLOB.bible_icon_state - B.inhand_icon_state = GLOB.bible_inhand_icon_state - B.name = GLOB.bible_name - B.deity_name = GLOB.deity - printer_cooldown = world.time + PRINTER_COOLDOWN - else - say("Printer currently unavailable, please wait a moment.") - if(href_list["printposter"]) - if(printer_cooldown < world.time) - new /obj/item/poster/random_official(src.loc) - printer_cooldown = world.time + PRINTER_COOLDOWN - else - say("Printer currently unavailable, please wait a moment.") - add_fingerprint(usr) - updateUsrDialog() - -/* - * Library Scanner - */ -/obj/machinery/libraryscanner - name = "scanner control interface" - icon = 'icons/obj/library.dmi' - icon_state = "bigscanner" - desc = "It servers the purpose of scanning stuff." - density = TRUE - var/obj/item/book/cache // Last scanned book - -/obj/machinery/libraryscanner/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/book)) - if(!user.transferItemToLoc(O, src)) - return - else - return ..() - -/obj/machinery/libraryscanner/attack_hand(mob/user) - . = ..() - if(.) - return - usr.set_machine(src) - var/dat = "" // - if(cache) - dat += "Data stored in memory.
                " - else - dat += "No data stored in memory.
                " - dat += "\[Scan\]" - if(cache) - dat += " \[Clear Memory\]

                \[Remove Book\]" - else - dat += "
                " - var/datum/browser/popup = new(user, "scanner", name, 600, 400) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/libraryscanner/Topic(href, href_list) - if(..()) - usr << browse(null, "window=scanner") - onclose(usr, "scanner") - return - - if(href_list["scan"]) - for(var/obj/item/book/B in contents) - cache = B - break - if(href_list["clear"]) - cache = null - if(href_list["eject"]) - for(var/obj/item/book/B in contents) - B.forceMove(drop_location()) - src.add_fingerprint(usr) - src.updateUsrDialog() - return - - -/* - * Book binder - */ -/obj/machinery/bookbinder - name = "book binder" - icon = 'icons/obj/library.dmi' - icon_state = "binder" - desc = "Only intended for binding paper products." - density = TRUE - var/busy = FALSE - -/obj/machinery/bookbinder/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/paper)) - bind_book(user, O) - else if(default_unfasten_wrench(user, O)) - return 1 - else - return ..() - -/obj/machinery/bookbinder/proc/bind_book(mob/user, obj/item/paper/P) - if(machine_stat) - return - if(busy) - to_chat(user, "The book binder is busy. Please wait for completion of previous operation.") - return - if(!user.transferItemToLoc(P, src)) - return - user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") - audible_message("[src] begins to hum as it warms up its printing drums.") - busy = TRUE - sleep(rand(200,400)) - busy = FALSE - if(P) - if(!machine_stat) - visible_message("[src] whirs as it prints and binds a new book.") - var/obj/item/book/B = new(src.loc) - B.dat = P.info - B.name = "Print Job #" + "[rand(100, 999)]" - B.icon_state = "book[rand(1,7)]" - qdel(P) - else - P.forceMove(drop_location()) +/* Library Machines + * + * Contains: + * Borrowbook datum + * Library Public Computer + * Cachedbook datum + * Library Computer + * Library Scanner + * Book Binder + */ + + + +/* + * Library Public Computer + */ +/obj/machinery/computer/libraryconsole + name = "library visitor console" + icon_state = "oldcomp" + icon_screen = "library" + icon_keyboard = "no_keyboard" + circuit = /obj/item/circuitboard/computer/libraryconsole + desc = "Checked out books MUST be returned on time." + var/screenstate = 0 + var/title + var/category = "Any" + var/author + var/search_page = 0 + COOLDOWN_DECLARE(library_visitor_topic_cooldown) + +/obj/machinery/computer/libraryconsole/ui_interact(mob/user) + . = ..() + var/list/dat = list() // + switch(screenstate) + if(0) + dat += "

                Search Settings


                " + dat += "Filter by Title: [title]
                " + dat += "Filter by Category: [category]
                " + dat += "Filter by Author: [author]
                " + dat += "\[Start Search\]
                " + if(1) + if (!SSdbcore.Connect()) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
                " + else if(QDELETED(user)) + return + else + dat += "" + dat += "" + var/SQLsearch = "isnull(deleted) AND " + if(category == "Any") + SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%'" + else + SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" + var/bookcount = 0 + var/booksperpage = 20 + var/datum/db_query/query_library_count_books = SSdbcore.NewQuery({" + SELECT COUNT(id) FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE '%' + :author + '%' + AND title LIKE '%' + :title + '%' + AND (:category = 'Any' OR category = :category) + "}, list("author" = author, "title" = title, "category" = category)) + if(!query_library_count_books.warn_execute()) + qdel(query_library_count_books) + return + if(query_library_count_books.NextRow()) + bookcount = text2num(query_library_count_books.item[1]) + qdel(query_library_count_books) + if(bookcount > booksperpage) + dat += "Page: " + var/pagecount = 1 + var/list/pagelist = list() + while(bookcount > 0) + pagelist += "[pagecount == search_page + 1 ? "\[[pagecount]\]" : "\[[pagecount]\]"]" + bookcount -= booksperpage + pagecount++ + dat += pagelist.Join(" | ") + search_page = text2num(search_page) + var/datum/db_query/query_library_list_books = SSdbcore.NewQuery({" + SELECT author, title, category, id + FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE '%' + :author + '%' + AND title LIKE '%' + :title + '%' + AND (:category = 'Any' OR category = :category) + LIMIT :skip, :take + "}, list("author" = author, "title" = title, "category" = category, "skip" = booksperpage * search_page, "take" = booksperpage)) + if(!query_library_list_books.Execute()) + dat += "ERROR: Unable to retrieve book listings. Please contact your system administrator for assistance.
                " + else + while(query_library_list_books.NextRow()) + var/author = query_library_list_books.item[1] + var/title = query_library_list_books.item[2] + var/category = query_library_list_books.item[3] + var/id = query_library_list_books.item[4] + dat += "" + qdel(query_library_list_books) + if(QDELETED(user)) + return + dat += "
                AUTHORTITLECATEGORYSS13BN
                [author][title][category][id]

                " + dat += "\[Go Back\]
                " + var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400) + popup.set_content(jointext(dat, "")) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/libraryconsole/Topic(href, href_list) + if(!COOLDOWN_FINISHED(src, library_visitor_topic_cooldown)) + return + COOLDOWN_START(src, library_visitor_topic_cooldown, 1 SECONDS) + . = ..() + if(.) + usr << browse(null, "window=publiclibrary") + onclose(usr, "publiclibrary") + return + + if(href_list["settitle"]) + var/newtitle = input("Enter a title to search for:") as text|null + if(newtitle) + title = sanitize(newtitle) + else + title = null + if(href_list["setcategory"]) + var/newcategory = input("Choose a category to search for:") in list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") + if(newcategory) + category = sanitize(newcategory) + else + category = "Any" + if(href_list["setauthor"]) + var/newauthor = input("Enter an author to search for:") as text|null + if(newauthor) + author = sanitize(newauthor) + else + author = null + if(href_list["search"]) + screenstate = 1 + + if(href_list["bookpagecount"]) + search_page = text2num(href_list["bookpagecount"]) + + if(href_list["back"]) + screenstate = 0 + + src.add_fingerprint(usr) + src.updateUsrDialog() + return + +/* + * Borrowbook datum + */ +/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. + var/bookname + var/mobname + var/getdate + var/duedate + +#define PRINTER_COOLDOWN 60 + +/* + * Library Computer + * After 860 days, it's finally a buildable computer. + */ +// TODO: Make this an actual /obj/machinery/computer that can be crafted from circuit boards and such +// It is August 22nd, 2012... This TODO has already been here for months.. I wonder how long it'll last before someone does something about it. +// It's December 25th, 2014, and this is STILL here, and it's STILL relevant. Kill me +/obj/machinery/computer/bookmanagement + name = "book inventory management console" + desc = "Librarian's command station." + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + pass_flags = PASSTABLE + + icon_state = "oldcomp" + icon_screen = "library" + icon_keyboard = "no_keyboard" + circuit = /obj/item/circuitboard/computer/libraryconsole + + var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book + + var/arcanecheckout = 0 + var/buffer_book + var/buffer_mob + var/upload_category = "Fiction" + var/list/checkouts = list() + var/list/inventory = list() + var/checkoutperiod = 5 // In minutes + var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive + var/page = 1 //current page of the external archives + var/printer_cooldown = 0 + COOLDOWN_DECLARE(library_console_topic_cooldown) + +/obj/machinery/computer/bookmanagement/Initialize() + . = ..() + if(circuit) + circuit.name = "Book Inventory Management Console (Machine Board)" + circuit.build_path = /obj/machinery/computer/bookmanagement + +/obj/machinery/computer/bookmanagement/ui_interact(mob/user) + . = ..() + var/dat = "" // + switch(screenstate) + if(0) + // Main Menu + dat += "1. View General Inventory
                " + dat += "2. View Checked Out Inventory
                " + dat += "3. Check out a Book
                " + dat += "4. Connect to External Archive
                " + dat += "5. Upload New Title to Archive
                " + dat += "6. Upload Scanned Title to Newscaster
                " + dat += "7. Print Corporate Materials
                " + if(obj_flags & EMAGGED) + dat += "8. Access the Forbidden Lore Vault
                " + if(src.arcanecheckout) + print_forbidden_lore(user) + src.arcanecheckout = 0 + if(1) + // Inventory + dat += "

                Inventory


                " + for(var/obj/item/book/b in inventory) + dat += "[b.name] (Delete)
                " + dat += "(Return to main menu)
                " + if(2) + // Checked Out + dat += "

                Checked Out Books


                " + for(var/datum/borrowbook/b in checkouts) + var/timetaken = world.time - b.getdate + timetaken /= 600 + timetaken = round(timetaken) + var/timedue = b.duedate - world.time + timedue /= 600 + if(timedue <= 0) + timedue = "(OVERDUE) [timedue]" + else + timedue = round(timedue) + dat += "\"[b.bookname]\", Checked out to: [b.mobname]
                --- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
                " + dat += "(Check In)

                " + dat += "(Return to main menu)
                " + if(3) + // Check Out a Book + dat += "

                Check Out a Book


                " + dat += "Book: [src.buffer_book] " + dat += "\[Edit\]
                " + dat += "Recipient: [src.buffer_mob] " + dat += "\[Edit\]
                " + dat += "Checkout Date : [world.time/600]
                " + dat += "Due Date: [(world.time + checkoutperiod)/600]
                " + dat += "(Checkout Period: [checkoutperiod] minutes) (+/-)" + dat += "(Commit Entry)
                " + dat += "(Return to main menu)
                " + if(4) + dat += "

                External Archive

                " + if(!SSdbcore.Connect()) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." + else + var/booksperpage = 50 + var/pagecount + var/datum/db_query/query_library_count_books = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("library")] WHERE isnull(deleted)") + if(!query_library_count_books.Execute()) + qdel(query_library_count_books) + return + if(query_library_count_books.NextRow()) + pagecount = CEILING(text2num(query_library_count_books.item[1]) / booksperpage, 1) + qdel(query_library_count_books) + var/list/booklist = list() + var/datum/db_query/query_library_get_books = SSdbcore.NewQuery({" + SELECT id, author, title, category + FROM [format_table_name("library")] + WHERE isnull(deleted) + LIMIT :skip, :take + "}, list("skip" = booksperpage * (page - 1), "take" = booksperpage)) + if(!query_library_get_books.Execute()) + qdel(query_library_get_books) + return + while(query_library_get_books.NextRow()) + booklist += "[query_library_get_books.item[2]][query_library_get_books.item[3]][query_library_get_books.item[4]]\[Order\]\n" + dat += "(Order book by SS13BN)

                " + dat += "" + dat += "" + dat += jointext(booklist, "") + dat += "" + dat += "
                AUTHORTITLECATEGORY
                <<<< >>>>
                " + qdel(query_library_get_books) + dat += "
                (Return to main menu)
                " + if(5) + dat += "

                Upload a New Title

                " + if(!scanner) + scanner = findscanner(9) + if(!scanner) + dat += "No scanner found within wireless network range.
                " + else if(!scanner.cache) + dat += "No data found in scanner memory.
                " + else + dat += "Data marked for upload...
                " + dat += "Title: [scanner.cache.name]
                " + if(!scanner.cache.author) + scanner.cache.author = "Anonymous" + dat += "Author: [scanner.cache.author]
                " + dat += "Category: [upload_category]
                " + dat += "\[Upload\]
                " + dat += "(Return to main menu)
                " + if(6) + dat += "

                Post Title to Newscaster

                " + if(!scanner) + scanner = findscanner(9) + if(!scanner) + dat += "No scanner found within wireless network range.
                " + else if(!scanner.cache) + dat += "No data found in scanner memory.
                " + else + dat += "Post [scanner.cache.name] to station newscasters?" + dat += "\[Post\]
                " + dat += "(Return to main menu)
                " + if(7) + dat += "

                NTGanda(tm) Universal Printing Module

                " + dat += "What would you like to print?
                " + dat += "\[Bible\]
                " + dat += "\[Poster\]
                " + dat += "(Return to main menu)
                " + if(8) + dat += "

                Accessing Forbidden Lore Vault v 1.3

                " + dat += "Are you absolutely sure you want to proceed? EldritchRelics Inc. takes no responsibilities for loss of sanity resulting from this action.

                " + dat += "Yes.
                " + dat += "No.
                " + + var/datum/browser/popup = new(user, "library", name, 600, 400) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/bookmanagement/proc/findscanner(viewrange) + for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src))) + return S + return null + +/obj/machinery/computer/bookmanagement/proc/print_forbidden_lore(mob/user) + new /obj/item/melee/cultblade/dagger(get_turf(src)) + to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...") + user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) + +/obj/machinery/computer/bookmanagement/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/barcodescanner)) + var/obj/item/barcodescanner/scanner = W + scanner.computer = src + to_chat(user, "[scanner]'s associated machine has been set to [src].") + audible_message("[src] lets out a low, short blip.") + else + return ..() + +/obj/machinery/computer/bookmanagement/emag_act(mob/user) + if(density && !(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + +/obj/machinery/computer/bookmanagement/Topic(href, href_list) + if(!COOLDOWN_FINISHED(src, library_console_topic_cooldown)) + return + COOLDOWN_START(src, library_console_topic_cooldown, 1 SECONDS) + if(..()) + usr << browse(null, "window=library") + onclose(usr, "library") + return + if(href_list["page"] && screenstate == 4) + page = text2num(href_list["page"]) + if(href_list["switchscreen"]) + switch(href_list["switchscreen"]) + if("0") + screenstate = 0 + if("1") + screenstate = 1 + if("2") + screenstate = 2 + if("3") + screenstate = 3 + if("4") + screenstate = 4 + if("5") + screenstate = 5 + if("6") + screenstate = 6 + if("7") + screenstate = 7 + if("8") + screenstate = 8 + if(href_list["arccheckout"]) + if(obj_flags & EMAGGED) + src.arcanecheckout = 1 + src.screenstate = 0 + if(href_list["increasetime"]) + checkoutperiod += 1 + if(href_list["decreasetime"]) + checkoutperiod -= 1 + if(checkoutperiod < 1) + checkoutperiod = 1 + if(href_list["editbook"]) + buffer_book = stripped_input(usr, "Enter the book's title:") + if(href_list["editmob"]) + buffer_mob = stripped_input(usr, "Enter the recipient's name:", max_length = MAX_NAME_LEN) + if(href_list["checkout"]) + var/datum/borrowbook/b = new /datum/borrowbook + b.bookname = sanitize(buffer_book) + b.mobname = sanitize(buffer_mob) + b.getdate = world.time + b.duedate = world.time + (checkoutperiod * 600) + checkouts.Add(b) + if(href_list["checkin"]) + var/datum/borrowbook/b = locate(href_list["checkin"]) in checkouts + if(b && istype(b)) + checkouts.Remove(b) + if(href_list["delbook"]) + var/obj/item/book/b = locate(href_list["delbook"]) in inventory + if(b && istype(b)) + inventory.Remove(b) + if(href_list["setauthor"]) + var/newauthor = stripped_input(usr, "Enter the author's name: ") + if(newauthor) + scanner.cache.author = newauthor + if(href_list["setcategory"]) + var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion","Technical") + if(newcategory) + upload_category = newcategory + if(href_list["upload"]) + if(scanner) + if(scanner.cache) + var/choice = input("Are you certain you wish to upload this title to the Archive?") in list("Confirm", "Abort") + if(choice == "Confirm") + if (!SSdbcore.Connect()) + alert("Connection to Archive has been severed. Aborting.") + else + var/msg = "[key_name(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs" + var/datum/db_query/query_library_upload = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) + VALUES (:author, :title, :content, :category, :ckey, Now(), :round_id) + "}, list("title" = scanner.cache.name, "author" = scanner.cache.author, "content" = scanner.cache.dat, "category" = upload_category, "ckey" = usr.ckey, "round_id" = GLOB.round_id)) + if(!query_library_upload.Execute()) + qdel(query_library_upload) + alert("Database error encountered uploading to Archive") + return + else + log_game(msg) + qdel(query_library_upload) + alert("Upload Complete. Uploaded title will be unavailable for printing for a short period") + if(href_list["newspost"]) + if(!GLOB.news_network) + alert("No news network found on station. Aborting.") + var/channelexists = 0 + for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) + if(FC.channel_name == "Nanotrasen Book Club") + channelexists = 1 + break + if(!channelexists) + GLOB.news_network.CreateFeedChannel("Nanotrasen Book Club", "Library", null) + GLOB.news_network.SubmitArticle(scanner.cache.dat, "[scanner.cache.name]", "Nanotrasen Book Club", null) + alert("Upload complete. Your uploaded title is now available on station newscasters.") + if(href_list["orderbyid"]) + if(printer_cooldown > world.time) + say("Printer unavailable. Please allow a short time before attempting to print.") + else + var/orderid = input("Enter your order:") as num|null + if(orderid) + if(isnum(orderid) && ISINTEGER(orderid)) + href_list["targetid"] = num2text(orderid) + + if(href_list["targetid"]) + var/id = href_list["targetid"] + if (!SSdbcore.Connect()) + alert("Connection to Archive has been severed. Aborting.") + if(printer_cooldown > world.time) + say("Printer unavailable. Please allow a short time before attempting to print.") + else + printer_cooldown = world.time + PRINTER_COOLDOWN + var/datum/db_query/query_library_print = SSdbcore.NewQuery( + "SELECT * FROM [format_table_name("library")] WHERE id=:id AND isnull(deleted)", + list("id" = id) + ) + if(!query_library_print.Execute()) + qdel(query_library_print) + say("PRINTER ERROR! Failed to print document (0x0000000F)") + return + while(query_library_print.NextRow()) + var/author = query_library_print.item[2] + var/title = query_library_print.item[3] + var/content = query_library_print.item[4] + if(!QDELETED(src)) + var/obj/item/book/B = new(get_turf(src)) + B.name = "Book: [title]" + B.title = title + B.author = author + B.dat = content + B.icon_state = "book[rand(1,8)]" + visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") + break + qdel(query_library_print) + if(href_list["printbible"]) + if(printer_cooldown < world.time) + var/obj/item/storage/book/bible/B = new /obj/item/storage/book/bible(src.loc) + if(GLOB.bible_icon_state && GLOB.bible_inhand_icon_state) + B.icon_state = GLOB.bible_icon_state + B.inhand_icon_state = GLOB.bible_inhand_icon_state + B.name = GLOB.bible_name + B.deity_name = GLOB.deity + printer_cooldown = world.time + PRINTER_COOLDOWN + else + say("Printer currently unavailable, please wait a moment.") + if(href_list["printposter"]) + if(printer_cooldown < world.time) + new /obj/item/poster/random_official(src.loc) + printer_cooldown = world.time + PRINTER_COOLDOWN + else + say("Printer currently unavailable, please wait a moment.") + add_fingerprint(usr) + updateUsrDialog() + +/* + * Library Scanner + */ +/obj/machinery/libraryscanner + name = "scanner control interface" + icon = 'icons/obj/library.dmi' + icon_state = "bigscanner" + desc = "It servers the purpose of scanning stuff." + density = TRUE + var/obj/item/book/cache // Last scanned book + +/obj/machinery/libraryscanner/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/book)) + if(!user.transferItemToLoc(O, src)) + return + else + return ..() + +/obj/machinery/libraryscanner/attack_hand(mob/user) + . = ..() + if(.) + return + usr.set_machine(src) + var/dat = "" // + if(cache) + dat += "Data stored in memory.
                " + else + dat += "No data stored in memory.
                " + dat += "\[Scan\]" + if(cache) + dat += " \[Clear Memory\]

                \[Remove Book\]" + else + dat += "
                " + var/datum/browser/popup = new(user, "scanner", name, 600, 400) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/libraryscanner/Topic(href, href_list) + if(..()) + usr << browse(null, "window=scanner") + onclose(usr, "scanner") + return + + if(href_list["scan"]) + for(var/obj/item/book/B in contents) + cache = B + break + if(href_list["clear"]) + cache = null + if(href_list["eject"]) + for(var/obj/item/book/B in contents) + B.forceMove(drop_location()) + src.add_fingerprint(usr) + src.updateUsrDialog() + return + + +/* + * Book binder + */ +/obj/machinery/bookbinder + name = "book binder" + icon = 'icons/obj/library.dmi' + icon_state = "binder" + desc = "Only intended for binding paper products." + density = TRUE + var/busy = FALSE + +/obj/machinery/bookbinder/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/paper)) + bind_book(user, O) + else if(default_unfasten_wrench(user, O)) + return 1 + else + return ..() + +/obj/machinery/bookbinder/proc/bind_book(mob/user, obj/item/paper/P) + if(machine_stat) + return + if(busy) + to_chat(user, "The book binder is busy. Please wait for completion of previous operation.") + return + if(!user.transferItemToLoc(P, src)) + return + user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") + audible_message("[src] begins to hum as it warms up its printing drums.") + busy = TRUE + sleep(rand(200,400)) + busy = FALSE + if(P) + if(!machine_stat) + visible_message("[src] whirs as it prints and binds a new book.") + var/obj/item/book/B = new(src.loc) + B.dat = P.info + B.name = "Print Job #" + "[rand(100, 999)]" + B.icon_state = "book[rand(1,7)]" + qdel(P) + else + P.forceMove(drop_location()) diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm index 835a95009c4..d60609147af 100644 --- a/code/modules/library/random_books.dm +++ b/code/modules/library/random_books.dm @@ -1,92 +1,92 @@ -/obj/item/book/manual/random - icon_state = "random_book" - -/obj/item/book/manual/random/Initialize() - ..() - var/static/banned_books = list(/obj/item/book/manual/random, /obj/item/book/manual/nuclear, /obj/item/book/manual/wiki) - var/newtype = pick(subtypesof(/obj/item/book/manual) - banned_books) - new newtype(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/book/random - icon_state = "random_book" - /// The category of books to pick from when creating this book. - var/random_category = null - /// If this book has already been 'generated' yet. - var/random_loaded = FALSE - -/obj/item/book/random/Initialize(mapload) - . = ..() - icon_state = "book[rand(1,8)]" - -/obj/item/book/random/attack_self() - if(!random_loaded) - create_random_books(1, loc, TRUE, random_category, src) - random_loaded = TRUE - return ..() - -/obj/structure/bookcase/random - load_random_books = TRUE - books_to_load = 2 - icon_state = "random_bookcase" - -/obj/structure/bookcase/random/Initialize(mapload) - . = ..() - if(books_to_load && isnum(books_to_load)) - books_to_load += pick(-1,-1,0,1,1) - update_icon() - -/proc/create_random_books(amount, location, fail_loud = FALSE, category = null, obj/item/book/existing_book) - . = list() - if(!isnum(amount) || amount<1) - return - if (!SSdbcore.Connect()) - if(existing_book && (fail_loud || prob(5))) - existing_book.author = "???" - existing_book.title = "Strange book" - existing_book.name = "Strange book" - existing_book.dat = "There once was a book from Nantucket
                But the database failed us, so f*$! it.
                I tried to be good to you
                Now this is an I.O.U
                If you're feeling entitled, well, stuff it!

                ~" - return - if(prob(25)) - category = null - var/datum/db_query/query_get_random_books = SSdbcore.NewQuery({" - SELECT author, title, content - FROM [format_table_name("library")] - WHERE isnull(deleted) AND (:category IS NULL OR category = :category) - ORDER BY rand() LIMIT :limit - "}, list("category" = category, "limit" = amount)) - if(query_get_random_books.Execute()) - while(query_get_random_books.NextRow()) - var/obj/item/book/B - B = existing_book ? existing_book : new(location) - B.author = query_get_random_books.item[1] - B.title = query_get_random_books.item[2] - B.dat = query_get_random_books.item[3] - B.name = "Book: [B.title]" - if(!existing_book) - B.icon_state= "book[rand(1,8)]" - qdel(query_get_random_books) - -/obj/structure/bookcase/random/fiction - name = "bookcase (Fiction)" - random_category = "Fiction" -/obj/structure/bookcase/random/nonfiction - name = "bookcase (Non-Fiction)" - random_category = "Non-fiction" -/obj/structure/bookcase/random/religion - name = "bookcase (Religion)" - random_category = "Religion" -/obj/structure/bookcase/random/adult - name = "bookcase (Adult)" - random_category = "Adult" - -/obj/structure/bookcase/random/reference - name = "bookcase (Reference)" - random_category = "Reference" - var/ref_book_prob = 20 - -/obj/structure/bookcase/random/reference/Initialize(mapload) - . = ..() - while(books_to_load > 0 && prob(ref_book_prob)) - books_to_load-- - new /obj/item/book/manual/random(src) +/obj/item/book/manual/random + icon_state = "random_book" + +/obj/item/book/manual/random/Initialize() + ..() + var/static/banned_books = list(/obj/item/book/manual/random, /obj/item/book/manual/nuclear, /obj/item/book/manual/wiki) + var/newtype = pick(subtypesof(/obj/item/book/manual) - banned_books) + new newtype(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/book/random + icon_state = "random_book" + /// The category of books to pick from when creating this book. + var/random_category = null + /// If this book has already been 'generated' yet. + var/random_loaded = FALSE + +/obj/item/book/random/Initialize(mapload) + . = ..() + icon_state = "book[rand(1,8)]" + +/obj/item/book/random/attack_self() + if(!random_loaded) + create_random_books(1, loc, TRUE, random_category, src) + random_loaded = TRUE + return ..() + +/obj/structure/bookcase/random + load_random_books = TRUE + books_to_load = 2 + icon_state = "random_bookcase" + +/obj/structure/bookcase/random/Initialize(mapload) + . = ..() + if(books_to_load && isnum(books_to_load)) + books_to_load += pick(-1,-1,0,1,1) + update_icon() + +/proc/create_random_books(amount, location, fail_loud = FALSE, category = null, obj/item/book/existing_book) + . = list() + if(!isnum(amount) || amount<1) + return + if (!SSdbcore.Connect()) + if(existing_book && (fail_loud || prob(5))) + existing_book.author = "???" + existing_book.title = "Strange book" + existing_book.name = "Strange book" + existing_book.dat = "There once was a book from Nantucket
                But the database failed us, so f*$! it.
                I tried to be good to you
                Now this is an I.O.U
                If you're feeling entitled, well, stuff it!

                ~" + return + if(prob(25)) + category = null + var/datum/db_query/query_get_random_books = SSdbcore.NewQuery({" + SELECT author, title, content + FROM [format_table_name("library")] + WHERE isnull(deleted) AND (:category IS NULL OR category = :category) + ORDER BY rand() LIMIT :limit + "}, list("category" = category, "limit" = amount)) + if(query_get_random_books.Execute()) + while(query_get_random_books.NextRow()) + var/obj/item/book/B + B = existing_book ? existing_book : new(location) + B.author = query_get_random_books.item[1] + B.title = query_get_random_books.item[2] + B.dat = query_get_random_books.item[3] + B.name = "Book: [B.title]" + if(!existing_book) + B.icon_state= "book[rand(1,8)]" + qdel(query_get_random_books) + +/obj/structure/bookcase/random/fiction + name = "bookcase (Fiction)" + random_category = "Fiction" +/obj/structure/bookcase/random/nonfiction + name = "bookcase (Non-Fiction)" + random_category = "Non-fiction" +/obj/structure/bookcase/random/religion + name = "bookcase (Religion)" + random_category = "Religion" +/obj/structure/bookcase/random/adult + name = "bookcase (Adult)" + random_category = "Adult" + +/obj/structure/bookcase/random/reference + name = "bookcase (Reference)" + random_category = "Reference" + var/ref_book_prob = 20 + +/obj/structure/bookcase/random/reference/Initialize(mapload) + . = ..() + while(books_to_load > 0 && prob(ref_book_prob)) + books_to_load-- + new /obj/item/book/manual/random(src) diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm index e405322b89f..ab8fb9c1b11 100644 --- a/code/modules/mapping/ruins.dm +++ b/code/modules/mapping/ruins.dm @@ -1,179 +1,179 @@ -/datum/map_template/ruin/proc/try_to_place(z,allowed_areas,turf/forced_turf) - var/sanity = forced_turf ? 1 : PLACEMENT_TRIES - if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS)) - return place_on_isolated_level(z) - while(sanity > 0) - sanity-- - var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2) - var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2) - var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z) - var/valid = TRUE - - for(var/turf/check in get_affected_turfs(central_turf,1)) - var/area/new_area = get_area(check) - valid = FALSE // set to false before we check - if(check.flags_1 & NO_RUINS_1) - break - for(var/type in allowed_areas) - if(istype(new_area, type)) // it's at least one of our types so it's whitelisted - valid = TRUE - break - if(!valid) - break - - if(!valid) - continue - - testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])") - - for(var/i in get_affected_turfs(central_turf, 1)) - var/turf/T = i - for(var/obj/structure/spawner/nest in T) - qdel(nest) - for(var/mob/living/simple_animal/monster in T) - qdel(monster) - for(var/obj/structure/flora/ash/plant in T) - qdel(plant) - - load(central_turf,centered = TRUE) - loaded++ - - for(var/turf/T in get_affected_turfs(central_turf, 1)) - T.flags_1 |= NO_RUINS_1 - - new /obj/effect/landmark/ruin(central_turf, src) - return central_turf - -/datum/map_template/ruin/proc/place_on_isolated_level(z) - var/datum/turf_reservation/reservation = SSmapping.RequestBlockReservation(width, height, z) //Make the new level creation work with different traits. - if(!reservation) - return - var/turf/placement = locate(reservation.bottom_left_coords[1],reservation.bottom_left_coords[2],reservation.bottom_left_coords[3]) - load(placement) - loaded++ - for(var/turf/T in get_affected_turfs(placement)) - T.flags_1 |= NO_RUINS_1 - var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z) - new /obj/effect/landmark/ruin(center, src) - return center - - -/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins) - if(!z_levels || !z_levels.len) - WARNING("No Z levels provided - Not generating ruins") - return - - for(var/zl in z_levels) - var/turf/T = locate(1, 1, zl) - if(!T) - WARNING("Z level [zl] does not exist - Not generating ruins") - return - - var/list/ruins = potentialRuins.Copy() - - var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf. - var/list/ruins_availible = list() //we can try these in the current pass - - //Set up the starting ruin list - for(var/key in ruins) - var/datum/map_template/ruin/R = ruins[key] - if(R.cost > budget) //Why would you do that - continue - if(R.always_place) - forced_ruins[R] = -1 - if(R.unpickable) - continue - ruins_availible[R] = R.placement_weight - while(budget > 0 && (ruins_availible.len || forced_ruins.len)) - var/datum/map_template/ruin/current_pick - var/forced = FALSE - var/forced_z //If set we won't pick z level and use this one instead. - var/forced_turf //If set we place the ruin centered on the given turf - if(forced_ruins.len) //We have something we need to load right now, so just pick it - for(var/ruin in forced_ruins) - current_pick = ruin - if(isturf(forced_ruins[ruin])) - var/turf/T = forced_ruins[ruin] - forced_z = T.z //In case of chained ruins - forced_turf = T - else if(forced_ruins[ruin] > 0) //Load into designated z - forced_z = forced_ruins[ruin] - forced = TRUE - break - else //Otherwise just pick random one - current_pick = pickweight(ruins_availible) - - var/placement_tries = forced_turf ? 1 : PLACEMENT_TRIES //Only try once if we target specific turf - var/failed_to_place = TRUE - var/target_z = 0 - var/turf/placed_turf //Where the ruin ended up if we succeeded - outer: - while(placement_tries > 0) - placement_tries-- - target_z = pick(z_levels) - if(forced_z) - target_z = forced_z - if(current_pick.always_spawn_with) //If the ruin has part below, make sure that z exists. - for(var/v in current_pick.always_spawn_with) - if(current_pick.always_spawn_with[v] == PLACE_BELOW) - var/turf/T = locate(1,1,target_z) - if(!SSmapping.get_turf_below(T)) - if(forced_z) - continue outer - else - break outer - - placed_turf = current_pick.try_to_place(target_z,whitelist,forced_turf) - if(!placed_turf) - continue - else - failed_to_place = FALSE - break - - //That's done remove from priority even if it failed - if(forced) - //TODO : handle forced ruins with multiple variants - forced_ruins -= current_pick - forced = FALSE - - if(failed_to_place) - for(var/datum/map_template/ruin/R in ruins_availible) - if(R.id == current_pick.id) - ruins_availible -= R - log_world("Failed to place [current_pick.name] ruin.") - else - budget -= current_pick.cost - if(!current_pick.allow_duplicates) - for(var/datum/map_template/ruin/R in ruins_availible) - if(R.id == current_pick.id) - ruins_availible -= R - if(current_pick.never_spawn_with) - for(var/blacklisted_type in current_pick.never_spawn_with) - for(var/possible_exclusion in ruins_availible) - if(istype(possible_exclusion,blacklisted_type)) - ruins_availible -= possible_exclusion - if(current_pick.always_spawn_with) - for(var/v in current_pick.always_spawn_with) - for(var/ruin_name in SSmapping.ruins_templates) //Because we might want to add space templates as linked of lava templates. - var/datum/map_template/ruin/linked = SSmapping.ruins_templates[ruin_name] //why are these assoc, very annoying. - if(istype(linked,v)) - switch(current_pick.always_spawn_with[v]) - if(PLACE_SAME_Z) - forced_ruins[linked] = target_z //I guess you might want a chain somehow - if(PLACE_LAVA_RUIN) - forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS)) - if(PLACE_SPACE_RUIN) - forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS)) - if(PLACE_DEFAULT) - forced_ruins[linked] = -1 - if(PLACE_BELOW) - forced_ruins[linked] = SSmapping.get_turf_below(placed_turf) - if(PLACE_ISOLATED) - forced_ruins[linked] = SSmapping.get_isolated_ruin_z() - - //Update the availible list - for(var/datum/map_template/ruin/R in ruins_availible) - if(R.cost > budget) - ruins_availible -= R - - log_world("Ruin loader finished with [budget] left to spend.") +/datum/map_template/ruin/proc/try_to_place(z,allowed_areas,turf/forced_turf) + var/sanity = forced_turf ? 1 : PLACEMENT_TRIES + if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS)) + return place_on_isolated_level(z) + while(sanity > 0) + sanity-- + var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2) + var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2) + var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z) + var/valid = TRUE + + for(var/turf/check in get_affected_turfs(central_turf,1)) + var/area/new_area = get_area(check) + valid = FALSE // set to false before we check + if(check.flags_1 & NO_RUINS_1) + break + for(var/type in allowed_areas) + if(istype(new_area, type)) // it's at least one of our types so it's whitelisted + valid = TRUE + break + if(!valid) + break + + if(!valid) + continue + + testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])") + + for(var/i in get_affected_turfs(central_turf, 1)) + var/turf/T = i + for(var/obj/structure/spawner/nest in T) + qdel(nest) + for(var/mob/living/simple_animal/monster in T) + qdel(monster) + for(var/obj/structure/flora/ash/plant in T) + qdel(plant) + + load(central_turf,centered = TRUE) + loaded++ + + for(var/turf/T in get_affected_turfs(central_turf, 1)) + T.flags_1 |= NO_RUINS_1 + + new /obj/effect/landmark/ruin(central_turf, src) + return central_turf + +/datum/map_template/ruin/proc/place_on_isolated_level(z) + var/datum/turf_reservation/reservation = SSmapping.RequestBlockReservation(width, height, z) //Make the new level creation work with different traits. + if(!reservation) + return + var/turf/placement = locate(reservation.bottom_left_coords[1],reservation.bottom_left_coords[2],reservation.bottom_left_coords[3]) + load(placement) + loaded++ + for(var/turf/T in get_affected_turfs(placement)) + T.flags_1 |= NO_RUINS_1 + var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z) + new /obj/effect/landmark/ruin(center, src) + return center + + +/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins) + if(!z_levels || !z_levels.len) + WARNING("No Z levels provided - Not generating ruins") + return + + for(var/zl in z_levels) + var/turf/T = locate(1, 1, zl) + if(!T) + WARNING("Z level [zl] does not exist - Not generating ruins") + return + + var/list/ruins = potentialRuins.Copy() + + var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf. + var/list/ruins_availible = list() //we can try these in the current pass + + //Set up the starting ruin list + for(var/key in ruins) + var/datum/map_template/ruin/R = ruins[key] + if(R.cost > budget) //Why would you do that + continue + if(R.always_place) + forced_ruins[R] = -1 + if(R.unpickable) + continue + ruins_availible[R] = R.placement_weight + while(budget > 0 && (ruins_availible.len || forced_ruins.len)) + var/datum/map_template/ruin/current_pick + var/forced = FALSE + var/forced_z //If set we won't pick z level and use this one instead. + var/forced_turf //If set we place the ruin centered on the given turf + if(forced_ruins.len) //We have something we need to load right now, so just pick it + for(var/ruin in forced_ruins) + current_pick = ruin + if(isturf(forced_ruins[ruin])) + var/turf/T = forced_ruins[ruin] + forced_z = T.z //In case of chained ruins + forced_turf = T + else if(forced_ruins[ruin] > 0) //Load into designated z + forced_z = forced_ruins[ruin] + forced = TRUE + break + else //Otherwise just pick random one + current_pick = pickweight(ruins_availible) + + var/placement_tries = forced_turf ? 1 : PLACEMENT_TRIES //Only try once if we target specific turf + var/failed_to_place = TRUE + var/target_z = 0 + var/turf/placed_turf //Where the ruin ended up if we succeeded + outer: + while(placement_tries > 0) + placement_tries-- + target_z = pick(z_levels) + if(forced_z) + target_z = forced_z + if(current_pick.always_spawn_with) //If the ruin has part below, make sure that z exists. + for(var/v in current_pick.always_spawn_with) + if(current_pick.always_spawn_with[v] == PLACE_BELOW) + var/turf/T = locate(1,1,target_z) + if(!SSmapping.get_turf_below(T)) + if(forced_z) + continue outer + else + break outer + + placed_turf = current_pick.try_to_place(target_z,whitelist,forced_turf) + if(!placed_turf) + continue + else + failed_to_place = FALSE + break + + //That's done remove from priority even if it failed + if(forced) + //TODO : handle forced ruins with multiple variants + forced_ruins -= current_pick + forced = FALSE + + if(failed_to_place) + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.id == current_pick.id) + ruins_availible -= R + log_world("Failed to place [current_pick.name] ruin.") + else + budget -= current_pick.cost + if(!current_pick.allow_duplicates) + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.id == current_pick.id) + ruins_availible -= R + if(current_pick.never_spawn_with) + for(var/blacklisted_type in current_pick.never_spawn_with) + for(var/possible_exclusion in ruins_availible) + if(istype(possible_exclusion,blacklisted_type)) + ruins_availible -= possible_exclusion + if(current_pick.always_spawn_with) + for(var/v in current_pick.always_spawn_with) + for(var/ruin_name in SSmapping.ruins_templates) //Because we might want to add space templates as linked of lava templates. + var/datum/map_template/ruin/linked = SSmapping.ruins_templates[ruin_name] //why are these assoc, very annoying. + if(istype(linked,v)) + switch(current_pick.always_spawn_with[v]) + if(PLACE_SAME_Z) + forced_ruins[linked] = target_z //I guess you might want a chain somehow + if(PLACE_LAVA_RUIN) + forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS)) + if(PLACE_SPACE_RUIN) + forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS)) + if(PLACE_DEFAULT) + forced_ruins[linked] = -1 + if(PLACE_BELOW) + forced_ruins[linked] = SSmapping.get_turf_below(placed_turf) + if(PLACE_ISOLATED) + forced_ruins[linked] = SSmapping.get_isolated_ruin_z() + + //Update the availible list + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.cost > budget) + ruins_availible -= R + + log_world("Ruin loader finished with [budget] left to spend.") diff --git a/code/modules/mapping/space_management/multiz_helpers.dm b/code/modules/mapping/space_management/multiz_helpers.dm index d4dcbbeec53..4f9174e719f 100644 --- a/code/modules/mapping/space_management/multiz_helpers.dm +++ b/code/modules/mapping/space_management/multiz_helpers.dm @@ -1,34 +1,34 @@ -/proc/get_step_multiz(ref, dir) - if(dir & UP) - dir &= ~UP - return get_step(SSmapping.get_turf_above(get_turf(ref)), dir) - if(dir & DOWN) - dir &= ~DOWN - return get_step(SSmapping.get_turf_below(get_turf(ref)), dir) - return get_step(ref, dir) - -/proc/get_dir_multiz(turf/us, turf/them) - us = get_turf(us) - them = get_turf(them) - if(!us || !them) - return NONE - if(us.z == them.z) - return get_dir(us, them) - else - var/turf/T = us.above() - var/dir = NONE - if(T && (T.z == them.z)) - dir = UP - else - T = us.below() - if(T && (T.z == them.z)) - dir = DOWN - else - return get_dir(us, them) - return (dir | get_dir(us, them)) - -/turf/proc/above() - return get_step_multiz(src, UP) - -/turf/proc/below() - return get_step_multiz(src, DOWN) +/proc/get_step_multiz(ref, dir) + if(dir & UP) + dir &= ~UP + return get_step(SSmapping.get_turf_above(get_turf(ref)), dir) + if(dir & DOWN) + dir &= ~DOWN + return get_step(SSmapping.get_turf_below(get_turf(ref)), dir) + return get_step(ref, dir) + +/proc/get_dir_multiz(turf/us, turf/them) + us = get_turf(us) + them = get_turf(them) + if(!us || !them) + return NONE + if(us.z == them.z) + return get_dir(us, them) + else + var/turf/T = us.above() + var/dir = NONE + if(T && (T.z == them.z)) + dir = UP + else + T = us.below() + if(T && (T.z == them.z)) + dir = DOWN + else + return get_dir(us, them) + return (dir | get_dir(us, them)) + +/turf/proc/above() + return get_step_multiz(src, UP) + +/turf/proc/below() + return get_step_multiz(src, DOWN) diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index f1b3b1ccdc5..265d2c996d0 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -1,74 +1,74 @@ - -//Yes, they can only be rectangular. -//Yes, I'm sorry. -/datum/turf_reservation - var/list/reserved_turfs = list() - var/width = 0 - var/height = 0 - var/bottom_left_coords[3] - var/top_right_coords[3] - var/wipe_reservation_on_release = TRUE - var/turf_type = /turf/open/space - -/datum/turf_reservation/transit - turf_type = /turf/open/space/transit - -/datum/turf_reservation/proc/Release() - var/v = reserved_turfs.Copy() - for(var/i in reserved_turfs) - reserved_turfs -= i - SSmapping.used_turfs -= i - SSmapping.reserve_turfs(v) - -/datum/turf_reservation/proc/Reserve(width, height, zlevel) - if(width > world.maxx || height > world.maxy || width < 1 || height < 1) - return FALSE - var/list/avail = SSmapping.unused_turfs["[zlevel]"] - var/turf/BL - var/turf/TR - var/list/turf/final = list() - var/passing = FALSE - for(var/i in avail) - CHECK_TICK - BL = i - if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1)) - continue - if(BL.x + width > world.maxx || BL.y + height > world.maxy) - continue - TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z) - if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1)) - continue - final = block(BL, TR) - if(!final) - continue - passing = TRUE - for(var/I in final) - var/turf/checking = I - if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1)) - passing = FALSE - break - if(!passing) - continue - break - if(!passing || !istype(BL) || !istype(TR)) - return FALSE - bottom_left_coords = list(BL.x, BL.y, BL.z) - top_right_coords = list(TR.x, TR.y, TR.z) - for(var/i in final) - var/turf/T = i - reserved_turfs |= T - T.flags_1 &= ~UNUSED_RESERVATION_TURF_1 - SSmapping.unused_turfs["[T.z]"] -= T - SSmapping.used_turfs[T] = src - T.ChangeTurf(turf_type, turf_type) - src.width = width - src.height = height - return TRUE - -/datum/turf_reservation/New() - LAZYADD(SSmapping.turf_reservations, src) - -/datum/turf_reservation/Destroy() - Release() - LAZYREMOVE(SSmapping.turf_reservations, src) - return ..() + +//Yes, they can only be rectangular. +//Yes, I'm sorry. +/datum/turf_reservation + var/list/reserved_turfs = list() + var/width = 0 + var/height = 0 + var/bottom_left_coords[3] + var/top_right_coords[3] + var/wipe_reservation_on_release = TRUE + var/turf_type = /turf/open/space + +/datum/turf_reservation/transit + turf_type = /turf/open/space/transit + +/datum/turf_reservation/proc/Release() + var/v = reserved_turfs.Copy() + for(var/i in reserved_turfs) + reserved_turfs -= i + SSmapping.used_turfs -= i + SSmapping.reserve_turfs(v) + +/datum/turf_reservation/proc/Reserve(width, height, zlevel) + if(width > world.maxx || height > world.maxy || width < 1 || height < 1) + return FALSE + var/list/avail = SSmapping.unused_turfs["[zlevel]"] + var/turf/BL + var/turf/TR + var/list/turf/final = list() + var/passing = FALSE + for(var/i in avail) + CHECK_TICK + BL = i + if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1)) + continue + if(BL.x + width > world.maxx || BL.y + height > world.maxy) + continue + TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z) + if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1)) + continue + final = block(BL, TR) + if(!final) + continue + passing = TRUE + for(var/I in final) + var/turf/checking = I + if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1)) + passing = FALSE + break + if(!passing) + continue + break + if(!passing || !istype(BL) || !istype(TR)) + return FALSE + bottom_left_coords = list(BL.x, BL.y, BL.z) + top_right_coords = list(TR.x, TR.y, TR.z) + for(var/i in final) + var/turf/T = i + reserved_turfs |= T + T.flags_1 &= ~UNUSED_RESERVATION_TURF_1 + SSmapping.unused_turfs["[T.z]"] -= T + SSmapping.used_turfs[T] = src + T.ChangeTurf(turf_type, turf_type) + src.width = width + src.height = height + return TRUE + +/datum/turf_reservation/New() + LAZYADD(SSmapping.turf_reservations, src) + +/datum/turf_reservation/Destroy() + Release() + LAZYREMOVE(SSmapping.turf_reservations, src) + return ..() diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm index e04337f4285..a2c004a09dc 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -1,172 +1,172 @@ -/*****************Pickaxes & Drills & Shovels****************/ -/obj/item/pickaxe - name = "pickaxe" - icon = 'icons/obj/mining.dmi' - icon_state = "pickaxe" - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK - force = 15 - throwforce = 10 - inhand_icon_state = "pickaxe" - worn_icon_state = "pickaxe" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - custom_materials = list(/datum/material/iron=2000) //one sheet, but where can you make them? - tool_behaviour = TOOL_MINING - toolspeed = 1 - usesound = list('sound/effects/picaxe1.ogg', 'sound/effects/picaxe2.ogg', 'sound/effects/picaxe3.ogg') - attack_verb = list("hit", "pierced", "sliced", "attacked") - -/obj/item/pickaxe/suicide_act(mob/living/user) - user.visible_message("[user] begins digging into [user.p_their()] chest! It looks like [user.p_theyre()] trying to commit suicide!") - if(use_tool(user, user, 30, volume=50)) - return BRUTELOSS - user.visible_message("[user] couldn't do it!") - return SHAME - -/obj/item/pickaxe/rusted - name = "rusty pickaxe" - desc = "A pickaxe that's been left to rust." - attack_verb = list("ineffectively hit") - force = 1 - throwforce = 1 - -/obj/item/pickaxe/mini - name = "compact pickaxe" - desc = "A smaller, compact version of the standard pickaxe." - icon_state = "minipick" - worn_icon_state = "pickaxe" - force = 10 - throwforce = 7 - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=1000) - -/obj/item/pickaxe/silver - name = "silver-plated pickaxe" - icon_state = "spickaxe" - inhand_icon_state = "spickaxe" - worn_icon_state = "spickaxe" - toolspeed = 0.5 //mines faster than a normal pickaxe, bought from mining vendor - desc = "A silver-plated pickaxe that mines slightly faster than standard-issue." - force = 17 - -/obj/item/pickaxe/diamond - name = "diamond-tipped pickaxe" - icon_state = "dpickaxe" - inhand_icon_state = "dpickaxe" - worn_icon_state = "dpickaxe" - toolspeed = 0.3 - desc = "A pickaxe with a diamond pick head. Extremely robust at cracking rock walls and digging up dirt." - force = 19 - -/obj/item/pickaxe/drill - name = "mining drill" - icon_state = "handdrill" - inhand_icon_state = "jackhammer" - worn_icon_state = "jackhammer" - slot_flags = ITEM_SLOT_BELT - toolspeed = 0.6 //available from roundstart, faster than a pickaxe. - usesound = 'sound/weapons/drill.ogg' - hitsound = 'sound/weapons/drill.ogg' - desc = "An electric mining drill for the especially scrawny." - -/obj/item/pickaxe/drill/cyborg - name = "cyborg mining drill" - desc = "An integrated electric mining drill." - flags_1 = NONE - -/obj/item/pickaxe/drill/cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/pickaxe/drill/diamonddrill - name = "diamond-tipped mining drill" - icon_state = "diamonddrill" - toolspeed = 0.2 - desc = "Yours is the drill that will pierce the heavens!" - -/obj/item/pickaxe/drill/cyborg/diamond //This is the BORG version! - name = "diamond-tipped cyborg mining drill" //To inherit the NODROP_1 flag, and easier to change borg specific drill mechanics. - icon_state = "diamonddrill" - toolspeed = 0.2 - -/obj/item/pickaxe/drill/jackhammer - name = "sonic jackhammer" - icon_state = "jackhammer" - inhand_icon_state = "jackhammer" - worn_icon_state = "jackhammer" - toolspeed = 0.1 //the epitome of powertools. extremely fast mining - usesound = 'sound/weapons/sonic_jackhammer.ogg' - hitsound = 'sound/weapons/sonic_jackhammer.ogg' - desc = "Cracks rocks with sonic blasts." - -/obj/item/pickaxe/improvised - name = "improvised pickaxe" - desc = "A pickaxe made with a knife and crowbar taped together, how does it not break?" - icon_state = "ipickaxe" - inhand_icon_state = "ipickaxe" - worn_icon_state = "pickaxe" - force = 10 - throwforce = 7 - toolspeed = 3 //3 times slower than a normal pickaxe - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=12050) //metal needed for a crowbar and for a knife, why the FUCK does a knife cost 6 metal sheets while a crowbar costs 0.025 sheets? shit makes no sense fuck this - -/obj/item/shovel - name = "shovel" - desc = "A large tool for digging and moving dirt." - icon = 'icons/obj/mining.dmi' - icon_state = "shovel" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 8 - tool_behaviour = TOOL_SHOVEL - toolspeed = 1 - usesound = 'sound/effects/shovel_dig.ogg' - throwforce = 4 - inhand_icon_state = "shovel" - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=50) - attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked") - sharpness = IS_SHARP - -/obj/item/shovel/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 150, 40) //it's sharp, so it works, but barely. - -/obj/item/shovel/suicide_act(mob/living/user) - user.visible_message("[user] begins digging their own grave! It looks like [user.p_theyre()] trying to commit suicide!") - if(use_tool(user, user, 30, volume=50)) - return BRUTELOSS - user.visible_message("[user] couldn't do it!") - return SHAME - -/obj/item/shovel/spade - name = "spade" - desc = "A small tool for digging and moving dirt." - icon_state = "spade" - inhand_icon_state = "spade" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - -/obj/item/shovel/serrated - name = "serrated bone shovel" - desc = "A wicked tool that cleaves through dirt just as easily as it does flesh. The design was styled after ancient lavaland tribal designs." - icon_state = "shovel_bone" - inhand_icon_state = "shovel_bone" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - force = 15 - throwforce = 12 - w_class = WEIGHT_CLASS_NORMAL - toolspeed = 0.7 - attack_verb = list("slashed", "impaled", "stabbed", "sliced") - sharpness = IS_SHARP +/*****************Pickaxes & Drills & Shovels****************/ +/obj/item/pickaxe + name = "pickaxe" + icon = 'icons/obj/mining.dmi' + icon_state = "pickaxe" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + force = 15 + throwforce = 10 + inhand_icon_state = "pickaxe" + worn_icon_state = "pickaxe" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + custom_materials = list(/datum/material/iron=2000) //one sheet, but where can you make them? + tool_behaviour = TOOL_MINING + toolspeed = 1 + usesound = list('sound/effects/picaxe1.ogg', 'sound/effects/picaxe2.ogg', 'sound/effects/picaxe3.ogg') + attack_verb = list("hit", "pierced", "sliced", "attacked") + +/obj/item/pickaxe/suicide_act(mob/living/user) + user.visible_message("[user] begins digging into [user.p_their()] chest! It looks like [user.p_theyre()] trying to commit suicide!") + if(use_tool(user, user, 30, volume=50)) + return BRUTELOSS + user.visible_message("[user] couldn't do it!") + return SHAME + +/obj/item/pickaxe/rusted + name = "rusty pickaxe" + desc = "A pickaxe that's been left to rust." + attack_verb = list("ineffectively hit") + force = 1 + throwforce = 1 + +/obj/item/pickaxe/mini + name = "compact pickaxe" + desc = "A smaller, compact version of the standard pickaxe." + icon_state = "minipick" + worn_icon_state = "pickaxe" + force = 10 + throwforce = 7 + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=1000) + +/obj/item/pickaxe/silver + name = "silver-plated pickaxe" + icon_state = "spickaxe" + inhand_icon_state = "spickaxe" + worn_icon_state = "spickaxe" + toolspeed = 0.5 //mines faster than a normal pickaxe, bought from mining vendor + desc = "A silver-plated pickaxe that mines slightly faster than standard-issue." + force = 17 + +/obj/item/pickaxe/diamond + name = "diamond-tipped pickaxe" + icon_state = "dpickaxe" + inhand_icon_state = "dpickaxe" + worn_icon_state = "dpickaxe" + toolspeed = 0.3 + desc = "A pickaxe with a diamond pick head. Extremely robust at cracking rock walls and digging up dirt." + force = 19 + +/obj/item/pickaxe/drill + name = "mining drill" + icon_state = "handdrill" + inhand_icon_state = "jackhammer" + worn_icon_state = "jackhammer" + slot_flags = ITEM_SLOT_BELT + toolspeed = 0.6 //available from roundstart, faster than a pickaxe. + usesound = 'sound/weapons/drill.ogg' + hitsound = 'sound/weapons/drill.ogg' + desc = "An electric mining drill for the especially scrawny." + +/obj/item/pickaxe/drill/cyborg + name = "cyborg mining drill" + desc = "An integrated electric mining drill." + flags_1 = NONE + +/obj/item/pickaxe/drill/cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/pickaxe/drill/diamonddrill + name = "diamond-tipped mining drill" + icon_state = "diamonddrill" + toolspeed = 0.2 + desc = "Yours is the drill that will pierce the heavens!" + +/obj/item/pickaxe/drill/cyborg/diamond //This is the BORG version! + name = "diamond-tipped cyborg mining drill" //To inherit the NODROP_1 flag, and easier to change borg specific drill mechanics. + icon_state = "diamonddrill" + toolspeed = 0.2 + +/obj/item/pickaxe/drill/jackhammer + name = "sonic jackhammer" + icon_state = "jackhammer" + inhand_icon_state = "jackhammer" + worn_icon_state = "jackhammer" + toolspeed = 0.1 //the epitome of powertools. extremely fast mining + usesound = 'sound/weapons/sonic_jackhammer.ogg' + hitsound = 'sound/weapons/sonic_jackhammer.ogg' + desc = "Cracks rocks with sonic blasts." + +/obj/item/pickaxe/improvised + name = "improvised pickaxe" + desc = "A pickaxe made with a knife and crowbar taped together, how does it not break?" + icon_state = "ipickaxe" + inhand_icon_state = "ipickaxe" + worn_icon_state = "pickaxe" + force = 10 + throwforce = 7 + toolspeed = 3 //3 times slower than a normal pickaxe + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=12050) //metal needed for a crowbar and for a knife, why the FUCK does a knife cost 6 metal sheets while a crowbar costs 0.025 sheets? shit makes no sense fuck this + +/obj/item/shovel + name = "shovel" + desc = "A large tool for digging and moving dirt." + icon = 'icons/obj/mining.dmi' + icon_state = "shovel" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 8 + tool_behaviour = TOOL_SHOVEL + toolspeed = 1 + usesound = 'sound/effects/shovel_dig.ogg' + throwforce = 4 + inhand_icon_state = "shovel" + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=50) + attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked") + sharpness = IS_SHARP + +/obj/item/shovel/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 150, 40) //it's sharp, so it works, but barely. + +/obj/item/shovel/suicide_act(mob/living/user) + user.visible_message("[user] begins digging their own grave! It looks like [user.p_theyre()] trying to commit suicide!") + if(use_tool(user, user, 30, volume=50)) + return BRUTELOSS + user.visible_message("[user] couldn't do it!") + return SHAME + +/obj/item/shovel/spade + name = "spade" + desc = "A small tool for digging and moving dirt." + icon_state = "spade" + inhand_icon_state = "spade" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + +/obj/item/shovel/serrated + name = "serrated bone shovel" + desc = "A wicked tool that cleaves through dirt just as easily as it does flesh. The design was styled after ancient lavaland tribal designs." + icon_state = "shovel_bone" + inhand_icon_state = "shovel_bone" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + force = 15 + throwforce = 12 + w_class = WEIGHT_CLASS_NORMAL + toolspeed = 0.7 + attack_verb = list("slashed", "impaled", "stabbed", "sliced") + sharpness = IS_SHARP diff --git a/code/modules/mining/laborcamp/laborshuttle.dm b/code/modules/mining/laborcamp/laborshuttle.dm index 17a0c50eb24..bf8f87cd5b8 100644 --- a/code/modules/mining/laborcamp/laborshuttle.dm +++ b/code/modules/mining/laborcamp/laborshuttle.dm @@ -1,27 +1,27 @@ -/obj/machinery/computer/shuttle/labor - name = "labor shuttle console" - desc = "Used to call and send the labor camp shuttle." - circuit = /obj/item/circuitboard/computer/labor_shuttle - shuttleId = "laborcamp" - possible_destinations = "laborcamp_home;laborcamp_away" - req_access = list(ACCESS_BRIG) - - -/obj/machinery/computer/shuttle/labor/one_way - name = "prisoner shuttle console" - desc = "A one-way shuttle console, used to summon the shuttle to the labor camp." - possible_destinations = "laborcamp_away" - circuit = /obj/item/circuitboard/computer/labor_shuttle/one_way - req_access = list( ) - -/obj/machinery/computer/shuttle/labor/one_way/Topic(href, href_list) - if(href_list["move"]) - var/obj/docking_port/mobile/M = SSshuttle.getShuttle("laborcamp") - if(!M) - to_chat(usr, "Cannot locate shuttle!") - return 0 - var/obj/docking_port/stationary/S = M.get_docked() - if(S && S.name == "laborcamp_away") - to_chat(usr, "Shuttle is already at the outpost!") - return 0 - ..() +/obj/machinery/computer/shuttle/labor + name = "labor shuttle console" + desc = "Used to call and send the labor camp shuttle." + circuit = /obj/item/circuitboard/computer/labor_shuttle + shuttleId = "laborcamp" + possible_destinations = "laborcamp_home;laborcamp_away" + req_access = list(ACCESS_BRIG) + + +/obj/machinery/computer/shuttle/labor/one_way + name = "prisoner shuttle console" + desc = "A one-way shuttle console, used to summon the shuttle to the labor camp." + possible_destinations = "laborcamp_away" + circuit = /obj/item/circuitboard/computer/labor_shuttle/one_way + req_access = list( ) + +/obj/machinery/computer/shuttle/labor/one_way/Topic(href, href_list) + if(href_list["move"]) + var/obj/docking_port/mobile/M = SSshuttle.getShuttle("laborcamp") + if(!M) + to_chat(usr, "Cannot locate shuttle!") + return 0 + var/obj/docking_port/stationary/S = M.get_docked() + if(S && S.name == "laborcamp_away") + to_chat(usr, "Shuttle is already at the outpost!") + return 0 + ..() diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index 60a34385841..44aa3ebec7f 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -1,161 +1,161 @@ -GLOBAL_LIST(labor_sheet_values) - -/**********************Prisoners' Console**************************/ - -/obj/machinery/mineral/labor_claim_console - name = "point claim console" - desc = "A stacking console with an electromagnetic writer, used to track ore mined by prisoners." - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - density = FALSE - ui_x = 315 - ui_y = 430 - - var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null - var/machinedir = SOUTH - var/obj/machinery/door/airlock/release_door - var/door_tag = "prisonshuttle" - var/obj/item/radio/Radio //needed to send messages to sec radio - -/obj/machinery/mineral/labor_claim_console/Initialize() - . = ..() - Radio = new/obj/item/radio(src) - Radio.listening = FALSE - locate_stacking_machine() - - if(!GLOB.labor_sheet_values) - var/sheet_list = list() - for(var/sheet_type in subtypesof(/obj/item/stack/sheet)) - var/obj/item/stack/sheet/sheet = sheet_type - if(!initial(sheet.point_value) || (initial(sheet.merge_type) && initial(sheet.merge_type) != sheet_type)) //ignore no-value sheets and x/fifty subtypes - continue - sheet_list += list(list("ore" = initial(sheet.name), "value" = initial(sheet.point_value))) - GLOB.labor_sheet_values = sortList(sheet_list, /proc/cmp_sheet_list) - -/proc/cmp_sheet_list(list/a, list/b) - return a["value"] - b["value"] - -/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "LaborClaimConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/mineral/labor_claim_console/ui_data(mob/user) - var/list/data = list() - var/can_go_home = FALSE - - data["emagged"] = (obj_flags & EMAGGED) ? 1 : 0 - if(obj_flags & EMAGGED) - can_go_home = TRUE - - var/obj/item/card/id/I = user.get_idcard(TRUE) - if(istype(I, /obj/item/card/id/prisoner)) - var/obj/item/card/id/prisoner/P = I - data["id_points"] = P.points - if(P.points >= P.goal) - can_go_home = TRUE - data["status_info"] = "Goal met!" - else - data["status_info"] = "You are [(P.goal - P.points)] points away." - else - data["status_info"] = "No Prisoner ID detected." - data["id_points"] = 0 - - if(stacking_machine) - data["unclaimed_points"] = stacking_machine.points - - data["ores"] = GLOB.labor_sheet_values - data["can_go_home"] = can_go_home - - return data - -/obj/machinery/mineral/labor_claim_console/ui_act(action, params) - if(..()) - return - switch(action) - if("claim_points") - var/mob/M = usr - var/obj/item/card/id/I = M.get_idcard(TRUE) - if(istype(I, /obj/item/card/id/prisoner)) - var/obj/item/card/id/prisoner/P = I - P.points += stacking_machine.points - stacking_machine.points = 0 - to_chat(usr, "Points transferred.") - . = TRUE - else - to_chat(usr, "No valid id for point transfer detected.") - if("move_shuttle") - if(!alone_in_area(get_area(src), usr)) - to_chat(usr, "Prisoners are only allowed to be released while alone.") - else - switch(SSshuttle.moveShuttle("laborcamp", "laborcamp_home", TRUE)) - if(1) - to_chat(usr, "Shuttle not found.") - if(2) - to_chat(usr, "Shuttle already at station.") - if(3) - to_chat(usr, "No permission to dock could be granted.") - else - if(!(obj_flags & EMAGGED)) - Radio.set_frequency(FREQ_SECURITY) - Radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY) - to_chat(usr, "Shuttle received message and will be sent shortly.") - . = TRUE - -/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() - stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) - if(stacking_machine) - stacking_machine.CONSOLE = src - else - qdel(src) - -/obj/machinery/mineral/labor_claim_console/emag_act(mob/user) - if(!(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") - -/**********************Prisoner Collection Unit**************************/ - -/obj/machinery/mineral/stacking_machine/laborstacker - force_connect = TRUE - var/points = 0 //The unclaimed value of ore stacked. - damage_deflection = 21 -/obj/machinery/mineral/stacking_machine/laborstacker/process_sheet(obj/item/stack/sheet/inp) - points += inp.point_value * inp.amount - ..() - -/obj/machinery/mineral/stacking_machine/laborstacker/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/stack/sheet) && user.canUnEquip(I)) - var/obj/item/stack/sheet/inp = I - points += inp.point_value * inp.amount - return ..() - -/**********************Point Lookup Console**************************/ - -/obj/machinery/mineral/labor_points_checker - name = "points checking console" - desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID." - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - density = FALSE - -/obj/machinery/mineral/labor_points_checker/attack_hand(mob/user) - . = ..() - if(.) - return - user.examinate(src) - -/obj/machinery/mineral/labor_points_checker/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/card/id)) - if(istype(I, /obj/item/card/id/prisoner)) - var/obj/item/card/id/prisoner/prisoner_id = I - to_chat(user, "ID: [prisoner_id.registered_name]") - to_chat(user, "Points Collected:[prisoner_id.points]") - to_chat(user, "Point Quota: [prisoner_id.goal]") - to_chat(user, "Collect points by bringing smelted minerals to the Labor Shuttle stacking machine. Reach your quota to earn your release.") - else - to_chat(user, "Error: Invalid ID") - else - return ..() +GLOBAL_LIST(labor_sheet_values) + +/**********************Prisoners' Console**************************/ + +/obj/machinery/mineral/labor_claim_console + name = "point claim console" + desc = "A stacking console with an electromagnetic writer, used to track ore mined by prisoners." + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = FALSE + ui_x = 315 + ui_y = 430 + + var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null + var/machinedir = SOUTH + var/obj/machinery/door/airlock/release_door + var/door_tag = "prisonshuttle" + var/obj/item/radio/Radio //needed to send messages to sec radio + +/obj/machinery/mineral/labor_claim_console/Initialize() + . = ..() + Radio = new/obj/item/radio(src) + Radio.listening = FALSE + locate_stacking_machine() + + if(!GLOB.labor_sheet_values) + var/sheet_list = list() + for(var/sheet_type in subtypesof(/obj/item/stack/sheet)) + var/obj/item/stack/sheet/sheet = sheet_type + if(!initial(sheet.point_value) || (initial(sheet.merge_type) && initial(sheet.merge_type) != sheet_type)) //ignore no-value sheets and x/fifty subtypes + continue + sheet_list += list(list("ore" = initial(sheet.name), "value" = initial(sheet.point_value))) + GLOB.labor_sheet_values = sortList(sheet_list, /proc/cmp_sheet_list) + +/proc/cmp_sheet_list(list/a, list/b) + return a["value"] - b["value"] + +/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "LaborClaimConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/mineral/labor_claim_console/ui_data(mob/user) + var/list/data = list() + var/can_go_home = FALSE + + data["emagged"] = (obj_flags & EMAGGED) ? 1 : 0 + if(obj_flags & EMAGGED) + can_go_home = TRUE + + var/obj/item/card/id/I = user.get_idcard(TRUE) + if(istype(I, /obj/item/card/id/prisoner)) + var/obj/item/card/id/prisoner/P = I + data["id_points"] = P.points + if(P.points >= P.goal) + can_go_home = TRUE + data["status_info"] = "Goal met!" + else + data["status_info"] = "You are [(P.goal - P.points)] points away." + else + data["status_info"] = "No Prisoner ID detected." + data["id_points"] = 0 + + if(stacking_machine) + data["unclaimed_points"] = stacking_machine.points + + data["ores"] = GLOB.labor_sheet_values + data["can_go_home"] = can_go_home + + return data + +/obj/machinery/mineral/labor_claim_console/ui_act(action, params) + if(..()) + return + switch(action) + if("claim_points") + var/mob/M = usr + var/obj/item/card/id/I = M.get_idcard(TRUE) + if(istype(I, /obj/item/card/id/prisoner)) + var/obj/item/card/id/prisoner/P = I + P.points += stacking_machine.points + stacking_machine.points = 0 + to_chat(usr, "Points transferred.") + . = TRUE + else + to_chat(usr, "No valid id for point transfer detected.") + if("move_shuttle") + if(!alone_in_area(get_area(src), usr)) + to_chat(usr, "Prisoners are only allowed to be released while alone.") + else + switch(SSshuttle.moveShuttle("laborcamp", "laborcamp_home", TRUE)) + if(1) + to_chat(usr, "Shuttle not found.") + if(2) + to_chat(usr, "Shuttle already at station.") + if(3) + to_chat(usr, "No permission to dock could be granted.") + else + if(!(obj_flags & EMAGGED)) + Radio.set_frequency(FREQ_SECURITY) + Radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY) + to_chat(usr, "Shuttle received message and will be sent shortly.") + . = TRUE + +/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() + stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) + if(stacking_machine) + stacking_machine.CONSOLE = src + else + qdel(src) + +/obj/machinery/mineral/labor_claim_console/emag_act(mob/user) + if(!(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") + +/**********************Prisoner Collection Unit**************************/ + +/obj/machinery/mineral/stacking_machine/laborstacker + force_connect = TRUE + var/points = 0 //The unclaimed value of ore stacked. + damage_deflection = 21 +/obj/machinery/mineral/stacking_machine/laborstacker/process_sheet(obj/item/stack/sheet/inp) + points += inp.point_value * inp.amount + ..() + +/obj/machinery/mineral/stacking_machine/laborstacker/attackby(obj/item/I, mob/living/user) + if(istype(I, /obj/item/stack/sheet) && user.canUnEquip(I)) + var/obj/item/stack/sheet/inp = I + points += inp.point_value * inp.amount + return ..() + +/**********************Point Lookup Console**************************/ + +/obj/machinery/mineral/labor_points_checker + name = "points checking console" + desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID." + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = FALSE + +/obj/machinery/mineral/labor_points_checker/attack_hand(mob/user) + . = ..() + if(.) + return + user.examinate(src) + +/obj/machinery/mineral/labor_points_checker/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/card/id)) + if(istype(I, /obj/item/card/id/prisoner)) + var/obj/item/card/id/prisoner/prisoner_id = I + to_chat(user, "ID: [prisoner_id.registered_name]") + to_chat(user, "Points Collected:[prisoner_id.points]") + to_chat(user, "Point Quota: [prisoner_id.goal]") + to_chat(user, "Collect points by bringing smelted minerals to the Labor Shuttle stacking machine. Reach your quota to earn your release.") + else + to_chat(user, "Error: Invalid ID") + else + return ..() diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index daf651e9db5..e92cca5173c 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -1,263 +1,263 @@ -#define SMELT_AMOUNT 10 - -/**********************Mineral processing unit console**************************/ - -/obj/machinery/mineral - processing_flags = START_PROCESSING_MANUALLY - subsystem_type = /datum/controller/subsystem/processing/fastprocess - /// The current direction of `input_turf`, in relation to the machine. - var/input_dir = NORTH - /// The current direction, in relation to the machine, that items will be output to. - var/output_dir = SOUTH - /// The turf the machines listens to for items to pick up. Calls the `pickup_item()` proc. - var/turf/input_turf = null - /// Determines if this machine needs to pick up items. Used to avoid registering signals to `/mineral` machines that don't pickup items. - var/needs_item_input = FALSE - -/obj/machinery/mineral/Initialize(mapload) - . = ..() - if(needs_item_input && anchored) - register_input_turf() - -/// Gets the turf in the `input_dir` direction adjacent to the machine, and registers signals for ATOM_ENTERED and ATOM_CREATED. Calls the `pickup_item()` proc when it receives these signals. -/obj/machinery/mineral/proc/register_input_turf() - input_turf = get_step(src, input_dir) - if(input_turf) // make sure there is actually a turf - RegisterSignal(input_turf, list(COMSIG_ATOM_CREATED, COMSIG_ATOM_ENTERED), .proc/pickup_item) - -/// Unregisters signals that are registered the machine's input turf, if it has one. -/obj/machinery/mineral/proc/unregister_input_turf() - if(input_turf) - UnregisterSignal(input_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_CREATED)) - -/obj/machinery/mineral/Moved() - . = ..() - if(!needs_item_input || !anchored) - return - unregister_input_turf() - register_input_turf() - -/** - Base proc for all `/mineral` subtype machines to use. Place your item pickup behavior in this proc when you override it for your specific machine. - - Called when the COMSIG_ATOM_ENTERED and COMSIG_ATOM_CREATED signals are sent. - - Arguments: - * source - the turf that is listening for the signals. - * target - the atom that just moved onto the `source` turf. - * oldLoc - the old location that `target` was at before moving onto `source`. -*/ -/obj/machinery/mineral/proc/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - return - -/// Generic unloading proc. Takes an atom as an argument and forceMove's it to the turf adjacent to this machine in the `output_dir` direction. -/obj/machinery/mineral/proc/unload_mineral(atom/movable/S) - S.forceMove(drop_location()) - var/turf/T = get_step(src,output_dir) - if(T) - S.forceMove(T) - -/obj/machinery/mineral/processing_unit_console - name = "production machine console" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - density = TRUE - var/obj/machinery/mineral/processing_unit/machine = null - var/machinedir = EAST - -/obj/machinery/mineral/processing_unit_console/Initialize() - . = ..() - machine = locate(/obj/machinery/mineral/processing_unit, get_step(src, machinedir)) - if (machine) - machine.CONSOLE = src - else - return INITIALIZE_HINT_QDEL - -/obj/machinery/mineral/processing_unit_console/ui_interact(mob/user) - . = ..() - if(!machine) - return - - var/dat = machine.get_machine_data() - - var/datum/browser/popup = new(user, "processing", "Smelting Console", 300, 500) - popup.set_content(dat) - popup.open() - -/obj/machinery/mineral/processing_unit_console/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(href_list["material"]) - var/datum/material/new_material = locate(href_list["material"]) - if(istype(new_material)) - machine.selected_material = new_material - machine.selected_alloy = null - - if(href_list["alloy"]) - machine.selected_material = null - machine.selected_alloy = href_list["alloy"] - - if(href_list["set_on"]) - machine.on = (href_list["set_on"] == "on") - machine.begin_processing() - - updateUsrDialog() - return - -/obj/machinery/mineral/processing_unit_console/Destroy() - machine = null - return ..() - - -/**********************Mineral processing unit**************************/ - - -/obj/machinery/mineral/processing_unit - name = "furnace" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "furnace" - density = TRUE - needs_item_input = TRUE - var/obj/machinery/mineral/CONSOLE = null - var/on = FALSE - var/datum/material/selected_material = null - var/selected_alloy = null - var/datum/techweb/stored_research - -/obj/machinery/mineral/processing_unit/Initialize() - . = ..() - proximity_monitor = new(src, 1) - AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/gold, /datum/material/diamond, /datum/material/plasma, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, TRUE, /obj/item/stack) - stored_research = new /datum/techweb/specialized/autounlocking/smelter - selected_material = SSmaterials.GetMaterialRef(/datum/material/iron) - -/obj/machinery/mineral/processing_unit/Destroy() - CONSOLE = null - QDEL_NULL(stored_research) - return ..() - -/obj/machinery/mineral/processing_unit/proc/process_ore(obj/item/stack/ore/O) - if(QDELETED(O)) - return - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/material_amount = materials.get_item_material_amount(O) - if(!materials.has_space(material_amount)) - unload_mineral(O) - else - materials.insert_item(O) - qdel(O) - if(CONSOLE) - CONSOLE.updateUsrDialog() - -/obj/machinery/mineral/processing_unit/proc/get_machine_data() - var/dat = "Smelter control console

                " - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/datum/material/M in materials.materials) - var/amount = materials.materials[M] - dat += "[M.name]: [amount] cm³" - if (selected_material == M) - dat += " Smelting" - else - dat += " Not Smelting " - dat += "
                " - - dat += "

                " - dat += "Smelt Alloys
                " - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - dat += "[D.name] " - if (selected_alloy == D.id) - dat += " Smelting" - else - dat += " Not Smelting " - dat += "
                " - - dat += "

                " - //On or off - dat += "Machine is currently " - if (on) - dat += "On " - else - dat += "Off " - - return dat - -/obj/machinery/mineral/processing_unit/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - if(QDELETED(target)) - return - if(istype(target, /obj/item/stack/ore)) - process_ore(target) - -/obj/machinery/mineral/processing_unit/process() - if(on) - if(selected_material) - smelt_ore() - - else if(selected_alloy) - smelt_alloy() - - - if(CONSOLE) - CONSOLE.updateUsrDialog() - else - end_processing() - -/obj/machinery/mineral/processing_unit/proc/smelt_ore() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/datum/material/mat = selected_material - if(mat) - var/sheets_to_remove = (materials.materials[mat] >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT) ) ? SMELT_AMOUNT : round(materials.materials[mat] / MINERAL_MATERIAL_AMOUNT) - if(!sheets_to_remove) - on = FALSE - else - var/out = get_step(src, output_dir) - materials.retrieve_sheets(sheets_to_remove, mat, out) - - -/obj/machinery/mineral/processing_unit/proc/smelt_alloy() - var/datum/design/alloy = stored_research.isDesignResearchedID(selected_alloy) //check if it's a valid design - if(!alloy) - on = FALSE - return - - var/amount = can_smelt(alloy) - - if(!amount) - on = FALSE - return - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.use_materials(alloy.materials, amount) - - generate_mineral(alloy.build_path) - -/obj/machinery/mineral/processing_unit/proc/can_smelt(datum/design/D) - if(D.make_reagents.len) - return FALSE - - var/build_amount = SMELT_AMOUNT - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - - for(var/mat_cat in D.materials) - var/required_amount = D.materials[mat_cat] - var/amount = materials.materials[mat_cat] - - build_amount = min(build_amount, round(amount / required_amount)) - - return build_amount - -/obj/machinery/mineral/processing_unit/proc/generate_mineral(P) - var/O = new P(src) - unload_mineral(O) - -/obj/machinery/mineral/processing_unit/on_deconstruction() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.retrieve_all() - ..() - -#undef SMELT_AMOUNT +#define SMELT_AMOUNT 10 + +/**********************Mineral processing unit console**************************/ + +/obj/machinery/mineral + processing_flags = START_PROCESSING_MANUALLY + subsystem_type = /datum/controller/subsystem/processing/fastprocess + /// The current direction of `input_turf`, in relation to the machine. + var/input_dir = NORTH + /// The current direction, in relation to the machine, that items will be output to. + var/output_dir = SOUTH + /// The turf the machines listens to for items to pick up. Calls the `pickup_item()` proc. + var/turf/input_turf = null + /// Determines if this machine needs to pick up items. Used to avoid registering signals to `/mineral` machines that don't pickup items. + var/needs_item_input = FALSE + +/obj/machinery/mineral/Initialize(mapload) + . = ..() + if(needs_item_input && anchored) + register_input_turf() + +/// Gets the turf in the `input_dir` direction adjacent to the machine, and registers signals for ATOM_ENTERED and ATOM_CREATED. Calls the `pickup_item()` proc when it receives these signals. +/obj/machinery/mineral/proc/register_input_turf() + input_turf = get_step(src, input_dir) + if(input_turf) // make sure there is actually a turf + RegisterSignal(input_turf, list(COMSIG_ATOM_CREATED, COMSIG_ATOM_ENTERED), .proc/pickup_item) + +/// Unregisters signals that are registered the machine's input turf, if it has one. +/obj/machinery/mineral/proc/unregister_input_turf() + if(input_turf) + UnregisterSignal(input_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_CREATED)) + +/obj/machinery/mineral/Moved() + . = ..() + if(!needs_item_input || !anchored) + return + unregister_input_turf() + register_input_turf() + +/** + Base proc for all `/mineral` subtype machines to use. Place your item pickup behavior in this proc when you override it for your specific machine. + + Called when the COMSIG_ATOM_ENTERED and COMSIG_ATOM_CREATED signals are sent. + + Arguments: + * source - the turf that is listening for the signals. + * target - the atom that just moved onto the `source` turf. + * oldLoc - the old location that `target` was at before moving onto `source`. +*/ +/obj/machinery/mineral/proc/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + return + +/// Generic unloading proc. Takes an atom as an argument and forceMove's it to the turf adjacent to this machine in the `output_dir` direction. +/obj/machinery/mineral/proc/unload_mineral(atom/movable/S) + S.forceMove(drop_location()) + var/turf/T = get_step(src,output_dir) + if(T) + S.forceMove(T) + +/obj/machinery/mineral/processing_unit_console + name = "production machine console" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = TRUE + var/obj/machinery/mineral/processing_unit/machine = null + var/machinedir = EAST + +/obj/machinery/mineral/processing_unit_console/Initialize() + . = ..() + machine = locate(/obj/machinery/mineral/processing_unit, get_step(src, machinedir)) + if (machine) + machine.CONSOLE = src + else + return INITIALIZE_HINT_QDEL + +/obj/machinery/mineral/processing_unit_console/ui_interact(mob/user) + . = ..() + if(!machine) + return + + var/dat = machine.get_machine_data() + + var/datum/browser/popup = new(user, "processing", "Smelting Console", 300, 500) + popup.set_content(dat) + popup.open() + +/obj/machinery/mineral/processing_unit_console/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + add_fingerprint(usr) + + if(href_list["material"]) + var/datum/material/new_material = locate(href_list["material"]) + if(istype(new_material)) + machine.selected_material = new_material + machine.selected_alloy = null + + if(href_list["alloy"]) + machine.selected_material = null + machine.selected_alloy = href_list["alloy"] + + if(href_list["set_on"]) + machine.on = (href_list["set_on"] == "on") + machine.begin_processing() + + updateUsrDialog() + return + +/obj/machinery/mineral/processing_unit_console/Destroy() + machine = null + return ..() + + +/**********************Mineral processing unit**************************/ + + +/obj/machinery/mineral/processing_unit + name = "furnace" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "furnace" + density = TRUE + needs_item_input = TRUE + var/obj/machinery/mineral/CONSOLE = null + var/on = FALSE + var/datum/material/selected_material = null + var/selected_alloy = null + var/datum/techweb/stored_research + +/obj/machinery/mineral/processing_unit/Initialize() + . = ..() + proximity_monitor = new(src, 1) + AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/gold, /datum/material/diamond, /datum/material/plasma, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, TRUE, /obj/item/stack) + stored_research = new /datum/techweb/specialized/autounlocking/smelter + selected_material = SSmaterials.GetMaterialRef(/datum/material/iron) + +/obj/machinery/mineral/processing_unit/Destroy() + CONSOLE = null + QDEL_NULL(stored_research) + return ..() + +/obj/machinery/mineral/processing_unit/proc/process_ore(obj/item/stack/ore/O) + if(QDELETED(O)) + return + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/material_amount = materials.get_item_material_amount(O) + if(!materials.has_space(material_amount)) + unload_mineral(O) + else + materials.insert_item(O) + qdel(O) + if(CONSOLE) + CONSOLE.updateUsrDialog() + +/obj/machinery/mineral/processing_unit/proc/get_machine_data() + var/dat = "Smelter control console

                " + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/M in materials.materials) + var/amount = materials.materials[M] + dat += "[M.name]: [amount] cm³" + if (selected_material == M) + dat += " Smelting" + else + dat += " Not Smelting " + dat += "
                " + + dat += "

                " + dat += "Smelt Alloys
                " + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + dat += "[D.name] " + if (selected_alloy == D.id) + dat += " Smelting" + else + dat += " Not Smelting " + dat += "
                " + + dat += "

                " + //On or off + dat += "Machine is currently " + if (on) + dat += "On " + else + dat += "Off " + + return dat + +/obj/machinery/mineral/processing_unit/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(QDELETED(target)) + return + if(istype(target, /obj/item/stack/ore)) + process_ore(target) + +/obj/machinery/mineral/processing_unit/process() + if(on) + if(selected_material) + smelt_ore() + + else if(selected_alloy) + smelt_alloy() + + + if(CONSOLE) + CONSOLE.updateUsrDialog() + else + end_processing() + +/obj/machinery/mineral/processing_unit/proc/smelt_ore() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/datum/material/mat = selected_material + if(mat) + var/sheets_to_remove = (materials.materials[mat] >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT) ) ? SMELT_AMOUNT : round(materials.materials[mat] / MINERAL_MATERIAL_AMOUNT) + if(!sheets_to_remove) + on = FALSE + else + var/out = get_step(src, output_dir) + materials.retrieve_sheets(sheets_to_remove, mat, out) + + +/obj/machinery/mineral/processing_unit/proc/smelt_alloy() + var/datum/design/alloy = stored_research.isDesignResearchedID(selected_alloy) //check if it's a valid design + if(!alloy) + on = FALSE + return + + var/amount = can_smelt(alloy) + + if(!amount) + on = FALSE + return + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.use_materials(alloy.materials, amount) + + generate_mineral(alloy.build_path) + +/obj/machinery/mineral/processing_unit/proc/can_smelt(datum/design/D) + if(D.make_reagents.len) + return FALSE + + var/build_amount = SMELT_AMOUNT + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + + for(var/mat_cat in D.materials) + var/required_amount = D.materials[mat_cat] + var/amount = materials.materials[mat_cat] + + build_amount = min(build_amount, round(amount / required_amount)) + + return build_amount + +/obj/machinery/mineral/processing_unit/proc/generate_mineral(P) + var/O = new P(src) + unload_mineral(O) + +/obj/machinery/mineral/processing_unit/on_deconstruction() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.retrieve_all() + ..() + +#undef SMELT_AMOUNT diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index 4b8279083ef..6182cf4dca0 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -1,133 +1,133 @@ -/**********************Mineral stacking unit console**************************/ - -/obj/machinery/mineral/stacking_unit_console - name = "stacking machine console" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - desc = "Controls a stacking machine... in theory." - density = FALSE - circuit = /obj/item/circuitboard/machine/stacking_unit_console - var/obj/machinery/mineral/stacking_machine/machine - var/machinedir = SOUTHEAST - -/obj/machinery/mineral/stacking_unit_console/Initialize() - . = ..() - machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) - if (machine) - machine.CONSOLE = src - -/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user) - . = ..() - - if(!machine) - to_chat(user, "[src] is not linked to a machine!") - return - - var/obj/item/stack/sheet/s - var/dat - - dat += text("Stacking unit console

                ") - - for(var/O in machine.stack_list) - s = machine.stack_list[O] - if(s.amount > 0) - dat += text("[capitalize(s.name)]: [s.amount] Release
                ") - - dat += text("
                Stacking: [machine.stack_amt]

                ") - - user << browse(dat, "window=console_stacking_machine") - -/obj/machinery/mineral/stacking_unit_console/multitool_act(mob/living/user, obj/item/I) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You store linkage information in [I]'s buffer.") - return TRUE - -/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - src.add_fingerprint(usr) - if(href_list["release"]) - if(!(text2path(href_list["release"]) in machine.stack_list)) - return //someone tried to spawn materials by spoofing hrefs - var/obj/item/stack/sheet/inp = machine.stack_list[text2path(href_list["release"])] - var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) - inp.amount = 0 - machine.unload_mineral(out) - - src.updateUsrDialog() - return - - -/**********************Mineral stacking unit**************************/ - - -/obj/machinery/mineral/stacking_machine - name = "stacking machine" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "stacker" - desc = "A machine that automatically stacks acquired materials. Controlled by a nearby console." - density = TRUE - circuit = /obj/item/circuitboard/machine/stacking_machine - input_dir = EAST - output_dir = WEST - var/obj/machinery/mineral/stacking_unit_console/CONSOLE - var/stk_types = list() - var/stk_amt = list() - var/stack_list[0] //Key: Type. Value: Instance of type. - var/stack_amt = 50 //amount to stack before releassing - var/datum/component/remote_materials/materials - var/force_connect = FALSE - -/obj/machinery/mineral/stacking_machine/Initialize(mapload) - . = ..() - proximity_monitor = new(src, 1) - materials = AddComponent(/datum/component/remote_materials, "stacking", mapload, FALSE, mapload && force_connect) - -/obj/machinery/mineral/stacking_machine/Destroy() - CONSOLE = null - materials = null - return ..() - -/obj/machinery/mineral/stacking_machine/HasProximity(atom/movable/AM) - if(QDELETED(AM)) - return - if(istype(AM, /obj/item/stack/sheet) && AM.loc == get_step(src, input_dir)) - process_sheet(AM) - -/obj/machinery/mineral/stacking_machine/multitool_act(mob/living/user, obj/item/multitool/M) - if(istype(M)) - if(istype(M.buffer, /obj/machinery/mineral/stacking_unit_console)) - CONSOLE = M.buffer - CONSOLE.machine = src - to_chat(user, "You link [src] to the console in [M]'s buffer.") - return TRUE - -/obj/machinery/mineral/stacking_machine/proc/process_sheet(obj/item/stack/sheet/inp) - if(QDELETED(inp)) - return - - // Dump the sheets to the silo if attached - if(materials.silo && !materials.on_hold()) - var/matlist = inp.custom_materials & materials.mat_container.materials - if (length(matlist)) - var/inserted = materials.mat_container.insert_item(inp) - materials.silo_log(src, "collected", inserted, "sheets", matlist) - qdel(inp) - return - - // No silo attached process to internal storage - var/key = inp.merge_type - var/obj/item/stack/sheet/storage = stack_list[key] - if(!storage) //It's the first of this sheet added - stack_list[key] = storage = new inp.type(src, 0) - storage.amount += inp.amount //Stack the sheets - qdel(inp) - - while(storage.amount >= stack_amt) //Get rid of excessive stackage - var/obj/item/stack/sheet/out = new inp.type(null, stack_amt) - unload_mineral(out) - storage.amount -= stack_amt +/**********************Mineral stacking unit console**************************/ + +/obj/machinery/mineral/stacking_unit_console + name = "stacking machine console" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + desc = "Controls a stacking machine... in theory." + density = FALSE + circuit = /obj/item/circuitboard/machine/stacking_unit_console + var/obj/machinery/mineral/stacking_machine/machine + var/machinedir = SOUTHEAST + +/obj/machinery/mineral/stacking_unit_console/Initialize() + . = ..() + machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) + if (machine) + machine.CONSOLE = src + +/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user) + . = ..() + + if(!machine) + to_chat(user, "[src] is not linked to a machine!") + return + + var/obj/item/stack/sheet/s + var/dat + + dat += text("Stacking unit console

                ") + + for(var/O in machine.stack_list) + s = machine.stack_list[O] + if(s.amount > 0) + dat += text("[capitalize(s.name)]: [s.amount] Release
                ") + + dat += text("
                Stacking: [machine.stack_amt]

                ") + + user << browse(dat, "window=console_stacking_machine") + +/obj/machinery/mineral/stacking_unit_console/multitool_act(mob/living/user, obj/item/I) + if(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You store linkage information in [I]'s buffer.") + return TRUE + +/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + src.add_fingerprint(usr) + if(href_list["release"]) + if(!(text2path(href_list["release"]) in machine.stack_list)) + return //someone tried to spawn materials by spoofing hrefs + var/obj/item/stack/sheet/inp = machine.stack_list[text2path(href_list["release"])] + var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) + inp.amount = 0 + machine.unload_mineral(out) + + src.updateUsrDialog() + return + + +/**********************Mineral stacking unit**************************/ + + +/obj/machinery/mineral/stacking_machine + name = "stacking machine" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "stacker" + desc = "A machine that automatically stacks acquired materials. Controlled by a nearby console." + density = TRUE + circuit = /obj/item/circuitboard/machine/stacking_machine + input_dir = EAST + output_dir = WEST + var/obj/machinery/mineral/stacking_unit_console/CONSOLE + var/stk_types = list() + var/stk_amt = list() + var/stack_list[0] //Key: Type. Value: Instance of type. + var/stack_amt = 50 //amount to stack before releassing + var/datum/component/remote_materials/materials + var/force_connect = FALSE + +/obj/machinery/mineral/stacking_machine/Initialize(mapload) + . = ..() + proximity_monitor = new(src, 1) + materials = AddComponent(/datum/component/remote_materials, "stacking", mapload, FALSE, mapload && force_connect) + +/obj/machinery/mineral/stacking_machine/Destroy() + CONSOLE = null + materials = null + return ..() + +/obj/machinery/mineral/stacking_machine/HasProximity(atom/movable/AM) + if(QDELETED(AM)) + return + if(istype(AM, /obj/item/stack/sheet) && AM.loc == get_step(src, input_dir)) + process_sheet(AM) + +/obj/machinery/mineral/stacking_machine/multitool_act(mob/living/user, obj/item/multitool/M) + if(istype(M)) + if(istype(M.buffer, /obj/machinery/mineral/stacking_unit_console)) + CONSOLE = M.buffer + CONSOLE.machine = src + to_chat(user, "You link [src] to the console in [M]'s buffer.") + return TRUE + +/obj/machinery/mineral/stacking_machine/proc/process_sheet(obj/item/stack/sheet/inp) + if(QDELETED(inp)) + return + + // Dump the sheets to the silo if attached + if(materials.silo && !materials.on_hold()) + var/matlist = inp.custom_materials & materials.mat_container.materials + if (length(matlist)) + var/inserted = materials.mat_container.insert_item(inp) + materials.silo_log(src, "collected", inserted, "sheets", matlist) + qdel(inp) + return + + // No silo attached process to internal storage + var/key = inp.merge_type + var/obj/item/stack/sheet/storage = stack_list[key] + if(!storage) //It's the first of this sheet added + stack_list[key] = storage = new inp.type(src, 0) + storage.amount += inp.amount //Stack the sheets + qdel(inp) + + while(storage.amount >= stack_amt) //Get rid of excessive stackage + var/obj/item/stack/sheet/out = new inp.type(null, stack_amt) + unload_mineral(out) + storage.amount -= stack_amt diff --git a/code/modules/mining/machine_unloading.dm b/code/modules/mining/machine_unloading.dm index ffddd0ef6f2..ed8696182aa 100644 --- a/code/modules/mining/machine_unloading.dm +++ b/code/modules/mining/machine_unloading.dm @@ -1,23 +1,23 @@ -/**********************Unloading unit**************************/ - - -/obj/machinery/mineral/unloading_machine - name = "unloading machine" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "unloader" - density = TRUE - input_dir = WEST - output_dir = EAST - needs_item_input = TRUE - processing_flags = START_PROCESSING_MANUALLY - -/obj/machinery/mineral/unloading_machine/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - if(QDELETED(target)) - return - if(istype(target, /obj/structure/ore_box)) - var/obj/structure/ore_box/box = target - for(var/obj/item/stack/ore/O in box) - unload_mineral(O) - else if(istype(target, /obj/item/stack/ore)) - var/obj/item/stack/ore/O = target - unload_mineral(O) +/**********************Unloading unit**************************/ + + +/obj/machinery/mineral/unloading_machine + name = "unloading machine" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "unloader" + density = TRUE + input_dir = WEST + output_dir = EAST + needs_item_input = TRUE + processing_flags = START_PROCESSING_MANUALLY + +/obj/machinery/mineral/unloading_machine/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(QDELETED(target)) + return + if(istype(target, /obj/structure/ore_box)) + var/obj/structure/ore_box/box = target + for(var/obj/item/stack/ore/O in box) + unload_mineral(O) + else if(istype(target, /obj/item/stack/ore)) + var/obj/item/stack/ore/O = target + unload_mineral(O) diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm index 847ac624f87..ce9f7f31a58 100644 --- a/code/modules/mining/mint.dm +++ b/code/modules/mining/mint.dm @@ -1,148 +1,148 @@ -/**********************Mint**************************/ - - -/obj/machinery/mineral/mint - name = "coin press" - icon = 'icons/obj/economy.dmi' - icon_state = "coinpress0" - density = TRUE - input_dir = EAST - ui_x = 300 - ui_y = 250 - needs_item_input = TRUE - var/obj/item/storage/bag/money/bag_to_use - var/produced_coins = 0 // how many coins the machine has made in it's last cycle - var/processing = FALSE - var/chosen = /datum/material/iron //which material will be used to make coins - - -/obj/machinery/mineral/mint/Initialize() - . = ..() - AddComponent(/datum/component/material_container, list( - /datum/material/iron, - /datum/material/plasma, - /datum/material/silver, - /datum/material/gold, - /datum/material/uranium, - /datum/material/titanium, - /datum/material/diamond, - /datum/material/bananium, - /datum/material/adamantine, - /datum/material/mythril, - /datum/material/plastic, - /datum/material/runite - ), MINERAL_MATERIAL_AMOUNT * 75, FALSE, /obj/item/stack) - chosen = SSmaterials.GetMaterialRef(chosen) - - -/obj/machinery/mineral/mint/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - if(QDELETED(target)) - return - if(!istype(target, /obj/item/stack)) - return - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/obj/item/stack/S = target - - if(materials.insert_item(S)) - qdel(S) - -/obj/machinery/mineral/mint/process() - if(processing) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/datum/material/M = chosen - - if(!M) - processing = FALSE - icon_state = "coinpress0" - return - - icon_state = "coinpress1" - var/coin_mat = MINERAL_MATERIAL_AMOUNT - - for(var/sheets in 1 to 2) - if(materials.use_amount_mat(coin_mat, chosen)) - for(var/coin_to_make in 1 to 5) - create_coins() - produced_coins++ - CHECK_TICK - else - var/found_new = FALSE - for(var/datum/material/inserted_material in materials.materials) - var/amount = materials.get_material_amount(inserted_material) - - if(amount) - chosen = inserted_material - found_new = TRUE - - if(!found_new) - processing = FALSE - else - end_processing() - icon_state = "coinpress0" - -/obj/machinery/mineral/mint/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Mint", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/mineral/mint/ui_data() - var/list/data = list() - data["inserted_materials"] = list() - data["chosen_material"] = null - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/datum/material/inserted_material in materials.materials) - var/amount = materials.get_material_amount(inserted_material) - if(!amount) - continue - data["inserted_materials"] += list(list( - "material" = inserted_material.name, - "amount" = amount, - )) - if(chosen == inserted_material) - data["chosen_material"] = inserted_material.name - - data["produced_coins"] = produced_coins - data["processing"] = processing - - return data; - -/obj/machinery/mineral/mint/ui_act(action, params, datum/tgui/ui) - . = ..() - if(.) - return - if(action == "startpress") - if (!processing) - if(produced_coins > 0) - log_econ("[produced_coins] coins were created by [src] in the last cycle.") - produced_coins = 0 - processing = TRUE - begin_processing() - return TRUE - if (action == "stoppress") - processing = FALSE - end_processing() - return TRUE - if (action == "changematerial") - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/datum/material/mat in materials.materials) - if (params["material_name"] == mat.name) - chosen = mat - return TRUE - -/obj/machinery/mineral/mint/proc/create_coins() - var/turf/T = get_step(src,output_dir) - var/temp_list = list() - temp_list[chosen] = 400 - if(T) - var/obj/item/O = new /obj/item/coin(src) - O.set_custom_materials(temp_list) - if(QDELETED(bag_to_use) || (bag_to_use.loc != T) || !SEND_SIGNAL(bag_to_use, COMSIG_TRY_STORAGE_INSERT, O, null, TRUE)) //important to send the signal so we don't overfill the bag. - bag_to_use = new(src) //make a new bag if we can't find or use the old one. - unload_mineral(bag_to_use) //just forcemove memes. - O.forceMove(bag_to_use) //don't bother sending the signal, the new bag is empty and all that. - - SSblackbox.record_feedback("amount", "coins_minted", 1) +/**********************Mint**************************/ + + +/obj/machinery/mineral/mint + name = "coin press" + icon = 'icons/obj/economy.dmi' + icon_state = "coinpress0" + density = TRUE + input_dir = EAST + ui_x = 300 + ui_y = 250 + needs_item_input = TRUE + var/obj/item/storage/bag/money/bag_to_use + var/produced_coins = 0 // how many coins the machine has made in it's last cycle + var/processing = FALSE + var/chosen = /datum/material/iron //which material will be used to make coins + + +/obj/machinery/mineral/mint/Initialize() + . = ..() + AddComponent(/datum/component/material_container, list( + /datum/material/iron, + /datum/material/plasma, + /datum/material/silver, + /datum/material/gold, + /datum/material/uranium, + /datum/material/titanium, + /datum/material/diamond, + /datum/material/bananium, + /datum/material/adamantine, + /datum/material/mythril, + /datum/material/plastic, + /datum/material/runite + ), MINERAL_MATERIAL_AMOUNT * 75, FALSE, /obj/item/stack) + chosen = SSmaterials.GetMaterialRef(chosen) + + +/obj/machinery/mineral/mint/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(QDELETED(target)) + return + if(!istype(target, /obj/item/stack)) + return + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/obj/item/stack/S = target + + if(materials.insert_item(S)) + qdel(S) + +/obj/machinery/mineral/mint/process() + if(processing) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/datum/material/M = chosen + + if(!M) + processing = FALSE + icon_state = "coinpress0" + return + + icon_state = "coinpress1" + var/coin_mat = MINERAL_MATERIAL_AMOUNT + + for(var/sheets in 1 to 2) + if(materials.use_amount_mat(coin_mat, chosen)) + for(var/coin_to_make in 1 to 5) + create_coins() + produced_coins++ + CHECK_TICK + else + var/found_new = FALSE + for(var/datum/material/inserted_material in materials.materials) + var/amount = materials.get_material_amount(inserted_material) + + if(amount) + chosen = inserted_material + found_new = TRUE + + if(!found_new) + processing = FALSE + else + end_processing() + icon_state = "coinpress0" + +/obj/machinery/mineral/mint/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Mint", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/mineral/mint/ui_data() + var/list/data = list() + data["inserted_materials"] = list() + data["chosen_material"] = null + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/inserted_material in materials.materials) + var/amount = materials.get_material_amount(inserted_material) + if(!amount) + continue + data["inserted_materials"] += list(list( + "material" = inserted_material.name, + "amount" = amount, + )) + if(chosen == inserted_material) + data["chosen_material"] = inserted_material.name + + data["produced_coins"] = produced_coins + data["processing"] = processing + + return data; + +/obj/machinery/mineral/mint/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return + if(action == "startpress") + if (!processing) + if(produced_coins > 0) + log_econ("[produced_coins] coins were created by [src] in the last cycle.") + produced_coins = 0 + processing = TRUE + begin_processing() + return TRUE + if (action == "stoppress") + processing = FALSE + end_processing() + return TRUE + if (action == "changematerial") + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/mat in materials.materials) + if (params["material_name"] == mat.name) + chosen = mat + return TRUE + +/obj/machinery/mineral/mint/proc/create_coins() + var/turf/T = get_step(src,output_dir) + var/temp_list = list() + temp_list[chosen] = 400 + if(T) + var/obj/item/O = new /obj/item/coin(src) + O.set_custom_materials(temp_list) + if(QDELETED(bag_to_use) || (bag_to_use.loc != T) || !SEND_SIGNAL(bag_to_use, COMSIG_TRY_STORAGE_INSERT, O, null, TRUE)) //important to send the signal so we don't overfill the bag. + bag_to_use = new(src) //make a new bag if we can't find or use the old one. + unload_mineral(bag_to_use) //just forcemove memes. + O.forceMove(bag_to_use) //don't bother sending the signal, the new bag is empty and all that. + + SSblackbox.record_feedback("amount", "coins_minted", 1) diff --git a/code/modules/mining/money_bag.dm b/code/modules/mining/money_bag.dm index 00e9d52d36d..005694221ec 100644 --- a/code/modules/mining/money_bag.dm +++ b/code/modules/mining/money_bag.dm @@ -1,29 +1,29 @@ -/*****************************Money bag********************************/ - -/obj/item/storage/bag/money - name = "money bag" - icon_state = "moneybag" - force = 10 - throwforce = 0 - resistance_flags = FLAMMABLE - max_integrity = 100 - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/money/Initialize() - . = ..() - if(prob(20)) - icon_state = "moneybagalt" - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_items = 40 - STR.max_combined_w_class = 40 - STR.set_holdable(list(/obj/item/coin, /obj/item/stack/spacecash, /obj/item/holochip)) - -/obj/item/storage/bag/money/vault/PopulateContents() - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/adamantine(src) +/*****************************Money bag********************************/ + +/obj/item/storage/bag/money + name = "money bag" + icon_state = "moneybag" + force = 10 + throwforce = 0 + resistance_flags = FLAMMABLE + max_integrity = 100 + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/money/Initialize() + . = ..() + if(prob(20)) + icon_state = "moneybagalt" + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_items = 40 + STR.max_combined_w_class = 40 + STR.set_holdable(list(/obj/item/coin, /obj/item/stack/spacecash, /obj/item/holochip)) + +/obj/item/storage/bag/money/vault/PopulateContents() + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/adamantine(src) diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 6f023dc0ce4..d92084c9d02 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -1,485 +1,485 @@ - -#define GIBTONITE_QUALITY_HIGH 3 -#define GIBTONITE_QUALITY_MEDIUM 2 -#define GIBTONITE_QUALITY_LOW 1 - -#define ORESTACK_OVERLAYS_MAX 10 - -/**********************Mineral ores**************************/ - -/obj/item/stack/ore - name = "rock" - icon = 'icons/obj/mining.dmi' - icon_state = "ore" - inhand_icon_state = "ore" - full_w_class = WEIGHT_CLASS_BULKY - singular_name = "ore chunk" - var/points = 0 //How many points this ore gets you from the ore redemption machine - var/refined_type = null //What this ore defaults to being refined into - var/mine_experience = 5 //How much experience do you get for mining this ore? - novariants = TRUE // Ore stacks handle their icon updates themselves to keep the illusion that there's more going - var/list/stack_overlays - var/scan_state = "" //Used by mineral turfs for their scan overlay. - var/spreadChance = 0 //Also used by mineral turfs for spreading veins - -/obj/item/stack/ore/update_overlays() - . = ..() - var/difference = min(ORESTACK_OVERLAYS_MAX, amount) - (LAZYLEN(stack_overlays)+1) - if(difference == 0) - return - else if(difference < 0 && LAZYLEN(stack_overlays)) //amount < stack_overlays, remove excess. - if (LAZYLEN(stack_overlays)-difference <= 0) - stack_overlays = null - else - stack_overlays.len += difference - else if(difference > 0) //amount > stack_overlays, add some. - for(var/i in 1 to difference) - var/mutable_appearance/newore = mutable_appearance(icon, icon_state) - newore.pixel_x = rand(-8,8) - newore.pixel_y = rand(-8,8) - LAZYADD(stack_overlays, newore) - if (stack_overlays) - . += stack_overlays - -/obj/item/stack/ore/welder_act(mob/living/user, obj/item/I) - ..() - if(!refined_type) - return TRUE - - if(I.use_tool(src, user, 0, volume=50, amount=15)) - new refined_type(drop_location()) - use(1) - - return TRUE - -/obj/item/stack/ore/fire_act(exposed_temperature, exposed_volume) - . = ..() - if(isnull(refined_type)) - return - else - var/probability = (rand(0,100))/100 - var/burn_value = probability*amount - var/amountrefined = round(burn_value, 1) - if(amountrefined < 1) - qdel(src) - else - new refined_type(drop_location(),amountrefined) - qdel(src) - -/obj/item/stack/ore/uranium - name = "uranium ore" - icon_state = "Uranium ore" - inhand_icon_state = "Uranium ore" - singular_name = "uranium ore chunk" - points = 30 - material_flags = MATERIAL_NO_EFFECTS - custom_materials = list(/datum/material/uranium=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/uranium - mine_experience = 6 - scan_state = "rock_Uranium" - spreadChance = 5 - -/obj/item/stack/ore/iron - name = "iron ore" - icon_state = "Iron ore" - inhand_icon_state = "Iron ore" - singular_name = "iron ore chunk" - points = 1 - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/metal - mine_experience = 1 - scan_state = "rock_Iron" - spreadChance = 20 - -/obj/item/stack/ore/glass - name = "sand pile" - icon_state = "Glass ore" - inhand_icon_state = "Glass ore" - singular_name = "sand pile" - points = 1 - custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/glass - w_class = WEIGHT_CLASS_TINY - mine_experience = 0 //its sand - -GLOBAL_LIST_INIT(sand_recipes, list(\ - new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50),\ - new /datum/stack_recipe("aesthetic volcanic floor tile", /obj/item/stack/tile/basalt, 2, 1, 50)\ -)) - -/obj/item/stack/ore/glass/get_main_recipes() - . = ..() - . += GLOB.sand_recipes - -/obj/item/stack/ore/glass/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(..() || !ishuman(hit_atom)) - return - var/mob/living/carbon/human/C = hit_atom - if(C.is_eyes_covered()) - C.visible_message("[C]'s eye protection blocks the sand!", "Your eye protection blocks the sand!") - return - C.adjust_blurriness(6) - C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage - C.confused += 5 - to_chat(C, "\The [src] gets into your eyes! The pain, it burns!") - qdel(src) - -/obj/item/stack/ore/glass/ex_act(severity, target) - if (severity == EXPLODE_NONE) - return - qdel(src) - -/obj/item/stack/ore/glass/basalt - name = "volcanic ash" - icon_state = "volcanic_sand" - inhand_icon_state = "volcanic_sand" - singular_name = "volcanic ash pile" - mine_experience = 0 - -/obj/item/stack/ore/plasma - name = "plasma ore" - icon_state = "Plasma ore" - inhand_icon_state = "Plasma ore" - singular_name = "plasma ore chunk" - points = 15 - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/plasma - mine_experience = 5 - scan_state = "rock_Plasma" - spreadChance = 8 - -/obj/item/stack/ore/plasma/welder_act(mob/living/user, obj/item/I) - to_chat(user, "You can't hit a high enough temperature to smelt [src] properly!") - return TRUE - - -/obj/item/stack/ore/silver - name = "silver ore" - icon_state = "Silver ore" - inhand_icon_state = "Silver ore" - singular_name = "silver ore chunk" - points = 16 - mine_experience = 3 - custom_materials = list(/datum/material/silver=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/silver - scan_state = "rock_Silver" - spreadChance = 5 - -/obj/item/stack/ore/gold - name = "gold ore" - icon_state = "Gold ore" - inhand_icon_state = "Gold ore" - singular_name = "gold ore chunk" - points = 18 - mine_experience = 5 - custom_materials = list(/datum/material/gold=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/gold - scan_state = "rock_Gold" - spreadChance = 5 - -/obj/item/stack/ore/diamond - name = "diamond ore" - icon_state = "Diamond ore" - inhand_icon_state = "Diamond ore" - singular_name = "diamond ore chunk" - points = 50 - custom_materials = list(/datum/material/diamond=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/diamond - mine_experience = 10 - scan_state = "rock_Diamond" - -/obj/item/stack/ore/bananium - name = "bananium ore" - icon_state = "Bananium ore" - inhand_icon_state = "Bananium ore" - singular_name = "bananium ore chunk" - points = 60 - custom_materials = list(/datum/material/bananium=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/bananium - mine_experience = 15 - scan_state = "rock_Bananium" - -/obj/item/stack/ore/titanium - name = "titanium ore" - icon_state = "Titanium ore" - inhand_icon_state = "Titanium ore" - singular_name = "titanium ore chunk" - points = 50 - custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/titanium - mine_experience = 3 - scan_state = "rock_Titanium" - spreadChance = 5 - -/obj/item/stack/ore/slag - name = "slag" - desc = "Completely useless." - icon_state = "slag" - inhand_icon_state = "slag" - singular_name = "slag chunk" - -/obj/item/gibtonite - name = "gibtonite ore" - desc = "Extremely explosive if struck with mining equipment, Gibtonite is often used by miners to speed up their work by using it as a mining charge. This material is illegal to possess by unauthorized personnel under space law." - icon = 'icons/obj/mining.dmi' - icon_state = "Gibtonite ore" - inhand_icon_state = "Gibtonite ore" - w_class = WEIGHT_CLASS_BULKY - throw_range = 0 - var/primed = FALSE - var/det_time = 100 - var/quality = GIBTONITE_QUALITY_LOW //How pure this gibtonite is, determines the explosion produced by it and is derived from the det_time of the rock wall it was taken from, higher value = better - var/attacher = "UNKNOWN" - var/det_timer - -/obj/item/gibtonite/ComponentInitialize() - . = ..() - AddComponent(/datum/component/two_handed, require_twohands=TRUE) - -/obj/item/gibtonite/Destroy() - qdel(wires) - wires = null - return ..() - -/obj/item/gibtonite/attackby(obj/item/I, mob/user, params) - if(!wires && istype(I, /obj/item/assembly/igniter)) - user.visible_message("[user] attaches [I] to [src].", "You attach [I] to [src].") - wires = new /datum/wires/explosive/gibtonite(src) - attacher = key_name(user) - qdel(I) - add_overlay("Gibtonite_igniter") - return - - if(wires && !primed) - if(is_wire_tool(I)) - wires.interact(user) - return - - if(I.tool_behaviour == TOOL_MINING || istype(I, /obj/item/resonator) || I.force >= 10) - GibtoniteReaction(user) - return - if(primed) - if(istype(I, /obj/item/mining_scanner) || istype(I, /obj/item/t_scanner/adv_mining_scanner) || I.tool_behaviour == TOOL_MULTITOOL) - primed = FALSE - if(det_timer) - deltimer(det_timer) - user.visible_message("The chain reaction stopped! ...The ore's quality looks diminished.", "You stopped the chain reaction. ...The ore's quality looks diminished.") - icon_state = "Gibtonite ore" - quality = GIBTONITE_QUALITY_LOW - return - ..() - -/obj/item/gibtonite/attack_self(user) - if(wires) - wires.interact(user) - else - ..() - -/obj/item/gibtonite/bullet_act(obj/projectile/P) - GibtoniteReaction(P.firer) - . = ..() - -/obj/item/gibtonite/ex_act() - GibtoniteReaction(null, 1) - -/obj/item/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0) - if(!primed) - primed = TRUE - playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,TRUE) - icon_state = "Gibtonite active" - var/notify_admins = FALSE - if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel - notify_admins = TRUE - - if(triggered_by == 1) - log_bomber(null, "An explosion has primed a", src, "for detonation", notify_admins) - else if(triggered_by == 2) - var/turf/bombturf = get_turf(src) - if(notify_admins) - message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") - var/bomb_message = "A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)]." - log_game(bomb_message) - GLOB.bombers += bomb_message - else - user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") - log_bomber(user, "has primed a", src, "for detonation", notify_admins) - det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) - -/obj/item/gibtonite/proc/detonate(notify_admins) - if(primed) - switch(quality) - if(GIBTONITE_QUALITY_HIGH) - explosion(src,2,4,9,adminlog = notify_admins) - if(GIBTONITE_QUALITY_MEDIUM) - explosion(src,1,2,5,adminlog = notify_admins) - if(GIBTONITE_QUALITY_LOW) - explosion(src,0,1,3,adminlog = notify_admins) - qdel(src) - -/obj/item/stack/ore/Initialize() - . = ..() - pixel_x = rand(0,16)-8 - pixel_y = rand(0,8)-8 - -/obj/item/stack/ore/ex_act(severity, target) - if (!severity || severity >= 2) - return - qdel(src) - - -/*****************************Coin********************************/ - -// The coin's value is a value of it's materials. -// Yes, the gold standard makes a come-back! -// This is the only way to make coins that are possible to produce on station actually worth anything. -/obj/item/coin - icon = 'icons/obj/economy.dmi' - name = "coin" - icon_state = "coin" - flags_1 = CONDUCT_1 - force = 1 - throwforce = 2 - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/iron = 400) - material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS - var/string_attached - var/list/sideslist = list("heads","tails") - var/cooldown = 0 - var/value - var/coinflip - item_flags = NO_MAT_REDEMPTION //You know, it's kind of a problem that money is worth more extrinsicly than intrinsically in this universe. - -/obj/item/coin/Initialize() - . = ..() - coinflip = pick(sideslist) - icon_state = "coin_[coinflip]" - pixel_x = rand(0,16)-8 - pixel_y = rand(0,8)-8 - -/obj/item/coin/set_custom_materials(var/list/materials, multiplier = 1) - . = ..() - value = 0 - for(var/i in custom_materials) - var/datum/material/M = i - value += M.value_per_unit * custom_materials[M] - -/obj/item/coin/get_item_credit_value() - return value - -/obj/item/coin/suicide_act(mob/living/user) - user.visible_message("[user] contemplates suicide with \the [src]!") - if (!attack_self(user)) - user.visible_message("[user] couldn't flip \the [src]!") - return SHAME - addtimer(CALLBACK(src, .proc/manual_suicide, user), 10)//10 = time takes for flip animation - return MANUAL_SUICIDE_NONLETHAL - -/obj/item/coin/proc/manual_suicide(mob/living/user) - var/index = sideslist.Find(coinflip) - if (index==2)//tails - user.visible_message("\the [src] lands on [coinflip]! [user] promptly falls over, dead!") - user.adjustOxyLoss(200) - user.death(0) - user.set_suicide(TRUE) - user.suicide_log() - else - user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") - -/obj/item/coin/examine(mob/user) - . = ..() - . += "It's worth [value] credit\s." - -/obj/item/coin/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/CC = W - if(string_attached) - to_chat(user, "There already is a string attached to this coin!") - return - - if (CC.use(1)) - add_overlay("coin_string_overlay") - string_attached = 1 - to_chat(user, "You attach a string to the coin.") - else - to_chat(user, "You need one length of cable to attach a string to the coin!") - return - else - ..() - -/obj/item/coin/wirecutter_act(mob/living/user, obj/item/I) - ..() - if(!string_attached) - return TRUE - - new /obj/item/stack/cable_coil(drop_location(), 1) - overlays = list() - string_attached = null - to_chat(user, "You detach the string from the coin.") - return TRUE - -/obj/item/coin/attack_self(mob/user) - if(cooldown < world.time) - if(string_attached) //does the coin have a wire attached - to_chat(user, "The coin won't flip very well with something attached!" ) - return FALSE//do not flip the coin - cooldown = world.time + 15 - flick("coin_[coinflip]_flip", src) - coinflip = pick(sideslist) - icon_state = "coin_[coinflip]" - playsound(user.loc, 'sound/items/coinflip.ogg', 50, TRUE) - var/oldloc = loc - sleep(15) - if(loc == oldloc && user && !user.incapacitated()) - user.visible_message("[user] flips [src]. It lands on [coinflip].", \ - "You flip [src]. It lands on [coinflip].", \ - "You hear the clattering of loose change.") - return TRUE//did the coin flip? useful for suicide_act - -/obj/item/coin/gold - custom_materials = list(/datum/material/gold = 400) - -/obj/item/coin/silver - custom_materials = list(/datum/material/silver = 400) - -/obj/item/coin/diamond - custom_materials = list(/datum/material/diamond = 400) - -/obj/item/coin/plasma - custom_materials = list(/datum/material/plasma = 400) - -/obj/item/coin/uranium - custom_materials = list(/datum/material/uranium = 400) - -/obj/item/coin/titanium - custom_materials = list(/datum/material/titanium = 400) - -/obj/item/coin/bananium - custom_materials = list(/datum/material/bananium = 400) - -/obj/item/coin/adamantine - custom_materials = list(/datum/material/adamantine = 400) - -/obj/item/coin/mythril - custom_materials = list(/datum/material/mythril = 400) - -/obj/item/coin/plastic - custom_materials = list(/datum/material/plastic = 400) - -/obj/item/coin/runite - custom_materials = list(/datum/material/runite = 400) - -/obj/item/coin/twoheaded - desc = "Hey, this coin's the same on both sides!" - sideslist = list("heads") - -/obj/item/coin/antagtoken - name = "antag token" - desc = "A novelty coin that helps the heart know what hard evidence cannot prove." - icon_state = "coin_valid" - custom_materials = list(/datum/material/plastic = 400) - sideslist = list("valid", "salad") - material_flags = NONE - -/obj/item/coin/iron - -#undef ORESTACK_OVERLAYS_MAX + +#define GIBTONITE_QUALITY_HIGH 3 +#define GIBTONITE_QUALITY_MEDIUM 2 +#define GIBTONITE_QUALITY_LOW 1 + +#define ORESTACK_OVERLAYS_MAX 10 + +/**********************Mineral ores**************************/ + +/obj/item/stack/ore + name = "rock" + icon = 'icons/obj/mining.dmi' + icon_state = "ore" + inhand_icon_state = "ore" + full_w_class = WEIGHT_CLASS_BULKY + singular_name = "ore chunk" + var/points = 0 //How many points this ore gets you from the ore redemption machine + var/refined_type = null //What this ore defaults to being refined into + var/mine_experience = 5 //How much experience do you get for mining this ore? + novariants = TRUE // Ore stacks handle their icon updates themselves to keep the illusion that there's more going + var/list/stack_overlays + var/scan_state = "" //Used by mineral turfs for their scan overlay. + var/spreadChance = 0 //Also used by mineral turfs for spreading veins + +/obj/item/stack/ore/update_overlays() + . = ..() + var/difference = min(ORESTACK_OVERLAYS_MAX, amount) - (LAZYLEN(stack_overlays)+1) + if(difference == 0) + return + else if(difference < 0 && LAZYLEN(stack_overlays)) //amount < stack_overlays, remove excess. + if (LAZYLEN(stack_overlays)-difference <= 0) + stack_overlays = null + else + stack_overlays.len += difference + else if(difference > 0) //amount > stack_overlays, add some. + for(var/i in 1 to difference) + var/mutable_appearance/newore = mutable_appearance(icon, icon_state) + newore.pixel_x = rand(-8,8) + newore.pixel_y = rand(-8,8) + LAZYADD(stack_overlays, newore) + if (stack_overlays) + . += stack_overlays + +/obj/item/stack/ore/welder_act(mob/living/user, obj/item/I) + ..() + if(!refined_type) + return TRUE + + if(I.use_tool(src, user, 0, volume=50, amount=15)) + new refined_type(drop_location()) + use(1) + + return TRUE + +/obj/item/stack/ore/fire_act(exposed_temperature, exposed_volume) + . = ..() + if(isnull(refined_type)) + return + else + var/probability = (rand(0,100))/100 + var/burn_value = probability*amount + var/amountrefined = round(burn_value, 1) + if(amountrefined < 1) + qdel(src) + else + new refined_type(drop_location(),amountrefined) + qdel(src) + +/obj/item/stack/ore/uranium + name = "uranium ore" + icon_state = "Uranium ore" + inhand_icon_state = "Uranium ore" + singular_name = "uranium ore chunk" + points = 30 + material_flags = MATERIAL_NO_EFFECTS + custom_materials = list(/datum/material/uranium=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/uranium + mine_experience = 6 + scan_state = "rock_Uranium" + spreadChance = 5 + +/obj/item/stack/ore/iron + name = "iron ore" + icon_state = "Iron ore" + inhand_icon_state = "Iron ore" + singular_name = "iron ore chunk" + points = 1 + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/metal + mine_experience = 1 + scan_state = "rock_Iron" + spreadChance = 20 + +/obj/item/stack/ore/glass + name = "sand pile" + icon_state = "Glass ore" + inhand_icon_state = "Glass ore" + singular_name = "sand pile" + points = 1 + custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/glass + w_class = WEIGHT_CLASS_TINY + mine_experience = 0 //its sand + +GLOBAL_LIST_INIT(sand_recipes, list(\ + new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50),\ + new /datum/stack_recipe("aesthetic volcanic floor tile", /obj/item/stack/tile/basalt, 2, 1, 50)\ +)) + +/obj/item/stack/ore/glass/get_main_recipes() + . = ..() + . += GLOB.sand_recipes + +/obj/item/stack/ore/glass/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(..() || !ishuman(hit_atom)) + return + var/mob/living/carbon/human/C = hit_atom + if(C.is_eyes_covered()) + C.visible_message("[C]'s eye protection blocks the sand!", "Your eye protection blocks the sand!") + return + C.adjust_blurriness(6) + C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage + C.confused += 5 + to_chat(C, "\The [src] gets into your eyes! The pain, it burns!") + qdel(src) + +/obj/item/stack/ore/glass/ex_act(severity, target) + if (severity == EXPLODE_NONE) + return + qdel(src) + +/obj/item/stack/ore/glass/basalt + name = "volcanic ash" + icon_state = "volcanic_sand" + inhand_icon_state = "volcanic_sand" + singular_name = "volcanic ash pile" + mine_experience = 0 + +/obj/item/stack/ore/plasma + name = "plasma ore" + icon_state = "Plasma ore" + inhand_icon_state = "Plasma ore" + singular_name = "plasma ore chunk" + points = 15 + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/plasma + mine_experience = 5 + scan_state = "rock_Plasma" + spreadChance = 8 + +/obj/item/stack/ore/plasma/welder_act(mob/living/user, obj/item/I) + to_chat(user, "You can't hit a high enough temperature to smelt [src] properly!") + return TRUE + + +/obj/item/stack/ore/silver + name = "silver ore" + icon_state = "Silver ore" + inhand_icon_state = "Silver ore" + singular_name = "silver ore chunk" + points = 16 + mine_experience = 3 + custom_materials = list(/datum/material/silver=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/silver + scan_state = "rock_Silver" + spreadChance = 5 + +/obj/item/stack/ore/gold + name = "gold ore" + icon_state = "Gold ore" + inhand_icon_state = "Gold ore" + singular_name = "gold ore chunk" + points = 18 + mine_experience = 5 + custom_materials = list(/datum/material/gold=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/gold + scan_state = "rock_Gold" + spreadChance = 5 + +/obj/item/stack/ore/diamond + name = "diamond ore" + icon_state = "Diamond ore" + inhand_icon_state = "Diamond ore" + singular_name = "diamond ore chunk" + points = 50 + custom_materials = list(/datum/material/diamond=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/diamond + mine_experience = 10 + scan_state = "rock_Diamond" + +/obj/item/stack/ore/bananium + name = "bananium ore" + icon_state = "Bananium ore" + inhand_icon_state = "Bananium ore" + singular_name = "bananium ore chunk" + points = 60 + custom_materials = list(/datum/material/bananium=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/bananium + mine_experience = 15 + scan_state = "rock_Bananium" + +/obj/item/stack/ore/titanium + name = "titanium ore" + icon_state = "Titanium ore" + inhand_icon_state = "Titanium ore" + singular_name = "titanium ore chunk" + points = 50 + custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/titanium + mine_experience = 3 + scan_state = "rock_Titanium" + spreadChance = 5 + +/obj/item/stack/ore/slag + name = "slag" + desc = "Completely useless." + icon_state = "slag" + inhand_icon_state = "slag" + singular_name = "slag chunk" + +/obj/item/gibtonite + name = "gibtonite ore" + desc = "Extremely explosive if struck with mining equipment, Gibtonite is often used by miners to speed up their work by using it as a mining charge. This material is illegal to possess by unauthorized personnel under space law." + icon = 'icons/obj/mining.dmi' + icon_state = "Gibtonite ore" + inhand_icon_state = "Gibtonite ore" + w_class = WEIGHT_CLASS_BULKY + throw_range = 0 + var/primed = FALSE + var/det_time = 100 + var/quality = GIBTONITE_QUALITY_LOW //How pure this gibtonite is, determines the explosion produced by it and is derived from the det_time of the rock wall it was taken from, higher value = better + var/attacher = "UNKNOWN" + var/det_timer + +/obj/item/gibtonite/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, require_twohands=TRUE) + +/obj/item/gibtonite/Destroy() + qdel(wires) + wires = null + return ..() + +/obj/item/gibtonite/attackby(obj/item/I, mob/user, params) + if(!wires && istype(I, /obj/item/assembly/igniter)) + user.visible_message("[user] attaches [I] to [src].", "You attach [I] to [src].") + wires = new /datum/wires/explosive/gibtonite(src) + attacher = key_name(user) + qdel(I) + add_overlay("Gibtonite_igniter") + return + + if(wires && !primed) + if(is_wire_tool(I)) + wires.interact(user) + return + + if(I.tool_behaviour == TOOL_MINING || istype(I, /obj/item/resonator) || I.force >= 10) + GibtoniteReaction(user) + return + if(primed) + if(istype(I, /obj/item/mining_scanner) || istype(I, /obj/item/t_scanner/adv_mining_scanner) || I.tool_behaviour == TOOL_MULTITOOL) + primed = FALSE + if(det_timer) + deltimer(det_timer) + user.visible_message("The chain reaction stopped! ...The ore's quality looks diminished.", "You stopped the chain reaction. ...The ore's quality looks diminished.") + icon_state = "Gibtonite ore" + quality = GIBTONITE_QUALITY_LOW + return + ..() + +/obj/item/gibtonite/attack_self(user) + if(wires) + wires.interact(user) + else + ..() + +/obj/item/gibtonite/bullet_act(obj/projectile/P) + GibtoniteReaction(P.firer) + . = ..() + +/obj/item/gibtonite/ex_act() + GibtoniteReaction(null, 1) + +/obj/item/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0) + if(!primed) + primed = TRUE + playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,TRUE) + icon_state = "Gibtonite active" + var/notify_admins = FALSE + if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel + notify_admins = TRUE + + if(triggered_by == 1) + log_bomber(null, "An explosion has primed a", src, "for detonation", notify_admins) + else if(triggered_by == 2) + var/turf/bombturf = get_turf(src) + if(notify_admins) + message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") + var/bomb_message = "A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)]." + log_game(bomb_message) + GLOB.bombers += bomb_message + else + user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") + log_bomber(user, "has primed a", src, "for detonation", notify_admins) + det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) + +/obj/item/gibtonite/proc/detonate(notify_admins) + if(primed) + switch(quality) + if(GIBTONITE_QUALITY_HIGH) + explosion(src,2,4,9,adminlog = notify_admins) + if(GIBTONITE_QUALITY_MEDIUM) + explosion(src,1,2,5,adminlog = notify_admins) + if(GIBTONITE_QUALITY_LOW) + explosion(src,0,1,3,adminlog = notify_admins) + qdel(src) + +/obj/item/stack/ore/Initialize() + . = ..() + pixel_x = rand(0,16)-8 + pixel_y = rand(0,8)-8 + +/obj/item/stack/ore/ex_act(severity, target) + if (!severity || severity >= 2) + return + qdel(src) + + +/*****************************Coin********************************/ + +// The coin's value is a value of it's materials. +// Yes, the gold standard makes a come-back! +// This is the only way to make coins that are possible to produce on station actually worth anything. +/obj/item/coin + icon = 'icons/obj/economy.dmi' + name = "coin" + icon_state = "coin" + flags_1 = CONDUCT_1 + force = 1 + throwforce = 2 + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/iron = 400) + material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + var/string_attached + var/list/sideslist = list("heads","tails") + var/cooldown = 0 + var/value + var/coinflip + item_flags = NO_MAT_REDEMPTION //You know, it's kind of a problem that money is worth more extrinsicly than intrinsically in this universe. + +/obj/item/coin/Initialize() + . = ..() + coinflip = pick(sideslist) + icon_state = "coin_[coinflip]" + pixel_x = rand(0,16)-8 + pixel_y = rand(0,8)-8 + +/obj/item/coin/set_custom_materials(var/list/materials, multiplier = 1) + . = ..() + value = 0 + for(var/i in custom_materials) + var/datum/material/M = i + value += M.value_per_unit * custom_materials[M] + +/obj/item/coin/get_item_credit_value() + return value + +/obj/item/coin/suicide_act(mob/living/user) + user.visible_message("[user] contemplates suicide with \the [src]!") + if (!attack_self(user)) + user.visible_message("[user] couldn't flip \the [src]!") + return SHAME + addtimer(CALLBACK(src, .proc/manual_suicide, user), 10)//10 = time takes for flip animation + return MANUAL_SUICIDE_NONLETHAL + +/obj/item/coin/proc/manual_suicide(mob/living/user) + var/index = sideslist.Find(coinflip) + if (index==2)//tails + user.visible_message("\the [src] lands on [coinflip]! [user] promptly falls over, dead!") + user.adjustOxyLoss(200) + user.death(0) + user.set_suicide(TRUE) + user.suicide_log() + else + user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") + +/obj/item/coin/examine(mob/user) + . = ..() + . += "It's worth [value] credit\s." + +/obj/item/coin/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if(string_attached) + to_chat(user, "There already is a string attached to this coin!") + return + + if (CC.use(1)) + add_overlay("coin_string_overlay") + string_attached = 1 + to_chat(user, "You attach a string to the coin.") + else + to_chat(user, "You need one length of cable to attach a string to the coin!") + return + else + ..() + +/obj/item/coin/wirecutter_act(mob/living/user, obj/item/I) + ..() + if(!string_attached) + return TRUE + + new /obj/item/stack/cable_coil(drop_location(), 1) + overlays = list() + string_attached = null + to_chat(user, "You detach the string from the coin.") + return TRUE + +/obj/item/coin/attack_self(mob/user) + if(cooldown < world.time) + if(string_attached) //does the coin have a wire attached + to_chat(user, "The coin won't flip very well with something attached!" ) + return FALSE//do not flip the coin + cooldown = world.time + 15 + flick("coin_[coinflip]_flip", src) + coinflip = pick(sideslist) + icon_state = "coin_[coinflip]" + playsound(user.loc, 'sound/items/coinflip.ogg', 50, TRUE) + var/oldloc = loc + sleep(15) + if(loc == oldloc && user && !user.incapacitated()) + user.visible_message("[user] flips [src]. It lands on [coinflip].", \ + "You flip [src]. It lands on [coinflip].", \ + "You hear the clattering of loose change.") + return TRUE//did the coin flip? useful for suicide_act + +/obj/item/coin/gold + custom_materials = list(/datum/material/gold = 400) + +/obj/item/coin/silver + custom_materials = list(/datum/material/silver = 400) + +/obj/item/coin/diamond + custom_materials = list(/datum/material/diamond = 400) + +/obj/item/coin/plasma + custom_materials = list(/datum/material/plasma = 400) + +/obj/item/coin/uranium + custom_materials = list(/datum/material/uranium = 400) + +/obj/item/coin/titanium + custom_materials = list(/datum/material/titanium = 400) + +/obj/item/coin/bananium + custom_materials = list(/datum/material/bananium = 400) + +/obj/item/coin/adamantine + custom_materials = list(/datum/material/adamantine = 400) + +/obj/item/coin/mythril + custom_materials = list(/datum/material/mythril = 400) + +/obj/item/coin/plastic + custom_materials = list(/datum/material/plastic = 400) + +/obj/item/coin/runite + custom_materials = list(/datum/material/runite = 400) + +/obj/item/coin/twoheaded + desc = "Hey, this coin's the same on both sides!" + sideslist = list("heads") + +/obj/item/coin/antagtoken + name = "antag token" + desc = "A novelty coin that helps the heart know what hard evidence cannot prove." + icon_state = "coin_valid" + custom_materials = list(/datum/material/plastic = 400) + sideslist = list("valid", "salad") + material_flags = NONE + +/obj/item/coin/iron + +#undef ORESTACK_OVERLAYS_MAX diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm index 10961cfa609..e689a37b250 100644 --- a/code/modules/mining/satchel_ore_boxdm.dm +++ b/code/modules/mining/satchel_ore_boxdm.dm @@ -1,105 +1,105 @@ - -/**********************Ore box**************************/ - -/obj/structure/ore_box - icon = 'icons/obj/mining.dmi' - icon_state = "orebox" - name = "ore box" - desc = "A heavy wooden box, which can be filled with a lot of ores." - density = TRUE - pressure_resistance = 5*ONE_ATMOSPHERE - - var/ui_x = 335 - var/ui_y = 415 - -/obj/structure/ore_box/attackby(obj/item/W, mob/user, params) - if (istype(W, /obj/item/stack/ore)) - user.transferItemToLoc(W, src) - else if(SEND_SIGNAL(W, COMSIG_CONTAINS_STORAGE)) - SEND_SIGNAL(W, COMSIG_TRY_STORAGE_TAKE_TYPE, /obj/item/stack/ore, src) - to_chat(user, "You empty the ore in [W] into \the [src].") - else - return ..() - -/obj/structure/ore_box/ComponentInitialize() - . = ..() - AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer - -/obj/structure/ore_box/crowbar_act(mob/living/user, obj/item/I) - if(I.use_tool(src, user, 50, volume=50)) - user.visible_message("[user] pries \the [src] apart.", - "You pry apart \the [src].", - "You hear splitting wood.") - deconstruct(TRUE, user) - return TRUE - -/obj/structure/ore_box/examine(mob/living/user) - if(Adjacent(user) && istype(user)) - ui_interact(user) - . = ..() - -/obj/structure/ore_box/attack_hand(mob/user) - . = ..() - if(.) - return - if(Adjacent(user)) - ui_interact(user) - -/obj/structure/ore_box/attack_robot(mob/user) - if(Adjacent(user)) - ui_interact(user) - -/obj/structure/ore_box/proc/dump_box_contents() - var/drop = drop_location() - for(var/obj/item/stack/ore/O in src) - if(QDELETED(O)) - continue - if(QDELETED(src)) - break - O.forceMove(drop) - if(TICK_CHECK) - stoplag() - drop = drop_location() - -/obj/structure/ore_box/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "OreBox", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/structure/ore_box/ui_data() - var/contents = list() - for(var/obj/item/stack/ore/O in src) - contents[O.type] += O.amount - - var/data = list() - data["materials"] = list() - for(var/type in contents) - var/obj/item/stack/ore/O = type - var/name = initial(O.name) - data["materials"] += list(list("name" = name, "amount" = contents[type], "id" = type)) - - return data - -/obj/structure/ore_box/ui_act(action, params) - if(..()) - return - if(!Adjacent(usr)) - return - add_fingerprint(usr) - usr.set_machine(src) - switch(action) - if("removeall") - dump_box_contents() - to_chat(usr, "You open the release hatch on the box..") - -/obj/structure/ore_box/deconstruct(disassembled = TRUE, mob/user) - var/obj/item/stack/sheet/mineral/wood/WD = new (loc, 4) - if(user) - WD.add_fingerprint(user) - dump_box_contents() - qdel(src) - -/obj/structure/ore_box/onTransitZ() - return + +/**********************Ore box**************************/ + +/obj/structure/ore_box + icon = 'icons/obj/mining.dmi' + icon_state = "orebox" + name = "ore box" + desc = "A heavy wooden box, which can be filled with a lot of ores." + density = TRUE + pressure_resistance = 5*ONE_ATMOSPHERE + + var/ui_x = 335 + var/ui_y = 415 + +/obj/structure/ore_box/attackby(obj/item/W, mob/user, params) + if (istype(W, /obj/item/stack/ore)) + user.transferItemToLoc(W, src) + else if(SEND_SIGNAL(W, COMSIG_CONTAINS_STORAGE)) + SEND_SIGNAL(W, COMSIG_TRY_STORAGE_TAKE_TYPE, /obj/item/stack/ore, src) + to_chat(user, "You empty the ore in [W] into \the [src].") + else + return ..() + +/obj/structure/ore_box/ComponentInitialize() + . = ..() + AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer + +/obj/structure/ore_box/crowbar_act(mob/living/user, obj/item/I) + if(I.use_tool(src, user, 50, volume=50)) + user.visible_message("[user] pries \the [src] apart.", + "You pry apart \the [src].", + "You hear splitting wood.") + deconstruct(TRUE, user) + return TRUE + +/obj/structure/ore_box/examine(mob/living/user) + if(Adjacent(user) && istype(user)) + ui_interact(user) + . = ..() + +/obj/structure/ore_box/attack_hand(mob/user) + . = ..() + if(.) + return + if(Adjacent(user)) + ui_interact(user) + +/obj/structure/ore_box/attack_robot(mob/user) + if(Adjacent(user)) + ui_interact(user) + +/obj/structure/ore_box/proc/dump_box_contents() + var/drop = drop_location() + for(var/obj/item/stack/ore/O in src) + if(QDELETED(O)) + continue + if(QDELETED(src)) + break + O.forceMove(drop) + if(TICK_CHECK) + stoplag() + drop = drop_location() + +/obj/structure/ore_box/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "OreBox", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/structure/ore_box/ui_data() + var/contents = list() + for(var/obj/item/stack/ore/O in src) + contents[O.type] += O.amount + + var/data = list() + data["materials"] = list() + for(var/type in contents) + var/obj/item/stack/ore/O = type + var/name = initial(O.name) + data["materials"] += list(list("name" = name, "amount" = contents[type], "id" = type)) + + return data + +/obj/structure/ore_box/ui_act(action, params) + if(..()) + return + if(!Adjacent(usr)) + return + add_fingerprint(usr) + usr.set_machine(src) + switch(action) + if("removeall") + dump_box_contents() + to_chat(usr, "You open the release hatch on the box..") + +/obj/structure/ore_box/deconstruct(disassembled = TRUE, mob/user) + var/obj/item/stack/sheet/mineral/wood/WD = new (loc, 4) + if(user) + WD.add_fingerprint(user) + dump_box_contents() + qdel(src) + +/obj/structure/ore_box/onTransitZ() + return diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm index 61543809ba3..8fb3cb17eab 100644 --- a/code/modules/mob/camera/camera.dm +++ b/code/modules/mob/camera/camera.dm @@ -1,27 +1,27 @@ -// Camera mob, used by AI camera and blob. - -/mob/camera - name = "camera mob" - density = FALSE - move_force = INFINITY - move_resist = INFINITY - status_flags = GODMODE // You can't damage it. - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - see_in_dark = 7 - invisibility = INVISIBILITY_ABSTRACT // No one can see us - sight = SEE_SELF - move_on_shuttle = FALSE - -/mob/camera/experience_pressure_difference() - return - -/mob/camera/forceMove(atom/destination) - var/oldloc = loc - loc = destination - Moved(oldloc, NONE, TRUE) - -/mob/camera/canUseStorage() - return FALSE - -/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE) - return FALSE +// Camera mob, used by AI camera and blob. + +/mob/camera + name = "camera mob" + density = FALSE + move_force = INFINITY + move_resist = INFINITY + status_flags = GODMODE // You can't damage it. + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + see_in_dark = 7 + invisibility = INVISIBILITY_ABSTRACT // No one can see us + sight = SEE_SELF + move_on_shuttle = FALSE + +/mob/camera/experience_pressure_difference() + return + +/mob/camera/forceMove(atom/destination) + var/oldloc = loc + loc = destination + Moved(oldloc, NONE, TRUE) + +/mob/camera/canUseStorage() + return FALSE + +/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE) + return FALSE diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 3729590abd4..2076cb3a193 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -1,137 +1,137 @@ -//Dead mobs can exist whenever. This is needful - -INITIALIZE_IMMEDIATE(/mob/dead) - -/mob/dead - sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF - move_resist = INFINITY - throwforce = 0 - -/mob/dead/Initialize() - SHOULD_CALL_PARENT(FALSE) - if(flags_1 & INITIALIZED_1) - stack_trace("Warning: [src]([type]) initialized multiple times!") - flags_1 |= INITIALIZED_1 - tag = "mob_[next_mob_id++]" - add_to_mob_list() - - prepare_huds() - - if(length(CONFIG_GET(keyed_list/cross_server))) - verbs += /mob/dead/proc/server_hop - set_focus(src) - return INITIALIZE_HINT_NORMAL - -/mob/dead/canUseStorage() - return FALSE - -/mob/dead/dust(just_ash, drop_items, force) //ghosts can't be vaporised. - return - -/mob/dead/gib() //ghosts can't be gibbed. - return - -/mob/dead/ConveyorMove() //lol - return - -/mob/dead/forceMove(atom/destination) - var/turf/old_turf = get_turf(src) - var/turf/new_turf = get_turf(destination) - if (old_turf?.z != new_turf?.z) - onTransitZ(old_turf?.z, new_turf?.z) - var/oldloc = loc - loc = destination - Moved(oldloc, NONE, TRUE) - -/mob/dead/Stat() - ..() - - if(!statpanel("Status")) - return - stat(null, "Game Mode: [SSticker.hide_mode ? "Secret" : "[GLOB.master_mode]"]") - - if(SSticker.HasRoundStarted()) - return - - var/time_remaining = SSticker.GetTimeLeft() - if(time_remaining > 0) - stat(null, "Time To Start: [round(time_remaining/10)]s") - else if(time_remaining == -10) - stat(null, "Time To Start: DELAYED") - else - stat(null, "Time To Start: SOON") - - stat(null, "Players: [SSticker.totalPlayers]") - if(client.holder) - stat(null, "Players Ready: [SSticker.totalPlayersReady]") - -/mob/dead/proc/server_hop() - set category = "OOC" - set name = "Server Hop!" - set desc= "Jump to the other server" - if(notransform) - return - var/list/our_id = CONFIG_GET(string/cross_comms_name) - var/list/csa = CONFIG_GET(keyed_list/cross_server) - our_id - var/pick - switch(csa.len) - if(0) - verbs -= /mob/dead/proc/server_hop - to_chat(src, "Server Hop has been disabled.") - if(1) - pick = csa[1] - else - pick = input(src, "Pick a server to jump to", "Server Hop") as null|anything in csa - - if(!pick) - return - - var/addr = csa[pick] - - if(alert(src, "Jump to server [pick] ([addr])?", "Server Hop", "Yes", "No") != "Yes") - return - - var/client/C = client - to_chat(C, "Sending you to [pick].") - new /obj/screen/splash(C) - - notransform = TRUE - sleep(29) //let the animation play - notransform = FALSE - - if(!C) - return - - winset(src, null, "command=.options") //other wise the user never knows if byond is downloading resources - - C << link("[addr]") - -/mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister - if (registered_z != new_z) - if (registered_z) - SSmobs.dead_players_by_zlevel[registered_z] -= src - if (client) - if (new_z) - SSmobs.dead_players_by_zlevel[new_z] += src - registered_z = new_z - else - registered_z = null - -/mob/dead/Login() - . = ..() - if(!. || !client) - return FALSE - var/turf/T = get_turf(src) - if (isturf(T)) - update_z(T.z) - -/mob/dead/auto_deadmin_on_login() - return - -/mob/dead/Logout() - update_z(null) - return ..() - -/mob/dead/onTransitZ(old_z,new_z) - ..() - update_z(new_z) +//Dead mobs can exist whenever. This is needful + +INITIALIZE_IMMEDIATE(/mob/dead) + +/mob/dead + sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF + move_resist = INFINITY + throwforce = 0 + +/mob/dead/Initialize() + SHOULD_CALL_PARENT(FALSE) + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + tag = "mob_[next_mob_id++]" + add_to_mob_list() + + prepare_huds() + + if(length(CONFIG_GET(keyed_list/cross_server))) + verbs += /mob/dead/proc/server_hop + set_focus(src) + return INITIALIZE_HINT_NORMAL + +/mob/dead/canUseStorage() + return FALSE + +/mob/dead/dust(just_ash, drop_items, force) //ghosts can't be vaporised. + return + +/mob/dead/gib() //ghosts can't be gibbed. + return + +/mob/dead/ConveyorMove() //lol + return + +/mob/dead/forceMove(atom/destination) + var/turf/old_turf = get_turf(src) + var/turf/new_turf = get_turf(destination) + if (old_turf?.z != new_turf?.z) + onTransitZ(old_turf?.z, new_turf?.z) + var/oldloc = loc + loc = destination + Moved(oldloc, NONE, TRUE) + +/mob/dead/Stat() + ..() + + if(!statpanel("Status")) + return + stat(null, "Game Mode: [SSticker.hide_mode ? "Secret" : "[GLOB.master_mode]"]") + + if(SSticker.HasRoundStarted()) + return + + var/time_remaining = SSticker.GetTimeLeft() + if(time_remaining > 0) + stat(null, "Time To Start: [round(time_remaining/10)]s") + else if(time_remaining == -10) + stat(null, "Time To Start: DELAYED") + else + stat(null, "Time To Start: SOON") + + stat(null, "Players: [SSticker.totalPlayers]") + if(client.holder) + stat(null, "Players Ready: [SSticker.totalPlayersReady]") + +/mob/dead/proc/server_hop() + set category = "OOC" + set name = "Server Hop!" + set desc= "Jump to the other server" + if(notransform) + return + var/list/our_id = CONFIG_GET(string/cross_comms_name) + var/list/csa = CONFIG_GET(keyed_list/cross_server) - our_id + var/pick + switch(csa.len) + if(0) + verbs -= /mob/dead/proc/server_hop + to_chat(src, "Server Hop has been disabled.") + if(1) + pick = csa[1] + else + pick = input(src, "Pick a server to jump to", "Server Hop") as null|anything in csa + + if(!pick) + return + + var/addr = csa[pick] + + if(alert(src, "Jump to server [pick] ([addr])?", "Server Hop", "Yes", "No") != "Yes") + return + + var/client/C = client + to_chat(C, "Sending you to [pick].") + new /obj/screen/splash(C) + + notransform = TRUE + sleep(29) //let the animation play + notransform = FALSE + + if(!C) + return + + winset(src, null, "command=.options") //other wise the user never knows if byond is downloading resources + + C << link("[addr]") + +/mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister + if (registered_z != new_z) + if (registered_z) + SSmobs.dead_players_by_zlevel[registered_z] -= src + if (client) + if (new_z) + SSmobs.dead_players_by_zlevel[new_z] += src + registered_z = new_z + else + registered_z = null + +/mob/dead/Login() + . = ..() + if(!. || !client) + return FALSE + var/turf/T = get_turf(src) + if (isturf(T)) + update_z(T.z) + +/mob/dead/auto_deadmin_on_login() + return + +/mob/dead/Logout() + update_z(null) + return ..() + +/mob/dead/onTransitZ(old_z,new_z) + ..() + update_z(new_z) diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm index a040bf2ba0e..237ce7f9e7e 100644 --- a/code/modules/mob/dead/new_player/login.dm +++ b/code/modules/mob/dead/new_player/login.dm @@ -1,38 +1,38 @@ -/mob/dead/new_player/Login() - if(!client) - return - if(CONFIG_GET(flag/use_exp_tracking)) - client.set_exp_from_db() - client.set_db_player_flags() - if(!mind) - mind = new /datum/mind(key) - mind.active = 1 - mind.current = src - - . = ..() - if(!. || !client) - return FALSE - - var/motd = global.config.motd - if(motd) - to_chat(src, "

                [motd]
                ", handle_whitespace=FALSE) - - if(GLOB.admin_notice) - to_chat(src, "Admin Notice:\n \t [GLOB.admin_notice]") - - var/spc = CONFIG_GET(number/soft_popcap) - if(spc && living_player_count() >= spc) - to_chat(src, "Server Notice:\n \t [CONFIG_GET(string/soft_popcap_message)]") - - sight |= SEE_TURFS - - new_player_panel() - client.playtitlemusic() - if(SSticker.current_state < GAME_STATE_SETTING_UP) - var/tl = SSticker.GetTimeLeft() - var/postfix - if(tl > 0) - postfix = "in about [DisplayTimeText(tl)]" - else - postfix = "soon" - to_chat(src, "Please set up your character and select \"Ready\". The game will start [postfix].") +/mob/dead/new_player/Login() + if(!client) + return + if(CONFIG_GET(flag/use_exp_tracking)) + client.set_exp_from_db() + client.set_db_player_flags() + if(!mind) + mind = new /datum/mind(key) + mind.active = 1 + mind.current = src + + . = ..() + if(!. || !client) + return FALSE + + var/motd = global.config.motd + if(motd) + to_chat(src, "
                [motd]
                ", handle_whitespace=FALSE) + + if(GLOB.admin_notice) + to_chat(src, "Admin Notice:\n \t [GLOB.admin_notice]") + + var/spc = CONFIG_GET(number/soft_popcap) + if(spc && living_player_count() >= spc) + to_chat(src, "Server Notice:\n \t [CONFIG_GET(string/soft_popcap_message)]") + + sight |= SEE_TURFS + + new_player_panel() + client.playtitlemusic() + if(SSticker.current_state < GAME_STATE_SETTING_UP) + var/tl = SSticker.GetTimeLeft() + var/postfix + if(tl > 0) + postfix = "in about [DisplayTimeText(tl)]" + else + postfix = "soon" + to_chat(src, "Please set up your character and select \"Ready\". The game will start [postfix].") diff --git a/code/modules/mob/dead/new_player/logout.dm b/code/modules/mob/dead/new_player/logout.dm index a0e39232239..849ab6fc69d 100644 --- a/code/modules/mob/dead/new_player/logout.dm +++ b/code/modules/mob/dead/new_player/logout.dm @@ -1,7 +1,7 @@ -/mob/dead/new_player/Logout() - ready = 0 - ..() - if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. - key = null//We null their key before deleting the mob, so they are properly kicked out. - qdel(src) - return +/mob/dead/new_player/Logout() + ready = 0 + ..() + if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. + key = null//We null their key before deleting the mob, so they are properly kicked out. + qdel(src) + return diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index ee5462fd23f..ce0fb63fece 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -1,519 +1,519 @@ -#define LINKIFY_READY(string, value) "[string]" - -/mob/dead/new_player - var/ready = 0 - var/spawning = 0//Referenced when you want to delete the new_player later on in the code. - - flags_1 = NONE - - invisibility = INVISIBILITY_ABSTRACT - - density = FALSE - stat = DEAD - hud_possible = list() - - var/mob/living/new_character //for instant transfer once the round is set up - - //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles - var/ineligible_for_roles = FALSE - -/mob/dead/new_player/Initialize() - if(client && SSticker.state == GAME_STATE_STARTUP) - var/obj/screen/splash/S = new(client, TRUE, TRUE) - S.Fade(TRUE) - - if(length(GLOB.newplayer_start)) - forceMove(pick(GLOB.newplayer_start)) - else - forceMove(locate(1,1,1)) - - ComponentInitialize() - - . = ..() - - GLOB.new_player_list += src - -/mob/dead/new_player/Destroy() - GLOB.new_player_list -= src - return ..() - -/mob/dead/new_player/prepare_huds() - return - -/mob/dead/new_player/proc/new_player_panel() - var/output = "

                Setup Character

                " - - if(SSticker.current_state <= GAME_STATE_PREGAME) - switch(ready) - if(PLAYER_NOT_READY) - output += "

                \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

                " - if(PLAYER_READY_TO_PLAY) - output += "

                \[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

                " - if(PLAYER_READY_TO_OBSERVE) - output += "

                \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]

                " - else - output += "

                View the Crew Manifest

                " - output += "

                Join Game!

                " - output += "

                [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

                " - - if(!IsGuestKey(src.key)) - if (SSdbcore.Connect()) - var/isadmin = FALSE - if(client?.holder) - isadmin = TRUE - var/datum/db_query/query_get_new_polls = SSdbcore.NewQuery({" - SELECT id FROM [format_table_name("poll_question")] - WHERE (adminonly = 0 OR :isadmin = 1) - AND Now() BETWEEN starttime AND endtime - AND deleted = 0 - AND id NOT IN ( - SELECT pollid FROM [format_table_name("poll_vote")] - WHERE ckey = :ckey - AND deleted = 0 - ) - AND id NOT IN ( - SELECT pollid FROM [format_table_name("poll_textreply")] - WHERE ckey = :ckey - AND deleted = 0 - ) - "}, list("isadmin" = isadmin, "ckey" = ckey)) - var/rs = REF(src) - if(!query_get_new_polls.Execute()) - qdel(query_get_new_polls) - return - if(query_get_new_polls.NextRow()) - output += "

                Show Player Polls (NEW!)

                " - else - output += "

                Show Player Polls

                " - qdel(query_get_new_polls) - if(QDELETED(src)) - return - - output += "
                " - - //src << browse(output,"window=playersetup;size=210x240;can_close=0") - var/datum/browser/popup = new(src, "playersetup", "
                New Player Options
                ", 250, 265) - popup.set_window_options("can_close=0") - popup.set_content(output) - popup.open(FALSE) - -/mob/dead/new_player/Topic(href, href_list[]) - if(src != usr) - return 0 - - if(!client) - return 0 - - //Determines Relevent Population Cap - var/relevant_cap - var/hpc = CONFIG_GET(number/hard_popcap) - var/epc = CONFIG_GET(number/extreme_popcap) - if(hpc && epc) - relevant_cap = min(hpc, epc) - else - relevant_cap = max(hpc, epc) - - if(href_list["show_preferences"]) - client.prefs.ShowChoices(src) - return 1 - - if(href_list["ready"]) - var/tready = text2num(href_list["ready"]) - //Avoid updating ready if we're after PREGAME (they should use latejoin instead) - //This is likely not an actual issue but I don't have time to prove that this - //no longer is required - if(SSticker.current_state <= GAME_STATE_PREGAME) - ready = tready - //if it's post initialisation and they're trying to observe we do the needful - if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE) - ready = tready - make_me_an_observer() - return - - if(href_list["refresh"]) - src << browse(null, "window=playersetup") //closes the player setup window - new_player_panel() - - if(href_list["late_join"]) - if(!SSticker?.IsRoundInProgress()) - to_chat(usr, "The round is either not ready, or has already finished...") - return - - if(href_list["late_join"] == "override") - LateChoices() - return - - if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) - to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)]") - - var/queue_position = SSticker.queued_players.Find(usr) - if(queue_position == 1) - to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up.") - else if(queue_position) - to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game.") - else - SSticker.queued_players += usr - to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].") - return - LateChoices() - - if(href_list["manifest"]) - ViewManifest() - - if(href_list["SelectedJob"]) - if(!SSticker?.IsRoundInProgress()) - to_chat(usr, "The round is either not ready, or has already finished...") - return - - if(!GLOB.enter_allowed) - to_chat(usr, "There is an administrative lock on entering the game!") - return - - if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) - if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) - to_chat(usr, "Server is full.") - return - - AttemptLateSpawn(href_list["SelectedJob"]) - return - - else if(!href_list["late_join"]) - new_player_panel() - - if(href_list["showpoll"]) - handle_player_polling() - return - - if(href_list["viewpoll"]) - var/datum/poll_question/poll = locate(href_list["viewpoll"]) in GLOB.polls - poll_player(poll) - - if(href_list["votepollref"]) - var/datum/poll_question/poll = locate(href_list["votepollref"]) in GLOB.polls - vote_on_poll_handler(poll, href_list) - -//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT) -/mob/dead/new_player/proc/make_me_an_observer() - if(QDELETED(src) || !src.client) - ready = PLAYER_NOT_READY - return FALSE - - var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No") - - if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes") - ready = PLAYER_NOT_READY - src << browse(null, "window=playersetup") //closes the player setup window - new_player_panel() - return FALSE - - var/mob/dead/observer/observer = new() - spawning = TRUE - - observer.started_as_observer = TRUE - close_spawn_windows() - var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list - to_chat(src, "Now teleporting.") - if (O) - observer.forceMove(O.loc) - else - to_chat(src, "Teleporting failed. Ahelp an admin please") - stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised") - observer.key = key - observer.client = client - observer.set_ghost_appearance() - if(observer.client && observer.client.prefs) - observer.real_name = observer.client.prefs.real_name - observer.name = observer.real_name - observer.update_icon() - observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) - QDEL_NULL(mind) - qdel(src) - return TRUE - -/proc/get_job_unavailable_error_message(retval, jobtitle) - switch(retval) - if(JOB_AVAILABLE) - return "[jobtitle] is available." - if(JOB_UNAVAILABLE_GENERIC) - return "[jobtitle] is unavailable." - if(JOB_UNAVAILABLE_BANNED) - return "You are currently banned from [jobtitle]." - if(JOB_UNAVAILABLE_PLAYTIME) - return "You do not have enough relevant playtime for [jobtitle]." - if(JOB_UNAVAILABLE_ACCOUNTAGE) - return "Your account is not old enough for [jobtitle]." - if(JOB_UNAVAILABLE_SLOTFULL) - return "[jobtitle] is already filled to capacity." - return "Error: Unknown job availability." - -/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) - var/datum/job/job = SSjob.GetJob(rank) - if(!job) - return JOB_UNAVAILABLE_GENERIC - if((job.current_positions >= job.total_positions) && job.total_positions != -1) - if(job.title == "Assistant") - if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants - return JOB_AVAILABLE - for(var/datum/job/J in SSjob.occupations) - if(J && J.current_positions < J.total_positions && J.title != job.title) - return JOB_UNAVAILABLE_SLOTFULL - else - return JOB_UNAVAILABLE_SLOTFULL - if(is_banned_from(ckey, rank)) - return JOB_UNAVAILABLE_BANNED - if(QDELETED(src)) - return JOB_UNAVAILABLE_GENERIC - if(!job.player_old_enough(client)) - return JOB_UNAVAILABLE_ACCOUNTAGE - if(job.required_playtime_remaining(client)) - return JOB_UNAVAILABLE_PLAYTIME - if(latejoin && !job.special_check_latejoin(client)) - return JOB_UNAVAILABLE_GENERIC - return JOB_AVAILABLE - -/mob/dead/new_player/proc/AttemptLateSpawn(rank) - var/error = IsJobUnavailable(rank) - if(error != JOB_AVAILABLE) - alert(src, get_job_unavailable_error_message(error, rank)) - return FALSE - - if(SSticker.late_join_disabled) - alert(src, "An administrator has disabled late join spawning.") - return FALSE - - var/arrivals_docked = TRUE - if(SSshuttle.arrivals) - close_spawn_windows() //In case we get held up - if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) - src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") - return FALSE - - if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) - SSshuttle.arrivals.RequireUndocked(src) - arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL - - //Remove the player from the join queue if he was in one and reset the timer - SSticker.queued_players -= src - SSticker.queue_delay = 4 - - SSjob.AssignRole(src, rank, 1) - - var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind - var/equip = SSjob.EquipRank(character, rank, TRUE) - if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. - character = equip - - var/datum/job/job = SSjob.GetJob(rank) - - if(job && !job.override_latejoin_spawn(character)) - SSjob.SendToLateJoin(character) - if(!arrivals_docked) - var/obj/screen/splash/Spl = new(character.client, TRUE) - Spl.Fade(TRUE) - character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) - - character.update_parallax_teleport() - - SSticker.minds += character.mind - - var/mob/living/carbon/human/humanc - if(ishuman(character)) - humanc = character //Let's retypecast the var to be human, - - if(humanc) //These procs all expect humans - GLOB.data_core.manifest_inject(humanc) - if(SSshuttle.arrivals) - SSshuttle.arrivals.QueueAnnounce(humanc, rank) - else - AnnounceArrival(humanc, rank) - AddEmploymentContract(humanc) - if(GLOB.highlander) - to_chat(humanc, "THERE CAN BE ONLY ONE!!!") - humanc.make_scottish() - - if(GLOB.summon_guns_triggered) - give_guns(humanc) - if(GLOB.summon_magic_triggered) - give_magic(humanc) - if(GLOB.curse_of_madness_triggered) - give_madness(humanc, GLOB.curse_of_madness_triggered) - - GLOB.joined_player_list += character.ckey - - if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_RECALL, SHUTTLE_IDLE) - SSticker.mode.make_antag_chance(humanc) - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) - SSticker.mode.make_antag_chance(humanc) - - if(humanc && CONFIG_GET(flag/roundstart_traits)) - SSquirks.AssignQuirks(humanc, humanc.client, TRUE) - - log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) - -/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) - //TODO: figure out a way to exclude wizards/nukeops/demons from this. - for(var/C in GLOB.employmentCabinets) - var/obj/structure/filingcabinet/employment/employmentCabinet = C - if(!employmentCabinet.virgin) - employmentCabinet.addFile(employee) - - -/mob/dead/new_player/proc/LateChoices() - var/list/dat = list("
                Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
                ") - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_ESCAPE) - dat += "
                The station has been evacuated.

                " - if(SHUTTLE_CALL) - if(!SSshuttle.canRecall()) - dat += "
                The station is currently undergoing evacuation procedures.

                " - for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) - if(prioritized_job.current_positions >= prioritized_job.total_positions) - SSjob.prioritized_jobs -= prioritized_job - dat += "
                " - var/column_counter = 0 - // render each category's available jobs - for(var/category in GLOB.position_categories) - // position_categories contains category names mapped to available jobs and an appropriate color - var/cat_color = GLOB.position_categories[category]["color"] - dat += "
                " - dat += "[category]" - var/list/dept_dat = list() - for(var/job in GLOB.position_categories[category]["jobs"]) - var/datum/job/job_datum = SSjob.name_occupations[job] - if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE) - var/command_bold = "" - if(job in GLOB.command_positions) - command_bold = " command" - if(job_datum in SSjob.prioritized_jobs) - dept_dat += "[job_datum.title] ([job_datum.current_positions])" - else - dept_dat += "[job_datum.title] ([job_datum.current_positions])" - if(!dept_dat.len) - dept_dat += "No positions open." - dat += jointext(dept_dat, "") - dat += "

                " - column_counter++ - if(column_counter > 0 && (column_counter % 3 == 0)) - dat += "
                " - dat += "
                " - dat += "" - var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) - popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') - popup.set_content(jointext(dat, "")) - popup.open(FALSE) // 0 is passed to open so that it doesn't use the onclose() proc - -/mob/dead/new_player/proc/create_character(transfer_after) - spawning = 1 - close_spawn_windows() - - var/mob/living/carbon/human/H = new(loc) - - var/frn = CONFIG_GET(flag/force_random_names) - var/admin_anon_names = SSticker.anonymousnames - if(!frn) - frn = is_banned_from(ckey, "Appearance") - if(QDELETED(src)) - return - if(frn) - client.prefs.random_character() - client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) - - if(admin_anon_names)//overrides random name because it achieves the same effect and is an admin enabled event tool - client.prefs.random_character() - client.prefs.real_name = anonymous_name(src) - - var/is_antag - if(mind in GLOB.pre_setup_antags) - is_antag = TRUE - - client.prefs.copy_to(H, antagonist = is_antag, is_latejoiner = transfer_after) - var/cur_scar_index = client.prefs.scars_index - if(client.prefs.persistent_scars && client.prefs.scars_list["[cur_scar_index]"]) - var/scar_string = client.prefs.scars_list["[cur_scar_index]"] - var/valid_scars = "" - for(var/scar_line in splittext(scar_string, ";")) - if(H.load_scar(scar_line)) - valid_scars += "[scar_line];" - - client.prefs.scars_list["[cur_scar_index]"] = valid_scars - client.prefs.save_character() - - client.prefs.copy_to(H, antagonist = is_antag) - H.dna.update_dna_identity() - if(mind) - if(transfer_after) - mind.late_joiner = TRUE - mind.active = 0 //we wish to transfer the key manually - mind.transfer_to(H) //won't transfer key since the mind is not active - mind.original_character = H - - H.name = real_name - - . = H - new_character = . - if(transfer_after) - transfer_character() - -/mob/dead/new_player/proc/transfer_character() - . = new_character - if(.) - new_character.key = key //Manually transfer the key to log them in, - new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC) - new_character = null - qdel(src) - -/mob/dead/new_player/proc/ViewManifest() - if(!client) - return - if(world.time < client.crew_manifest_delay) - return - client.crew_manifest_delay = world.time + (1 SECONDS) - - var/dat = "" - dat += "

                Crew Manifest

                " - dat += GLOB.data_core.get_manifest_html() - - src << browse(dat, "window=manifest;size=387x420;can_close=1") - -/mob/dead/new_player/Move() - return 0 - - -/mob/dead/new_player/proc/close_spawn_windows() - - src << browse(null, "window=latechoices") //closes late choices window - src << browse(null, "window=playersetup") //closes the player setup window - src << browse(null, "window=preferences") //closes job selection - src << browse(null, "window=mob_occupation") - src << browse(null, "window=latechoices") //closes late job selection - -// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. -// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled -// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" -// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role -// This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong -/mob/dead/new_player/proc/check_preferences() - if(!client) - return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. - if(client.prefs.joblessrole != RETURNTOLOBBY) - return TRUE - // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. - var/has_antags = FALSE - if(client.prefs.be_special.len > 0) - has_antags = TRUE - if(client.prefs.job_preferences.len == 0) - if(!ineligible_for_roles) - to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") - ineligible_for_roles = TRUE - ready = PLAYER_NOT_READY - if(has_antags) - log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") - message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") - - return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well - return TRUE +#define LINKIFY_READY(string, value) "[string]" + +/mob/dead/new_player + var/ready = 0 + var/spawning = 0//Referenced when you want to delete the new_player later on in the code. + + flags_1 = NONE + + invisibility = INVISIBILITY_ABSTRACT + + density = FALSE + stat = DEAD + hud_possible = list() + + var/mob/living/new_character //for instant transfer once the round is set up + + //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles + var/ineligible_for_roles = FALSE + +/mob/dead/new_player/Initialize() + if(client && SSticker.state == GAME_STATE_STARTUP) + var/obj/screen/splash/S = new(client, TRUE, TRUE) + S.Fade(TRUE) + + if(length(GLOB.newplayer_start)) + forceMove(pick(GLOB.newplayer_start)) + else + forceMove(locate(1,1,1)) + + ComponentInitialize() + + . = ..() + + GLOB.new_player_list += src + +/mob/dead/new_player/Destroy() + GLOB.new_player_list -= src + return ..() + +/mob/dead/new_player/prepare_huds() + return + +/mob/dead/new_player/proc/new_player_panel() + var/output = "

                Setup Character

                " + + if(SSticker.current_state <= GAME_STATE_PREGAME) + switch(ready) + if(PLAYER_NOT_READY) + output += "

                \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

                " + if(PLAYER_READY_TO_PLAY) + output += "

                \[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

                " + if(PLAYER_READY_TO_OBSERVE) + output += "

                \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]

                " + else + output += "

                View the Crew Manifest

                " + output += "

                Join Game!

                " + output += "

                [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

                " + + if(!IsGuestKey(src.key)) + if (SSdbcore.Connect()) + var/isadmin = FALSE + if(client?.holder) + isadmin = TRUE + var/datum/db_query/query_get_new_polls = SSdbcore.NewQuery({" + SELECT id FROM [format_table_name("poll_question")] + WHERE (adminonly = 0 OR :isadmin = 1) + AND Now() BETWEEN starttime AND endtime + AND deleted = 0 + AND id NOT IN ( + SELECT pollid FROM [format_table_name("poll_vote")] + WHERE ckey = :ckey + AND deleted = 0 + ) + AND id NOT IN ( + SELECT pollid FROM [format_table_name("poll_textreply")] + WHERE ckey = :ckey + AND deleted = 0 + ) + "}, list("isadmin" = isadmin, "ckey" = ckey)) + var/rs = REF(src) + if(!query_get_new_polls.Execute()) + qdel(query_get_new_polls) + return + if(query_get_new_polls.NextRow()) + output += "

                Show Player Polls (NEW!)

                " + else + output += "

                Show Player Polls

                " + qdel(query_get_new_polls) + if(QDELETED(src)) + return + + output += "
                " + + //src << browse(output,"window=playersetup;size=210x240;can_close=0") + var/datum/browser/popup = new(src, "playersetup", "
                New Player Options
                ", 250, 265) + popup.set_window_options("can_close=0") + popup.set_content(output) + popup.open(FALSE) + +/mob/dead/new_player/Topic(href, href_list[]) + if(src != usr) + return 0 + + if(!client) + return 0 + + //Determines Relevent Population Cap + var/relevant_cap + var/hpc = CONFIG_GET(number/hard_popcap) + var/epc = CONFIG_GET(number/extreme_popcap) + if(hpc && epc) + relevant_cap = min(hpc, epc) + else + relevant_cap = max(hpc, epc) + + if(href_list["show_preferences"]) + client.prefs.ShowChoices(src) + return 1 + + if(href_list["ready"]) + var/tready = text2num(href_list["ready"]) + //Avoid updating ready if we're after PREGAME (they should use latejoin instead) + //This is likely not an actual issue but I don't have time to prove that this + //no longer is required + if(SSticker.current_state <= GAME_STATE_PREGAME) + ready = tready + //if it's post initialisation and they're trying to observe we do the needful + if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE) + ready = tready + make_me_an_observer() + return + + if(href_list["refresh"]) + src << browse(null, "window=playersetup") //closes the player setup window + new_player_panel() + + if(href_list["late_join"]) + if(!SSticker?.IsRoundInProgress()) + to_chat(usr, "The round is either not ready, or has already finished...") + return + + if(href_list["late_join"] == "override") + LateChoices() + return + + if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) + to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)]") + + var/queue_position = SSticker.queued_players.Find(usr) + if(queue_position == 1) + to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up.") + else if(queue_position) + to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game.") + else + SSticker.queued_players += usr + to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].") + return + LateChoices() + + if(href_list["manifest"]) + ViewManifest() + + if(href_list["SelectedJob"]) + if(!SSticker?.IsRoundInProgress()) + to_chat(usr, "The round is either not ready, or has already finished...") + return + + if(!GLOB.enter_allowed) + to_chat(usr, "There is an administrative lock on entering the game!") + return + + if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) + if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) + to_chat(usr, "Server is full.") + return + + AttemptLateSpawn(href_list["SelectedJob"]) + return + + else if(!href_list["late_join"]) + new_player_panel() + + if(href_list["showpoll"]) + handle_player_polling() + return + + if(href_list["viewpoll"]) + var/datum/poll_question/poll = locate(href_list["viewpoll"]) in GLOB.polls + poll_player(poll) + + if(href_list["votepollref"]) + var/datum/poll_question/poll = locate(href_list["votepollref"]) in GLOB.polls + vote_on_poll_handler(poll, href_list) + +//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT) +/mob/dead/new_player/proc/make_me_an_observer() + if(QDELETED(src) || !src.client) + ready = PLAYER_NOT_READY + return FALSE + + var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No") + + if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes") + ready = PLAYER_NOT_READY + src << browse(null, "window=playersetup") //closes the player setup window + new_player_panel() + return FALSE + + var/mob/dead/observer/observer = new() + spawning = TRUE + + observer.started_as_observer = TRUE + close_spawn_windows() + var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list + to_chat(src, "Now teleporting.") + if (O) + observer.forceMove(O.loc) + else + to_chat(src, "Teleporting failed. Ahelp an admin please") + stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised") + observer.key = key + observer.client = client + observer.set_ghost_appearance() + if(observer.client && observer.client.prefs) + observer.real_name = observer.client.prefs.real_name + observer.name = observer.real_name + observer.update_icon() + observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) + QDEL_NULL(mind) + qdel(src) + return TRUE + +/proc/get_job_unavailable_error_message(retval, jobtitle) + switch(retval) + if(JOB_AVAILABLE) + return "[jobtitle] is available." + if(JOB_UNAVAILABLE_GENERIC) + return "[jobtitle] is unavailable." + if(JOB_UNAVAILABLE_BANNED) + return "You are currently banned from [jobtitle]." + if(JOB_UNAVAILABLE_PLAYTIME) + return "You do not have enough relevant playtime for [jobtitle]." + if(JOB_UNAVAILABLE_ACCOUNTAGE) + return "Your account is not old enough for [jobtitle]." + if(JOB_UNAVAILABLE_SLOTFULL) + return "[jobtitle] is already filled to capacity." + return "Error: Unknown job availability." + +/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) + var/datum/job/job = SSjob.GetJob(rank) + if(!job) + return JOB_UNAVAILABLE_GENERIC + if((job.current_positions >= job.total_positions) && job.total_positions != -1) + if(job.title == "Assistant") + if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants + return JOB_AVAILABLE + for(var/datum/job/J in SSjob.occupations) + if(J && J.current_positions < J.total_positions && J.title != job.title) + return JOB_UNAVAILABLE_SLOTFULL + else + return JOB_UNAVAILABLE_SLOTFULL + if(is_banned_from(ckey, rank)) + return JOB_UNAVAILABLE_BANNED + if(QDELETED(src)) + return JOB_UNAVAILABLE_GENERIC + if(!job.player_old_enough(client)) + return JOB_UNAVAILABLE_ACCOUNTAGE + if(job.required_playtime_remaining(client)) + return JOB_UNAVAILABLE_PLAYTIME + if(latejoin && !job.special_check_latejoin(client)) + return JOB_UNAVAILABLE_GENERIC + return JOB_AVAILABLE + +/mob/dead/new_player/proc/AttemptLateSpawn(rank) + var/error = IsJobUnavailable(rank) + if(error != JOB_AVAILABLE) + alert(src, get_job_unavailable_error_message(error, rank)) + return FALSE + + if(SSticker.late_join_disabled) + alert(src, "An administrator has disabled late join spawning.") + return FALSE + + var/arrivals_docked = TRUE + if(SSshuttle.arrivals) + close_spawn_windows() //In case we get held up + if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) + src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") + return FALSE + + if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) + SSshuttle.arrivals.RequireUndocked(src) + arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL + + //Remove the player from the join queue if he was in one and reset the timer + SSticker.queued_players -= src + SSticker.queue_delay = 4 + + SSjob.AssignRole(src, rank, 1) + + var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind + var/equip = SSjob.EquipRank(character, rank, TRUE) + if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. + character = equip + + var/datum/job/job = SSjob.GetJob(rank) + + if(job && !job.override_latejoin_spawn(character)) + SSjob.SendToLateJoin(character) + if(!arrivals_docked) + var/obj/screen/splash/Spl = new(character.client, TRUE) + Spl.Fade(TRUE) + character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) + + character.update_parallax_teleport() + + SSticker.minds += character.mind + + var/mob/living/carbon/human/humanc + if(ishuman(character)) + humanc = character //Let's retypecast the var to be human, + + if(humanc) //These procs all expect humans + GLOB.data_core.manifest_inject(humanc) + if(SSshuttle.arrivals) + SSshuttle.arrivals.QueueAnnounce(humanc, rank) + else + AnnounceArrival(humanc, rank) + AddEmploymentContract(humanc) + if(GLOB.highlander) + to_chat(humanc, "THERE CAN BE ONLY ONE!!!") + humanc.make_scottish() + + if(GLOB.summon_guns_triggered) + give_guns(humanc) + if(GLOB.summon_magic_triggered) + give_magic(humanc) + if(GLOB.curse_of_madness_triggered) + give_madness(humanc, GLOB.curse_of_madness_triggered) + + GLOB.joined_player_list += character.ckey + + if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_RECALL, SHUTTLE_IDLE) + SSticker.mode.make_antag_chance(humanc) + if(SHUTTLE_CALL) + if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) + SSticker.mode.make_antag_chance(humanc) + + if(humanc && CONFIG_GET(flag/roundstart_traits)) + SSquirks.AssignQuirks(humanc, humanc.client, TRUE) + + log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) + +/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) + //TODO: figure out a way to exclude wizards/nukeops/demons from this. + for(var/C in GLOB.employmentCabinets) + var/obj/structure/filingcabinet/employment/employmentCabinet = C + if(!employmentCabinet.virgin) + employmentCabinet.addFile(employee) + + +/mob/dead/new_player/proc/LateChoices() + var/list/dat = list("
                Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
                ") + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_ESCAPE) + dat += "
                The station has been evacuated.

                " + if(SHUTTLE_CALL) + if(!SSshuttle.canRecall()) + dat += "
                The station is currently undergoing evacuation procedures.

                " + for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) + if(prioritized_job.current_positions >= prioritized_job.total_positions) + SSjob.prioritized_jobs -= prioritized_job + dat += "
                " + var/column_counter = 0 + // render each category's available jobs + for(var/category in GLOB.position_categories) + // position_categories contains category names mapped to available jobs and an appropriate color + var/cat_color = GLOB.position_categories[category]["color"] + dat += "
                " + dat += "[category]" + var/list/dept_dat = list() + for(var/job in GLOB.position_categories[category]["jobs"]) + var/datum/job/job_datum = SSjob.name_occupations[job] + if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE) + var/command_bold = "" + if(job in GLOB.command_positions) + command_bold = " command" + if(job_datum in SSjob.prioritized_jobs) + dept_dat += "[job_datum.title] ([job_datum.current_positions])" + else + dept_dat += "[job_datum.title] ([job_datum.current_positions])" + if(!dept_dat.len) + dept_dat += "No positions open." + dat += jointext(dept_dat, "") + dat += "

                " + column_counter++ + if(column_counter > 0 && (column_counter % 3 == 0)) + dat += "
                " + dat += "
                " + dat += "" + var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) + popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') + popup.set_content(jointext(dat, "")) + popup.open(FALSE) // 0 is passed to open so that it doesn't use the onclose() proc + +/mob/dead/new_player/proc/create_character(transfer_after) + spawning = 1 + close_spawn_windows() + + var/mob/living/carbon/human/H = new(loc) + + var/frn = CONFIG_GET(flag/force_random_names) + var/admin_anon_names = SSticker.anonymousnames + if(!frn) + frn = is_banned_from(ckey, "Appearance") + if(QDELETED(src)) + return + if(frn) + client.prefs.random_character() + client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) + + if(admin_anon_names)//overrides random name because it achieves the same effect and is an admin enabled event tool + client.prefs.random_character() + client.prefs.real_name = anonymous_name(src) + + var/is_antag + if(mind in GLOB.pre_setup_antags) + is_antag = TRUE + + client.prefs.copy_to(H, antagonist = is_antag, is_latejoiner = transfer_after) + var/cur_scar_index = client.prefs.scars_index + if(client.prefs.persistent_scars && client.prefs.scars_list["[cur_scar_index]"]) + var/scar_string = client.prefs.scars_list["[cur_scar_index]"] + var/valid_scars = "" + for(var/scar_line in splittext(scar_string, ";")) + if(H.load_scar(scar_line)) + valid_scars += "[scar_line];" + + client.prefs.scars_list["[cur_scar_index]"] = valid_scars + client.prefs.save_character() + + client.prefs.copy_to(H, antagonist = is_antag) + H.dna.update_dna_identity() + if(mind) + if(transfer_after) + mind.late_joiner = TRUE + mind.active = 0 //we wish to transfer the key manually + mind.transfer_to(H) //won't transfer key since the mind is not active + mind.original_character = H + + H.name = real_name + + . = H + new_character = . + if(transfer_after) + transfer_character() + +/mob/dead/new_player/proc/transfer_character() + . = new_character + if(.) + new_character.key = key //Manually transfer the key to log them in, + new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC) + new_character = null + qdel(src) + +/mob/dead/new_player/proc/ViewManifest() + if(!client) + return + if(world.time < client.crew_manifest_delay) + return + client.crew_manifest_delay = world.time + (1 SECONDS) + + var/dat = "" + dat += "

                Crew Manifest

                " + dat += GLOB.data_core.get_manifest_html() + + src << browse(dat, "window=manifest;size=387x420;can_close=1") + +/mob/dead/new_player/Move() + return 0 + + +/mob/dead/new_player/proc/close_spawn_windows() + + src << browse(null, "window=latechoices") //closes late choices window + src << browse(null, "window=playersetup") //closes the player setup window + src << browse(null, "window=preferences") //closes job selection + src << browse(null, "window=mob_occupation") + src << browse(null, "window=latechoices") //closes late job selection + +// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. +// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled +// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" +// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role +// This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong +/mob/dead/new_player/proc/check_preferences() + if(!client) + return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. + if(client.prefs.joblessrole != RETURNTOLOBBY) + return TRUE + // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. + var/has_antags = FALSE + if(client.prefs.be_special.len > 0) + has_antags = TRUE + if(client.prefs.job_preferences.len == 0) + if(!ineligible_for_roles) + to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") + ineligible_for_roles = TRUE + ready = PLAYER_NOT_READY + if(has_antags) + log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") + message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") + + return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well + return TRUE diff --git a/code/modules/mob/dead/new_player/poll.dm b/code/modules/mob/dead/new_player/poll.dm index 173282282a9..231e3deafea 100644 --- a/code/modules/mob/dead/new_player/poll.dm +++ b/code/modules/mob/dead/new_player/poll.dm @@ -1,569 +1,569 @@ -/** - * Shows a list of currently running polls a player can vote/has voted on - * - */ -/mob/dead/new_player/proc/handle_player_polling() - var/list/output = list("
                Player polls
                ") - var/rs = REF(src) - for(var/p in GLOB.polls) - var/datum/poll_question/poll = p - if((poll.admin_only && !client.holder) || poll.future_poll) - continue - output += "" - output += "
                [poll.question]
                " - src << browse(jointext(output, ""),"window=playerpolllist;size=500x300") - -/** - * Redirects a player to the correct poll window based on poll type. - * - */ -/mob/dead/new_player/proc/poll_player(datum/poll_question/poll) - if(!poll) - return - if(!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return - switch(poll.poll_type) - if(POLLTYPE_OPTION) - poll_player_option(poll) - if(POLLTYPE_TEXT) - poll_player_text(poll) - if(POLLTYPE_RATING) - poll_player_rating(poll) - if(POLLTYPE_MULTI) - poll_player_multi(poll) - if(POLLTYPE_IRV) - poll_player_irv(poll) - -/** - * Shows voting window for an option type poll, listing its options and relevant details. - * - * If already voted on, the option a player voted for is pre-selected. - * - */ -/mob/dead/new_player/proc/poll_player_option(datum/poll_question/poll) - var/datum/db_query/query_option_get_voted = SSdbcore.NewQuery({" - SELECT optionid FROM [format_table_name("poll_vote")] - WHERE pollid = :pollid AND ckey = :ckey AND deleted = 0 - "}, list("pollid" = poll.poll_id, "ckey" = ckey)) - if(!query_option_get_voted.warn_execute()) - qdel(query_option_get_voted) - return - var/voted_option_id = 0 - if(query_option_get_voted.NextRow()) - voted_option_id = text2num(query_option_get_voted.item[1]) - qdel(query_option_get_voted) - var/list/output = list("
                Player poll
                Question: [poll.question]
                ") - if(poll.subtitle) - output += "[poll.subtitle]
                " - output += "Poll runs from [poll.start_datetime] until [poll.end_datetime]
                " - if(poll.allow_revoting) - output += "Revoting is enabled." - if(!voted_option_id || poll.allow_revoting) - output += {"
                - - - "} - output += "
                " - for(var/o in poll.options) - var/datum/poll_option/option = o - output += "